From b95ec24b3c1cae690648760fcc89422056b130ff Mon Sep 17 00:00:00 2001 From: DatDang Date: Thu, 2 Jan 2025 14:27:26 +0700 Subject: [PATCH] TF-3385 Update move emails --- .../domain/state/move_to_mailbox_state.dart | 6 + .../usecases/move_to_mailbox_interactor.dart | 10 +- .../controller/single_email_controller.dart | 76 ++++++++--- .../presentation/mailbox_controller.dart | 61 +++++++++ .../mailbox_dashboard_controller.dart | 118 ++++++++++++++---- .../move_emails_to_mailbox_extension.dart | 26 ++++ ..._emails_with_new_mailbox_id_extension.dart | 30 +++++ .../presentation/search_email_controller.dart | 18 +++ .../move_multiple_email_to_mailbox_state.dart | 12 ++ ..._multiple_email_to_mailbox_interactor.dart | 20 ++- .../mixin/email_action_controller.dart | 61 +++++++-- .../presentation/thread_controller.dart | 18 +++ model/lib/email/presentation_email.dart | 50 ++++++++ 13 files changed, 450 insertions(+), 56 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart create mode 100644 lib/features/mailbox_dashboard/presentation/extensions/update_emails_with_new_mailbox_id_extension.dart diff --git a/lib/features/email/domain/state/move_to_mailbox_state.dart b/lib/features/email/domain/state/move_to_mailbox_state.dart index 56809a70d..f91ff1e39 100644 --- a/lib/features/email/domain/state/move_to_mailbox_state.dart +++ b/lib/features/email/domain/state/move_to_mailbox_state.dart @@ -14,6 +14,8 @@ class MoveToMailboxSuccess extends UIState { final MoveAction moveAction; final EmailActionType emailActionType; final String? destinationPath; + final Map> originalMailboxIdsWithEmailIds; + final Map emailIdsWithReadStatus; MoveToMailboxSuccess( this.emailId, @@ -23,6 +25,8 @@ class MoveToMailboxSuccess extends UIState { this.emailActionType, { this.destinationPath, + required this.originalMailboxIdsWithEmailIds, + required this.emailIdsWithReadStatus, } ); @@ -34,6 +38,8 @@ class MoveToMailboxSuccess extends UIState { moveAction, emailActionType, destinationPath, + originalMailboxIdsWithEmailIds, + emailIdsWithReadStatus, ]; } diff --git a/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart b/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart index 0303c3648..0a92e06f9 100644 --- a/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart +++ b/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart @@ -3,6 +3,7 @@ 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/email/email.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; @@ -12,7 +13,12 @@ class MoveToMailboxInteractor { MoveToMailboxInteractor(this._emailRepository); - Stream> execute(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) async* { + Stream> execute( + Session session, + AccountId accountId, + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, + ) async* { try { yield Right(LoadingMoveToMailbox()); final result = await _emailRepository.moveToMailbox(session, accountId, moveRequest); @@ -24,6 +30,8 @@ class MoveToMailboxInteractor { moveRequest.moveAction, moveRequest.emailActionType, destinationPath: moveRequest.destinationPath, + originalMailboxIdsWithEmailIds: moveRequest.currentMailboxes, + emailIdsWithReadStatus: emailIdsWithReadStatus, )); } else { yield Left(MoveToMailboxFailure(moveRequest.emailActionType)); diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 4b06b8801..2c9868b08 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -942,7 +942,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { {currentMailbox.id: [emailSelected.id!]}, destinationMailbox.id, MoveAction.moving, - EmailActionType.moveToTrash)); + EmailActionType.moveToTrash), + {emailSelected.id!: emailSelected.hasRead}); } else if (destinationMailbox.isSpam) { _moveToSpamAction( context, @@ -952,7 +953,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { {currentMailbox.id: [emailSelected.id!]}, destinationMailbox.id, MoveAction.moving, - EmailActionType.moveToSpam)); + EmailActionType.moveToSpam), + {emailSelected.id!: emailSelected.hasRead}); } else { _moveToMailbox( context, @@ -963,13 +965,25 @@ class SingleEmailController extends BaseController with AppLoaderMixin { destinationMailbox.id, MoveAction.moving, EmailActionType.moveToMailbox, - destinationPath: destinationMailbox.mailboxPath)); + destinationPath: destinationMailbox.mailboxPath), + {emailSelected.id!: emailSelected.hasRead}); } } - void _moveToMailbox(BuildContext context, Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { + void _moveToMailbox( + BuildContext context, + Session session, + AccountId accountId, + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, + ) { closeEmailView(context: context); - consumeState(_moveToMailboxInteractor.execute(session, accountId, moveRequest)); + consumeState(_moveToMailboxInteractor.execute( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + )); } void _moveToMailboxSuccess(MoveToMailboxSuccess success) { @@ -981,10 +995,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin { actionName: AppLocalizations.of(currentContext!).undo, onActionClick: () { _revertedToOriginalMailbox(MoveToMailboxRequest( - {success.destinationMailboxId: [success.emailId]}, - success.currentMailboxId, - MoveAction.undo, - success.emailActionType)); + {success.destinationMailboxId: [success.emailId]}, + success.currentMailboxId, + MoveAction.undo, + success.emailActionType), + success.emailIdsWithReadStatus, + ); }, leadingSVGIcon: imagePaths.icFolderMailbox, leadingSVGIconColor: Colors.white, @@ -995,9 +1011,18 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } - void _revertedToOriginalMailbox(MoveToMailboxRequest newMoveRequest) { + void _revertedToOriginalMailbox( + MoveToMailboxRequest newMoveRequest, + Map emailIdsWithReadStatus, + ) { if (accountId != null && session != null) { - _moveToMailbox(currentContext!, session!, accountId!, newMoveRequest); + _moveToMailbox( + currentContext!, + session!, + accountId!, + newMoveRequest, + emailIdsWithReadStatus, + ); } } @@ -1014,7 +1039,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { {currentMailbox.id: [email.id!]}, trashMailboxId, MoveAction.moving, - EmailActionType.moveToTrash) + EmailActionType.moveToTrash), + {email.id!: email.hasRead}, ); } } @@ -1023,10 +1049,16 @@ class SingleEmailController extends BaseController with AppLoaderMixin { BuildContext context, Session session, AccountId accountId, - MoveToMailboxRequest moveRequest + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, ) { closeEmailView(context: context); - mailboxDashBoardController.moveToMailbox(session, accountId, moveRequest); + mailboxDashBoardController.moveToMailbox( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + ); } void moveToSpam(BuildContext context, PresentationEmail email) { @@ -1042,7 +1074,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { {currentMailbox.id: [email.id!]}, spamMailboxId, MoveAction.moving, - EmailActionType.moveToSpam) + EmailActionType.moveToSpam), + {email.id!: email.hasRead}, ); } } @@ -1060,7 +1093,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { {spamMailboxId: [email.id!]}, inboxMailboxId, MoveAction.moving, - EmailActionType.unSpam) + EmailActionType.unSpam), + {email.id!: email.hasRead}, ); } } @@ -1069,10 +1103,16 @@ class SingleEmailController extends BaseController with AppLoaderMixin { BuildContext context, Session session, AccountId accountId, - MoveToMailboxRequest moveRequest + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, ) { closeEmailView(context: context); - mailboxDashBoardController.moveToMailbox(session, accountId, moveRequest); + mailboxDashBoardController.moveToMailbox( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + ); } void markAsStarEmail( diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 4bc677cbc..ad8ff7577 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -25,6 +25,7 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanent import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_restored_deleted_message_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; @@ -80,6 +81,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/search_query.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_as_multiple_email_read_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'; import 'package:tmail_ui_user/main/routes/dialog_router.dart'; @@ -351,6 +353,24 @@ class MailboxController extends BaseMailboxController affectedMailboxId: reactionState.mailboxId, totalEmailsChanged: -reactionState.emailIds.length, ); + } else if (reactionState is MoveToMailboxSuccess) { + _handleMoveEmailsToMailbox( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + emailIdsWithReadStatus: reactionState.emailIdsWithReadStatus, + ); + } else if (reactionState is MoveMultipleEmailToMailboxAllSuccess) { + _handleMoveEmailsToMailbox( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + emailIdsWithReadStatus: reactionState.emailIdsWithReadStatus, + ); + } else if (reactionState is MoveMultipleEmailToMailboxHasSomeEmailFailure) { + _handleMoveEmailsToMailbox( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithMoveSucceededEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + emailIdsWithReadStatus: reactionState.moveSucceededEmailIdsWithReadStatus, + ); } }); } @@ -400,6 +420,47 @@ class MailboxController extends BaseMailboxController ); } + void _handleMoveEmailsToMailbox({ + required Map> originalMailboxIdsWithEmailIds, + required MailboxId destinationMailboxId, + required Map emailIdsWithReadStatus, + }) { + // Update changes in original mailboxes + for (var originalMailboxIdWithEmailIds in originalMailboxIdsWithEmailIds.entries) { + final originalMailboxId = originalMailboxIdWithEmailIds.key; + final emailsMovedCount = originalMailboxIdWithEmailIds.value.length; + final unreadEmailMovedCount = originalMailboxIdWithEmailIds.value + .where((emailId) => emailIdsWithReadStatus[emailId] == false) + .length; + updateMailboxTotalEmailsCountById( + originalMailboxId, + -emailsMovedCount, + ); + updateUnreadCountOfMailboxById( + originalMailboxId, + unreadChanges: -unreadEmailMovedCount, + ); + } + + // Update changes in destination mailbox + updateMailboxTotalEmailsCountById( + destinationMailboxId, + originalMailboxIdsWithEmailIds.entries.fold( + 0, + (sum, entry) => sum + entry.value.length, + ), + ); + updateUnreadCountOfMailboxById( + destinationMailboxId, + unreadChanges: originalMailboxIdsWithEmailIds + .values + .fold( + 0, + (sum, emails) => sum + emails.where((emailId) => emailIdsWithReadStatus[emailId] == false).length + ), + ); + } + void _initWebSocketQueueHandler() { _webSocketQueueHandler = WebSocketQueueHandler( processMessageCallback: _handleWebSocketMessage, 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 b9a89e655..25db5e08d 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -820,8 +820,18 @@ class MailboxDashBoardController extends ReloadableController } } - void moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { - consumeState(_moveToMailboxInteractor.execute(session, accountId, moveRequest)); + void moveToMailbox( + Session session, + AccountId accountId, + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, + ) { + consumeState(_moveToMailboxInteractor.execute( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + )); } void _moveToMailboxSuccess(MoveToMailboxSuccess success) { @@ -836,7 +846,7 @@ class MailboxDashBoardController extends ReloadableController success.currentMailboxId, MoveAction.undo, success.emailActionType - )); + ), success.emailIdsWithReadStatus); }, leadingSVGIcon: imagePaths.icFolderMailbox, leadingSVGIconColor: Colors.white, @@ -846,11 +856,19 @@ class MailboxDashBoardController extends ReloadableController } } - void _revertedToOriginalMailbox(MoveToMailboxRequest newMoveRequest) { + void _revertedToOriginalMailbox( + MoveToMailboxRequest newMoveRequest, + Map emailIdsWithReadStatus, + ) { final currentAccountId = accountId.value; final session = sessionCurrent; if (currentAccountId != null && session != null) { - consumeState(_moveToMailboxInteractor.execute(session, currentAccountId, newMoveRequest)); + consumeState(_moveToMailboxInteractor.execute( + session, + currentAccountId, + newMoveRequest, + emailIdsWithReadStatus, + )); } } @@ -1048,7 +1066,12 @@ class MailboxDashBoardController extends ReloadableController sessionCurrent!, listEmails.listEmailIds, currentMailbox, - destinationMailbox + destinationMailbox, + Map.fromEntries( + listEmails + .where((email) => email.id != null) + .map((email) => MapEntry(email.id!, email.hasRead)), + ), ); } } @@ -1059,7 +1082,8 @@ class MailboxDashBoardController extends ReloadableController Session session, List listEmailIds, PresentationMailbox currentMailbox, - PresentationMailbox destinationMailbox + PresentationMailbox destinationMailbox, + Map emailIdsWithReadStatus, ) { if (destinationMailbox.isTrash) { _moveSelectedEmailMultipleToMailboxAction( @@ -1069,7 +1093,8 @@ class MailboxDashBoardController extends ReloadableController {currentMailbox.id: listEmailIds}, destinationMailbox.id, MoveAction.moving, - EmailActionType.moveToTrash)); + EmailActionType.moveToTrash), + emailIdsWithReadStatus); } else if (destinationMailbox.isSpam) { _moveSelectedEmailMultipleToMailboxAction( session, @@ -1078,7 +1103,8 @@ class MailboxDashBoardController extends ReloadableController {currentMailbox.id: listEmailIds}, destinationMailbox.id, MoveAction.moving, - EmailActionType.moveToSpam)); + EmailActionType.moveToSpam), + emailIdsWithReadStatus); } else { _moveSelectedEmailMultipleToMailboxAction( session, @@ -1088,7 +1114,8 @@ class MailboxDashBoardController extends ReloadableController destinationMailbox.id, MoveAction.moving, EmailActionType.moveToMailbox, - destinationPath: destinationMailbox.mailboxPath)); + destinationPath: destinationMailbox.mailboxPath), + emailIdsWithReadStatus); } } @@ -1096,6 +1123,10 @@ class MailboxDashBoardController extends ReloadableController List listEmails, PresentationMailbox destinationMailbox, ) { + final emailIdsWithReadStatus = Map.fromEntries(listEmails + .where((email) => email.id != null) + .map((e) => MapEntry(e.id!, e.hasRead)) + ); if (searchController.isSearchEmailRunning){ final Map> mapListEmailSelectedByMailBoxId = {}; for (var element in listEmails) { @@ -1108,11 +1139,17 @@ class MailboxDashBoardController extends ReloadableController } } } - _handleDragSelectedMultipleEmailToMailboxAction(mapListEmailSelectedByMailBoxId, destinationMailbox); - } else { - if (selectedMailbox.value != null) { - _handleDragSelectedMultipleEmailToMailboxAction({selectedMailbox.value!.id: listEmails.listEmailIds}, destinationMailbox); - } + _handleDragSelectedMultipleEmailToMailboxAction( + mapListEmailSelectedByMailBoxId, + destinationMailbox, + emailIdsWithReadStatus, + ); + } else if (selectedMailbox.value != null) { + _handleDragSelectedMultipleEmailToMailboxAction( + {selectedMailbox.value!.id: listEmails.listEmailIds}, + destinationMailbox, + emailIdsWithReadStatus, + ); } } @@ -1120,6 +1157,7 @@ class MailboxDashBoardController extends ReloadableController void _handleDragSelectedMultipleEmailToMailboxAction( Map> mapListEmails, PresentationMailbox destinationMailbox, + Map emailIdsWithReadStatus, ) async { if (accountId.value != null && sessionCurrent != null) { if (destinationMailbox.isTrash) { @@ -1132,6 +1170,7 @@ class MailboxDashBoardController extends ReloadableController MoveAction.moving, EmailActionType.moveToTrash, ), + emailIdsWithReadStatus, ); } else if (destinationMailbox.isSpam) { moveToMailbox( @@ -1143,6 +1182,7 @@ class MailboxDashBoardController extends ReloadableController MoveAction.moving, EmailActionType.moveToSpam, ), + emailIdsWithReadStatus, ); } else { moveToMailbox( @@ -1155,6 +1195,7 @@ class MailboxDashBoardController extends ReloadableController EmailActionType.moveToMailbox, destinationPath: destinationMailbox.mailboxPath, ), + emailIdsWithReadStatus, ); } } @@ -1164,9 +1205,15 @@ class MailboxDashBoardController extends ReloadableController void _moveSelectedEmailMultipleToMailboxAction( Session session, AccountId accountId, - MoveToMailboxRequest moveRequest + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, ) { - consumeState(_moveMultipleEmailToMailboxInteractor.execute(session, accountId, moveRequest)); + consumeState(_moveMultipleEmailToMailboxInteractor.execute( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + )); } void _moveSelectedMultipleEmailToMailboxSuccess(Success success) { @@ -1176,6 +1223,7 @@ class MailboxDashBoardController extends ReloadableController MailboxId? destinationMailboxId; MoveAction? moveAction; EmailActionType? emailActionType; + Map? emailIdsWithReadStatus; if (success is MoveMultipleEmailToMailboxAllSuccess) { destinationPath = success.destinationPath; @@ -1184,6 +1232,7 @@ class MailboxDashBoardController extends ReloadableController destinationMailboxId = success.destinationMailboxId; moveAction = success.moveAction; emailActionType = success.emailActionType; + emailIdsWithReadStatus = success.emailIdsWithReadStatus; } else if (success is MoveMultipleEmailToMailboxHasSomeEmailFailure) { destinationPath = success.destinationPath; movedEmailIds = success.movedListEmailId; @@ -1191,6 +1240,7 @@ class MailboxDashBoardController extends ReloadableController destinationMailboxId = success.destinationMailboxId; moveAction = success.moveAction; emailActionType = success.emailActionType; + emailIdsWithReadStatus = success.moveSucceededEmailIdsWithReadStatus; } if (currentContext != null && @@ -1213,7 +1263,7 @@ class MailboxDashBoardController extends ReloadableController MoveAction.undo, emailActionType!, destinationPath: destinationPath - )); + ), emailIdsWithReadStatus ?? {}); } }, leadingSVGIconColor: Colors.white, @@ -1225,12 +1275,16 @@ class MailboxDashBoardController extends ReloadableController } } - void _revertedSelectionEmailToOriginalMailbox(MoveToMailboxRequest newMoveRequest) { + void _revertedSelectionEmailToOriginalMailbox( + MoveToMailboxRequest newMoveRequest, + Map emailIdsWithReadStatus, + ) { if (accountId.value != null && sessionCurrent != null) { consumeState(_moveMultipleEmailToMailboxInteractor.execute( sessionCurrent!, accountId.value!, - newMoveRequest)); + newMoveRequest, + emailIdsWithReadStatus)); } } @@ -1244,7 +1298,12 @@ class MailboxDashBoardController extends ReloadableController {mailboxCurrent.id: listEmails.listEmailIds}, trashMailboxId, MoveAction.moving, - EmailActionType.moveToTrash) + EmailActionType.moveToTrash), + Map.fromIterable( + listEmails + .where((email) => email.id != null) + .map((email) => MapEntry(email.id!, email.hasRead)), + ), ); } } @@ -1279,7 +1338,12 @@ class MailboxDashBoardController extends ReloadableController {mailboxCurrent.id: listEmail.listEmailIds}, spamMailboxId!, MoveAction.moving, - EmailActionType.moveToSpam) + EmailActionType.moveToSpam), + Map.fromIterable( + listEmail + .where((email) => email.id != null) + .map((email) => MapEntry(email.id!, email.hasRead)), + ), ); } @@ -1325,7 +1389,12 @@ class MailboxDashBoardController extends ReloadableController {spamMailboxId!: listEmail.listEmailIds}, inboxMailboxId, MoveAction.moving, - EmailActionType.unSpam) + EmailActionType.unSpam), + Map.fromIterable( + listEmail + .where((email) => email.id != null) + .map((email) => MapEntry(email.id!, email.hasRead)), + ), ); } @@ -2793,7 +2862,8 @@ class MailboxDashBoardController extends ReloadableController moveToMailbox( sessionCurrent!, accountId.value!, - moveToArchiveMailboxRequest + moveToArchiveMailboxRequest, + {email.id!: email.hasRead} ); } } diff --git a/lib/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart new file mode 100644 index 000000000..e0e4e6d9c --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart @@ -0,0 +1,26 @@ +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; + +extension MoveEmailsToMailboxExtension on MailboxDashBoardController { + void handleMoveEmailsToMailbox({ + required Map> originalMailboxIdsWithEmailIds, + required MailboxId destinationMailboxId, + }) { + if (destinationMailboxId == selectedMailbox.value?.id) return; + + final currentEmails = List.from( + emailsInCurrentMailbox, + ); + final movedEmailIds = originalMailboxIdsWithEmailIds.entries.fold( + {}, + (emailIds, entry) { + emailIds.addAll(entry.value); + return emailIds; + }, + ).toList(); + currentEmails.removeWhere((email) => movedEmailIds.contains(email.id)); + updateEmailList(currentEmails); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/extensions/update_emails_with_new_mailbox_id_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/update_emails_with_new_mailbox_id_extension.dart new file mode 100644 index 000000000..a94af546d --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/update_emails_with_new_mailbox_id_extension.dart @@ -0,0 +1,30 @@ +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; + +extension UpdateEmailsWithNewMailboxIdExtension on MailboxDashBoardController { + handleUpdateEmailsWithNewMailboxId({ + required Map> originalMailboxIdsWithEmailIds, + required MailboxId destinationMailboxId, + }) { + final currentEmails = List.from( + emailsInCurrentMailbox, + ); + final movedEmailIds = originalMailboxIdsWithEmailIds.entries.fold( + {}, + (emailIds, entry) { + emailIds.addAll(entry.value); + return emailIds; + }, + ).toList(); + for (var email in currentEmails) { + if (!movedEmailIds.contains(email.id)) continue; + + email = email.copyWith( + mailboxIds: {destinationMailboxId: true}, + mailboxContain: mapMailboxById[destinationMailboxId], + ); + } + } +} \ No newline at end of file diff --git a/lib/features/search/email/presentation/search_email_controller.dart b/lib/features/search/email/presentation/search_email_controller.dart index edf34f0a0..674876967 100644 --- a/lib/features/search/email/presentation/search_email_controller.dart +++ b/lib/features/search/email/presentation/search_email_controller.dart @@ -37,6 +37,7 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanent import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; @@ -50,6 +51,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/delete_emails_in_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_emails_with_new_mailbox_id_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; @@ -70,6 +72,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_sta import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_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_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_more_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; @@ -328,6 +331,21 @@ class SearchEmailController extends BaseController emailIds: reactionState.emailIds, affectedMailboxId: reactionState.mailboxId, ); + } else if (reactionState is MoveToMailboxSuccess) { + mailboxDashBoardController.handleUpdateEmailsWithNewMailboxId( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + ); + } else if (reactionState is MoveMultipleEmailToMailboxAllSuccess) { + mailboxDashBoardController.handleUpdateEmailsWithNewMailboxId( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + ); + } else if (reactionState is MoveMultipleEmailToMailboxHasSomeEmailFailure) { + mailboxDashBoardController.handleUpdateEmailsWithNewMailboxId( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithMoveSucceededEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + ); } }); } diff --git a/lib/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart b/lib/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart index b4f179a33..8d2ce732a 100644 --- a/lib/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart +++ b/lib/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart @@ -14,6 +14,8 @@ class MoveMultipleEmailToMailboxAllSuccess extends UIState { final MoveAction moveAction; final EmailActionType emailActionType; final String? destinationPath; + final Map> originalMailboxIdsWithEmailIds; + final Map emailIdsWithReadStatus; MoveMultipleEmailToMailboxAllSuccess( this.movedListEmailId, @@ -23,6 +25,8 @@ class MoveMultipleEmailToMailboxAllSuccess extends UIState { this.emailActionType, { this.destinationPath, + required this.originalMailboxIdsWithEmailIds, + required this.emailIdsWithReadStatus, } ); @@ -34,6 +38,8 @@ class MoveMultipleEmailToMailboxAllSuccess extends UIState { moveAction, emailActionType, destinationPath, + originalMailboxIdsWithEmailIds, + emailIdsWithReadStatus, ]; } @@ -54,6 +60,8 @@ class MoveMultipleEmailToMailboxHasSomeEmailFailure extends UIState { final MoveAction moveAction; final EmailActionType emailActionType; final String? destinationPath; + final Map> originalMailboxIdsWithMoveSucceededEmailIds; + final Map moveSucceededEmailIdsWithReadStatus; MoveMultipleEmailToMailboxHasSomeEmailFailure( this.movedListEmailId, @@ -63,6 +71,8 @@ class MoveMultipleEmailToMailboxHasSomeEmailFailure extends UIState { this.emailActionType, { this.destinationPath, + required this.originalMailboxIdsWithMoveSucceededEmailIds, + required this.moveSucceededEmailIdsWithReadStatus, } ); @@ -74,6 +84,8 @@ class MoveMultipleEmailToMailboxHasSomeEmailFailure extends UIState { moveAction, emailActionType, destinationPath, + originalMailboxIdsWithMoveSucceededEmailIds, + moveSucceededEmailIdsWithReadStatus, ]; } diff --git a/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart b/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart index f7481c5b6..4ba72049f 100644 --- a/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart +++ b/lib/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart @@ -1,10 +1,13 @@ import 'dart:async'; +import 'package:core/presentation/extensions/map_extensions.dart'; 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/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; @@ -17,7 +20,8 @@ class MoveMultipleEmailToMailboxInteractor { Stream> execute( Session session, AccountId accountId, - MoveToMailboxRequest moveRequest + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, ) async* { try { yield Right(LoadingMoveMultipleEmailToMailboxAll()); @@ -30,10 +34,22 @@ class MoveMultipleEmailToMailboxInteractor { moveRequest.moveAction, moveRequest.emailActionType, destinationPath: moveRequest.destinationPath, + originalMailboxIdsWithEmailIds: moveRequest.currentMailboxes, + emailIdsWithReadStatus: emailIdsWithReadStatus, )); } else if (result.emailIdsSuccess.isEmpty) { yield Left(MoveMultipleEmailToMailboxAllFailure(moveRequest.moveAction, moveRequest.emailActionType)); } else { + final originalMailboxIdsWithEmailIds = Map>.from( + moveRequest.currentMailboxes, + ); + final originalMailboxIdsWithMoveSucceededEmailIds = originalMailboxIdsWithEmailIds + .map((key, value) => MapEntry( + key, + value.where(result.emailIdsSuccess.contains).toList() + )); + final moveSucceededEmailIdsWithReadStatus = emailIdsWithReadStatus + .where((emailId, _) => result.emailIdsSuccess.contains(emailId)); yield Right(MoveMultipleEmailToMailboxHasSomeEmailFailure( result.emailIdsSuccess, moveRequest.currentMailboxes.keys.first, @@ -41,6 +57,8 @@ class MoveMultipleEmailToMailboxInteractor { moveRequest.moveAction, moveRequest.emailActionType, destinationPath: moveRequest.destinationPath, + originalMailboxIdsWithMoveSucceededEmailIds: originalMailboxIdsWithMoveSucceededEmailIds, + moveSucceededEmailIdsWithReadStatus: moveSucceededEmailIdsWithReadStatus, )); } } catch (e) { diff --git a/lib/features/thread/presentation/mixin/email_action_controller.dart b/lib/features/thread/presentation/mixin/email_action_controller.dart index 04e2f12dd..64e4b1e72 100644 --- a/lib/features/thread/presentation/mixin/email_action_controller.dart +++ b/lib/features/thread/presentation/mixin/email_action_controller.dart @@ -11,6 +11,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/mark_star_action.dart'; import 'package:model/email/presentation_email.dart'; @@ -60,13 +61,24 @@ mixin EmailActionController { {mailboxContain.id: email.id != null ? [email.id!] : []}, trashMailboxId, MoveAction.moving, - EmailActionType.moveToTrash) + EmailActionType.moveToTrash), + email.id != null ? {email.id! : email.hasRead} : {}, ); } } - void _moveToTrashAction(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { - mailboxDashBoardController.moveToMailbox(session, accountId, moveRequest); + void _moveToTrashAction( + Session session, + AccountId accountId, + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, + ) { + mailboxDashBoardController.moveToMailbox( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + ); } void moveToSpam(PresentationEmail email, {PresentationMailbox? mailboxContain}) async { @@ -82,7 +94,8 @@ mixin EmailActionController { {mailboxContain.id: email.id != null ? [email.id!] : []}, spamMailboxId, MoveAction.moving, - EmailActionType.moveToSpam) + EmailActionType.moveToSpam), + email.id != null ? {email.id! : email.hasRead} : {}, ); } } @@ -101,13 +114,24 @@ mixin EmailActionController { {spamMailboxId: email.id != null ? [email.id!] : []}, inboxMailboxId, MoveAction.moving, - EmailActionType.unSpam) + EmailActionType.unSpam), + email.id != null ? {email.id! : email.hasRead} : {}, ); } } - void moveToSpamAction(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { - mailboxDashBoardController.moveToMailbox(session, accountId, moveRequest); + void moveToSpamAction( + Session session, + AccountId accountId, + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, + ) { + mailboxDashBoardController.moveToMailbox( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + ); } void moveToMailbox( @@ -157,7 +181,8 @@ mixin EmailActionController { {currentMailbox.id: emailSelected.id != null ? [emailSelected.id!] : []}, destinationMailbox.id, MoveAction.moving, - EmailActionType.moveToTrash)); + EmailActionType.moveToTrash), + emailSelected.id != null ? {emailSelected.id! : emailSelected.hasRead} : {}); } else if (destinationMailbox.isSpam) { moveToSpamAction( session, @@ -166,7 +191,8 @@ mixin EmailActionController { {currentMailbox.id: emailSelected.id != null ? [emailSelected.id!] : []}, destinationMailbox.id, MoveAction.moving, - EmailActionType.moveToSpam)); + EmailActionType.moveToSpam), + emailSelected.id != null ? {emailSelected.id! : emailSelected.hasRead} : {}); } else { _moveToMailboxAction( session, @@ -176,12 +202,23 @@ mixin EmailActionController { destinationMailbox.id, MoveAction.moving, EmailActionType.moveToMailbox, - destinationPath: destinationMailbox.mailboxPath)); + destinationPath: destinationMailbox.mailboxPath), + emailSelected.id != null ? {emailSelected.id! : emailSelected.hasRead} : {}); } } - void _moveToMailboxAction(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { - mailboxDashBoardController.moveToMailbox(session, accountId, moveRequest); + void _moveToMailboxAction( + Session session, + AccountId accountId, + MoveToMailboxRequest moveRequest, + Map emailIdsWithReadStatus, + ) { + mailboxDashBoardController.moveToMailbox( + session, + accountId, + moveRequest, + emailIdsWithReadStatus, + ); } void deleteEmailPermanently(BuildContext context, PresentationEmail email) { diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 8008e179d..549d2d79a 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanent import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; @@ -31,6 +32,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read 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; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/delete_emails_in_mailbox_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; @@ -56,6 +58,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state import 'package:tmail_ui_user/features/thread/domain/state/load_more_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_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'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; @@ -407,6 +410,21 @@ class ThreadController extends BaseController with EmailActionController { emailIds: reactionState.emailIds, affectedMailboxId: reactionState.mailboxId, ); + } else if (reactionState is MoveToMailboxSuccess) { + mailboxDashBoardController.handleMoveEmailsToMailbox( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + ); + } else if (reactionState is MoveMultipleEmailToMailboxAllSuccess) { + mailboxDashBoardController.handleMoveEmailsToMailbox( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + ); + } else if (reactionState is MoveMultipleEmailToMailboxHasSomeEmailFailure) { + mailboxDashBoardController.handleMoveEmailsToMailbox( + originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithMoveSucceededEmailIds, + destinationMailboxId: reactionState.destinationMailboxId, + ); } }); } diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index 1989247b8..d3ec44432 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -176,4 +176,54 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin { searchSnippetSubject, searchSnippetPreview, ]; + + PresentationEmail copyWith({ + EmailId? id, + Id? blobId, + Map? keywords, + UnsignedInt? size, + UTCDate? receivedAt, + bool? hasAttachment, + String? preview, + String? subject, + UTCDate? sentAt, + Set? from, + Set? to, + Set? cc, + Set? bcc, + Set? replyTo, + Map? mailboxIds, + SelectMode? selectMode, + Uri? routeWeb, + PresentationMailbox? mailboxContain, + List? emailHeader, + Set? htmlBody, + Map? bodyValues, + Map? headerCalendarEvent, + }) { + return PresentationEmail( + id: id ?? this.id, + blobId: blobId ?? this.blobId, + keywords: keywords ?? this.keywords, + size: size ?? this.size, + receivedAt: receivedAt ?? this.receivedAt, + hasAttachment: hasAttachment ?? this.hasAttachment, + preview: preview ?? this.preview, + subject: subject ?? this.subject, + sentAt: sentAt ?? this.sentAt, + from: from ?? this.from, + to: to ?? this.to, + cc: cc ?? this.cc, + bcc: bcc ?? this.bcc, + replyTo: replyTo ?? this.replyTo, + mailboxIds: mailboxIds ?? this.mailboxIds, + selectMode: selectMode ?? this.selectMode, + routeWeb: routeWeb ?? this.routeWeb, + mailboxContain: mailboxContain ?? this.mailboxContain, + emailHeader: emailHeader ?? this.emailHeader, + htmlBody: htmlBody ?? this.htmlBody, + bodyValues: bodyValues ?? this.bodyValues, + headerCalendarEvent: headerCalendarEvent ?? this.headerCalendarEvent, + ); + } } \ No newline at end of file