TF-4050 refactor: move attachment preview & download logic to download controller

This commit is contained in:
dab246
2025-10-23 12:14:39 +07:00
committed by Dat H. Pham
parent 1398a30ed6
commit 92286b48e1
33 changed files with 1055 additions and 651 deletions
@@ -2,15 +2,11 @@ import 'dart:async';
import 'dart:io';
import 'package:core/core.dart';
import 'package:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart';
import 'package:core/presentation/utils/html_transformer/text/new_line_transformer.dart';
import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart';
import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart';
import 'package:core/utils/html/html_utils.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -36,12 +32,10 @@ import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_manager.dart';
import 'package:tmail_ui_user/features/base/state/button_state.dart';
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart';
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/send_receipt_to_sender_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/view_entire_message_request.dart';
import 'package:tmail_ui_user/features/email/domain/state/calendar_event_accept_state.dart';
@@ -54,13 +48,9 @@ import 'package:tmail_ui_user/features/email/domain/state/export_all_attachments
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_entire_message_as_document_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/parse_email_by_blob_id_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/preview_pdf_file_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/print_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/send_receipt_to_sender_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart';
@@ -71,13 +61,10 @@ import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachme
import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_entire_message_as_document_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_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/parse_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/send_receipt_to_sender_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
@@ -87,8 +74,8 @@ import 'package:tmail_ui_user/features/email/presentation/bindings/mdn_interacto
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_attendee_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_organizer_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/handle_open_attachment_list_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/handle_mail_action_by_shortcut_action_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/handle_open_attachment_list_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/update_attendance_status_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/blob_calendar_event.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
@@ -97,15 +84,13 @@ import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscrib
import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/html_attachment_previewer.dart';
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_dashboard/presentation/action/download_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_download_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
@@ -133,8 +118,6 @@ import 'package:tmail_ui_user/main/utils/app_utils.dart';
class SingleEmailController extends BaseController with AppLoaderMixin {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final _downloadManager = Get.find<DownloadManager>();
final _printUtils = Get.find<PrintUtils>();
final GetEmailContentInteractor _getEmailContentInteractor;
final MarkAsEmailReadInteractor _markAsEmailReadInteractor;
@@ -143,9 +126,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final GetAllIdentitiesInteractor _getAllIdentitiesInteractor;
final StoreOpenedEmailInteractor _storeOpenedEmailInteractor;
final PrintEmailInteractor _printEmailInteractor;
final ParseEmailByBlobIdInteractor _parseEmailByBlobIdInteractor;
final PreviewEmailFromEmlFileInteractor _previewEmailFromEmlFileInteractor;
final GetHtmlContentFromAttachmentInteractor _getHtmlContentFromAttachmentInteractor;
final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor;
final EmailId? _currentEmailId;
@@ -218,9 +198,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
this._getAllIdentitiesInteractor,
this._storeOpenedEmailInteractor,
this._printEmailInteractor,
this._parseEmailByBlobIdInteractor,
this._previewEmailFromEmlFileInteractor,
this._getHtmlContentFromAttachmentInteractor,
this._exportAllAttachmentsInteractor, {
EmailId? currentEmailId,
}) : _currentEmailId = currentEmailId;
@@ -281,24 +258,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_handlePrintEmailSuccess(success);
} else if (success is CalendarEventReplySuccess) {
calendarEventSuccess(success);
} else if (success is ParseEmailByBlobIdSuccess) {
_handleParseEmailByBlobIdSuccess(success);
} else if (success is PreviewEmailFromEmlFileSuccess) {
_handlePreviewEmailFromEMLFileSuccess(success);
} else if (success is GetHtmlContentFromAttachmentSuccess) {
_updateAttachmentsViewState(success.attachment.blobId, Right(success));
Get.dialog(HtmlAttachmentPreviewer(
title: success.htmlAttachmentTitle,
htmlContent: success.sanitizedHtmlContent,
mailToClicked: openMailToLink,
downloadAttachmentClicked: () {
if (currentContext == null || attachments.isEmpty) return;
handleDownloadAttachmentAction(success.attachment);
},
responsiveUtils: responsiveUtils,
));
} else if (success is GettingHtmlContentFromAttachment) {
_updateAttachmentsViewState(success.attachment.blobId, Right(success));
} else {
super.handleSuccessViewState(success);
}
@@ -320,14 +279,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_showMessageWhenEmailPrintingFailed(failure);
} else if (failure is CalendarEventReplyFailure) {
_calendarEventFailure(failure);
} else if (failure is ParseEmailByBlobIdFailure) {
_handleParseEmailByBlobIdFailure(failure);
} else if (failure is PreviewEmailFromEmlFileFailure) {
_handlePreviewEmailFromEMLFileFailure(failure);
} else if (failure is GetHtmlContentFromAttachmentFailure) {
_handleGetHtmlContentFromAttachmentFailure(failure);
} else if (failure is PreviewPDFFileFailure) {
_handlePreviewPDFFileFailure(failure);
} else {
super.handleFailureViewState(failure);
}
@@ -427,7 +378,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
mailboxDashBoardController.downloadController.downloadUIAction,
(action) {
if (action is UpdateAttachmentsViewStateAction) {
_updateAttachmentsViewState(action.blobId, Right(action.success));
_updateAttachmentsViewState(action.blobId, action.viewState);
}
},
));
@@ -921,20 +872,13 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
return isDownloadAllSupported() && attachments.length > 1;
}
void _downloadPDFFile(Uint8List bytes, String fileName) {
_downloadManager.createAnchorElementDownloadFileWeb(bytes, fileName);
}
void _printPDFFile(Uint8List bytes, String fileName) async {
await _printUtils.printPDFFile(bytes, fileName);
}
void _updateAttachmentsViewState(
Id? attachmentBlobId,
Either<Failure, Success> viewState) {
if (attachmentBlobId != null) {
attachmentsViewState[attachmentBlobId] = viewState;
}
Either<Failure, Success> viewState,
) {
if (attachmentBlobId != null) {
attachmentsViewState[attachmentBlobId] = viewState;
}
}
void moveToMailbox(PresentationEmail email) async {
@@ -1156,23 +1100,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
Future<void> openMailToLink(Uri? uri) async {
if (uri == null) return;
final navigationRouter = RouteUtils.generateNavigationRouterFromMailtoLink(uri.toString());
log('SingleEmailController::openMailToLink(): ${uri.toString()}');
if (!RouteUtils.canOpenComposerFromNavigationRouter(navigationRouter)) return;
mailboxDashBoardController.openComposer(
ComposerArguments.fromMailtoUri(
listEmailAddress: navigationRouter.listEmailAddress,
cc: navigationRouter.cc,
bcc: navigationRouter.bcc,
subject: navigationRouter.subject,
body: navigationRouter.body
)
);
}
Future<void> openMailToLink(Uri? uri) =>
mailboxDashBoardController.openMailToLink(uri);
void deleteEmailPermanently(PresentationEmail email) {
emailActionReactor.deleteEmailPermanently(
@@ -1697,7 +1626,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} else if (PlatformInfo.isMobile) {
exportAttachment(attachment);
} else {
log('EmailView::handleDownloadAttachmentAction: THE PLATFORM IS SUPPORTED');
log('$runtimeType::handleDownloadAttachmentAction: THE PLATFORM IS SUPPORTED');
}
}
@@ -1715,258 +1644,18 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void handleViewAttachmentAction(BuildContext context, Attachment attachment) {
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit && attachment.isPDFFile) {
previewPDFFileAction(context, attachment);
} else if (attachment.isEMLFile) {
previewEMLFileAction(attachment.blobId, AppLocalizations.of(context));
} else if (attachment.isHTMLFile) {
final attachmentEvaluation = evaluateAttachment(attachment);
if (!attachmentEvaluation.canDownloadAttachment) {
consumeState(Stream.value(Left(GetHtmlContentFromAttachmentFailure(
exception: null,
attachment: attachment,
))));
return;
}
consumeState(_getHtmlContentFromAttachmentInteractor.execute(
attachmentEvaluation.accountId!,
attachment,
DownloadTaskId(attachmentEvaluation.blobId!.value),
attachmentEvaluation.downloadUrl!,
TransformConfiguration.create(
customDomTransformers: [SanitizeHyperLinkTagInHtmlTransformer()],
customTextTransformers: [const StandardizeHtmlSanitizingTransformers()],
),
));
} else {
handleDownloadAttachmentAction(
attachment,
previewerSupported: attachmentPreviewSupported(attachment),
);
}
}
bool attachmentPreviewSupported(Attachment attachment) {
return attachment.isImage || attachment.isText || attachment.isJson;
}
({
bool canDownloadAttachment,
AccountId? accountId,
String? downloadUrl,
Id? blobId,
}) evaluateAttachment(
Attachment attachment,
) {
final accountId = mailboxDashBoardController.accountId.value;
dynamic downloadUrl;
try {
downloadUrl = mailboxDashBoardController.sessionCurrent
?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
} catch (e) {
logError('SingleEmailController::_getEmailContentAction(): $e');
downloadUrl = null;
}
final blobId = attachment.blobId;
if (accountId == null || downloadUrl == null || blobId == null) {
return (
canDownloadAttachment: false,
accountId: accountId,
downloadUrl: downloadUrl,
blobId: blobId,
);
}
return (
canDownloadAttachment: true,
accountId: accountId,
downloadUrl: downloadUrl,
blobId: blobId,
);
}
Future<void> previewPDFFileAction(BuildContext context, Attachment attachment) async {
if (accountId == null || session == null) {
appToast.showToastErrorMessage(
context,
AppLocalizations.of(context).noPreviewAvailable);
return;
}
try {
final downloadUrl = session!.getDownloadUrl(
jmapUrl: dynamicUrlInterceptors.jmapUrl,
);
await Get.generalDialog(
barrierColor: Colors.black.withValues(alpha: 0.8),
pageBuilder: (_, __, ___) {
return PointerInterceptor(
child: PDFViewer(
attachment: attachment,
accountId: accountId!,
downloadUrl: downloadUrl,
downloadAction: _downloadPDFFile,
printAction: _printPDFFile,
)
);
},
);
} catch (e) {
logError('SingleEmailController::previewPDFFileAction(): $e');
consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(e))));
}
}
void previewEMLFileAction(Id? blobId, AppLocalizations appLocalizations) {
SmartDialog.showLoading(
msg: appLocalizations.loadingPleaseWait,
maskColor: Colors.black38,
);
if (accountId == null) {
consumeState(Stream.value(Left(ParseEmailByBlobIdFailure(NotFoundAccountIdException()))));
return;
}
if (blobId == null) {
consumeState(Stream.value(Left(ParseEmailByBlobIdFailure(NotFoundBlobIdException([])))));
return;
}
consumeState(_parseEmailByBlobIdInteractor.execute(accountId!, blobId));
}
void _handleParseEmailByBlobIdSuccess(ParseEmailByBlobIdSuccess success) {
if (session == null) {
consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(NotFoundSessionException()))));
return;
}
if (accountId == null) {
consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(NotFoundAccountIdException()))));
return;
}
if (currentContext == null) {
consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(NotFoundContextException()))));
return;
}
try {
consumeState(_previewEmailFromEmlFileInteractor.execute(
PreviewEmailEMLRequest(
accountId: accountId!,
ownEmailAddress: ownEmailAddress,
blobId: success.blobId,
email: success.email,
locale: Localizations.localeOf(currentContext!),
appLocalizations: AppLocalizations.of(currentContext!),
baseDownloadUrl: session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
),
));
} catch (e) {
logError('SingleEmailController::_handleParseEmailByBlobIdSuccess(): $e');
consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(e))));
}
}
void _handleParseEmailByBlobIdFailure(ParseEmailByBlobIdFailure failure) {
SmartDialog.dismiss();
toastManager.showMessageFailure(failure);
}
void _handlePreviewPDFFileFailure(PreviewPDFFileFailure failure) {
SmartDialog.dismiss();
toastManager.showMessageFailure(failure);
}
void _handlePreviewEmailFromEMLFileFailure(PreviewEmailFromEmlFileFailure failure) {
SmartDialog.dismiss();
toastManager.showMessageFailure(failure);
}
void _handlePreviewEmailFromEMLFileSuccess(PreviewEmailFromEmlFileSuccess success) {
SmartDialog.dismiss();
if (PlatformInfo.isWeb) {
bool isOpen = HtmlUtils.openNewWindowByUrl(
RouteUtils.createUrlWebLocationBar(
AppRoutes.emailEMLPreviewer,
previewId: success.emlPreviewer.id
).toString(),
);
if (!isOpen) {
toastManager.showMessageFailure(PreviewEmailFromEmlFileFailure(CannotOpenNewWindowException()));
}
} else if (PlatformInfo.isMobile) {
if (currentContext == null) {
toastManager.showMessageFailure(PreviewEmailFromEmlFileFailure(NotFoundContextException()));
return;
}
if (PlatformInfo.isAndroid) {
showModalSheetToPreviewEMLAttachment(
currentContext!,
success.emlPreviewer,
);
} if (PlatformInfo.isIOS) {
showDialogToPreviewEMLAttachment(
currentContext!,
success.emlPreviewer,
);
}
}
}
void showModalSheetToPreviewEMLAttachment(
BuildContext context,
EMLPreviewer emlPreviewer,
) {
showModalBottomSheet(
mailboxDashBoardController.previewAttachmentAction(
context: context,
showDragHandle: true,
useSafeArea: true,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
builder: (_) {
return DraggableScrollableSheet(
initialChildSize: 1.0,
builder: (context, ___) => EmailPreviewerDialogView(
emlPreviewer: emlPreviewer,
imagePaths: imagePaths,
onMailtoDelegateAction: openMailToLink,
onPreviewEMLDelegateAction: (uri) => _openEMLPreviewer(context, uri),
onDownloadAttachmentDelegateAction: (uri) =>
_downloadAttachmentInEMLPreview(uri),
),
attachment: attachment,
onDownloadAttachment: (attachment) {
handleDownloadAttachmentAction(
attachment,
previewerSupported: attachment.isPreviewSupported,
);
},
);
}
void showDialogToPreviewEMLAttachment(BuildContext context, EMLPreviewer emlPreviewer) {
Get.dialog(
EmailPreviewerDialogView(
emlPreviewer: emlPreviewer,
imagePaths: imagePaths,
onMailtoDelegateAction: openMailToLink,
onPreviewEMLDelegateAction: (uri) => _openEMLPreviewer(context, uri),
onDownloadAttachmentDelegateAction: (uri) =>
_downloadAttachmentInEMLPreview(uri),
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
}
Future<void> _openEMLPreviewer(BuildContext context, Uri? uri) async {
log('SingleEmailController::_openEMLPreviewer:uri = $uri');
if (uri == null) return;
@@ -1975,7 +1664,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
log('SingleEmailController::_openEMLPreviewer:blobId = $blobId');
if (blobId.isEmpty) return;
previewEMLFileAction(Id(blobId), AppLocalizations.of(context));
mailboxDashBoardController.previewEMLFileAction(
appLocalizations: AppLocalizations.of(context),
blobId: Id(blobId),
);
}
Future<void> _downloadAttachmentInEMLPreview(Uri? uri) async {
@@ -2009,15 +1701,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
void _handleGetHtmlContentFromAttachmentFailure(GetHtmlContentFromAttachmentFailure failure) {
_updateAttachmentsViewState(failure.attachment.blobId, Left(failure));
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).thisHtmlAttachmentCannotBePreviewed);
}
}
void onHtmlContentClippedAction(bool isClipped) {
log('SingleEmailController::onHtmlContentClippedAction:isClipped = $isClipped');
isEmailContentClipped.value = isClipped;
@@ -2054,13 +1737,17 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
onSuccess: (success) {
SmartDialog.dismiss();
showDialogToPreviewEMLAttachment(
context,
EMLPreviewer(
mailboxDashBoardController.showDialogToPreviewEMLAttachment(
context: context,
emlPreviewer: EMLPreviewer(
id: presentationEmail.id?.asString ?? '',
title: presentationEmail.subject ?? '',
content: success.messageDocument,
),
imagePaths: imagePaths,
onMailtoAction: openMailToLink,
onPreviewAction: (uri) => _openEMLPreviewer(context, uri),
onDownloadAction: _downloadAttachmentInEMLPreview,
);
},
onFailure: (_) {