diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index 17a0e36752..5c0a523d15 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -133,6 +133,8 @@ extension EmailActionTypeExtension on EmailActionType { return imagePaths.icUnsubscribe; case EmailActionType.archiveMessage: return imagePaths.icMailboxArchived; + case EmailActionType.downloadMessageAsEML: + return imagePaths.icDownloadAttachment; default: return ''; } @@ -152,6 +154,8 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).unsubscribe; case EmailActionType.archiveMessage: return AppLocalizations.of(context).archiveMessage; + case EmailActionType.downloadMessageAsEML: + return AppLocalizations.of(context).downloadMessageAsEML; default: return ''; } diff --git a/lib/features/email/presentation/bindings/email_bindings.dart b/lib/features/email/presentation/bindings/email_bindings.dart index 19f0b8aeba..dcdcabb6df 100644 --- a/lib/features/email/presentation/bindings/email_bindings.dart +++ b/lib/features/email/presentation/bindings/email_bindings.dart @@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/email/data/repository/email_repository_im import 'package:tmail_ui_user/features/email/domain/repository/email_repository.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/download_message_as_eml_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_stored_email_state_interactor.dart'; @@ -69,7 +70,8 @@ class EmailBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), - Get.find() + Get.find(), + Get.find(), )); } @@ -152,6 +154,11 @@ class EmailBindings extends BaseBindings { Get.find())); Get.lazyPut(() => StoreOpenedEmailInteractor(Get.find())); Get.lazyPut(() => PrintEmailInteractor(Get.find())); + Get.lazyPut(() => DownloadMessageAsEMLInteractor( + Get.find(), + Get.find(), + Get.find(), + Get.find())); IdentityInteractorsBindings().dependencies(); } diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index be310b4fd2..e58d142f35 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -28,6 +28,7 @@ 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/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/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'; @@ -38,6 +39,7 @@ import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_reques import 'package:tmail_ui_user/features/email/domain/model/send_receipt_to_sender_request.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/download_message_as_eml_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/mark_as_email_read_state.dart'; @@ -50,6 +52,7 @@ import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_stat import 'package:tmail_ui_user/features/email/domain/state/view_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/email/domain/usecases/download_attachments_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/download_message_as_eml_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'; @@ -115,6 +118,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final StoreOpenedEmailInteractor _storeOpenedEmailInteractor; final ViewAttachmentForWebInteractor _viewAttachmentForWebInteractor; final PrintEmailInteractor _printEmailInteractor; + final DownloadMessageAsEMLInteractor _downloadMessageAsEMLInteractor; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor; @@ -154,6 +158,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { this._storeOpenedEmailInteractor, this._viewAttachmentForWebInteractor, this._printEmailInteractor, + this._downloadMessageAsEMLInteractor, ); @override @@ -212,6 +217,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _showMessageWhenStartingEmailPrinting(); } else if (success is PrintEmailSuccess) { _handlePrintEmailSuccess(success); + } else if (success is StartDownloadMessageAsEML) { + _showMessageWhenStartingDownloadMessageAsEML(); } } @@ -1118,6 +1125,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin { case EmailActionType.printAll: _printEmail(context, presentationEmail); break; + case EmailActionType.downloadMessageAsEML: + _downloadMessageAsEML(presentationEmail); + break; default: break; } @@ -1671,4 +1681,39 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ) ); } + + void _downloadMessageAsEML(PresentationEmail presentationEmail) { + final accountId = mailboxDashBoardController.accountId.value; + final session = mailboxDashBoardController.sessionCurrent; + + if (accountId == null || session == null) { + consumeState(Stream.value(Left(DownloadMessageAsEMLFailure(NotFoundSessionException())))); + return; + } + + final blobId = presentationEmail.blobId; + if (blobId == null) { + consumeState(Stream.value(Left(DownloadMessageAsEMLFailure(NotFoundEmailBlobIdException())))); + return; + } + + final baseDownloadUrl = session.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + + consumeState(_downloadMessageAsEMLInteractor.execute( + accountId, + baseDownloadUrl, + blobId, + presentationEmail.getEmailTitle() + )); + } + + void _showMessageWhenStartingDownloadMessageAsEML() { + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).downloadMessageAsEMLInProgress, + leadingSVGIconColor: AppColor.primaryColor, + leadingSVGIcon: imagePaths.icDownloadAttachment); + } + } } \ No newline at end of file diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 3af984622a..5e5f44f837 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -471,6 +471,8 @@ class EmailView extends GetWidget { EmailActionType.unsubscribe, if (mailboxContain?.isArchive == false) EmailActionType.archiveMessage, + if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit) + EmailActionType.downloadMessageAsEML ]; if (position == null) { diff --git a/lib/features/thread/data/extensions/email_cache_extension.dart b/lib/features/thread/data/extensions/email_cache_extension.dart index 5d101f04ab..340f472ff2 100644 --- a/lib/features/thread/data/extensions/email_cache_extension.dart +++ b/lib/features/thread/data/extensions/email_cache_extension.dart @@ -15,6 +15,7 @@ extension EmailCacheExtension on EmailCache { Email toEmail() { return Email( id: EmailId(Id(id)), + blobId: blobId != null ? Id(blobId!) : null, keywords: keywords != null ? Map.fromIterables(keywords!.keys.map((value) => KeyWordIdentifier(value)), keywords!.values) : null, diff --git a/lib/features/thread/data/extensions/email_extension.dart b/lib/features/thread/data/extensions/email_extension.dart index d6a02dd60d..a7dca55f26 100644 --- a/lib/features/thread/data/extensions/email_extension.dart +++ b/lib/features/thread/data/extensions/email_extension.dart @@ -11,6 +11,7 @@ extension EmailExtension on Email { EmailCache toEmailCache() { return EmailCache( id!.id.value, + blobId: blobId?.value, keywords: keywords?.toMapString(), size: size?.value.round(), receivedAt: receivedAt?.value, diff --git a/lib/features/thread/data/model/email_cache.dart b/lib/features/thread/data/model/email_cache.dart index 6db80e1d50..7bd49c54d3 100644 --- a/lib/features/thread/data/model/email_cache.dart +++ b/lib/features/thread/data/model/email_cache.dart @@ -13,50 +13,54 @@ class EmailCache extends HiveObject with EquatableMixin { final String id; @HiveField(1) - final Map? keywords; + final String? blobId; @HiveField(2) - final int? size; + final Map? keywords; @HiveField(3) - final DateTime? receivedAt; + final int? size; @HiveField(4) - final bool? hasAttachment; + final DateTime? receivedAt; @HiveField(5) - final String? preview; + final bool? hasAttachment; @HiveField(6) - final String? subject; + final String? preview; @HiveField(7) - final DateTime? sentAt; + final String? subject; @HiveField(8) - final List? from; + final DateTime? sentAt; @HiveField(9) - final List? to; + final List? from; @HiveField(10) - final List? cc; + final List? to; @HiveField(11) - final List? bcc; + final List? cc; @HiveField(12) - final List? replyTo; + final List? bcc; @HiveField(13) - Map? mailboxIds; + final List? replyTo; @HiveField(14) + Map? mailboxIds; + + @HiveField(15) Map? headerCalendarEvent; EmailCache( this.id, { + this.blobId, this.keywords, this.size, this.receivedAt, @@ -77,6 +81,7 @@ class EmailCache extends HiveObject with EquatableMixin { @override List get props => [ id, + blobId, subject, from, to, diff --git a/lib/features/thread/domain/constants/thread_constants.dart b/lib/features/thread/domain/constants/thread_constants.dart index b72edf5be0..a5da5b4027 100644 --- a/lib/features/thread/domain/constants/thread_constants.dart +++ b/lib/features/thread/domain/constants/thread_constants.dart @@ -8,6 +8,7 @@ class ThreadConstants { static final defaultLimit = UnsignedInt(maxCountEmails); static final propertiesDefault = Properties({ EmailProperty.id, + EmailProperty.blobId, EmailProperty.subject, EmailProperty.from, EmailProperty.to, @@ -29,6 +30,7 @@ class ThreadConstants { static final propertiesQuickSearch = Properties({ EmailProperty.id, + EmailProperty.blobId, EmailProperty.subject, EmailProperty.from, EmailProperty.to, @@ -53,6 +55,7 @@ class ThreadConstants { static final propertiesGetDetailedEmail = Properties({ EmailProperty.id, + EmailProperty.blobId, EmailProperty.subject, EmailProperty.from, EmailProperty.to, @@ -74,6 +77,7 @@ class ThreadConstants { static final propertiesCalendarEvent = Properties({ EmailProperty.id, + EmailProperty.blobId, EmailProperty.subject, EmailProperty.from, EmailProperty.to, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 7d4629b125..e60d177d16 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-04-19T16:31:35.757887", + "@@last_modified": "2024-05-07T16:16:03.932801", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3695,5 +3695,17 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "downloadMessageAsEML": "Download message as EML", + "@downloadMessageAsEML": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "downloadMessageAsEMLInProgress": "Download message as EML in progress", + "@downloadMessageAsEMLInProgress": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 86450eae04..eb141c0749 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3851,4 +3851,18 @@ class AppLocalizations { name: 'noSuitableBrowserForOIDC' ); } + + String get downloadMessageAsEML { + return Intl.message( + 'Download message as EML', + name: 'downloadMessageAsEML', + ); + } + + String get downloadMessageAsEMLInProgress { + return Intl.message( + 'Download message as EML in progress', + name: 'downloadMessageAsEMLInProgress' + ); + } } \ No newline at end of file diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index e9a7b4460f..a125b4c3ae 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -27,5 +27,6 @@ enum EmailActionType { unsubscribe, composeFromUnsubscribeMailtoLink, archiveMessage, - printAll + printAll, + downloadMessageAsEML } \ No newline at end of file diff --git a/model/lib/email/email_property.dart b/model/lib/email/email_property.dart index f705555db8..d1981e4043 100644 --- a/model/lib/email/email_property.dart +++ b/model/lib/email/email_property.dart @@ -1,6 +1,7 @@ class EmailProperty { static const String id = 'id'; + static const String blobId = 'blobId'; static const String keywords = 'keywords'; static const String size = 'size'; static const String receivedAt = 'receivedAt'; diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index 599aced126..999605dd79 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/extensions/string_extension.dart'; import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; @@ -21,6 +22,7 @@ import 'package:model/mailbox/select_mode.dart'; class PresentationEmail with EquatableMixin { final EmailId? id; + final Id? blobId; final Map? keywords; final UnsignedInt? size; final UTCDate? receivedAt; @@ -44,6 +46,7 @@ class PresentationEmail with EquatableMixin { PresentationEmail({ this.id, + this.blobId, this.keywords, this.size, this.receivedAt, @@ -142,6 +145,7 @@ class PresentationEmail with EquatableMixin { @override List get props => [ id, + blobId, keywords, size, receivedAt, diff --git a/model/lib/extensions/email_extension.dart b/model/lib/extensions/email_extension.dart index a7738ebc37..7b7707abde 100644 --- a/model/lib/extensions/email_extension.dart +++ b/model/lib/extensions/email_extension.dart @@ -67,6 +67,7 @@ extension EmailExtension on Email { Email updatedEmail({Map? newKeywords, Map? newMailboxIds}) { return Email( id: id, + blobId: blobId, keywords: newKeywords ?? keywords, size: size, receivedAt: receivedAt, @@ -91,6 +92,7 @@ extension EmailExtension on Email { PresentationEmail toPresentationEmail({SelectMode selectMode = SelectMode.INACTIVE}) { return PresentationEmail( id: id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, @@ -113,6 +115,7 @@ extension EmailExtension on Email { Email combineEmail(Email newEmail, Properties updatedProperties) { return Email( id: newEmail.id, + blobId: updatedProperties.contain(EmailProperty.blobId) ? newEmail.blobId : blobId, keywords: updatedProperties.contain(EmailProperty.keywords) ? newEmail.keywords : keywords, size: updatedProperties.contain(EmailProperty.size) ? newEmail.size : size, receivedAt: updatedProperties.contain(EmailProperty.receivedAt) ? newEmail.receivedAt : receivedAt, @@ -171,6 +174,7 @@ extension EmailExtension on Email { ) { return PresentationEmail( id: emailId ?? id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, diff --git a/model/lib/extensions/presentation_email_extension.dart b/model/lib/extensions/presentation_email_extension.dart index efab88131c..e16c7e05a7 100644 --- a/model/lib/extensions/presentation_email_extension.dart +++ b/model/lib/extensions/presentation_email_extension.dart @@ -44,6 +44,7 @@ extension PresentationEmailExtension on PresentationEmail { PresentationEmail toggleSelect() { return PresentationEmail( id: this.id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, @@ -67,6 +68,7 @@ extension PresentationEmailExtension on PresentationEmail { PresentationEmail toSelectedEmail({required SelectMode selectMode}) { return PresentationEmail( id: this.id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, @@ -90,6 +92,7 @@ extension PresentationEmailExtension on PresentationEmail { Email toEmail() { return Email( id: this.id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, @@ -146,6 +149,7 @@ extension PresentationEmailExtension on PresentationEmail { return PresentationEmail( id: this.id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, @@ -182,6 +186,7 @@ extension PresentationEmailExtension on PresentationEmail { PresentationEmail withRouteWeb(Uri routeWeb) { return PresentationEmail( id: this.id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, @@ -205,6 +210,7 @@ extension PresentationEmailExtension on PresentationEmail { PresentationEmail updateKeywords(Map? newKeywords) { return PresentationEmail( id: this.id, + blobId: blobId, keywords: newKeywords, size: size, receivedAt: receivedAt, @@ -228,6 +234,7 @@ extension PresentationEmailExtension on PresentationEmail { PresentationEmail syncPresentationEmail({PresentationMailbox? mailboxContain, Uri? routeWeb}) { return PresentationEmail( id: this.id, + blobId: blobId, keywords: keywords, size: size, receivedAt: receivedAt, diff --git a/test/features/email/presentation/controller/single_email_controller_test.dart b/test/features/email/presentation/controller/single_email_controller_test.dart index c3233e3d45..3f02248c44 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/email/domain/state/view_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/email/domain/usecases/download_attachments_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/download_message_as_eml_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'; @@ -71,6 +72,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -102,6 +104,7 @@ void main() { final responsiveUtils = MockResponsiveUtils(); final uuid = MockUuid(); final printEmailInteractor = MockPrintEmailInteractor(); + final downloadMessageAsEMLInteractor = MockDownloadMessageAsEMLInteractor(); late SingleEmailController singleEmailController = SingleEmailController( getEmailContentInteractor, @@ -116,6 +119,7 @@ void main() { storeOpenedEmailInteractor, viewAttachmentForWebInteractor, printEmailInteractor, + downloadMessageAsEMLInteractor, ); final testAccountId = AccountId(Id('123'));