TF-4050 Preview and download uploaded file in composer on web

This commit is contained in:
dab246
2025-11-07 01:12:20 +07:00
committed by Dat H. Pham
parent ecd3ac0295
commit 972b20ec16
72 changed files with 2438 additions and 1751 deletions
@@ -1,7 +1,5 @@
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart';
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/mark_as_email_read_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart';
@@ -22,12 +20,10 @@ class EmailBindings extends Bindings {
Get.put(SingleEmailController(
Get.find<GetEmailContentInteractor>(),
Get.find<MarkAsEmailReadInteractor>(),
Get.find<ExportAttachmentInteractor>(),
Get.find<MarkAsStarEmailInteractor>(),
Get.find<GetAllIdentitiesInteractor>(),
Get.find<StoreOpenedEmailInteractor>(),
Get.find<PrintEmailInteractor>(),
Get.find<ExportAllAttachmentsInteractor>(),
currentEmailId: currentEmailId,
), tag: tag);
}
@@ -16,8 +16,6 @@ import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart';
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_stored_email_state_interactor.dart';
@@ -26,9 +24,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_
import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart';
@@ -111,23 +106,12 @@ class EmailInteractorBindings extends InteractorsBindings {
void bindingsInteractor() {
Get.lazyPut(() => GetEmailContentInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => ExportAttachmentInteractor(
Get.find<EmailRepository>(),
Get.find<CredentialRepository>(),
Get.find<AccountRepository>(),
Get.find<AuthenticationOIDCRepository>(),
));
Get.lazyPut(() => MoveToMailboxInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MarkAsStarEmailInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => GetStoredEmailStateInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => StoreOpenedEmailInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => PrintEmailInteractor(Get.find<EmailRepository>()));
IdentityInteractorsBindings().dependencies();
Get.lazyPut(() => ExportAllAttachmentsInteractor(
Get.find<EmailRepository>(),
Get.find<AccountRepository>(),
Get.find<AuthenticationOIDCRepository>(),
Get.find<CredentialRepository>()));
if (PlatformInfo.isIOS) {
Get.lazyPut(() => GetEntireMessageAsDocumentInteractor(
Get.find<EmailRepository>(),
@@ -1,13 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:core/core.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:dartz/dartz.dart';
import 'package:dio/dio.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:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
@@ -26,12 +23,12 @@ import 'package:jmap_dart_client/jmap/mdn/disposition.dart';
import 'package:jmap_dart_client/jmap/mdn/mdn.dart';
import 'package:model/error_type_handler/unknown_uri_exception.dart';
import 'package:model/model.dart';
import 'package:open_file/open_file.dart' as open_file;
import 'package:pointer_interceptor/pointer_interceptor.dart';
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/download/domain/model/download_source_view.dart';
import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.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';
@@ -43,9 +40,6 @@ import 'package:tmail_ui_user/features/email/domain/state/calendar_event_counter
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_attachment_for_web_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_entire_message_as_document_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
@@ -57,8 +51,6 @@ import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_stat
import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_accept_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_counter_accept_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_reject_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/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/mark_as_email_read_interactor.dart';
@@ -71,7 +63,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/calendar_event_interactor_bindings.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/mdn_interactor_bindings.dart';
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_mail_action_by_shortcut_action_extension.dart';
@@ -84,12 +75,11 @@ 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/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_download_attachment_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';
@@ -121,12 +111,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final GetEmailContentInteractor _getEmailContentInteractor;
final MarkAsEmailReadInteractor _markAsEmailReadInteractor;
final ExportAttachmentInteractor _exportAttachmentInteractor;
final MarkAsStarEmailInteractor _markAsStarEmailInteractor;
final GetAllIdentitiesInteractor _getAllIdentitiesInteractor;
final StoreOpenedEmailInteractor _storeOpenedEmailInteractor;
final PrintEmailInteractor _printEmailInteractor;
final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor;
final EmailId? _currentEmailId;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
@@ -193,12 +181,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
SingleEmailController(
this._getEmailContentInteractor,
this._markAsEmailReadInteractor,
this._exportAttachmentInteractor,
this._markAsStarEmailInteractor,
this._getAllIdentitiesInteractor,
this._storeOpenedEmailInteractor,
this._printEmailInteractor,
this._exportAllAttachmentsInteractor, {
this._printEmailInteractor, {
EmailId? currentEmailId,
}) : _currentEmailId = currentEmailId;
@@ -236,10 +222,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_getEmailContentOfflineSuccess(success);
} else if (success is MarkAsEmailReadSuccess) {
_handleMarkAsEmailReadCompleted(success);
} else if (success is ExportAttachmentSuccess) {
_exportAttachmentSuccessAction(success);
} else if (success is ExportAllAttachmentsSuccess) {
_exportAllAttachmentsSuccessAction(success);
} else if (success is MarkAsStarEmailSuccess) {
_markAsEmailStarSuccess(success);
} else if (success is GetAllIdentitiesSuccess) {
@@ -267,10 +249,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void handleFailureViewState(Failure failure) {
if (failure is MarkAsEmailReadFailure) {
_handleMarkAsEmailReadFailure(failure);
} else if (failure is ExportAttachmentFailure) {
_exportAttachmentFailureAction(failure);
} else if (failure is ExportAllAttachmentsFailure) {
_exportAllAttachmentsFailureAction(failure);
} else if (failure is ParseCalendarEventFailure) {
_handleParseCalendarEventFailure(failure);
} else if (failure is GetEmailContentFailure) {
@@ -697,173 +675,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_threadDetailController?.markCollapsedEmailReadSuccess(success);
}
void exportAttachment(Attachment attachment) {
final cancelToken = CancelToken();
_showDownloadingFileDialog(attachment.name ?? '', cancelToken: cancelToken);
_exportAttachmentAction(attachment, cancelToken);
}
void _showDownloadingFileDialog(String attachmentName, {CancelToken? cancelToken}) {
if (cancelToken != null) {
Get.dialog(
PointerInterceptor(
child: Builder(
builder: (context) {
final appLocalizations = AppLocalizations.of(context);
return (DownloadingFileDialogBuilder()
..key(const Key('downloading_file_dialog'))
..title(appLocalizations.preparing_to_export)
..content(appLocalizations.downloading_file(attachmentName))
..actionText(appLocalizations.cancel)
..addCancelDownloadActionClick(() {
cancelToken.cancel([
appLocalizations.user_cancel_download_file,
]);
popBack();
}))
.build();
},
),
),
barrierDismissible: false,
);
} else {
Get.dialog(
PointerInterceptor(
child: Builder(
builder: (context) {
final appLocalizations = AppLocalizations.of(context);
return (DownloadingFileDialogBuilder()
..key(const Key('downloading_file_for_web_dialog'))
..title(appLocalizations.preparing_to_save)
..content(
appLocalizations.downloading_file(attachmentName),
))
.build();
},
),
),
);
}
}
void _exportAttachmentAction(Attachment attachment, CancelToken cancelToken) {
if (accountId != null && session != null) {
try {
final baseDownloadUrl = session!.getDownloadUrl(
jmapUrl: dynamicUrlInterceptors.jmapUrl,
);
consumeState(_exportAttachmentInteractor.execute(
attachment,
accountId!,
baseDownloadUrl,
cancelToken,
));
} catch (e) {
logError('SingleEmailController::_exportAttachmentAction(): $e');
consumeState(Stream.value(Left(ExportAttachmentFailure(e))));
}
} else {
consumeState(Stream.value(Left(ExportAttachmentFailure(NotFoundAccountIdException()))));
}
}
void exportAllAttachments(String outputFileName) {
final cancelToken = CancelToken();
_showDownloadingFileDialog(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) {
consumeState(Stream.value(Left(ExportAllAttachmentsFailure())));
return;
}
final baseDownloadAllUrl = session!.getDownloadAllCapability(accountId)!.endpoint!;
consumeState(_exportAllAttachmentsInteractor.execute(
accountId!,
currentEmail!.id!,
baseDownloadAllUrl,
outputFileName,
cancelToken: cancelToken,
));
}
void _exportAttachmentFailureAction(ExportAttachmentFailure failure) {
if (failure.exception is! CancelDownloadFileException) {
if (Get.isDialogOpen == true) {
popBack();
}
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).attachment_download_failed);
}
}
}
void _exportAttachmentSuccessAction(ExportAttachmentSuccess success) async {
popBack();
_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();
_saveFileToStorage(success.downloadedResponse.filePath);
}
void _openDownloadedPreviewWorkGroupDocument(DownloadedResponse downloadedResponse) async {
log('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): $downloadedResponse');
final filePath = downloadedResponse.filePath;
final mediaType = downloadedResponse.mediaType;
if (mediaType == null) {
_saveFileToStorage(filePath);
return;
}
final openResult = await open_file.OpenFile.open(
filePath,
type: Platform.isAndroid ? mediaType.mimeType : null,
// "xdg" is default value
linuxDesktopName: Platform.isIOS
? mediaType.getDocumentUti().value ?? 'xdg'
: 'xdg',
);
if (openResult.type != open_file.ResultType.done) {
logError('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): no preview available');
_saveFileToStorage(filePath);
}
}
Future<void> _saveFileToStorage(String filePath) async {
final params = SaveFileDialogParams(sourceFilePath: filePath);
await FlutterFileDialog.saveFile(params: params);
}
bool isDownloadAllSupported() {
return session?.isDownloadAllSupported(accountId) ?? false;
}
@@ -1059,7 +870,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_printEmail(presentationEmail);
break;
case EmailActionType.downloadMessageAsEML:
mailboxDashBoardController.downloadMessageAsEML(presentationEmail);
mailboxDashBoardController.downloadMessageAsEML(
presentationEmail: presentationEmail,
showBottomDownloadProgressBar: true,
);
break;
case EmailActionType.editAsNewEmail:
_editAsNewEmail(presentationEmail);
@@ -1100,8 +914,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
Future<void> openMailToLink(Uri? uri) =>
mailboxDashBoardController.openMailToLink(uri);
void openMailToLink(Uri? uri) {
if (uri == null) return;
mailboxDashBoardController.openComposerFromMailToLink(uri);
}
void deleteEmailPermanently(PresentationEmail email) {
emailActionReactor.deleteEmailPermanently(
@@ -1618,68 +1434,36 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
Attachment attachment,
{bool previewerSupported = false}
) {
if (PlatformInfo.isWeb) {
mailboxDashBoardController.downloadAttachmentForWeb(
attachment: attachment,
previewerSupported: previewerSupported,
);
} else if (PlatformInfo.isMobile) {
exportAttachment(attachment);
} else {
log('$runtimeType::handleDownloadAttachmentAction: THE PLATFORM IS SUPPORTED');
}
mailboxDashBoardController.downloadAttachment(
attachment: attachment,
previewerSupported: previewerSupported,
showBottomDownloadProgressBar: true,
sourceView: DownloadSourceView.emailView,
);
}
void handleDownloadAllAttachmentsAction(String outputFileName) {
if (PlatformInfo.isWeb) {
mailboxDashBoardController.downloadAllAttachmentsForWeb(
outputFileName: outputFileName,
currentEmail: currentEmail,
);
} else if (PlatformInfo.isMobile) {
exportAllAttachments(outputFileName);
} else {
log('EmailView::handleDownloadAllAttachmentsAction: THE PLATFORM IS SUPPORTED');
}
mailboxDashBoardController.downloadAllAttachments(
outputFileName: outputFileName,
emailId: currentEmail?.id,
showBottomDownloadProgressBar: true,
);
}
void handleViewAttachmentAction(BuildContext context, Attachment attachment) {
mailboxDashBoardController.previewAttachmentAction(
mailboxDashBoardController.previewAttachment(
context: context,
attachment: attachment,
onDownloadAttachment: (attachment) {
sourceView: DownloadSourceView.emailView,
onPreviewOrDownloadAction: (attachment, isPreview) {
handleDownloadAttachmentAction(
attachment,
previewerSupported: attachment.isPreviewSupported,
previewerSupported: isPreview,
);
},
);
}
Future<void> _openEMLPreviewer(BuildContext context, Uri? uri) async {
log('SingleEmailController::_openEMLPreviewer:uri = $uri');
if (uri == null) return;
final blobId = uri.path;
log('SingleEmailController::_openEMLPreviewer:blobId = $blobId');
if (blobId.isEmpty) return;
mailboxDashBoardController.previewEMLFileAction(
appLocalizations: AppLocalizations.of(context),
blobId: Id(blobId),
);
}
Future<void> _downloadAttachmentInEMLPreview(Uri? uri) async {
log('SingleEmailController::_downloadAttachmentInEMLPreview:uri = $uri');
if (uri == null) return;
final attachment = EmailUtils.parsingAttachmentByUri(uri);
if (attachment == null) return;
handleDownloadAttachmentAction(attachment);
}
void handleMailToAttendees(CalendarOrganizer? organizer, List<CalendarAttendee>? attendees) {
List<EmailAddress> listEmailAddressAttendees = [];
@@ -1745,9 +1529,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
content: success.messageDocument,
),
imagePaths: imagePaths,
onMailtoAction: openMailToLink,
onPreviewAction: (uri) => _openEMLPreviewer(context, uri),
onDownloadAction: _downloadAttachmentInEMLPreview,
onMailtoAction: (uri) async => openMailToLink(uri),
onDownloadFileAction: handleDownloadAttachmentAction,
);
},
onFailure: (_) {
@@ -336,7 +336,7 @@ class EmailView extends GetWidget<SingleEmailController> {
Obx(() => CalendarEventDetailWidget(
calendarEvent: calendarEvent,
emailContent: controller.currentEmailLoaded.value?.htmlContent ?? '',
onMailtoDelegateAction: controller.openMailToLink,
onMailtoDelegateAction: (uri) async => controller.openMailToLink(uri),
presentationEmail: controller.currentEmail,
scrollController: scrollController,
isInsideThreadDetailView: isInsideThreadDetailView,
@@ -385,7 +385,7 @@ class EmailView extends GetWidget<SingleEmailController> {
contentPadding: 0,
useDefaultFontStyle: true,
maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight,
onMailtoDelegateAction: controller.openMailToLink,
onMailtoDelegateAction: (uri) async => controller.openMailToLink(uri),
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
onScrollHorizontalEnd: controller.onScrollHorizontalEnd,
keepAlive: isInsideThreadDetailView,
@@ -423,7 +423,7 @@ class EmailView extends GetWidget<SingleEmailController> {
direction: AppUtils.getCurrentDirection(context),
contentPadding: 0,
useDefaultFontStyle: true,
onMailtoDelegateAction: controller.openMailToLink,
onMailtoDelegateAction: (uri) async => controller.openMailToLink(uri),
keepAlive: isInsideThreadDetailView,
enableQuoteToggle: true,
onScrollHorizontalEnd: controller.onScrollHorizontalEnd,
@@ -474,8 +474,7 @@ class EmailView extends GetWidget<SingleEmailController> {
),
onTapShowAllAttachmentFile: controller.showAllAttachmentsAction,
onTapHideAllAttachments: controller.hideAllAttachmentsAction,
showDownloadAllAttachmentsButton:
controller.downloadAllButtonIsEnabled(),
showDownloadAllAttachmentsButton: controller.downloadAllButtonIsEnabled(),
onTapDownloadAllButton: () =>
controller.handleDownloadAllAttachmentsAction(
'TwakeMail-${DateTime.now()}',
@@ -1,444 +0,0 @@
import 'dart:typed_data';
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/download/download_task_id.dart';
import 'package:model/email/attachment.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.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/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/usecases/get_html_content_from_attachment_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/presentation/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/eml_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/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_utils.dart';
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
typedef OnDownloadAttachmentAction = void Function(Attachment attachment);
mixin PreviewAttachmentMixin {
final DownloadManager _downloadManager = Get.find<DownloadManager>();
final PrintUtils _printUtils = Get.find<PrintUtils>();
final AppToast _appToast = Get.find<AppToast>();
final ToastManager _toastManager = Get.find<ToastManager>();
final DynamicUrlInterceptors _dynamicUrlInterceptors =
Get.find<DynamicUrlInterceptors>();
void previewAttachmentAction({
required BuildContext context,
required Attachment attachment,
required AccountId? accountId,
required Session? session,
required String ownEmailAddress,
required BaseController controller,
required ParseEmailByBlobIdInteractor parseEmailInteractor,
required GetHtmlContentFromAttachmentInteractor getHtmlInteractor,
required OnDownloadAttachmentAction onDownloadAttachment,
}) {
if (PlatformInfo.isWeb && attachment.isPDFFile) {
_previewPDFFile(
context: context,
attachment: attachment,
accountId: accountId,
session: session,
);
} else if (attachment.isEMLFile) {
previewEMLFileAction(
appLocalizations: AppLocalizations.of(context),
accountId: accountId,
session: session,
ownEmailAddress: ownEmailAddress,
blobId: attachment.blobId,
controller: controller,
parseEmailByBlobIdInteractor: parseEmailInteractor,
);
} else if (attachment.isHTMLFile) {
_previewHtmlFileAction(
session: session,
accountId: accountId,
attachment: attachment,
controller: controller,
getHtmlInteractor: getHtmlInteractor,
);
} else {
onDownloadAttachment(attachment);
}
}
Future<void> _previewPDFFile({
required BuildContext context,
required Attachment attachment,
required AccountId? accountId,
required Session? session,
}) 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) {
if (context.mounted) {
_appToast.showToastErrorMessage(
context,
AppLocalizations.of(context).noPreviewAvailable,
);
}
}
}
void _downloadPDFFile(Uint8List bytes, String fileName) {
_downloadManager.createAnchorElementDownloadFileWeb(bytes, fileName);
}
Future<void> _printPDFFile(Uint8List bytes, String fileName) async {
await _printUtils.printPDFFile(bytes, fileName);
}
void previewEMLFileAction({
required AppLocalizations appLocalizations,
required AccountId? accountId,
required Session? session,
required String ownEmailAddress,
required Id? blobId,
required ParseEmailByBlobIdInteractor parseEmailByBlobIdInteractor,
required BaseController controller,
}) {
SmartDialog.showLoading(
msg: appLocalizations.loadingPleaseWait,
maskColor: Colors.black38,
);
if (session == null) {
_emitFailure(
controller: controller,
failure: ParseEmailByBlobIdFailure(NotFoundSessionException()),
);
return;
}
if (accountId == null) {
_emitFailure(
controller: controller,
failure: ParseEmailByBlobIdFailure(NotFoundAccountIdException()),
);
return;
}
if (blobId == null) {
_emitFailure(
controller: controller,
failure: ParseEmailByBlobIdFailure(NotFoundBlobIdException([])),
);
return;
}
controller.consumeState(
parseEmailByBlobIdInteractor.execute(
accountId,
session,
ownEmailAddress,
blobId,
),
);
}
({
bool canDownloadAttachment,
AccountId? accountId,
String? downloadUrl,
Id? blobId,
}) evaluateAttachment({
required Session? session,
required AccountId? accountId,
required Attachment attachment,
}) {
String? downloadUrl;
try {
downloadUrl = session?.getDownloadUrl(
jmapUrl: _dynamicUrlInterceptors.jmapUrl,
);
} catch (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,
);
}
void _previewHtmlFileAction({
required Session? session,
required AccountId? accountId,
required Attachment attachment,
required BaseController controller,
required GetHtmlContentFromAttachmentInteractor getHtmlInteractor,
}) {
final attachmentEvaluation = evaluateAttachment(
accountId: accountId,
session: session,
attachment: attachment,
);
if (!attachmentEvaluation.canDownloadAttachment) {
_emitFailure(
controller: controller,
failure: GetHtmlContentFromAttachmentFailure(
exception: null,
attachment: attachment,
),
);
return;
}
controller.consumeState(
getHtmlInteractor.execute(
attachmentEvaluation.accountId!,
attachment,
DownloadTaskId(attachmentEvaluation.blobId!.value),
attachmentEvaluation.downloadUrl!,
TransformConfiguration.create(
customDomTransformers: [SanitizeHyperLinkTagInHtmlTransformer()],
customTextTransformers: [
const StandardizeHtmlSanitizingTransformers()
],
),
),
);
}
void _emitFailure({
required BaseController controller,
required FeatureFailure failure,
}) {
controller.consumeState(Stream.value(Left(failure)));
}
void handleParseEmailByBlobIdSuccess({
required BuildContext? context,
required AccountId? accountId,
required Session? session,
required String ownEmailAddress,
required Id blobId,
required Email email,
required BaseController controller,
required PreviewEmailFromEmlFileInteractor previewInteractor,
}) {
if (session == null) {
_emitFailure(
controller: controller,
failure: PreviewEmailFromEmlFileFailure(NotFoundSessionException()),
);
return;
}
if (accountId == null) {
_emitFailure(
controller: controller,
failure: PreviewEmailFromEmlFileFailure(NotFoundAccountIdException()),
);
return;
}
if (context == null) {
_emitFailure(
controller: controller,
failure: PreviewEmailFromEmlFileFailure(NotFoundContextException()),
);
return;
}
try {
controller.consumeState(previewInteractor.execute(
PreviewEmailEMLRequest(
accountId: accountId,
session: session,
ownEmailAddress: ownEmailAddress,
blobId: blobId,
email: email,
locale: Localizations.localeOf(context),
appLocalizations: AppLocalizations.of(context),
baseDownloadUrl: session.getDownloadUrl(
jmapUrl: _dynamicUrlInterceptors.jmapUrl,
),
),
));
} catch (e) {
_emitFailure(
controller: controller,
failure: PreviewEmailFromEmlFileFailure(e),
);
}
}
void handleParseEmailByBlobIdFailure(ParseEmailByBlobIdFailure failure) {
SmartDialog.dismiss();
_toastManager.showMessageFailure(failure);
}
void handlePreviewEmailFromEMLFileFailure(
PreviewEmailFromEmlFileFailure failure,
) {
SmartDialog.dismiss();
_toastManager.showMessageFailure(failure);
}
void handlePreviewEmailFromEMLFileSuccess({
required EMLPreviewer emlPreviewer,
required BuildContext? context,
required ImagePaths imagePaths,
required OnMailtoDelegateAction onMailtoAction,
required OnDownloadAttachmentDelegateAction onDownloadAction,
required OnPreviewEMLDelegateAction onPreviewAction,
}) {
SmartDialog.dismiss();
if (PlatformInfo.isWeb) {
bool isOpen = HtmlUtils.openNewWindowByUrl(
RouteUtils.createUrlWebLocationBar(
AppRoutes.emailEMLPreviewer,
previewId: emlPreviewer.id,
).toString(),
);
if (!isOpen) {
_toastManager.showMessageFailure(
PreviewEmailFromEmlFileFailure(CannotOpenNewWindowException()),
);
}
} else if (PlatformInfo.isMobile) {
if (context == null) {
_toastManager.showMessageFailure(
PreviewEmailFromEmlFileFailure(NotFoundContextException()),
);
return;
}
if (PlatformInfo.isAndroid) {
showModalSheetToPreviewEMLAttachment(
context: context,
emlPreviewer: emlPreviewer,
imagePaths: imagePaths,
onMailtoAction: onMailtoAction,
onDownloadAction: onDownloadAction,
onPreviewAction: onPreviewAction,
);
} else if (PlatformInfo.isIOS) {
showDialogToPreviewEMLAttachment(
context: context,
emlPreviewer: emlPreviewer,
imagePaths: imagePaths,
onMailtoAction: onMailtoAction,
onDownloadAction: onDownloadAction,
onPreviewAction: onPreviewAction,
);
}
}
}
void showModalSheetToPreviewEMLAttachment({
required BuildContext context,
required EMLPreviewer emlPreviewer,
required ImagePaths imagePaths,
required OnMailtoDelegateAction onMailtoAction,
required OnDownloadAttachmentDelegateAction onDownloadAction,
required OnPreviewEMLDelegateAction onPreviewAction,
}) {
showModalBottomSheet(
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: onMailtoAction,
onPreviewEMLDelegateAction: onPreviewAction,
onDownloadAttachmentDelegateAction: onDownloadAction,
),
);
},
);
}
void showDialogToPreviewEMLAttachment({
required BuildContext context,
required EMLPreviewer emlPreviewer,
required ImagePaths imagePaths,
required OnMailtoDelegateAction onMailtoAction,
required OnDownloadAttachmentDelegateAction onDownloadAction,
required OnPreviewEMLDelegateAction onPreviewAction,
}) {
Get.dialog(
EmailPreviewerDialogView(
emlPreviewer: emlPreviewer,
imagePaths: imagePaths,
onMailtoDelegateAction: onMailtoAction,
onPreviewEMLDelegateAction: onPreviewAction,
onDownloadAttachmentDelegateAction: onDownloadAction,
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
}
}
@@ -14,8 +14,8 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/email/attachment.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/get_html_content_from_attachment_state.dart';
import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
@@ -83,11 +83,11 @@ class EmailUtils {
return state?.fold(
(failure) {
return failure is DownloadAttachmentForWebFailure
|| failure is GetHtmlContentFromAttachmentFailure;
|| failure is DownloadAndGetHtmlContentFromAttachmentFailure;
},
(success) {
return success is DownloadAttachmentForWebSuccess
|| success is GetHtmlContentFromAttachmentSuccess
|| success is DownloadAndGetHtmlContentFromAttachmentSuccess
|| success is IdleDownloadAttachmentForWeb;
}) ?? false;
}
@@ -13,9 +13,9 @@ import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:model/download/download_task_id.dart';
import 'package:model/email/attachment.dart';
import 'package:tmail_ui_user/features/email/domain/exceptions/download_attachment_exceptions.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart';
import 'package:tmail_ui_user/features/download/domain/exceptions/download_attachment_exceptions.dart';
import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:twake_previewer_flutter/core/previewer_options/options/loading_options.dart';