diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart index 081472b13..839be6126 100644 --- a/lib/features/base/base_controller.dart +++ b/lib/features/base/base_controller.dart @@ -16,6 +16,7 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/model.dart'; import 'package:rule_filter/rule_filter/capability_rule_filter.dart'; import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart'; +import 'package:tmail_ui_user/features/base/mixin/emit_state_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/logout_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; @@ -63,7 +64,7 @@ import 'package:tmail_ui_user/main/utils/twake_app_manager.dart'; import 'package:uuid/uuid.dart'; abstract class BaseController extends GetxController - with PopupContextMenuActionMixin, LogoutMixin { + with PopupContextMenuActionMixin, LogoutMixin, EmitStateMixin { final CachingManager cachingManager = Get.find(); final LanguageCacheManager languageCacheManager = Get.find(); diff --git a/lib/features/base/mixin/emit_state_mixin.dart b/lib/features/base/mixin/emit_state_mixin.dart new file mode 100644 index 000000000..4db57e5cd --- /dev/null +++ b/lib/features/base/mixin/emit_state_mixin.dart @@ -0,0 +1,12 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; + +mixin EmitStateMixin { + void emitFailure({ + required BaseController controller, + required FeatureFailure failure, + }) { + controller.consumeState(Stream.value(Left(failure))); + } +} diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 973a84e21..c3c2f1035 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -15,6 +15,7 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_e import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_recipients_collapsed_extensions.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/mark_as_important_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/preview_upload_file_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/remove_draggable_email_address_between_recipient_fields_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart'; @@ -70,6 +71,22 @@ class ComposerView extends GetWidget { } }); + final attachmentWidget = Obx(() { + if (controller.uploadController.listUploadAttachments.isNotEmpty) { + return AttachmentComposerWidget( + listFileUploaded: controller.uploadController.listUploadAttachments, + isCollapsed: controller.isAttachmentCollapsed, + onDeleteAttachmentAction: controller.deleteAttachmentUploaded, + onPreviewAttachmentAction: (id) => + controller.previewUploadFile(context, id), + onToggleExpandAttachmentAction: (isCollapsed) => + controller.isAttachmentCollapsed = isCollapsed, + ); + } else { + return const SizedBox.shrink(); + } + }); + final bodyWidget = ResponsiveWidget( responsiveUtils: controller.responsiveUtils, mobile: MobileResponsiveContainerView( @@ -251,18 +268,7 @@ class ComposerView extends GetWidget { } ), ), - Obx(() { - if (controller.uploadController.listUploadAttachments.isNotEmpty) { - return AttachmentComposerWidget( - listFileUploaded: controller.uploadController.listUploadAttachments, - isCollapsed: controller.isAttachmentCollapsed, - onDeleteAttachmentAction: controller.deleteAttachmentUploaded, - onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, - ); - } else { - return const SizedBox.shrink(); - } - }), + attachmentWidget, Obx(() { if (controller.richTextWebController!.isFormattingOptionsEnabled) { return ToolbarRichTextWidget( @@ -504,18 +510,7 @@ class ComposerView extends GetWidget { } ), ), - Obx(() { - if (controller.uploadController.listUploadAttachments.isNotEmpty) { - return AttachmentComposerWidget( - listFileUploaded: controller.uploadController.listUploadAttachments, - isCollapsed: controller.isAttachmentCollapsed, - onDeleteAttachmentAction: controller.deleteAttachmentUploaded, - onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, - ); - } else { - return const SizedBox.shrink(); - } - }), + attachmentWidget, Obx(() { if (controller.richTextWebController!.isFormattingOptionsEnabled) { return ToolbarRichTextWidget( @@ -785,18 +780,7 @@ class ComposerView extends GetWidget { } ), ), - Obx(() { - if (controller.uploadController.listUploadAttachments.isNotEmpty) { - return AttachmentComposerWidget( - listFileUploaded: controller.uploadController.listUploadAttachments, - isCollapsed: controller.isAttachmentCollapsed, - onDeleteAttachmentAction: controller.deleteAttachmentUploaded, - onToggleExpandAttachmentAction: (isCollapsed) => controller.isAttachmentCollapsed = isCollapsed, - ); - } else { - return const SizedBox.shrink(); - } - }), + attachmentWidget, Obx(() { if (controller.richTextWebController!.isFormattingOptionsEnabled) { return ToolbarRichTextWidget( diff --git a/lib/features/composer/presentation/extensions/preview_upload_file_extension.dart b/lib/features/composer/presentation/extensions/preview_upload_file_extension.dart new file mode 100644 index 000000000..3ce6710b4 --- /dev/null +++ b/lib/features/composer/presentation/extensions/preview_upload_file_extension.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_download_attachment_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart'; +import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +extension PreviewUploadFileExtension on ComposerController { + void previewUploadFile(BuildContext context, UploadTaskId uploadId) { + final uploadFile = uploadController.getUploadFileId(uploadId); + + if (uploadFile == null || uploadFile.attachment == null) { + appToast.showToastWarningMessage( + context, + AppLocalizations.of(context).noPreviewAvailable, + ); + return; + } + + mailboxDashBoardController.previewUploadFile( + context: context, + uploadFile: uploadFile, + isDialogLoadingVisible: true, + onPreviewOrDownloadAction: (attachment, isPreview) { + mailboxDashBoardController.downloadAttachment( + attachment: attachment, + previewerSupported: isPreview, + ); + }, + ); + } +} diff --git a/lib/features/composer/presentation/widgets/attachment_item_composer_widget.dart b/lib/features/composer/presentation/widgets/attachment_item_composer_widget.dart index fecabe3f4..ac0281517 100644 --- a/lib/features/composer/presentation/widgets/attachment_item_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/attachment_item_composer_widget.dart @@ -3,7 +3,6 @@ import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/text/middle_ellipsis_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_item_composer_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_progress_loading_composer_widget.dart'; @@ -11,11 +10,11 @@ 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'; typedef OnDeleteAttachmentAction = void Function(UploadTaskId uploadTaskId); +typedef OnPreviewAttachmentAction = void Function(UploadTaskId uploadTaskId); class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin { - final _imagePaths = Get.find(); - + final ImagePaths imagePaths; final String fileIcon; final String fileName; final String fileSize; @@ -26,10 +25,12 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin { final EdgeInsetsGeometry? itemMargin; final EdgeInsetsGeometry? itemPadding; final OnDeleteAttachmentAction? onDeleteAttachmentAction; + final OnPreviewAttachmentAction? onPreviewAttachmentAction; final Widget? buttonAction; - AttachmentItemComposerWidget({ + const AttachmentItemComposerWidget({ super.key, + required this.imagePaths, required this.fileIcon, required this.fileName, required this.fileSize, @@ -41,10 +42,65 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin { this.itemPadding, this.buttonAction, this.onDeleteAttachmentAction, + this.onPreviewAttachmentAction, }); @override Widget build(BuildContext context) { + Widget bodyItem = Row( + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SvgPicture.asset( + fileIcon, + width: AttachmentItemComposerWidgetStyle.iconSize, + height: AttachmentItemComposerWidgetStyle.iconSize, + fit: BoxFit.fill + ), + const SizedBox(width: AttachmentItemComposerWidgetStyle.space), + Flexible( + child: MiddleEllipsisText( + fileName, + style: AttachmentItemComposerWidgetStyle.labelTextStyle, + ) + ), + const SizedBox(width: AttachmentItemComposerWidgetStyle.space), + Text( + fileSize, + style: AttachmentItemComposerWidgetStyle.sizeLabelTextStyle + ), + ], + ), + AttachmentProgressLoadingComposerWidget( + uploadStatus: uploadStatus, + percentUploading: percentUploading, + ) + ], + ), + ), + const SizedBox(width: AttachmentItemComposerWidgetStyle.space), + TMailButtonWidget.fromIcon( + icon: imagePaths.icCancel, + iconSize: AttachmentItemComposerWidgetStyle.deleteIconSize, + borderRadius: AttachmentItemComposerWidgetStyle.deleteIconRadius, + padding: AttachmentItemComposerWidgetStyle.deleteIconPadding, + iconColor: AttachmentItemComposerWidgetStyle.deleteIconColor, + onTapActionCallback: () => onDeleteAttachmentAction?.call(uploadTaskId), + ) + ], + ); + + if (onPreviewAttachmentAction != null) { + bodyItem = InkWell( + onTap: () => onPreviewAttachmentAction!(uploadTaskId), + child: bodyItem, + ); + } + return Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(AttachmentItemComposerWidgetStyle.radius)), @@ -54,52 +110,7 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin { width: AttachmentItemComposerWidgetStyle.width, padding: itemPadding ?? AttachmentItemComposerWidgetStyle.padding, margin: itemMargin, - child: Row( - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - SvgPicture.asset( - fileIcon, - width: AttachmentItemComposerWidgetStyle.iconSize, - height: AttachmentItemComposerWidgetStyle.iconSize, - fit: BoxFit.fill - ), - const SizedBox(width: AttachmentItemComposerWidgetStyle.space), - Flexible( - child: MiddleEllipsisText( - fileName, - style: AttachmentItemComposerWidgetStyle.labelTextStyle, - ) - ), - const SizedBox(width: AttachmentItemComposerWidgetStyle.space), - Text( - fileSize, - style: AttachmentItemComposerWidgetStyle.sizeLabelTextStyle - ), - ], - ), - AttachmentProgressLoadingComposerWidget( - uploadStatus: uploadStatus, - percentUploading: percentUploading, - ) - ], - ), - ), - const SizedBox(width: AttachmentItemComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: _imagePaths.icCancel, - iconSize: AttachmentItemComposerWidgetStyle.deleteIconSize, - borderRadius: AttachmentItemComposerWidgetStyle.deleteIconRadius, - padding: AttachmentItemComposerWidgetStyle.deleteIconPadding, - iconColor: AttachmentItemComposerWidgetStyle.deleteIconColor, - onTapActionCallback: () => onDeleteAttachmentAction?.call(uploadTaskId), - ) - ], - ), + child: bodyItem, ); } } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/mobile/mobile_attachment_composer_widget.dart b/lib/features/composer/presentation/widgets/mobile/mobile_attachment_composer_widget.dart index 6c77ae4a0..0e3803cd3 100644 --- a/lib/features/composer/presentation/widgets/mobile/mobile_attachment_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/mobile/mobile_attachment_composer_widget.dart @@ -76,6 +76,7 @@ class _MobileAttachmentComposerWidgetState extends State { runSpacing: AttachmentComposerWidgetStyle.listItemSpace, children: widget.listFileUploaded .map((file) => AttachmentItemComposerWidget( + imagePaths: _imagePaths, fileIcon: file.getIcon(_imagePaths), fileName: file.fileName, fileSize: filesize(file.fileSize), @@ -83,6 +86,7 @@ class _AttachmentComposerWidgetState extends State { percentUploading: file.percentUploading, uploadTaskId: file.uploadTaskId, onDeleteAttachmentAction: widget.onDeleteAttachmentAction, + onPreviewAttachmentAction: widget.onPreviewAttachmentAction, )) .toList(), ), diff --git a/lib/features/mailbox_dashboard/data/datasource/download_datasource.dart b/lib/features/download/data/datasource/download_datasource.dart similarity index 100% rename from lib/features/mailbox_dashboard/data/datasource/download_datasource.dart rename to lib/features/download/data/datasource/download_datasource.dart diff --git a/lib/features/mailbox_dashboard/data/datasource_impl/download_datasource_impl.dart b/lib/features/download/data/datasource_impl/download_datasource_impl.dart similarity index 95% rename from lib/features/mailbox_dashboard/data/datasource_impl/download_datasource_impl.dart rename to lib/features/download/data/datasource_impl/download_datasource_impl.dart index 1e0903488..cbc789f2b 100644 --- a/lib/features/mailbox_dashboard/data/datasource_impl/download_datasource_impl.dart +++ b/lib/features/download/data/datasource_impl/download_datasource_impl.dart @@ -11,7 +11,7 @@ import 'package:model/account/account_request.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/download_datasource.dart'; +import 'package:tmail_ui_user/features/download/data/datasource/download_datasource.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class DownloadDatasourceImpl extends DownloadDatasource { diff --git a/lib/features/mailbox_dashboard/data/repository/download_repository_impl.dart b/lib/features/download/data/repository/download_repository_impl.dart similarity index 79% rename from lib/features/mailbox_dashboard/data/repository/download_repository_impl.dart rename to lib/features/download/data/repository/download_repository_impl.dart index 65e2b0c7b..c5263597b 100644 --- a/lib/features/mailbox_dashboard/data/repository/download_repository_impl.dart +++ b/lib/features/download/data/repository/download_repository_impl.dart @@ -10,12 +10,12 @@ 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:tmail_ui_user/features/download/data/datasource/download_datasource.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.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/domain/model/preview_email_eml_request.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/download_datasource.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; class DownloadRepositoryImpl extends DownloadRepository { final DownloadDatasource _downloadDatasource; @@ -131,4 +131,40 @@ class DownloadRepositoryImpl extends DownloadRepository { return _emailDataSource[DataSourceType.session]! .getPreviewEMLContentInMemory(keyStored); } + + @override + Future exportAttachment( + Attachment attachment, + AccountId accountId, + String baseDownloadUrl, + AccountRequest accountRequest, + CancelToken cancelToken, + ) { + return _emailDataSource[DataSourceType.network]!.exportAttachment( + attachment, + accountId, + baseDownloadUrl, + accountRequest, + cancelToken, + ); + } + + @override + Future exportAllAttachments( + AccountId accountId, + EmailId emailId, + String baseDownloadAllUrl, + String outputFileName, + AccountRequest accountRequest, + CancelToken cancelToken, + ) { + return _emailDataSource[DataSourceType.network]!.exportAllAttachments( + accountId, + emailId, + baseDownloadAllUrl, + outputFileName, + accountRequest, + cancelToken: cancelToken, + ); + } } diff --git a/lib/features/download/domain/exceptions/download_attachment_exceptions.dart b/lib/features/download/domain/exceptions/download_attachment_exceptions.dart new file mode 100644 index 000000000..2a9c3df03 --- /dev/null +++ b/lib/features/download/domain/exceptions/download_attachment_exceptions.dart @@ -0,0 +1,5 @@ +class DownloadAttachmentInteractorIsNull implements Exception {} + +class CapabilityDownloadAllNotSupportedException implements Exception {} + +class DownloadUrlIsNullException implements Exception {} \ No newline at end of file diff --git a/lib/features/download/domain/model/download_source_view.dart b/lib/features/download/domain/model/download_source_view.dart new file mode 100644 index 000000000..14585c096 --- /dev/null +++ b/lib/features/download/domain/model/download_source_view.dart @@ -0,0 +1,4 @@ +enum DownloadSourceView { + emailView, + composerView; +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/repository/download_repository.dart b/lib/features/download/domain/repository/download_repository.dart similarity index 81% rename from lib/features/mailbox_dashboard/domain/repository/download_repository.dart rename to lib/features/download/domain/repository/download_repository.dart index df818eef8..5d2bc4003 100644 --- a/lib/features/mailbox_dashboard/domain/repository/download_repository.dart +++ b/lib/features/download/domain/repository/download_repository.dart @@ -1,6 +1,7 @@ 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:core/presentation/utils/html_transformer/transform_configuration.dart'; @@ -59,4 +60,21 @@ abstract class DownloadRepository { Future removePreviewEmailEMLContentShared(String keyStored); Future getPreviewEMLContentInMemory(String keyStored); + + Future exportAttachment( + Attachment attachment, + AccountId accountId, + String baseDownloadUrl, + AccountRequest accountRequest, + CancelToken cancelToken, + ); + + Future exportAllAttachments( + AccountId accountId, + EmailId emailId, + String baseDownloadAllUrl, + String outputFileName, + AccountRequest accountRequest, + CancelToken cancelToken, + ); } diff --git a/lib/features/email/domain/state/download_all_attachments_for_web_state.dart b/lib/features/download/domain/state/download_all_attachments_for_web_state.dart similarity index 100% rename from lib/features/email/domain/state/download_all_attachments_for_web_state.dart rename to lib/features/download/domain/state/download_all_attachments_for_web_state.dart diff --git a/lib/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart b/lib/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart new file mode 100644 index 000000000..ebe0a7cfa --- /dev/null +++ b/lib/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart @@ -0,0 +1,62 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:model/email/attachment.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; + +class DownloadAndGettingHtmlContentFromAttachment extends LoadingState { + DownloadAndGettingHtmlContentFromAttachment({ + required this.blobId, + this.sourceView, + }); + + final Id? blobId; + final DownloadSourceView? sourceView; + + @override + List get props => [blobId, sourceView]; +} + +class DownloadAndGetHtmlContentFromAttachmentSuccess extends UIState { + DownloadAndGetHtmlContentFromAttachmentSuccess({ + required this.sanitizedHtmlContent, + required this.htmlAttachmentTitle, + required this.attachment, + required this.accountId, + required this.session, + this.sourceView, + }); + + final String sanitizedHtmlContent; + final String htmlAttachmentTitle; + final Attachment attachment; + final AccountId accountId; + final Session session; + final DownloadSourceView? sourceView; + + @override + List get props => [ + sanitizedHtmlContent, + htmlAttachmentTitle, + attachment, + accountId, + session, + sourceView, + ]; +} + +class DownloadAndGetHtmlContentFromAttachmentFailure extends FeatureFailure { + DownloadAndGetHtmlContentFromAttachmentFailure({ + super.exception, + required this.blobId, + this.sourceView, + }); + + final Id? blobId; + final DownloadSourceView? sourceView; + + @override + List get props => [blobId, sourceView]; +} diff --git a/lib/features/email/domain/state/download_attachment_for_web_state.dart b/lib/features/download/domain/state/download_attachment_for_web_state.dart similarity index 55% rename from lib/features/email/domain/state/download_attachment_for_web_state.dart rename to lib/features/download/domain/state/download_attachment_for_web_state.dart index ec8409c6a..c805e037e 100644 --- a/lib/features/email/domain/state/download_attachment_for_web_state.dart +++ b/lib/features/download/domain/state/download_attachment_for_web_state.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/state/success.dart'; import 'package:dio/dio.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; class IdleDownloadAttachmentForWeb extends UIState {} @@ -13,11 +14,24 @@ class StartDownloadAttachmentForWeb extends UIState { final Attachment attachment; final CancelToken? cancelToken; final bool previewerSupported; + final DownloadSourceView? sourceView; - StartDownloadAttachmentForWeb(this.taskId, this.attachment, [this.cancelToken, this.previewerSupported = false]); + StartDownloadAttachmentForWeb( + this.taskId, + this.attachment, [ + this.cancelToken, + this.previewerSupported = false, + this.sourceView, + ]); @override - List get props => [taskId, attachment, cancelToken, previewerSupported]; + List get props => [ + taskId, + attachment, + cancelToken, + previewerSupported, + sourceView, + ]; } class DownloadingAttachmentForWeb extends UIState { @@ -27,22 +41,25 @@ class DownloadingAttachmentForWeb extends UIState { final double progress; final int downloaded; final int total; + final DownloadSourceView? sourceView; DownloadingAttachmentForWeb( this.taskId, this.attachment, this.progress, this.downloaded, - this.total + this.total, + this.sourceView, ); @override - List get props => [ + List get props => [ taskId, attachment, progress, downloaded, - total + total, + sourceView, ]; } @@ -52,11 +69,24 @@ class DownloadAttachmentForWebSuccess extends UIState { final Attachment attachment; final Uint8List bytes; final bool previewerSupported; + final DownloadSourceView? sourceView; - DownloadAttachmentForWebSuccess(this.taskId, this.attachment, this.bytes, this.previewerSupported); + DownloadAttachmentForWebSuccess( + this.taskId, + this.attachment, + this.bytes, + this.previewerSupported, + this.sourceView, + ); @override - List get props => [taskId, attachment, bytes, previewerSupported]; + List get props => [ + taskId, + attachment, + bytes, + previewerSupported, + sourceView, + ]; } class DownloadAttachmentForWebFailure extends FeatureFailure { @@ -64,14 +94,22 @@ class DownloadAttachmentForWebFailure extends FeatureFailure { final DownloadTaskId? taskId; final Attachment? attachment; final CancelToken? cancelToken; + final DownloadSourceView? sourceView; DownloadAttachmentForWebFailure({ this.attachment, this.taskId, this.cancelToken, - super.exception + this.sourceView, + super.exception, }); @override - List get props => [attachment, taskId, cancelToken, ...super.props]; + List get props => [ + attachment, + taskId, + cancelToken, + sourceView, + ...super.props, + ]; } \ No newline at end of file diff --git a/lib/features/email/domain/state/export_all_attachments_state.dart b/lib/features/download/domain/state/export_all_attachments_state.dart similarity index 100% rename from lib/features/email/domain/state/export_all_attachments_state.dart rename to lib/features/download/domain/state/export_all_attachments_state.dart diff --git a/lib/features/email/domain/state/export_attachment_state.dart b/lib/features/download/domain/state/export_attachment_state.dart similarity index 100% rename from lib/features/email/domain/state/export_attachment_state.dart rename to lib/features/download/domain/state/export_attachment_state.dart diff --git a/lib/features/download/domain/state/get_html_content_from_upload_file_state.dart b/lib/features/download/domain/state/get_html_content_from_upload_file_state.dart new file mode 100644 index 000000000..6f31a0cc9 --- /dev/null +++ b/lib/features/download/domain/state/get_html_content_from_upload_file_state.dart @@ -0,0 +1,36 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:model/email/attachment.dart'; + +class GettingHtmlContentFromUploadFile extends LoadingState {} + +class GetHtmlContentFromUploadFileSuccess extends UIState { + GetHtmlContentFromUploadFileSuccess({ + required this.sanitizedHtmlContent, + required this.htmlAttachmentTitle, + required this.attachment, + required this.accountId, + required this.session, + }); + + final String sanitizedHtmlContent; + final String htmlAttachmentTitle; + final Attachment attachment; + final AccountId? accountId; + final Session? session; + + @override + List get props => [ + sanitizedHtmlContent, + htmlAttachmentTitle, + attachment, + accountId, + session, + ]; +} + +class GetHtmlContentFromUploadFileFailure extends FeatureFailure { + GetHtmlContentFromUploadFileFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/email/domain/state/get_preview_email_eml_content_shared_state.dart b/lib/features/download/domain/state/get_preview_email_eml_content_shared_state.dart similarity index 100% rename from lib/features/email/domain/state/get_preview_email_eml_content_shared_state.dart rename to lib/features/download/domain/state/get_preview_email_eml_content_shared_state.dart diff --git a/lib/features/email/domain/state/get_preview_eml_content_in_memory_state.dart b/lib/features/download/domain/state/get_preview_eml_content_in_memory_state.dart similarity index 100% rename from lib/features/email/domain/state/get_preview_eml_content_in_memory_state.dart rename to lib/features/download/domain/state/get_preview_eml_content_in_memory_state.dart diff --git a/lib/features/email/domain/state/move_preview_eml_content_from_persistent_to_memory_state.dart b/lib/features/download/domain/state/move_preview_eml_content_from_persistent_to_memory_state.dart similarity index 100% rename from lib/features/email/domain/state/move_preview_eml_content_from_persistent_to_memory_state.dart rename to lib/features/download/domain/state/move_preview_eml_content_from_persistent_to_memory_state.dart diff --git a/lib/features/email/domain/state/parse_email_by_blob_id_state.dart b/lib/features/download/domain/state/parse_email_by_blob_id_state.dart similarity index 94% rename from lib/features/email/domain/state/parse_email_by_blob_id_state.dart rename to lib/features/download/domain/state/parse_email_by_blob_id_state.dart index 0d528b6f5..eb0718c00 100644 --- a/lib/features/email/domain/state/parse_email_by_blob_id_state.dart +++ b/lib/features/download/domain/state/parse_email_by_blob_id_state.dart @@ -8,8 +8,8 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; class ParsingEmailByBlobId extends LoadingState {} class ParseEmailByBlobIdSuccess extends UIState { - final AccountId? accountId; - final Session? session; + final AccountId accountId; + final Session session; final String ownEmailAddress; final Email email; final Id blobId; diff --git a/lib/features/email/domain/state/preview_email_from_eml_file_state.dart b/lib/features/download/domain/state/preview_email_from_eml_file_state.dart similarity index 80% rename from lib/features/email/domain/state/preview_email_from_eml_file_state.dart rename to lib/features/download/domain/state/preview_email_from_eml_file_state.dart index 89bb7339b..94698d3b0 100644 --- a/lib/features/email/domain/state/preview_email_from_eml_file_state.dart +++ b/lib/features/download/domain/state/preview_email_from_eml_file_state.dart @@ -3,20 +3,23 @@ import 'package:core/presentation/state/success.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class PreviewingEmailFromEmlFile extends LoadingState {} class PreviewEmailFromEmlFileSuccess extends UIState { final EMLPreviewer emlPreviewer; - final AccountId? accountId; - final Session? session; + final AccountId accountId; + final Session session; final String ownEmailAddress; + final AppLocalizations appLocalizations; PreviewEmailFromEmlFileSuccess( this.emlPreviewer, this.accountId, this.session, this.ownEmailAddress, + this.appLocalizations, ); @override @@ -25,6 +28,7 @@ class PreviewEmailFromEmlFileSuccess extends UIState { accountId, session, ownEmailAddress, + appLocalizations, ]; } diff --git a/lib/features/email/domain/state/remove_preview_email_eml_content_shared_state.dart b/lib/features/download/domain/state/remove_preview_email_eml_content_shared_state.dart similarity index 100% rename from lib/features/email/domain/state/remove_preview_email_eml_content_shared_state.dart rename to lib/features/download/domain/state/remove_preview_email_eml_content_shared_state.dart diff --git a/lib/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart b/lib/features/download/domain/usecase/download_all_attachments_for_web_interactor.dart similarity index 94% rename from lib/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart rename to lib/features/download/domain/usecase/download_all_attachments_for_web_interactor.dart index 5a3fef917..7e786f0b1 100644 --- a/lib/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart +++ b/lib/features/download/domain/usecase/download_all_attachments_for_web_interactor.dart @@ -13,11 +13,11 @@ import 'package:model/account/authentication_type.dart'; import 'package:model/account/password.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_all_attachments_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_all_attachments_for_web_state.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class DownloadAllAttachmentsForWebInteractor { const DownloadAllAttachmentsForWebInteractor( diff --git a/lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart b/lib/features/download/domain/usecase/download_and_get_html_content_from_attachment_interactor.dart similarity index 56% rename from lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart rename to lib/features/download/domain/usecase/download_and_get_html_content_from_attachment_interactor.dart index dfd6165aa..e33cbe649 100644 --- a/lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart +++ b/lib/features/download/domain/usecase/download_and_get_html_content_from_attachment_interactor.dart @@ -7,14 +7,16 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/string_convert.dart'; import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; -class GetHtmlContentFromAttachmentInteractor { - GetHtmlContentFromAttachmentInteractor( +class DownloadAndGetHtmlContentFromAttachmentInteractor { + DownloadAndGetHtmlContentFromAttachmentInteractor( this._downloadAttachmentForWebInteractor, ); @@ -22,31 +24,41 @@ class GetHtmlContentFromAttachmentInteractor { Stream> execute( AccountId accountId, + Session session, Attachment attachment, DownloadTaskId taskId, String baseDownloadUrl, - TransformConfiguration transformConfiguration, - ) async* { + TransformConfiguration transformConfiguration, { + DownloadSourceView? sourceView, + }) async* { try { - yield Right(GettingHtmlContentFromAttachment(attachment: attachment)); + yield Right(DownloadAndGettingHtmlContentFromAttachment( + blobId: attachment.blobId, + sourceView: sourceView, + )); final downloadState = await _downloadAttachmentForWebInteractor.execute( taskId, attachment, accountId, baseDownloadUrl, + sourceView: sourceView, ).last; Either? sanitizeState; await downloadState.fold( (failure) { - sanitizeState = Left(GetHtmlContentFromAttachmentFailure( + sanitizeState = Left(DownloadAndGetHtmlContentFromAttachmentFailure( exception: failure is FeatureFailure ? failure.exception : null, - attachment: attachment, + blobId: attachment.blobId, + sourceView: sourceView, )); }, (success) async { if (success is! DownloadAttachmentForWebSuccess) { - sanitizeState = Right(GettingHtmlContentFromAttachment(attachment: attachment)); + sanitizeState = Right(DownloadAndGettingHtmlContentFromAttachment( + blobId: attachment.blobId, + sourceView: sourceView, + )); } else { final htmlContent = StringConvert.decodeFromBytes( success.bytes, @@ -57,6 +69,9 @@ class GetHtmlContentFromAttachmentInteractor { htmlContent, transformConfiguration, attachment, + accountId, + session, + sourceView, ); } }, @@ -65,17 +80,18 @@ class GetHtmlContentFromAttachmentInteractor { if (sanitizeState != null) { yield sanitizeState!; } else { - yield Left(GetHtmlContentFromAttachmentFailure( + yield Left(DownloadAndGetHtmlContentFromAttachmentFailure( exception: null, - attachment: attachment, + blobId: attachment.blobId, + sourceView: sourceView, )); } - } catch (e) { logError('GetHtmlContentFromAttachmentInteractor:exception: $e'); - yield Left(GetHtmlContentFromAttachmentFailure( + yield Left(DownloadAndGetHtmlContentFromAttachmentFailure( exception: e, - attachment: attachment, + blobId: attachment.blobId, + sourceView: sourceView, )); } } @@ -84,21 +100,28 @@ class GetHtmlContentFromAttachmentInteractor { String htmlContent, TransformConfiguration transformConfiguration, Attachment attachment, + AccountId accountId, + Session session, + DownloadSourceView? sourceView, ) async { try { final sanitizedHtmlContent = await _downloadAttachmentForWebInteractor .downloadRepository .sanitizeHtmlContent(htmlContent, transformConfiguration); - return Right(GetHtmlContentFromAttachmentSuccess( + return Right(DownloadAndGetHtmlContentFromAttachmentSuccess( sanitizedHtmlContent: sanitizedHtmlContent, htmlAttachmentTitle: attachment.generateFileName(), attachment: attachment, + accountId: accountId, + session: session, + sourceView: sourceView, )); } catch (e) { - return Left(GetHtmlContentFromAttachmentFailure( + return Left(DownloadAndGetHtmlContentFromAttachmentFailure( exception: e, - attachment: attachment, + blobId: attachment.blobId, + sourceView: sourceView, )); } } diff --git a/lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart b/lib/features/download/domain/usecase/download_attachment_for_web_interactor.dart similarity index 81% rename from lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart rename to lib/features/download/domain/usecase/download_attachment_for_web_interactor.dart index 5ec2edfa0..c550f6d63 100644 --- a/lib/features/email/domain/usecases/download_attachment_for_web_interactor.dart +++ b/lib/features/download/domain/usecase/download_attachment_for_web_interactor.dart @@ -11,11 +11,12 @@ import 'package:model/account/authentication_type.dart'; import 'package:model/account/password.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class DownloadAttachmentForWebInteractor { final DownloadRepository downloadRepository; @@ -37,10 +38,20 @@ class DownloadAttachmentForWebInteractor { StreamController>? onReceiveController, CancelToken? cancelToken, bool previewerSupported = false, + DownloadSourceView? sourceView, }) async* { try { - yield Right(StartDownloadAttachmentForWeb(taskId, attachment, cancelToken, previewerSupported)); - onReceiveController?.add(Right(StartDownloadAttachmentForWeb(taskId, attachment, cancelToken, previewerSupported))); + final loadingState = Right( + StartDownloadAttachmentForWeb( + taskId, + attachment, + cancelToken, + previewerSupported, + sourceView, + ), + ); + yield loadingState; + onReceiveController?.add(loadingState); final currentAccount = await _accountRepository.getCurrentAccount(); AccountRequest? accountRequest; @@ -72,6 +83,7 @@ class DownloadAttachmentForWebInteractor { attachment, bytesDownloaded, previewerSupported, + sourceView, ) ); } catch (exception) { @@ -79,8 +91,9 @@ class DownloadAttachmentForWebInteractor { DownloadAttachmentForWebFailure( attachment: attachment, taskId: taskId, - exception: exception, + sourceView: sourceView, cancelToken: cancelToken, + exception: exception, ) ); } diff --git a/lib/features/email/domain/usecases/export_all_attachments_interactor.dart b/lib/features/download/domain/usecase/export_all_attachments_interactor.dart similarity index 79% rename from lib/features/email/domain/usecases/export_all_attachments_interactor.dart rename to lib/features/download/domain/usecase/export_all_attachments_interactor.dart index d0ee0ca8a..1dd4ceece 100644 --- a/lib/features/email/domain/usecases/export_all_attachments_interactor.dart +++ b/lib/features/download/domain/usecase/export_all_attachments_interactor.dart @@ -9,31 +9,31 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/account/account_request.dart'; import 'package:model/account/authentication_type.dart'; import 'package:model/account/password.dart'; -import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; -import 'package:tmail_ui_user/features/email/domain/state/export_all_attachments_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/export_all_attachments_state.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; class ExportAllAttachmentsInteractor { const ExportAllAttachmentsInteractor( - this._emailRepository, + this._downloadRepository, this._accountRepository, this._authenticationOIDCRepository, - this.credentialRepository, + this._credentialRepository, ); - final EmailRepository _emailRepository; + final DownloadRepository _downloadRepository; final AccountRepository _accountRepository; final AuthenticationOIDCRepository _authenticationOIDCRepository; - final CredentialRepository credentialRepository; + final CredentialRepository _credentialRepository; Stream> execute( AccountId accountId, EmailId emailId, String baseDownloadAllUrl, String outputFileName, - {CancelToken? cancelToken} + CancelToken cancelToken, ) async* { try { yield Right(ExportingAllAttachments()); @@ -43,20 +43,20 @@ class ExportAllAttachmentsInteractor { final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id); accountRequest = AccountRequest.withOidc(token: tokenOidc); } else { - final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored(); + final authenticationInfoCache = await _credentialRepository.getAuthenticationInfoStored(); accountRequest = AccountRequest.withBasic( userName: UserName(authenticationInfoCache.username), password: Password(authenticationInfoCache.password), ); } - final result = await _emailRepository.exportAllAttachments( + final result = await _downloadRepository.exportAllAttachments( accountId, emailId, baseDownloadAllUrl, outputFileName, accountRequest, - cancelToken: cancelToken, + cancelToken, ); yield Right(ExportAllAttachmentsSuccess(result)); diff --git a/lib/features/email/domain/usecases/export_attachment_interactor.dart b/lib/features/download/domain/usecase/export_attachment_interactor.dart similarity index 78% rename from lib/features/email/domain/usecases/export_attachment_interactor.dart rename to lib/features/download/domain/usecase/export_attachment_interactor.dart index 1dc8d86f5..84e8c0bc4 100644 --- a/lib/features/email/domain/usecases/export_attachment_interactor.dart +++ b/lib/features/download/domain/usecase/export_attachment_interactor.dart @@ -6,21 +6,21 @@ import 'package:dio/dio.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:model/model.dart'; -import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; -import 'package:tmail_ui_user/features/email/domain/state/export_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/export_attachment_state.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; class ExportAttachmentInteractor { - final EmailRepository emailRepository; - final CredentialRepository credentialRepository; + final DownloadRepository _downloadRepository; + final CredentialRepository _credentialRepository; final AccountRepository _accountRepository; final AuthenticationOIDCRepository _authenticationOIDCRepository; ExportAttachmentInteractor( - this.emailRepository, - this.credentialRepository, + this._downloadRepository, + this._credentialRepository, this._accountRepository, this._authenticationOIDCRepository, ); @@ -40,14 +40,14 @@ class ExportAttachmentInteractor { final tokenOidc = await _authenticationOIDCRepository.getStoredTokenOIDC(currentAccount.id); accountRequest = AccountRequest.withOidc(token: tokenOidc); } else { - final authenticationInfoCache = await credentialRepository.getAuthenticationInfoStored(); + final authenticationInfoCache = await _credentialRepository.getAuthenticationInfoStored(); accountRequest = AccountRequest.withBasic( userName: UserName(authenticationInfoCache.username), password: Password(authenticationInfoCache.password), ); } - final downloadedResponse = await emailRepository.exportAttachment( + final downloadedResponse = await _downloadRepository.exportAttachment( attachment, accountId, baseDownloadUrl, diff --git a/lib/features/download/domain/usecase/get_html_content_from_upload_file_interactor.dart b/lib/features/download/domain/usecase/get_html_content_from_upload_file_interactor.dart new file mode 100644 index 000000000..37a3e1ae3 --- /dev/null +++ b/lib/features/download/domain/usecase/get_html_content_from_upload_file_interactor.dart @@ -0,0 +1,57 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; +import 'package:core/utils/string_convert.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_html_content_from_upload_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart'; + +class GetHtmlContentFromUploadFileInteractor { + final DownloadRepository _downloadRepository; + + GetHtmlContentFromUploadFileInteractor(this._downloadRepository); + + Stream> execute({ + required UploadFileState uploadFile, + required Session? session, + required AccountId? accountId, + }) async* { + try { + yield Right(GettingHtmlContentFromUploadFile()); + + final htmlContent = StringConvert.decodeFromBytes( + uploadFile.file!.bytes!, + charset: uploadFile.attachment!.charset, + isHtml: true, + ); + + final sanitizedHtmlContent = + await _downloadRepository.sanitizeHtmlContent( + htmlContent, + TransformConfiguration.create( + customDomTransformers: [SanitizeHyperLinkTagInHtmlTransformer()], + customTextTransformers: [ + const StandardizeHtmlSanitizingTransformers() + ], + ), + ); + + yield Right(GetHtmlContentFromUploadFileSuccess( + sanitizedHtmlContent: sanitizedHtmlContent, + htmlAttachmentTitle: uploadFile.attachment!.generateFileName(), + attachment: uploadFile.attachment!, + accountId: accountId, + session: session, + )); + } catch (e) { + yield Left(GetHtmlContentFromUploadFileFailure(exception: e)); + } + } +} diff --git a/lib/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart b/lib/features/download/domain/usecase/get_preview_email_eml_content_shared_interactor.dart similarity index 80% rename from lib/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart rename to lib/features/download/domain/usecase/get_preview_email_eml_content_shared_interactor.dart index 42d73f293..4c23cd0e6 100644 --- a/lib/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart +++ b/lib/features/download/domain/usecase/get_preview_email_eml_content_shared_interactor.dart @@ -1,8 +1,8 @@ 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/state/get_preview_email_eml_content_shared_state.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_preview_email_eml_content_shared_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class GetPreviewEmailEMLContentSharedInteractor { final DownloadRepository _downloadRepository; diff --git a/lib/features/email/domain/usecases/get_preview_eml_content_in_memory_interactor.dart b/lib/features/download/domain/usecase/get_preview_eml_content_in_memory_interactor.dart similarity index 79% rename from lib/features/email/domain/usecases/get_preview_eml_content_in_memory_interactor.dart rename to lib/features/download/domain/usecase/get_preview_eml_content_in_memory_interactor.dart index 4b523db7f..eaaa7c3ef 100644 --- a/lib/features/email/domain/usecases/get_preview_eml_content_in_memory_interactor.dart +++ b/lib/features/download/domain/usecase/get_preview_eml_content_in_memory_interactor.dart @@ -1,8 +1,8 @@ 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/state/get_preview_eml_content_in_memory_state.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_preview_eml_content_in_memory_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class GetPreviewEmlContentInMemoryInteractor { final DownloadRepository _downloadRepository; diff --git a/lib/features/email/domain/usecases/move_preview_eml_content_from_persistent_to_memory_interactor.dart b/lib/features/download/domain/usecase/move_preview_eml_content_from_persistent_to_memory_interactor.dart similarity index 82% rename from lib/features/email/domain/usecases/move_preview_eml_content_from_persistent_to_memory_interactor.dart rename to lib/features/download/domain/usecase/move_preview_eml_content_from_persistent_to_memory_interactor.dart index 416f44f13..f24b2f799 100644 --- a/lib/features/email/domain/usecases/move_preview_eml_content_from_persistent_to_memory_interactor.dart +++ b/lib/features/download/domain/usecase/move_preview_eml_content_from_persistent_to_memory_interactor.dart @@ -1,9 +1,9 @@ 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/state/move_preview_eml_content_from_persistent_to_memory_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/move_preview_eml_content_from_persistent_to_memory_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class MovePreviewEmlContentFromPersistentToMemoryInteractor { final DownloadRepository _downloadRepository; diff --git a/lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart b/lib/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart similarity index 85% rename from lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart rename to lib/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart index a920c859f..8b8295e4f 100644 --- a/lib/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart +++ b/lib/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart @@ -4,8 +4,8 @@ import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:tmail_ui_user/features/email/domain/state/parse_email_by_blob_id_state.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class ParseEmailByBlobIdInteractor { final DownloadRepository _downloadRepository; diff --git a/lib/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart b/lib/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart similarity index 87% rename from lib/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart rename to lib/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart index 15a38ebc3..ce358ede7 100644 --- a/lib/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart +++ b/lib/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart @@ -3,9 +3,9 @@ import 'package:core/presentation/state/success.dart'; import 'package:core/utils/platform_info.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/state/preview_email_from_eml_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class PreviewEmailFromEmlFileInteractor { final DownloadRepository _downloadRepository; @@ -43,6 +43,7 @@ class PreviewEmailFromEmlFileInteractor { previewEmailEMLRequest.accountId, previewEmailEMLRequest.session, previewEmailEMLRequest.ownEmailAddress, + previewEmailEMLRequest.appLocalizations, ), ); } catch (e) { diff --git a/lib/features/email/domain/usecases/remove_preview_email_eml_content_shared_interactor.dart b/lib/features/download/domain/usecase/remove_preview_email_eml_content_shared_interactor.dart similarity index 78% rename from lib/features/email/domain/usecases/remove_preview_email_eml_content_shared_interactor.dart rename to lib/features/download/domain/usecase/remove_preview_email_eml_content_shared_interactor.dart index cf410104d..eccb3dcc1 100644 --- a/lib/features/email/domain/usecases/remove_preview_email_eml_content_shared_interactor.dart +++ b/lib/features/download/domain/usecase/remove_preview_email_eml_content_shared_interactor.dart @@ -1,8 +1,8 @@ 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/state/remove_preview_email_eml_content_shared_state.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/remove_preview_email_eml_content_shared_state.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; class RemovePreviewEmailEmlContentSharedInteractor { final DownloadRepository _downloadRepository; diff --git a/lib/features/mailbox_dashboard/presentation/bindings/download_interactor_bindings.dart b/lib/features/download/presentation/bindings/download_interactor_bindings.dart similarity index 65% rename from lib/features/mailbox_dashboard/presentation/bindings/download_interactor_bindings.dart rename to lib/features/download/presentation/bindings/download_interactor_bindings.dart index eae580942..8ae927b06 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/download_interactor_bindings.dart +++ b/lib/features/download/presentation/bindings/download_interactor_bindings.dart @@ -3,6 +3,22 @@ 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/caching/utils/session_storage_manager.dart'; +import 'package:tmail_ui_user/features/download/data/datasource/download_datasource.dart'; +import 'package:tmail_ui_user/features/download/data/datasource_impl/download_datasource_impl.dart'; +import 'package:tmail_ui_user/features/download/data/repository/download_repository_impl.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_all_attachments_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_and_get_html_content_from_attachment_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/export_all_attachments_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/export_attachment_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_html_content_from_upload_file_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_preview_eml_content_in_memory_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/move_preview_eml_content_from_persistent_to_memory_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/remove_preview_email_eml_content_shared_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_impl/email_datasource_impl.dart'; @@ -11,22 +27,9 @@ import 'package:tmail_ui_user/features/email/data/datasource_impl/email_session_ import 'package:tmail_ui_user/features/email/data/datasource_impl/html_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/domain/usecases/download_all_attachments_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.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/domain/usecases/get_preview_eml_content_in_memory_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/move_preview_eml_content_from_persistent_to_memory_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/remove_preview_email_eml_content_shared_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/download_datasource.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/download_datasource_impl.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/data/repository/download_repository_impl.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; @@ -84,6 +87,22 @@ class DownloadInteractorBindings extends InteractorsBindings { Get.find(), ), ); + Get.lazyPut( + () => ExportAttachmentInteractor( + Get.find(), + Get.find(), + Get.find(), + Get.find(), + ), + ); + Get.lazyPut( + () => ExportAllAttachmentsInteractor( + Get.find(), + Get.find(), + Get.find(), + Get.find(), + ), + ); Get.lazyPut( () => ParseEmailByBlobIdInteractor(Get.find()), ); @@ -91,7 +110,7 @@ class DownloadInteractorBindings extends InteractorsBindings { () => PreviewEmailFromEmlFileInteractor(Get.find()), ); Get.lazyPut( - () => GetHtmlContentFromAttachmentInteractor( + () => DownloadAndGetHtmlContentFromAttachmentInteractor( Get.find(), ), ); @@ -116,6 +135,11 @@ class DownloadInteractorBindings extends InteractorsBindings { Get.find(), ), ); + Get.lazyPut( + () => GetHtmlContentFromUploadFileInteractor( + Get.find(), + ), + ); } @override diff --git a/lib/features/download/presentation/controllers/download_controller.dart b/lib/features/download/presentation/controllers/download_controller.dart new file mode 100644 index 000000000..f8bd7fbf4 --- /dev/null +++ b/lib/features/download/presentation/controllers/download_controller.dart @@ -0,0 +1,294 @@ + +import 'dart:async'; + +import 'package:core/data/network/download/download_manager.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/print_utils.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:model/download/download_task_id.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_all_attachments_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/export_all_attachments_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/export_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_html_content_from_upload_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_all_attachments_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_and_get_html_content_from_attachment_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/export_all_attachments_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/export_attachment_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_html_content_from_upload_file_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/download_attachment_download_controller_extension.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/preview_attachment_download_controller_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +typedef UpdateDownloadTaskStateCallback = DownloadTaskState Function(DownloadTaskState currentState); + +class DownloadController extends BaseController { + final DownloadManager downloadManager; + final PrintUtils printUtils; + final DownloadAttachmentForWebInteractor downloadAttachmentForWebInteractor; + final DownloadAllAttachmentsForWebInteractor + downloadAllAttachmentsForWebInteractor; + final ParseEmailByBlobIdInteractor parseEmailByBlobIdInteractor; + final PreviewEmailFromEmlFileInteractor previewEmailFromEmlFileInteractor; + final DownloadAndGetHtmlContentFromAttachmentInteractor + downloadAndGetHtmlContentFromAttachmentInteractor; + final GetHtmlContentFromUploadFileInteractor + getHtmlContentFromUploadFileInteractor; + final ExportAttachmentInteractor exportAttachmentInteractor; + final ExportAllAttachmentsInteractor exportAllAttachmentsInteractor; + + DownloadController( + this.downloadManager, + this.printUtils, + this.downloadAttachmentForWebInteractor, + this.downloadAllAttachmentsForWebInteractor, + this.parseEmailByBlobIdInteractor, + this.previewEmailFromEmlFileInteractor, + this.downloadAndGetHtmlContentFromAttachmentInteractor, + this.getHtmlContentFromUploadFileInteractor, + this.exportAttachmentInteractor, + this.exportAllAttachmentsInteractor, + ); + + final listDownloadTaskState = RxList(); + final hideDownloadTaskbar = RxBool(false); + final downloadUIAction = Rxn(); + + final downloadProgressStateController = + StreamController>.broadcast(); + StreamSubscription>? + _downloadProgressStateSubscription; + + @override + void onInit() { + super.onInit(); + _registerDownloadProgressState(); + } + + void _registerDownloadProgressState() { + _downloadProgressStateSubscription = downloadProgressStateController.stream + .listen(_onDownloadProgressStateChanged); + } + + void _onDownloadProgressStateChanged(Either state) { + state.fold((_) => null, (success) { + if (success is StartDownloadAttachmentForWeb) { + _handleStartSingleDownload(success); + } else if (success is DownloadingAttachmentForWeb) { + _updateDownloadProgress( + success.taskId, + success.progress, + success.downloaded, + success.total, + ); + } else if (success is StartDownloadAllAttachmentsForWeb) { + _handleStartAllDownload(success); + } else if (success is DownloadingAllAttachmentsForWeb) { + _updateDownloadProgress( + success.taskId, + success.progress, + success.downloaded, + success.total, + ); + } + }); + } + + void _handleStartSingleDownload(StartDownloadAttachmentForWeb success) { + if (success.previewerSupported) return; + + addDownloadTask( + DownloadTaskState( + taskId: success.taskId, + attachment: success.attachment, + onCancel: success.cancelToken?.cancel, + ), + ); + + if (_hasValidContext) { + appToast.showToastMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).your_download_has_started, + leadingSVGIconColor: AppColor.primaryColor, + leadingSVGIcon: imagePaths.icDownload, + ); + } + } + + void _handleStartAllDownload(StartDownloadAllAttachmentsForWeb success) { + addDownloadTask( + DownloadTaskState( + taskId: success.taskId, + attachment: success.attachment, + onCancel: () => success.cancelToken?.cancel(), + ), + ); + + if (_hasValidContext) { + appToast.showToastSuccessMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).creatingAnArchiveForDownloading, + leadingSVGIconColor: Colors.white, + leadingSVGIcon: imagePaths.icDownloadAll, + ); + } + } + + void _updateDownloadProgress( + DownloadTaskId taskId, + double progress, + int downloaded, + int total, + ) { + final percent = progress.round(); + log('$runtimeType::_updateDownloadProgress(): $percent%'); + + updateDownloadTaskByTaskId(taskId, (currentTask) { + return currentTask.copyWith( + progress: progress, + downloaded: downloaded, + total: total, + ); + }); + } + + bool get _hasValidContext => + currentOverlayContext != null && currentContext != null; + + bool get notEmptyListDownloadTask => listDownloadTaskState.isNotEmpty; + + void addDownloadTask(DownloadTaskState task) { + log('DownloadController::addDownloadTask(): ${task.taskId}'); + listDownloadTaskState.add(task); + hideDownloadTaskbar.value = false; + } + + void updateDownloadTaskByTaskId( + DownloadTaskId downloadTaskId, + UpdateDownloadTaskStateCallback updateDownloadTaskCallback, + ) { + final matchIndex = listDownloadTaskState + .indexWhere((task) => task.taskId == downloadTaskId); + if (matchIndex >= 0) { + listDownloadTaskState[matchIndex] = updateDownloadTaskCallback(listDownloadTaskState[matchIndex]); + listDownloadTaskState.refresh(); + } + } + + void deleteDownloadTask(DownloadTaskId taskId) { + log('DownloadController::deleteDownloadTask(): $taskId'); + final matchIndex = listDownloadTaskState + .indexWhere((task) => task.taskId == taskId); + if (matchIndex >= 0) { + listDownloadTaskState.removeAt(matchIndex); + listDownloadTaskState.refresh(); + } + if (listDownloadTaskState.isEmpty) { + hideDownloadTaskbar.value = true; + } + } + + void pushDownloadUIAction(DownloadUIAction action) { + downloadUIAction.value = action; + } + + void clearDownloadUIAction() { + downloadUIAction.value = null; + } + + @override + void handleSuccessViewState(Success success) { + if (success is DownloadAttachmentForWebSuccess) { + handleDownloadAttachmentForWebSuccess(success); + } else if (success is StartDownloadAttachmentForWeb && + success.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction(UpdateAttachmentsViewStateAction( + success.attachment.blobId, + Right(success), + )); + } else if (success is DownloadingAttachmentForWeb && + success.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction(UpdateAttachmentsViewStateAction( + success.attachment.blobId, + Right(success), + )); + } else if (success is DownloadAllAttachmentsForWebSuccess) { + deleteDownloadTask(success.taskId); + } else if (success is ParseEmailByBlobIdSuccess) { + handleParseEmailByBlobIdSuccess( + context: currentContext, + accountId: success.accountId, + session: success.session, + ownEmailAddress: success.ownEmailAddress, + blobId: success.blobId, + email: success.email, + ); + } else if (success is PreviewEmailFromEmlFileSuccess) { + handlePreviewEmailFromEmlFileSuccess(success); + } else if (success is DownloadAndGetHtmlContentFromAttachmentSuccess) { + handleDownloadAndGetHtmlContentFromAttachmentSuccess(success); + } else if (success is DownloadAndGettingHtmlContentFromAttachment && + success.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction( + UpdateAttachmentsViewStateAction( + success.blobId, + Right(success), + ), + ); + } else if (success is ExportAttachmentSuccess) { + exportAttachmentSuccessAction(success.downloadedResponse); + } else if (success is ExportAllAttachmentsSuccess) { + exportAllAttachmentsSuccessAction(success.downloadedResponse.filePath); + } else if (success is GetHtmlContentFromUploadFileSuccess) { + handleGetHtmlContentFromUploadFileSuccess(success); + } else { + super.handleSuccessViewState(success); + } + } + + @override + void handleFailureViewState(Failure failure) { + if (failure is DownloadAllAttachmentsForWebFailure) { + downloadAllAttachmentsForWebFailure(failureState: failure); + } else if (failure is DownloadAttachmentForWebFailure) { + downloadAttachmentForWebFailureAction(failureState: failure); + } else if (failure is ParseEmailByBlobIdFailure) { + handleParseEmailByBlobIdFailure(failure); + } else if (failure is PreviewEmailFromEmlFileFailure) { + handlePreviewEmailFromEMLFileFailure(failure); + } else if (failure is GetHtmlContentFromUploadFileFailure || + failure is DownloadAndGetHtmlContentFromAttachmentFailure) { + handlePreviewHtmlFileFailure(failureState: failure); + } else if (failure is ExportAttachmentFailure) { + exportAttachmentFailureAction(failure); + } else if (failure is ExportAllAttachmentsFailure) { + exportAllAttachmentsFailureAction(failure); + } else { + super.handleFailureViewState(failure); + } + } + + @override + void onClose() { + _downloadProgressStateSubscription?.cancel(); + _downloadProgressStateSubscription = null; + downloadProgressStateController.close(); + super.onClose(); + } +} \ No newline at end of file diff --git a/lib/features/download/presentation/extensions/download_attachment_download_controller_extension.dart b/lib/features/download/presentation/extensions/download_attachment_download_controller_extension.dart new file mode 100644 index 000000000..b348893f2 --- /dev/null +++ b/lib/features/download/presentation/extensions/download_attachment_download_controller_extension.dart @@ -0,0 +1,631 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:core/data/network/download/downloaded_response.dart'; +import 'package:core/domain/exceptions/download_file_exception.dart'; +import 'package:core/presentation/extensions/media_type_extension.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/views/dialog/downloading_file_dialog_builder.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_file_dialog/flutter_file_dialog.dart'; +import 'package:get/get.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:model/download/download_task_id.dart'; +import 'package:model/email/attachment.dart'; +import 'package:model/email/eml_attachment.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/presentation_email_extension.dart'; +import 'package:model/extensions/session_extension.dart'; +import 'package:open_file/open_file.dart' as open_file; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/download/domain/exceptions/download_attachment_exceptions.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_all_attachments_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/export_all_attachments_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/export_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/preview_attachment_download_controller_extension.dart'; +import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_item_widget.dart'; +import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; +import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +typedef OnDownloadWebFileAction = void Function(String name, Uint8List bytes); + +extension DownloadAttachmentDownloadControllerExtension on DownloadController { + void downloadAttachment({ + required Attachment attachment, + required AccountId? accountId, + required Session? session, + bool previewerSupported = false, + bool showBottomDownloadProgressBar = false, + DownloadSourceView? sourceView, + }) { + if (PlatformInfo.isWeb) { + downloadAttachmentForWeb( + attachment: attachment, + accountId: accountId, + session: session, + previewerSupported: previewerSupported, + onReceiveController: showBottomDownloadProgressBar + ? downloadProgressStateController + : null, + sourceView: sourceView, + ); + } else if (PlatformInfo.isMobile) { + exportAttachment( + attachment: attachment, + accountId: accountId, + session: session, + ); + } else { + log('$runtimeType::downloadAttachment: THE PLATFORM IS SUPPORTED'); + } + } + + Future downloadAllAttachments({ + required String outputFileName, + required EmailId? emailId, + required AccountId? accountId, + required Session? session, + bool showBottomDownloadProgressBar = false, + }) async { + if (PlatformInfo.isWeb) { + downloadAllAttachmentsForWeb( + outputFileName: outputFileName, + emailId: emailId, + session: session, + accountId: accountId, + onReceiveController: showBottomDownloadProgressBar + ? downloadProgressStateController + : null, + ); + } else if (PlatformInfo.isMobile) { + exportAllAttachments( + outputFileName: outputFileName, + emailId: emailId, + session: session, + accountId: accountId, + ); + } else { + log('$runtimeType::downloadAllAttachments: THE PLATFORM IS SUPPORTED'); + } + } + + void exportAttachment({ + required Attachment attachment, + required AccountId? accountId, + required Session? session, + }) { + final cancelToken = CancelToken(); + + showDownloadingFileDialog( + attachmentName: attachment.name ?? '', + cancelToken: cancelToken, + ); + + if (session == null) { + emitFailure( + controller: this, + failure: ExportAttachmentFailure(NotFoundSessionException()), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: ExportAttachmentFailure(NotFoundAccountIdException()), + ); + return; + } + + final baseDownloadUrl = session.getSafetyDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + + consumeState( + exportAttachmentInteractor.execute( + attachment, + accountId, + baseDownloadUrl, + cancelToken, + ), + ); + } + + void showDownloadingFileDialog({ + required String attachmentName, + required CancelToken cancelToken, + }) { + Get.dialog( + PointerInterceptor( + child: Builder( + builder: (context) { + final appLocalizations = AppLocalizations.of(context); + return (DownloadingFileDialogBuilder() + ..key(const Key('downloading_file_dialog')) + ..title(appLocalizations.preparing_to_export) + ..content(appLocalizations.downloading_file(attachmentName)) + ..actionText(appLocalizations.cancel) + ..addCancelDownloadActionClick(() { + cancelToken.cancel([ + appLocalizations.user_cancel_download_file, + ]); + popBack(); + })) + .build(); + }, + ), + ), + barrierDismissible: false, + ); + } + + void downloadAttachmentForWeb({ + required Attachment attachment, + required AccountId? accountId, + required Session? session, + bool previewerSupported = false, + StreamController>? onReceiveController, + DownloadSourceView? sourceView, + }) { + if (session == null) { + emitFailure( + controller: this, + failure: DownloadAttachmentForWebFailure( + attachment: attachment, + exception: NotFoundSessionException(), + sourceView: sourceView, + ), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: DownloadAttachmentForWebFailure( + attachment: attachment, + exception: NotFoundAccountIdException(), + sourceView: sourceView, + ), + ); + return; + } + + final generateTaskId = DownloadTaskId(uuid.v4()); + final baseDownloadUrl = session.getSafetyDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + final cancelToken = CancelToken(); + + consumeState(downloadAttachmentForWebInteractor.execute( + generateTaskId, + attachment, + accountId, + baseDownloadUrl, + onReceiveController: onReceiveController, + cancelToken: cancelToken, + previewerSupported: previewerSupported, + sourceView: sourceView, + )); + } + + void exportAttachmentSuccessAction(DownloadedResponse downloadedResponse) { + popBack(); + _openDownloadedPreviewWorkGroupDocument( + filePath: downloadedResponse.filePath, + mediaType: downloadedResponse.mediaType, + ); + } + + Future _openDownloadedPreviewWorkGroupDocument({ + required String filePath, + MediaType? mediaType, + }) async { + if (mediaType == null) { + await _saveFileToStorage(filePath); + return; + } + + final openResult = await open_file.OpenFile.open( + filePath, + type: Platform.isAndroid ? mediaType.mimeType : null, + // "xdg" is default value + linuxDesktopName: + Platform.isIOS ? mediaType.getDocumentUti().value ?? 'xdg' : 'xdg', + ); + + if (openResult.type != open_file.ResultType.done) { + await _saveFileToStorage(filePath); + } + } + + Future _saveFileToStorage(String filePath) async { + final params = SaveFileDialogParams(sourceFilePath: filePath); + await FlutterFileDialog.saveFile(params: params); + } + + void exportAllAttachmentsSuccessAction(String filePath) { + popBack(); + _saveFileToStorage(filePath); + } + + void exportAllAttachmentsFailureAction(dynamic exception) { + if (exception is CancelDownloadFileException) { + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastWarningMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).user_cancel_download_file, + ); + } + return; + } + + popBack(); + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastErrorMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).attachment_download_failed, + ); + } + } + + void exportAttachmentFailureAction(dynamic exception) { + if (exception is CancelDownloadFileException) { + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastWarningMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).user_cancel_download_file, + ); + } + return; + } + + if (Get.isDialogOpen == true) { + popBack(); + } + + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastErrorMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!).attachment_download_failed, + ); + } + } + + void downloadAttachmentForWebFailureAction({ + required DownloadAttachmentForWebFailure failureState, + }) { + closeDialogLoading(); + + if (failureState.taskId != null) { + deleteDownloadTask(failureState.taskId!); + } + + if (failureState.attachment != null && + failureState.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction( + UpdateAttachmentsViewStateAction( + failureState.attachment?.blobId, + Left(failureState), + ), + ); + } + + if (currentOverlayContext == null || currentContext == null) return; + + final appLocalizations = AppLocalizations.of(currentContext!); + String message = appLocalizations.attachment_download_failed; + if (failureState.attachment is EMLAttachment) { + message = appLocalizations.downloadMessageAsEMLFailed; + } else if (failureState.cancelToken?.isCancelled == true) { + message = appLocalizations.downloadAttachmentHasBeenCancelled; + } + + appToast.showToastErrorMessage(currentOverlayContext!, message); + } + + void downloadAllAttachmentsForWebFailure({ + required DownloadAllAttachmentsForWebFailure failureState, + }) { + deleteDownloadTask(failureState.taskId); + + if (currentOverlayContext == null || currentContext == null) return; + + final appLocalizations = AppLocalizations.of(currentContext!); + final message = failureState.cancelToken?.isCancelled == true + ? appLocalizations.downloadAttachmentHasBeenCancelled + : appLocalizations.attachment_download_failed; + + appToast.showToastErrorMessage(currentOverlayContext!, message); + } + + void downloadFileWeb({ + required String fileName, + required Uint8List fileBytes, + }) { + downloadManager.createAnchorElementDownloadFileWeb( + fileBytes, + fileName, + ); + } + + void downloadAttachmentInEMLPreview({ + required OnDownloadAttachmentFileAction onDownloadAction, + required Uri? uri, + }) { + if (uri == null) return; + + final attachment = EmailUtils.parsingAttachmentByUri(uri); + if (attachment == null) return; + + onDownloadAction(attachment); + } + + void exportAllAttachments({ + required String outputFileName, + required EmailId? emailId, + required AccountId? accountId, + required Session? session, + }) { + final cancelToken = CancelToken(); + + showDownloadingFileDialog( + attachmentName: outputFileName, + cancelToken: cancelToken, + ); + + if (session == null) { + emitFailure( + controller: this, + failure: ExportAllAttachmentsFailure( + exception: NotFoundSessionException(), + ), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: ExportAllAttachmentsFailure( + exception: NotFoundAccountIdException(), + ), + ); + return; + } + + if (emailId == null) { + emitFailure( + controller: this, + failure: ExportAllAttachmentsFailure( + exception: NotFoundEmailException(), + ), + ); + return; + } + + final downloadAllSupported = session.isDownloadAllSupported(accountId); + if (!downloadAllSupported) { + emitFailure( + controller: this, + failure: ExportAllAttachmentsFailure( + exception: CapabilityDownloadAllNotSupportedException(), + ), + ); + return; + } + + final baseDownloadAllUrl = + session.getDownloadAllCapability(accountId)!.endpoint!; + + consumeState( + exportAllAttachmentsInteractor.execute( + accountId, + emailId, + baseDownloadAllUrl, + outputFileName, + cancelToken, + ), + ); + } + + void downloadAllAttachmentsForWeb({ + required String outputFileName, + required EmailId? emailId, + required AccountId? accountId, + required Session? session, + bool previewerSupported = false, + StreamController>? onReceiveController, + }) { + final taskId = DownloadTaskId(uuid.v4()); + + if (session == null) { + emitFailure( + controller: this, + failure: DownloadAllAttachmentsForWebFailure( + exception: NotFoundSessionException(), + taskId: taskId, + ), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: DownloadAllAttachmentsForWebFailure( + exception: NotFoundAccountIdException(), + taskId: taskId, + ), + ); + return; + } + + if (emailId == null) { + emitFailure( + controller: this, + failure: DownloadAllAttachmentsForWebFailure( + exception: NotFoundEmailException(), + taskId: taskId, + ), + ); + return; + } + + final downloadAllSupported = session.isDownloadAllSupported(accountId); + + if (!downloadAllSupported) { + emitFailure( + controller: this, + failure: DownloadAllAttachmentsForWebFailure( + exception: CapabilityDownloadAllNotSupportedException(), + taskId: taskId, + ), + ); + return; + } + + final baseDownloadAllUrl = + session.getDownloadAllCapability(accountId)!.endpoint!; + + final downloadAttachment = Attachment( + name: outputFileName, + type: MediaType('application', 'zip'), + ); + + final cancelToken = CancelToken(); + + consumeState( + downloadAllAttachmentsForWebInteractor.execute( + accountId, + emailId, + baseDownloadAllUrl, + downloadAttachment, + taskId, + onReceiveController: onReceiveController, + cancelToken: cancelToken, + ), + ); + } + + void downloadMessageAsEML({ + required PresentationEmail presentationEmail, + required AccountId? accountId, + required Session? session, + bool showBottomDownloadProgressBar = false, + }) { + if (session == null) { + emitFailure( + controller: this, + failure: DownloadAttachmentForWebFailure( + exception: NotFoundSessionException(), + ), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: DownloadAttachmentForWebFailure( + exception: NotFoundAccountIdException(), + ), + ); + return; + } + + final emlAttachment = presentationEmail.createEMLAttachment(); + if (emlAttachment.blobId == null) { + emitFailure( + controller: this, + failure: DownloadAttachmentForWebFailure( + exception: NotFoundEmailBlobIdException(), + ), + ); + return; + } + + final generateTaskId = DownloadTaskId(uuid.v4()); + final baseDownloadUrl = session.getSafetyDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + final cancelToken = CancelToken(); + + consumeState( + downloadAttachmentForWebInteractor.execute( + generateTaskId, + emlAttachment, + accountId, + baseDownloadUrl, + onReceiveController: showBottomDownloadProgressBar + ? downloadProgressStateController + : null, + cancelToken: cancelToken, + previewerSupported: false, + ), + ); + } + + void handleDownloadAttachmentForWebSuccess( + DownloadAttachmentForWebSuccess success, + ) { + closeDialogLoading(); + + if (success.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction(UpdateAttachmentsViewStateAction( + success.attachment.blobId, + Right(success), + )); + } + + deleteDownloadTask(success.taskId); + + if (!success.previewerSupported) { + downloadFileWeb( + fileBytes: success.bytes, + fileName: success.attachment.generateFileName(), + ); + return; + } + + if (success.attachment.isImage) { + previewImageFile( + fileName: success.attachment.generateFileName(), + imageBytes: success.bytes, + context: currentContext, + onDownloadWebFileAction: (name, bytes) => downloadFileWeb( + fileName: name, + fileBytes: bytes, + ), + ); + } else if (success.attachment.isText || success.attachment.isJson) { + previewPlainTextFile( + fileName: success.attachment.generateFileName(), + fileBytes: success.bytes, + context: currentContext, + onDownloadWebFileAction: (name, bytes) => downloadFileWeb( + fileName: name, + fileBytes: bytes, + ), + ); + } + } +} diff --git a/lib/features/download/presentation/extensions/preview_attachment_download_controller_extension.dart b/lib/features/download/presentation/extensions/preview_attachment_download_controller_extension.dart new file mode 100644 index 000000000..ec97759d1 --- /dev/null +++ b/lib/features/download/presentation/extensions/preview_attachment_download_controller_extension.dart @@ -0,0 +1,763 @@ +import 'package:core/domain/exceptions/web_session_exception.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; +import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/html/html_utils.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:get/get_navigation/src/dialog/dialog_route.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:model/download/download_task_id.dart'; +import 'package:model/email/attachment.dart'; +import 'package:model/extensions/session_extension.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/download/domain/exceptions/download_attachment_exceptions.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_html_content_from_upload_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/download_attachment_download_controller_extension.dart'; +import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; +import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_item_widget.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/html_attachment_previewer.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart'; +import 'package:tmail_ui_user/features/email_previewer/email_previewer_dialog_view.dart'; +import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; +import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/app_routes.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; +import 'package:tmail_ui_user/main/routes/route_utils.dart'; +import 'package:twake_previewer_flutter/core/constants/supported_charset.dart'; +import 'package:twake_previewer_flutter/core/previewer_options/options/previewer_state.dart'; +import 'package:twake_previewer_flutter/core/previewer_options/options/top_bar_options.dart'; +import 'package:twake_previewer_flutter/core/previewer_options/previewer_options.dart'; +import 'package:twake_previewer_flutter/twake_image_previewer/twake_image_previewer.dart'; +import 'package:twake_previewer_flutter/twake_plain_text_previewer/twake_plain_text_previewer.dart'; + +typedef OnPreviewOrDownloadAttachmentAction = void Function( + Attachment attachment, + bool isPreviewSupported, +); + +extension PreviewAttachmentDownloadControllerExtension on DownloadController { + void previewAttachment({ + required BuildContext context, + required Attachment attachment, + required AccountId? accountId, + required Session? session, + required String ownEmailAddress, + required OnPreviewOrDownloadAttachmentAction onPreviewOrDownloadAction, + bool isDialogLoadingVisible = false, + DownloadSourceView? sourceView, + }) { + final appLocalizations = AppLocalizations.of(context); + + if (PlatformInfo.isWeb && attachment.isPDFFile) { + _previewPDFFile( + context: context, + attachment: attachment, + accountId: accountId, + session: session, + ); + } else if (attachment.isEMLFile) { + _preparePreviewEMLFile( + appLocalizations: appLocalizations, + accountId: accountId, + session: session, + ownEmailAddress: ownEmailAddress, + blobId: attachment.blobId, + ); + } else if (attachment.isHTMLFile) { + _preparePreviewHtmlFile( + appLocalizations: appLocalizations, + session: session, + accountId: accountId, + attachment: attachment, + isDialogLoadingVisible: isDialogLoadingVisible, + sourceView: sourceView, + ); + } else { + if (isDialogLoadingVisible) { + showDialogLoading(appLocalizations); + } + onPreviewOrDownloadAction( + attachment, + attachment.isPreviewSupported, + ); + } + } + + void previewUploadFile({ + required BuildContext context, + required UploadFileState uploadFile, + required AccountId? accountId, + required Session? session, + required String ownEmailAddress, + required OnPreviewOrDownloadAttachmentAction onPreviewOrDownloadAction, + bool isDialogLoadingVisible = false, + }) { + final attachment = uploadFile.attachment; + final appLocalizations = AppLocalizations.of(context); + + if (attachment == null) { + showPreviewNotAvailableToastMessage(context); + return; + } + + if (PlatformInfo.isWeb && attachment.isPDFFile == true) { + _previewPDFFile( + context: context, + attachment: attachment, + accountId: accountId, + session: session, + ); + return; + } + + if (attachment.isEMLFile == true) { + _preparePreviewEMLFile( + appLocalizations: appLocalizations, + accountId: accountId, + session: session, + ownEmailAddress: ownEmailAddress, + blobId: attachment.blobId, + ); + return; + } + + if (attachment.isHTMLFile == true) { + if (uploadFile.file?.bytes != null) { + _preparePreviewHtmlFileByUploadFile( + appLocalizations: appLocalizations, + uploadFile: uploadFile, + accountId: accountId, + session: session, + isDialogLoadingVisible: isDialogLoadingVisible, + ); + } else { + _preparePreviewHtmlFile( + appLocalizations: appLocalizations, + attachment: attachment, + accountId: accountId, + session: session, + isDialogLoadingVisible: isDialogLoadingVisible, + ); + } + return; + } + + if (attachment.isImage == true && uploadFile.file?.bytes != null) { + previewImageFile( + fileName: attachment.generateFileName(), + imageBytes: uploadFile.file!.bytes!, + context: context, + onDownloadWebFileAction: (fileName, fileBytes) => downloadFileWeb( + fileName: fileName, + fileBytes: fileBytes, + ), + ); + return; + } + + if ((attachment.isText == true || attachment.isJson == true) && + uploadFile.file?.bytes != null) { + previewPlainTextFile( + fileName: attachment.generateFileName(), + fileBytes: uploadFile.file!.bytes!, + context: context, + onDownloadWebFileAction: (fileName, fileBytes) => downloadFileWeb( + fileName: fileName, + fileBytes: fileBytes, + ), + ); + return; + } + + if (uploadFile.file?.bytes != null) { + downloadFileWeb( + fileName: attachment.generateFileName(), + fileBytes: uploadFile.file!.bytes!, + ); + return; + } + + if (isDialogLoadingVisible) { + showDialogLoading(appLocalizations); + } + onPreviewOrDownloadAction(attachment, attachment.isPreviewSupported); + } + + Future _previewPDFFile({ + required BuildContext context, + required Attachment attachment, + required AccountId? accountId, + required Session? session, + }) async { + final downloadUrl = session?.getSafetyDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + + if (accountId == null || session == null || downloadUrl == null) { + showPreviewNotAvailableToastMessage(context); + return; + } + + await Get.generalDialog( + barrierColor: Colors.black.withValues(alpha: 0.8), + pageBuilder: (_, __, ___) { + return PointerInterceptor( + child: PDFViewer( + attachment: attachment, + accountId: accountId, + downloadUrl: downloadUrl, + downloadAction: (bytes, name) => + downloadFileWeb(fileName: name, fileBytes: bytes), + printAction: printUtils.printPDFFile, + ), + ); + }, + ); + } + + void _preparePreviewEMLFile({ + required AppLocalizations appLocalizations, + required AccountId? accountId, + required Session? session, + required String ownEmailAddress, + required Id? blobId, + }) { + showDialogLoading(appLocalizations); + + if (session == null) { + emitFailure( + controller: this, + failure: ParseEmailByBlobIdFailure(NotFoundSessionException()), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: ParseEmailByBlobIdFailure(NotFoundAccountIdException()), + ); + return; + } + + if (blobId == null) { + emitFailure( + controller: this, + failure: ParseEmailByBlobIdFailure(NotFoundBlobIdException([])), + ); + return; + } + + consumeState( + parseEmailByBlobIdInteractor.execute( + accountId, + session, + ownEmailAddress, + blobId, + ), + ); + } + + void _preparePreviewHtmlFile({ + required AppLocalizations appLocalizations, + required Session? session, + required AccountId? accountId, + required Attachment attachment, + bool isDialogLoadingVisible = false, + DownloadSourceView? sourceView, + }) { + if (isDialogLoadingVisible) { + showDialogLoading(appLocalizations); + } + + final downloadUrl = session?.getSafetyDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + final blobId = attachment.blobId; + + if (session == null) { + emitFailure( + controller: this, + failure: DownloadAndGetHtmlContentFromAttachmentFailure( + exception: NotFoundSessionException(), + blobId: blobId, + sourceView: sourceView, + ), + ); + return; + } + + if (accountId == null) { + emitFailure( + controller: this, + failure: DownloadAndGetHtmlContentFromAttachmentFailure( + exception: NotFoundAccountIdException(), + blobId: blobId, + sourceView: sourceView, + ), + ); + return; + } + + if (blobId == null) { + emitFailure( + controller: this, + failure: DownloadAndGetHtmlContentFromAttachmentFailure( + exception: NotFoundBlobIdException([]), + blobId: blobId, + sourceView: sourceView, + ), + ); + return; + } + + if (downloadUrl == null) { + emitFailure( + controller: this, + failure: DownloadAndGetHtmlContentFromAttachmentFailure( + exception: DownloadUrlIsNullException(), + blobId: blobId, + sourceView: sourceView, + ), + ); + return; + } + + consumeState( + downloadAndGetHtmlContentFromAttachmentInteractor.execute( + accountId, + session, + attachment, + DownloadTaskId(blobId.value), + downloadUrl, + TransformConfiguration.create( + customDomTransformers: [SanitizeHyperLinkTagInHtmlTransformer()], + customTextTransformers: [ + const StandardizeHtmlSanitizingTransformers() + ], + ), + sourceView: sourceView, + ), + ); + } + + void _preparePreviewHtmlFileByUploadFile({ + required AppLocalizations appLocalizations, + required UploadFileState uploadFile, + required AccountId? accountId, + required Session? session, + bool isDialogLoadingVisible = false, + }) { + if (isDialogLoadingVisible) { + showDialogLoading(appLocalizations); + } + consumeState( + getHtmlContentFromUploadFileInteractor.execute( + uploadFile: uploadFile, + accountId: accountId, + session: session, + ), + ); + } + + void handleParseEmailByBlobIdSuccess({ + required BuildContext? context, + required AccountId accountId, + required Session session, + required String ownEmailAddress, + required Id blobId, + required Email email, + }) { + if (context == null) { + emitFailure( + controller: this, + failure: PreviewEmailFromEmlFileFailure(NotFoundContextException()), + ); + return; + } + + final downloadUrl = session.getDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + + if (downloadUrl.isEmpty) { + emitFailure( + controller: this, + failure: PreviewEmailFromEmlFileFailure(DownloadUrlIsNullException()), + ); + return; + } + + consumeState( + previewEmailFromEmlFileInteractor.execute( + PreviewEmailEMLRequest( + accountId: accountId, + session: session, + ownEmailAddress: ownEmailAddress, + blobId: blobId, + email: email, + locale: Localizations.localeOf(context), + appLocalizations: AppLocalizations.of(context), + baseDownloadUrl: downloadUrl, + ), + ), + ); + } + + void handleParseEmailByBlobIdFailure(ParseEmailByBlobIdFailure failure) { + closeDialogLoading(); + toastManager.showMessageFailure(failure); + } + + void handlePreviewEmailFromEMLFileFailure( + PreviewEmailFromEmlFileFailure failure, + ) { + closeDialogLoading(); + toastManager.showMessageFailure(failure); + } + + void handleGetHtmlContentFromUploadFileSuccess( + GetHtmlContentFromUploadFileSuccess success, + ) { + closeDialogLoading(); + + previewHtmlFile( + attachment: success.attachment, + title: success.htmlAttachmentTitle, + content: success.sanitizedHtmlContent, + openMailToLink: (uri) async { + if (uri == null) return; + pushDownloadUIAction(OpenComposerFromMailtoLinkAction(uri)); + }, + onDownloadAction: (attachment) => downloadAttachment( + attachment: attachment, + accountId: success.accountId, + session: success.session, + ), + ); + } + + void handleDownloadAndGetHtmlContentFromAttachmentSuccess( + DownloadAndGetHtmlContentFromAttachmentSuccess success, + ) { + if (success.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction( + UpdateAttachmentsViewStateAction( + success.attachment.blobId, + Right(success), + ), + ); + } + + closeDialogLoading(); + + previewHtmlFile( + attachment: success.attachment, + title: success.htmlAttachmentTitle, + content: success.sanitizedHtmlContent, + openMailToLink: (uri) async { + if (uri == null) return; + pushDownloadUIAction(OpenComposerFromMailtoLinkAction(uri)); + }, + onDownloadAction: (attachment) => downloadAttachment( + attachment: attachment, + accountId: success.accountId, + session: success.session, + ), + ); + } + + void handlePreviewEmailFromEmlFileSuccess( + PreviewEmailFromEmlFileSuccess success, + ) { + previewEMLFile( + emlPreviewer: success.emlPreviewer, + context: currentContext, + onMailtoAction: (uri) async { + if (uri == null) return; + pushDownloadUIAction(OpenComposerFromMailtoLinkAction(uri)); + }, + onDownloadAction: (uri) async => downloadAttachmentInEMLPreview( + uri: uri, + onDownloadAction: (attachment) => downloadAttachment( + attachment: attachment, + accountId: success.accountId, + session: success.session, + )), + onPreviewAction: (uri) async => openEMLPreviewer( + accountId: success.accountId, + session: success.session, + ownEmailAddress: success.ownEmailAddress, + uri: uri, + appLocalizations: success.appLocalizations, + ), + ); + } + + void previewEMLFile({ + required EMLPreviewer emlPreviewer, + required BuildContext? context, + required OnMailtoDelegateAction onMailtoAction, + required OnDownloadAttachmentDelegateAction onDownloadAction, + required OnPreviewEMLDelegateAction onPreviewAction, + }) { + closeDialogLoading(); + + if (PlatformInfo.isWeb) { + bool isOpen = HtmlUtils.openNewWindowByUrl( + RouteUtils.createUrlWebLocationBar( + AppRoutes.emailEMLPreviewer, + previewId: emlPreviewer.id, + ).toString(), + ); + + if (!isOpen) { + toastManager.showMessageFailure( + PreviewEmailFromEmlFileFailure(CannotOpenNewWindowException()), + ); + } + } else if (PlatformInfo.isMobile) { + if (context == null) { + toastManager.showMessageFailure( + PreviewEmailFromEmlFileFailure(NotFoundContextException()), + ); + return; + } + + if (PlatformInfo.isAndroid) { + showModalSheetToPreviewEMLAttachment( + context: context, + emlPreviewer: emlPreviewer, + onMailtoAction: onMailtoAction, + onDownloadAction: onDownloadAction, + onPreviewAction: onPreviewAction, + ); + } else if (PlatformInfo.isIOS) { + showDialogToPreviewEMLAttachment( + context: context, + emlPreviewer: emlPreviewer, + onMailtoAction: onMailtoAction, + onDownloadAction: onDownloadAction, + onPreviewAction: onPreviewAction, + ); + } + } + } + + void showModalSheetToPreviewEMLAttachment({ + required BuildContext context, + required EMLPreviewer emlPreviewer, + required OnMailtoDelegateAction onMailtoAction, + required OnDownloadAttachmentDelegateAction onDownloadAction, + required OnPreviewEMLDelegateAction onPreviewAction, + }) { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.0), + topRight: Radius.circular(20.0), + ), + ), + builder: (_) { + return DraggableScrollableSheet( + initialChildSize: 1.0, + builder: (context, ___) => EmailPreviewerDialogView( + emlPreviewer: emlPreviewer, + imagePaths: imagePaths, + onMailtoDelegateAction: onMailtoAction, + onPreviewEMLDelegateAction: onPreviewAction, + onDownloadAttachmentDelegateAction: onDownloadAction, + ), + ); + }, + ); + } + + void showDialogToPreviewEMLAttachment({ + required BuildContext context, + required EMLPreviewer emlPreviewer, + required OnMailtoDelegateAction onMailtoAction, + required OnDownloadAttachmentDelegateAction onDownloadAction, + required OnPreviewEMLDelegateAction onPreviewAction, + }) { + Get.dialog( + EmailPreviewerDialogView( + emlPreviewer: emlPreviewer, + imagePaths: imagePaths, + onMailtoDelegateAction: onMailtoAction, + onPreviewEMLDelegateAction: onPreviewAction, + onDownloadAttachmentDelegateAction: onDownloadAction, + ), + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + ); + } + + void handlePreviewHtmlFileFailure({required Failure failureState}) { + if (failureState is DownloadAndGetHtmlContentFromAttachmentFailure && + failureState.sourceView == DownloadSourceView.emailView) { + pushDownloadUIAction( + UpdateAttachmentsViewStateAction( + failureState.blobId, + Left(failureState), + ), + ); + } + + closeDialogLoading(); + + if (currentOverlayContext != null && currentContext != null) { + appToast.showToastErrorMessage( + currentOverlayContext!, + AppLocalizations.of(currentContext!) + .thisHtmlAttachmentCannotBePreviewed, + ); + } + } + + void previewImageFile({ + required String fileName, + required Uint8List imageBytes, + required BuildContext? context, + required OnDownloadWebFileAction onDownloadWebFileAction, + }) { + log('$runtimeType::previewImageFile: Attachment fileName is $fileName'); + if (context == null) return; + + Navigator.of(context).push( + GetDialogRoute( + pageBuilder: (context, _, __) => PointerInterceptor( + child: TwakeImagePreviewer( + bytes: imageBytes, + zoomable: true, + previewerOptions: const PreviewerOptions( + previewerState: PreviewerState.success, + ), + topBarOptions: TopBarOptions( + title: fileName, + onClose: () => Navigator.maybePop(context), + onDownload: () => onDownloadWebFileAction(fileName, imageBytes), + ), + ), + ), + barrierDismissible: false, + ), + ); + } + + void previewPlainTextFile({ + required String fileName, + required Uint8List fileBytes, + required BuildContext? context, + required OnDownloadWebFileAction onDownloadWebFileAction, + }) { + log('$runtimeType::previewPlainTextFile: Attachment fileName is $fileName'); + if (context == null) return; + + Navigator.of(context).push( + GetDialogRoute( + pageBuilder: (context, _, __) => PointerInterceptor( + child: TwakePlainTextPreviewer( + supportedCharset: SupportedCharset.utf8, + bytes: fileBytes, + previewerOptions: PreviewerOptions( + previewerState: PreviewerState.success, + width: context.width * 0.8, + ), + topBarOptions: TopBarOptions( + title: fileName, + onClose: () => Navigator.maybePop(context), + onDownload: () => onDownloadWebFileAction(fileName, fileBytes), + ), + ), + ), + barrierDismissible: false, + ), + ); + } + + void previewHtmlFile({ + required Attachment attachment, + required String title, + required String content, + required OnMailtoDelegateAction openMailToLink, + required OnDownloadAttachmentFileAction onDownloadAction, + }) { + Get.dialog( + HtmlAttachmentPreviewer( + title: title, + htmlContent: content, + mailToClicked: openMailToLink, + downloadAttachmentClicked: () => onDownloadAction(attachment), + responsiveUtils: responsiveUtils, + ), + ); + } + + Future openEMLPreviewer({ + required AppLocalizations appLocalizations, + required Uri? uri, + required AccountId? accountId, + required Session? session, + required String ownEmailAddress, + }) async { + if (uri == null) return; + + final blobId = uri.path; + if (blobId.isEmpty) return; + + _preparePreviewEMLFile( + appLocalizations: appLocalizations, + accountId: accountId, + session: session, + ownEmailAddress: ownEmailAddress, + blobId: Id(blobId), + ); + } + + void showDialogLoading(AppLocalizations appLocalizations) { + SmartDialog.showLoading( + msg: appLocalizations.loadingPleaseWait, + maskColor: Colors.black38, + ); + } + + void closeDialogLoading() { + if (SmartDialog.checkExist()) { + SmartDialog.dismiss(); + } + } + + void showPreviewNotAvailableToastMessage(BuildContext context) { + appToast.showToastErrorMessage( + context, + AppLocalizations.of(context).noPreviewAvailable, + ); + } +} diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 166f576f6..9a016857f 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -57,11 +57,12 @@ import 'package:tmail_ui_user/features/base/mixin/handle_error_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/mail_api_mixin.dart'; import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart'; import 'package:tmail_ui_user/features/email/domain/model/restore_deleted_message_request.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_all_attachments_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_all_attachments_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; @@ -343,12 +344,18 @@ class EmailAPI with HandleSetErrorMixin, MailAPIMixin { progress = (downloaded / total) * 100; } log('EmailAPI::downloadFileForWeb(): progress = ${progress.round()}%'); - onReceiveController?.add(Right(DownloadingAttachmentForWeb( - taskId, - attachment, - progress, - downloaded, - total))); + onReceiveController?.add( + Right( + DownloadingAttachmentForWeb( + taskId, + attachment, + progress, + downloaded, + total, + DownloadSourceView.emailView, + ), + ), + ); } ); diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 1216917b3..ed99bedad 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:core/data/model/source_type/data_source_type.dart'; -import 'package:core/data/network/download/downloaded_response.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dio/dio.dart'; @@ -14,8 +13,6 @@ 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/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:model/account/account_request.dart'; -import 'package:model/email/attachment.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/mark_star_action.dart'; import 'package:model/email/read_actions.dart'; @@ -135,22 +132,6 @@ class EmailRepositoryImpl extends EmailRepository { return result; } - @override - Future exportAttachment( - Attachment attachment, - AccountId accountId, - String baseDownloadUrl, - AccountRequest accountRequest, - CancelToken cancelToken - ) { - return emailDataSource[DataSourceType.network]!.exportAttachment( - attachment, - accountId, - baseDownloadUrl, - accountRequest, - cancelToken); - } - @override Future<({ List emailIdsSuccess, @@ -494,22 +475,6 @@ class EmailRepositoryImpl extends EmailRepository { return _printFileDataSource.printEmail(emailPrint); } - @override - Future exportAllAttachments( - AccountId accountId, - EmailId emailId, - String baseDownloadAllUrl, - String outputFileName, - AccountRequest accountRequest, { - CancelToken? cancelToken, - }) => emailDataSource[DataSourceType.network]!.exportAllAttachments( - accountId, - emailId, - baseDownloadAllUrl, - outputFileName, - accountRequest, - ); - @override Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest) { return emailDataSource[DataSourceType.local]!.generateEntireMessageAsDocument(entireMessageRequest); diff --git a/lib/features/email/domain/exceptions/download_attachment_exceptions.dart b/lib/features/email/domain/exceptions/download_attachment_exceptions.dart deleted file mode 100644 index 390c68f96..000000000 --- a/lib/features/email/domain/exceptions/download_attachment_exceptions.dart +++ /dev/null @@ -1 +0,0 @@ -class DownloadAttachmentInteractorIsNull implements Exception {} \ 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 6c4e3ece4..2407cdb1c 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:core/data/network/download/downloaded_response.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:dio/dio.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart'; @@ -12,8 +11,6 @@ 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/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:model/account/account_request.dart'; -import 'package:model/email/attachment.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/mark_star_action.dart'; import 'package:model/email/read_actions.dart'; @@ -52,14 +49,6 @@ abstract class EmailRepository { ReadActions readActions, ); - Future exportAttachment( - Attachment attachment, - AccountId accountId, - String baseDownloadUrl, - AccountRequest accountRequest, - CancelToken cancelToken - ); - Future<({ List emailIdsSuccess, Map mapErrors, @@ -170,14 +159,5 @@ abstract class EmailRepository { Future printEmail(EmailPrint emailPrint); - Future exportAllAttachments( - AccountId accountId, - EmailId emailId, - String baseDownloadAllUrl, - String outputFileName, - AccountRequest accountRequest, - {CancelToken? cancelToken} - ); - Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest); } \ No newline at end of file diff --git a/lib/features/email/domain/state/get_html_content_from_attachment_state.dart b/lib/features/email/domain/state/get_html_content_from_attachment_state.dart deleted file mode 100644 index 632550ef8..000000000 --- a/lib/features/email/domain/state/get_html_content_from_attachment_state.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:core/presentation/state/failure.dart'; -import 'package:core/presentation/state/success.dart'; -import 'package:model/email/attachment.dart'; - -class GettingHtmlContentFromAttachment extends LoadingState { - GettingHtmlContentFromAttachment({required this.attachment}); - - final Attachment attachment; - - @override - List get props => [attachment]; -} - -class GetHtmlContentFromAttachmentSuccess extends UIState { - GetHtmlContentFromAttachmentSuccess({ - required this.sanitizedHtmlContent, - required this.htmlAttachmentTitle, - required this.attachment, - }); - - final String sanitizedHtmlContent; - final String htmlAttachmentTitle; - final Attachment attachment; - - @override - List get props => [ - sanitizedHtmlContent, - htmlAttachmentTitle, - attachment, - ]; -} - -class GetHtmlContentFromAttachmentFailure extends FeatureFailure { - GetHtmlContentFromAttachmentFailure({super.exception, required this.attachment}); - - final Attachment attachment; - - @override - List get props => [attachment]; -} \ 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 53ceb3495..76d48df76 100644 --- a/lib/features/email/presentation/bindings/email_bindings.dart +++ b/lib/features/email/presentation/bindings/email_bindings.dart @@ -1,7 +1,5 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; @@ -22,12 +20,10 @@ class EmailBindings extends Bindings { Get.put(SingleEmailController( Get.find(), Get.find(), - Get.find(), Get.find(), Get.find(), Get.find(), Get.find(), - Get.find(), currentEmailId: currentEmailId, ), tag: tag); } diff --git a/lib/features/email/presentation/bindings/email_interactor_bindings.dart b/lib/features/email/presentation/bindings/email_interactor_bindings.dart index 03492864b..e689a5e74 100644 --- a/lib/features/email/presentation/bindings/email_interactor_bindings.dart +++ b/lib/features/email/presentation/bindings/email_interactor_bindings.dart @@ -16,8 +16,6 @@ import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_entire_message_as_document_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_stored_email_state_interactor.dart'; @@ -26,9 +24,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_ import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; -import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; -import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; -import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart'; @@ -111,23 +106,12 @@ class EmailInteractorBindings extends InteractorsBindings { void bindingsInteractor() { Get.lazyPut(() => GetEmailContentInteractor(Get.find())); Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find())); - Get.lazyPut(() => ExportAttachmentInteractor( - Get.find(), - Get.find(), - Get.find(), - Get.find(), - )); Get.lazyPut(() => MoveToMailboxInteractor(Get.find())); Get.lazyPut(() => MarkAsStarEmailInteractor(Get.find())); Get.lazyPut(() => GetStoredEmailStateInteractor(Get.find())); Get.lazyPut(() => StoreOpenedEmailInteractor(Get.find())); Get.lazyPut(() => PrintEmailInteractor(Get.find())); IdentityInteractorsBindings().dependencies(); - Get.lazyPut(() => ExportAllAttachmentsInteractor( - Get.find(), - Get.find(), - Get.find(), - Get.find())); if (PlatformInfo.isIOS) { Get.lazyPut(() => GetEntireMessageAsDocumentInteractor( 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 775a4ea38..0dc87d033 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -1,13 +1,10 @@ import 'dart:async'; -import 'dart:io'; import 'package:core/core.dart'; import 'package:core/presentation/utils/html_transformer/text/new_line_transformer.dart'; import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_unescape_html_transformer.dart'; import 'package:dartz/dartz.dart'; -import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -26,12 +23,12 @@ import 'package:jmap_dart_client/jmap/mdn/disposition.dart'; import 'package:jmap_dart_client/jmap/mdn/mdn.dart'; import 'package:model/error_type_handler/unknown_uri_exception.dart'; import 'package:model/model.dart'; -import 'package:open_file/open_file.dart' as open_file; -import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_manager.dart'; import 'package:tmail_ui_user/features/base/state/button_state.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; @@ -43,9 +40,6 @@ import 'package:tmail_ui_user/features/email/domain/state/calendar_event_counter import 'package:tmail_ui_user/features/email/domain/state/calendar_event_maybe_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/calendar_event_reject_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/calendar_event_reply_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/export_all_attachments_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/export_attachment_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_entire_message_as_document_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; @@ -57,8 +51,6 @@ import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_stat import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_accept_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_counter_accept_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_reject_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_entire_message_as_document_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; @@ -71,7 +63,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_ import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/calendar_event_interactor_bindings.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/mdn_interactor_bindings.dart'; -import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_attendee_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_organizer_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/handle_mail_action_by_shortcut_action_extension.dart'; @@ -84,12 +75,11 @@ import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscrib import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; -import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_download_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_download_attachment_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; @@ -121,12 +111,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final GetEmailContentInteractor _getEmailContentInteractor; final MarkAsEmailReadInteractor _markAsEmailReadInteractor; - final ExportAttachmentInteractor _exportAttachmentInteractor; final MarkAsStarEmailInteractor _markAsStarEmailInteractor; final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; final StoreOpenedEmailInteractor _storeOpenedEmailInteractor; final PrintEmailInteractor _printEmailInteractor; - final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor; final EmailId? _currentEmailId; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; @@ -193,12 +181,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { SingleEmailController( this._getEmailContentInteractor, this._markAsEmailReadInteractor, - this._exportAttachmentInteractor, this._markAsStarEmailInteractor, this._getAllIdentitiesInteractor, this._storeOpenedEmailInteractor, - this._printEmailInteractor, - this._exportAllAttachmentsInteractor, { + this._printEmailInteractor, { EmailId? currentEmailId, }) : _currentEmailId = currentEmailId; @@ -236,10 +222,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _getEmailContentOfflineSuccess(success); } else if (success is MarkAsEmailReadSuccess) { _handleMarkAsEmailReadCompleted(success); - } else if (success is ExportAttachmentSuccess) { - _exportAttachmentSuccessAction(success); - } else if (success is ExportAllAttachmentsSuccess) { - _exportAllAttachmentsSuccessAction(success); } else if (success is MarkAsStarEmailSuccess) { _markAsEmailStarSuccess(success); } else if (success is GetAllIdentitiesSuccess) { @@ -267,10 +249,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void handleFailureViewState(Failure failure) { if (failure is MarkAsEmailReadFailure) { _handleMarkAsEmailReadFailure(failure); - } else if (failure is ExportAttachmentFailure) { - _exportAttachmentFailureAction(failure); - } else if (failure is ExportAllAttachmentsFailure) { - _exportAllAttachmentsFailureAction(failure); } else if (failure is ParseCalendarEventFailure) { _handleParseCalendarEventFailure(failure); } else if (failure is GetEmailContentFailure) { @@ -697,173 +675,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _threadDetailController?.markCollapsedEmailReadSuccess(success); } - void exportAttachment(Attachment attachment) { - final cancelToken = CancelToken(); - _showDownloadingFileDialog(attachment.name ?? '', cancelToken: cancelToken); - _exportAttachmentAction(attachment, cancelToken); - } - - void _showDownloadingFileDialog(String attachmentName, {CancelToken? cancelToken}) { - if (cancelToken != null) { - Get.dialog( - PointerInterceptor( - child: Builder( - builder: (context) { - final appLocalizations = AppLocalizations.of(context); - return (DownloadingFileDialogBuilder() - ..key(const Key('downloading_file_dialog')) - ..title(appLocalizations.preparing_to_export) - ..content(appLocalizations.downloading_file(attachmentName)) - ..actionText(appLocalizations.cancel) - ..addCancelDownloadActionClick(() { - cancelToken.cancel([ - appLocalizations.user_cancel_download_file, - ]); - popBack(); - })) - .build(); - }, - ), - ), - barrierDismissible: false, - ); - } else { - Get.dialog( - PointerInterceptor( - child: Builder( - builder: (context) { - final appLocalizations = AppLocalizations.of(context); - return (DownloadingFileDialogBuilder() - ..key(const Key('downloading_file_for_web_dialog')) - ..title(appLocalizations.preparing_to_save) - ..content( - appLocalizations.downloading_file(attachmentName), - )) - .build(); - }, - ), - ), - ); - } - } - - void _exportAttachmentAction(Attachment attachment, CancelToken cancelToken) { - if (accountId != null && session != null) { - try { - final baseDownloadUrl = session!.getDownloadUrl( - jmapUrl: dynamicUrlInterceptors.jmapUrl, - ); - consumeState(_exportAttachmentInteractor.execute( - attachment, - accountId!, - baseDownloadUrl, - cancelToken, - )); - } catch (e) { - logError('SingleEmailController::_exportAttachmentAction(): $e'); - consumeState(Stream.value(Left(ExportAttachmentFailure(e)))); - } - } else { - consumeState(Stream.value(Left(ExportAttachmentFailure(NotFoundAccountIdException())))); - } - } - - void exportAllAttachments(String outputFileName) { - final cancelToken = CancelToken(); - _showDownloadingFileDialog(outputFileName, cancelToken: cancelToken); - _exportAllAttachmentsAction(outputFileName, cancelToken); - } - - void _exportAllAttachmentsAction(String outputFileName, CancelToken cancelToken) { - if (accountId == null || session == null) { - consumeState(Stream.value(Left(ExportAllAttachmentsFailure( - exception: NotFoundSessionException(), - )))); - return; - } - - final downloadAllSupported = session!.isDownloadAllSupported(accountId); - if (!downloadAllSupported) { - consumeState(Stream.value(Left(ExportAllAttachmentsFailure()))); - return; - } - - final baseDownloadAllUrl = session!.getDownloadAllCapability(accountId)!.endpoint!; - consumeState(_exportAllAttachmentsInteractor.execute( - accountId!, - currentEmail!.id!, - baseDownloadAllUrl, - outputFileName, - cancelToken: cancelToken, - )); - } - - void _exportAttachmentFailureAction(ExportAttachmentFailure failure) { - if (failure.exception is! CancelDownloadFileException) { - if (Get.isDialogOpen == true) { - popBack(); - } - - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).attachment_download_failed); - } - } - } - - void _exportAttachmentSuccessAction(ExportAttachmentSuccess success) async { - popBack(); - _openDownloadedPreviewWorkGroupDocument(success.downloadedResponse); - } - - void _exportAllAttachmentsFailureAction(ExportAllAttachmentsFailure failure) { - if (failure.exception is! CancelDownloadFileException) { - popBack(); - - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).attachment_download_failed); - } - } - } - - void _exportAllAttachmentsSuccessAction(ExportAllAttachmentsSuccess success) { - popBack(); - _saveFileToStorage(success.downloadedResponse.filePath); - } - - void _openDownloadedPreviewWorkGroupDocument(DownloadedResponse downloadedResponse) async { - log('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): $downloadedResponse'); - final filePath = downloadedResponse.filePath; - final mediaType = downloadedResponse.mediaType; - - if (mediaType == null) { - _saveFileToStorage(filePath); - return; - } - - final openResult = await open_file.OpenFile.open( - filePath, - type: Platform.isAndroid ? mediaType.mimeType : null, - // "xdg" is default value - linuxDesktopName: Platform.isIOS - ? mediaType.getDocumentUti().value ?? 'xdg' - : 'xdg', - ); - - if (openResult.type != open_file.ResultType.done) { - logError('SingleEmailController::_openDownloadedPreviewWorkGroupDocument(): no preview available'); - _saveFileToStorage(filePath); - } - } - - Future _saveFileToStorage(String filePath) async { - final params = SaveFileDialogParams(sourceFilePath: filePath); - await FlutterFileDialog.saveFile(params: params); - } - bool isDownloadAllSupported() { return session?.isDownloadAllSupported(accountId) ?? false; } @@ -1059,7 +870,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _printEmail(presentationEmail); break; case EmailActionType.downloadMessageAsEML: - mailboxDashBoardController.downloadMessageAsEML(presentationEmail); + mailboxDashBoardController.downloadMessageAsEML( + presentationEmail: presentationEmail, + showBottomDownloadProgressBar: true, + ); break; case EmailActionType.editAsNewEmail: _editAsNewEmail(presentationEmail); @@ -1100,8 +914,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ); } - Future openMailToLink(Uri? uri) => - mailboxDashBoardController.openMailToLink(uri); + void openMailToLink(Uri? uri) { + if (uri == null) return; + mailboxDashBoardController.openComposerFromMailToLink(uri); + } void deleteEmailPermanently(PresentationEmail email) { emailActionReactor.deleteEmailPermanently( @@ -1618,68 +1434,36 @@ class SingleEmailController extends BaseController with AppLoaderMixin { Attachment attachment, {bool previewerSupported = false} ) { - if (PlatformInfo.isWeb) { - mailboxDashBoardController.downloadAttachmentForWeb( - attachment: attachment, - previewerSupported: previewerSupported, - ); - } else if (PlatformInfo.isMobile) { - exportAttachment(attachment); - } else { - log('$runtimeType::handleDownloadAttachmentAction: THE PLATFORM IS SUPPORTED'); - } + mailboxDashBoardController.downloadAttachment( + attachment: attachment, + previewerSupported: previewerSupported, + showBottomDownloadProgressBar: true, + sourceView: DownloadSourceView.emailView, + ); } void handleDownloadAllAttachmentsAction(String outputFileName) { - if (PlatformInfo.isWeb) { - mailboxDashBoardController.downloadAllAttachmentsForWeb( - outputFileName: outputFileName, - currentEmail: currentEmail, - ); - } else if (PlatformInfo.isMobile) { - exportAllAttachments(outputFileName); - } else { - log('EmailView::handleDownloadAllAttachmentsAction: THE PLATFORM IS SUPPORTED'); - } + mailboxDashBoardController.downloadAllAttachments( + outputFileName: outputFileName, + emailId: currentEmail?.id, + showBottomDownloadProgressBar: true, + ); } void handleViewAttachmentAction(BuildContext context, Attachment attachment) { - mailboxDashBoardController.previewAttachmentAction( + mailboxDashBoardController.previewAttachment( context: context, attachment: attachment, - onDownloadAttachment: (attachment) { + sourceView: DownloadSourceView.emailView, + onPreviewOrDownloadAction: (attachment, isPreview) { handleDownloadAttachmentAction( attachment, - previewerSupported: attachment.isPreviewSupported, + previewerSupported: isPreview, ); }, ); } - Future _openEMLPreviewer(BuildContext context, Uri? uri) async { - log('SingleEmailController::_openEMLPreviewer:uri = $uri'); - if (uri == null) return; - - final blobId = uri.path; - log('SingleEmailController::_openEMLPreviewer:blobId = $blobId'); - if (blobId.isEmpty) return; - - mailboxDashBoardController.previewEMLFileAction( - appLocalizations: AppLocalizations.of(context), - blobId: Id(blobId), - ); - } - - Future _downloadAttachmentInEMLPreview(Uri? uri) async { - log('SingleEmailController::_downloadAttachmentInEMLPreview:uri = $uri'); - if (uri == null) return; - - final attachment = EmailUtils.parsingAttachmentByUri(uri); - if (attachment == null) return; - - handleDownloadAttachmentAction(attachment); - } - void handleMailToAttendees(CalendarOrganizer? organizer, List? attendees) { List listEmailAddressAttendees = []; @@ -1745,9 +1529,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { content: success.messageDocument, ), imagePaths: imagePaths, - onMailtoAction: openMailToLink, - onPreviewAction: (uri) => _openEMLPreviewer(context, uri), - onDownloadAction: _downloadAttachmentInEMLPreview, + onMailtoAction: (uri) async => openMailToLink(uri), + onDownloadFileAction: handleDownloadAttachmentAction, ); }, onFailure: (_) { diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 1e7126620..b053e81ea 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -336,7 +336,7 @@ class EmailView extends GetWidget { Obx(() => CalendarEventDetailWidget( calendarEvent: calendarEvent, emailContent: controller.currentEmailLoaded.value?.htmlContent ?? '', - onMailtoDelegateAction: controller.openMailToLink, + onMailtoDelegateAction: (uri) async => controller.openMailToLink(uri), presentationEmail: controller.currentEmail, scrollController: scrollController, isInsideThreadDetailView: isInsideThreadDetailView, @@ -385,7 +385,7 @@ class EmailView extends GetWidget { contentPadding: 0, useDefaultFontStyle: true, maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight, - onMailtoDelegateAction: controller.openMailToLink, + onMailtoDelegateAction: (uri) async => controller.openMailToLink(uri), onHtmlContentClippedAction: controller.onHtmlContentClippedAction, onScrollHorizontalEnd: controller.onScrollHorizontalEnd, keepAlive: isInsideThreadDetailView, @@ -423,7 +423,7 @@ class EmailView extends GetWidget { direction: AppUtils.getCurrentDirection(context), contentPadding: 0, useDefaultFontStyle: true, - onMailtoDelegateAction: controller.openMailToLink, + onMailtoDelegateAction: (uri) async => controller.openMailToLink(uri), keepAlive: isInsideThreadDetailView, enableQuoteToggle: true, onScrollHorizontalEnd: controller.onScrollHorizontalEnd, @@ -474,8 +474,7 @@ class EmailView extends GetWidget { ), onTapShowAllAttachmentFile: controller.showAllAttachmentsAction, onTapHideAllAttachments: controller.hideAllAttachmentsAction, - showDownloadAllAttachmentsButton: - controller.downloadAllButtonIsEnabled(), + showDownloadAllAttachmentsButton: controller.downloadAllButtonIsEnabled(), onTapDownloadAllButton: () => controller.handleDownloadAllAttachmentsAction( 'TwakeMail-${DateTime.now()}', diff --git a/lib/features/email/presentation/mixin/preview_attachment_mixin.dart b/lib/features/email/presentation/mixin/preview_attachment_mixin.dart deleted file mode 100644 index 842ab7e11..000000000 --- a/lib/features/email/presentation/mixin/preview_attachment_mixin.dart +++ /dev/null @@ -1,444 +0,0 @@ -import 'dart:typed_data'; - -import 'package:core/core.dart'; -import 'package:dartz/dartz.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:model/download/download_task_id.dart'; -import 'package:model/email/attachment.dart'; -import 'package:model/extensions/session_extension.dart'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; -import 'package:tmail_ui_user/features/base/base_controller.dart'; -import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; -import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/parse_email_by_blob_id_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart'; -import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; -import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; -import 'package:tmail_ui_user/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart'; -import 'package:tmail_ui_user/features/email_previewer/email_previewer_dialog_view.dart'; -import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/routes/app_routes.dart'; -import 'package:tmail_ui_user/main/routes/route_utils.dart'; -import 'package:tmail_ui_user/main/utils/toast_manager.dart'; - -typedef OnDownloadAttachmentAction = void Function(Attachment attachment); - -mixin PreviewAttachmentMixin { - final DownloadManager _downloadManager = Get.find(); - final PrintUtils _printUtils = Get.find(); - final AppToast _appToast = Get.find(); - final ToastManager _toastManager = Get.find(); - final DynamicUrlInterceptors _dynamicUrlInterceptors = - Get.find(); - - void previewAttachmentAction({ - required BuildContext context, - required Attachment attachment, - required AccountId? accountId, - required Session? session, - required String ownEmailAddress, - required BaseController controller, - required ParseEmailByBlobIdInteractor parseEmailInteractor, - required GetHtmlContentFromAttachmentInteractor getHtmlInteractor, - required OnDownloadAttachmentAction onDownloadAttachment, - }) { - if (PlatformInfo.isWeb && attachment.isPDFFile) { - _previewPDFFile( - context: context, - attachment: attachment, - accountId: accountId, - session: session, - ); - } else if (attachment.isEMLFile) { - previewEMLFileAction( - appLocalizations: AppLocalizations.of(context), - accountId: accountId, - session: session, - ownEmailAddress: ownEmailAddress, - blobId: attachment.blobId, - controller: controller, - parseEmailByBlobIdInteractor: parseEmailInteractor, - ); - } else if (attachment.isHTMLFile) { - _previewHtmlFileAction( - session: session, - accountId: accountId, - attachment: attachment, - controller: controller, - getHtmlInteractor: getHtmlInteractor, - ); - } else { - onDownloadAttachment(attachment); - } - } - - Future _previewPDFFile({ - required BuildContext context, - required Attachment attachment, - required AccountId? accountId, - required Session? session, - }) async { - if (accountId == null || session == null) { - _appToast.showToastErrorMessage( - context, - AppLocalizations.of(context).noPreviewAvailable, - ); - return; - } - - try { - final downloadUrl = session.getDownloadUrl( - jmapUrl: _dynamicUrlInterceptors.jmapUrl, - ); - - await Get.generalDialog( - barrierColor: Colors.black.withValues(alpha: 0.8), - pageBuilder: (_, __, ___) { - return PointerInterceptor( - child: PDFViewer( - attachment: attachment, - accountId: accountId, - downloadUrl: downloadUrl, - downloadAction: _downloadPDFFile, - printAction: _printPDFFile, - ), - ); - }, - ); - } catch (e) { - if (context.mounted) { - _appToast.showToastErrorMessage( - context, - AppLocalizations.of(context).noPreviewAvailable, - ); - } - } - } - - void _downloadPDFFile(Uint8List bytes, String fileName) { - _downloadManager.createAnchorElementDownloadFileWeb(bytes, fileName); - } - - Future _printPDFFile(Uint8List bytes, String fileName) async { - await _printUtils.printPDFFile(bytes, fileName); - } - - void previewEMLFileAction({ - required AppLocalizations appLocalizations, - required AccountId? accountId, - required Session? session, - required String ownEmailAddress, - required Id? blobId, - required ParseEmailByBlobIdInteractor parseEmailByBlobIdInteractor, - required BaseController controller, - }) { - SmartDialog.showLoading( - msg: appLocalizations.loadingPleaseWait, - maskColor: Colors.black38, - ); - - if (session == null) { - _emitFailure( - controller: controller, - failure: ParseEmailByBlobIdFailure(NotFoundSessionException()), - ); - return; - } - - if (accountId == null) { - _emitFailure( - controller: controller, - failure: ParseEmailByBlobIdFailure(NotFoundAccountIdException()), - ); - return; - } - - if (blobId == null) { - _emitFailure( - controller: controller, - failure: ParseEmailByBlobIdFailure(NotFoundBlobIdException([])), - ); - return; - } - - controller.consumeState( - parseEmailByBlobIdInteractor.execute( - accountId, - session, - ownEmailAddress, - blobId, - ), - ); - } - - ({ - bool canDownloadAttachment, - AccountId? accountId, - String? downloadUrl, - Id? blobId, - }) evaluateAttachment({ - required Session? session, - required AccountId? accountId, - required Attachment attachment, - }) { - String? downloadUrl; - try { - downloadUrl = session?.getDownloadUrl( - jmapUrl: _dynamicUrlInterceptors.jmapUrl, - ); - } catch (e) { - downloadUrl = null; - } - final blobId = attachment.blobId; - - if (accountId == null || downloadUrl == null || blobId == null) { - return ( - canDownloadAttachment: false, - accountId: accountId, - downloadUrl: downloadUrl, - blobId: blobId, - ); - } - - return ( - canDownloadAttachment: true, - accountId: accountId, - downloadUrl: downloadUrl, - blobId: blobId, - ); - } - - void _previewHtmlFileAction({ - required Session? session, - required AccountId? accountId, - required Attachment attachment, - required BaseController controller, - required GetHtmlContentFromAttachmentInteractor getHtmlInteractor, - }) { - final attachmentEvaluation = evaluateAttachment( - accountId: accountId, - session: session, - attachment: attachment, - ); - - if (!attachmentEvaluation.canDownloadAttachment) { - _emitFailure( - controller: controller, - failure: GetHtmlContentFromAttachmentFailure( - exception: null, - attachment: attachment, - ), - ); - return; - } - - controller.consumeState( - getHtmlInteractor.execute( - attachmentEvaluation.accountId!, - attachment, - DownloadTaskId(attachmentEvaluation.blobId!.value), - attachmentEvaluation.downloadUrl!, - TransformConfiguration.create( - customDomTransformers: [SanitizeHyperLinkTagInHtmlTransformer()], - customTextTransformers: [ - const StandardizeHtmlSanitizingTransformers() - ], - ), - ), - ); - } - - void _emitFailure({ - required BaseController controller, - required FeatureFailure failure, - }) { - controller.consumeState(Stream.value(Left(failure))); - } - - void handleParseEmailByBlobIdSuccess({ - required BuildContext? context, - required AccountId? accountId, - required Session? session, - required String ownEmailAddress, - required Id blobId, - required Email email, - required BaseController controller, - required PreviewEmailFromEmlFileInteractor previewInteractor, - }) { - if (session == null) { - _emitFailure( - controller: controller, - failure: PreviewEmailFromEmlFileFailure(NotFoundSessionException()), - ); - return; - } - - if (accountId == null) { - _emitFailure( - controller: controller, - failure: PreviewEmailFromEmlFileFailure(NotFoundAccountIdException()), - ); - return; - } - - if (context == null) { - _emitFailure( - controller: controller, - failure: PreviewEmailFromEmlFileFailure(NotFoundContextException()), - ); - return; - } - - try { - controller.consumeState(previewInteractor.execute( - PreviewEmailEMLRequest( - accountId: accountId, - session: session, - ownEmailAddress: ownEmailAddress, - blobId: blobId, - email: email, - locale: Localizations.localeOf(context), - appLocalizations: AppLocalizations.of(context), - baseDownloadUrl: session.getDownloadUrl( - jmapUrl: _dynamicUrlInterceptors.jmapUrl, - ), - ), - )); - } catch (e) { - _emitFailure( - controller: controller, - failure: PreviewEmailFromEmlFileFailure(e), - ); - } - } - - void handleParseEmailByBlobIdFailure(ParseEmailByBlobIdFailure failure) { - SmartDialog.dismiss(); - _toastManager.showMessageFailure(failure); - } - - void handlePreviewEmailFromEMLFileFailure( - PreviewEmailFromEmlFileFailure failure, - ) { - SmartDialog.dismiss(); - _toastManager.showMessageFailure(failure); - } - - void handlePreviewEmailFromEMLFileSuccess({ - required EMLPreviewer emlPreviewer, - required BuildContext? context, - required ImagePaths imagePaths, - required OnMailtoDelegateAction onMailtoAction, - required OnDownloadAttachmentDelegateAction onDownloadAction, - required OnPreviewEMLDelegateAction onPreviewAction, - }) { - SmartDialog.dismiss(); - - if (PlatformInfo.isWeb) { - bool isOpen = HtmlUtils.openNewWindowByUrl( - RouteUtils.createUrlWebLocationBar( - AppRoutes.emailEMLPreviewer, - previewId: emlPreviewer.id, - ).toString(), - ); - - if (!isOpen) { - _toastManager.showMessageFailure( - PreviewEmailFromEmlFileFailure(CannotOpenNewWindowException()), - ); - } - } else if (PlatformInfo.isMobile) { - if (context == null) { - _toastManager.showMessageFailure( - PreviewEmailFromEmlFileFailure(NotFoundContextException()), - ); - return; - } - - if (PlatformInfo.isAndroid) { - showModalSheetToPreviewEMLAttachment( - context: context, - emlPreviewer: emlPreviewer, - imagePaths: imagePaths, - onMailtoAction: onMailtoAction, - onDownloadAction: onDownloadAction, - onPreviewAction: onPreviewAction, - ); - } else if (PlatformInfo.isIOS) { - showDialogToPreviewEMLAttachment( - context: context, - emlPreviewer: emlPreviewer, - imagePaths: imagePaths, - onMailtoAction: onMailtoAction, - onDownloadAction: onDownloadAction, - onPreviewAction: onPreviewAction, - ); - } - } - } - - void showModalSheetToPreviewEMLAttachment({ - required BuildContext context, - required EMLPreviewer emlPreviewer, - required ImagePaths imagePaths, - required OnMailtoDelegateAction onMailtoAction, - required OnDownloadAttachmentDelegateAction onDownloadAction, - required OnPreviewEMLDelegateAction onPreviewAction, - }) { - showModalBottomSheet( - context: context, - showDragHandle: true, - useSafeArea: true, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20.0), - topRight: Radius.circular(20.0), - ), - ), - builder: (_) { - return DraggableScrollableSheet( - initialChildSize: 1.0, - builder: (context, ___) => EmailPreviewerDialogView( - emlPreviewer: emlPreviewer, - imagePaths: imagePaths, - onMailtoDelegateAction: onMailtoAction, - onPreviewEMLDelegateAction: onPreviewAction, - onDownloadAttachmentDelegateAction: onDownloadAction, - ), - ); - }, - ); - } - - void showDialogToPreviewEMLAttachment({ - required BuildContext context, - required EMLPreviewer emlPreviewer, - required ImagePaths imagePaths, - required OnMailtoDelegateAction onMailtoAction, - required OnDownloadAttachmentDelegateAction onDownloadAction, - required OnPreviewEMLDelegateAction onPreviewAction, - }) { - Get.dialog( - EmailPreviewerDialogView( - emlPreviewer: emlPreviewer, - imagePaths: imagePaths, - onMailtoDelegateAction: onMailtoAction, - onPreviewEMLDelegateAction: onPreviewAction, - onDownloadAttachmentDelegateAction: onDownloadAction, - ), - barrierColor: AppColor.colorDefaultCupertinoActionSheet, - ); - } -} diff --git a/lib/features/email/presentation/utils/email_utils.dart b/lib/features/email/presentation/utils/email_utils.dart index 428a2c46d..ce7511fae 100644 --- a/lib/features/email/presentation/utils/email_utils.dart +++ b/lib/features/email/presentation/utils/email_utils.dart @@ -14,8 +14,8 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_and_get_html_content_from_attachment_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; @@ -83,11 +83,11 @@ class EmailUtils { return state?.fold( (failure) { return failure is DownloadAttachmentForWebFailure - || failure is GetHtmlContentFromAttachmentFailure; + || failure is DownloadAndGetHtmlContentFromAttachmentFailure; }, (success) { return success is DownloadAttachmentForWebSuccess - || success is GetHtmlContentFromAttachmentSuccess + || success is DownloadAndGetHtmlContentFromAttachmentSuccess || success is IdleDownloadAttachmentForWeb; }) ?? false; } diff --git a/lib/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart b/lib/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart index 98a2e6acb..e2728c5cd 100644 --- a/lib/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart +++ b/lib/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart @@ -13,9 +13,9 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/email/domain/exceptions/download_attachment_exceptions.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/exceptions/download_attachment_exceptions.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:twake_previewer_flutter/core/previewer_options/options/loading_options.dart'; diff --git a/lib/features/email_previewer/download_attachment_loading_bar.dart b/lib/features/email_previewer/download_attachment_loading_bar.dart index 3f582dac2..427e57886 100644 --- a/lib/features/email_previewer/download_attachment_loading_bar.dart +++ b/lib/features/email_previewer/download_attachment_loading_bar.dart @@ -2,7 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:flutter/material.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; class DownloadAttachmentLoadingBar extends StatelessWidget { diff --git a/lib/features/email_previewer/email_previewer_bindings.dart b/lib/features/email_previewer/email_previewer_bindings.dart index 5f819db1c..3203feb91 100644 --- a/lib/features/email_previewer/email_previewer_bindings.dart +++ b/lib/features/email_previewer/email_previewer_bindings.dart @@ -1,14 +1,14 @@ import 'package:core/data/network/download/download_manager.dart'; import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/get_preview_email_eml_content_shared_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/get_preview_eml_content_in_memory_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/move_preview_eml_content_from_persistent_to_memory_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/remove_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_preview_eml_content_in_memory_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/move_preview_eml_content_from_persistent_to_memory_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/remove_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_dashboard/presentation/bindings/download_interactor_bindings.dart'; +import 'package:tmail_ui_user/features/download/presentation/bindings/download_interactor_bindings.dart'; class EmailPreviewerBindings extends Bindings { @override diff --git a/lib/features/email_previewer/email_previewer_controller.dart b/lib/features/email_previewer/email_previewer_controller.dart index 5a628529f..a41f85dfa 100644 --- a/lib/features/email_previewer/email_previewer_controller.dart +++ b/lib/features/email_previewer/email_previewer_controller.dart @@ -20,18 +20,18 @@ import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dar import 'package:tmail_ui_user/features/caching/exceptions/local_storage_exception.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_preview_email_eml_content_shared_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_preview_eml_content_in_memory_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/parse_email_by_blob_id_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.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/domain/usecases/get_preview_eml_content_in_memory_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/move_preview_eml_content_from_persistent_to_memory_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/remove_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/state/download_attachment_for_web_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_preview_email_eml_content_shared_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_preview_eml_content_in_memory_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_preview_email_eml_content_shared_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_preview_eml_content_in_memory_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/move_preview_eml_content_from_persistent_to_memory_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/remove_preview_email_eml_content_shared_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; diff --git a/lib/features/email_previewer/email_previewer_view.dart b/lib/features/email_previewer/email_previewer_view.dart index 5a8ce961d..84ba1c7a1 100644 --- a/lib/features/email_previewer/email_previewer_view.dart +++ b/lib/features/email_previewer/email_previewer_view.dart @@ -3,9 +3,9 @@ import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_w 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_eml_content_shared_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_preview_eml_content_in_memory_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_preview_email_eml_content_shared_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/get_preview_eml_content_in_memory_state.dart'; +import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; import 'package:tmail_ui_user/features/email_previewer/download_attachment_loading_bar.dart'; import 'package:tmail_ui_user/features/email_previewer/email_previewer_controller.dart'; diff --git a/lib/features/mailbox_dashboard/presentation/action/download_ui_action.dart b/lib/features/mailbox_dashboard/presentation/action/download_ui_action.dart index d98404f03..863aef9ef 100644 --- a/lib/features/mailbox_dashboard/presentation/action/download_ui_action.dart +++ b/lib/features/mailbox_dashboard/presentation/action/download_ui_action.dart @@ -2,7 +2,6 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; -import 'package:model/email/attachment.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; class DownloadUIAction extends UIAction { @@ -24,19 +23,10 @@ class UpdateAttachmentsViewStateAction extends DownloadUIAction { List get props => [blobId, viewState]; } -class DownloadAttachmentsQuicklyAction extends DownloadUIAction { - DownloadAttachmentsQuicklyAction(this.attachment); +class OpenComposerFromMailtoLinkAction extends DownloadUIAction { + OpenComposerFromMailtoLinkAction(this.uri); - final Attachment attachment; - - @override - List get props => [attachment]; -} - -class OpenMailtoLinkFromPreviewAttachmentAction extends DownloadUIAction { - OpenMailtoLinkFromPreviewAttachmentAction(this.uri); - - final Uri? uri; + final Uri uri; @override List get props => [uri]; 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 37e4f616d..6f3874040 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -17,6 +17,8 @@ import 'package:tmail_ui_user/features/composer/data/repository/contact_reposito 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'; import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/export_all_attachments_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/export_attachment_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'; @@ -32,15 +34,16 @@ import 'package:tmail_ui_user/features/email/data/repository/email_repository_im import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/delete_email_permanently_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_all_attachments_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_attachment_for_web_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/download_and_get_html_content_from_attachment_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/get_html_content_from_upload_file_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_restored_deleted_message_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/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/download/domain/usecase/parse_email_by_blob_id_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/preview_email_from_eml_file_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/restore_deleted_message_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/unsubscribe_email_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart'; @@ -99,10 +102,10 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_email_sort_order_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_last_time_dismissed_spam_reported_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_spam_report_state_interactor.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/bindings/download_interactor_bindings.dart'; +import 'package:tmail_ui_user/features/download/presentation/bindings/download_interactor_bindings.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; @@ -175,12 +178,16 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), )); Get.put(DownloadController( + Get.find(), + Get.find(), Get.find(), Get.find(), - Get.find(), Get.find(), Get.find(), - Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), )); Get.put(SearchController( Get.find(), diff --git a/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart deleted file mode 100644 index de7d9f85d..000000000 --- a/lib/features/mailbox_dashboard/presentation/controller/download/download_controller.dart +++ /dev/null @@ -1,617 +0,0 @@ - -import 'dart:async'; - -import 'package:core/data/network/config/dynamic_url_interceptors.dart'; -import 'package:core/data/network/download/download_manager.dart'; -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/state/failure.dart'; -import 'package:core/presentation/state/success.dart'; -import 'package:core/utils/app_logger.dart'; -import 'package:core/utils/platform_info.dart'; -import 'package:dartz/dartz.dart'; -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:get/get_navigation/src/dialog/dialog_route.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:model/download/download_task_id.dart'; -import 'package:model/email/attachment.dart'; -import 'package:model/email/eml_attachment.dart'; -import 'package:model/email/presentation_email.dart'; -import 'package:model/extensions/presentation_email_extension.dart'; -import 'package:model/extensions/session_extension.dart'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; -import 'package:tmail_ui_user/features/base/base_controller.dart'; -import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_all_attachments_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/get_html_content_from_attachment_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/parse_email_by_blob_id_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_all_attachments_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/parse_email_by_blob_id_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/preview_email_from_eml_file_interactor.dart'; -import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart'; -import 'package:tmail_ui_user/features/email/presentation/mixin/preview_attachment_mixin.dart'; -import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; -import 'package:tmail_ui_user/features/email/presentation/widgets/html_attachment_previewer.dart'; -import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; -import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/routes/route_navigation.dart'; -import 'package:twake_previewer_flutter/core/constants/supported_charset.dart'; -import 'package:twake_previewer_flutter/core/previewer_options/options/previewer_state.dart'; -import 'package:twake_previewer_flutter/core/previewer_options/options/top_bar_options.dart'; -import 'package:twake_previewer_flutter/core/previewer_options/previewer_options.dart'; -import 'package:twake_previewer_flutter/twake_image_previewer/twake_image_previewer.dart'; -import 'package:twake_previewer_flutter/twake_plain_text_previewer/twake_plain_text_previewer.dart'; -import 'package:uuid/uuid.dart'; - -typedef UpdateDownloadTaskStateCallback = DownloadTaskState Function(DownloadTaskState currentState); - -class DownloadController extends BaseController with PreviewAttachmentMixin { - final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor; - final DownloadAllAttachmentsForWebInteractor - _downloadAllAttachmentsForWebInteractor; - final DownloadManager _downloadManager; - final ParseEmailByBlobIdInteractor parseEmailByBlobIdInteractor; - final PreviewEmailFromEmlFileInteractor previewEmailFromEmlFileInteractor; - final GetHtmlContentFromAttachmentInteractor - getHtmlContentFromAttachmentInteractor; - - DownloadController( - this._downloadAttachmentForWebInteractor, - this._downloadAllAttachmentsForWebInteractor, - this._downloadManager, - this.parseEmailByBlobIdInteractor, - this.previewEmailFromEmlFileInteractor, - this.getHtmlContentFromAttachmentInteractor, - ); - - final listDownloadTaskState = RxList(); - final hideDownloadTaskbar = RxBool(false); - final downloadUIAction = Rxn(); - - final _downloadProgressStateController = - StreamController>.broadcast(); - StreamSubscription>? - _downloadProgressStateSubscription; - - @override - void onInit() { - super.onInit(); - _registerDownloadProgressState(); - } - - void _registerDownloadProgressState() { - _downloadProgressStateSubscription = _downloadProgressStateController.stream - .listen(_onDownloadProgressStateChanged); - } - - void _onDownloadProgressStateChanged(Either state) { - state.fold((_) => null, (success) { - if (success is StartDownloadAttachmentForWeb) { - _handleStartSingleDownload(success); - } else if (success is DownloadingAttachmentForWeb) { - _updateDownloadProgress( - success.taskId, - success.progress, - success.downloaded, - success.total, - ); - } else if (success is StartDownloadAllAttachmentsForWeb) { - _handleStartAllDownload(success); - } else if (success is DownloadingAllAttachmentsForWeb) { - _updateDownloadProgress( - success.taskId, - success.progress, - success.downloaded, - success.total, - ); - } - }); - } - - void _handleStartSingleDownload(StartDownloadAttachmentForWeb success) { - if (success.previewerSupported) return; - - addDownloadTask( - DownloadTaskState( - taskId: success.taskId, - attachment: success.attachment, - onCancel: success.cancelToken?.cancel, - ), - ); - - if (_hasValidContext) { - appToast.showToastMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).your_download_has_started, - leadingSVGIconColor: AppColor.primaryColor, - leadingSVGIcon: imagePaths.icDownload, - ); - } - } - - void _handleStartAllDownload(StartDownloadAllAttachmentsForWeb success) { - addDownloadTask( - DownloadTaskState( - taskId: success.taskId, - attachment: success.attachment, - onCancel: () => success.cancelToken?.cancel(), - ), - ); - - if (_hasValidContext) { - appToast.showToastSuccessMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).creatingAnArchiveForDownloading, - leadingSVGIconColor: Colors.white, - leadingSVGIcon: imagePaths.icDownloadAll, - ); - } - } - - void _updateDownloadProgress( - DownloadTaskId taskId, - double progress, - int downloaded, - int total, - ) { - final percent = progress.round(); - log('$runtimeType::_updateDownloadProgress(): $percent%'); - - updateDownloadTaskByTaskId(taskId, (currentTask) { - return currentTask.copyWith( - progress: progress, - downloaded: downloaded, - total: total, - ); - }); - } - - bool get _hasValidContext => - currentOverlayContext != null && currentContext != null; - - bool get notEmptyListDownloadTask => listDownloadTaskState.isNotEmpty; - - void addDownloadTask(DownloadTaskState task) { - log('DownloadController::addDownloadTask(): ${task.taskId}'); - listDownloadTaskState.add(task); - hideDownloadTaskbar.value = false; - } - - void updateDownloadTaskByTaskId( - DownloadTaskId downloadTaskId, - UpdateDownloadTaskStateCallback updateDownloadTaskCallback, - ) { - final matchIndex = listDownloadTaskState - .indexWhere((task) => task.taskId == downloadTaskId); - if (matchIndex >= 0) { - listDownloadTaskState[matchIndex] = updateDownloadTaskCallback(listDownloadTaskState[matchIndex]); - listDownloadTaskState.refresh(); - } - } - - void deleteDownloadTask(DownloadTaskId taskId) { - log('DownloadController::deleteDownloadTask(): $taskId'); - final matchIndex = listDownloadTaskState - .indexWhere((task) => task.taskId == taskId); - if (matchIndex >= 0) { - listDownloadTaskState.removeAt(matchIndex); - listDownloadTaskState.refresh(); - } - if (listDownloadTaskState.isEmpty) { - hideDownloadTaskbar.value = true; - } - } - - void downloadAttachmentForWeb({ - required Attachment attachment, - required AccountId? accountId, - required Session? session, - bool previewerSupported = false, - }) { - if (accountId == null || session == null) { - consumeState(Stream.value( - Left(DownloadAttachmentForWebFailure( - attachment: attachment, - exception: NotFoundSessionException(), - )), - )); - return; - } - - final generateTaskId = DownloadTaskId(uuid.v4()); - try { - final baseDownloadUrl = session.getDownloadUrl( - jmapUrl: dynamicUrlInterceptors.jmapUrl, - ); - final cancelToken = CancelToken(); - consumeState(_downloadAttachmentForWebInteractor.execute( - generateTaskId, - attachment, - accountId, - baseDownloadUrl, - onReceiveController: _downloadProgressStateController, - cancelToken: cancelToken, - previewerSupported: previewerSupported, - )); - } catch (e) { - consumeState(Stream.value( - Left(DownloadAttachmentForWebFailure( - attachment: attachment, - taskId: generateTaskId, - exception: e, - )), - )); - } - } - - void downloadAllAttachmentsForWeb({ - required String outputFileName, - required PresentationEmail? currentEmail, - required AccountId? accountId, - required Session? session, - bool previewerSupported = false, - }) { - final taskId = DownloadTaskId(uuid.v4()); - - if (accountId == null || session == null) { - consumeState(Stream.value( - Left(DownloadAllAttachmentsForWebFailure( - exception: NotFoundSessionException(), - taskId: taskId, - )), - )); - return; - } - - final downloadAllSupported = session.isDownloadAllSupported(accountId); - final emailId = currentEmail?.id; - - if (!downloadAllSupported || emailId == null) { - consumeState(Stream.value( - Left(DownloadAllAttachmentsForWebFailure(taskId: taskId)), - )); - return; - } - - final baseDownloadAllUrl = session.getDownloadAllCapability(accountId)!.endpoint!; - final downloadAttachment = Attachment( - name: outputFileName, - type: MediaType('application', 'zip'), - ); - final cancelToken = CancelToken(); - consumeState(_downloadAllAttachmentsForWebInteractor.execute( - accountId, - emailId, - baseDownloadAllUrl, - downloadAttachment, - taskId, - onReceiveController: _downloadProgressStateController, - cancelToken: cancelToken, - )); - } - - void downloadMessageAsEML({ - required PresentationEmail presentationEmail, - required AccountId? accountId, - required Session? session, - }) { - if (accountId == null || session == null) return; - - final emlAttachment = presentationEmail.createEMLAttachment(); - if (emlAttachment.blobId == null) { - consumeState(Stream.value( - Left(DownloadAttachmentForWebFailure( - exception: NotFoundEmailBlobIdException(), - )), - )); - return; - } - - final generateTaskId = DownloadTaskId(const Uuid().v4()); - try { - final baseDownloadUrl = session.getDownloadUrl( - jmapUrl: getBinding()?.jmapUrl, - ); - final cancelToken = CancelToken(); - consumeState(_downloadAttachmentForWebInteractor.execute( - generateTaskId, - emlAttachment, - accountId, - baseDownloadUrl, - onReceiveController: _downloadProgressStateController, - cancelToken: cancelToken, - previewerSupported: false, - )); - } catch (e) { - consumeState(Stream.value(Left(DownloadAttachmentForWebFailure( - attachment: emlAttachment, - taskId: generateTaskId, - exception: e, - )))); - } - } - - void _pushDownloadUIAction(DownloadUIAction action) { - downloadUIAction.value = action; - } - - void _handleDownloadAttachmentForWebSuccess( - DownloadAttachmentForWebSuccess success, - ) { - _pushDownloadUIAction(UpdateAttachmentsViewStateAction( - success.attachment.blobId, - Right(success), - )); - - deleteDownloadTask(success.taskId); - - if (!success.previewerSupported) { - _downloadManager.createAnchorElementDownloadFileWeb( - success.bytes, - success.attachment.generateFileName(), - ); - return; - } - - if (success.attachment.isImage) { - _previewImageFile(attachment: success.attachment, bytes: success.bytes); - } else if (success.attachment.isText || success.attachment.isJson) { - _previewTextPlainFile( - attachment: success.attachment, - bytes: success.bytes, - ); - } - } - - void _downloadAttachmentQuickly(Attachment attachment) { - if (PlatformInfo.isWeb) { - _pushDownloadUIAction(DownloadAttachmentsQuicklyAction(attachment)); - } - } - - void _previewImageFile({ - required Uint8List bytes, - required Attachment attachment, - }) { - if (currentContext == null) return; - - Navigator.of(currentContext!).push(GetDialogRoute( - pageBuilder: (context, _, __) => PointerInterceptor( - child: TwakeImagePreviewer( - bytes: bytes, - zoomable: true, - previewerOptions: const PreviewerOptions( - previewerState: PreviewerState.success, - ), - topBarOptions: TopBarOptions( - title: attachment.generateFileName(), - onClose: () => Navigator.maybePop(context), - onDownload: currentContext == null - ? null - : () => _downloadAttachmentQuickly(attachment), - ), - ), - ), - barrierDismissible: false, - )); - } - - void _previewTextPlainFile({ - required Uint8List bytes, - required Attachment attachment, - }) { - if (currentContext == null) return; - - Navigator.of(currentContext!).push(GetDialogRoute( - pageBuilder: (context, _, __) => PointerInterceptor( - child: TwakePlainTextPreviewer( - supportedCharset: SupportedCharset.utf8, - bytes: bytes, - previewerOptions: PreviewerOptions( - previewerState: PreviewerState.success, - width: currentContext == null ? 200 : currentContext!.width * 0.8, - ), - topBarOptions: TopBarOptions( - title: attachment.generateFileName(), - onClose: () => Navigator.maybePop(context), - onDownload: currentContext == null - ? null - : () => _downloadAttachmentQuickly(attachment), - ), - ), - ), - barrierDismissible: false, - )); - } - - void clearDownloadUIAction() { - downloadUIAction.value = null; - } - - void _downloadAllAttachmentsForWebFailure( - DownloadAllAttachmentsForWebFailure failure, - ) { - deleteDownloadTask(failure.taskId); - - if (currentOverlayContext == null || currentContext == null) return; - - final appLocalizations = AppLocalizations.of(currentContext!); - - String message = failure.cancelToken?.isCancelled == true - ? appLocalizations.downloadAttachmentHasBeenCancelled - : appLocalizations.attachment_download_failed; - - appToast.showToastErrorMessage(currentOverlayContext!, message); - } - - void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) { - if (failure.taskId != null) { - deleteDownloadTask(failure.taskId!); - } - - if (failure.attachment != null) { - _pushDownloadUIAction( - UpdateAttachmentsViewStateAction( - failure.attachment?.blobId, - Left(failure), - ), - ); - } - - if (currentOverlayContext == null || currentContext == null) return; - - final appLocalizations = AppLocalizations.of(currentContext!); - - String message = appLocalizations.attachment_download_failed; - if (failure.attachment is EMLAttachment) { - message = appLocalizations.downloadMessageAsEMLFailed; - } else if (failure.cancelToken?.isCancelled == true) { - message = appLocalizations.downloadAttachmentHasBeenCancelled; - } - - appToast.showToastErrorMessage(currentOverlayContext!, message); - } - - @override - void handleSuccessViewState(Success success) { - if (success is DownloadAttachmentForWebSuccess) { - _handleDownloadAttachmentForWebSuccess(success); - } else if (success is StartDownloadAttachmentForWeb) { - _pushDownloadUIAction(UpdateAttachmentsViewStateAction( - success.attachment.blobId, - Right(success), - )); - } else if (success is DownloadingAttachmentForWeb) { - _pushDownloadUIAction(UpdateAttachmentsViewStateAction( - success.attachment.blobId, - Right(success), - )); - } else if (success is DownloadAllAttachmentsForWebSuccess) { - deleteDownloadTask(success.taskId); - } else if (success is ParseEmailByBlobIdSuccess) { - handleParseEmailByBlobIdSuccess( - context: currentContext, - accountId: success.accountId, - session: success.session, - ownEmailAddress: success.ownEmailAddress, - blobId: success.blobId, - email: success.email, - controller: this, - previewInteractor: previewEmailFromEmlFileInteractor, - ); - } else if (success is PreviewEmailFromEmlFileSuccess) { - handlePreviewEmailFromEMLFileSuccess( - emlPreviewer: success.emlPreviewer, - context: currentContext, - imagePaths: imagePaths, - onMailtoAction: _openMailtoLink, - onDownloadAction: (uri) async { - if (uri == null) return; - - final attachment = EmailUtils.parsingAttachmentByUri(uri); - if (attachment == null) return; - - _downloadAttachmentQuickly(attachment); - }, - onPreviewAction: (uri) async { - if (currentContext != null && uri?.path.isNotEmpty == true) { - previewEMLFileAction( - appLocalizations: AppLocalizations.of(currentContext!), - accountId: success.accountId, - session: success.session, - ownEmailAddress: success.ownEmailAddress, - blobId: Id(uri!.path), - controller: this, - parseEmailByBlobIdInteractor: parseEmailByBlobIdInteractor, - ); - } - }, - ); - } else if (success is GetHtmlContentFromAttachmentSuccess) { - _pushDownloadUIAction( - UpdateAttachmentsViewStateAction( - success.attachment.blobId, - Right(success), - ), - ); - - Get.dialog(HtmlAttachmentPreviewer( - title: success.htmlAttachmentTitle, - htmlContent: success.sanitizedHtmlContent, - mailToClicked: _openMailtoLink, - downloadAttachmentClicked: () => - _downloadAttachmentQuickly(success.attachment), - responsiveUtils: responsiveUtils, - )); - } else if (success is GettingHtmlContentFromAttachment) { - _pushDownloadUIAction( - UpdateAttachmentsViewStateAction( - success.attachment.blobId, - Right(success), - ), - ); - } else { - super.handleSuccessViewState(success); - } - } - - @override - void handleFailureViewState(Failure failure) { - if (failure is DownloadAllAttachmentsForWebFailure) { - _downloadAllAttachmentsForWebFailure(failure); - } else if (failure is DownloadAttachmentForWebFailure) { - _downloadAttachmentForWebFailureAction(failure); - } else if (failure is ParseEmailByBlobIdFailure) { - handleParseEmailByBlobIdFailure(failure); - } else if (failure is PreviewEmailFromEmlFileFailure) { - handlePreviewEmailFromEMLFileFailure(failure); - } else if (failure is GetHtmlContentFromAttachmentFailure) { - _handleGetHtmlContentFromAttachmentFailure(failure); - } else { - super.handleFailureViewState(failure); - } - } - - void _handleGetHtmlContentFromAttachmentFailure( - GetHtmlContentFromAttachmentFailure failure, - ) { - _pushDownloadUIAction( - UpdateAttachmentsViewStateAction( - failure.attachment.blobId, - Left(failure), - ), - ); - - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!) - .thisHtmlAttachmentCannotBePreviewed, - ); - } - } - - Future _openMailtoLink(Uri? uri) async { - _pushDownloadUIAction(OpenMailtoLinkFromPreviewAttachmentAction(uri)); - } - - @override - void onClose() { - _downloadProgressStateSubscription?.cancel(); - _downloadProgressStateSubscription = null; - _downloadProgressStateController.close(); - super.onClose(); - } -} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 750654de1..e01d28713 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -50,6 +50,7 @@ import 'package:tmail_ui_user/features/composer/presentation/manager/composer_ma import 'package:tmail_ui_user/features/composer/presentation/model/compose_action_mode.dart'; import 'package:tmail_ui_user/features/contact/presentation/model/contact_arguments.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.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'; @@ -113,7 +114,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_e import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/cleanup_recent_search_extension.dart'; @@ -121,7 +121,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_action_type_for_email_selection.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_clear_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_download_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preferences_setting_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_reactive_obx_variable_extension.dart'; @@ -765,11 +764,8 @@ class MailboxDashBoardController extends ReloadableController _downloadUIActionWorker = ever( downloadController.downloadUIAction, (action) { - if (action is DownloadAttachmentsQuicklyAction) { - downloadAttachmentForWeb(attachment: action.attachment); - downloadController.clearDownloadUIAction(); - } else if (action is OpenMailtoLinkFromPreviewAttachmentAction) { - openMailToLink(action.uri); + if (action is OpenComposerFromMailtoLinkAction) { + openComposerFromMailToLink(action.uri); downloadController.clearDownloadUIAction(); } }, diff --git a/lib/features/mailbox_dashboard/presentation/extensions/handle_download_attachment_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/handle_download_attachment_extension.dart new file mode 100644 index 000000000..c383e0347 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/handle_download_attachment_extension.dart @@ -0,0 +1,47 @@ +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:model/email/attachment.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/download_attachment_download_controller_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; + +extension HandleDownloadAttachmentExtension on MailboxDashBoardController { + void downloadMessageAsEML({ + required PresentationEmail presentationEmail, + bool showBottomDownloadProgressBar = false, + }) => + downloadController.downloadMessageAsEML( + presentationEmail: presentationEmail, + accountId: accountId.value, + session: sessionCurrent, + showBottomDownloadProgressBar: showBottomDownloadProgressBar, + ); + + void downloadAttachment({ + required Attachment attachment, + bool previewerSupported = false, + bool showBottomDownloadProgressBar = false, + DownloadSourceView? sourceView, + }) => + downloadController.downloadAttachment( + attachment: attachment, + accountId: accountId.value, + session: sessionCurrent, + previewerSupported: previewerSupported, + showBottomDownloadProgressBar: showBottomDownloadProgressBar, + sourceView: sourceView, + ); + + void downloadAllAttachments({ + required String outputFileName, + required EmailId? emailId, + bool showBottomDownloadProgressBar = false, + }) => + downloadController.downloadAllAttachments( + outputFileName: outputFileName, + emailId: emailId, + accountId: accountId.value, + session: sessionCurrent, + showBottomDownloadProgressBar: showBottomDownloadProgressBar, + ); +} diff --git a/lib/features/mailbox_dashboard/presentation/extensions/handle_download_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/handle_download_extension.dart deleted file mode 100644 index dd9f1ecb7..000000000 --- a/lib/features/mailbox_dashboard/presentation/extensions/handle_download_extension.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:model/email/attachment.dart'; -import 'package:model/email/presentation_email.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; - -extension HandleDownloadExtension on MailboxDashBoardController { - void downloadMessageAsEML(PresentationEmail presentationEmail) { - downloadController.downloadMessageAsEML( - presentationEmail: presentationEmail, - accountId: accountId.value, - session: sessionCurrent, - ); - } - - void downloadAttachmentForWeb({ - required Attachment attachment, - bool previewerSupported = false, - }) { - downloadController.downloadAttachmentForWeb( - attachment: attachment, - accountId: accountId.value, - session: sessionCurrent, - previewerSupported: previewerSupported, - ); - } - - void downloadAllAttachmentsForWeb({ - required String outputFileName, - PresentationEmail? currentEmail, - }) { - downloadController.downloadAllAttachmentsForWeb( - outputFileName: outputFileName, - currentEmail: currentEmail, - accountId: accountId.value, - session: sessionCurrent, - ); - } -} diff --git a/lib/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart index a2d2dd74b..e3eaecce6 100644 --- a/lib/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart +++ b/lib/features/mailbox_dashboard/presentation/extensions/handle_preview_attachment_extension.dart @@ -2,48 +2,53 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart'; import 'package:core/utils/app_logger.dart'; import 'package:flutter/cupertino.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:model/email/attachment.dart'; -import 'package:tmail_ui_user/features/email/presentation/mixin/preview_attachment_mixin.dart'; +import 'package:tmail_ui_user/features/download/domain/model/download_source_view.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/download_attachment_download_controller_extension.dart'; +import 'package:tmail_ui_user/features/download/presentation/extensions/preview_attachment_download_controller_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_download_attachment_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; +import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; extension HandlePreviewAttachmentExtension on MailboxDashBoardController { - void previewAttachmentAction({ + void previewAttachment({ required BuildContext context, required Attachment attachment, - required OnDownloadAttachmentAction onDownloadAttachment, + required OnPreviewOrDownloadAttachmentAction onPreviewOrDownloadAction, + bool isDialogLoadingVisible = false, + DownloadSourceView? sourceView, }) => - downloadController.previewAttachmentAction( + downloadController.previewAttachment( context: context, attachment: attachment, accountId: accountId.value, session: sessionCurrent, ownEmailAddress: ownEmailAddress.value, - controller: downloadController, - parseEmailInteractor: downloadController.parseEmailByBlobIdInteractor, - getHtmlInteractor: - downloadController.getHtmlContentFromAttachmentInteractor, - onDownloadAttachment: onDownloadAttachment, + sourceView: sourceView, + onPreviewOrDownloadAction: onPreviewOrDownloadAction, + isDialogLoadingVisible: isDialogLoadingVisible, ); - void previewEMLFileAction({ - required AppLocalizations appLocalizations, - required Id? blobId, + void previewUploadFile({ + required BuildContext context, + required UploadFileState uploadFile, + required OnPreviewOrDownloadAttachmentAction onPreviewOrDownloadAction, + bool isDialogLoadingVisible = false, }) => - downloadController.previewEMLFileAction( - appLocalizations: appLocalizations, + downloadController.previewUploadFile( + context: context, + uploadFile: uploadFile, accountId: accountId.value, session: sessionCurrent, ownEmailAddress: ownEmailAddress.value, - blobId: blobId, - controller: downloadController, - parseEmailByBlobIdInteractor: - downloadController.parseEmailByBlobIdInteractor, + onPreviewOrDownloadAction: onPreviewOrDownloadAction, + isDialogLoadingVisible: isDialogLoadingVisible, ); void showDialogToPreviewEMLAttachment({ @@ -51,24 +56,32 @@ extension HandlePreviewAttachmentExtension on MailboxDashBoardController { required EMLPreviewer emlPreviewer, required ImagePaths imagePaths, required OnMailtoDelegateAction onMailtoAction, - required OnDownloadAttachmentDelegateAction onDownloadAction, - required OnPreviewEMLDelegateAction onPreviewAction, + required OnDownloadAttachmentFileAction onDownloadFileAction, }) => downloadController.showDialogToPreviewEMLAttachment( context: context, emlPreviewer: emlPreviewer, - imagePaths: imagePaths, onMailtoAction: onMailtoAction, - onDownloadAction: onDownloadAction, - onPreviewAction: onPreviewAction, + onPreviewAction: (uri) => downloadController.openEMLPreviewer( + appLocalizations: AppLocalizations.of(context), + uri: uri, + accountId: accountId.value, + session: sessionCurrent, + ownEmailAddress: ownEmailAddress.value, + ), + onDownloadAction: (uri) async => downloadController.downloadAttachmentInEMLPreview( + uri: uri, + onDownloadAction: (attachment) => downloadAttachment( + attachment: attachment, + ) + ), ); - Future openMailToLink(Uri? uri) async { - if (uri == null) return; + void openComposerFromMailToLink(Uri uri) { final navigationRouter = RouteUtils.generateNavigationRouterFromMailtoLink( uri.toString(), ); - log('$runtimeType::openMailToLink(): ${uri.toString()}'); + log('$runtimeType::openComposerFromMailToLink(): ${uri.toString()}'); if (!RouteUtils.canOpenComposerFromNavigationRouter(navigationRouter)) { return; } diff --git a/lib/features/upload/presentation/controller/upload_controller.dart b/lib/features/upload/presentation/controller/upload_controller.dart index 906c91f4f..ef6d0d54e 100644 --- a/lib/features/upload/presentation/controller/upload_controller.dart +++ b/lib/features/upload/presentation/controller/upload_controller.dart @@ -267,6 +267,11 @@ class UploadController extends BaseController { .toList(); } + UploadFileState? getUploadFileId(UploadTaskId id) { + return listUploadAttachments + .firstWhereOrNull((fileState) => fileState.uploadTaskId == id); + } + Set? generateAttachments() { if (attachmentsUploaded.isEmpty) { return null; diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index 3d97c0996..1670bf51d 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -23,8 +23,8 @@ import 'package:tmail_ui_user/features/email/domain/exceptions/calendar_event_ex import 'package:tmail_ui_user/features/email/domain/model/move_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/calendar_event_reply_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart'; -import 'package:tmail_ui_user/features/email/domain/state/parse_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/download/domain/state/parse_email_by_blob_id_state.dart'; +import 'package:tmail_ui_user/features/download/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'; diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart index ca6fc441f..24c7d7f2a 100644 --- a/model/lib/extensions/session_extension.dart +++ b/model/lib/extensions/session_extension.dart @@ -40,6 +40,14 @@ extension SessionExtension on Session { return downloadUrlDecode; } + String getSafetyDownloadUrl({String? jmapUrl}) { + try { + return getDownloadUrl(jmapUrl: jmapUrl); + } catch(_) { + return ''; + } + } + Uri getUploadUri(AccountId accountId, {String? jmapUrl}) { final Uri uploadUrlValid; if (jmapUrl != null) { 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 d17bf3e84..5151d44e1 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 @@ -6,9 +6,9 @@ import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:model/extensions/session_extension.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/usecases/parse_email_by_blob_id_interactor.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/download_repository.dart'; +import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart'; +import 'package:tmail_ui_user/features/download/domain/usecase/parse_email_by_blob_id_interactor.dart'; +import 'package:tmail_ui_user/features/download/domain/repository/download_repository.dart'; import '../../../../fixtures/account_fixtures.dart'; import '../../../../fixtures/email_fixtures.dart'; 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 1e86615e8..2628016af 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -29,8 +29,6 @@ import 'package:tmail_ui_user/features/email/domain/state/get_email_content_stat import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_accept_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/calendar_event_reject_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_all_attachments_interactor.dart'; -import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; @@ -45,7 +43,7 @@ import 'package:tmail_ui_user/features/login/data/network/interceptors/authoriza import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; @@ -72,7 +70,6 @@ const fallbackGenerators = { @GenerateNiceMocks([ MockSpec(), MockSpec(), - MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -100,7 +97,6 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), - MockSpec(), MockSpec(), ]) void main() { @@ -108,7 +104,6 @@ void main() { final getEmailContentInteractor = MockGetEmailContentInteractor(); final markAsEmailReadInteractor = MockMarkAsEmailReadInteractor(); - final exportAttachmentInteractor = MockExportAttachmentInteractor(); final markAsStarEmailInteractor = MockMarkAsStarEmailInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); final storeOpenedEmailInteractor = MockStoreOpenedEmailInteractor(); @@ -132,8 +127,6 @@ void main() { final mockToastManager = MockToastManager(); final mockTwakeAppManager = MockTwakeAppManager(); - final exportAllAttachmentsInteractor = MockExportAllAttachmentsInteractor(); - late SingleEmailController singleEmailController; final testAccountId = AccountId(Id('123')); @@ -178,12 +171,10 @@ void main() { singleEmailController = SingleEmailController( getEmailContentInteractor, markAsEmailReadInteractor, - exportAttachmentInteractor, markAsStarEmailInteractor, getAllIdentitiesInteractor, storeOpenedEmailInteractor, printEmailInteractor, - exportAllAttachmentsInteractor, ); }); diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index 0d5cebfed..6cdb1f818 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -71,7 +71,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_e import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index 4177c2492..ace5d64a7 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -68,7 +68,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_email_sort_order_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; diff --git a/test/features/search/verify_before_time_in_search_email_filter_test.dart b/test/features/search/verify_before_time_in_search_email_filter_test.dart index 781d6fb68..d81a2eaab 100644 --- a/test/features/search/verify_before_time_in_search_email_filter_test.dart +++ b/test/features/search/verify_before_time_in_search_email_filter_test.dart @@ -54,7 +54,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_email_sort_order_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/download/download_controller.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart';