diff --git a/core/lib/core.dart b/core/lib/core.dart index 1d92e3eef..048775c12 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -14,9 +14,9 @@ export 'presentation/extensions/compare_string_extension.dart'; export 'presentation/extensions/compare_list_extensions.dart'; export 'presentation/extensions/string_extension.dart'; export 'presentation/extensions/tap_down_details_extension.dart'; -export 'domain/extensions/media_type_extension.dart'; export 'presentation/extensions/map_extensions.dart'; export 'presentation/extensions/either_view_state_extension.dart'; +export 'presentation/extensions/media_type_extension.dart'; // Exceptions export 'domain/exceptions/download_file_exception.dart'; @@ -51,6 +51,7 @@ export 'utils/broadcast_channel/broadcast_channel.dart'; export 'utils/list_utils.dart'; export 'utils/mail/domain.dart'; export 'utils/mail/mail_address.dart'; +export 'utils/preview_eml_file_utils.dart'; // Views export 'presentation/views/text/slogan_builder.dart'; diff --git a/core/lib/data/constants/constant.dart b/core/lib/data/constants/constant.dart index 06b5d45ea..93968f5ad 100644 --- a/core/lib/data/constants/constant.dart +++ b/core/lib/data/constants/constant.dart @@ -9,4 +9,5 @@ class Constant { static const imageType = 'image'; static const textVCardMimeType = 'text/x-vcard'; static const textPlainMimeType = 'text/plain'; + static const emlMimeType = 'message/rfc822'; } \ No newline at end of file diff --git a/core/lib/domain/extensions/media_type_extension.dart b/core/lib/domain/extensions/media_type_extension.dart deleted file mode 100644 index 79e719af4..000000000 --- a/core/lib/domain/extensions/media_type_extension.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:core/domain/preview/document_uti.dart'; -import 'package:core/domain/preview/supported_preview_file_types.dart'; -import 'package:http_parser/http_parser.dart'; - -extension MediaTypeExtension on MediaType { - bool isAndroidSupportedPreview() => SupportedPreviewFileTypes.androidSupportedTypes.contains(mimeType); - - bool isIOSSupportedPreview() => SupportedPreviewFileTypes.iOSSupportedTypes.containsKey(mimeType); - - bool isImageFile() => SupportedPreviewFileTypes.imageMimeTypes.contains(mimeType); - - bool isDocFile() => SupportedPreviewFileTypes.docMimeTypes.contains(mimeType); - - bool isPowerPointFile() => SupportedPreviewFileTypes.pptMimeTypes.contains(mimeType); - - bool isExcelFile() => SupportedPreviewFileTypes.xlsMimeTypes.contains(mimeType); - - bool isZipFile() => SupportedPreviewFileTypes.zipMimeTypes.contains(mimeType); - - bool isPdfFile() => SupportedPreviewFileTypes.pdfMimeTypes.contains(mimeType); - - DocumentUti getDocumentUti() => DocumentUti(SupportedPreviewFileTypes.iOSSupportedTypes[mimeType]); -} diff --git a/core/lib/domain/preview/supported_preview_file_types.dart b/core/lib/domain/preview/supported_preview_file_types.dart index 2689a1d1e..99df0bdbf 100644 --- a/core/lib/domain/preview/supported_preview_file_types.dart +++ b/core/lib/domain/preview/supported_preview_file_types.dart @@ -14,9 +14,7 @@ class SupportedPreviewFileTypes { 'application/msword', 'application/vnd.ms-works']; - static const pdfMimeTypes = [ - 'application/pdf', - 'application/rtf']; + static const rtfMimeTypes = ['application/rtf']; static const xlsMimeTypes = [ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', diff --git a/core/lib/presentation/extensions/media_type_extension.dart b/core/lib/presentation/extensions/media_type_extension.dart new file mode 100644 index 000000000..dde1016fd --- /dev/null +++ b/core/lib/presentation/extensions/media_type_extension.dart @@ -0,0 +1,50 @@ +import 'package:core/data/constants/constant.dart'; +import 'package:core/domain/preview/document_uti.dart'; +import 'package:core/domain/preview/supported_preview_file_types.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:http_parser/http_parser.dart'; + +extension MediaTypeExtension on MediaType { + bool isAndroidSupportedPreview() => SupportedPreviewFileTypes.androidSupportedTypes.contains(mimeType); + + bool isIOSSupportedPreview() => SupportedPreviewFileTypes.iOSSupportedTypes.containsKey(mimeType); + + bool isImageFile() => SupportedPreviewFileTypes.imageMimeTypes.contains(mimeType); + + bool isDocFile() => SupportedPreviewFileTypes.docMimeTypes.contains(mimeType); + + bool isPowerPointFile() => SupportedPreviewFileTypes.pptMimeTypes.contains(mimeType); + + bool isExcelFile() => SupportedPreviewFileTypes.xlsMimeTypes.contains(mimeType); + + bool isZipFile() => SupportedPreviewFileTypes.zipMimeTypes.contains(mimeType); + + bool isRtfFile() => SupportedPreviewFileTypes.rtfMimeTypes.contains(mimeType); + + DocumentUti getDocumentUti() => DocumentUti(SupportedPreviewFileTypes.iOSSupportedTypes[mimeType]); + + String getIcon(ImagePaths imagePaths, {String? fileName}) { + if (isPDFFile(fileName: fileName) == true || isRtfFile()) { + return imagePaths.icFilePdf; + } else if (isDocFile()) { + return imagePaths.icFileDocx; + } else if (isExcelFile()) { + return imagePaths.icFileXlsx; + } else if (isPowerPointFile()) { + return imagePaths.icFilePptx; + } else if (isZipFile()) { + return imagePaths.icFileZip; + } else if (isImageFile()) { + return imagePaths.icFilePng; + } else { + return imagePaths.icFileEPup; + } + } + + bool isPDFFile({required String? fileName}) => + mimeType == Constant.pdfMimeType || + (mimeType == Constant.octetStreamMimeType && + fileName?.endsWith(Constant.pdfExtension) == true); + + bool get isEMLFile => mimeType == Constant.emlMimeType; +} diff --git a/core/lib/utils/html/html_template.dart b/core/lib/utils/html/html_template.dart index e0ee7bef8..c9a06e955 100644 --- a/core/lib/utils/html/html_template.dart +++ b/core/lib/utils/html/html_template.dart @@ -1,5 +1,6 @@ class HtmlTemplate { + static const String fontLink = ''; static const String nameClassToolTip = 'tmail-tooltip'; static const String tooltipLinkCss = ''' .$nameClassToolTip .tooltiptext { @@ -64,4 +65,154 @@ class HtmlTemplate { } '''; + + static const String previewEMLFileCssStyle = ''' + + '''; } \ No newline at end of file diff --git a/core/lib/utils/html/html_utils.dart b/core/lib/utils/html/html_utils.dart index 63a212074..4bc73c69e 100644 --- a/core/lib/utils/html/html_utils.dart +++ b/core/lib/utils/html/html_utils.dart @@ -421,4 +421,28 @@ class HtmlUtils { const fileSizeSpan = document.getElementById('file-size'); fileSizeSpan.textContent = formatFileSize(bytesJs.length);'''; } + + static void openNewWindowByUrl( + String url, + { + int width = 800, + int height = 600, + } + ) async { + try { + final screenWidth = html.window.screen?.width ?? width; + final screenHeight = html.window.screen?.height ?? height; + + final left = (screenWidth - width) ~/ 2; + final top = (screenHeight - height) ~/ 2; + + final options = 'width=$width,height=$height,top=$top,left=$left'; + + html.window.open(url, '_blank', options); + + html.Url.revokeObjectUrl(url); + } catch (e) { + logError('AppUtils::openNewWindowByUrl:Exception = $e'); + } + } } diff --git a/core/lib/utils/preview_eml_file_utils.dart b/core/lib/utils/preview_eml_file_utils.dart new file mode 100644 index 000000000..78cc2f502 --- /dev/null +++ b/core/lib/utils/preview_eml_file_utils.dart @@ -0,0 +1,179 @@ + +import 'package:core/data/model/print_attachment.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/html/html_template.dart'; +import 'package:core/utils/html/html_utils.dart'; +import 'package:html/dom.dart'; +import 'package:html/parser.dart'; + +class PreviewEmlFileUtils { + + Element? _createEmailElement({ + required String subjectPrefix, + required String fromPrefix, + required String toPrefix, + required String ccPrefix, + required String bccPrefix, + required String replyToPrefix, + required String titleAttachment, + String? subject, + String? senderName, + String? senderEmailAddress, + String? toAddress, + String? ccAddress, + String? bccAddress, + String? replyToAddress, + String? dateTime, + String? attachmentIcon, + String? emailContent, + List? listAttachment, + }) { + try { + return Element.html(''' +
+ + + + + + + + + + ${listAttachment?.isNotEmpty == true ? _createAttachmentsElement(listAttachment: listAttachment ?? [], titleAttachment: titleAttachment) : ''} + '''); + } catch (e) { + logError('PreviewEmlFileUtils::_createEmailElement: Exception = $e'); + return null; + } + } + + String _createRecipientHtmlTag(String prefix, String emailAddress) { + try { + return ''' +
+ $prefix: $emailAddress +
+ '''; + } catch (e) { + logError('PreviewEmlFileUtils::_createRecipientHtmlTag: Exception = $e'); + return ''; + } + } + + String _createAttachmentHtmlTag(PrintAttachment printAttachment) { + return ''' +
+
+ attachment-icon +
+
+
${printAttachment.name}
+
${printAttachment.size}
+
+
+ '''; + } + + String _createAttachmentsElement({ + required String titleAttachment, + required List listAttachment + }) { + try { + return ''' +
+ +
${listAttachment.length} $titleAttachment
+ + + ${listAttachment.map((attachment) => _createAttachmentHtmlTag(attachment)).toList().join('\n')} +
+ '''; + } catch (e) { + logError('PreviewEmlFileUtils::_createAttachmentsElement: Exception = $e'); + return ''; + } + } + + String generatePreviewEml({ + required String appName, + required String userName, + required String subjectPrefix, + required String subject, + required String emailContent, + required String senderName, + required String senderEmailAddress, + required String dateTime, + required String fromPrefix, + required String toPrefix, + required String ccPrefix, + required String bccPrefix, + required String replyToPrefix, + required String titleAttachment, + required String attachmentIcon, + String? toAddress, + String? ccAddress, + String? bccAddress, + String? replyToAddress, + List? listAttachment, + }) { + Document document = parse(HtmlUtils.createTemplateHtmlDocument(title: '$subject - $userName')); + + Element? emailElement = _createEmailElement( + subjectPrefix: subjectPrefix, + fromPrefix: fromPrefix, + toPrefix: toPrefix, + ccPrefix: ccPrefix, + bccPrefix: bccPrefix, + replyToPrefix: replyToPrefix, + subject: subject, + toAddress: toAddress, + ccAddress: ccAddress, + bccAddress: bccAddress, + replyToAddress: replyToAddress, + senderName: senderName, + senderEmailAddress: senderEmailAddress, + dateTime: dateTime, + emailContent: emailContent, + attachmentIcon: attachmentIcon, + titleAttachment: titleAttachment, + listAttachment: listAttachment, + ); + + Element linkFontElement = Element.html(HtmlTemplate.fontLink); + document.head?.append(linkFontElement); + + Element styleElement = Element.html(HtmlTemplate.previewEMLFileCssStyle); + document.head?.append(styleElement); + + if (emailElement != null) { + document.body?.append(emailElement); + } + + final htmlDocument = document.outerHtml; + + return htmlDocument; + } +} diff --git a/lib/features/caching/exceptions/local_storage_exception.dart b/lib/features/caching/exceptions/local_storage_exception.dart new file mode 100644 index 000000000..0c8c81cb9 --- /dev/null +++ b/lib/features/caching/exceptions/local_storage_exception.dart @@ -0,0 +1 @@ +class NotFoundDataWithThisKeyException implements Exception {} \ No newline at end of file diff --git a/lib/features/caching/utils/local_storage_manager.dart b/lib/features/caching/utils/local_storage_manager.dart new file mode 100644 index 000000000..0ff67c6d6 --- /dev/null +++ b/lib/features/caching/utils/local_storage_manager.dart @@ -0,0 +1,27 @@ + +import 'package:collection/collection.dart'; +import 'package:tmail_ui_user/features/caching/exceptions/local_storage_exception.dart'; +import 'package:universal_html/html.dart' as html; + +class LocalStorageManager { + + void save(String key, String value) { + html.window.localStorage.addAll({key: value}); + } + + String get(String key) { + final entry = html.window.localStorage + .entries + .firstWhereOrNull((entry) => entry.key == key); + + if (entry != null) { + return entry.value; + } else { + throw NotFoundDataWithThisKeyException(); + } + } + + void remove(String key) { + html.window.localStorage.remove(key); + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index 903399479..e2ad10338 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -2,6 +2,7 @@ import 'package:core/core.dart'; import 'package:core/utils/application_manager.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/contact_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/datasource_impl/composer_datasource_impl.dart'; @@ -24,6 +25,7 @@ import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dar import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/print_file_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; @@ -131,6 +133,9 @@ class ComposerBindings extends BaseBindings { Get.find(), Get.find(), Get.find())); + Get.lazyPut(() => EmailLocalStorageDataSourceImpl( + Get.find(), + Get.find())); Get.lazyPut(() => RemoteServerSettingsDataSourceImpl( Get.find(), Get.find())); @@ -170,7 +175,8 @@ class ComposerBindings extends BaseBindings { Get.lazyPut(() => EmailRepositoryImpl( { DataSourceType.network: Get.find(), - DataSourceType.hiveCache: Get.find() + DataSourceType.hiveCache: Get.find(), + DataSourceType.local: Get.find(), }, Get.find(), Get.find(), diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index 791626a6d..a989e2b1e 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart' import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; import 'package:tmail_ui_user/features/email/domain/model/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/restore_deleted_message_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart'; @@ -187,4 +188,10 @@ abstract class EmailDataSource { List emailIds); Future> parseEmailByBlobIds(AccountId accountId, Set blobIds); + + Future generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest); + + Future sharePreviewEmailEMLContent(String keyStored, String previewEMLContent); + + Future getPreviewEmailEMLContentShared(String keyStored); } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_datasource_impl.dart index 15365ac8e..6c89cd042 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -1,13 +1,23 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:core/data/model/print_attachment.dart'; import 'package:core/data/network/download/downloaded_response.dart'; +import 'package:core/domain/extensions/datetime_extension.dart'; +import 'package:core/presentation/extensions/html_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/file_utils.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:core/utils/preview_eml_file_utils.dart'; import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart'; import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; +import 'package:filesize/filesize.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/error/set_error.dart'; @@ -19,11 +29,14 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; import 'package:tmail_ui_user/features/email/domain/model/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/restore_deleted_message_request.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; @@ -34,6 +47,10 @@ class EmailDataSourceImpl extends EmailDataSource { final EmailAPI emailAPI; final ExceptionThrower _exceptionThrower; final ExceptionThrower _sendEmailExceptionThrower = Get.find(); + final PreviewEmlFileUtils _previewEmailUtils = Get.find(); + final HtmlAnalyzer _htmlAnalyzer = Get.find(); + final FileUtils _fileUtils = Get.find(); + final ImagePaths _imagePaths = Get.find(); EmailDataSourceImpl(this.emailAPI, this._exceptionThrower); @@ -385,4 +402,125 @@ class EmailDataSourceImpl extends EmailDataSource { return await emailAPI.parseEmailByBlobIds(accountId, blobIds); }).catchError(_exceptionThrower.throwException); } + + @override + Future generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) { + return Future.sync(() async { + final email = previewEmailEMLRequest.email; + final appLocalizations = previewEmailEMLRequest.appLocalizations; + final locale = previewEmailEMLRequest.locale.toLanguageTag(); + + final listAttachments = email + .allAttachments + .getListAttachmentsDisplayedOutside(email.htmlBodyAttachments); + + final listInlineImages = email + .allAttachments + .listAttachmentsDisplayedInContent; + + final mapCidImageDownloadUrl = listInlineImages.toMapCidImageDownloadUrl( + accountId: previewEmailEMLRequest.accountId, + downloadUrl: previewEmailEMLRequest.baseDownloadUrl, + ); + + final emailContentEscaped = await _transformEmailContent( + email.emailContentList, + mapCidImageDownloadUrl, + ); + + final sender = email.from?.isNotEmpty == true + ? email.from!.first + : null; + + final receiveTime = email.getReceivedAt( + newLocale: locale, + pattern: email.receivedAt?.value.toLocal().toPatternForPrinting(locale) + ); + + final sentTime = email.getSentAt( + newLocale: locale, + pattern: email.sentAt?.value.toLocal().toPatternForPrinting(locale) + ); + + final List listPrintAttachment = []; + + if (listAttachments.isNotEmpty) { + await Future.forEach(listAttachments, (attachment) async { + final iconBase64Data = await _fileUtils.convertImageAssetToBase64( + attachment.getIcon(_imagePaths)); + + final printAttachment = PrintAttachment( + iconBase64Data: iconBase64Data, + name: attachment.name.escapeLtGtHtmlString(), + size: filesize(attachment.size?.value), + ); + + listPrintAttachment.add(printAttachment); + }); + } + + final attachmentIconBase64Data = email.hasAttachment == true + ? await _fileUtils.convertImageAssetToBase64(_imagePaths.icAttachment) + : ''; + + final previewEmlHtmlDocument = _previewEmailUtils.generatePreviewEml( + appName: appLocalizations.app_name, + userName: previewEmailEMLRequest.userName.value, + subjectPrefix: appLocalizations.subject, + subject: previewEmailEMLRequest.email.subject?.escapeLtGtHtmlString() ?? '', + emailContent: emailContentEscaped, + senderName: sender?.name.escapeLtGtHtmlString() ?? '', + senderEmailAddress: sender?.email ?? '', + dateTime: receiveTime.isNotEmpty ? receiveTime : sentTime, + 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(), + attachmentIcon: attachmentIconBase64Data, + toAddress: email.to?.listEmailAddressToString(isFullEmailAddress: true), + ccAddress: email.cc?.listEmailAddressToString(isFullEmailAddress: true), + bccAddress: email.bcc?.listEmailAddressToString(isFullEmailAddress: true), + replyToAddress: email.replyTo?.listEmailAddressToString(isFullEmailAddress: true), + listAttachment: listPrintAttachment, + ); + + return previewEmlHtmlDocument; + }).catchError(_exceptionThrower.throwException); + } + + Future _transformEmailContent( + List emailContents, + Map mapCidImageDownloadUrl, + ) async { + try { + final transformConfiguration = PlatformInfo.isWeb + ? TransformConfiguration.forPreviewEmailOnWeb() + : TransformConfiguration.forPreviewEmail(); + + final listEmailContent = await Future.wait(emailContents + .map((emailContent) async => await _htmlAnalyzer.transformEmailContent( + emailContent, + mapCidImageDownloadUrl, + transformConfiguration, + )) + .toList()); + + return listEmailContent.asHtmlString; + } catch (e) { + logError('EmailDataSourceImpl::_transformEmailContent:Exception = $e'); + return ''; + } + } + + @override + Future sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) { + throw UnimplementedError(); + } + + @override + Future getPreviewEmailEMLContentShared(String keyStored) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart index f142c9af6..fe38c8ed6 100644 --- a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart @@ -34,6 +34,7 @@ import 'package:tmail_ui_user/features/email/domain/extensions/detailed_email_hi import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; import 'package:tmail_ui_user/features/email/domain/model/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/restore_deleted_message_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/offline_mode/extensions/list_sending_email_hive_cache_extension.dart'; @@ -531,4 +532,19 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Future> parseEmailByBlobIds(AccountId accountId, Set blobIds) { throw UnimplementedError(); } + + @override + Future generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) { + throw UnimplementedError(); + } + + @override + Future sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) { + throw UnimplementedError(); + } + + @override + Future getPreviewEmailEMLContentShared(String keyStored) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart new file mode 100644 index 000000000..bdea0f469 --- /dev/null +++ b/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart @@ -0,0 +1,224 @@ + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:core/data/network/download/downloaded_response.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:email_recovery/email_recovery/email_recovery_action.dart'; +import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/error/set_error.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/properties/properties.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/mail/email/email.dart'; +import 'package:model/account/account_request.dart'; +import 'package:model/download/download_task_id.dart'; +import 'package:model/email/attachment.dart'; +import 'package:model/email/mark_star_action.dart'; +import 'package:model/email/read_actions.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; +import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; +import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; +import 'package:tmail_ui_user/features/email/domain/model/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/restore_deleted_message_request.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; +import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; + +class EmailLocalStorageDataSourceImpl extends EmailDataSource { + + final LocalStorageManager _localStorageManager; + final ExceptionThrower _exceptionThrower; + + EmailLocalStorageDataSourceImpl( + this._localStorageManager, + this._exceptionThrower, + ); + + @override + Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId, {CancelToken? cancelToken}) { + throw UnimplementedError(); + } + + @override + Future<({List emailIdsSuccess, Map mapErrors})> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds) { + throw UnimplementedError(); + } + + @override + Future deleteMultipleSendingEmail(AccountId accountId, UserName userName, List sendingIds) { + throw UnimplementedError(); + } + + @override + Future deleteSendingEmail(AccountId accountId, UserName userName, String sendingId) { + throw UnimplementedError(); + } + + @override + Future downloadAttachmentForWeb(DownloadTaskId taskId, Attachment attachment, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest, StreamController> onReceiveController, {CancelToken? cancelToken}) { + throw UnimplementedError(); + } + + @override + Future> downloadAttachments(List attachments, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest) { + throw UnimplementedError(); + } + + @override + Future exportAttachment(Attachment attachment, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest, CancelToken cancelToken) { + throw UnimplementedError(); + } + + @override + Future generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) { + throw UnimplementedError(); + } + + @override + Future> getAllSendingEmails(AccountId accountId, UserName userName) { + throw UnimplementedError(); + } + + @override + Future getDetailedEmailById(Session session, AccountId accountId, EmailId emailId) { + throw UnimplementedError(); + } + + @override + Future getEmailContent(Session session, AccountId accountId, EmailId emailId, {Properties? additionalProperties}) { + throw UnimplementedError(); + } + + @override + Future getRestoredDeletedMessage(EmailRecoveryActionId emailRecoveryActionId) { + throw UnimplementedError(); + } + + @override + Future getStoredEmail(Session session, AccountId accountId, EmailId emailId) { + throw UnimplementedError(); + } + + @override + Future getStoredNewEmail(Session session, AccountId accountId, EmailId emailId) { + throw UnimplementedError(); + } + + @override + Future getStoredOpenedEmail(Session session, AccountId accountId, EmailId emailId) { + throw UnimplementedError(); + } + + @override + Future getStoredSendingEmail(AccountId accountId, UserName userName, String sendingId) { + throw UnimplementedError(); + } + + @override + Future<({List emailIdsSuccess, Map mapErrors})> markAsRead(Session session, AccountId accountId, List emailIds, ReadActions readActions) { + throw UnimplementedError(); + } + + @override + Future<({List emailIdsSuccess, Map mapErrors})> markAsStar(Session session, AccountId accountId, List emailIds, MarkStarAction markStarAction) { + throw UnimplementedError(); + } + + @override + Future<({List emailIdsSuccess, Map mapErrors})> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { + throw UnimplementedError(); + } + + @override + Future> parseEmailByBlobIds(AccountId accountId, Set blobIds) { + throw UnimplementedError(); + } + + @override + Future removeEmailDrafts(Session session, AccountId accountId, EmailId emailId, {CancelToken? cancelToken}) { + throw UnimplementedError(); + } + + @override + Future restoreDeletedMessage(RestoredDeletedMessageRequest restoredDeletedMessageRequest) { + throw UnimplementedError(); + } + + @override + Future saveEmailAsDrafts(Session session, AccountId accountId, Email email, {CancelToken? cancelToken}) { + throw UnimplementedError(); + } + + @override + Future sendEmail(Session session, AccountId accountId, EmailRequest emailRequest, {CreateNewMailboxRequest? mailboxRequest, CancelToken? cancelToken}) { + throw UnimplementedError(); + } + + @override + Future sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) { + return Future.sync(() async { + return _localStorageManager.save(keyStored, previewEMLContent); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future storeDetailedNewEmail(Session session, AccountId accountId, DetailedEmail detailedEmail) { + throw UnimplementedError(); + } + + @override + Future storeEmail(Session session, AccountId accountId, Email email) { + throw UnimplementedError(); + } + + @override + Future storeEventAttendanceStatus(Session session, AccountId accountId, EmailId emailId, EventActionType eventActionType) { + throw UnimplementedError(); + } + + @override + Future storeOpenedEmail(Session session, AccountId accountId, DetailedEmail detailedEmail) { + throw UnimplementedError(); + } + + @override + Future storeSendingEmail(AccountId accountId, UserName userName, SendingEmail sendingEmail) { + throw UnimplementedError(); + } + + @override + Future unsubscribeMail(Session session, AccountId accountId, EmailId emailId) { + throw UnimplementedError(); + } + + @override + Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId, {CancelToken? cancelToken}) { + throw UnimplementedError(); + } + + @override + Future> updateMultipleSendingEmail(AccountId accountId, UserName userName, List newSendingEmails) { + throw UnimplementedError(); + } + + @override + Future updateSendingEmail(AccountId accountId, UserName userName, SendingEmail newSendingEmail) { + throw UnimplementedError(); + } + + @override + Future getPreviewEmailEMLContentShared(String keyStored) { + return Future.sync(() async { + return _localStorageManager.get(keyStored); + }).catchError(_exceptionThrower.throwException); + } +} \ No newline at end of file diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 9b0d2172c..d851fd5b2 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -902,7 +902,9 @@ class EmailAPI with HandleSetErrorMixin { Future> parseEmailByBlobIds(AccountId accountId, Set blobIds) async { final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); - final parseEmailMethod = ParseEmailMethod(accountId, blobIds); + final parseEmailMethod = ParseEmailMethod(accountId, blobIds) + ..addProperties(ThreadConstants.propertiesParseEmailByBlobId) + ..addFetchHTMLBodyValues(true); final parseEmailInvocation = requestBuilder.invocation(parseEmailMethod); final response = await (requestBuilder diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 263814227..1cbc92f9a 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -32,6 +32,7 @@ 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/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/restore_deleted_message_request.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; @@ -486,6 +487,21 @@ class EmailRepositoryImpl extends EmailRepository { @override Future> parseEmailByBlobIds(AccountId accountId, Set blobIds) { - throw UnimplementedError(); + return emailDataSource[DataSourceType.network]!.parseEmailByBlobIds(accountId, blobIds); + } + + @override + Future generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) { + return emailDataSource[DataSourceType.network]!.generatePreviewEmailEMLContent(previewEmailEMLRequest); + } + + @override + Future sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) { + return emailDataSource[DataSourceType.local]!.sharePreviewEmailEMLContent(keyStored, previewEMLContent); + } + + @override + Future getPreviewEmailEMLContentShared(String keyStored) { + return emailDataSource[DataSourceType.local]!.getPreviewEmailEMLContentShared(keyStored); } } \ No newline at end of file diff --git a/lib/features/email/domain/model/preview_email_eml_request.dart b/lib/features/email/domain/model/preview_email_eml_request.dart new file mode 100644 index 000000000..e790b3211 --- /dev/null +++ b/lib/features/email/domain/model/preview_email_eml_request.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class PreviewEmailEMLRequest with EquatableMixin { + final AccountId accountId; + final UserName userName; + final Id blobId; + final Email email; + final Locale locale; + final AppLocalizations appLocalizations; + final String baseDownloadUrl; + + PreviewEmailEMLRequest({ + required this.accountId, + required this.userName, + required this.blobId, + required this.email, + required this.locale, + required this.appLocalizations, + required this.baseDownloadUrl, + }); + + String get keyStored => blobId.value; + + @override + List get props => [ + accountId, + userName, + blobId, + email, + locale, + appLocalizations, + baseDownloadUrl, + ]; +} \ No newline at end of file diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index 1c3b4ea56..9e86760a7 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -27,6 +27,7 @@ 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/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/restore_deleted_message_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; @@ -181,4 +182,10 @@ abstract class EmailRepository { EventActionType eventActionType); Future> parseEmailByBlobIds(AccountId accountId, Set blobIds); + + Future generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest); + + Future sharePreviewEmailEMLContent(String keyStored, String previewEMLContent); + + Future getPreviewEmailEMLContentShared(String keyStored); } \ No newline at end of file diff --git a/lib/features/email/domain/state/get_preview_email_content_shared_state.dart b/lib/features/email/domain/state/get_preview_email_content_shared_state.dart new file mode 100644 index 000000000..c64fa3397 --- /dev/null +++ b/lib/features/email/domain/state/get_preview_email_content_shared_state.dart @@ -0,0 +1,19 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class GettingPreviewEmailEMLContentShared extends LoadingState {} + +class GetPreviewEmailEMLContentSharedSuccess extends UIState { + + final String previewEMLContent; + + GetPreviewEmailEMLContentSharedSuccess(this.previewEMLContent); + + @override + List get props => [previewEMLContent]; +} + +class GetPreviewEmailEMLContentSharedFailure extends FeatureFailure { + + GetPreviewEmailEMLContentSharedFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/email/domain/state/parse_email_by_blob_id_state.dart b/lib/features/email/domain/state/parse_email_by_blob_id_state.dart index 7f06b5bba..f4214cb4c 100644 --- a/lib/features/email/domain/state/parse_email_by_blob_id_state.dart +++ b/lib/features/email/domain/state/parse_email_by_blob_id_state.dart @@ -1,5 +1,6 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; class ParsingEmailByBlobId extends LoadingState {} @@ -7,11 +8,12 @@ class ParsingEmailByBlobId extends LoadingState {} class ParseEmailByBlobIdSuccess extends UIState { final Email email; + final Id blobId; - ParseEmailByBlobIdSuccess(this.email); + ParseEmailByBlobIdSuccess(this.email, this.blobId); @override - List get props => [email]; + List get props => [email, blobId]; } class ParseEmailByBlobIdFailure extends FeatureFailure { diff --git a/lib/features/email/domain/state/preview_email_from_eml_file_state.dart b/lib/features/email/domain/state/preview_email_from_eml_file_state.dart new file mode 100644 index 000000000..aa6a9b115 --- /dev/null +++ b/lib/features/email/domain/state/preview_email_from_eml_file_state.dart @@ -0,0 +1,19 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class PreviewingEmailFromEmlFile extends LoadingState {} + +class PreviewEmailFromEmlFileSuccess extends UIState { + + final String storeKey; + + PreviewEmailFromEmlFileSuccess(this.storeKey); + + @override + List get props => [storeKey]; +} + +class PreviewEmailFromEmlFileFailure extends FeatureFailure { + + PreviewEmailFromEmlFileFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart b/lib/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart new file mode 100644 index 000000000..744dbb62f --- /dev/null +++ b/lib/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart @@ -0,0 +1,24 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/email/domain/state/get_preview_email_content_shared_state.dart'; + +class GetPreviewEmailEMLContentSharedInteractor { + final EmailRepository _emailRepository; + + const GetPreviewEmailEMLContentSharedInteractor(this._emailRepository); + + Stream> execute(String keyStored) async* { + try { + yield Right(GettingPreviewEmailEMLContentShared()); + + final previewEMLContent = await _emailRepository + .getPreviewEmailEMLContentShared(keyStored); + + yield Right(GetPreviewEmailEMLContentSharedSuccess(previewEMLContent)); + } catch (e) { + yield Left(GetPreviewEmailEMLContentSharedFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart b/lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart index 6b34755ce..45748c59c 100644 --- a/lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart +++ b/lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart @@ -20,7 +20,10 @@ class ParseEmailByBlobIdInteractor { {blobId}, ); - yield Right(ParseEmailByBlobIdSuccess(emailParsed.first)); + yield Right(ParseEmailByBlobIdSuccess( + emailParsed.first, + blobId, + )); } catch (e) { yield Left(ParseEmailByBlobIdFailure(e)); } diff --git a/lib/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart b/lib/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart new file mode 100644 index 000000000..c406bb777 --- /dev/null +++ b/lib/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart @@ -0,0 +1,31 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart'; + +class PreviewEmailFromEmlFileInteractor { + final EmailRepository _emailRepository; + + const PreviewEmailFromEmlFileInteractor(this._emailRepository); + + Stream> execute( + PreviewEmailEMLRequest previewEmailEMLRequest, + ) async* { + try { + yield Right(PreviewingEmailFromEmlFile()); + + final previewEMLContent = await _emailRepository + .generatePreviewEmailEMLContent(previewEmailEMLRequest); + + final keyStored = previewEmailEMLRequest.keyStored; + + await _emailRepository.sharePreviewEmailEMLContent(keyStored, previewEMLContent); + + yield Right(PreviewEmailFromEmlFileSuccess(keyStored)); + } catch (e) { + yield Left(PreviewEmailFromEmlFileFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/email/presentation/bindings/email_bindings.dart b/lib/features/email/presentation/bindings/email_bindings.dart index 071b20d0f..9019e3a35 100644 --- a/lib/features/email/presentation/bindings/email_bindings.dart +++ b/lib/features/email/presentation/bindings/email_bindings.dart @@ -1,11 +1,13 @@ import 'package:core/core.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/print_file_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; @@ -20,6 +22,8 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_stored_email_st import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_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/store_event_attendance_status_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; @@ -70,6 +74,8 @@ class EmailBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), + Get.find(), )); } @@ -118,6 +124,9 @@ class EmailBindings extends BaseBindings { Get.find(), Get.find(), Get.find())); + Get.lazyPut(() => EmailLocalStorageDataSourceImpl( + Get.find(), + Get.find())); } @override @@ -148,6 +157,8 @@ class EmailBindings extends BaseBindings { Get.lazyPut(() => StoreOpenedEmailInteractor(Get.find())); Get.lazyPut(() => PrintEmailInteractor(Get.find())); Get.lazyPut(() => StoreEventAttendanceStatusInteractor(Get.find())); + Get.lazyPut(() => ParseEmailByBlobIdInteractor(Get.find())); + Get.lazyPut(() => PreviewEmailFromEmlFileInteractor(Get.find())); IdentityInteractorsBindings().dependencies(); } @@ -169,7 +180,8 @@ class EmailBindings extends BaseBindings { Get.lazyPut(() => EmailRepositoryImpl( { DataSourceType.network: Get.find(), - DataSourceType.hiveCache: Get.find() + DataSourceType.hiveCache: Get.find(), + DataSourceType.local: Get.find(), }, Get.find(), Get.find(), diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 7b3194df8..3ba95fd01 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -7,10 +7,12 @@ 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:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -46,6 +48,7 @@ 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/state/calendar_event_accept_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/calendar_event_maybe_state.dart'; @@ -59,6 +62,8 @@ import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_sta 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/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/store_event_attendance_status_state.dart'; @@ -74,6 +79,8 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i 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/send_receipt_to_sender_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_event_attendance_status_interactor.dart'; @@ -138,6 +145,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final StoreOpenedEmailInteractor _storeOpenedEmailInteractor; final PrintEmailInteractor _printEmailInteractor; final StoreEventAttendanceStatusInteractor _storeEventAttendanceStatusInteractor; + final ParseEmailByBlobIdInteractor _parseEmailByBlobIdInteractor; + final PreviewEmailFromEmlFileInteractor _previewEmailFromEmlFileInteractor; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor; @@ -191,6 +200,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { this._storeOpenedEmailInteractor, this._printEmailInteractor, this._storeEventAttendanceStatusInteractor, + this._parseEmailByBlobIdInteractor, + this._previewEmailFromEmlFileInteractor, ); @override @@ -246,6 +257,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { calendarEventSuccess(success); } else if (success is StoreEventAttendanceStatusSuccess) { _showToastMessageEventAttendanceSuccess(success); + } else if (success is ParseEmailByBlobIdSuccess) { + _handleParseEmailByBlobIdSuccess(success); + } else if (success is PreviewEmailFromEmlFileSuccess) { + _handlePreviewEmailFromEMLFileSuccess(success); } } @@ -269,6 +284,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } else if (failure is CalendarEventReplyFailure || failure is StoreEventAttendanceStatusFailure) { _calendarEventFailure(failure); + } else if (failure is ParseEmailByBlobIdFailure) { + _handleParseEmailByBlobIdFailure(failure); + } else if (failure is PreviewEmailFromEmlFileFailure) { + _handlePreviewEmailFromEMLFileFailure(failure); } } @@ -1896,16 +1915,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } void handleViewAttachmentAction(BuildContext context, Attachment attachment) { - if (PlatformInfo.isWeb) { - if (PlatformInfo.isCanvasKit && attachment.validatePDFIcon()) { - previewPDFFileAction(context, attachment); - } else { - downloadAttachmentForWeb(attachment); - } - } else if (PlatformInfo.isMobile) { - exportAttachment(context, attachment); + if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit && attachment.isPDFFile) { + previewPDFFileAction(context, attachment); + } else if (attachment.isEMLFile) { + previewEMLFileAction(attachment, AppLocalizations.of(context)); } else { - log('EmailView::_handleViewAttachmentAction: THE PLATFORM IS SUPPORTED'); + handleDownloadAttachmentAction(context, attachment); } } @@ -1936,6 +1951,80 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ); } + void previewEMLFileAction(Attachment attachment, AppLocalizations appLocalizations) { + SmartDialog.showLoading( + msg: appLocalizations.loadingPleaseWait, + maskColor: Colors.black38, + ); + + if (accountId == null) { + consumeState(Stream.value(Left(ParseEmailByBlobIdFailure(NotFoundAccountIdException())))); + return; + } + + if (attachment.blobId == null) { + consumeState(Stream.value(Left(ParseEmailByBlobIdFailure(NotFoundBlobIdException([]))))); + return; + } + + consumeState(_parseEmailByBlobIdInteractor.execute( + accountId!, + attachment.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; + } + + 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), + ), + )); + } + + void _handleParseEmailByBlobIdFailure(ParseEmailByBlobIdFailure failure) { + SmartDialog.dismiss(); + toastManager.showMessageFailure(failure); + } + + void _handlePreviewEmailFromEMLFileFailure(PreviewEmailFromEmlFileFailure failure) { + SmartDialog.dismiss(); + toastManager.showMessageFailure(failure); + } + + void _handlePreviewEmailFromEMLFileSuccess(PreviewEmailFromEmlFileSuccess success) { + SmartDialog.dismiss(); + + if (PlatformInfo.isWeb) { + HtmlUtils.openNewWindowByUrl( + RouteUtils.createUrlWebLocationBar( + AppRoutes.emailEMLPreviewer, + previewId: success.storeKey + ).toString(), + ); + } + } + void handleMailToAttendees(CalendarOrganizer? organizer, List? attendees) { final listEmailAddressAttendees = attendees ?.map((attendee) => EmailAddress(attendee.name?.name, attendee.mailto?.mailAddress.value)) diff --git a/lib/features/email/presentation/extensions/attachment_extension.dart b/lib/features/email/presentation/extensions/attachment_extension.dart index 64fc09d28..e631581aa 100644 --- a/lib/features/email/presentation/extensions/attachment_extension.dart +++ b/lib/features/email/presentation/extensions/attachment_extension.dart @@ -1,9 +1,11 @@ +import 'package:core/presentation/extensions/media_type_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart'; extension AttachmentExtension on Attachment { String getIcon(ImagePaths imagePaths) => type?.getIcon(imagePaths, fileName: name) ?? imagePaths.icFileEPup; - bool validatePDFIcon() => type?.validatePDFIcon(fileName: name) ?? false; + bool get isPDFFile => type?.isPDFFile(fileName: name) ?? false; + + bool get isEMLFile => type?.isEMLFile ?? false; } \ No newline at end of file diff --git a/lib/features/email_previewer/email_previewer_bindings.dart b/lib/features/email_previewer/email_previewer_bindings.dart new file mode 100644 index 000000000..8a52fe404 --- /dev/null +++ b/lib/features/email_previewer/email_previewer_bindings.dart @@ -0,0 +1,107 @@ + +import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/utils/file_utils.dart'; +import 'package:core/utils/print_utils.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/base_bindings.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/print_file_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; +import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; +import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/features/email_previewer/email_previewer_controller.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/state_datasource_impl.dart'; +import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; +import 'package:tmail_ui_user/features/offline_mode/manager/new_email_cache_manager.dart'; +import 'package:tmail_ui_user/features/offline_mode/manager/new_email_cache_worker_queue.dart'; +import 'package:tmail_ui_user/features/offline_mode/manager/opened_email_cache_manager.dart'; +import 'package:tmail_ui_user/features/offline_mode/manager/opened_email_cache_worker_queue.dart'; +import 'package:tmail_ui_user/features/offline_mode/manager/sending_email_cache_manager.dart'; +import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart'; +import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; +import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; +import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; + +class EmailPreviewerBindings extends BaseBindings { + + @override + void bindingsController() { + Get.lazyPut(() => EmailPreviewerController( + Get.find())); + } + + @override + void bindingsDataSource() { + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + } + + @override + void bindingsDataSourceImpl() { + Get.lazyPut(() => EmailDataSourceImpl( + Get.find(), + Get.find())); + Get.lazyPut(() => HtmlDataSourceImpl( + Get.find(), + Get.find())); + Get.lazyPut(() => StateDataSourceImpl( + Get.find(), + Get.find(), + Get.find())); + Get.lazyPut(() => PrintFileDataSourceImpl( + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find())); + Get.lazyPut(() => EmailHiveCacheDataSourceImpl( + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find())); + Get.lazyPut(() => EmailLocalStorageDataSourceImpl( + Get.find(), + Get.find())); + } + + @override + void bindingsInteractor() { + Get.lazyPut(() => GetPreviewEmailEMLContentSharedInteractor( + Get.find())); + } + + @override + void bindingsRepository() { + Get.lazyPut(() => Get.find()); + } + + @override + void bindingsRepositoryImpl() { + Get.lazyPut(() => EmailRepositoryImpl( + { + DataSourceType.network: Get.find(), + DataSourceType.hiveCache: Get.find(), + DataSourceType.local: Get.find(), + }, + Get.find(), + Get.find(), + Get.find())); + } +} \ No newline at end of file diff --git a/lib/features/email_previewer/email_previewer_controller.dart b/lib/features/email_previewer/email_previewer_controller.dart new file mode 100644 index 000000000..d2666ba69 --- /dev/null +++ b/lib/features/email_previewer/email_previewer_controller.dart @@ -0,0 +1,41 @@ + +import 'package:dartz/dartz.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; +import 'package:tmail_ui_user/features/email/domain/state/get_preview_email_content_shared_state.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/main/routes/route_utils.dart'; + +class EmailPreviewerController extends BaseController { + + final GetPreviewEmailEMLContentSharedInteractor _getPreviewEmailEMLContentSharedInteractor; + + EmailPreviewerController( + this._getPreviewEmailEMLContentSharedInteractor, + ); + + @override + void onReady() { + super.onReady(); + _getPreviewEmailEMLContentShared(); + } + + void _getPreviewEmailEMLContentShared() { + final parameters = Get.parameters; + + if (parameters.isEmpty) { + consumeState(Stream.value(Left(GetPreviewEmailEMLContentSharedFailure( + NotFoundBlobIdException([]))))); + return; + } + + final keyStored = parameters[RouteUtils.paramID]; + if (keyStored?.trim().isNotEmpty == true) { + consumeState(_getPreviewEmailEMLContentSharedInteractor.execute(keyStored!)); + } else { + consumeState(Stream.value(Left(GetPreviewEmailEMLContentSharedFailure( + NotFoundBlobIdException([]))))); + } + } +} \ No newline at end of file diff --git a/lib/features/email_previewer/email_previewer_view.dart b/lib/features/email_previewer/email_previewer_view.dart new file mode 100644 index 000000000..534623019 --- /dev/null +++ b/lib/features/email_previewer/email_previewer_view.dart @@ -0,0 +1,48 @@ + +import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart'; +import 'package:core/presentation/views/loading/cupertino_loading_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/email/domain/state/get_preview_email_content_shared_state.dart'; +import 'package:tmail_ui_user/features/email_previewer/email_previewer_controller.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class EmailPreviewerView extends GetWidget { + + const EmailPreviewerView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Obx(() { + final viewState = controller.viewState.value; + return viewState.fold( + (failure) { + if (viewState is GetPreviewEmailEMLContentSharedFailure) { + return Center( + child: Text(AppLocalizations.of(context).previewEmailFromEMLFileFailed), + ); + } else { + return const Center( + child: CupertinoLoadingWidget(), + ); + } + }, + (success) { + if (success is GetPreviewEmailEMLContentSharedSuccess) { + return HtmlContentViewerOnWeb( + widthContent: context.width, + heightContent: context.height, + contentHtml: success.previewEMLContent, + ); + } else { + return const Center( + child: CupertinoLoadingWidget(), + ); + } + }); + }), + ); + } +} \ No newline at end of file diff --git a/lib/features/home/data/exceptions/session_exceptions.dart b/lib/features/home/data/exceptions/session_exceptions.dart index d9a028bb4..a3a2cc485 100644 --- a/lib/features/home/data/exceptions/session_exceptions.dart +++ b/lib/features/home/data/exceptions/session_exceptions.dart @@ -1,2 +1,6 @@ -class NotFoundSessionException implements Exception {} \ No newline at end of file +class NotFoundSessionException implements Exception {} + +class NotFoundAccountIdException implements Exception {} + +class NotFoundContextException implements Exception {} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 1f7c0a782..0308ab80e 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -7,6 +7,7 @@ import 'package:core/utils/print_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/composer/data/repository/contact_repository_impl.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; @@ -15,6 +16,7 @@ import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dar import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/print_file_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; @@ -254,6 +256,9 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), Get.find())); + Get.lazyPut(() => EmailLocalStorageDataSourceImpl( + Get.find(), + Get.find())); Get.lazyPut(() => RemoteServerSettingsDataSourceImpl( Get.find(), Get.find() @@ -340,7 +345,8 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => EmailRepositoryImpl( { DataSourceType.network: Get.find(), - DataSourceType.hiveCache: Get.find() + DataSourceType.hiveCache: Get.find(), + DataSourceType.local: Get.find(), }, Get.find(), Get.find(), diff --git a/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart b/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart index f5983336b..c605a695a 100644 --- a/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart +++ b/lib/features/offline_mode/bindings/sending_email_interactor_bindings.dart @@ -4,12 +4,14 @@ import 'package:core/utils/file_utils.dart'; import 'package:core/utils/print_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/print_file_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; @@ -89,6 +91,9 @@ class SendEmailInteractorBindings extends InteractorsBindings { Get.find(), Get.find(), Get.find())); + Get.lazyPut(() => EmailLocalStorageDataSourceImpl( + Get.find(), + Get.find())); Get.lazyPut(() => RemoteServerSettingsDataSourceImpl( Get.find(), Get.find())); @@ -111,7 +116,8 @@ class SendEmailInteractorBindings extends InteractorsBindings { Get.lazyPut(() => EmailRepositoryImpl( { DataSourceType.network: Get.find(), - DataSourceType.hiveCache: Get.find() + DataSourceType.hiveCache: Get.find(), + DataSourceType.local: Get.find(), }, Get.find(), Get.find(), diff --git a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart index eada6b3b1..ee3b3813b 100644 --- a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart +++ b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart @@ -4,11 +4,13 @@ import 'package:core/utils/file_utils.dart'; import 'package:core/utils/print_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/print_file_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; @@ -127,6 +129,9 @@ class FcmInteractorBindings extends InteractorsBindings { Get.find(), Get.find(), Get.find())); + Get.lazyPut(() => EmailLocalStorageDataSourceImpl( + Get.find(), + Get.find())); } @override @@ -180,7 +185,8 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => EmailRepositoryImpl( { DataSourceType.network: Get.find(), - DataSourceType.hiveCache: Get.find() + DataSourceType.hiveCache: Get.find(), + DataSourceType.local: Get.find(), }, Get.find(), Get.find(), diff --git a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart index 64b2e8595..3791453bc 100644 --- a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart +++ b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart @@ -2,10 +2,10 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; -import 'package:tip_dialog/tip_dialog.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart'; @@ -44,7 +44,7 @@ class TwakeWelcomeController extends ReloadableController { } void onClickSignIn(BuildContext context) { - TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait); + SmartDialog.showLoading(msg: AppLocalizations.of(context).loadingPleaseWait); final baseUri = Uri.tryParse(AppConfig.saasJmapServerUrl); @@ -64,7 +64,7 @@ class TwakeWelcomeController extends ReloadableController { } void onSignUpTwakeWorkplace(BuildContext context) { - TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait); + SmartDialog.showLoading(msg: AppLocalizations.of(context).loadingPleaseWait); final baseUri = Uri.tryParse(AppConfig.saasJmapServerUrl); @@ -115,7 +115,7 @@ class TwakeWelcomeController extends ReloadableController { @override void handleReloaded(Session session) { - TipDialogHelper.dismiss(); + SmartDialog.dismiss(); popAndPush( RouteUtils.generateNavigationRoute(AppRoutes.dashboard), @@ -124,7 +124,7 @@ class TwakeWelcomeController extends ReloadableController { @override void handleGetSessionFailure(GetSessionFailure failure) { - TipDialogHelper.dismiss(); + SmartDialog.dismiss(); toastManager.showMessageFailure(failure); } @@ -147,13 +147,13 @@ class TwakeWelcomeController extends ReloadableController { } void _handleSignInTwakeWorkplaceFailure(SignInTwakeWorkplaceFailure failure) { - TipDialogHelper.dismiss(); + SmartDialog.dismiss(); toastManager.showMessageFailure(failure); } void _handleSignUpTwakeWorkplaceFailure(SignUpTwakeWorkplaceFailure failure) { - TipDialogHelper.dismiss(); + SmartDialog.dismiss(); toastManager.showMessageFailure(failure); } diff --git a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart index c16bc67a9..5d56b4aea 100644 --- a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart +++ b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:linagora_design_flutter/linagora_design_flutter.dart'; -import 'package:tip_dialog/tip_dialog.dart'; import 'package:tmail_ui_user/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart'; import 'package:tmail_ui_user/features/starting_page/presentation/twake_welcome/twake_welcome_view_style.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -19,42 +18,29 @@ class TwakeWelcomeView extends GetWidget { DeviceOrientation.portraitDown ]); - return Stack( - children: [ - TwakeWelcomeScreen( - logo: Padding( - padding: const EdgeInsetsDirectional.only(bottom: 16), - child: SvgPicture.asset( - controller.imagePaths.icLogoTwakeWelcome, - fit: BoxFit.fill, - ), - ), - focusColor: Colors.transparent, - hoverColor: Colors.transparent, - highlightColor: Colors.transparent, - overlayColor: WidgetStateProperty.all(Colors.transparent), - signInTitle: AppLocalizations.of(context).signIn.capitalizeFirst ?? '', - onSignInOnTap: () => controller.onClickSignIn(context), - onCreateTwakeIdOnTap: () => controller.onSignUpTwakeWorkplace(context), - createTwakeIdTitle: AppLocalizations.of(context).createTwakeId, - useCompanyServerTitle: AppLocalizations.of(context).useCompanyServer, - description: AppLocalizations.of(context).descriptionWelcomeTo, - descriptionTextStyle: TwakeWelcomeViewStyle.descriptionTextStyle, - privacyPolicy: AppLocalizations.of(context).privacyPolicy, - descriptionPrivacyPolicy: AppLocalizations.of(context).byContinuingYouAreAgreeingToOur, - onPrivacyPolicyOnTap: controller.onClickPrivacyPolicy, - onUseCompanyServerOnTap: controller.handleUseCompanyServer, + return TwakeWelcomeScreen( + logo: Padding( + padding: const EdgeInsetsDirectional.only(bottom: 16), + child: SvgPicture.asset( + controller.imagePaths.icLogoTwakeWelcome, + fit: BoxFit.fill, ), - TipDialogContainer( - duration: const Duration(seconds: 2), - outsideTouchable: true, - onOutsideTouch: (tipDialog) { - if (tipDialog is TipDialog && tipDialog.type == TipDialogType.LOADING) { - TipDialogHelper.dismiss(); - } - } - ) - ], + ), + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + overlayColor: WidgetStateProperty.all(Colors.transparent), + signInTitle: AppLocalizations.of(context).signIn.capitalizeFirst ?? '', + onSignInOnTap: () => controller.onClickSignIn(context), + onCreateTwakeIdOnTap: () => controller.onSignUpTwakeWorkplace(context), + createTwakeIdTitle: AppLocalizations.of(context).createTwakeId, + useCompanyServerTitle: AppLocalizations.of(context).useCompanyServer, + description: AppLocalizations.of(context).descriptionWelcomeTo, + descriptionTextStyle: TwakeWelcomeViewStyle.descriptionTextStyle, + privacyPolicy: AppLocalizations.of(context).privacyPolicy, + descriptionPrivacyPolicy: AppLocalizations.of(context).byContinuingYouAreAgreeingToOur, + onPrivacyPolicyOnTap: controller.onClickPrivacyPolicy, + onUseCompanyServerOnTap: controller.handleUseCompanyServer, ); } } \ No newline at end of file diff --git a/lib/features/thread/domain/constants/thread_constants.dart b/lib/features/thread/domain/constants/thread_constants.dart index 2e5791697..a7ca6aaea 100644 --- a/lib/features/thread/domain/constants/thread_constants.dart +++ b/lib/features/thread/domain/constants/thread_constants.dart @@ -84,4 +84,28 @@ class ThreadConstants { static const int defaultMaxHeightEmailItemOnBrowser = 40; static const int defaultMaxHeightBrowser = 1200; + + static final propertiesParseEmailByBlobId = Properties({ + EmailProperty.id, + EmailProperty.blobId, + EmailProperty.subject, + EmailProperty.from, + EmailProperty.to, + EmailProperty.cc, + EmailProperty.bcc, + EmailProperty.keywords, + EmailProperty.size, + EmailProperty.receivedAt, + EmailProperty.sentAt, + EmailProperty.preview, + EmailProperty.hasAttachment, + EmailProperty.replyTo, + EmailProperty.mailboxIds, + EmailProperty.bodyValues, + EmailProperty.htmlBody, + EmailProperty.attachments, + EmailProperty.headers, + EmailProperty.messageId, + EmailProperty.references, + }); } \ No newline at end of file diff --git a/lib/features/upload/domain/extensions/media_type_extension.dart b/lib/features/upload/domain/extensions/media_type_extension.dart deleted file mode 100644 index e3bfcc8d8..000000000 --- a/lib/features/upload/domain/extensions/media_type_extension.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:core/domain/extensions/media_type_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:http_parser/http_parser.dart'; - -extension MediaTypeExtension on MediaType { - String getIcon(ImagePaths imagePaths, {String? fileName}) { - if (validatePDFIcon(fileName: fileName) == true) { - return imagePaths.icFilePdf; - } else if (isDocFile()) { - return imagePaths.icFileDocx; - } else if (isExcelFile()) { - return imagePaths.icFileXlsx; - } else if (isPowerPointFile()) { - return imagePaths.icFilePptx; - } else if (isPdfFile()) { - return imagePaths.icFilePdf; - } else if (isZipFile()) { - return imagePaths.icFileZip; - } else if (isImageFile()) { - return imagePaths.icFilePng; - } else { - return imagePaths.icFileEPup; - } - } - - bool validatePDFIcon({required String? fileName}) => mimeType == 'application/pdf' || - (mimeType == 'application/octet-stream' && fileName?.endsWith('.pdf') == true); -} diff --git a/lib/features/upload/presentation/model/upload_file_state.dart b/lib/features/upload/presentation/model/upload_file_state.dart index f0e58b78c..acdb5aca0 100644 --- a/lib/features/upload/presentation/model/upload_file_state.dart +++ b/lib/features/upload/presentation/model/upload_file_state.dart @@ -1,4 +1,5 @@ +import 'package:core/presentation/extensions/media_type_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dio/dio.dart'; @@ -6,7 +7,6 @@ import 'package:equatable/equatable.dart'; import 'package:http_parser/http_parser.dart'; import 'package:model/email/attachment.dart'; import 'package:model/upload/file_info.dart'; -import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart'; diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 61d952d41..d531aa332 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-01-09T04:35:09.125475", + "@@last_modified": "2025-01-10T23:14:09.007935", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4089,5 +4089,17 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "parseEmailByBlobIdFailed": "Cannot parse email by blob id", + "@parseEmailByBlobIdFailed": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "previewEmailFromEMLFileFailed": "Cannot preview this eml file", + "@previewEmailFromEMLFileFailed": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 9ba0af392..39fb43ef3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/build_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; import 'package:tmail_ui_user/main/bindings/main_bindings.dart'; @@ -74,6 +75,8 @@ class TMailApp extends StatelessWidget { unknownRoute: AppPages.unknownRoutePage, defaultTransition: Transition.noTransition, initialRoute: AppRoutes.home, - getPages: AppPages.pages); + getPages: AppPages.pages, + builder: FlutterSmartDialog.init(), + ); } } \ No newline at end of file diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index 9725bac0d..7a0194267 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -7,12 +7,14 @@ import 'package:core/utils/application_manager.dart'; import 'package:core/utils/config/app_config_loader.dart'; import 'package:core/utils/file_utils.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:core/utils/preview_eml_file_utils.dart'; import 'package:core/utils/print_utils.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart'; +import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart'; import 'package:tmail_ui_user/features/sending_queue/presentation/utils/sending_queue_isolate_manager.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; @@ -72,6 +74,7 @@ class CoreBindings extends Bindings { if (PlatformInfo.isIOS) { Get.put(IOSNotificationManager()); } + Get.put(PreviewEmlFileUtils()); } void _bindingIsolate() { @@ -88,5 +91,6 @@ class CoreBindings extends Bindings { accessibility: KeychainAccessibility.first_unlock_this_device ) )); + Get.put(LocalStorageManager()); } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index ffaf432c0..1531225ba 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4294,4 +4294,17 @@ class AppLocalizations { ); } + String get parseEmailByBlobIdFailed { + return Intl.message( + 'Cannot parse email by blob id', + name: 'parseEmailByBlobIdFailed', + ); + } + + String get previewEmailFromEMLFileFailed { + return Intl.message( + 'Cannot preview this eml file', + name: 'previewEmailFromEMLFileFailed', + ); + } } diff --git a/lib/main/pages/app_pages.dart b/lib/main/pages/app_pages.dart index 61cb38e51..9320b9551 100644 --- a/lib/main/pages/app_pages.dart +++ b/lib/main/pages/app_pages.dart @@ -8,6 +8,7 @@ import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart' import 'package:tmail_ui_user/features/contact/presentation/contact_bindings.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_bindings.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_view.dart' deferred as destination_picker; +import 'package:tmail_ui_user/features/email_previewer/email_previewer_bindings.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_bindings.dart'; import 'package:tmail_ui_user/features/home/presentation/home_bindings.dart'; import 'package:tmail_ui_user/features/home/presentation/home_view.dart'; @@ -36,6 +37,7 @@ import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_view.dart' deferred as search_mailbox_view; import 'package:tmail_ui_user/features/mailto/presentation/mailto_url_view.dart' deferred as mailto_url_view; import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_view.dart' deferred as email_recovery; +import 'package:tmail_ui_user/features/email_previewer/email_previewer_view.dart' deferred as email_previewer_view; class AppPages { static final pages = [ @@ -82,6 +84,14 @@ class AppPages { contact_view.loadLibrary, () => contact_view.ContactView()), binding: ContactBindings()), + GetPage( + name: AppRoutes.emailEMLPreviewer, + page: () => DeferredWidget( + email_previewer_view.loadLibrary, + () => email_previewer_view.EmailPreviewerView(), + ), + binding: EmailPreviewerBindings(), + ), unknownRoutePage, if (PlatformInfo.isMobile) ...[ diff --git a/lib/main/routes/app_routes.dart b/lib/main/routes/app_routes.dart index 9ae2e98ee..8b4fff03d 100644 --- a/lib/main/routes/app_routes.dart +++ b/lib/main/routes/app_routes.dart @@ -16,4 +16,5 @@ abstract class AppRoutes { static const searchMailbox = '/search_mailbox'; static const mailtoURL = '/mailto'; static const emailRecovery = '/email_recovery'; + static const emailEMLPreviewer = '/mail/eml/preview'; } \ No newline at end of file diff --git a/lib/main/routes/route_utils.dart b/lib/main/routes/route_utils.dart index 28e25356e..2188dd3e3 100644 --- a/lib/main/routes/route_utils.dart +++ b/lib/main/routes/route_utils.dart @@ -83,12 +83,22 @@ abstract class RouteUtils { return servicePath; } + static ServicePath _createEmailEMLPreviewerServicePath(String route, {String? previewId}) { + ServicePath servicePath = ServicePath(route); + if (previewId?.trim().isNotEmpty == true) { + servicePath = servicePath.withQueryParameters([ + StringQueryParameter(paramID, previewId!) + ]); + } + return servicePath; + } + static ServicePath _createServicePath(String route) { ServicePath servicePath = ServicePath(route); return servicePath; } - static Uri createUrlWebLocationBar(String route, {NavigationRouter? router}) { + static Uri createUrlWebLocationBar(String route, {NavigationRouter? router, String? previewId}) { if (route == AppRoutes.dashboard) { final servicePath = _createDashboardServicePath( '$baseOriginUrl$route', @@ -98,6 +108,12 @@ abstract class RouteUtils { } else if (route == AppRoutes.settings) { final servicePath = _createSettingServicePath('$baseOriginUrl$route', router: router); return Uri.parse(servicePath.path); + } else if (route == AppRoutes.emailEMLPreviewer) { + final servicePath = _createEmailEMLPreviewerServicePath( + '$baseOriginUrl$route', + previewId: previewId, + ); + return Uri.parse(servicePath.path); } else { final servicePath = _createServicePath('$baseOriginUrl$route'); return Uri.parse(servicePath.path); diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index fd0d076bb..8f15f06e7 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:model/email/email_action_type.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_action.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/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart'; @@ -84,6 +86,10 @@ class ToastManager { } else { message = AppLocalizations.of(currentContext!).createTwakeIdFailed; } + } else if (failure is ParseEmailByBlobIdFailure) { + message = AppLocalizations.of(currentContext!).parseEmailByBlobIdFailed; + } else if (failure is PreviewEmailFromEmlFileFailure) { + message = AppLocalizations.of(currentContext!).previewEmailFromEMLFileFailed; } if (message?.isNotEmpty == true) { diff --git a/model/lib/extensions/email_extension.dart b/model/lib/extensions/email_extension.dart index e6002557f..70e0294aa 100644 --- a/model/lib/extensions/email_extension.dart +++ b/model/lib/extensions/email_extension.dart @@ -64,6 +64,16 @@ extension EmailExtension on Email { return ''; } + String getSentAt({required String newLocale, String? pattern}) { + if (sentAt != null) { + return sentAt!.formatDateToLocal( + pattern: pattern ?? sentAt!.value.toLocal().toPattern(), + locale: newLocale + ); + } + return ''; + } + Set getRecipientEmailAddressList() { final listEmailAddress = {}; final listToAddress = to.getListAddress() ?? []; diff --git a/pubspec.lock b/pubspec.lock index 0c77d0b7e..6631dd36f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1021,6 +1021,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + flutter_smart_dialog: + dependency: "direct main" + description: + name: flutter_smart_dialog + sha256: d7b915461fdc9bb8111d23a709b4ce910dbc4b9bef0fbd941655f74bf7de09a6 + url: "https://pub.dev" + source: hosted + version: "4.9.8+5" flutter_staggered_grid_view: dependency: "direct main" description: @@ -1337,14 +1345,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - loading_view: - dependency: transitive - description: - name: loading_view - sha256: "3d31c2c5293c2e3518b7330ffdc2fab5c83daaa9218cc45d4406c875f08f0795" - url: "https://pub.dev" - source: hosted - version: "1.1.0" logging: dependency: transitive description: @@ -2023,14 +2023,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tip_dialog: - dependency: "direct main" - description: - name: tip_dialog - sha256: edc1ebbb4b9f9575220c85206cf6986921c2b63d173744be0babac00c2e48bd0 - url: "https://pub.dev" - source: hosted - version: "4.0.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c634fdb9e..9536602ff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -247,7 +247,7 @@ dependencies: web_socket_channel: 2.4.3 - tip_dialog: 4.0.0 + flutter_smart_dialog: 4.9.8+5 flutter_web_auth_2: 3.1.1 diff --git a/test/features/email/domain/usecases/parse_email_by_blob_id_interactor_test.dart b/test/features/email/domain/usecases/parse_email_by_blob_id_interactor_test.dart index bbd44ba7c..46c2cd50d 100644 --- a/test/features/email/domain/usecases/parse_email_by_blob_id_interactor_test.dart +++ b/test/features/email/domain/usecases/parse_email_by_blob_id_interactor_test.dart @@ -47,7 +47,7 @@ void main() { result, emitsInOrder([ Right(ParsingEmailByBlobId()), - Right(ParseEmailByBlobIdSuccess(parsedEmail)), + Right(ParseEmailByBlobIdSuccess(parsedEmail, blobId)), ]), ); 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 9ba67d1e3..fed4c41d3 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -39,6 +39,8 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_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/store_event_attendance_status_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; @@ -98,6 +100,8 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -134,6 +138,8 @@ void main() { final uuid = MockUuid(); final printEmailInteractor = MockPrintEmailInteractor(); final storeEventAttendanceStatusInteractor = MockStoreEventAttendanceStatusInteractor(); + final parseEmailByBlobIdInteractor = MockParseEmailByBlobIdInteractor(); + final previewEmailFromEmlFileInteractor = MockPreviewEmailFromEmlFileInteractor(); final printUtils = MockPrintUtils(); final applicationManager = MockApplicationManager(); final mockToastManager = MockToastManager(); @@ -191,6 +197,8 @@ void main() { storeOpenedEmailInteractor, printEmailInteractor, storeEventAttendanceStatusInteractor, + parseEmailByBlobIdInteractor, + previewEmailFromEmlFileInteractor, ); }); diff --git a/test/features/model/attachment_extension_test.dart b/test/features/model/attachment_extension_test.dart index 2b686ec27..689c8edf8 100644 --- a/test/features/model/attachment_extension_test.dart +++ b/test/features/model/attachment_extension_test.dart @@ -1,7 +1,7 @@ +import 'package:core/core.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http_parser/http_parser.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart'; void main() { final attachmentA = Attachment( @@ -34,7 +34,7 @@ void main() { 'WHEN perform call `attachmentA.isDisplayedPDFIcon()`\n' 'SHOULD return true', () { - bool result = attachmentA.type!.validatePDFIcon(fileName: null); + bool result = attachmentA.type!.isPDFFile(fileName: null); expect(result, isTrue); }); @@ -45,7 +45,7 @@ void main() { 'WHEN perform call `attachmentB.isDisplayedPDFIcon()`\n' 'SHOULD return true', () { - bool result = attachmentB.type!.validatePDFIcon(fileName: 'attachmentB.pdf'); + bool result = attachmentB.type!.isPDFFile(fileName: 'attachmentB.pdf'); expect(result, isTrue); }); @@ -56,7 +56,7 @@ void main() { 'WHEN perform call `attachmentC.isDisplayedPDFIcon()`\n' 'SHOULD return false', () { - bool result = attachmentC.type!.validatePDFIcon(fileName: 'attachmentC.docx'); + bool result = attachmentC.type!.isPDFFile(fileName: 'attachmentC.docx'); expect(result, isFalse); }); @@ -67,7 +67,7 @@ void main() { 'WHEN perform call `attachmentD.isDisplayedPDFIcon()`\n' 'SHOULD return false', () { - bool result = attachmentD.type!.validatePDFIcon(fileName: 'attachmentD.png'); + bool result = attachmentD.type!.isPDFFile(fileName: 'attachmentD.png'); expect(result, isFalse); }); @@ -78,7 +78,7 @@ void main() { 'WHEN perform call `attachmentE.isDisplayedPDFIcon()`\n' 'SHOULD return false', () { - bool result = attachmentE.type!.validatePDFIcon(fileName: 'attachmentE.pdf'); + bool result = attachmentE.type!.isPDFFile(fileName: 'attachmentE.pdf'); expect(result, isFalse); });