import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:better_open_file/better_open_file.dart' as open_file; import 'package:core/core.dart'; import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart'; import 'package:core/presentation/utils/html_transformer/text/new_line_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:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart'; 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:get/get_navigation/src/dialog/dialog_route.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'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/attendance/calendar_event_attendance.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/attendee/calendar_attendee.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_organizer.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mdn/disposition.dart'; import 'package:jmap_dart_client/jmap/mdn/mdn.dart'; import 'package:model/email/eml_attachment.dart'; import 'package:model/error_type_handler/unknown_uri_exception.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:share_plus/share_plus.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/state/button_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/calendar_event_exceptions.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/email_print.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/move_action.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.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'; 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_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/move_to_mailbox_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'; 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/get_entire_message_as_document_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'; import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/download_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/move_to_mailbox_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/get_html_content_from_attachment_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'; 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/controller/email_supervisor_controller.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/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'; import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_list/attachment_list_bottom_sheet_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_list/attachment_list_dialog_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_dialog_builder.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/presentation/model/mailbox_actions.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/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'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_email_rule_filter_request.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/localizations/localization_service.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/dialog_router.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; import 'package:twake_previewer_flutter/core/constants/supported_charset.dart'; import 'package:twake_previewer_flutter/core/previewer_options/options/previewer_state.dart'; import 'package:twake_previewer_flutter/core/previewer_options/options/top_bar_options.dart'; import 'package:twake_previewer_flutter/core/previewer_options/previewer_options.dart'; import 'package:twake_previewer_flutter/twake_image_previewer/twake_image_previewer.dart'; import 'package:twake_previewer_flutter/twake_plain_text_previewer/twake_plain_text_previewer.dart'; class SingleEmailController extends BaseController with AppLoaderMixin { final mailboxDashBoardController = Get.find(); final emailSupervisorController = Get.find(); final _downloadManager = Get.find(); final _printUtils = Get.find(); final _attachmentListScrollController = ScrollController(); final GetEmailContentInteractor _getEmailContentInteractor; final MarkAsEmailReadInteractor _markAsEmailReadInteractor; final DownloadAttachmentsInteractor _downloadAttachmentsInteractor; final DeviceManager _deviceManager; final ExportAttachmentInteractor _exportAttachmentInteractor; final MoveToMailboxInteractor _moveToMailboxInteractor; final MarkAsStarEmailInteractor _markAsStarEmailInteractor; final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor; final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; final StoreOpenedEmailInteractor _storeOpenedEmailInteractor; final PrintEmailInteractor _printEmailInteractor; final ParseEmailByBlobIdInteractor _parseEmailByBlobIdInteractor; final PreviewEmailFromEmlFileInteractor _previewEmailFromEmlFileInteractor; final GetHtmlContentFromAttachmentInteractor _getHtmlContentFromAttachmentInteractor; final DownloadAllAttachmentsForWebInteractor _downloadAllAttachmentsForWebInteractor; final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor; ParseCalendarEventInteractor? _parseCalendarEventInteractor; AcceptCalendarEventInteractor? _acceptCalendarEventInteractor; MaybeCalendarEventInteractor? _maybeCalendarEventInteractor; RejectCalendarEventInteractor? _rejectCalendarEventInteractor; final emailContents = RxnString(); final attachments = [].obs; final blobCalendarEvent = Rxn(); final emailLoadedViewState = Rx>(Right(UIState.idle)); final emailUnsubscribe = Rxn(); final attachmentsViewState = RxMap>(); final isEmailContentHidden = RxBool(false); final currentEmailLoaded = Rxn(); final isEmailContentClipped = RxBool(false); final attendanceStatus = Rxn(); final htmlContentViewKey = GlobalKey(); EmailId? _currentEmailId; Identity? _identitySelected; ButtonState? _printEmailButtonState; final StreamController> _downloadProgressStateController = StreamController>.broadcast(); Stream> get downloadProgressState => _downloadProgressStateController.stream; PresentationEmail? get currentEmail => mailboxDashBoardController.selectedEmail.value; bool get calendarEventProcessing => viewState.value.fold( (failure) => false, (success) => success is CalendarEventReplying); CalendarEvent? get calendarEvent => blobCalendarEvent.value?.calendarEventList.firstOrNull; Id? get _displayingEventBlobId => blobCalendarEvent.value?.blobId; bool get isCalendarEventFree => blobCalendarEvent.value?.isFree ?? true; AccountId? get accountId => mailboxDashBoardController.accountId.value; Session? get session => mailboxDashBoardController.sessionCurrent; UserName? get userName => session?.username; SingleEmailController( this._getEmailContentInteractor, this._markAsEmailReadInteractor, this._downloadAttachmentsInteractor, this._deviceManager, this._exportAttachmentInteractor, this._moveToMailboxInteractor, this._markAsStarEmailInteractor, this._downloadAttachmentForWebInteractor, this._getAllIdentitiesInteractor, this._storeOpenedEmailInteractor, this._printEmailInteractor, this._parseEmailByBlobIdInteractor, this._previewEmailFromEmlFileInteractor, this._getHtmlContentFromAttachmentInteractor, this._downloadAllAttachmentsForWebInteractor, this._exportAllAttachmentsInteractor, ); @override void onInit() { _registerObxStreamListener(); _listenDownloadAttachmentProgressState(); super.onInit(); } @override void onClose() { _downloadProgressStateController.close(); _attachmentListScrollController.dispose(); super.onClose(); } @override void handleSuccessViewState(Success success) { super.handleSuccessViewState(success); if (success is GetEmailContentSuccess) { _getEmailContentSuccess(success); } else if (success is GetEmailContentFromCacheSuccess) { _getEmailContentOfflineSuccess(success); } else if (success is MarkAsEmailReadSuccess) { _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) { _markAsEmailStarSuccess(success); } else if (success is DownloadAttachmentForWebSuccess) { _downloadAttachmentForWebSuccessAction(success); } else if (success is StartDownloadAttachmentForWeb) { _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) { _sendReceiptToSenderSuccess(success); } else if (success is CreateNewRuleFilterSuccess) { _createNewRuleFilterSuccess(success); } else if (success is ParseCalendarEventLoading) { emailLoadedViewState.value = Right(success); } else if (success is ParseCalendarEventSuccess) { _handleParseCalendarEventSuccess(success); } else if (success is PrintEmailLoading) { _showMessageWhenStartingEmailPrinting(); } else if (success is PrintEmailSuccess) { _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(currentContext!, success.attachment); }, responsiveUtils: responsiveUtils, )); } else if (success is GettingHtmlContentFromAttachment) { _updateAttachmentsViewState(success.attachment.blobId, Right(success)); } } @override void handleFailureViewState(Failure failure) { super.handleFailureViewState(failure); if (failure is MarkAsEmailReadFailure) { _handleMarkAsEmailReadCompleted(failure.readActions); } else if (failure is DownloadAttachmentsFailure) { _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) { _handleGetEmailContentFailure(failure); } else if (failure is PrintEmailFailure) { _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); } } void _handleGetEmailContentFailure(GetEmailContentFailure failure) { emailLoadedViewState.value = Left(failure); showRetryToast(failure); } void _registerObxStreamListener() { ever(mailboxDashBoardController.accountId, (accountId) { if (accountId is AccountId) { _injectAndGetInteractorBindings( session, accountId ); } }); ever( mailboxDashBoardController.selectedEmail, _handleOpenEmailDetailedView ); ever(mailboxDashBoardController.emailUIAction, (action) { if (action is CloseEmailDetailedViewToRedirectToTheInboxAction) { if (emailSupervisorController.supportedPageView.isTrue) { emailSupervisorController.popEmailQueue(_currentEmailId); emailSupervisorController.setCurrentEmailIndex(-1); emailSupervisorController.disposePageViewController(); } _updateCurrentEmailId(null); _resetToOriginalValue(isEmailClosing: true); mailboxDashBoardController.clearSelectedEmail(); mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread); mailboxDashBoardController.clearEmailUIAction(); } else if (action is CloseEmailDetailedViewAction) { closeEmailView(context: currentContext); mailboxDashBoardController.clearEmailUIAction(); } else if (action is HideEmailContentViewAction) { isEmailContentHidden.value = true; mailboxDashBoardController.clearEmailUIAction(); } else if (action is ShowEmailContentViewAction) { isEmailContentHidden.value = false; mailboxDashBoardController.clearEmailUIAction(); } }); ever(mailboxDashBoardController.viewState, (viewState) { viewState.map((success) { if (success is UnsubscribeEmailSuccess) { emailUnsubscribe.value = null; } }); }); } bool isListEmailContainSelectedEmail(PresentationEmail selectedEmail) { return emailSupervisorController.currentListEmail.isNotEmpty && emailSupervisorController.currentListEmail.listEmailIds.contains(selectedEmail.id); } void _handleOpenEmailDetailedView(PresentationEmail? selectedEmail) { if (selectedEmail == null || _currentEmailId == selectedEmail.id) { log('SingleEmailController::_handleOpenEmailDetailedView(): email unselected'); return; } emailLoadedViewState.value = Right(GetEmailContentLoading()); emailSupervisorController.updateNewCurrentListEmail(); _updateCurrentEmailId(selectedEmail.id); _resetToOriginalValue(); if (isListEmailContainSelectedEmail(selectedEmail)) { _createMultipleEmailViewAsPageView(selectedEmail.id!); } else { _createSingleEmailView(selectedEmail.id!); } if (!selectedEmail.hasRead) { markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap); } if (mailboxDashBoardController.listIdentities.isEmpty) { _getAllIdentities(); } else { _initializeSelectedIdentity(mailboxDashBoardController.listIdentities); } } void _updateCurrentEmailId(EmailId? emailId) { _currentEmailId = emailId; } void _createMultipleEmailViewAsPageView(EmailId emailId) { log('SingleEmailController::_createMultipleEmailViewAsPageView():'); emailSupervisorController.supportedPageView.value = true; emailSupervisorController.createPageControllerAndJumpToEmailById(emailId); _getEmailContentAction(emailId); } void _createSingleEmailView(EmailId emailId) { log('SingleEmailController::_createSingleEmailView():'); emailSupervisorController.supportedPageView.value = false; _getEmailContentAction(emailId); } void _listenDownloadAttachmentProgressState() { downloadProgressState.listen((state) { log('SingleEmailController::_listenDownloadAttachmentProgressState(): $state'); state.fold( (failure) => null, (success) { if (success is StartDownloadAttachmentForWeb && !success.previewerSupported) { emailSupervisorController.mailboxDashBoardController.addDownloadTask( DownloadTaskState( taskId: success.taskId, attachment: success.attachment, onCancel: () => success.cancelToken?.cancel(), ), ); if (currentOverlayContext != null && currentContext != null && !success.previewerSupported) { appToast.showToastMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).your_download_has_started, leadingSVGIconColor: AppColor.primaryColor, leadingSVGIcon: imagePaths.icDownload); } } else if (success is DownloadingAttachmentForWeb) { 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; }); } else if (success is StartDownloadAllAttachmentsForWeb) { emailSupervisorController.mailboxDashBoardController.addDownloadTask( DownloadTaskState( taskId: success.taskId, attachment: success.attachment, onCancel: () => success.cancelToken?.cancel(), ), ); 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; }); } }); }); } void _injectAndGetInteractorBindings(Session? session, AccountId accountId) { injectRuleFilterBindings(session, accountId); injectMdnBindings(session, accountId); _injectCalendarEventBindings(session, accountId); _createNewEmailRuleFilterInteractor = getBinding(); _sendReceiptToSenderInteractor = getBinding(); _parseCalendarEventInteractor = getBinding(); _acceptCalendarEventInteractor = getBinding(); _maybeCalendarEventInteractor = getBinding(); _rejectCalendarEventInteractor = getBinding(); } void _injectCalendarEventBindings(Session? session, AccountId? accountId) { if (session != null && accountId != null) { if (CapabilityIdentifier.jamesCalendarEvent.isSupported(session, accountId)) { CalendarEventInteractorBindings().dependencies(); } } } void _getAllIdentities() { if (accountId != null && session != null) { consumeState(_getAllIdentitiesInteractor.execute(session!, accountId!)); } } void _getAllIdentitiesSuccess(GetAllIdentitiesSuccess success) { if (success.identities?.isNotEmpty == true) { _initializeSelectedIdentity(success.identities!); } } void _initializeSelectedIdentity(List identities) { if (currentEmail != null) { final currentMailbox = getMailboxContain(currentEmail!); if (_isBelongToTeamMailboxes(currentMailbox)) { _setUpDefaultIdentityForTeamMailbox(identities, currentMailbox!); } else { _setUpDefaultIdentity(identities); } } else { _setUpDefaultIdentity(identities); } } bool _isBelongToTeamMailboxes(PresentationMailbox? presentationMailbox) { return presentationMailbox != null && presentationMailbox.isPersonal == false; } void _setUpDefaultIdentityForTeamMailbox(List identities, PresentationMailbox currentMailbox) { final matchedTeamMailboxIdentity = identities.firstWhereOrNull((identity) => identity.email == currentMailbox.emailTeamMailBoxes); if (matchedTeamMailboxIdentity != null) { _identitySelected = matchedTeamMailboxIdentity; } else { _identitySelected = identities.first; } } void _setUpDefaultIdentity(List identities) { _identitySelected = identities.first; } void _getEmailContentAction(EmailId emailId) { final emailLoaded = emailSupervisorController.getEmailInQueueByEmailId(emailId); if (emailLoaded != null) { consumeState(Stream.value(Right( GetEmailContentSuccess( htmlEmailContent: emailLoaded.htmlContent, attachments: emailLoaded.attachments, inlineImages: emailLoaded.inlineImages, emailCurrent: emailLoaded.emailCurrent ) ))); } else { if (session != null && accountId != null) { try { final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); TransformConfiguration transformConfiguration = PlatformInfo.isWeb ? TransformConfiguration.forPreviewEmailOnWeb() : TransformConfiguration.forPreviewEmail(); consumeState(_getEmailContentInteractor.execute( session!, accountId!, emailId, baseDownloadUrl, transformConfiguration )); } catch (e) { logError('SingleEmailController::_getEmailContentAction(): $e'); consumeState(Stream.value(Left(GetEmailContentFailure( e, onRetry: e is UnknownUriException ? null : _getEmailContentInteractor.execute( session!, accountId!, emailId, session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), PlatformInfo.isWeb ? TransformConfiguration.forPreviewEmailOnWeb() : TransformConfiguration.forPreviewEmail(), ), )))); } } } } void _getEmailContentOfflineSuccess(GetEmailContentFromCacheSuccess success) { emailLoadedViewState.value = Right(success); if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) { emailSupervisorController.popFirstEmailQueue(); } emailSupervisorController.popEmailQueue(success.emailCurrent.id); currentEmailLoaded.value = EmailLoaded( htmlContent: success.htmlEmailContent, attachments: List.of(success.attachments ?? []), inlineImages: List.of(success.inlineImages ?? []), emailCurrent: success.emailCurrent, ); emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!); if (success.emailCurrent.id == currentEmail?.id) { attachments.value = success.attachments ?? []; attachmentsViewState.value = { for (var attachment in attachments.where((item) => item.blobId != null)) attachment.blobId!: Right(IdleDownloadAttachmentForWeb()) }; if (_canParseCalendarEvent(blobIds: success.attachments?.calendarEventBlobIds ?? {})) { _parseCalendarEventAction( accountId: mailboxDashBoardController.accountId.value!, blobIds: success.attachments?.calendarEventBlobIds ?? {}, ); } else { emailContents.value = success.htmlEmailContent; } final isShowMessageReadReceipt = success.emailCurrent.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true; if (isShowMessageReadReceipt) { _handleReadReceipt(); } if (currentEmail?.isSubscribed == false && success.emailCurrent.hasListUnsubscribe == true) { _handleUnsubscribe(success.emailCurrent.listUnsubscribe); } else { emailUnsubscribe.value = null; } } } void _getEmailContentSuccess(GetEmailContentSuccess success) { emailLoadedViewState.value = Right(success); if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) { emailSupervisorController.popFirstEmailQueue(); } emailSupervisorController.popEmailQueue(success.emailCurrent?.id); currentEmailLoaded.value = EmailLoaded( htmlContent: success.htmlEmailContent, attachments: List.of(success.attachments ?? []), inlineImages: List.of(success.inlineImages ?? []), emailCurrent: success.emailCurrent, ); emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!); if (success.emailCurrent?.id == currentEmail?.id) { attachments.value = success.attachments ?? []; attachmentsViewState.value = { for (var attachment in attachments.where((item) => item.blobId != null)) attachment.blobId!: Right(IdleDownloadAttachmentForWeb()) }; if (_canParseCalendarEvent(blobIds: success.attachments?.calendarEventBlobIds ?? {})) { _parseCalendarEventAction( accountId: mailboxDashBoardController.accountId.value!, blobIds: success.attachments?.calendarEventBlobIds ?? {}, ); } else { emailContents.value = success.htmlEmailContent; } if (PlatformInfo.isMobile) { final detailedEmail = DetailedEmail( emailId: currentEmail!.id!, createdTime: currentEmail?.receivedAt?.value ?? DateTime.now(), attachments: success.attachments, inlineImages: success.inlineImages, headers: success.emailCurrent?.headers, keywords: success.emailCurrent?.keywords, htmlEmailContent: success.htmlEmailContent, messageId: success.emailCurrent?.messageId, references: success.emailCurrent?.references, sMimeStatusHeader: success.emailCurrent?.sMimeStatusHeader, ); _storeOpenedEmailAction( session, accountId, detailedEmail ); } final isShowMessageReadReceipt = success.emailCurrent ?.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true; if (isShowMessageReadReceipt) { _handleReadReceipt(); } if (currentEmail?.isSubscribed == false && success.emailCurrent?.hasListUnsubscribe == true) { _handleUnsubscribe(success.emailCurrent!.listUnsubscribe); } else { emailUnsubscribe.value = null; } } } void _handleUnsubscribe(String listUnsubscribe) { emailUnsubscribe.value = EmailUtils.parsingUnsubscribe(listUnsubscribe); } void _handleReadReceipt() { if (currentContext != null) { showConfirmDialogAction(currentContext!, AppLocalizations.of(currentContext!).subTitleReadReceiptRequestNotificationMessage, AppLocalizations.of(currentContext!).yes, onConfirmAction: () => _handleSendReceiptToSenderAction(currentContext!), showAsBottomSheet: true, dialogMargin: MediaQuery.paddingOf(currentContext!).add(const EdgeInsets.only(bottom: 12)), title: AppLocalizations.of(currentContext!).titleReadReceiptRequestNotificationMessage, cancelTitle: AppLocalizations.of(currentContext!).no, ); } } void _resetToOriginalValue({bool isEmailClosing = false}) { emailContents.value = null; currentEmailLoaded.value = null; attachments.clear(); attachmentsViewState.value = {}; blobCalendarEvent.value = null; emailUnsubscribe.value = null; _identitySelected = null; isEmailContentClipped.value = false; if (isEmailClosing) { emailLoadedViewState.value = Right(UIState.idle); viewState.value = Right(UIState.idle); } } PresentationMailbox? getMailboxContain(PresentationEmail email) { if (mailboxDashBoardController.selectedMailbox.value == null) { return email.findMailboxContain(mailboxDashBoardController.mapMailboxById); } else { return mailboxDashBoardController.searchController.isSearchEmailRunning ? email.findMailboxContain(mailboxDashBoardController.mapMailboxById) : mailboxDashBoardController.selectedMailbox.value; } } void markAsEmailRead( PresentationEmail presentationEmail, ReadActions readActions, MarkReadAction markReadAction, ) { if (accountId != null && session != null) { consumeState(_markAsEmailReadInteractor.execute( session!, accountId!, presentationEmail.id!, readActions, markReadAction, presentationEmail.mailboxContain?.mailboxId, )); } } void _handleMarkAsEmailReadCompleted(ReadActions readActions) { if (_currentEmailId != null) { mailboxDashBoardController.updateEmailFlagByEmailIds( [_currentEmailId!], readAction: readActions, ); } if (readActions == ReadActions.markAsUnread) { closeEmailView(context: currentContext); } } void downloadAttachments(BuildContext context, List attachments) async { final needRequestPermission = await _deviceManager.isNeedRequestStoragePermissionOnAndroid(); if (needRequestPermission) { final status = await Permission.storage.status; switch (status) { case PermissionStatus.granted: _downloadAttachmentsAction(attachments); break; case PermissionStatus.permanentlyDenied: if (context.mounted && currentOverlayContext != null && currentContext != null) { appToast.showToastMessage( currentOverlayContext!, AppLocalizations.of(context).you_need_to_grant_files_permission_to_download_attachments, ); } break; default: { final requested = await Permission.storage.request(); switch (requested) { case PermissionStatus.granted: _downloadAttachmentsAction(attachments); break; default: if (context.mounted && currentOverlayContext != null && currentContext != null) { appToast.showToastMessage( currentOverlayContext!, AppLocalizations.of(context).you_need_to_grant_files_permission_to_download_attachments, ); } break; } } } } else { _downloadAttachmentsAction(attachments); } } void _downloadAttachmentsAction(List attachments) { if (accountId != null && session != null) { try { final baseDownloadUrl = session!.getDownloadUrl( jmapUrl: dynamicUrlInterceptors.jmapUrl, ); consumeState(_downloadAttachmentsInteractor.execute( attachments, accountId!, baseDownloadUrl, )); } catch (e) { logError('SingleEmailController::_downloadAttachmentsAction(): $e'); consumeState(Stream.value(Left(DownloadAttachmentsFailure(e)))); } } } void _downloadAttachmentsFailure(DownloadAttachmentsFailure failure) { if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).attachment_download_failed); } } void exportAttachment(BuildContext context, Attachment attachment) { final cancelToken = CancelToken(); _showDownloadingFileDialog(context, attachment.name ?? '', cancelToken: cancelToken); _exportAttachmentAction(attachment, cancelToken); } void _showDownloadingFileDialog(BuildContext context, String attachmentName, {CancelToken? cancelToken}) { if (cancelToken != null) { showCupertinoDialog( context: context, builder: (_) => PointerInterceptor(child: (DownloadingFileDialogBuilder() ..key(const Key('downloading_file_dialog')) ..title(AppLocalizations.of(context).preparing_to_export) ..content(AppLocalizations.of(context).downloading_file(attachmentName)) ..actionText(AppLocalizations.of(context).cancel) ..addCancelDownloadActionClick(() { cancelToken.cancel([AppLocalizations.of(context).user_cancel_download_file]); popBack(); })) .build())); } else { showCupertinoDialog( context: context, builder: (_) => PointerInterceptor(child: (DownloadingFileDialogBuilder() ..key(const Key('downloading_file_for_web_dialog')) ..title(AppLocalizations.of(context).preparing_to_save) ..content(AppLocalizations.of(context).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)))); } } } 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(); 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(); _saveDownloadedZipAttachments(success.downloadedResponse.filePath); } void _openDownloadedPreviewWorkGroupDocument(DownloadedResponse downloadedResponse) async { log('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): $downloadedResponse'); if (downloadedResponse.mediaType == null) { await Share.shareXFiles([XFile(downloadedResponse.filePath)]); } final openResult = await open_file.OpenFile.open( downloadedResponse.filePath, type: Platform.isAndroid ? downloadedResponse.mediaType!.mimeType : null, uti: Platform.isIOS ? downloadedResponse.mediaType!.getDocumentUti().value : null); if (openResult.type != open_file.ResultType.done) { logError('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): no preview available'); if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).noPreviewAvailable); } } } Future _saveDownloadedZipAttachments(String filePath) async { final params = SaveFileDialogParams(sourceFilePath: filePath); await FlutterFileDialog.saveFile(params: params); } void downloadAttachmentForWeb(Attachment attachment, {bool previewerSupported = false}) { if (accountId != null && session != null) { final generateTaskId = DownloadTaskId(uuid.v4()); try { final baseDownloadUrl = session!.getDownloadUrl( jmapUrl: dynamicUrlInterceptors.jmapUrl, ); final cancelToken = CancelToken(); consumeState(_downloadAttachmentForWebInteractor.execute( generateTaskId, attachment, accountId!, baseDownloadUrl, _downloadProgressStateController, cancelToken: cancelToken, previewerSupported: previewerSupported, )); } catch (e) { logError('SingleEmailController::downloadAttachmentForWeb(): $e'); consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(attachment: attachment, taskId: generateTaskId, exception: e)))); } } else { consumeState(Stream.value( Left(DownloadAttachmentForWebFailure( attachment: attachment, exception: NotFoundSessionException())) )); } } 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) return; String message = AppLocalizations.of(currentContext!).attachment_download_failed; if (failure.cancelToken?.isCancelled == true) { message = AppLocalizations.of(currentContext!).downloadAttachmentHasBeenCancelled; } appToast.showToastErrorMessage(currentOverlayContext!, message); } void _downloadAttachmentForWebSuccessAction(DownloadAttachmentForWebSuccess success) { log('SingleEmailController::_downloadAttachmentForWebSuccessAction():'); _updateAttachmentsViewState(success.attachment.blobId, Right(success)); mailboxDashBoardController.deleteDownloadTask(success.taskId); if (!success.previewerSupported) { _downloadManager.createAnchorElementDownloadFileWeb( success.bytes, success.attachment.generateFileName()); return; } if (success.attachment.isImage) { _updateAttachmentsViewState(success.attachment.blobId, Right(success)); Navigator.of(currentContext!).push(GetDialogRoute( pageBuilder: (context, _, __) => PointerInterceptor(child: TwakeImagePreviewer( bytes: success.bytes, zoomable: true, previewerOptions: const PreviewerOptions( previewerState: PreviewerState.success, ), topBarOptions: TopBarOptions( title: success.attachment.generateFileName(), onClose: () => Navigator.maybePop(context), onDownload: currentContext == null ? null : () => handleDownloadAttachmentAction( currentContext!, success.attachment), ), )), barrierDismissible: false, )); } else if (success.attachment.isText || success.attachment.isJson) { _updateAttachmentsViewState(success.attachment.blobId, Right(success)); Navigator.of(currentContext!).push(GetDialogRoute( pageBuilder: (context, _, __) => PointerInterceptor(child: TwakePlainTextPreviewer( supportedCharset: SupportedCharset.utf8, bytes: success.bytes, previewerOptions: PreviewerOptions( previewerState: PreviewerState.success, width: currentContext == null ? 200 : currentContext!.width * 0.8, ), topBarOptions: TopBarOptions( title: success.attachment.generateFileName(), onClose: () => Navigator.maybePop(context), onDownload: currentContext == null ? null : () => handleDownloadAttachmentAction( currentContext!, success.attachment), ), )), barrierDismissible: false, )); } } void _downloadPDFFile(Uint8List bytes, String fileName) { _downloadManager.createAnchorElementDownloadFileWeb(bytes, fileName); } void _printPDFFile(Uint8List bytes, String fileName) async { await _printUtils.printPDFFile(bytes, fileName); } void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) { log('SingleEmailController::_downloadAttachmentForWebFailureAction(): $failure'); if (failure.taskId != null) { mailboxDashBoardController.deleteDownloadTask(failure.taskId!); } if (failure.attachment != null) { _updateAttachmentsViewState(failure.attachment?.blobId, Left(failure)); } if (currentOverlayContext == null || currentContext == null) return; String message = AppLocalizations.of(currentContext!).attachment_download_failed; if (failure.attachment is EMLAttachment) { message = AppLocalizations.of(currentContext!).downloadMessageAsEMLFailed; } else if (failure.cancelToken?.isCancelled == true) { message = AppLocalizations.of(currentContext!).downloadAttachmentHasBeenCancelled; } appToast.showToastErrorMessage(currentOverlayContext!, message); } void _updateAttachmentsViewState( Id? attachmentBlobId, Either viewState) { if (attachmentBlobId != null) { attachmentsViewState[attachmentBlobId] = viewState; } } void moveToMailbox(BuildContext context, PresentationEmail email) async { final currentMailbox = getMailboxContain(email); if (currentMailbox != null && accountId != null) { final arguments = DestinationPickerArguments( accountId!, MailboxActions.moveEmail, session, mailboxIdSelected: currentMailbox.mailboxId ); final destinationMailbox = PlatformInfo.isWeb ? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.destinationPicker, arguments: arguments) : await push(AppRoutes.destinationPicker, arguments: arguments); if (destinationMailbox != null && destinationMailbox is PresentationMailbox && session != null && context.mounted ) { _dispatchMoveToAction( context, accountId!, session!, email, currentMailbox, destinationMailbox); } } } void _dispatchMoveToAction( BuildContext context, AccountId accountId, Session session, PresentationEmail emailSelected, PresentationMailbox currentMailbox, PresentationMailbox destinationMailbox ) { if (destinationMailbox.isTrash) { _moveToTrashAction( context, session, accountId, MoveToMailboxRequest( {currentMailbox.id: [emailSelected.id!]}, destinationMailbox.id, MoveAction.moving, EmailActionType.moveToTrash), {emailSelected.id!: emailSelected.hasRead}); } else if (destinationMailbox.isSpam) { _moveToSpamAction( context, session, accountId, MoveToMailboxRequest( {currentMailbox.id: [emailSelected.id!]}, destinationMailbox.id, MoveAction.moving, EmailActionType.moveToSpam), {emailSelected.id!: emailSelected.hasRead}); } else { _moveToMailbox( context, session, accountId, MoveToMailboxRequest( {currentMailbox.id: [emailSelected.id!]}, destinationMailbox.id, MoveAction.moving, EmailActionType.moveToMailbox, destinationPath: destinationMailbox.mailboxPath), {emailSelected.id!: emailSelected.hasRead}); } } void _moveToMailbox( BuildContext context, Session session, AccountId accountId, MoveToMailboxRequest moveRequest, Map emailIdsWithReadStatus, ) { closeEmailView(context: context); consumeState(_moveToMailboxInteractor.execute( session, accountId, moveRequest, emailIdsWithReadStatus, )); } void _moveToMailboxSuccess(MoveToMailboxSuccess success) { mailboxDashBoardController.dispatchState(Right(success)); if (success.moveAction == MoveAction.moving && currentContext != null && currentOverlayContext != null) { appToast.showToastMessage( currentOverlayContext!, success.emailActionType.getToastMessageMoveToMailboxSuccess(currentContext!, destinationPath: success.destinationPath), actionName: AppLocalizations.of(currentContext!).undo, onActionClick: () { _revertedToOriginalMailbox(MoveToMailboxRequest( {success.destinationMailboxId: [success.emailId]}, success.currentMailboxId, MoveAction.undo, success.emailActionType), success.emailIdsWithReadStatus, ); }, leadingSVGIcon: imagePaths.icFolderMailbox, leadingSVGIconColor: Colors.white, backgroundColor: AppColor.toastSuccessBackgroundColor, textColor: Colors.white, actionIcon: SvgPicture.asset(imagePaths.icUndo) ); } } void _revertedToOriginalMailbox( MoveToMailboxRequest newMoveRequest, Map emailIdsWithReadStatus, ) { if (accountId != null && session != null) { _moveToMailbox( currentContext!, session!, accountId!, newMoveRequest, emailIdsWithReadStatus, ); } } void moveToTrash(BuildContext context, PresentationEmail email) { final trashMailboxId = mailboxDashBoardController.getMailboxIdByRole(PresentationMailbox.roleTrash); final currentMailbox = getMailboxContain(email); if (session != null && accountId != null && currentMailbox != null && trashMailboxId != null) { _moveToTrashAction( context, session!, accountId!, MoveToMailboxRequest( {currentMailbox.id: [email.id!]}, trashMailboxId, MoveAction.moving, EmailActionType.moveToTrash), {email.id!: email.hasRead}, ); } } void _moveToTrashAction( BuildContext context, Session session, AccountId accountId, MoveToMailboxRequest moveRequest, Map emailIdsWithReadStatus, ) { closeEmailView(context: context); mailboxDashBoardController.moveToMailbox( session, accountId, moveRequest, emailIdsWithReadStatus, ); } void moveToSpam(BuildContext context, PresentationEmail email) { final spamMailboxId = mailboxDashBoardController.spamMailboxId; final currentMailbox = getMailboxContain(email); if (session != null && accountId != null && currentMailbox != null && spamMailboxId != null) { _moveToSpamAction( context, session!, accountId!, MoveToMailboxRequest( {currentMailbox.id: [email.id!]}, spamMailboxId, MoveAction.moving, EmailActionType.moveToSpam), {email.id!: email.hasRead}, ); } } void unSpam(BuildContext context, PresentationEmail email) { final spamMailboxId = mailboxDashBoardController.spamMailboxId; final inboxMailboxId = mailboxDashBoardController.getMailboxIdByRole(PresentationMailbox.roleInbox); if (session != null && accountId != null && spamMailboxId != null && inboxMailboxId != null) { _moveToSpamAction( context, session!, accountId!, MoveToMailboxRequest( {spamMailboxId: [email.id!]}, inboxMailboxId, MoveAction.moving, EmailActionType.unSpam), {email.id!: email.hasRead}, ); } } void _moveToSpamAction( BuildContext context, Session session, AccountId accountId, MoveToMailboxRequest moveRequest, Map emailIdsWithReadStatus, ) { closeEmailView(context: context); mailboxDashBoardController.moveToMailbox( session, accountId, moveRequest, emailIdsWithReadStatus, ); } void markAsStarEmail( PresentationEmail presentationEmail, MarkStarAction markStarAction, ) { if (accountId != null && session != null) { consumeState(_markAsStarEmailInteractor.execute( session!, accountId!, presentationEmail.id!, markStarAction, )); } } void _markAsEmailStarSuccess(MarkAsStarEmailSuccess success) { final newEmail = currentEmail?.updateKeywords({ KeyWordIdentifier.emailFlagged: success.markStarAction == MarkStarAction.markStar, }); mailboxDashBoardController.setSelectedEmail(newEmail); final emailId = newEmail?.id; if (emailId == null) return; mailboxDashBoardController.updateEmailFlagByEmailIds( [emailId], markStarAction: success.markStarAction, ); } void handleEmailAction(BuildContext context, PresentationEmail presentationEmail, EmailActionType actionType) { switch(actionType) { case EmailActionType.markAsUnread: markAsEmailRead(presentationEmail, ReadActions.markAsUnread, MarkReadAction.tap); break; case EmailActionType.markAsStarred: markAsStarEmail(presentationEmail, MarkStarAction.markStar); break; case EmailActionType.unMarkAsStarred: markAsStarEmail(presentationEmail, MarkStarAction.unMarkStar); break; case EmailActionType.moveToMailbox: moveToMailbox(context, presentationEmail); break; case EmailActionType.moveToTrash: moveToTrash(context, presentationEmail); break; case EmailActionType.deletePermanently: deleteEmailPermanently(context, presentationEmail); break; case EmailActionType.moveToSpam: moveToSpam(context, presentationEmail); break; case EmailActionType.unSpam: unSpam(context, presentationEmail); break; case EmailActionType.createRule: quickCreatingRule(context, presentationEmail.from!.first); break; case EmailActionType.unsubscribe: _unsubscribeEmail(context, presentationEmail); break; case EmailActionType.archiveMessage: archiveMessage(context, presentationEmail); break; case EmailActionType.printAll: _printEmail(context, presentationEmail); break; case EmailActionType.downloadMessageAsEML: _downloadMessageAsEML(presentationEmail); break; case EmailActionType.editAsNewEmail: _editAsNewEmail(presentationEmail); break; default: break; } } void openEmailAddressDialog(BuildContext context, EmailAddress emailAddress) { if (responsiveUtils.isScreenWithShortestSide(context)) { (EmailAddressBottomSheetBuilder(context, imagePaths, emailAddress) ..addOnCloseContextMenuAction(() => popBack()) ..addOnCopyEmailAddressAction((emailAddress) => copyEmailAddress(context, emailAddress)) ..addOnComposeEmailAction((emailAddress) => composeEmailFromEmailAddress(emailAddress)) ..addOnQuickCreatingRuleEmailBottomSheetAction((emailAddress) => quickCreatingRule(context, emailAddress)) ).show(); } else { Get.dialog( PointerInterceptor( child: EmailAddressDialogBuilder( emailAddress, onCloseDialogAction: () => popBack(), onCopyEmailAddressAction: (emailAddress) => copyEmailAddress(context, emailAddress), onComposeEmailAction: (emailAddress) => composeEmailFromEmailAddress(emailAddress), onQuickCreatingRuleEmailDialogAction: (emailAddress) => quickCreatingRule(context, emailAddress) ) ), barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } } void copyEmailAddress(BuildContext context, EmailAddress emailAddress) { popBack(); AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); } void composeEmailFromEmailAddress(EmailAddress emailAddress) { popBack(); mailboxDashBoardController.openComposer(ComposerArguments.fromEmailAddress(emailAddress)); } Future 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 ) ); } void deleteEmailPermanently(BuildContext context, PresentationEmail email) { if (responsiveUtils.isMobile(context)) { (ConfirmationDialogActionSheetBuilder(context) ..messageText(DeleteActionType.single.getContentDialog(context)) ..onCancelAction(AppLocalizations.of(context).cancel, () => popBack()) ..onConfirmAction(DeleteActionType.single.getConfirmActionName(context), () => _deleteEmailPermanentlyAction(context, email))) .show(); } else { Get.dialog( PointerInterceptor(child: ConfirmationDialogBuilder( key: const Key('confirm_dialog_delete_email_permanently'), imagePath: imagePaths, title: DeleteActionType.single.getTitleDialog(context), textContent: DeleteActionType.single.getContentDialog(context), cancelText: DeleteActionType.single.getConfirmActionName(context), confirmText: AppLocalizations.of(context).cancel, onCancelButtonAction: () => _deleteEmailPermanentlyAction(context, email), onConfirmButtonAction: popBack, onCloseButtonAction: popBack, )), barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } } void _deleteEmailPermanentlyAction(BuildContext context, PresentationEmail email) { popBack(); closeEmailView(context: context); mailboxDashBoardController.deleteEmailPermanently(email); } void _handleSendReceiptToSenderAction(BuildContext context) { if (accountId == null || session == null) { return; } if (_sendReceiptToSenderInteractor == null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(context).toastMessageNotSupportMdnWhenSendReceipt); return; } if (_identitySelected == null || _identitySelected?.id == null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(context).toastMessageCannotFoundIdentityWhenSendReceipt); return; } if (currentEmail == null || _currentEmailId == null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(context).toastMessageCannotFoundEmailIdWhenSendReceipt); return; } final receiverEmailAddress = _getReceiverEmailAddress(currentEmail!) ?? session!.getOwnEmailAddress(); log('SingleEmailController::_handleSendReceiptToSenderAction():receiverEmailAddress: $receiverEmailAddress'); final mdnToSender = _generateMDN(context, currentEmail!, receiverEmailAddress); final sendReceiptRequest = SendReceiptToSenderRequest( mdn: mdnToSender, identityId: _identitySelected!.id!, sendId: Id(uuid.v1())); log('SingleEmailController::_handleSendReceiptToSenderAction(): sendReceiptRequest: $sendReceiptRequest'); consumeState(_sendReceiptToSenderInteractor!.execute(accountId!, sendReceiptRequest)); } String? _getReceiverEmailAddress(PresentationEmail presentationEmail) { final currentMailbox = getMailboxContain(presentationEmail); if (_isBelongToTeamMailboxes(currentMailbox)) { return currentMailbox!.emailTeamMailBoxes; } else { return null; } } MDN _generateMDN(BuildContext context, PresentationEmail email, String emailAddress) { final receiverEmailAddress = emailAddress; final subjectEmail = email.subject ?? ''; final timeCurrent = DateTime.now(); final timeAsString = '${timeCurrent.formatDate( pattern: 'EEE, d MMM yyyy HH:mm:ss')} (${timeCurrent.timeZoneName})'; final mdnToSender = MDN( forEmailId: email.id, subject: AppLocalizations.of(context).subjectSendReceiptToSender(subjectEmail), textBody: AppLocalizations.of(context).textBodySendReceiptToSender( receiverEmailAddress, subjectEmail, timeAsString), disposition: Disposition( ActionMode.manual, SendingMode.manually, DispositionType.displayed)); return mdnToSender; } void _sendReceiptToSenderSuccess(SendReceiptToSenderSuccess success) { log('SingleEmailController::_sendReceiptToSenderSuccess(): ${success.mdn.toString()}'); if (currentContext != null) { appToast.showToastSuccessMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).toastMessageSendReceiptSuccess, leadingSVGIcon: imagePaths.icReadReceiptMessage); } } void closeEmailView({BuildContext? context}) { if (emailSupervisorController.supportedPageView.isTrue) { emailSupervisorController.popEmailQueue(_currentEmailId); emailSupervisorController.setCurrentEmailIndex(-1); emailSupervisorController.disposePageViewController(); } mailboxDashBoardController.clearSelectedEmail(); _updateCurrentEmailId(null); _resetToOriginalValue(isEmailClosing: true); _replaceBrowserHistory(); if (mailboxDashBoardController.searchController.isSearchEmailRunning) { if (context != null && responsiveUtils.isWebDesktop(context)) { mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread); } else { mailboxDashBoardController.dispatchRoute(DashboardRoutes.searchEmail); } } else { mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread); if (isOpenEmailNotMailboxFromRoute) { mailboxDashBoardController.dispatchMailboxUIAction(SelectMailboxDefaultAction()); } } } bool get isOpenEmailNotMailboxFromRoute => emailSupervisorController.supportedPageView.isFalse && mailboxDashBoardController.selectedMailbox.value == null; void _replaceBrowserHistory() { if (PlatformInfo.isWeb) { final selectedMailboxId = mailboxDashBoardController.selectedMailbox.value?.id; final isSearchRunning = mailboxDashBoardController.searchController.isSearchEmailRunning; RouteUtils.replaceBrowserHistory( title: isSearchRunning ? 'SearchEmail' : 'Mailbox-${selectedMailboxId?.id.value}', url: RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( mailboxId: isSearchRunning ? null : selectedMailboxId, dashboardType: isSearchRunning ? DashboardType.search : DashboardType.normal, searchQuery: isSearchRunning ? mailboxDashBoardController.searchController.searchQuery : null ) ) ); } } void pressEmailAction( EmailActionType emailActionType, PresentationEmail presentationEmail ) { switch(emailActionType) { case EmailActionType.compose: mailboxDashBoardController.openComposer(ComposerArguments()); break; case EmailActionType.reply: mailboxDashBoardController.openComposer( ComposerArguments.replyEmail( presentationEmail: presentationEmail, content: currentEmailLoaded.value?.htmlContent ?? '', inlineImages: currentEmailLoaded.value?.inlineImages ?? [], mailboxRole: presentationEmail.mailboxContain?.role, messageId: currentEmailLoaded.value?.emailCurrent?.messageId, references: currentEmailLoaded.value?.emailCurrent?.references, listPost: currentEmailLoaded.value?.emailCurrent?.listPost, ) ); break; case EmailActionType.replyToList: log('SingleEmailController::pressEmailAction:replyToList'); mailboxDashBoardController.openComposer( ComposerArguments.replyToListEmail( presentationEmail: presentationEmail, content: currentEmailLoaded.value?.htmlContent ?? '', inlineImages: currentEmailLoaded.value?.inlineImages ?? [], mailboxRole: presentationEmail.mailboxContain?.role, messageId: currentEmailLoaded.value?.emailCurrent?.messageId, references: currentEmailLoaded.value?.emailCurrent?.references, listPost: currentEmailLoaded.value?.emailCurrent?.listPost, ) ); break; case EmailActionType.replyAll: mailboxDashBoardController.openComposer( ComposerArguments.replyAllEmail( presentationEmail: presentationEmail, content: currentEmailLoaded.value?.htmlContent ?? '', inlineImages: currentEmailLoaded.value?.inlineImages ?? [], mailboxRole: presentationEmail.mailboxContain?.role, messageId: currentEmailLoaded.value?.emailCurrent?.messageId, references: currentEmailLoaded.value?.emailCurrent?.references, listPost: currentEmailLoaded.value?.emailCurrent?.listPost, ) ); break; case EmailActionType.forward: mailboxDashBoardController.openComposer( ComposerArguments.forwardEmail( presentationEmail: presentationEmail, content: currentEmailLoaded.value?.htmlContent ?? '', attachments: attachments, inlineImages: currentEmailLoaded.value?.inlineImages ?? [], messageId: currentEmailLoaded.value?.emailCurrent?.messageId, references: currentEmailLoaded.value?.emailCurrent?.references, ) ); break; default: break; } } void quickCreatingRule(BuildContext context, EmailAddress emailAddress) async { popBack(); if (accountId != null && session != null) { final arguments = RulesFilterCreatorArguments( accountId!, session!, emailAddress: emailAddress); final newRuleFilterRequest = PlatformInfo.isWeb ? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.rulesFilterCreator, arguments: arguments) : await push(AppRoutes.rulesFilterCreator, arguments: arguments); if (newRuleFilterRequest is CreateNewEmailRuleFilterRequest) { _createNewRuleFilterAction(accountId!, newRuleFilterRequest); } } } void _createNewRuleFilterAction( AccountId accountId, CreateNewEmailRuleFilterRequest ruleFilterRequest ) async { try { _createNewEmailRuleFilterInteractor = Get.find(); } catch (e) { logError('SingleEmailController::onInit(): ${e.toString()}'); } if (_createNewEmailRuleFilterInteractor != null) { consumeState(_createNewEmailRuleFilterInteractor!.execute(accountId, ruleFilterRequest)); } } void _createNewRuleFilterSuccess(CreateNewRuleFilterSuccess success) { if (success.newListRules.isNotEmpty == true) { if (currentOverlayContext != null && currentContext != null) { appToast.showToastSuccessMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).newFilterWasCreated); } } } void toggleScrollPhysicsPagerView(bool leftDirection) { log('SingleEmailController::toggleScrollPhysicsPagerView():leftDirection: $leftDirection'); if (leftDirection) { emailSupervisorController.moveToNextEmail(); } else { emailSupervisorController.backToPreviousEmail(); } } void _storeOpenedEmailAction(Session? session, AccountId? accountId, DetailedEmail detailedEmail) async { if (session != null && accountId != null) { consumeState(_storeOpenedEmailInteractor.execute(session, accountId, detailedEmail)); } } bool _canParseCalendarEvent({required Set blobIds}) { return _isCalendarEventSupported && currentEmail?.hasCalendarEvent == true && blobIds.isNotEmpty && _parseCalendarEventInteractor != null; } bool get _isCalendarEventSupported { return session != null && accountId != null && CapabilityIdentifier.jamesCalendarEvent.isSupported(session!, accountId!); } @visibleForTesting parseCalendarEventAction({ required AccountId accountId, required Set blobIds, }) => _parseCalendarEventAction(accountId: accountId, blobIds: blobIds); void _parseCalendarEventAction({ required AccountId accountId, required Set blobIds, }) { log("SingleEmailController::_parseCalendarEventAction:blobIds: $blobIds"); consumeState(_parseCalendarEventInteractor!.execute( accountId, blobIds, TransformConfiguration.fromTextTransformers(const [ SanitizeAutolinkUnescapeHtmlTransformer(), StandardizeHtmlSanitizingTransformers(), NewLineTransformer(), ]) )); } void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) { emailLoadedViewState.value = Right(success); blobCalendarEvent.value = success.blobCalendarEventList.firstOrNull; if (PlatformInfo.isMobile) { _enableScrollPageView(); } updateAttendanceStatus(success); } void _handleParseCalendarEventFailure(ParseCalendarEventFailure failure) { emailLoadedViewState.value = Left(failure); emailContents.value = currentEmailLoaded.value?.htmlContent; } void _enableScrollPageView() { emailSupervisorController.scrollPhysicsPageView.value = null; } void openNewTabAction(String link) { AppUtils.launchLink(link); } void openNewComposerAction(String mailTo) { final emailAddress = EmailAddress(mailTo, mailTo); mailboxDashBoardController.openComposer(ComposerArguments.fromEmailAddress(emailAddress)); } void openAttachmentList(BuildContext context, List attachments) { if (responsiveUtils.isMobile(context)) { (AttachmentListBottomSheetBuilder(context, attachments, imagePaths, _attachmentListScrollController) ..onCloseButtonAction(() => popBack()) ..onDownloadAttachmentFileAction((attachment) => handleDownloadAttachmentAction(context, attachment)) ..onViewAttachmentFileAction((attachment) => handleViewAttachmentAction(context, attachment)) ..onDownloadAllButtonAction(isDownloadAllSupported() ? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}') : null ) ).show(); } else { Get.dialog( PointerInterceptor( child: AttachmentListDialogBuilder( imagePaths: imagePaths, attachments: attachments, responsiveUtils: responsiveUtils, scrollController: _attachmentListScrollController, backgroundColor: Colors.black.withAlpha(24), onCloseButtonAction: () => popBack(), onDownloadAttachmentFileAction: (attachment) => handleDownloadAttachmentAction(context, attachment), onViewAttachmentFileAction: (attachment) => handleViewAttachmentAction(context, attachment), onDownloadAllButtonAction: isDownloadAllSupported() ? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}') : null, ) ), barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } } void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) { showConfirmDialogAction( context, '', AppLocalizations.of(context).unsubscribe, onConfirmAction: () { if (emailUnsubscribe.value?.httpLinks.isNotEmpty == true) { _handleUnsubscribeMailByHttpsLink( context: context, emailId: presentationEmail.id!, httpLinks: emailUnsubscribe.value!.httpLinks ); } else if (emailUnsubscribe.value?.mailtoLinks.isNotEmpty == true) { _handleUnsubscribeMailByMailtoLink( context: context, emailId: presentationEmail.id!, mailtoLinks: emailUnsubscribe.value!.mailtoLinks ); } }, showAsBottomSheet: true, title: AppLocalizations.of(context).unsubscribeMail, messageStyle: ThemeUtils.textStyleBodyBody2(color: AppColor.steelGray400), listTextSpan: [ TextSpan(text: AppLocalizations.of(context).unsubscribeMailDialogMessage), TextSpan( text: ' ${presentationEmail.getSenderName()}', style: ThemeUtils.textStyleBodyBody2( color: AppColor.steelGray400, fontWeight: FontWeight.w700, ), ), const TextSpan(text: ' ?'), ] ); } void _handleUnsubscribeMailByHttpsLink({ required BuildContext context, required EmailId emailId, required List httpLinks }) { log('SingleEmailController::_handleUnsubscribeMailByHttpsLink:httpLinks: $httpLinks'); mailboxDashBoardController.unsubscribeMail(emailId); AppUtils.launchLink(httpLinks.first); } void _handleUnsubscribeMailByMailtoLink({ required BuildContext context, required EmailId emailId, required List mailtoLinks }) { log('SingleEmailController::_handleUnsubscribeMailByMailtoLink:mailtoLinks: $mailtoLinks'); final navigationRouter = RouteUtils.generateNavigationRouterFromMailtoLink(mailtoLinks.first); mailboxDashBoardController.openComposer( ComposerArguments.fromUnsubscribeMailtoLink( listEmailAddress: navigationRouter.listEmailAddress, subject: navigationRouter.subject, body: navigationRouter.body, previousEmailId: emailId, ) ); } void archiveMessage(BuildContext context, PresentationEmail email) { mailboxDashBoardController.archiveMessage(context, email); } void _printEmail(BuildContext context, PresentationEmail email) { if (_printEmailButtonState == ButtonState.disabled) { log('SingleEmailController::_printEmail: Print email started'); return; } _printEmailButtonState = ButtonState.disabled; final locale = Localizations.localeOf(context); final appLocalizations = AppLocalizations.of(context); consumeState( _printEmailInteractor.execute( EmailPrint( appName: appLocalizations.app_name, userName: mailboxDashBoardController.getOwnEmailAddress(), attachments: currentEmailLoaded.value!.attachments, emailContent: currentEmailLoaded.value!.htmlContent, fromPrefix: appLocalizations.from_email_address_prefix, toPrefix: appLocalizations.to_email_address_prefix, ccPrefix: appLocalizations.cc_email_address_prefix, bccPrefix: appLocalizations.bcc_email_address_prefix, replyToPrefix: appLocalizations.replyToEmailAddressPrefix, titleAttachment: appLocalizations.attachments.toLowerCase(), toAddress: email.to?.listEmailAddressToString(isFullEmailAddress: true), ccAddress: email.cc?.listEmailAddressToString(isFullEmailAddress: true), bccAddress: email.bcc?.listEmailAddressToString(isFullEmailAddress: true), replyToAddress: email.replyTo?.listEmailAddressToString(isFullEmailAddress: true), sender: email.from?.isNotEmpty == true ? email.from!.first : null, receiveTime: email.getReceivedAt( locale.toLanguageTag(), pattern: email.receivedAt ?.value .toLocal() .toPatternForPrinting(locale.toLanguageTag()), ), subject: email.subject, ) ) ); } void _showMessageWhenStartingEmailPrinting() { if (currentOverlayContext != null && currentContext != null) { appToast.showToastMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).printingInProgress, leadingSVGIconColor: AppColor.primaryColor, leadingSVGIcon: imagePaths.icPrinter); } } void _handlePrintEmailSuccess(PrintEmailSuccess success) { _printEmailButtonState = ButtonState.enabled; } void _showMessageWhenEmailPrintingFailed(PrintEmailFailure failure) { _printEmailButtonState = ButtonState.enabled; if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).printingFailed); } } void onCalendarEventReplyAction(EventActionType eventActionType, EmailId emailId) { switch (eventActionType) { case EventActionType.yes: _acceptCalendarEventAction(emailId); break; case EventActionType.maybe: _maybeCalendarEventAction(emailId); break; case EventActionType.no: _rejectCalendarEventAction(emailId); break; default: break; } } void _acceptCalendarEventAction(EmailId emailId) { if (_acceptCalendarEventInteractor == null || _displayingEventBlobId == null || accountId == null || session == null || session!.validateCalendarEventCapability(accountId!).isAvailable == false ) { consumeState(Stream.value(Left(CalendarEventAcceptFailure()))); } else { consumeState(_acceptCalendarEventInteractor!.execute( accountId!, {_displayingEventBlobId!}, emailId, session!.getLanguageForCalendarEvent( LocalizationService.getLocaleFromLanguage(), accountId!, ), )); } } void _rejectCalendarEventAction(EmailId emailId) { if (_rejectCalendarEventInteractor == null || _displayingEventBlobId == null || accountId == null || session == null || session!.validateCalendarEventCapability(accountId!).isAvailable == false ) { consumeState(Stream.value(Left(CalendarEventRejectFailure()))); } else { consumeState(_rejectCalendarEventInteractor!.execute( accountId!, {_displayingEventBlobId!}, emailId, session!.getLanguageForCalendarEvent( LocalizationService.getLocaleFromLanguage(), accountId!, ), )); } } void _maybeCalendarEventAction(EmailId emailId) { if (_maybeCalendarEventInteractor == null || _displayingEventBlobId == null || accountId == null || session == null || session!.validateCalendarEventCapability(accountId!).isAvailable == false ) { consumeState(Stream.value(Left(CalendarEventMaybeFailure()))); } else { consumeState(_maybeCalendarEventInteractor!.execute( accountId!, {_displayingEventBlobId!}, emailId, session!.getLanguageForCalendarEvent( LocalizationService.getLocaleFromLanguage(), accountId!, ), )); } } void calendarEventSuccess(CalendarEventReplySuccess success) { updateAttendanceStatus(success); if (currentOverlayContext == null || currentContext == null) { return; } appToast.showToastSuccessMessage( currentOverlayContext!, success.getEventActionType().getToastMessageSuccess(currentContext!)); } void _calendarEventFailure(Failure failure) { if (currentOverlayContext == null || currentContext == null) { return; } if (failure is CalendarEventReplyFailure && failure.exception is CannotReplyCalendarEventException) { final replyEventException = failure.exception as CannotReplyCalendarEventException; if (replyEventException.mapErrors?.isNotEmpty == true) { appToast.showToastErrorMessage( currentOverlayContext!, replyEventException.mapErrors!.values.first.description ?? AppLocalizations.of(currentContext!).eventReplyWasSentUnsuccessfully); return; } } appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).eventReplyWasSentUnsuccessfully); } void _downloadMessageAsEML(PresentationEmail presentationEmail) { final emlAttachment = presentationEmail.createEMLAttachment(); if (emlAttachment.blobId == null) { consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(exception: NotFoundEmailBlobIdException())))); return; } downloadAttachmentForWeb(emlAttachment); } void _editAsNewEmail(PresentationEmail presentationEmail) { if (accountId == null || session == null) return; mailboxDashBoardController.openComposer( ComposerArguments.editAsNewEmail(presentationEmail), ); } void handleDownloadAttachmentAction( BuildContext context, Attachment attachment, {bool previewerSupported = false} ) { if (PlatformInfo.isWeb) { downloadAttachmentForWeb(attachment, previewerSupported: previewerSupported); } else if (PlatformInfo.isMobile) { exportAttachment(context, attachment); } else { log('EmailView::handleDownloadAttachmentAction: THE PLATFORM IS SUPPORTED'); } } 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); } 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( context, 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 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.withOpacity(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!, userName: userName!, 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( 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(context, uri), ), ); }, ); } void showDialogToPreviewEMLAttachment(BuildContext context, EMLPreviewer emlPreviewer) { Get.dialog( EmailPreviewerDialogView( emlPreviewer: emlPreviewer, imagePaths: imagePaths, onMailtoDelegateAction: openMailToLink, onPreviewEMLDelegateAction: (uri) => _openEMLPreviewer(context, uri), onDownloadAttachmentDelegateAction: (uri) => _downloadAttachmentInEMLPreview(context, uri), ), barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } Future _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; previewEMLFileAction(Id(blobId), AppLocalizations.of(context)); } Future _downloadAttachmentInEMLPreview(BuildContext context, Uri? uri) async { log('SingleEmailController::_downloadAttachmentInEMLPreview:uri = $uri'); if (uri == null) return; final attachment = EmailUtils.parsingAttachmentByUri(uri); if (attachment == null) return; handleDownloadAttachmentAction(context, attachment); } void handleMailToAttendees(CalendarOrganizer? organizer, List? attendees) { List listEmailAddressAttendees = []; if (organizer != null) { listEmailAddressAttendees.add(organizer.toEmailAddress()); } final listEmailAddress = attendees ?.map((attendee) => attendee.toEmailAddress()) .toList() ?? []; listEmailAddressAttendees.addAll(listEmailAddress); final currentUserEmail = mailboxDashBoardController.getOwnEmailAddress(); final listEmailAddressMailTo = listEmailAddressAttendees.removeInvalidEmails(currentUserEmail); log('SingleEmailController::handleMailToAttendees: listEmailAddressMailTo = $listEmailAddressMailTo'); mailboxDashBoardController.openComposer( ComposerArguments.fromMailtoUri(listEmailAddress: listEmailAddressMailTo) ); } void _handleGetHtmlContentFromAttachmentFailure(GetHtmlContentFromAttachmentFailure failure) { _updateAttachmentsViewState(failure.attachment.blobId, Left(failure)); if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).thisHtmlAttachmentCannotBePreviewed); } } String getOwnEmailAddress() { try { return session?.getOwnEmailAddress() ?? ''; } catch (e) { return ''; } } void onHtmlContentClippedAction(bool isClipped) { log('SingleEmailController::onHtmlContentClippedAction:isClipped = $isClipped'); isEmailContentClipped.value = isClipped; } Future onViewEntireMessage({ required BuildContext context, required PresentationEmail presentationEmail, required String emailContent, }) async { final getEntireMessageAsDocumentInteractor = getBinding(); if (getEntireMessageAsDocumentInteractor == null) return; final appLocalizations = AppLocalizations.of(context); SmartDialog.showLoading( msg: appLocalizations.loadingPleaseWait, maskColor: Colors.black38, ); final viewEntireMessageRequest = ViewEntireMessageRequest( userName: userName ?? UserName(''), presentationEmail: presentationEmail, attachments: attachments, emailContent: emailContent, locale: Localizations.localeOf(context), appLocalizations: appLocalizations, ); final resultState = await getEntireMessageAsDocumentInteractor .execute(viewEntireMessageRequest) .last; resultState.foldSuccess( onSuccess: (success) { SmartDialog.dismiss(); showDialogToPreviewEMLAttachment( context, EMLPreviewer( id: presentationEmail.id?.asString ?? '', title: presentationEmail.subject ?? '', content: success.messageDocument, ), ); }, onFailure: (_) { SmartDialog.dismiss(); } ); } }