TF-3454 Download all attachments as ZIP

This commit is contained in:
DatDang
2025-02-07 14:38:53 +07:00
committed by Dat H. Pham
parent 636df94458
commit f66e1460e4
34 changed files with 1175 additions and 100 deletions
@@ -13,9 +13,11 @@ import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:http_parser/http_parser.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
@@ -55,8 +57,10 @@ import 'package:tmail_ui_user/features/email/domain/state/calendar_event_accept_
import 'package:tmail_ui_user/features/email/domain/state/calendar_event_maybe_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/calendar_event_reject_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/calendar_event_reply_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_all_attachments_for_web_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachments_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/export_all_attachments_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/export_attachment_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart';
@@ -72,6 +76,8 @@ import 'package:tmail_ui_user/features/email/domain/state/send_receipt_to_sender
import 'package:tmail_ui_user/features/email/domain/state/store_event_attendance_status_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_accept_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/maybe_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_reject_interactor.dart';
@@ -109,6 +115,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/html_attachmen
import 'package:tmail_ui_user/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart';
import 'package:tmail_ui_user/features/email_previewer/email_previewer_dialog_view.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
@@ -157,6 +164,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final ParseEmailByBlobIdInteractor _parseEmailByBlobIdInteractor;
final PreviewEmailFromEmlFileInteractor _previewEmailFromEmlFileInteractor;
final GetHtmlContentFromAttachmentInteractor _getHtmlContentFromAttachmentInteractor;
final DownloadAllAttachmentsForWebInteractor _downloadAllAttachmentsForWebInteractor;
final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor;
@@ -213,6 +222,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
this._parseEmailByBlobIdInteractor,
this._previewEmailFromEmlFileInteractor,
this._getHtmlContentFromAttachmentInteractor,
this._downloadAllAttachmentsForWebInteractor,
this._exportAllAttachmentsInteractor,
);
@override
@@ -240,6 +251,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_handleMarkAsEmailReadCompleted(success.readActions);
} else if (success is ExportAttachmentSuccess) {
_exportAttachmentSuccessAction(success);
} else if (success is ExportAllAttachmentsSuccess) {
_exportAllAttachmentsSuccessAction(success);
} else if (success is MoveToMailboxSuccess) {
_moveToMailboxSuccess(success);
} else if (success is MarkAsStarEmailSuccess) {
@@ -250,6 +263,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_updateAttachmentsViewState(success.attachment.blobId, Right(success));
} else if (success is DownloadingAttachmentForWeb) {
_updateAttachmentsViewState(success.attachment.blobId, Right(success));
} else if (success is DownloadAllAttachmentsForWebSuccess) {
mailboxDashBoardController.deleteDownloadTask(success.taskId);
} else if (success is GetAllIdentitiesSuccess) {
_getAllIdentitiesSuccess(success);
} else if (success is SendReceiptToSenderSuccess) {
@@ -298,8 +313,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_downloadAttachmentsFailure(failure);
} else if (failure is ExportAttachmentFailure) {
_exportAttachmentFailureAction(failure);
} else if (failure is ExportAllAttachmentsFailure) {
_exportAllAttachmentsFailureAction(failure);
} else if (failure is DownloadAttachmentForWebFailure) {
_downloadAttachmentForWebFailureAction(failure);
} else if (failure is DownloadAllAttachmentsForWebFailure) {
_downloadAllAttachmentsForWebFailure(failure);
} else if (failure is ParseCalendarEventFailure) {
_handleParseCalendarEventFailure(failure);
} else if (failure is GetEmailContentFailure) {
@@ -445,15 +464,44 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
emailSupervisorController.mailboxDashBoardController.updateDownloadTask(
success.taskId,
(currentTask) {
final newTask = currentTask.copyWith(
progress: success.progress,
downloaded: success.downloaded,
total: success.total);
success.taskId,
(currentTask) {
final newTask = currentTask.copyWith(
progress: success.progress,
downloaded: success.downloaded,
total: success.total);
return newTask;
});
return newTask;
});
} else if (success is StartDownloadAllAttachmentsForWeb) {
emailSupervisorController.mailboxDashBoardController.addDownloadTask(
DownloadTaskState(
taskId: success.taskId,
attachment: success.attachment,
),
);
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).your_download_has_started,
leadingSVGIconColor: AppColor.primaryColor,
leadingSVGIcon: imagePaths.icDownload);
}
} else if (success is DownloadingAllAttachmentsForWeb) {
final percent = success.progress.round();
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
emailSupervisorController.mailboxDashBoardController.updateDownloadTask(
success.taskId,
(currentTask) {
final newTask = currentTask.copyWith(
progress: success.progress,
downloaded: success.downloaded,
total: success.total);
return newTask;
});
}
});
});
@@ -804,11 +852,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void exportAttachment(BuildContext context, Attachment attachment) {
final cancelToken = CancelToken();
_showDownloadingFileDialog(context, attachment, cancelToken: cancelToken);
_showDownloadingFileDialog(context, attachment.name ?? '', cancelToken: cancelToken);
_exportAttachmentAction(attachment, cancelToken);
}
void _showDownloadingFileDialog(BuildContext context, Attachment attachment, {CancelToken? cancelToken}) {
void _showDownloadingFileDialog(BuildContext context, String attachmentName, {CancelToken? cancelToken}) {
if (cancelToken != null) {
showCupertinoDialog(
context: context,
@@ -816,7 +864,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
PointerInterceptor(child: (DownloadingFileDialogBuilder()
..key(const Key('downloading_file_dialog'))
..title(AppLocalizations.of(context).preparing_to_export)
..content(AppLocalizations.of(context).downloading_file(attachment.name ?? ''))
..content(AppLocalizations.of(context).downloading_file(attachmentName))
..actionText(AppLocalizations.of(context).cancel)
..addCancelDownloadActionClick(() {
cancelToken.cancel([AppLocalizations.of(context).user_cancel_download_file]);
@@ -830,7 +878,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
PointerInterceptor(child: (DownloadingFileDialogBuilder()
..key(const Key('downloading_file_for_web_dialog'))
..title(AppLocalizations.of(context).preparing_to_save)
..content(AppLocalizations.of(context).downloading_file(attachment.name ?? '')))
..content(AppLocalizations.of(context).downloading_file(attachmentName)))
.build()));
}
}
@@ -854,6 +902,36 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void exportAllAttachments(BuildContext context, String outputFileName) {
final cancelToken = CancelToken();
_showDownloadingFileDialog(context, outputFileName, cancelToken: cancelToken);
_exportAllAttachmentsAction(outputFileName, cancelToken);
}
void _exportAllAttachmentsAction(String outputFileName, CancelToken cancelToken) {
if (accountId == null || session == null) {
consumeState(Stream.value(Left(ExportAllAttachmentsFailure(
exception: NotFoundSessionException(),
))));
return;
}
final downloadAllSupported = session!.isDownloadAllSupported(accountId);
if (!downloadAllSupported || _currentEmailId == null) {
consumeState(Stream.value(Left(ExportAllAttachmentsFailure())));
return;
}
final baseDownloadAllUrl = session!.getDownloadAllCapability(accountId)!.endpoint!;
consumeState(_exportAllAttachmentsInteractor.execute(
accountId!,
_currentEmailId!,
baseDownloadAllUrl,
outputFileName,
cancelToken: cancelToken,
));
}
void _exportAttachmentFailureAction(ExportAttachmentFailure failure) {
if (failure.exception is! CancelDownloadFileException) {
popBack();
@@ -871,6 +949,23 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_openDownloadedPreviewWorkGroupDocument(success.downloadedResponse);
}
void _exportAllAttachmentsFailureAction(ExportAllAttachmentsFailure failure) {
if (failure.exception is! CancelDownloadFileException) {
popBack();
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).attachment_download_failed);
}
}
}
void _exportAllAttachmentsSuccessAction(ExportAllAttachmentsSuccess success) {
popBack();
_saveDownloadedZipAttachments(success.downloadedResponse.filePath);
}
void _openDownloadedPreviewWorkGroupDocument(DownloadedResponse downloadedResponse) async {
log('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): $downloadedResponse');
if (downloadedResponse.mediaType == null) {
@@ -892,6 +987,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
Future<void> _saveDownloadedZipAttachments(String filePath) async {
final params = SaveFileDialogParams(sourceFilePath: filePath);
await FlutterFileDialog.saveFile(params: params);
}
void downloadAttachmentForWeb(Attachment attachment) {
if (accountId != null && session != null) {
final generateTaskId = DownloadTaskId(uuid.v4());
@@ -921,6 +1021,65 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void downloadAllAttachmentsForWeb(String outputFileName) {
final taskId = DownloadTaskId(uuid.v4());
final session = this.session;
final accountId = this.accountId;
if (accountId == null || session == null) {
consumeState(Stream.value(Left(DownloadAllAttachmentsForWebFailure(
exception: NotFoundSessionException(),
taskId: taskId,
))));
return;
}
final downloadAllSupported = session.isDownloadAllSupported(accountId);
final emailId = _currentEmailId;
if (!downloadAllSupported || emailId == null) {
consumeState(Stream.value(Left(DownloadAllAttachmentsForWebFailure(
taskId: taskId,
))));
return;
}
final baseDownloadAllUrl = session.getDownloadAllCapability(accountId)!.endpoint!;
final downloadAttachment = Attachment(
name: outputFileName,
type: MediaType('application', 'zip'),
);
final cancelToken = CancelToken();
consumeState(_downloadAllAttachmentsForWebInteractor.execute(
accountId,
emailId,
baseDownloadAllUrl,
downloadAttachment,
taskId,
_downloadProgressStateController,
cancelToken: cancelToken,
));
}
bool isDownloadAllSupported() {
return session?.isDownloadAllSupported(accountId) ?? false;
}
bool downloadAllButtonIsEnabled() {
return isDownloadAllSupported() && attachments.length > 1;
}
void _downloadAllAttachmentsForWebFailure(
DownloadAllAttachmentsForWebFailure failure,
) {
mailboxDashBoardController.deleteDownloadTask(failure.taskId);
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).attachment_download_failed);
}
}
void _downloadAttachmentForWebSuccessAction(DownloadAttachmentForWebSuccess success) {
log('SingleEmailController::_downloadAttachmentForWebSuccessAction():');
@@ -1675,6 +1834,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
..onCloseButtonAction(() => popBack())
..onDownloadAttachmentFileAction((attachment) => handleDownloadAttachmentAction(context, attachment))
..onViewAttachmentFileAction((attachment) => handleViewAttachmentAction(context, attachment))
..onDownloadAllButtonAction(isDownloadAllSupported()
? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}')
: null
)
).show();
} else {
Get.dialog(
@@ -1688,6 +1851,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
onCloseButtonAction: () => popBack(),
onDownloadAttachmentFileAction: (attachment) => handleDownloadAttachmentAction(context, attachment),
onViewAttachmentFileAction: (attachment) => handleViewAttachmentAction(context, attachment),
onDownloadAllButtonAction: isDownloadAllSupported()
? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}')
: null,
)
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
@@ -1995,6 +2161,19 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void handleDownloadAllAttachmentsAction(
BuildContext context,
String outputFileName,
) {
if (PlatformInfo.isWeb) {
downloadAllAttachmentsForWeb(outputFileName);
} else if (PlatformInfo.isMobile) {
exportAllAttachments(context, outputFileName);
} else {
log('EmailView::handleDownloadAllAttachmentsAction: THE PLATFORM IS SUPPORTED');
}
}
void handleViewAttachmentAction(BuildContext context, Attachment attachment) {
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit && attachment.isPDFFile) {
previewPDFFileAction(context, attachment);