diff --git a/contact/pubspec.lock b/contact/pubspec.lock index a3f209a444..b852623621 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -659,8 +659,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/contact/pubspec.yaml b/contact/pubspec.yaml index b85a3cd0ff..4938454e2c 100644 --- a/contact/pubspec.yaml +++ b/contact/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/core/lib/presentation/resources/assets_paths.dart b/core/lib/presentation/resources/assets_paths.dart index a8c7401165..40734a5628 100644 --- a/core/lib/presentation/resources/assets_paths.dart +++ b/core/lib/presentation/resources/assets_paths.dart @@ -1,5 +1,4 @@ class AssetsPaths { static const images = 'assets/images/'; - static const icons = 'assets/icons/'; static const configurationImages = 'configurations/icons/'; } \ No newline at end of file diff --git a/email_recovery/pubspec.lock b/email_recovery/pubspec.lock index 9977a0fb8c..8ff58703fc 100644 --- a/email_recovery/pubspec.lock +++ b/email_recovery/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/email_recovery/pubspec.yaml b/email_recovery/pubspec.yaml index 9ffdcbdd12..bb2cfc4374 100644 --- a/email_recovery/pubspec.yaml +++ b/email_recovery/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/fcm/pubspec.lock b/fcm/pubspec.lock index 92848214ba..9002e98da8 100644 --- a/fcm/pubspec.lock +++ b/fcm/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/fcm/pubspec.yaml b/fcm/pubspec.yaml index e418fae836..32db3ff8ac 100644 --- a/fcm/pubspec.yaml +++ b/fcm/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/forward/pubspec.lock b/forward/pubspec.lock index 92848214ba..9002e98da8 100644 --- a/forward/pubspec.lock +++ b/forward/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/forward/pubspec.yaml b/forward/pubspec.yaml index 6af273b33e..1c7ef30c27 100644 --- a/forward/pubspec.yaml +++ b/forward/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/lib/features/base/mixin/email_action_handler_mixin.dart b/lib/features/base/mixin/email_action_handler_mixin.dart new file mode 100644 index 0000000000..f914b222fd --- /dev/null +++ b/lib/features/base/mixin/email_action_handler_mixin.dart @@ -0,0 +1,37 @@ + +import 'dart:ui'; + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +mixin EmailActionHandlerMixin implements MessageDialogActionMixin{ + Future showConfirmDialogWhenMakeToActionForSelectionAllEmails({ + required ImagePaths imagePaths, + required int totalEmails, + required String folderName, + required VoidCallback onConfirmAction, + }) async { + if (currentContext == null) return; + + final appLocalizations = AppLocalizations.of(currentContext!); + + await showConfirmDialogAction( + currentContext!, + appLocalizations.messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox( + totalEmails, + folderName, + ), + appLocalizations.ok, + title: appLocalizations.confirmBulkAction, + icon: SvgPicture.asset( + imagePaths.icQuotasWarning, + colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), + ), + onConfirmAction: onConfirmAction, + ); + } +} \ No newline at end of file diff --git a/lib/features/base/mixin/mailbox_action_handler_mixin.dart b/lib/features/base/mixin/mailbox_action_handler_mixin.dart index f124709534..37cc523220 100644 --- a/lib/features/base/mixin/mailbox_action_handler_mixin.dart +++ b/lib/features/base/mixin/mailbox_action_handler_mixin.dart @@ -34,14 +34,13 @@ mixin MailboxActionHandlerMixin { final session = dashboardController.sessionCurrent; final accountId = dashboardController.accountId.value; final mailboxId = presentationMailbox.id; - final countEmailsUnread = presentationMailbox.unreadEmails?.value.value ?? 0; if (session != null && accountId != null) { dashboardController.markAsReadMailbox( session, accountId, mailboxId, presentationMailbox.getDisplayName(context), - countEmailsUnread.toInt() + presentationMailbox.countUnreadEmails ); onCallbackAction?.call(context); diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index aaa4341fb8..904fa9efda 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -1,4 +1,5 @@ +import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/html_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/cupertino.dart'; @@ -129,6 +130,7 @@ extension EmailActionTypeExtension on EmailActionType { String getIcon(ImagePaths imagePaths) { switch(this) { case EmailActionType.markAsUnread: + case EmailActionType.markAllAsUnread: return imagePaths.icUnreadEmail; case EmailActionType.unSpam: return imagePaths.icNotSpam; @@ -142,6 +144,16 @@ extension EmailActionTypeExtension on EmailActionType { return imagePaths.icMailboxArchived; case EmailActionType.downloadMessageAsEML: return imagePaths.icDownloadAttachment; + case EmailActionType.markAsRead: + case EmailActionType.markAllAsRead: + return imagePaths.icRead; + case EmailActionType.moveToMailbox: + case EmailActionType.moveAll: + return imagePaths.icMove; + case EmailActionType.moveToTrash: + case EmailActionType.moveAllToTrash: + case EmailActionType.deleteAllPermanently: + return imagePaths.icDeleteComposer; default: return ''; } @@ -163,8 +175,33 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).archiveMessage; case EmailActionType.downloadMessageAsEML: return AppLocalizations.of(context).downloadMessageAsEML; + case EmailActionType.markAsRead: + return AppLocalizations.of(context).mark_as_read; + case EmailActionType.moveToMailbox: + return AppLocalizations.of(context).move; + case EmailActionType.moveToTrash: + return AppLocalizations.of(context).move_to_trash; + case EmailActionType.markAllAsRead: + return AppLocalizations.of(context).mark_all_as_read; + case EmailActionType.markAllAsUnread: + return AppLocalizations.of(context).markAllAsUnread; + case EmailActionType.moveAll: + return AppLocalizations.of(context).moveAll; + case EmailActionType.moveAllToTrash: + return AppLocalizations.of(context).moveAllToTrash; + case EmailActionType.deleteAllPermanently: + return AppLocalizations.of(context).deleteAllPermanently; default: return ''; } } + + Color getIconColor() { + switch(this) { + case EmailActionType.deleteAllPermanently: + return AppColor.colorDeletePermanentlyButton; + default: + return AppColor.primaryColor; + } + } } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_datasource_impl.dart index c64e3ea668..7ae59ccea5 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -82,7 +82,7 @@ class EmailDataSourceImpl extends EmailDataSource { ReadActions readActions ) { return Future.sync(() async { - return await emailAPI.markAsRead(session, accountId, emails, readActions); + return await emailAPI.markAsReadAndGetResult(session, accountId, emails, readActions); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 4ff1280058..0502d85f3e 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -239,7 +239,7 @@ class EmailAPI with HandleSetErrorMixin { } } - Future> markAsRead( + Future> markAsReadAndGetResult( Session session, AccountId accountId, List emails, @@ -277,6 +277,41 @@ class EmailAPI with HandleSetErrorMixin { }); } + Future> markAsRead( + Session session, + AccountId accountId, + List emails, + ReadActions readActions + ) async { + final setEmailMethod = SetEmailMethod(accountId) + ..addUpdates(emails.listEmailIds.generateMapUpdateObjectMarkAsRead(readActions)); + + final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final setEmailInvocation = requestBuilder.invocation(setEmailMethod); + + final capabilities = setEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final response = await (requestBuilder..usings(capabilities)) + .build() + .execute(); + + final setEmailResponse = response.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize + ); + + final listIdUpdated = setEmailResponse?.updated?.keys.toList(); + final mapErrors = handleSetResponse([setEmailResponse]); + + if (listIdUpdated != null && mapErrors.isEmpty) { + final listEmailIdUpdated = listIdUpdated.map((id) => EmailId(id)).toList(); + return listEmailIdUpdated; + } else { + throw SetMethodException(mapErrors); + } + } + Future> downloadAttachments( List attachments, AccountId accountId, @@ -795,4 +830,47 @@ class EmailAPI with HandleSetErrorMixin { throw NotFoundEmailException(); } } + + Future> moveSelectionAllEmailsToFolder( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + List listEmailId, + { + bool isDestinationSpamMailbox = false + } + ) async { + final moveProperties = isDestinationSpamMailbox + ? listEmailId.generateMapUpdateObjectMoveToSpam(currentMailboxId, destinationMailboxId) + : listEmailId.generateMapUpdateObjectMoveToMailbox(currentMailboxId, destinationMailboxId); + + final setEmailMethod = SetEmailMethod(accountId) + ..addUpdates(moveProperties); + + final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final setEmailInvocation = requestBuilder.invocation(setEmailMethod); + + final capabilities = setEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final response = await (requestBuilder..usings(capabilities)) + .build() + .execute(); + + final setEmailResponse = response.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize + ); + + final listIdUpdated = setEmailResponse?.updated?.keys.toList(); + final mapErrors = handleSetResponse([setEmailResponse]); + + if (listIdUpdated != null && mapErrors.isEmpty) { + final listEmailIdUpdated = listIdUpdated.map((id) => EmailId(id)).toList(); + return listEmailIdUpdated; + } else { + throw SetMethodException(mapErrors); + } + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/datasource/mailbox_datasource.dart b/lib/features/mailbox/data/datasource/mailbox_datasource.dart index dd781a6185..5a70c5b6e6 100644 --- a/lib/features/mailbox/data/datasource/mailbox_datasource.dart +++ b/lib/features/mailbox/data/datasource/mailbox_datasource.dart @@ -39,7 +39,7 @@ abstract class MailboxDataSource { Future moveMailbox(Session session, AccountId accountId, MoveMailboxRequest request); - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart index 03cf6c6aff..e863818945 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart @@ -79,7 +79,7 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { } @override - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart index 1dbb9c9f4d..2464f76bdf 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart @@ -88,7 +88,7 @@ class MailboxDataSourceImpl extends MailboxDataSource { } @override - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/data/network/mailbox_isolate_worker.dart b/lib/features/mailbox/data/network/mailbox_isolate_worker.dart index 58a3306f1f..04991d12a9 100644 --- a/lib/features/mailbox/data/network/mailbox_isolate_worker.dart +++ b/lib/features/mailbox/data/network/mailbox_isolate_worker.dart @@ -37,7 +37,7 @@ class MailboxIsolateWorker { MailboxIsolateWorker(this._threadApi, this._emailApi, this._isolateExecutor); - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, @@ -68,7 +68,7 @@ class MailboxIsolateWorker { ), fun1: _handleMarkAsMailboxReadAction, notification: (value) { - if (value is List) { + if (value is List) { log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: PERCENT ${value.length / totalEmailUnread}'); onProgressController.add(Right(UpdatingMarkAsMailboxReadState( mailboxId: mailboxId, @@ -80,7 +80,7 @@ class MailboxIsolateWorker { } } - static Future> _handleMarkAsMailboxReadAction( + static Future> _handleMarkAsMailboxReadAction( MailboxMarkAsReadArguments args, TypeSendPort sendPort ) async { @@ -88,7 +88,7 @@ class MailboxIsolateWorker { BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); await HiveCacheConfig.instance.setUp(); - List emailListCompleted = List.empty(growable: true); + List emailIdListCompleted = List.empty(growable: true); try { bool mailboxHasEmails = true; UTCDate? lastReceivedDate; @@ -109,7 +109,6 @@ class MailboxIsolateWorker { ..setIsAscending(false)), properties: Properties({ EmailProperty.id, - EmailProperty.keywords, EmailProperty.receivedAt, })) .then((response) { @@ -138,25 +137,25 @@ class MailboxIsolateWorker { ReadActions.markAsRead); log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): MARK_READ: ${result.length}'); - emailListCompleted.addAll(result); - sendPort.send(emailListCompleted); + emailIdListCompleted.addAll(result); + sendPort.send(emailIdListCompleted); } } } catch (e) { log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): ERROR: $e'); } - log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): TOTAL_READ: ${emailListCompleted.length}'); - return emailListCompleted; + log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): TOTAL_READ: ${emailIdListCompleted.length}'); + return emailIdListCompleted; } - Future> _handleMarkAsMailboxReadActionOnWeb( + Future> _handleMarkAsMailboxReadActionOnWeb( Session session, AccountId accountId, MailboxId mailboxId, int totalEmailUnread, StreamController> onProgressController ) async { - List emailListCompleted = List.empty(growable: true); + List emailIdListCompleted = List.empty(growable: true); try { bool mailboxHasEmails = true; UTCDate? lastReceivedDate; @@ -177,7 +176,6 @@ class MailboxIsolateWorker { ..setIsAscending(false)), properties: Properties({ EmailProperty.id, - EmailProperty.keywords, EmailProperty.receivedAt, }) ).then((response) { @@ -201,18 +199,18 @@ class MailboxIsolateWorker { final result = await _emailApi.markAsRead(session, accountId, listEmailUnread, ReadActions.markAsRead); log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): MARK_READ: ${result.length}'); - emailListCompleted.addAll(result); + emailIdListCompleted.addAll(result); onProgressController.add(Right(UpdatingMarkAsMailboxReadState( mailboxId: mailboxId, totalUnread: totalEmailUnread, - countRead: emailListCompleted.length))); + countRead: emailIdListCompleted.length))); } } } catch (e) { log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): ERROR: $e'); } - log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): TOTAL_READ: ${emailListCompleted.length}'); - return emailListCompleted; + log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): TOTAL_READ: ${emailIdListCompleted.length}'); + return emailIdListCompleted; } } diff --git a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart index 8e28f45913..f7a109149e 100644 --- a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart +++ b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart @@ -217,7 +217,7 @@ class MailboxRepositoryImpl extends MailboxRepository { } @override - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/domain/repository/mailbox_repository.dart b/lib/features/mailbox/domain/repository/mailbox_repository.dart index 4b9e319ed8..8f65b1ff93 100644 --- a/lib/features/mailbox/domain/repository/mailbox_repository.dart +++ b/lib/features/mailbox/domain/repository/mailbox_repository.dart @@ -32,7 +32,7 @@ abstract class MailboxRepository { Future renameMailbox(Session session, AccountId accountId, RenameMailboxRequest request); - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart index 6f16d3e4e9..c5b259aec0 100644 --- a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart +++ b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart @@ -4,7 +4,7 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; -class MarkAsMailboxReadLoading extends UIState {} +class MarkAsMailboxReadLoading extends LoadingState {} class UpdatingMarkAsMailboxReadState extends UIState { @@ -23,9 +23,12 @@ class UpdatingMarkAsMailboxReadState extends UIState { class MarkAsMailboxReadAllSuccess extends UIActionState { + final MailboxId mailboxId; final String mailboxDisplayName; - MarkAsMailboxReadAllSuccess(this.mailboxDisplayName, + MarkAsMailboxReadAllSuccess( + this.mailboxId, + this.mailboxDisplayName, { jmap.State? currentEmailState, jmap.State? currentMailboxState, @@ -34,6 +37,7 @@ class MarkAsMailboxReadAllSuccess extends UIActionState { @override List get props => [ + mailboxId, mailboxDisplayName, ...super.props ]; @@ -41,10 +45,12 @@ class MarkAsMailboxReadAllSuccess extends UIActionState { class MarkAsMailboxReadHasSomeEmailFailure extends UIActionState { + final MailboxId mailboxId; final String mailboxDisplayName; final int countEmailsRead; MarkAsMailboxReadHasSomeEmailFailure( + this.mailboxId, this.mailboxDisplayName, this.countEmailsRead, { @@ -55,6 +61,7 @@ class MarkAsMailboxReadHasSomeEmailFailure extends UIActionState { @override List get props => [ + mailboxId, mailboxDisplayName, countEmailsRead, ...super.props @@ -78,4 +85,7 @@ class MarkAsMailboxReadFailure extends FeatureFailure { required this.mailboxDisplayName, dynamic exception }) : super(exception: exception); + + @override + List get props => [mailboxDisplayName, ...super.props]; } \ No newline at end of file diff --git a/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart b/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart index bd892595a3..1dd3d3d2c8 100644 --- a/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart +++ b/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart @@ -35,22 +35,24 @@ class MarkAsMailboxReadInteractor { final currentMailboxState = listState.first; final currentEmailState = listState.last; - final listEmails = await _mailboxRepository.markAsMailboxRead( + final listEmailId = await _mailboxRepository.markAsMailboxRead( session, accountId, mailboxId, totalEmailUnread, onProgressController); - if (totalEmailUnread == listEmails.length) { + if (totalEmailUnread == listEmailId.length) { yield Right(MarkAsMailboxReadAllSuccess( + mailboxId, mailboxDisplayName, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); - } else if (listEmails.isNotEmpty) { + } else if (listEmailId.isNotEmpty) { yield Right(MarkAsMailboxReadHasSomeEmailFailure( + mailboxId, mailboxDisplayName, - listEmails.length, + listEmailId.length, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); } else { diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index ef6fc7cb7a..b655502877 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -89,9 +89,13 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; @@ -304,6 +308,20 @@ class MailboxController extends BaseMailboxController _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } else if (success is GetRestoredDeletedMessageSuccess) { _refreshMailboxChanges(properties: MailboxConstants.propertiesDefault); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } }); }); @@ -594,6 +612,12 @@ class MailboxController extends BaseMailboxController log('MailboxController::_handleOpenMailbox():MAILBOX_ID = ${presentationMailboxSelected.id.asString} | MAILBOX_NAME: ${presentationMailboxSelected.name?.name}'); KeyboardUtils.hideKeyboard(context); mailboxDashBoardController.clearSelectedEmail(); + if (mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue) { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + } + if (mailboxDashBoardController.isSelectAllPageEnabled.isTrue) { + mailboxDashBoardController.isSelectAllPageEnabled.value = false; + } if (presentationMailboxSelected.id != mailboxDashBoardController.selectedMailbox.value?.id) { mailboxDashBoardController.clearFilterMessageOption(); } diff --git a/lib/features/mailbox/presentation/mailbox_view_web.dart b/lib/features/mailbox/presentation/mailbox_view_web.dart index 0be90fdbf6..6434810aab 100644 --- a/lib/features/mailbox/presentation/mailbox_view_web.dart +++ b/lib/features/mailbox/presentation/mailbox_view_web.dart @@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_load import 'package:tmail_ui_user/features/mailbox/presentation/widgets/user_information_widget.dart'; import 'package:tmail_ui_user/features/quotas/presentation/quotas_view.dart'; import 'package:tmail_ui_user/features/quotas/presentation/styles/quotas_view_styles.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; @@ -393,15 +394,29 @@ class MailboxView extends BaseMailboxView { }).toList() ?? []; } - void _handleDragItemAccepted(List listEmails, PresentationMailbox presentationMailbox) { + void _handleDragItemAccepted( + DraggableEmailData draggableEmailData, + PresentationMailbox presentationMailbox, + ) { final mailboxPath = controller.findNodePath(presentationMailbox.id) ?? presentationMailbox.name?.name; log('MailboxView::_handleDragItemAccepted(): mailboxPath: $mailboxPath'); if (mailboxPath != null) { - final newMailbox = presentationMailbox.toPresentationMailboxWithMailboxPath(mailboxPath); - controller.mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(listEmails, newMailbox); + presentationMailbox = presentationMailbox + .toPresentationMailboxWithMailboxPath(mailboxPath); + } + + if (draggableEmailData.isSelectAllEmailsEnabled) { + controller + .mailboxDashBoardController + .dragAllSelectedEmailToMailboxAction(presentationMailbox); } else { - controller.mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(listEmails, presentationMailbox); + controller + .mailboxDashBoardController + .dragSelectedMultipleEmailToMailboxAction( + draggableEmailData.listEmails!, + presentationMailbox, + ); } } } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart b/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart index 035c1f0771..cc4fcfd877 100644 --- a/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart +++ b/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; typedef OnClickOpenMailboxAction = void Function(PresentationMailbox); typedef OnClickOpenMenuMailboxAction = void Function(RelativeRect, PresentationMailbox); typedef OnSelectMailboxAction = void Function(PresentationMailbox); -typedef OnDragEmailToMailboxAccepted = void Function(List, PresentationMailbox); +typedef OnDragEmailToMailboxAccepted = void Function(DraggableEmailData, PresentationMailbox); typedef OnLongPressMailboxAction = void Function(PresentationMailbox); typedef OnClickExpandMailboxNodeAction = void Function(MailboxNode); diff --git a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart index dba008eb3b..90d278df64 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; -import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; @@ -16,6 +15,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/styles/mailbox_item_ import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_method_action_define.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/label_mailbox_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/leading_mailbox_item_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; class MailboxItemWidget extends StatefulWidget { @@ -65,8 +65,8 @@ class _MailboxItemWidgetState extends State { @override Widget build(BuildContext context) { if (_responsiveUtils.isWebDesktop(context) && widget.mailboxDisplayed == MailboxDisplayed.mailbox) { - return DragTarget>( - builder: (context, candidateEmails, rejectedEmails) { + return DragTarget( + builder: (context, _, __) { return InkWell( onTap: () => widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), onHover: (value) => setState(() => _isItemHovered = value), @@ -103,7 +103,8 @@ class _MailboxItemWidgetState extends State { ), ); }, - onAcceptWithDetails: (emails) => widget.onDragItemAccepted?.call(emails.data, widget.mailboxNode.item), + onAcceptWithDetails: (details) => + widget.onDragItemAccepted?.call(details.data, widget.mailboxNode.item), ); } else { if (widget.mailboxDisplayed == MailboxDisplayed.mailbox) { diff --git a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart index 2ba5d48bc7..5359452a29 100644 --- a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart +++ b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart @@ -1,4 +1,5 @@ +import 'package:flutter/material.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/email/email_action_type.dart'; @@ -147,4 +148,15 @@ class OpenAdvancedSearchViewAction extends DashBoardAction {} class ClearSearchFilterAppliedAction extends DashBoardAction {} -class ClearAdvancedSearchFilterEmailAction extends DashBoardAction {} \ No newline at end of file +class ClearAdvancedSearchFilterEmailAction extends DashBoardAction {} + +class MoreSelectedEmailAction extends DashBoardAction { + + final BuildContext context; + final RelativeRect position; + + MoreSelectedEmailAction(this.context, this.position); + + @override + List get props => [context, position]; +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 61f04d8fcc..55b7314a7a 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -115,11 +115,15 @@ import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_isolate_worker.dart'; import 'package:tmail_ui_user/features/thread/data/repository/thread_repository_impl.dart'; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; @@ -184,6 +188,10 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), )); Get.put(AdvancedFilterController()); } @@ -359,6 +367,26 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => GetIdentityCacheOnWebInteractor( Get.find() )); + Get.lazyPut(() => MarkAllAsUnreadSelectionAllEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); + Get.lazyPut(() => MoveAllSelectionAllEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); + Get.lazyPut(() => DeleteAllPermanentlyEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); + Get.lazyPut(() => MarkAllAsStarredSelectionAllEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); } @override 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 1bf8d4d8c2..abd3305a9c 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -30,6 +30,7 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:rxdart/transformers.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/base/mixin/contact_support_mixin.dart'; +import 'package:tmail_ui_user/features/base/mixin/email_action_handler_mixin.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/extensions/email_request_extension.dart'; @@ -143,18 +144,26 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_send import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart'; @@ -174,7 +183,9 @@ import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart'; import 'package:uuid/uuid.dart'; class MailboxDashBoardController extends ReloadableController - with UserSettingPopupMenuMixin, ContactSupportMixin { + with UserSettingPopupMenuMixin, + ContactSupportMixin, + EmailActionHandlerMixin { final RemoveEmailDraftsInteractor _removeEmailDraftsInteractor = Get.find(); final EmailReceiveManager _emailReceiveManager = Get.find(); @@ -209,6 +220,10 @@ class MailboxDashBoardController extends ReloadableController final GetRestoredDeletedMessageInterator _getRestoredDeletedMessageInteractor; final RemoveComposerCacheOnWebInteractor _removeComposerCacheOnWebInteractor; final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; + final MarkAllAsUnreadSelectionAllEmailsInteractor _markAllAsUnreadSelectionAllEmailsInteractor; + final MoveAllSelectionAllEmailsInteractor _moveAllSelectionAllEmailsInteractor; + final DeleteAllPermanentlyEmailsInteractor _deleteAllPermanentlyEmailsInteractor; + final MarkAllAsStarredSelectionAllEmailsInteractor _markAllAsStarredSelectionAllEmailsInteractor; GetAllVacationInteractor? _getAllVacationInteractor; UpdateVacationInteractor? _updateVacationInteractor; @@ -242,6 +257,12 @@ class MailboxDashBoardController extends ReloadableController final attachmentDraggableAppState = Rxn(); final isRecoveringDeletedMessage = RxBool(false); final localFileDraggableAppState = Rxn(); + final isSelectAllEmailsEnabled = RxBool(false); + final isSelectAllPageEnabled = RxBool(false); + final markAllAsUnreadSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); + final moveAllSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); + final deleteAllPermanentlyEmailsViewState = Rx>(Right(UIState.idle)); + final markAllAsStarredSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); Session? sessionCurrent; Map mapDefaultMailboxIdByRole = {}; @@ -258,12 +279,25 @@ class MailboxDashBoardController extends ReloadableController DeepLinksManager? _deepLinksManager; StreamSubscription? _deepLinkDataStreamSubscription; - final StreamController> _progressStateController = - StreamController>.broadcast(); - Stream> get progressState => _progressStateController.stream; + late StreamSubscription _markAsReadMailboxStreamSubscription; + late StreamSubscription _refreshActionStreamSubscription; + late StreamSubscription _markAllAsUnreadSelectionAllEmailsStreamSubscription; + late StreamSubscription _moveAllSelectionAllEmailsStreamSubscription; + late StreamSubscription _deleteAllPermanentlyEmailsStreamSubscription; + late StreamSubscription _markAllAsStarredSelectionAllEmailsStreamSubscription; + final StreamController> _markAsReadMailboxStreamController = + StreamController>.broadcast(); final StreamController _refreshActionEventController = StreamController.broadcast(); + final StreamController> _markAllAsUnreadSelectionAllEmailsStreamController = + StreamController>.broadcast(); + final StreamController> _moveAllSelectionAllEmailsStreamController = + StreamController>.broadcast(); + final StreamController> _deleteAllPermanentlyEmailsStreamController = + StreamController>.broadcast(); + final StreamController> _markAllAsStarredSelectionAllEmailsStreamController = + StreamController>.broadcast(); final _notificationManager = LocalNotificationManager.instance; final _fcmService = FcmService.instance; @@ -294,6 +328,10 @@ class MailboxDashBoardController extends ReloadableController this._getRestoredDeletedMessageInteractor, this._removeComposerCacheOnWebInteractor, this._getAllIdentitiesInteractor, + this._markAllAsUnreadSelectionAllEmailsInteractor, + this._moveAllSelectionAllEmailsInteractor, + this._deleteAllPermanentlyEmailsInteractor, + this._markAllAsStarredSelectionAllEmailsInteractor, ); @override @@ -366,7 +404,7 @@ class MailboxDashBoardController extends ReloadableController _deleteEmailPermanentlySuccess(success); } else if (success is MarkAsMailboxReadAllSuccess || success is MarkAsMailboxReadHasSomeEmailFailure) { - _markAsReadMailboxSuccess(success); + _handleMarkMailboxAsReadSuccess(success); } else if (success is GetAllVacationSuccess) { if (success.listVacationResponse.isNotEmpty) { vacationResponse.value = success.listVacationResponse.first; @@ -417,6 +455,17 @@ class MailboxDashBoardController extends ReloadableController goToComposer(ComposerArguments.fromSessionStorageBrowser(success.composerCache)); } else if (success is GetIdentityCacheOnWebSuccess) { goToSettings(); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess + || success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _handleMarkAllAsUnreadSelectionAllEmailsSuccess(success); + } else if (success is MoveAllSelectionAllEmailsAllSuccess + || success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _handleMoveAllSelectionAllEmailsSuccess(success); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _handleDeleteAllPermanentlyEmailsSuccess(success); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess + || success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _handleMarkAllAsStarredSelectionAllEmailsSuccess(success); } } @@ -431,10 +480,9 @@ class MailboxDashBoardController extends ReloadableController _handleUpdateEmailAsDraftsFailure(failure); } else if (failure is RemoveEmailDraftsFailure) { clearState(); - } else if (failure is MarkAsMailboxReadAllFailure) { - _markAsReadMailboxAllFailure(failure); - } else if (failure is MarkAsMailboxReadFailure) { - _markAsReadMailboxFailure(failure); + } else if (failure is MarkAsMailboxReadAllFailure + || failure is MarkAsMailboxReadFailure) { + _handleMarkMailboxAsReadFailure(failure); } else if (failure is GetEmailByIdFailure) { _handleGetEmailByIdFailure(failure); } else if (failure is RestoreDeletedMessageFailure) { @@ -446,6 +494,17 @@ class MailboxDashBoardController extends ReloadableController toastManager.showMessageFailure(failure); } else if (failure is GetComposerCacheFailure) { _handleIdentityCache(); + } else if (failure is MarkAllAsUnreadSelectionAllEmailsFailure + || failure is MarkAllAsUnreadSelectionAllEmailsAllFailure) { + _handleMarkAllAsUnreadSelectionAllEmailsFailure(failure); + } else if (failure is MoveAllSelectionAllEmailsFailure + || failure is MoveAllSelectionAllEmailsAllFailure) { + _handleMoveAllSelectionAllEmailsFailure(failure); + } else if (failure is DeleteAllPermanentlyEmailsFailure) { + _handleDeleteAllPermanentlyEmailsFailure(failure); + } else if (failure is MarkAllAsStarredSelectionAllEmailsFailure + || failure is MarkAllAsStarredSelectionAllEmailsAllFailure) { + _handleMarkAllAsStarredSelectionAllEmailsFailure(failure); } } @@ -627,14 +686,30 @@ class MailboxDashBoardController extends ReloadableController } void _registerStreamListener() { - progressState.listen((state) { + _markAsReadMailboxStreamSubscription = _markAsReadMailboxStreamController.stream.listen((state) { viewStateMarkAsReadMailbox.value = state; }); - _refreshActionEventController.stream + _refreshActionStreamSubscription = _refreshActionEventController.stream .debounceTime(const Duration(milliseconds: FcmUtils.durationMessageComing)) .listen(_handleRefreshActionWhenBackToApp); + _markAllAsUnreadSelectionAllEmailsStreamSubscription = _markAllAsUnreadSelectionAllEmailsStreamController.stream.listen((state) { + markAllAsUnreadSelectionAllEmailsViewState.value = state; + }); + + _moveAllSelectionAllEmailsStreamSubscription = _moveAllSelectionAllEmailsStreamController.stream.listen((state) { + moveAllSelectionAllEmailsViewState.value = state; + }); + + _deleteAllPermanentlyEmailsStreamSubscription = _deleteAllPermanentlyEmailsStreamController.stream.listen((state) { + deleteAllPermanentlyEmailsViewState.value = state; + }); + + _markAllAsStarredSelectionAllEmailsStreamSubscription = _markAllAsStarredSelectionAllEmailsStreamController.stream.listen((state) { + markAllAsStarredSelectionAllEmailsViewState.value = state; + }); + _registerLocalNotificationStreamListener(); } @@ -740,8 +815,8 @@ class MailboxDashBoardController extends ReloadableController } MailboxId? get spamMailboxId { - return mapDefaultMailboxIdByRole[PresentationMailbox.roleJunk] - ?? mapDefaultMailboxIdByRole[PresentationMailbox.roleSpam]; + return getMailboxIdByRole(PresentationMailbox.roleJunk) + ?? getMailboxIdByRole(PresentationMailbox.roleSpam); } void setMapDefaultMailboxIdByRole(Map newMapMailboxId) { @@ -1153,6 +1228,122 @@ class MailboxDashBoardController extends ReloadableController } } + void dragAllSelectedEmailToMailboxAction(PresentationMailbox destinationMailbox) { + if (selectedMailbox.value == null) return; + + showConfirmDialogWhenMakeToActionForSelectionAllEmails( + imagePaths: imagePaths, + totalEmails: selectedMailbox.value!.countTotalEmails, + folderName: currentContext != null + ? selectedMailbox.value!.getDisplayName(currentContext!) + : '', + onConfirmAction: () => handleActionsForSelectionAllEmails( + context: currentContext!, + selectedMailbox: selectedMailbox.value!, + actionType: _getTypeMoveAllActionForMailbox(destinationMailbox), + destinationMailbox: destinationMailbox, + ), + ); + } + + EmailActionType _getTypeMoveAllActionForMailbox(PresentationMailbox presentationMailbox) { + if (presentationMailbox.isTrash) { + return EmailActionType.moveAllToTrash; + } else if (presentationMailbox.isSpam) { + return EmailActionType.markAllAsSpam; + } else { + return EmailActionType.moveAll; + } + } + + void handleActionsForSelectionAllEmails({ + required BuildContext context, + required PresentationMailbox selectedMailbox, + required EmailActionType actionType, + PresentationMailbox? destinationMailbox, + }) { + log('MailboxDashBoardController::handleActionsForSelectionAllEmails:actionType = $actionType'); + if (sessionCurrent == null || accountId.value == null) { + logError('MailboxDashBoardController::_handleActionsForSelectionAllEmails: SESSION & ACCOUNT_ID is null'); + return; + } + + switch(actionType) { + case EmailActionType.markAllAsRead: + markAsReadMailbox( + sessionCurrent!, + accountId.value!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countUnreadEmails, + ); + break; + case EmailActionType.markAllAsUnread: + markAllAsUnreadSelectionAllEmails( + sessionCurrent!, + accountId.value!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countReadEmails, + ); + break; + case EmailActionType.moveAll: + moveAllSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + destinationMailbox: destinationMailbox, + ); + break; + case EmailActionType.moveAllToTrash: + moveAllToTrashSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + trashMailbox: destinationMailbox, + ); + break; + case EmailActionType.deleteAllPermanently: + deleteAllPermanentlyEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + ); + break; + case EmailActionType.markAllAsStarred: + markAllAsStarredSelectionAllEmails( + sessionCurrent!, + accountId.value!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countTotalEmails, + ); + break; + case EmailActionType.markAllAsSpam: + maskAllAsSpamSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + spamMailbox: destinationMailbox, + ); + break; + case EmailActionType.allUnSpam: + allUnSpamSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + ); + break; + default: + break; + } + } + void dragSelectedMultipleEmailToMailboxAction( List listEmails, PresentationMailbox destinationMailbox, @@ -1175,7 +1366,6 @@ class MailboxDashBoardController extends ReloadableController _handleDragSelectedMultipleEmailToMailboxAction({selectedMailbox.value!.id: listEmails.listEmailIds}, destinationMailbox); } } - } void _handleDragSelectedMultipleEmailToMailboxAction( @@ -1592,15 +1782,13 @@ class MailboxDashBoardController extends ReloadableController void markAsReadMailboxAction(BuildContext context) { final session = sessionCurrent; final currentAccountId = accountId.value; - final mailboxId = selectedMailbox.value?.id; - final countEmailsUnread = selectedMailbox.value?.unreadEmails?.value.value ?? 0; - if (session != null && currentAccountId != null && mailboxId != null) { + if (session != null && currentAccountId != null && selectedMailbox.value != null) { markAsReadMailbox( session, currentAccountId, - mailboxId, - selectedMailbox.value?.getDisplayName(context) ?? '', - countEmailsUnread.toInt() + selectedMailbox.value!.id, + selectedMailbox.value!.getDisplayName(context), + selectedMailbox.value!.countUnreadEmails ); } } @@ -1618,49 +1806,31 @@ class MailboxDashBoardController extends ReloadableController mailboxId, mailboxDisplayName, totalEmailsUnread, - _progressStateController)); + _markAsReadMailboxStreamController)); } - void _markAsReadMailboxSuccess(Success success) { + void _handleMarkMailboxAsReadSuccess(Success success) { viewStateMarkAsReadMailbox.value = Right(UIState.idle); - if (success is MarkAsMailboxReadAllSuccess) { - if (currentContext != null && currentOverlayContext != null) { - appToast.showToastSuccessMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsMailboxReadSuccess(success.mailboxDisplayName), - leadingSVGIcon: imagePaths.icReadToast); - } - } else if (success is MarkAsMailboxReadHasSomeEmailFailure) { - if (currentContext != null && currentOverlayContext != null) { - appToast.showToastSuccessMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsMailboxReadHasSomeEmailFailure(success.mailboxDisplayName, success.countEmailsRead), - leadingSVGIcon: imagePaths.icReadToast); - } - } - } + if (currentContext == null || currentOverlayContext == null) return; - void _markAsReadMailboxFailure(MarkAsMailboxReadFailure failure) { - viewStateMarkAsReadMailbox.value = Right(UIState.idle); - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsReadFolderAllFailure( - failure.mailboxDisplayName, - ) - ); - } + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); } - void _markAsReadMailboxAllFailure(MarkAsMailboxReadAllFailure failure) { + void _handleMarkMailboxAsReadFailure(Failure failure) { viewStateMarkAsReadMailbox.value = Right(UIState.idle); - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsReadFolderAllFailure(failure.mailboxDisplayName) - ); - } + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); } void goToComposer(ComposerArguments arguments) async { @@ -2580,6 +2750,7 @@ class MailboxDashBoardController extends ReloadableController } void selectAllEmailAction() { + isSelectAllPageEnabled.value = true; dispatchAction(SelectionAllEmailAction()); } @@ -3011,6 +3182,277 @@ class MailboxDashBoardController extends ReloadableController accountId: accountId.value!); } + void markAllAsUnreadSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmailsRead + ) { + consumeState(_markAllAsUnreadSelectionAllEmailsInteractor.execute( + session, + accountId, + mailboxId, + mailboxDisplayName, + totalEmailsRead, + _markAllAsUnreadSelectionAllEmailsStreamController + )); + } + + void _handleMarkAllAsUnreadSelectionAllEmailsSuccess(Success success) { + markAllAsUnreadSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleMarkAllAsUnreadSelectionAllEmailsFailure(Failure failure) { + markAllAsUnreadSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + + Future moveAllSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox, + { + PresentationMailbox? destinationMailbox, + } + ) async { + if (destinationMailbox == null) { + final arguments = DestinationPickerArguments( + accountId, + MailboxActions.moveEmail, + session, + mailboxIdSelected: currentMailbox.id, + ); + + destinationMailbox = PlatformInfo.isWeb + ? await DialogRouter.pushGeneralDialog( + routeName: AppRoutes.destinationPicker, + arguments: arguments, + ) + : await push(AppRoutes.destinationPicker, arguments: arguments); + } + + if (destinationMailbox is PresentationMailbox) { + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + destinationMailbox.id, + destinationMailbox.mailboxPath ?? (context.mounted + ? destinationMailbox.getDisplayName(context) + : ''), + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController, + isDestinationSpamMailbox: destinationMailbox.isSpam + )); + } + } + + void _handleMoveAllSelectionAllEmailsSuccess(Success success) { + moveAllSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleMoveAllSelectionAllEmailsFailure(Failure failure) { + moveAllSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + + Future moveAllToTrashSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox, + { + PresentationMailbox? trashMailbox, + } + ) async { + MailboxId? trashMailboxId = trashMailbox?.id; + String? trashMailboxPath = trashMailbox?.getDisplayName(context) ?? ''; + + if (trashMailbox == null) { + trashMailboxId = getMailboxIdByRole(PresentationMailbox.roleTrash); + if (trashMailboxId == null) return; + trashMailboxPath = mapMailboxById[trashMailboxId]?.getDisplayName(context) ?? ''; + } + + if (trashMailboxId == null) return; + + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + trashMailboxId, + trashMailboxPath, + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController + )); + } + + Future deleteAllPermanentlyEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + consumeState(_deleteAllPermanentlyEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + currentMailbox.countTotalEmails, + _deleteAllPermanentlyEmailsStreamController + )); + } + + void _handleDeleteAllPermanentlyEmailsSuccess(Success success) { + deleteAllPermanentlyEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleDeleteAllPermanentlyEmailsFailure(Failure failure) { + deleteAllPermanentlyEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + + void markAllAsStarredSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmails + ) { + consumeState(_markAllAsStarredSelectionAllEmailsInteractor.execute( + session, + accountId, + mailboxId, + mailboxDisplayName, + totalEmails, + _markAllAsUnreadSelectionAllEmailsStreamController + )); + } + + void _handleMarkAllAsStarredSelectionAllEmailsSuccess(Success success) { + markAllAsStarredSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleMarkAllAsStarredSelectionAllEmailsFailure(Failure failure) { + markAllAsStarredSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + + Future maskAllAsSpamSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox, + { + PresentationMailbox? spamMailbox, + } + ) async { + MailboxId? spamMailboxId = spamMailbox?.id; + String spamMailboxPath = spamMailbox?.getDisplayName(context) ?? ''; + + if (spamMailbox == null) { + spamMailboxId = this.spamMailboxId; + if (spamMailboxId == null) return; + spamMailboxPath = mapMailboxById[spamMailboxId]?.getDisplayName(context) ?? ''; + } + + if (spamMailboxId == null) return; + + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + spamMailboxId, + spamMailboxPath, + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController + )); + } + + Future allUnSpamSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + final inboxMailboxId = getMailboxIdByRole(PresentationMailbox.roleInbox); + + if (inboxMailboxId == null) return; + + final inboxMailboxPath = mapMailboxById[inboxMailboxId]?.getDisplayName(context) ?? ''; + + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + inboxMailboxId, + inboxMailboxPath, + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController + )); + } + @override void onClose() { if (PlatformInfo.isWeb) { @@ -3026,8 +3468,18 @@ class MailboxDashBoardController extends ReloadableController _emailReceiveManager.closeEmailReceiveManagerStream(); _deepLinkDataStreamSubscription?.cancel(); } - _progressStateController.close(); + _markAsReadMailboxStreamSubscription.cancel(); + _markAsReadMailboxStreamController.close(); + _refreshActionStreamSubscription.cancel(); _refreshActionEventController.close(); + _markAllAsUnreadSelectionAllEmailsStreamSubscription.cancel(); + _markAllAsUnreadSelectionAllEmailsStreamController.close(); + _moveAllSelectionAllEmailsStreamSubscription.cancel(); + _moveAllSelectionAllEmailsStreamController.close(); + _deleteAllPermanentlyEmailsStreamSubscription.cancel(); + _deleteAllPermanentlyEmailsStreamController.close(); + _markAllAsStarredSelectionAllEmailsStreamSubscription.cancel(); + _markAllAsStarredSelectionAllEmailsStreamController.close(); _notificationManager.closeStream(); _fcmService.closeStream(); applicationManager.releaseUserAgent(); diff --git a/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart index 418a882698..da2ac50faa 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart @@ -84,7 +84,7 @@ class SpamReportController extends BaseController { accountId, spamMailbox.id, spamMailbox.getDisplayName(context), - spamMailbox.unreadEmails?.value.value.toInt() ?? 0 + spamMailbox.countUnreadEmails ); presentationSpamMailbox.value = null; } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index bef9b16a6a..f80177716d 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -25,11 +25,16 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/mai import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/styles/vacation_notification_message_widget_style.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; @@ -133,6 +138,21 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } }), _buildListButtonQuickSearchFilter(context), + Obx(() => MarkMailboxAsReadLoadingWidget( + viewState: controller.viewStateMarkAsReadMailbox.value, + )), + Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget( + viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value, + )), + Obx(() => MoveAllSelectionAllEmailsLoadingWidget( + viewState: controller.moveAllSelectionAllEmailsViewState.value, + )), + Obx(() => DeleteAllPermanentlyEmailsLoadingWidget( + viewState: controller.deleteAllPermanentlyEmailsViewState.value, + )), + Obx(() => MarkAllAsStarredSelectionAllEmailsLoadingWidget( + viewState: controller.markAllAsStarredSelectionAllEmailsViewState.value, + )), Expanded(child: Obx(() { switch(controller.dashboardRoute.value) { case DashboardRoutes.thread: @@ -235,19 +255,24 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { Obx(() { final listEmailSelected = controller.listEmailSelected; if (controller.isSelectionEnabled() && listEmailSelected.isNotEmpty) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.5, horizontal: 16), - child: TopBarThreadSelection( - listEmailSelected, - controller.mapMailboxById, - onCancelSelection: () => - controller.dispatchAction(CancelSelectionAllEmailAction()), - onEmailActionTypeAction: (listEmails, actionType) => - controller.dispatchAction(HandleEmailActionTypeAction( - listEmails, - actionType - )), - ), + return TopBarThreadSelection( + imagePaths: controller.imagePaths, + listEmail: listEmailSelected, + mapMailbox: controller.mapMailboxById, + isSelectAllEmailsEnabled: controller.isSelectAllEmailsEnabled.value, + selectedMailbox: controller.selectedMailbox.value, + onCancelSelection: () => + controller.dispatchAction(CancelSelectionAllEmailAction()), + onEmailActionTypeAction: (listEmails, actionType) => + controller.dispatchAction(HandleEmailActionTypeAction( + listEmails, + actionType, + )), + onMoreSelectedEmailAction: (position) => + controller.dispatchAction(MoreSelectedEmailAction( + context, + position, + )), ); } else { return Padding( diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart new file mode 100644 index 0000000000..fc977262de --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart @@ -0,0 +1,297 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/email_action_type.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/list_presentation_email_extension.dart'; +import 'package:model/extensions/presentation_mailbox_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnEmailActionTypeAction = Function( + List listEmail, + EmailActionType actionType, +); +typedef OnMoreSelectedEmailAction = Function(RelativeRect position); + +class TopBarThreadSelection extends StatelessWidget{ + + final ImagePaths imagePaths; + final List listEmail; + final Map mapMailbox; + final OnEmailActionTypeAction? onEmailActionTypeAction; + final VoidCallback? onCancelSelection; + final bool isSelectAllEmailsEnabled; + final PresentationMailbox? selectedMailbox; + final OnMoreSelectedEmailAction? onMoreSelectedEmailAction; + + const TopBarThreadSelection({ + super.key, + required this.imagePaths, + required this.listEmail, + required this.mapMailbox, + required this.isSelectAllEmailsEnabled, + this.selectedMailbox, + this.onEmailActionTypeAction, + this.onCancelSelection, + this.onMoreSelectedEmailAction, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.5, horizontal: 16), + child: Row(children: [ + TMailButtonWidget.fromIcon( + icon: imagePaths.icClose, + iconColor: AppColor.primaryColor, + tooltipMessage: AppLocalizations.of(context).cancel, + backgroundColor: Colors.transparent, + iconSize: 28, + padding: const EdgeInsets.all(3), + onTapActionCallback: onCancelSelection + ), + if (!isSelectAllEmailsEnabled) + Padding( + padding: const EdgeInsetsDirectional.only(end: 30), + child: Text( + AppLocalizations.of(context).count_email_selected(listEmail.length), + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: AppColor.colorTextButton + ) + ), + ), + TMailButtonWidget.fromIcon( + icon: _getIconForMarkAsRead(), + tooltipMessage: _getTooltipMessageForMarkAsRead(context), + backgroundColor: Colors.transparent, + iconSize: 24, + onTapActionCallback: () => onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMarkAsRead() + ) + ), + TMailButtonWidget.fromIcon( + icon: _getIconForMarkAsStar(), + tooltipMessage: _getTooltipMessageForMarkAsStar(context), + backgroundColor: Colors.transparent, + iconSize: 24, + onTapActionCallback: () => onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMarkAsStar() + ) + ), + if (canSpamAndMove) + ...[ + TMailButtonWidget.fromIcon( + icon: imagePaths.icMove, + iconSize: 22, + tooltipMessage: _getTooltipMessageForMove(context), + backgroundColor: Colors.transparent, + onTapActionCallback: () => onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMove() + ) + ), + TMailButtonWidget.fromIcon( + icon: _getIconForMoveToSpam(), + backgroundColor: Colors.transparent, + iconSize: 24, + tooltipMessage: _getTooltipMessageForMoveToSpam(context), + onTapActionCallback: () { + onEmailActionTypeAction?.call( + List.from(listEmail.listEmailCanSpam(mapMailbox)), + _getActionTypeForMoveToSpam() + ); + } + ) + ], + if (isAllBelongToTheSameMailbox) + TMailButtonWidget.fromIcon( + icon: imagePaths.icDeleteComposer, + backgroundColor: Colors.transparent, + iconSize: 20, + iconColor: _getIconColorForMoveToTrash(), + tooltipMessage: _getTooltipMessageForMoveToTrash(context), + onTapActionCallback: () { + onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMoveToTrash() + ); + } + ), + if (isSelectAllEmailsEnabled) + TMailButtonWidget.fromIcon( + icon: imagePaths.icMoreVertical, + iconSize: 22, + iconColor: AppColor.primaryColor, + tooltipMessage: AppLocalizations.of(context).more, + backgroundColor: Colors.transparent, + onTapActionAtPositionCallback: onMoreSelectedEmailAction + ), + ]), + ); + } + + bool get canDeletePermanently => listEmail.isAllCanDeletePermanently(mapMailbox); + + bool get canDeleteAllPermanently => isSelectAllEmailsEnabled + && (selectedMailbox?.isTrash == true + || selectedMailbox?.isSpam == true + || selectedMailbox?.isDrafts == true); + + bool get canSpamAndMove => listEmail.isAllCanSpamAndMove(mapMailbox); + + bool get isAllSpam => listEmail.isAllSpam(mapMailbox); + + bool get isAllBelongToTheSameMailbox => listEmail.isAllBelongToTheSameMailbox(mapMailbox); + + EmailActionType _getActionTypeForMarkAsRead() { + if (isSelectAllEmailsEnabled) { + return EmailActionType.markAllAsRead; + } else { + return listEmail.isAllEmailRead + ? EmailActionType.markAsUnread + : EmailActionType.markAsRead; + } + } + + String _getIconForMarkAsRead() { + if (isSelectAllEmailsEnabled) { + return imagePaths.icRead; + } else { + return listEmail.isAllEmailRead + ? imagePaths.icUnread + : imagePaths.icRead; + } + } + + String _getTooltipMessageForMarkAsRead(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).mark_all_as_read; + } else { + return listEmail.isAllEmailRead + ? AppLocalizations.of(context).mark_as_unread + : AppLocalizations.of(context).mark_as_read; + } + } + + String _getTooltipMessageForMove(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).moveAll; + } else { + return AppLocalizations.of(context).move; + } + } + + EmailActionType _getActionTypeForMove() { + if (isSelectAllEmailsEnabled) { + return EmailActionType.moveAll; + } else { + return EmailActionType.moveToMailbox; + } + } + + Color _getIconColorForMoveToTrash() { + if (canDeleteAllPermanently) { + return AppColor.colorDeletePermanentlyButton; + } else if (isSelectAllEmailsEnabled) { + return AppColor.primaryColor; + } else if (canDeletePermanently) { + return AppColor.colorDeletePermanentlyButton; + } else { + return AppColor.primaryColor; + } + } + + String _getTooltipMessageForMoveToTrash(BuildContext context) { + if (canDeleteAllPermanently) { + return AppLocalizations.of(context).deleteAllPermanently; + } else if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).moveAllToTrash; + } else if (canDeletePermanently) { + return AppLocalizations.of(context).delete_permanently; + } else { + return AppLocalizations.of(context).move_to_trash; + } + } + + EmailActionType _getActionTypeForMoveToTrash() { + if (canDeleteAllPermanently) { + return EmailActionType.deleteAllPermanently; + } else if (isSelectAllEmailsEnabled) { + return EmailActionType.moveAllToTrash; + } else if (canDeletePermanently) { + return EmailActionType.deletePermanently; + } else { + return EmailActionType.moveToTrash; + } + } + + String _getIconForMarkAsStar() { + if (isSelectAllEmailsEnabled) { + return imagePaths.icStar; + } else { + return listEmail.isAllEmailStarred + ? imagePaths.icUnStar + : imagePaths.icStar; + } + } + + String _getTooltipMessageForMarkAsStar(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).allStarred; + } else { + return listEmail.isAllEmailStarred + ? AppLocalizations.of(context).un_star + : AppLocalizations.of(context).star; + } + } + + EmailActionType _getActionTypeForMarkAsStar() { + if (isSelectAllEmailsEnabled) { + return EmailActionType.markAllAsStarred; + } else { + return listEmail.isAllEmailStarred + ? EmailActionType.unMarkAsStarred + : EmailActionType.markAsStarred; + } + } + + String _getIconForMoveToSpam() { + if (isSelectAllEmailsEnabled) { + return selectedMailbox?.isSpam == true ? imagePaths.icNotSpam : imagePaths.icSpam; + } else { + return isAllSpam ? imagePaths.icNotSpam : imagePaths.icSpam; + } + } + + String _getTooltipMessageForMoveToSpam(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return selectedMailbox?.isSpam == true + ? AppLocalizations.of(context).allUnSpam + : AppLocalizations.of(context).markAllAsSpam; + } else { + return isAllSpam + ? AppLocalizations.of(context).un_spam + : AppLocalizations.of(context).mark_as_spam; + } + } + + EmailActionType _getActionTypeForMoveToSpam() { + if (isSelectAllEmailsEnabled) { + return selectedMailbox?.isSpam == true + ? EmailActionType.allUnSpam + : EmailActionType.markAllAsSpam; + } else { + return isAllSpam + ? EmailActionType.unSpam + : EmailActionType.moveToSpam; + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart new file mode 100644 index 0000000000..b46e3c24b1 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart @@ -0,0 +1,38 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; + +class DeleteAllPermanentlyEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const DeleteAllPermanentlyEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is DeleteAllPermanentlyEmailsLoading) { + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, + ); + } else if (success is DeleteAllPermanentlyEmailsUpdating) { + final percent = success.total > 0 + ? success.countDeleted / success.total + : 0.0; + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart new file mode 100644 index 0000000000..53ec1be556 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart @@ -0,0 +1,42 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; + +class MarkAllAsStarredSelectionAllEmailsLoadingWidget extends StatelessWidget + with AppLoaderMixin { + + final Either viewState; + + const MarkAllAsStarredSelectionAllEmailsLoadingWidget({ + super.key, + required this.viewState, + }); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MarkAllAsStarredSelectionAllEmailsLoading) { + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, + ); + } else if (success is MarkAllAsStarredSelectionAllEmailsUpdating) { + final percent = success.total > 0 + ? success.countStarred / success.total + : 0.0; + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart new file mode 100644 index 0000000000..2f29c6d0e1 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart @@ -0,0 +1,38 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; + +class MarkAllAsUnreadSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MarkAllAsUnreadSelectionAllEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MarkAllAsUnreadSelectionAllEmailsLoading) { + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, + ); + } else if (success is MarkAllAsUnreadSelectionAllEmailsUpdating) { + final percent = success.totalRead > 0 + ? success.countUnread / success.totalRead + : 0.0; + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart new file mode 100644 index 0000000000..73640b7b54 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart @@ -0,0 +1,38 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; + +class MarkMailboxAsReadLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MarkMailboxAsReadLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MarkAsMailboxReadLoading) { + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, + ); + } else if (success is UpdatingMarkAsMailboxReadState) { + final percent = success.totalUnread > 0 + ? success.countRead / success.totalUnread + : 0.0; + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart new file mode 100644 index 0000000000..130f7e94a2 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart @@ -0,0 +1,38 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; + +class MoveAllSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MoveAllSelectionAllEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MoveAllSelectionAllEmailsLoading) { + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, + ); + } else if (success is MoveAllSelectionAllEmailsUpdating) { + final percent = success.total > 0 + ? success.countMoved / success.total + : 0.0; + return Padding( + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart deleted file mode 100644 index 82270ce269..0000000000 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ /dev/null @@ -1,150 +0,0 @@ - -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/views/button/tmail_button_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; -import 'package:model/email/email_action_type.dart'; -import 'package:model/email/presentation_email.dart'; -import 'package:model/extensions/list_presentation_email_extension.dart'; -import 'package:model/mailbox/presentation_mailbox.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -typedef OnEmailActionTypeAction = Function(List listEmail, EmailActionType actionType); - -class TopBarThreadSelection extends StatelessWidget{ - - final imagePaths = Get.find(); - - final List listEmail; - final Map mapMailbox; - final OnEmailActionTypeAction? onEmailActionTypeAction; - final VoidCallback? onCancelSelection; - - TopBarThreadSelection( - this.listEmail, - this.mapMailbox, - { - super.key, - this.onEmailActionTypeAction, - this.onCancelSelection, - } - ); - - @override - Widget build(BuildContext context) { - final canDeletePermanently = listEmail.isAllCanDeletePermanently(mapMailbox); - final canSpamAndMove = listEmail.isAllCanSpamAndMove(mapMailbox); - final isAllSpam = listEmail.isAllSpam(mapMailbox); - final isAllBelongToTheSameMailbox = listEmail.isAllBelongToTheSameMailbox(mapMailbox); - - return Row(children: [ - TMailButtonWidget.fromIcon( - icon: imagePaths.icClose, - iconColor: AppColor.primaryColor, - tooltipMessage: AppLocalizations.of(context).cancel, - backgroundColor: Colors.transparent, - iconSize: 28, - padding: const EdgeInsets.all(3), - onTapActionCallback: onCancelSelection - ), - Text( - AppLocalizations.of(context).count_email_selected(listEmail.length), - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.w500, - color: AppColor.colorTextButton - ) - ), - const SizedBox(width: 30), - TMailButtonWidget.fromIcon( - icon: listEmail.isAllEmailRead - ? imagePaths.icUnread - : imagePaths.icRead, - tooltipMessage: listEmail.isAllEmailRead - ? AppLocalizations.of(context).mark_as_unread - : AppLocalizations.of(context).mark_as_read, - backgroundColor: Colors.transparent, - iconSize: 24, - onTapActionCallback: () => onEmailActionTypeAction?.call( - List.from(listEmail), - listEmail.isAllEmailRead ? EmailActionType.markAsUnread : EmailActionType.markAsRead - ) - ), - TMailButtonWidget.fromIcon( - icon: listEmail.isAllEmailStarred - ? imagePaths.icUnStar - : imagePaths.icStar, - tooltipMessage: listEmail.isAllEmailStarred - ? AppLocalizations.of(context).un_star - : AppLocalizations.of(context).star, - backgroundColor: Colors.transparent, - iconSize: 24, - onTapActionCallback: () => onEmailActionTypeAction?.call( - List.from(listEmail), - listEmail.isAllEmailStarred ? EmailActionType.unMarkAsStarred : EmailActionType.markAsStarred - ) - ), - if (canSpamAndMove) - ...[ - TMailButtonWidget.fromIcon( - icon: imagePaths.icMove, - iconSize: 22, - tooltipMessage: AppLocalizations.of(context).move, - backgroundColor: Colors.transparent, - onTapActionCallback: () => onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.moveToMailbox - ) - ), - TMailButtonWidget.fromIcon( - icon: isAllSpam ? imagePaths.icNotSpam : imagePaths.icSpam, - backgroundColor: Colors.transparent, - iconSize: 24, - tooltipMessage: isAllSpam - ? AppLocalizations.of(context).un_spam - : AppLocalizations.of(context).mark_as_spam, - onTapActionCallback: () { - if (isAllSpam) { - onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.unSpam - ); - } else { - onEmailActionTypeAction?.call( - List.from(listEmail.listEmailCanSpam(mapMailbox)), - EmailActionType.moveToSpam - ); - } - } - ) - ], - if (isAllBelongToTheSameMailbox) - TMailButtonWidget.fromIcon( - icon: imagePaths.icDeleteComposer, - backgroundColor: Colors.transparent, - iconSize: 20, - iconColor: canDeletePermanently - ? AppColor.colorDeletePermanentlyButton - : AppColor.primaryColor, - tooltipMessage: canDeletePermanently - ? AppLocalizations.of(context).delete_permanently - : AppLocalizations.of(context).move_to_trash, - onTapActionCallback: () { - if (canDeletePermanently) { - onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.deletePermanently - ); - } else { - onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.moveToTrash - ); - } - } - ), - ]); - } -} \ No newline at end of file diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index 9176583661..b6a6a30137 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -62,6 +62,10 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_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/dialog_router.dart'; @@ -167,6 +171,20 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa _handleSubscribeMultipleMailboxHasSomeSuccess(success); } else if (success is CreateNewMailboxSuccess) { _createNewMailboxSuccess(success); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); } } diff --git a/lib/features/search/mailbox/presentation/search_mailbox_view.dart b/lib/features/search/mailbox/presentation/search_mailbox_view.dart index b388f23fc9..3297f66b05 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_view.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_view.dart @@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_action import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_controller.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/utils/search_mailbox_utils.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class SearchMailboxView extends GetWidget @@ -183,7 +184,7 @@ class SearchMailboxView extends GetWidget controller.responsiveUtils, mailboxCurrent, maxWidth: constraints.maxWidth, - onDragEmailToMailboxAccepted: controller.dashboardController.dragSelectedMultipleEmailToMailboxAction, + onDragEmailToMailboxAccepted: _handleDragItemAccepted, onClickOpenMailboxAction: (mailbox) => controller.openMailboxAction(context, mailbox), onClickOpenMenuMailboxAction: (position, mailbox) => _openMailboxMenuAction(context, mailbox, position: position), onLongPressMailboxAction: (mailbox) => _openMailboxMenuAction(context, mailbox), @@ -195,6 +196,24 @@ class SearchMailboxView extends GetWidget }); } + void _handleDragItemAccepted( + DraggableEmailData draggableEmailData, + PresentationMailbox presentationMailbox, + ) { + if (draggableEmailData.isSelectAllEmailsEnabled) { + controller + .dashboardController + .dragAllSelectedEmailToMailboxAction(presentationMailbox); + } else { + controller + .dashboardController + .dragSelectedMultipleEmailToMailboxAction( + draggableEmailData.listEmails!, + presentationMailbox, + ); + } + } + List _listPopupMenuItemAction(BuildContext context, PresentationMailbox mailbox) { final bool subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported( controller.dashboardController.sessionCurrent, diff --git a/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart b/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart index b6bef14622..253f3dd14e 100644 --- a/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart +++ b/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart @@ -8,12 +8,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:focused_menu_custom/focused_menu.dart'; import 'package:focused_menu_custom/modals.dart'; -import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_method_action_define.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/utils/search_mailbox_utils.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; class MailboxSearchedItemBuilder extends StatefulWidget { @@ -52,10 +52,13 @@ class _MailboxSearchedItemBuilderState extends State @override Widget build(BuildContext context) { if (PlatformInfo.isWeb) { - return DragTarget>( + return DragTarget( builder: (_, __, ___) => _buildMailboxItem(context), - onAcceptWithDetails: (emails) { - widget.onDragEmailToMailboxAccepted?.call(emails.data, widget._presentationMailbox); + onAcceptWithDetails: (details) { + widget.onDragEmailToMailboxAccepted?.call( + details.data, + widget._presentationMailbox, + ); } ); } else { diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index ccd7f46d89..c1c2788797 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -71,4 +74,40 @@ abstract class ThreadDataSource { ); Future getEmailById(Session session, AccountId accountId, EmailId emailId, {Properties? properties}); + + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ); + + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ); + + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ); + + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 769df93776..d680f09361 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -120,4 +123,52 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { Future getEmailById(Session session, AccountId accountId, EmailId emailId, {Properties? properties}) { throw UnimplementedError(); } + + @override + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) { + throw UnimplementedError(); + } + + @override + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ) { + throw UnimplementedError(); + } + + @override + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) { + throw UnimplementedError(); + } + + @override + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index 48c317357a..fc98e1d530 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -133,4 +136,86 @@ class ThreadDataSourceImpl extends ThreadDataSource { return email.toPresentationEmail(); }).catchError(_exceptionThrower.throwException); } + + @override + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) { + return Future.sync(() async { + return await _threadIsolateWorker.markAllAsUnreadForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmailRead, + onProgressController + ); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ) { + return Future.sync(() async { + return await _threadIsolateWorker.moveAllSelectionAllEmails( + session, + accountId, + currentMailboxId, + destinationMailboxId, + totalEmails, + onProgressController, + isDestinationSpamMailbox: isDestinationSpamMailbox + ); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) { + return Future.sync(() async { + return await _threadIsolateWorker.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) { + return Future.sync(() async { + return await _threadIsolateWorker.markAllAsStarredForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_api.dart b/lib/features/thread/data/network/thread_api.dart index bdb0b57d47..05ac95ae48 100644 --- a/lib/features/thread/data/network/thread_api.dart +++ b/lib/features/thread/data/network/thread_api.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; @@ -16,6 +17,9 @@ import 'package:jmap_dart_client/jmap/jmap_request.dart'; import 'package:jmap_dart_client/jmap/mail/email/changes/changes_email_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/changes/changes_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/query/query_email_method.dart'; @@ -24,6 +28,9 @@ import 'package:tmail_ui_user/features/thread/data/extensions/list_email_extensi import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart'; import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_response.dart'; +import 'package:jmap_dart_client/jmap/mail/email/set/set_email_method.dart'; +import 'package:jmap_dart_client/jmap/mail/email/set/set_email_response.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; @@ -306,4 +313,54 @@ class ThreadAPI { return resultList!.list.first; } + + Future, List>> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId + ) async { + final processingInvocation = ProcessingInvocation(); + + final jmapRequestBuilder = JmapRequestBuilder(httpClient, processingInvocation); + + final queryEmailMethod = QueryEmailMethod(accountId) + ..addLimit(UnsignedInt(30)) + ..addFilters(EmailFilterCondition(inMailbox: mailboxId)) + ..addSorts({}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + )); + + final queryEmailInvocation = jmapRequestBuilder.invocation(queryEmailMethod); + + final setEmailMethod = SetEmailMethod(accountId) + ..addReferenceDestroy( + processingInvocation.createResultReference( + queryEmailInvocation.methodCallId, + ReferencePath.idsPath + ) + ); + + final setEmailInvocation = jmapRequestBuilder.invocation(setEmailMethod); + + final capabilities = setEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final result = await (jmapRequestBuilder..usings(capabilities)) + .build() + .execute(); + + final queryEmailResponse = result.parse( + queryEmailInvocation.methodCallId, + QueryEmailResponse.deserialize); + + final setEmailResponse = result.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize); + + final listMatchedEmailId = queryEmailResponse?.ids.map((id) => EmailId(id)).toList() ?? []; + final listDeletedEmailId = setEmailResponse?.destroyed?.map((id) => EmailId(id)).toList() ?? []; + log('ThreadAPI::deleteAllPermanentlyEmails:listMatchedEmailId = $listMatchedEmailId'); + log('ThreadAPI::deleteAllPermanentlyEmails:listDeletedEmailId = $listDeletedEmailId'); + return dartz.Tuple2(listMatchedEmailId, listDeletedEmailId); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index 73b69e6666..1ef9f51b77 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -1,17 +1,25 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/email_property.dart'; +import 'package:model/email/mark_star_action.dart'; +import 'package:model/email/read_actions.dart'; import 'package:model/extensions/list_email_extension.dart'; import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; @@ -19,6 +27,10 @@ import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/thread/data/model/empty_mailbox_folder_arguments.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/domain/exceptions/thread_exceptions.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/exceptions/isolate_exception.dart'; import 'package:worker_manager/worker_manager.dart'; @@ -131,15 +143,13 @@ class ThreadIsolateWorker { EmailComparator(EmailComparatorProperty.receivedAt) ..setIsAscending(false)), filter: EmailFilterCondition(inMailbox: mailboxId, before: lastEmail?.receivedAt), - properties: Properties({EmailProperty.id})); - - var newEmailList = emailsResponse.emailList ?? []; - if (lastEmail != null) { - newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList(); - } - + properties: Properties({EmailProperty.id}) + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmail?.id + )); + final newEmailList = emailsResponse.emailList ?? []; log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ${newEmailList.length}'); - if (newEmailList.isNotEmpty) { lastEmail = newEmailList.last; hasEmails = true; @@ -153,9 +163,267 @@ class ThreadIsolateWorker { } } } catch (e) { - log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ERROR: $e'); + logError('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ERROR: $e'); } log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): TOTAL_REMOVE: ${emailListCompleted.length}'); return emailListCompleted; } + + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await _threadAPI.getAllEmail( + session, + accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: mailboxId, + hasKeyword: KeyWordIdentifier.emailSeen.value, + before: lastReceivedDate + ), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + ), + properties: Properties({ + EmailProperty.id, + EmailProperty.receivedAt, + }) + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmailId + )); + final listEmailRead = emailResponse.emailList; + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails: LIST_EMAIL_READ = ${listEmailRead?.length}'); + if (listEmailRead == null || listEmailRead.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmailRead.last.id; + lastReceivedDate = listEmailRead.last.receivedAt; + + final listEmailId = await _emailAPI.markAsRead( + session, + accountId, + listEmailRead, + ReadActions.markAsUnread + ); + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): MARK_UNREAD: ${listEmailId.length}'); + emailIdListCompleted.addAll(listEmailId); + onProgressController.add( + dartz.Right(MarkAllAsUnreadSelectionAllEmailsUpdating( + totalRead: totalEmailRead, + countUnread: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + logError('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): ERROR: $e'); + } + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): TOTAL_UNREAD: ${emailIdListCompleted.length}'); + return emailIdListCompleted; + } + + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ) async { + log('ThreadIsolateWorker::moveAllSelectionAllEmails: destinationMailboxId = $destinationMailboxId | totalEmails = $totalEmails'); + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await _threadAPI.getAllEmail( + session, + accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: currentMailboxId, + before: lastReceivedDate + ), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + ), + properties: Properties({ + EmailProperty.id, + EmailProperty.receivedAt, + }) + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmailId + )); + final listEmail = emailResponse.emailList; + log('ThreadIsolateWorker::moveAllSelectionAllEmails: LIST_EMAIL = ${listEmail?.length}'); + if (listEmail == null || listEmail.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmail.last.id; + lastReceivedDate = listEmail.last.receivedAt; + + final listEmailId = await _emailAPI.moveSelectionAllEmailsToFolder( + session, + accountId, + currentMailboxId, + destinationMailboxId, + listEmail.listEmailIds, + isDestinationSpamMailbox: isDestinationSpamMailbox + ); + log('ThreadIsolateWorker::moveAllSelectionAllEmails(): MOVED: ${listEmailId.length}'); + emailIdListCompleted.addAll(listEmailId); + onProgressController.add( + dartz.Right(MoveAllSelectionAllEmailsUpdating( + total: totalEmails, + countMoved: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + logError('ThreadIsolateWorker::moveAllSelectionAllEmails(): ERROR: $e'); + } + log('ThreadIsolateWorker::moveAllSelectionAllEmails(): TOTAL_MOVED: ${emailIdListCompleted.length}'); + return emailIdListCompleted; + } + + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + + while (mailboxHasEmails) { + final listResult = await _threadAPI.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId + ); + + if (listResult.value1.isNotEmpty) { + mailboxHasEmails = true; + } else { + mailboxHasEmails = false; + } + + if (listResult.value2.isNotEmpty) { + emailIdListCompleted.addAll(listResult.value2); + + onProgressController.add( + dartz.Right(MoveAllSelectionAllEmailsUpdating( + total: totalEmails, + countMoved: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + logError('ThreadIsolateWorker::deleteAllPermanentlyEmails(): ERROR: $e'); + } + log('ThreadIsolateWorker::deleteAllPermanentlyEmails(): TOTAL_DELETED_PERMANENTLY: ${emailIdListCompleted.length}'); + return emailIdListCompleted; + } + + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await _threadAPI.getAllEmail( + session, + accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: mailboxId, + notKeyword: KeyWordIdentifier.emailFlagged.value, + before: lastReceivedDate + ), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + ), + properties: Properties({ + EmailProperty.id, + EmailProperty.receivedAt, + }) + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmailId + )); + final listEmails = emailResponse.emailList; + + if (listEmails == null || listEmails.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmails.last.id; + lastReceivedDate = listEmails.last.receivedAt; + + final listResult = await _emailAPI.markAsStar( + session, + accountId, + listEmails, + MarkStarAction.markStar + ); + + emailIdListCompleted.addAll(listResult.listEmailIds); + onProgressController.add( + dartz.Right(MarkAllAsStarredSelectionAllEmailsUpdating( + total: totalEmails, + countStarred: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + logError('ThreadIsolateWorker::markAllAsStarredForSelectionAllEmails(): ERROR: $e'); + } + return emailIdListCompleted; + } + + EmailsResponse _removeDuplicatedLatestEmailFromEmailResponse({ + required EmailsResponse emailsResponse, + EmailId? latestEmailId, + }) { + List listEmails = emailsResponse.emailList ?? []; + if (listEmails.isNotEmpty && latestEmailId != null) { + listEmails = listEmails + .where((email) => email.id != latestEmailId) + .toList(); + + return EmailsResponse(emailList: listEmails, state: emailsResponse.state); + } else { + return emailsResponse; + } + } } diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 3541a07f63..6d7209172c 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -1,5 +1,9 @@ +import 'dart:async'; + import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -408,4 +412,85 @@ class ThreadRepositoryImpl extends ThreadRepository { return listEmailIdDeleted; } + + @override + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) { + return mapDataSource[DataSourceType.network]!.markAllAsUnreadForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmailRead, + onProgressController + ); + } + + @override + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ) { + return mapDataSource[DataSourceType.network]!.moveAllSelectionAllEmails( + session, + accountId, + currentMailboxId, + destinationMailboxId, + totalEmails, + onProgressController, + isDestinationSpamMailbox: isDestinationSpamMailbox + ); + } + + @override + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) async { + final listEmailIdDeleted = await mapDataSource[DataSourceType.network]!.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + + await _updateEmailCache( + accountId, + session.username, + newDestroyed: listEmailIdDeleted); + + return listEmailIdDeleted; + } + + @override + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) { + return mapDataSource[DataSourceType.network]!.markAllAsStarredForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController + ); + } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index 87e6ea37bb..bf833c8c89 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -72,4 +75,40 @@ abstract class ThreadRepository { AccountId accountId, MailboxId spamMailboxId, ); + + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ); + + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ); + + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ); + + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/delete_all_permanently_emails_state.dart b/lib/features/thread/domain/state/delete_all_permanently_emails_state.dart new file mode 100644 index 0000000000..205664f38a --- /dev/null +++ b/lib/features/thread/domain/state/delete_all_permanently_emails_state.dart @@ -0,0 +1,40 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; + +class DeleteAllPermanentlyEmailsLoading extends LoadingState {} + +class DeleteAllPermanentlyEmailsUpdating extends UIState { + + final int total; + final int countDeleted; + + DeleteAllPermanentlyEmailsUpdating({ + required this.total, + required this.countDeleted + }); + + @override + List get props => [total, countDeleted]; +} + +class DeleteAllPermanentlyEmailsSuccess extends UIActionState { + + final List emailIds; + + DeleteAllPermanentlyEmailsSuccess( + this.emailIds, { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + }) : super(currentEmailState, currentMailboxState); + + @override + List get props => [emailIds, ...super.props]; +} + +class DeleteAllPermanentlyEmailsFailure extends FeatureFailure { + + DeleteAllPermanentlyEmailsFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart b/lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart new file mode 100644 index 0000000000..f489219b5d --- /dev/null +++ b/lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart @@ -0,0 +1,56 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; + +class MarkAllAsStarredSelectionAllEmailsLoading extends LoadingState {} + +class MarkAllAsStarredSelectionAllEmailsUpdating extends UIState { + + final int total; + final int countStarred; + + MarkAllAsStarredSelectionAllEmailsUpdating({ + required this.total, + required this.countStarred + }); + + @override + List get props => [total, countStarred]; +} + +class MarkAllAsStarredSelectionAllEmailsAllSuccess extends UIActionState { + + MarkAllAsStarredSelectionAllEmailsAllSuccess({ + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + }) : super(currentMailboxState, currentEmailState); +} + +class MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure extends UIActionState { + + final int countStarred; + + MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure( + this.countStarred, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentMailboxState, currentEmailState); + + @override + List get props => [ + countStarred, + ...super.props + ]; +} + +class MarkAllAsStarredSelectionAllEmailsAllFailure extends FeatureFailure {} + +class MarkAllAsStarredSelectionAllEmailsFailure extends FeatureFailure { + + MarkAllAsStarredSelectionAllEmailsFailure({ + dynamic exception + }) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart b/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart new file mode 100644 index 0000000000..d910e092bc --- /dev/null +++ b/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart @@ -0,0 +1,56 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; + +class MarkAllAsUnreadSelectionAllEmailsLoading extends LoadingState {} + +class MarkAllAsUnreadSelectionAllEmailsUpdating extends UIState { + + final int totalRead; + final int countUnread; + + MarkAllAsUnreadSelectionAllEmailsUpdating({ + required this.totalRead, + required this.countUnread + }); + + @override + List get props => [totalRead, countUnread]; +} + +class MarkAllAsUnreadSelectionAllEmailsAllSuccess extends UIActionState { + + MarkAllAsUnreadSelectionAllEmailsAllSuccess({ + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + }) : super(currentMailboxState, currentEmailState); +} + +class MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure extends UIActionState { + + final int countEmailsUnread; + + MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure( + this.countEmailsUnread, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentMailboxState, currentEmailState); + + @override + List get props => [ + countEmailsUnread, + ...super.props + ]; +} + +class MarkAllAsUnreadSelectionAllEmailsAllFailure extends FeatureFailure {} + +class MarkAllAsUnreadSelectionAllEmailsFailure extends FeatureFailure { + + MarkAllAsUnreadSelectionAllEmailsFailure({ + dynamic exception + }) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/thread/domain/state/move_all_selection_all_emails_state.dart b/lib/features/thread/domain/state/move_all_selection_all_emails_state.dart new file mode 100644 index 0000000000..3911508642 --- /dev/null +++ b/lib/features/thread/domain/state/move_all_selection_all_emails_state.dart @@ -0,0 +1,81 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; + +class MoveAllSelectionAllEmailsLoading extends LoadingState {} + +class MoveAllSelectionAllEmailsUpdating extends UIState { + + final int total; + final int countMoved; + + MoveAllSelectionAllEmailsUpdating({ + required this.total, + required this.countMoved + }); + + @override + List get props => [total, countMoved]; +} + +class MoveAllSelectionAllEmailsAllSuccess extends UIActionState { + final String destinationPath; + + MoveAllSelectionAllEmailsAllSuccess( + this.destinationPath, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentEmailState, currentMailboxState); + + @override + List get props => [ + destinationPath, + ...super.props + ]; +} + +class MoveAllSelectionAllEmailsHasSomeEmailFailure extends UIActionState { + final String destinationPath; + final int countEmailsMoved; + + MoveAllSelectionAllEmailsHasSomeEmailFailure( + this.destinationPath, + this.countEmailsMoved, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentEmailState, currentMailboxState); + + @override + List get props => [ + countEmailsMoved, + destinationPath, + ...super.props + ]; +} + +class MoveAllSelectionAllEmailsAllFailure extends FeatureFailure { + final String destinationPath; + + MoveAllSelectionAllEmailsAllFailure(this.destinationPath); + + @override + List get props => [destinationPath]; +} + +class MoveAllSelectionAllEmailsFailure extends FeatureFailure { + + final String destinationPath; + + MoveAllSelectionAllEmailsFailure({ + required this.destinationPath, + dynamic exception + }) : super(exception: exception); + + @override + List get props => [destinationPath, ...super.props]; +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart b/lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart new file mode 100644 index 0000000000..d813143753 --- /dev/null +++ b/lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +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/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; + +class DeleteAllPermanentlyEmailsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ThreadRepository _threadRepository; + + DeleteAllPermanentlyEmailsInteractor( + this._emailRepository, + this._mailboxRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) async* { + try { + yield Right(DeleteAllPermanentlyEmailsLoading()); + onProgressController.add(Right(DeleteAllPermanentlyEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final emailIdDeleted = await _threadRepository.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + + yield Right(DeleteAllPermanentlyEmailsSuccess( + emailIdDeleted, + currentMailboxState: currentMailboxState, + currentEmailState: currentEmailState, + )); + } catch (e) { + yield Left(DeleteAllPermanentlyEmailsFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart new file mode 100644 index 0000000000..bbdb1707ad --- /dev/null +++ b/lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +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/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; + +class MarkAllAsStarredSelectionAllEmailsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ThreadRepository _threadRepository; + + MarkAllAsStarredSelectionAllEmailsInteractor( + this._emailRepository, + this._mailboxRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmails, + StreamController> onProgressController + ) async* { + try { + yield Right(MarkAllAsStarredSelectionAllEmailsLoading()); + onProgressController.add(Right(MarkAllAsStarredSelectionAllEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final listEmailId = await _threadRepository.markAllAsStarredForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController); + + if (totalEmails == listEmailId.length) { + yield Right(MarkAllAsStarredSelectionAllEmailsAllSuccess( + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else if (listEmailId.isNotEmpty) { + yield Right(MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure( + listEmailId.length, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else { + yield Left(MarkAllAsStarredSelectionAllEmailsAllFailure()); + } + } catch (e) { + yield Left(MarkAllAsStarredSelectionAllEmailsFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart new file mode 100644 index 0000000000..94068cf929 --- /dev/null +++ b/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +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/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; + +class MarkAllAsUnreadSelectionAllEmailsInteractor { + final MailboxRepository _mailboxRepository; + final EmailRepository _emailRepository; + final ThreadRepository _threadRepository; + + MarkAllAsUnreadSelectionAllEmailsInteractor( + this._mailboxRepository, + this._emailRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmailRead, + StreamController> onProgressController + ) async* { + try { + yield Right(MarkAllAsUnreadSelectionAllEmailsLoading()); + onProgressController.add(Right(MarkAllAsUnreadSelectionAllEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final listEmailId = await _threadRepository.markAllAsUnreadForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmailRead, + onProgressController); + + if (totalEmailRead == listEmailId.length) { + yield Right(MarkAllAsUnreadSelectionAllEmailsAllSuccess( + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else if (listEmailId.isNotEmpty) { + yield Right(MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure( + listEmailId.length, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else { + yield Left(MarkAllAsUnreadSelectionAllEmailsAllFailure()); + } + } catch (e) { + yield Left(MarkAllAsUnreadSelectionAllEmailsFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart new file mode 100644 index 0000000000..e7c81711fb --- /dev/null +++ b/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart @@ -0,0 +1,76 @@ +import 'dart:async'; + +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/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; + +class MoveAllSelectionAllEmailsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ThreadRepository _threadRepository; + + MoveAllSelectionAllEmailsInteractor( + this._emailRepository, + this._mailboxRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + MailboxId destinationMailboxId, + String destinationPath, + int totalEmails, + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } + ) async* { + try { + yield Right(MoveAllSelectionAllEmailsLoading()); + onProgressController.add(Right(MoveAllSelectionAllEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final listEmailId = await _threadRepository.moveAllSelectionAllEmails( + session, + accountId, + currentMailboxId, + destinationMailboxId, + totalEmails, + onProgressController, + isDestinationSpamMailbox: isDestinationSpamMailbox); + + if (totalEmails == listEmailId.length) { + yield Right(MoveAllSelectionAllEmailsAllSuccess( + destinationPath, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else if (listEmailId.isNotEmpty) { + yield Right(MoveAllSelectionAllEmailsHasSomeEmailFailure( + destinationPath, + listEmailId.length, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else { + yield Left(MoveAllSelectionAllEmailsAllFailure(destinationPath)); + } + } catch (e) { + yield Left(MoveAllSelectionAllEmailsFailure(destinationPath: destinationPath, exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/model/draggable_email_data.dart b/lib/features/thread/presentation/model/draggable_email_data.dart new file mode 100644 index 0000000000..68c1ee0df0 --- /dev/null +++ b/lib/features/thread/presentation/model/draggable_email_data.dart @@ -0,0 +1,22 @@ + +import 'package:equatable/equatable.dart'; +import 'package:model/email/presentation_email.dart'; + +class DraggableEmailData with EquatableMixin { + final List? listEmails; + final bool isSelectAllEmailsEnabled; + + DraggableEmailData({ + this.isSelectAllEmailsEnabled = false, + this.listEmails, + }); + + factory DraggableEmailData.withSelectAllEmails() => + DraggableEmailData(isSelectAllEmailsEnabled: true); + + @override + List get props => [ + listEmails, + isSelectAllEmailsEnabled, + ]; +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index e6ad7cedae..1368176f89 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -18,9 +18,12 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/base/mixin/email_action_handler_mixin.dart'; +import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; @@ -33,6 +36,7 @@ import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action 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/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search; @@ -51,13 +55,17 @@ import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_all_email_state.dart'; @@ -86,7 +94,10 @@ import 'package:universal_html/html.dart' as html; typedef StartRangeSelection = int; typedef EndRangeSelection = int; -class ThreadController extends BaseController with EmailActionController { +class ThreadController extends BaseController + with EmailActionController, + PopupMenuWidgetMixin, + EmailActionHandlerMixin { final networkConnectionController = Get.find(); @@ -286,7 +297,25 @@ class ThreadController extends BaseController with EmailActionController { filterMessagesAction(action.option); mailboxDashBoardController.clearDashBoardAction(); } else if (action is HandleEmailActionTypeAction) { - pressEmailSelectionAction(action.emailAction, action.listEmailSelected); + if (_validateToShowConfirmBulkActionEmailsDialog()) { + showConfirmDialogWhenMakeToActionForSelectionAllEmails( + imagePaths: imagePaths, + totalEmails: selectedMailbox?.countTotalEmails ?? 0, + folderName: currentContext != null + ? selectedMailbox?.getDisplayName(currentContext!) ?? '' + : '', + onConfirmAction: () => mailboxDashBoardController.handleActionsForSelectionAllEmails( + context: currentContext!, + selectedMailbox: selectedMailbox!, + actionType: action.emailAction + ), + ); + } else { + pressEmailSelectionAction( + action.emailAction, + action.listEmailSelected, + ); + } mailboxDashBoardController.clearDashBoardAction(); } else if (action is OpenEmailDetailedFromSuggestionQuickSearchAction) { final mailboxContain = action.presentationEmail.findMailboxContain(mailboxDashBoardController.mapMailboxById); @@ -328,6 +357,9 @@ class ThreadController extends BaseController with EmailActionController { } canSearchMore = true; mailboxDashBoardController.emailsInCurrentMailbox.clear(); + } else if (action is MoreSelectedEmailAction) { + showPopupMenuSelectionEmailAction(action.context, action.position); + mailboxDashBoardController.clearDashBoardAction(); } }); @@ -362,8 +394,14 @@ class ThreadController extends BaseController with EmailActionController { } else if (success is UpdateEmailDraftsSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MarkAsMailboxReadAllSuccess) { + if (success.mailboxId == selectedMailbox?.id) { + cancelSelectEmail(); + } _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MarkAsMailboxReadHasSomeEmailFailure) { + if (success.mailboxId == selectedMailbox?.id) { + cancelSelectEmail(); + } _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MoveMultipleEmailToMailboxAllSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); @@ -387,6 +425,27 @@ class ThreadController extends BaseController with EmailActionController { _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is StoreEventAttendanceStatusSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); } }); }); @@ -720,6 +779,12 @@ class ThreadController extends BaseController with EmailActionController { } void selectEmail(PresentationEmail presentationEmailSelected) { + if (mailboxDashBoardController.isSelectAllPageEnabled.isTrue) { + mailboxDashBoardController.isSelectAllPageEnabled.value = false; + } + if (mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue) { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + } final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; if (_rangeSelectionMode && latestEmailSelectedOrUnselected.value != null && latestEmailSelectedOrUnselected.value?.id != presentationEmailSelected.id) { @@ -770,11 +835,17 @@ class ThreadController extends BaseController with EmailActionController { final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox .map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)) .toList(); + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + mailboxDashBoardController.isSelectAllPageEnabled.value = false; mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; mailboxDashBoardController.listEmailSelected.clear(); } + void enableSelectAllEmails() { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = true; + } + void closeFilterMessageActionSheet() { popBack(); } @@ -1338,10 +1409,76 @@ class ThreadController extends BaseController with EmailActionController { void handleLoadMoreEmailsRequest() { log('ThreadController::handleLoadMoreEmailsRequest:'); + if (mailboxDashBoardController.isSelectAllPageEnabled.isTrue) { + mailboxDashBoardController.isSelectAllPageEnabled.value = false; + } + if (mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue) { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + } if (isSearchActive) { _searchMoreEmails(); } else { _loadMoreEmails(); } } + + bool validateToShowSelectionEmailsBanner() { + return mailboxDashBoardController.isSelectAllPageEnabled.isTrue && + selectedMailbox != null && + selectedMailbox!.countTotalEmails > ThreadConstants.maxCountEmails && + mailboxDashBoardController.listEmailSelected.length < + selectedMailbox!.countTotalEmails; + } + + void showPopupMenuSelectionEmailAction(BuildContext context, RelativeRect position) { + final listSelectionEmailActions = [ + EmailActionType.markAllAsRead, + EmailActionType.markAllAsUnread, + if (selectedMailbox == null || selectedMailbox?.isDrafts == false) + EmailActionType.moveAll, + if (selectedMailbox?.isTrash == true || + selectedMailbox?.isSpam == true || + selectedMailbox?.isDrafts == true) + EmailActionType.deleteAllPermanently + else + EmailActionType.moveAllToTrash + ]; + + openPopupMenuAction( + context, + position, + listSelectionEmailActions.map((action) => PopupMenuItem( + padding: EdgeInsets.zero, + child: popupItem( + action.getIcon(imagePaths), + action.getTitle(context), + colorIcon: action.getIconColor(), + styleName: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black + ), + onCallbackAction: () { + popBack(); + if (!isSearchActive) { + showConfirmDialogWhenMakeToActionForSelectionAllEmails( + imagePaths: imagePaths, + totalEmails: selectedMailbox?.countTotalEmails ?? 0, + folderName: selectedMailbox?.getDisplayName(context) ?? '', + onConfirmAction: () => mailboxDashBoardController.handleActionsForSelectionAllEmails( + context: context, + selectedMailbox: selectedMailbox!, + actionType: action, + ), + ); + } + } + ) + )).toList() + ); + } + + bool _validateToShowConfirmBulkActionEmailsDialog() { + return mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue; + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index a12a60f5aa..f6a4fdc168 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -11,6 +11,7 @@ import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart' import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; @@ -21,6 +22,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/scroll_to_top_button_widget_styles.dart'; @@ -30,11 +32,13 @@ import 'package:tmail_ui_user/features/thread/presentation/widgets/app_bar/app_b import 'package:tmail_ui_user/features/thread/presentation/widgets/banner_delete_all_spam_emails_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/banner_empty_trash_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/bottom_bar_thread_selection_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart' if (dart.library.html) 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_web_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/empty_emails_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/scroll_to_top_button_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/spam_banner/spam_report_banner_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_loading_bar_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -163,6 +167,30 @@ class ThreadView extends GetWidget }), if (!controller.responsiveUtils.isDesktop(context)) _buildMarkAsMailboxReadLoading(context), + if (controller.responsiveUtils.isWebDesktop(context)) + Obx(() { + if (controller.validateToShowSelectionEmailsBanner()) { + final selectedMailbox = controller + .mailboxDashBoardController + .selectedMailbox + .value!; + + final listEmailSelectedLength = controller + .mailboxDashBoardController + .listEmailSelected + .length; + + return SelectAllEmailInMailboxBanner( + limitEmailsInPage: listEmailSelectedLength, + totalEmails: selectedMailbox.countTotalEmails, + folderName: selectedMailbox.getDisplayName(context), + onSelectAllEmailAction: controller.enableSelectAllEmails, + onClearSelection: controller.cancelSelectEmail + ); + } else { + return const SizedBox.shrink(); + } + }), Obx(() => ThreadViewLoadingBarWidget(viewState: controller.viewState.value)), Expanded( child: Container( @@ -471,13 +499,33 @@ class ThreadView extends GetWidget } Widget _buildEmailItemDraggable(BuildContext context, PresentationEmail presentationEmail) { + final isSelectAllEmailsEnabled = controller + .mailboxDashBoardController + .isSelectAllEmailsEnabled + .value; + return GestureDetector( behavior: HitTestBehavior.translucent, onSecondaryTapDown: (_) {}, onTapDown: (_) {}, - child: Draggable>( - data: controller.listEmailDrag, - feedback: _buildFeedBackWidget(context), + child: Draggable( + data: isSelectAllEmailsEnabled + ? DraggableEmailData.withSelectAllEmails() + : DraggableEmailData(listEmails: controller.listEmailDrag), + feedback: Obx(() { + final isSelectAllEmailsEnabled = controller + .mailboxDashBoardController + .isSelectAllEmailsEnabled + .value; + return DraggableFeedbackWidget( + icon: controller.imagePaths.icFilterMessageAll, + title: isSelectAllEmailsEnabled + ? AppLocalizations.of(context).moveAllConversation + : AppLocalizations.of(context).moveConversation( + controller.listEmailDrag.length, + ), + ); + }), childWhenDragging: _buildEmailItemWhenDragging(context, presentationEmail), dragAnchorStrategy: pointerDragAnchorStrategy, onDragStarted: () { @@ -624,43 +672,6 @@ class ThreadView extends GetWidget } } - Widget _buildFeedBackWidget(BuildContext context) { - return SizedBox( - height: 60, - child: Material( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(10), - color: AppColor.colorTextButton, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - SvgPicture.asset( - controller.imagePaths.icFilterMessageAll, - width: 24, - height: 24, - fit: BoxFit.fill, - colorFilter: Colors.white.asFilter(), - ), - const SizedBox(width: 10), - Obx( - () => Text( - AppLocalizations.of(context).moveConversation(controller.listEmailDrag.length), - overflow: TextOverflow.clip, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - ), - ), - ], - ), - ), - ), - ); - } - Widget _buildEmptyEmail(BuildContext context) { return Obx(() => controller.viewState.value.fold( (failure) => const SizedBox.shrink(), diff --git a/lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart b/lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart new file mode 100644 index 0000000000..9932d77892 --- /dev/null +++ b/lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart @@ -0,0 +1,52 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class DraggableFeedbackWidget extends StatelessWidget { + + final String icon; + final String title; + + const DraggableFeedbackWidget({ + super.key, + required this.icon, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 60, + child: Material( + clipBehavior: Clip.hardEdge, + borderRadius: const BorderRadius.all(Radius.circular(10)), + color: AppColor.colorTextButton, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + SvgPicture.asset( + icon, + width: 24, + height: 24, + fit: BoxFit.fill, + colorFilter: Colors.white.asFilter(), + ), + const SizedBox(width: 10), + Text( + title, + overflow: TextOverflow.clip, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart new file mode 100644 index 0000000000..c4d896d296 --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart @@ -0,0 +1,45 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class MessageSelectAllEmailInMailboxWidget extends StatelessWidget { + + final int totalEmails; + final String folderName; + final VoidCallback onClearSelection; + + const MessageSelectAllEmailInMailboxWidget({ + super.key, + required this.totalEmails, + required this.folderName, + required this.onClearSelection + }); + + @override + Widget build(BuildContext context) { + return Text.rich( + TextSpan( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontSize: 14, + ), + children: [ + TextSpan( + text: '${AppLocalizations.of(context).mailsInMailboxAreSelected(totalEmails, folderName)} ', + ), + TextSpan( + text: AppLocalizations.of(context).clearSelection, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.primaryColor, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + recognizer: TapGestureRecognizer()..onTap = onClearSelection + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart new file mode 100644 index 0000000000..9103b13514 --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart @@ -0,0 +1,50 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class MessageSelectEmailInPageWidget extends StatelessWidget { + + final int limitEmailsInPage; + final int totalEmails; + final String folderName; + final VoidCallback onSelectAllEmailAction; + + const MessageSelectEmailInPageWidget({ + super.key, + required this.limitEmailsInPage, + required this.totalEmails, + required this.folderName, + required this.onSelectAllEmailAction, + }); + + @override + Widget build(BuildContext context) { + return Text.rich( + TextSpan( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontSize: 14, + ), + children: [ + TextSpan( + text: '${AppLocalizations.of(context).mailsOnThisPageAreSelected(limitEmailsInPage)} ', + ), + TextSpan( + text: AppLocalizations.of(context).selectAllMailInMailbox( + totalEmails, + folderName, + ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.primaryColor, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + recognizer: TapGestureRecognizer()..onTap = onSelectAllEmailAction + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart new file mode 100644 index 0000000000..6ad0a06583 --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart @@ -0,0 +1,57 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart'; + +class SelectAllEmailInMailboxBanner extends StatefulWidget { + final int limitEmailsInPage; + final int totalEmails; + final String folderName; + final VoidCallback onSelectAllEmailAction; + final VoidCallback onClearSelection; + + const SelectAllEmailInMailboxBanner({ + Key? key, + required this.limitEmailsInPage, + required this.totalEmails, + required this.folderName, + required this.onSelectAllEmailAction, + required this.onClearSelection, + }) : super(key: key); + + @override + State createState() => _SelectAllEmailInMailboxBannerState(); +} + +class _SelectAllEmailInMailboxBannerState extends State { + + bool _isSelectAllEmailsEnabled = false; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + color: AppColor.colorBgDesktop, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 16, + vertical: 12, + ), + alignment: AlignmentDirectional.centerStart, + child: _isSelectAllEmailsEnabled + ? MessageSelectAllEmailInMailboxWidget( + totalEmails: widget.totalEmails, + folderName: widget.folderName, + onClearSelection: widget.onClearSelection, + ) + : MessageSelectEmailInPageWidget( + limitEmailsInPage: widget.limitEmailsInPage, + totalEmails: widget.totalEmails, + folderName: widget.folderName, + onSelectAllEmailAction: () { + widget.onSelectAllEmailAction(); + setState(() => _isSelectAllEmailsEnabled = true); + }, + ) + ); + } +} diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 08575bb675..d3ece7ad4b 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-12-12T15:50:46.911821", + "@@last_modified": "2024-12-16T10:24:28.478967", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4205,5 +4205,247 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "mailsOnThisPageAreSelected": "All {countEmails} mails on this page are selected.", + "@mailsOnThisPageAreSelected": { + "type": "text", + "placeholders_order": [ + "countEmails" + ], + "placeholders": { + "countEmails": {} + } + }, + "selectAllMailInMailbox": "Select all {total} mails in {folderName}", + "@selectAllMailInMailbox": { + "type": "text", + "placeholders_order": [ + "total", + "folderName" + ], + "placeholders": { + "total": {}, + "folderName": {} + } + }, + "mailsInMailboxAreSelected": "All {totalEmails} mails in {folderName} are selected.", + "@mailsInMailboxAreSelected": { + "type": "text", + "placeholders_order": [ + "totalEmails", + "folderName" + ], + "placeholders": { + "totalEmails": {}, + "folderName": {} + } + }, + "clearSelection": "Clear selection", + "@clearSelection": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "confirmBulkAction": "Confirm bulk action", + "@confirmBulkAction": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "ok": "OK", + "@ok": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox": "This action will affect all {total} mails in {folderName}. Are you sure you want to continue?", + "@messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox": { + "type": "text", + "placeholders_order": [ + "total", + "folderName" + ], + "placeholders": { + "total": {}, + "folderName": {} + } + }, + "markAllAsUnread": "Mark all as unread", + "@markAllAsUnread": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess": "You’ve marked all mails as unread", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure": "You’ve marked {count} mails as unread", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure": "All mails could not be marked as unread", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason": "All mails could not be marked as unread. Due \"{reason}\"", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "reason" + ], + "placeholders": { + "reason": {} + } + }, + "toastMessageMarkAsReadFolderFailureWithReason": "Folder \"{folderName}\" could not be marked as read. Due \"{reason}\"", + "@toastMessageMarkAsReadFolderFailureWithReason": { + "type": "text", + "placeholders_order": [ + "folderName", + "reason" + ], + "placeholders": { + "folderName": {}, + "reason": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsSuccess": "You’ve moved all mails to {destinationPath}", + "@toastMessageMoveAllSelectionAllEmailsSuccess": { + "type": "text", + "placeholders_order": [ + "destinationPath" + ], + "placeholders": { + "destinationPath": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure": "You’ve moved {count} mails to {destinationPath}", + "@toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure": { + "type": "text", + "placeholders_order": [ + "count", + "destinationPath" + ], + "placeholders": { + "count": {}, + "destinationPath": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsAllFailure": "All mails could not be moved to {destinationPath}", + "@toastMessageMoveAllSelectionAllEmailsAllFailure": { + "type": "text", + "placeholders_order": [ + "destinationPath" + ], + "placeholders": { + "destinationPath": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsFailureWithReason": "All mails could not be moved to {destinationPath}. Due \"{reason}\"", + "@toastMessageMoveAllSelectionAllEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "destinationPath", + "reason" + ], + "placeholders": { + "destinationPath": {}, + "reason": {} + } + }, + "moveAll": "Move all", + "@moveAll": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "moveAllToTrash": "Move all to trash", + "@moveAllToTrash": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "deleteAllPermanently": "Delete all permanently", + "@deleteAllPermanently": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageDeleteAllPermanentlyEmailsFailureWithReason": "All mails could not be delete forever. Due \"{reason}\"", + "@toastMessageDeleteAllPermanentlyEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "reason" + ], + "placeholders": { + "reason": {} + } + }, + "allStarred": "All starred", + "@allStarred": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsSuccess": "You’ve marked all mails as starred", + "@toastMessageMarkAllAsStarredSelectionAllEmailsSuccess": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure": "You’ve marked {count} mails as starred", + "@toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure": "All mails could not be marked as starred", + "@toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason": "All mails could not be marked as starred. Due \"{reason}\"", + "@toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "reason" + ], + "placeholders": { + "reason": {} + } + }, + "allUnSpam": "All UnSpam", + "@allUnSpam": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "markAllAsSpam": "Mark all as spam", + "@markAllAsSpam": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "moveAllConversation": "Move all conversation", + "@moveAllConversation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index b220c3e363..fa0fe97dbe 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -50,7 +50,10 @@ class CoreBindings extends Bindings { void _bindingToast() { Get.put(AppToast()); - Get.put(ToastManager(Get.find())); + Get.put(ToastManager( + Get.find(), + Get.find(), + )); } void _bindingDeviceManager() { diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 6984007288..e4fc2408cf 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4416,4 +4416,207 @@ class AppLocalizations { ); } -} + String mailsOnThisPageAreSelected(int countEmails) { + return Intl.message( + 'All $countEmails mails on this page are selected.', + name: 'mailsOnThisPageAreSelected', + args: [countEmails] + ); + } + + String selectAllMailInMailbox(int total, String folderName) { + return Intl.message( + 'Select all $total mails in $folderName', + name: 'selectAllMailInMailbox', + args: [total, folderName] + ); + } + + String mailsInMailboxAreSelected(int totalEmails, String folderName) { + return Intl.message( + 'All $totalEmails mails in $folderName are selected.', + name: 'mailsInMailboxAreSelected', + args: [totalEmails, folderName] + ); + } + + String get clearSelection { + return Intl.message( + 'Clear selection', + name: 'clearSelection', + ); + } + + String get confirmBulkAction { + return Intl.message( + 'Confirm bulk action', + name: 'confirmBulkAction'); + } + + String get ok { + return Intl.message( + 'OK', + name: 'ok'); + } + + String messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox(int total, String folderName) { + return Intl.message( + 'This action will affect all $total mails in $folderName. Are you sure you want to continue?', + name: 'messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox', + args: [total, folderName] + ); + } + + String get markAllAsUnread { + return Intl.message( + 'Mark all as unread', + name: 'markAllAsUnread', + ); + } + + String get toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess { + return Intl.message( + 'You’ve marked all mails as unread', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess'); + } + + String toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure(int count) { + return Intl.message( + 'You’ve marked $count mails as unread', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure', + args: [count]); + } + + String get toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure { + return Intl.message( + 'All mails could not be marked as unread', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure'); + } + + String toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason(String reason) { + return Intl.message( + 'All mails could not be marked as unread. Due "$reason"', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason', + args: [reason] + ); + } + + String toastMessageMarkAsReadFolderFailureWithReason(String folderName, String reason) { + return Intl.message( + 'Folder "$folderName" could not be marked as read. Due "$reason"', + name: 'toastMessageMarkAsReadFolderFailureWithReason', + args: [folderName, reason] + ); + } + + String toastMessageMoveAllSelectionAllEmailsSuccess(String destinationPath) { + return Intl.message( + 'You’ve moved all mails to $destinationPath', + name: 'toastMessageMoveAllSelectionAllEmailsSuccess', + args: [destinationPath]); + } + + String toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure(int count, String destinationPath) { + return Intl.message( + 'You’ve moved $count mails to $destinationPath', + name: 'toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure', + args: [count, destinationPath]); + } + + String toastMessageMoveAllSelectionAllEmailsAllFailure(String destinationPath) { + return Intl.message( + 'All mails could not be moved to $destinationPath', + name: 'toastMessageMoveAllSelectionAllEmailsAllFailure', + args: [destinationPath]); + } + + String toastMessageMoveAllSelectionAllEmailsFailureWithReason(String destinationPath, String reason) { + return Intl.message( + 'All mails could not be moved to $destinationPath. Due "$reason"', + name: 'toastMessageMoveAllSelectionAllEmailsFailureWithReason', + args: [destinationPath, reason] + ); + } + + String get moveAll { + return Intl.message( + 'Move all', + name: 'moveAll'); + } + + String get moveAllToTrash { + return Intl.message( + 'Move all to trash', + name: 'moveAllToTrash', + ); + } + + String get deleteAllPermanently { + return Intl.message( + 'Delete all permanently', + name: 'deleteAllPermanently'); + } + + String toastMessageDeleteAllPermanentlyEmailsFailureWithReason(String reason) { + return Intl.message( + 'All mails could not be delete forever. Due "$reason"', + name: 'toastMessageDeleteAllPermanentlyEmailsFailureWithReason', + args: [reason] + ); + } + + String get allStarred { + return Intl.message( + 'All starred', + name: 'allStarred', + ); + } + + String get toastMessageMarkAllAsStarredSelectionAllEmailsSuccess { + return Intl.message( + 'You’ve marked all mails as starred', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsSuccess'); + } + + String toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure(int count) { + return Intl.message( + 'You’ve marked $count mails as starred', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure', + args: [count]); + } + + String get toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure { + return Intl.message( + 'All mails could not be marked as starred', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure'); + } + + String toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason(String reason) { + return Intl.message( + 'All mails could not be marked as starred. Due "$reason"', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason', + args: [reason] + ); + } + + String get allUnSpam { + return Intl.message( + 'All UnSpam', + name: 'allUnSpam', + ); + } + + String get markAllAsSpam { + return Intl.message( + 'Mark all as spam', + name: 'markAllAsSpam', + ); + } + + String get moveAllConversation { + return Intl.message( + 'Move all conversation', + name: 'moveAllConversation', + ); + } +} \ No newline at end of file diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index 595b20f6a4..d6186e9d57 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -1,4 +1,6 @@ +import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/utils/app_logger.dart'; import 'package:flutter/material.dart'; @@ -9,18 +11,24 @@ import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.d 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'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ToastManager { - final AppToast appToast; + final AppToast _appToast; + final ImagePaths _imagePaths; - ToastManager(this.appToast); + ToastManager(this._appToast, this._imagePaths); String? getMessageByException(BuildContext context, dynamic exception) { if (exception is CanNotFoundBaseUrl) { @@ -84,7 +92,137 @@ class ToastManager { } if (message?.isNotEmpty == true) { - appToast.showToastErrorMessage(currentOverlayContext!, message!); + _appToast.showToastErrorMessage(currentOverlayContext!, message!); + } + } + + void showSuccessMessage({ + required BuildContext context, + required BuildContext overlayContext, + required Success success + }) { + if (success is MarkAsMailboxReadAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsMailboxReadSuccess(success.mailboxDisplayName), + leadingSVGIcon: _imagePaths.icReadToast, + ); + } else if (success is MarkAsMailboxReadHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsMailboxReadHasSomeEmailFailure(success.mailboxDisplayName, success.countEmailsRead), + leadingSVGIcon: _imagePaths.icReadToast, + ); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess, + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure(success.countEmailsUnread), + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsSuccess(success.destinationPath), + leadingSVGIconColor: Colors.white, + leadingSVGIcon: _imagePaths.icFolderMailbox, + ); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure(success.countEmailsMoved, success.destinationPath), + leadingSVGIconColor: Colors.white, + leadingSVGIcon: _imagePaths.icFolderMailbox, + ); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toast_message_empty_trash_folder_success, + ); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsSuccess, + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure(success.countStarred), + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } + } + + void showFailureMessage({ + required BuildContext context, + required BuildContext overlayContext, + required Failure failure + }) { + if (failure is MarkAsMailboxReadFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsReadFolderFailureWithReason( + failure.mailboxDisplayName, + failure.exception.toString(), + ), + ); + } else if (failure is MarkAsMailboxReadAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsReadFolderAllFailure(failure.mailboxDisplayName), + ); + } else if (failure is MarkAllAsUnreadSelectionAllEmailsFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason( + failure.exception.toString(), + ), + ); + } else if (failure is MarkAllAsUnreadSelectionAllEmailsAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure, + ); + } else if (failure is MoveAllSelectionAllEmailsFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsFailureWithReason( + failure.destinationPath, + failure.exception.toString(), + ), + ); + } else if (failure is MoveAllSelectionAllEmailsAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsAllFailure( + failure.destinationPath, + ), + ); + } else if (failure is DeleteAllPermanentlyEmailsFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageDeleteAllPermanentlyEmailsFailureWithReason( + failure.exception.toString(), + ), + ); + } else if (failure is MarkAllAsStarredSelectionAllEmailsFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason( + failure.exception.toString(), + ), + ); + } else if (failure is MarkAllAsStarredSelectionAllEmailsAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure, + ); } } } diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index a125b4c3ae..f9063818a4 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -28,5 +28,13 @@ enum EmailActionType { composeFromUnsubscribeMailtoLink, archiveMessage, printAll, - downloadMessageAsEML + downloadMessageAsEML, + markAllAsRead, + markAllAsUnread, + moveAll, + moveAllToTrash, + deleteAllPermanently, + markAllAsStarred, + markAllAsSpam, + allUnSpam, } \ No newline at end of file diff --git a/model/lib/extensions/presentation_mailbox_extension.dart b/model/lib/extensions/presentation_mailbox_extension.dart index eb58c78aa7..1775364cbd 100644 --- a/model/lib/extensions/presentation_mailbox_extension.dart +++ b/model/lib/extensions/presentation_mailbox_extension.dart @@ -30,6 +30,8 @@ extension PresentationMailboxExtension on PresentationMailbox { int get countTotalEmails => totalEmails?.value.value.toInt() ?? 0; + int get countReadEmails => countTotalEmails > countUnreadEmails ? countTotalEmails - countUnreadEmails : 0; + bool get isInbox => role == PresentationMailbox.roleInbox; bool get isSpam => role == PresentationMailbox.roleSpam || role == PresentationMailbox.roleJunk; diff --git a/model/pubspec.lock b/model/pubspec.lock index 02e72ad737..96927594ad 100644 --- a/model/pubspec.lock +++ b/model/pubspec.lock @@ -651,8 +651,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/model/pubspec.yaml b/model/pubspec.yaml index 5c0a75a1e8..60d425fd4e 100644 --- a/model/pubspec.yaml +++ b/model/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### cupertino_icons: 1.0.6 diff --git a/pubspec.lock b/pubspec.lock index 3e18616358..8ca729c8af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1315,8 +1315,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" @@ -1482,7 +1482,7 @@ packages: source: hosted version: "2.1.0" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" diff --git a/pubspec.yaml b/pubspec.yaml index 78b5717ad3..3d6cb052b2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property contacts_service: git: @@ -263,6 +263,8 @@ dependencies: flutter_smart_dialog: 4.9.8+5 + package_info_plus: 4.2.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/rule_filter/pubspec.lock b/rule_filter/pubspec.lock index 92848214ba..9002e98da8 100644 --- a/rule_filter/pubspec.lock +++ b/rule_filter/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/rule_filter/pubspec.yaml b/rule_filter/pubspec.yaml index bb8c5c7e0f..07f7cf33e2 100644 --- a/rule_filter/pubspec.yaml +++ b/rule_filter/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/server_settings/pubspec.lock b/server_settings/pubspec.lock index fbf8947c30..ea7d962b2d 100644 --- a/server_settings/pubspec.lock +++ b/server_settings/pubspec.lock @@ -287,8 +287,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/server_settings/pubspec.yaml b/server_settings/pubspec.yaml index 4e70d55723..c16a05d1dd 100644 --- a/server_settings/pubspec.yaml +++ b/server_settings/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 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 b890e6126c..914cd2f164 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 @@ -79,13 +79,17 @@ import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants. import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; @@ -178,6 +182,10 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), ]) void main() { // mock mailbox dashboard controller direct dependencies @@ -263,6 +271,10 @@ void main() { final refreshAllMailboxInteractor = MockRefreshAllMailboxInteractor(); final removeComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); + final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); + final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); + final deleteAllPermanentlyEmailsInteractor = MockDeleteAllPermanentlyEmailsInteractor(); + final markAllAsStarredSelectionAllEmailsInteractor = MockMarkAllAsStarredSelectionAllEmailsInteractor(); late MailboxController mailboxController; // mock thread controller direct dependencies @@ -314,6 +326,10 @@ void main() { Get.put(updateAccountCacheInteractor); Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor); + Get.put(markAllAsUnreadSelectionAllEmailsInteractor); + Get.put(moveAllSelectionAllEmailsInteractor); + Get.put(deleteAllPermanentlyEmailsInteractor); + Get.put(markAllAsStarredSelectionAllEmailsInteractor); searchController = SearchController( quickSearchEmailInteractor, @@ -349,6 +365,10 @@ void main() { getRestoredDeletedMessageInteractor, removeComposerCacheOnWebInteractor, getAllIdentitiesInteractor, + markAllAsUnreadSelectionAllEmailsInteractor, + moveAllSelectionAllEmailsInteractor, + deleteAllPermanentlyEmailsInteractor, + markAllAsStarredSelectionAllEmailsInteractor, ); }); 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 a090c81866..e24b0962c2 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 @@ -77,13 +77,17 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_send import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; @@ -180,6 +184,10 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), ]) void main() { final moveToMailboxInteractor = MockMoveToMailboxInteractor(); @@ -250,6 +258,10 @@ void main() { final refreshAllMailboxInteractor = MockRefreshAllMailboxInteractor(); final removeComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); + final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); + final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); + final deleteAllPermanentlyEmailsInteractor = MockDeleteAllPermanentlyEmailsInteractor(); + final markAllAsStarredSelectionAllEmailsInteractor = MockMarkAllAsStarredSelectionAllEmailsInteractor(); final getEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); final refreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); @@ -312,6 +324,10 @@ void main() { Get.put(updateAccountCacheInteractor); Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor); + Get.put(markAllAsUnreadSelectionAllEmailsInteractor); + Get.put(moveAllSelectionAllEmailsInteractor); + Get.put(deleteAllPermanentlyEmailsInteractor); + Get.put(markAllAsStarredSelectionAllEmailsInteractor); when(emailReceiveManager.pendingSharedFileInfo).thenAnswer((_) => BehaviorSubject.seeded([])); @@ -348,6 +364,10 @@ void main() { getRestoredDeletedMessageInteractor, removeComposerCacheOnWebInteractor, getAllIdentitiesInteractor, + markAllAsUnreadSelectionAllEmailsInteractor, + moveAllSelectionAllEmailsInteractor, + deleteAllPermanentlyEmailsInteractor, + markAllAsStarredSelectionAllEmailsInteractor, ); Get.put(mailboxDashboardController); mailboxDashboardController.onReady(); diff --git a/test/features/thread/presentation/controller/thread_controller_test.dart b/test/features/thread/presentation/controller/thread_controller_test.dart index 38c823642d..f9a4b62231 100644 --- a/test/features/thread/presentation/controller/thread_controller_test.dart +++ b/test/features/thread/presentation/controller/thread_controller_test.dart @@ -376,6 +376,8 @@ void main() { when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all)); when(mockMailboxDashBoardController.currentSelectMode).thenReturn(Rx(SelectMode.INACTIVE)); when(mockMailboxDashBoardController.listEmailSelected).thenReturn(RxList([])); + when(mockMailboxDashBoardController.isSelectAllEmailsEnabled).thenReturn(RxBool(false)); + when(mockMailboxDashBoardController.isSelectAllPageEnabled).thenReturn(RxBool(false)); when(mockSearchController.searchState).thenReturn(Rx(SearchState.initial())); when(mockSearchController.isAdvancedSearchViewOpen).thenReturn(RxBool(false)); when(mockSearchController.isSearchEmailRunning).thenReturn(true);