diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart index e72f03f6d..62ba44dc2 100644 --- a/lib/features/base/base_mailbox_controller.dart +++ b/lib/features/base/base_mailbox_controller.dart @@ -568,4 +568,41 @@ abstract class BaseMailboxController extends BaseController { } } } + + void updateUnreadCountOfMailboxById( + MailboxId mailboxId, { + required int unreadChanges, + }) { + final mailboxTrees = [ + defaultMailboxTree, + personalMailboxTree, + teamMailboxesTree, + ]; + + for (var mailboxTree in mailboxTrees) { + if (mailboxTree.value.updateMailboxUnreadCountById(mailboxId, unreadChanges)) { + mailboxTree.refresh(); + break; + } + } + } + + void clearUnreadCount(MailboxId mailboxId) { + final mailboxTrees = [ + defaultMailboxTree, + personalMailboxTree, + teamMailboxesTree, + ]; + + for (var mailboxTree in mailboxTrees) { + final selectedNode = mailboxTree.value.findNode((node) => node.item.id == mailboxId); + if (selectedNode == null) continue; + final currentUnreadCount = selectedNode.item.unreadEmails?.value.value.toInt(); + teamMailboxesTree.value.updateMailboxUnreadCountById( + mailboxId, + -(currentUnreadCount ?? 0)); + teamMailboxesTree.refresh(); + break; + } + } } \ No newline at end of file diff --git a/lib/features/email/domain/state/mark_as_email_read_state.dart b/lib/features/email/domain/state/mark_as_email_read_state.dart index 1c327f1da..1bfb2e205 100644 --- a/lib/features/email/domain/state/mark_as_email_read_state.dart +++ b/lib/features/email/domain/state/mark_as_email_read_state.dart @@ -1,6 +1,7 @@ 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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/read_actions.dart'; import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; @@ -8,15 +9,17 @@ class MarkAsEmailReadSuccess extends UIState { final EmailId emailId; final ReadActions readActions; final MarkReadAction markReadAction; + final MailboxId? mailboxId; MarkAsEmailReadSuccess( this.emailId, this.readActions, this.markReadAction, + this.mailboxId, ); @override - List get props => [emailId, readActions, markReadAction]; + List get props => [emailId, readActions, markReadAction, mailboxId]; } class MarkAsEmailReadFailure extends FeatureFailure { diff --git a/lib/features/email/domain/state/mark_as_email_star_state.dart b/lib/features/email/domain/state/mark_as_email_star_state.dart index 3313c9525..91e87c19f 100644 --- a/lib/features/email/domain/state/mark_as_email_star_state.dart +++ b/lib/features/email/domain/state/mark_as_email_star_state.dart @@ -1,14 +1,16 @@ 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:model/email/mark_star_action.dart'; class MarkAsStarEmailSuccess extends UIState { final MarkStarAction markStarAction; + final EmailId emailId; - MarkAsStarEmailSuccess(this.markStarAction); + MarkAsStarEmailSuccess(this.markStarAction, this.emailId); @override - List get props => [markStarAction]; + List get props => [markStarAction, emailId]; } class MarkAsStarEmailFailure extends FeatureFailure { diff --git a/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart b/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart index 5f5659bc6..684289d7b 100644 --- a/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart +++ b/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart @@ -4,6 +4,7 @@ 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:model/email/read_actions.dart'; import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; @@ -20,6 +21,7 @@ class MarkAsEmailReadInteractor { EmailId emailId, ReadActions readAction, MarkReadAction markReadAction, + MailboxId? mailboxId, ) async* { try { final result = await _emailRepository.markAsRead( @@ -36,7 +38,8 @@ class MarkAsEmailReadInteractor { result.emailIdsSuccess.first, readAction, markReadAction, - )); + mailboxId, + )); } } catch (e) { yield Left(MarkAsEmailReadFailure(readAction, exception: e)); diff --git a/lib/features/email/domain/usecases/mark_as_star_email_interactor.dart b/lib/features/email/domain/usecases/mark_as_star_email_interactor.dart index aa8cc68c7..db2b9704b 100644 --- a/lib/features/email/domain/usecases/mark_as_star_email_interactor.dart +++ b/lib/features/email/domain/usecases/mark_as_star_email_interactor.dart @@ -26,7 +26,7 @@ class MarkAsStarEmailInteractor { [emailId], markStarAction, ); - yield Right(MarkAsStarEmailSuccess(markStarAction)); + yield Right(MarkAsStarEmailSuccess(markStarAction, emailId)); } catch (e) { yield Left(MarkAsStarEmailFailure(markStarAction, exception: e)); } diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index b5a703bd3..4b06b8801 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -96,6 +96,7 @@ import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.d import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/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/download/download_task_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_email_rule_filter_request.dart'; @@ -663,11 +664,18 @@ class SingleEmailController extends BaseController with AppLoaderMixin { presentationEmail.id!, readActions, markReadAction, + presentationEmail.mailboxContain?.mailboxId, )); } } void _handleMarkAsEmailReadCompleted(ReadActions readActions) { + if (_currentEmailId != null) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + [_currentEmailId!], + readAction: ReadActions.markAsRead, + ); + } if (readActions == ReadActions.markAsUnread) { closeEmailView(context: currentContext); } @@ -1083,7 +1091,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void _markAsEmailStarSuccess(MarkAsStarEmailSuccess success) { final newEmail = currentEmail?.updateKeywords({ - KeyWordIdentifier.emailFlagged: true, + KeyWordIdentifier.emailFlagged: success.markStarAction == MarkStarAction.markStar, }); mailboxDashBoardController.setSelectedEmail(newEmail); } 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 60a2df0c1..6be792a79 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 @@ -1,6 +1,7 @@ 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:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; @@ -24,8 +25,10 @@ class UpdatingMarkAsMailboxReadState extends UIState { class MarkAsMailboxReadAllSuccess extends UIActionState { final String mailboxDisplayName; + final MailboxId mailboxId; MarkAsMailboxReadAllSuccess(this.mailboxDisplayName, + this.mailboxId, { jmap.State? currentEmailState, jmap.State? currentMailboxState, @@ -35,6 +38,7 @@ class MarkAsMailboxReadAllSuccess extends UIActionState { @override List get props => [ mailboxDisplayName, + mailboxId, ...super.props ]; } @@ -43,16 +47,22 @@ class MarkAsMailboxReadHasSomeEmailFailure extends UIState { final String mailboxDisplayName; final int countEmailsRead; + final MailboxId mailboxId; + final List successEmailIds; MarkAsMailboxReadHasSomeEmailFailure( this.mailboxDisplayName, this.countEmailsRead, + this.mailboxId, + this.successEmailIds, ); @override List get props => [ mailboxDisplayName, countEmailsRead, + mailboxId, + successEmailIds, ]; } 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 074efdd51..547ec1e57 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 @@ -34,11 +34,13 @@ class MarkAsMailboxReadInteractor { onProgressController); if (totalEmailUnread == listEmails.length) { - yield Right(MarkAsMailboxReadAllSuccess(mailboxDisplayName)); + yield Right(MarkAsMailboxReadAllSuccess(mailboxDisplayName, mailboxId)); } else if (listEmails.isNotEmpty) { yield Right(MarkAsMailboxReadHasSomeEmailFailure( mailboxDisplayName, listEmails.length, + mailboxId, + listEmails, )); } else { yield Left(MarkAsMailboxReadAllFailure(mailboxDisplayName: mailboxDisplayName)); diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 837bf1612..79e462fe0 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -20,6 +20,7 @@ import 'package:tmail_ui_user/features/base/base_mailbox_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/contact_support_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/mailbox_action_handler_mixin.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_action.dart'; +import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_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'; @@ -36,6 +37,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_request.da import 'package:tmail_ui_user/features/mailbox/domain/state/create_new_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/delete_multiple_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/move_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/refresh_all_mailboxes_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/refresh_changes_all_mailboxes_state.dart'; @@ -70,6 +72,7 @@ import 'package:tmail_ui_user/features/push_notification/presentation/websocket/ import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.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/mark_as_multiple_email_read_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'; @@ -255,6 +258,71 @@ class MailboxController extends BaseMailboxController mailboxDashBoardController.clearMailboxUIAction(); } }); + + ever(mailboxDashBoardController.viewState, (viewState) { + final reactionState = viewState.getOrElse(() => UIState.idle); + if (reactionState is MarkAsEmailReadSuccess) { + _handleMarkEmailsAsReadOrUnread( + affectedMailboxId: reactionState.mailboxId, + readCount: reactionState.readActions == ReadActions.markAsRead + ? 1 + : null, + unreadCount: reactionState.readActions == ReadActions.markAsUnread + ? 1 + : null, + ); + } else if (reactionState is MarkAsMultipleEmailReadAllSuccess) { + _handleMarkEmailsAsReadOrUnread( + affectedMailboxId: reactionState.mailboxId, + readCount: reactionState.readActions == ReadActions.markAsRead + ? reactionState.emailIds.length + : null, + unreadCount: reactionState.readActions == ReadActions.markAsUnread + ? reactionState.emailIds.length + : null, + ); + } else if (reactionState is MarkAsMultipleEmailReadHasSomeEmailFailure) { + _handleMarkEmailsAsReadOrUnread( + affectedMailboxId: reactionState.mailboxId, + readCount: reactionState.readActions == ReadActions.markAsRead + ? reactionState.successEmailIds.length + : null, + unreadCount: reactionState.readActions == ReadActions.markAsUnread + ? reactionState.successEmailIds.length + : null, + ); + } else if (reactionState is MarkAsMailboxReadAllSuccess) { + _handleMarkMailboxAsRead( + affectedMailboxId: reactionState.mailboxId, + ); + } else if (reactionState is MarkAsMailboxReadHasSomeEmailFailure) { + _handleMarkEmailsAsReadOrUnread( + affectedMailboxId: reactionState.mailboxId, + readCount: reactionState.successEmailIds.length, + ); + } + }); + } + + void _handleMarkEmailsAsReadOrUnread({ + required MailboxId? affectedMailboxId, + int? readCount, + int? unreadCount, + }) { + if (affectedMailboxId == null) return; + + updateUnreadCountOfMailboxById( + affectedMailboxId, + unreadChanges: (unreadCount ?? 0) - (readCount ?? 0), + ); + } + + void _handleMarkMailboxAsRead({ + required MailboxId? affectedMailboxId, + }) { + if (affectedMailboxId == null) return; + + clearUnreadCount(affectedMailboxId); } void _initWebSocketQueueHandler() { @@ -638,6 +706,10 @@ class MailboxController extends BaseMailboxController } } + void _renameMailboxSuccess(RenameMailboxSuccess success) { + updateMailboxNameById(success.request.mailboxId, success.request.newName); + } + void _renameMailboxFailure(RenameMailboxFailure failure) { if (currentOverlayContext != null && currentContext != null) { final exception = failure.exception; diff --git a/lib/features/mailbox/presentation/model/mailbox_tree.dart b/lib/features/mailbox/presentation/model/mailbox_tree.dart index a682cecd1..97bff1f3f 100644 --- a/lib/features/mailbox/presentation/model/mailbox_tree.dart +++ b/lib/features/mailbox/presentation/model/mailbox_tree.dart @@ -93,16 +93,18 @@ class MailboxTree with EquatableMixin { return false; } - void updateMailboxUnreadCountById(MailboxId mailboxId, int unreadCount) { + bool updateMailboxUnreadCountById(MailboxId mailboxId, int unreadCount) { final matchedNode = findNode((node) => node.item.id == mailboxId); if (matchedNode != null) { final currentUnreadCount = matchedNode.item.unreadEmails?.value.value ?? 0; final updatedUnreadCount = currentUnreadCount + unreadCount; - if (updatedUnreadCount < 0) return; + if (updatedUnreadCount < 0) return true; matchedNode.item = matchedNode.item.copyWith( unreadEmails: UnreadEmails(UnsignedInt(updatedUnreadCount)), ); + return true; } + return false; } String? getNodePath(MailboxId mailboxId) { 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 8fafc74f2..b2791f064 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -884,6 +884,7 @@ class MailboxDashBoardController extends ReloadableController EmailId emailId, ReadActions readActions, MarkReadAction markReadAction, + MailboxId? mailboxId, ) { if (accountId.value != null && sessionCurrent != null) { consumeState(_markAsEmailReadInteractor.execute( @@ -892,6 +893,7 @@ class MailboxDashBoardController extends ReloadableController emailId, readActions, markReadAction, + mailboxId, )); } } @@ -923,6 +925,7 @@ class MailboxDashBoardController extends ReloadableController accountId.value!, listEmailNeedMarkAsRead.listEmailIds, readActions, + listPresentationEmail.firstOrNull?.mailboxContain?.mailboxId, )); } } @@ -960,7 +963,7 @@ class MailboxDashBoardController extends ReloadableController message, actionName: AppLocalizations.of(currentContext!).undo, onActionClick: () { - markAsEmailRead(success.emailId, undoAction, MarkReadAction.undo); + markAsEmailRead(success.emailId, undoAction, MarkReadAction.undo, success.mailboxId); }, leadingSVGIcon: imagePaths.icToastSuccessMessage, backgroundColor: AppColor.toastSuccessBackgroundColor, diff --git a/lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart new file mode 100644 index 000000000..4572d768a --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart @@ -0,0 +1,56 @@ +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:model/email/mark_star_action.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/email/read_actions.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; + +extension UpdateCurrentEmailsFlagsExtension on MailboxDashBoardController { + void updateEmailFlagByEmailIds( + List emailIds, { + ReadActions? readAction, + MarkStarAction? markStarAction, + }) { + if (readAction == null && markStarAction == null) return; + + for (var email in emailsInCurrentMailbox) { + if (!emailIds.contains(email.id)) continue; + + switch (readAction) { + case ReadActions.markAsRead: + _updateKeyword(email, KeyWordIdentifier.emailSeen, true); + break; + case ReadActions.markAsUnread: + _updateKeyword(email, KeyWordIdentifier.emailSeen, false); + break; + default: + break; + } + + switch (markStarAction) { + case MarkStarAction.markStar: + _updateKeyword(email, KeyWordIdentifier.emailFlagged, true); + break; + case MarkStarAction.unMarkStar: + _updateKeyword(email, KeyWordIdentifier.emailFlagged, false); + break; + default: + break; + } + } + + emailsInCurrentMailbox.refresh(); + } + + void _updateKeyword( + PresentationEmail presentationEmail, + KeyWordIdentifier keyword, + bool value, + ) { + if (value) { + presentationEmail.keywords?[keyword] = true; + } else { + presentationEmail.keywords?.remove(keyword); + } + } +} \ 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 ccd7b79f7..4d088d674 100644 --- a/lib/features/search/email/presentation/search_email_controller.dart +++ b/lib/features/search/email/presentation/search_email_controller.dart @@ -33,6 +33,8 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_e import 'package:tmail_ui_user/features/contact/presentation/model/contact_arguments.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.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/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'; @@ -44,6 +46,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_all import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.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_receive_time_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; @@ -60,6 +63,8 @@ import 'package:tmail_ui_user/features/search/email/presentation/model/search_mo import 'package:tmail_ui_user/features/search/email/presentation/search_email_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.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/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'; @@ -260,6 +265,41 @@ class SearchEmailController extends BaseController } }, ); + + ever(mailboxDashBoardController.viewState, (viewState) { + final reactionState = viewState.getOrElse(() => UIState.idle); + if (reactionState is MarkAsEmailReadSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + [reactionState.emailId], + readAction: reactionState.readActions, + ); + } else if (reactionState is MarkAsMultipleEmailReadAllSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.emailIds, + readAction: reactionState.readActions, + ); + } else if (reactionState is MarkAsMultipleEmailReadHasSomeEmailFailure) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.successEmailIds, + readAction: reactionState.readActions, + ); + } else if (reactionState is MarkAsStarEmailSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + [reactionState.emailId], + markStarAction: reactionState.markStarAction, + ); + } else if (reactionState is MarkAsStarMultipleEmailAllSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.emailIds, + markStarAction: reactionState.markStarAction, + ); + } else if (reactionState is MarkAsStarMultipleEmailHasSomeEmailFailure) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.successEmailIds, + markStarAction: reactionState.markStarAction, + ); + } + }); } void _refreshEmailChanges({jmap.State? newState}) { diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index e28d98aeb..6cc65fe53 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -33,6 +33,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_request.da import 'package:tmail_ui_user/features/mailbox/domain/state/create_new_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/delete_multiple_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/move_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/refresh_changes_all_mailboxes_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/rename_mailbox_state.dart'; @@ -185,6 +186,18 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa _refreshMailboxChanges(newState: action.newState); } }); + + ever(dashboardController.viewState, (viewState) { + final reactionState = viewState.getOrElse(() => UIState.idle); + if (reactionState is MarkAsMailboxReadAllSuccess) { + clearUnreadCount(reactionState.mailboxId); + } else if (reactionState is MarkAsMailboxReadHasSomeEmailFailure) { + updateUnreadCountOfMailboxById( + reactionState.mailboxId, + unreadChanges: -reactionState.countEmailsRead, + ); + } + }); } void _getAllMailboxAction() { diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 9b4c5d94d..6df6a4a03 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -49,6 +49,7 @@ class ThreadRepositoryImpl extends ThreadRepository { Properties? propertiesCreated, Properties? propertiesUpdated, bool getLatestChanges = true, + bool skipCache = false, } ) async* { log('ThreadRepositoryImpl::getAllEmail(): filter = ${emailFilter?.mailboxId}'); @@ -88,7 +89,7 @@ class ThreadRepositoryImpl extends ThreadRepository { ); } yield networkEmailResponse; - } else { + } else if (!skipCache) { yield localEmailResponse; } diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index ec0503061..8eb198ec6 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -29,6 +29,7 @@ abstract class ThreadRepository { Properties? propertiesCreated, Properties? propertiesUpdated, bool getLatestChanges = true, + bool skipCache = false, } ); diff --git a/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart b/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart index 9134f49b2..0eb6ab7fc 100644 --- a/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart +++ b/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart @@ -1,20 +1,24 @@ 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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/read_actions.dart'; class LoadingMarkAsMultipleEmailReadAll extends UIState {} class MarkAsMultipleEmailReadAllSuccess extends UIState { - final int countMarkAsReadSuccess; + final List emailIds; final ReadActions readActions; + final MailboxId? mailboxId; MarkAsMultipleEmailReadAllSuccess( - this.countMarkAsReadSuccess, - this.readActions, + this.emailIds, + this.readActions, + this.mailboxId, ); @override - List get props => [countMarkAsReadSuccess, readActions]; + List get props => [emailIds, readActions, mailboxId]; } class MarkAsMultipleEmailReadAllFailure extends FeatureFailure { @@ -27,16 +31,18 @@ class MarkAsMultipleEmailReadAllFailure extends FeatureFailure { } class MarkAsMultipleEmailReadHasSomeEmailFailure extends UIState { - final int countMarkAsReadSuccess; + final List successEmailIds; final ReadActions readActions; + final MailboxId? mailboxId; MarkAsMultipleEmailReadHasSomeEmailFailure( - this.countMarkAsReadSuccess, + this.successEmailIds, this.readActions, + this.mailboxId, ); @override - List get props => [countMarkAsReadSuccess, readActions]; + List get props => [successEmailIds, readActions, mailboxId]; } class MarkAsMultipleEmailReadFailure extends FeatureFailure { diff --git a/lib/features/thread/domain/state/mark_as_star_multiple_email_state.dart b/lib/features/thread/domain/state/mark_as_star_multiple_email_state.dart index b41a89154..c7c5cecab 100644 --- a/lib/features/thread/domain/state/mark_as_star_multiple_email_state.dart +++ b/lib/features/thread/domain/state/mark_as_star_multiple_email_state.dart @@ -1,5 +1,6 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/email/mark_star_action.dart'; class LoadingMarkAsStarMultipleEmailAll extends UIState {} @@ -7,14 +8,16 @@ class LoadingMarkAsStarMultipleEmailAll extends UIState {} class MarkAsStarMultipleEmailAllSuccess extends UIState { final int countMarkStarSuccess; final MarkStarAction markStarAction; + final List emailIds; MarkAsStarMultipleEmailAllSuccess( this.countMarkStarSuccess, this.markStarAction, + this.emailIds, ); @override - List get props => [countMarkStarSuccess, markStarAction]; + List get props => [countMarkStarSuccess, markStarAction, emailIds]; } class MarkAsStarMultipleEmailAllFailure extends FeatureFailure { @@ -29,14 +32,16 @@ class MarkAsStarMultipleEmailAllFailure extends FeatureFailure { class MarkAsStarMultipleEmailHasSomeEmailFailure extends UIState { final int countMarkStarSuccess; final MarkStarAction markStarAction; + final List successEmailIds; MarkAsStarMultipleEmailHasSomeEmailFailure( this.countMarkStarSuccess, this.markStarAction, + this.successEmailIds, ); @override - List get props => [countMarkStarSuccess, markStarAction]; + List get props => [countMarkStarSuccess, markStarAction, successEmailIds]; } class MarkAsStarMultipleEmailFailure extends FeatureFailure { diff --git a/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart b/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart index 0eccaae50..8e8a98bf5 100644 --- a/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart +++ b/lib/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart @@ -27,6 +27,7 @@ class GetEmailsInMailboxInteractor { Properties? propertiesCreated, Properties? propertiesUpdated, bool getLatestChanges = true, + bool skipCache = false, } ) async* { try { @@ -41,7 +42,8 @@ class GetEmailsInMailboxInteractor { emailFilter: emailFilter, propertiesCreated: propertiesCreated, propertiesUpdated: propertiesUpdated, - getLatestChanges: getLatestChanges) + getLatestChanges: getLatestChanges, + skipCache: skipCache) .map((emailResponse) => _toGetEmailState( emailResponse: emailResponse, currentMailboxId: emailFilter?.mailboxId diff --git a/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart b/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart index 67299d960..5bd076361 100644 --- a/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart +++ b/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart @@ -4,6 +4,7 @@ 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:model/model.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; @@ -17,7 +18,8 @@ class MarkAsMultipleEmailReadInteractor { Session session, AccountId accountId, List emailIds, - ReadActions readAction + ReadActions readAction, + MailboxId? mailboxId, ) async* { try { yield Right(LoadingMarkAsMultipleEmailReadAll()); @@ -31,15 +33,17 @@ class MarkAsMultipleEmailReadInteractor { if (emailIds.length == result.emailIdsSuccess.length) { yield Right(MarkAsMultipleEmailReadAllSuccess( - result.emailIdsSuccess.length, - readAction, + result.emailIdsSuccess, + readAction, + mailboxId, )); } else if (result.emailIdsSuccess.isEmpty) { yield Left(MarkAsMultipleEmailReadAllFailure(readAction)); } else { yield Right(MarkAsMultipleEmailReadHasSomeEmailFailure( - result.emailIdsSuccess.length, - readAction, + result.emailIdsSuccess, + readAction, + mailboxId, )); } } catch (e) { diff --git a/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart b/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart index 8ac2e00b4..980a1ff96 100644 --- a/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart +++ b/lib/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart @@ -26,15 +26,17 @@ class MarkAsStarMultipleEmailInteractor { if (emailIds.length == result.emailIdsSuccess.length) { yield Right(MarkAsStarMultipleEmailAllSuccess( - emailIds.length, - markStarAction, + emailIds.length, + markStarAction, + result.emailIdsSuccess, )); } else if (result.emailIdsSuccess.isEmpty) { yield Left(MarkAsStarMultipleEmailAllFailure(markStarAction)); } else { yield Right(MarkAsStarMultipleEmailHasSomeEmailFailure( - result.emailIdsSuccess.length, - markStarAction, + result.emailIdsSuccess.length, + markStarAction, + result.emailIdsSuccess, )); } } 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 baefee994..04e2f12dd 100644 --- a/lib/features/thread/presentation/mixin/email_action_controller.dart +++ b/lib/features/thread/presentation/mixin/email_action_controller.dart @@ -233,6 +233,7 @@ mixin EmailActionController { presentationEmail.id!, readActions, markReadAction, + presentationEmail.mailboxContain?.mailboxId, ); } diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 9b8781581..1a0718638 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -20,11 +20,15 @@ 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/email/domain/model/mark_read_action.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/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'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_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; +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'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart'; @@ -45,6 +49,8 @@ import 'package:tmail_ui_user/features/thread/domain/model/search_query.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_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/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'; @@ -331,6 +337,59 @@ class ThreadController extends BaseController with EmailActionController { mailboxDashBoardController.clearEmailUIAction(); } }); + + ever(mailboxDashBoardController.viewState, (viewState) { + final reactionState = viewState.getOrElse(() => UIState.idle); + if (reactionState is MarkAsEmailReadSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + [reactionState.emailId], + readAction: reactionState.readActions, + ); + } else if (reactionState is MarkAsMultipleEmailReadAllSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.emailIds, + readAction: reactionState.readActions, + ); + } else if (reactionState is MarkAsMultipleEmailReadHasSomeEmailFailure) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.successEmailIds, + readAction: reactionState.readActions, + ); + } else if (reactionState is MarkAsMailboxReadAllSuccess) { + _handleMarkEmailsAsReadByMailboxId(reactionState.mailboxId); + } else if (reactionState is MarkAsMailboxReadHasSomeEmailFailure) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.successEmailIds, + readAction: ReadActions.markAsRead, + ); + } else if (reactionState is MarkAsStarEmailSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + [reactionState.emailId], + markStarAction: reactionState.markStarAction, + ); + } else if (reactionState is MarkAsStarMultipleEmailAllSuccess) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.emailIds, + markStarAction: reactionState.markStarAction, + ); + } else if (reactionState is MarkAsStarMultipleEmailHasSomeEmailFailure) { + mailboxDashBoardController.updateEmailFlagByEmailIds( + reactionState.successEmailIds, + markStarAction: reactionState.markStarAction, + ); + } + }); + } + + void _handleMarkEmailsAsReadByMailboxId(MailboxId mailboxId) { + if (mailboxDashBoardController.selectedMailbox.value?.id != mailboxId) return; + + for (var presentationEmail in mailboxDashBoardController.emailsInCurrentMailbox) { + if (presentationEmail.mailboxContain?.id != mailboxId) continue; + + presentationEmail.keywords?[KeyWordIdentifier.emailSeen] = true; + } + mailboxDashBoardController.emailsInCurrentMailbox.refresh(); } void _registerBrowserResizeListener() { @@ -444,7 +503,10 @@ class ThreadController extends BaseController with EmailActionController { } } - void _getAllEmailAction({bool getLatestChanges = true}) { + void _getAllEmailAction({ + bool getLatestChanges = true, + bool skipCache = false, + }) { log('ThreadController::_getAllEmailAction:'); if (_session != null &&_accountId != null) { consumeState(_getEmailsInMailboxInteractor.execute( @@ -460,6 +522,7 @@ class ThreadController extends BaseController with EmailActionController { propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod(_session!, _accountId!), propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, getLatestChanges: getLatestChanges, + skipCache: skipCache, )); } else { consumeState(Stream.value(Left(GetAllEmailFailure(NotFoundSessionException())))); @@ -508,7 +571,7 @@ class ThreadController extends BaseController with EmailActionController { if (searchController.isSearchEmailRunning) { _searchEmail(limit: limitEmailFetched); } else { - _getAllEmailAction(); + _getAllEmailAction(skipCache: true); } } @@ -792,6 +855,9 @@ class ThreadController extends BaseController with EmailActionController { } void cancelSelectEmail() { + if (mailboxDashBoardController.currentSelectMode.value == SelectMode.INACTIVE) { + return; + } final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox .map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)) .toList(); diff --git a/test/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension_test.dart b/test/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension_test.dart new file mode 100644 index 000000000..39fcfe770 --- /dev/null +++ b/test/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension_test.dart @@ -0,0 +1,150 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/email/mark_star_action.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/email/read_actions.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; + +import 'update_current_emails_flags_extension_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + const numberOfEmails = 3; + late List emailIds; + final mailboxDashBoardController = MockMailboxDashBoardController(); + + setUp(() { + emailIds = List.generate( + numberOfEmails, + (index) => EmailId(Id('email-id-$index')), + ); + }); + + group('updateEmailFlagByEmailIds test:', () { + test( + 'should mark emails as read', + () { + // arrange + final readEmailIds = emailIds.sublist(1); + when(mailboxDashBoardController.emailsInCurrentMailbox).thenReturn( + emailIds.map((emailId) => PresentationEmail( + id: emailId, + keywords: {}, + )).toList().obs, + ); + expect( + mailboxDashBoardController.emailsInCurrentMailbox.every( + (presentationEmail) => !presentationEmail.hasRead, + ), + true, + ); + + // act + mailboxDashBoardController.updateEmailFlagByEmailIds( + readEmailIds, + readAction: ReadActions.markAsRead, + ); + + // assert + expect(mailboxDashBoardController.emailsInCurrentMailbox[0].hasRead, false); + expect(mailboxDashBoardController.emailsInCurrentMailbox[1].hasRead, true); + expect(mailboxDashBoardController.emailsInCurrentMailbox[2].hasRead, true); + }); + + test( + 'should mark emails as unread', + () { + // arrange + final unreadEmailIds = emailIds.sublist(1); + when(mailboxDashBoardController.emailsInCurrentMailbox).thenReturn( + emailIds.map((emailId) => PresentationEmail( + id: emailId, + keywords: {KeyWordIdentifier.emailSeen: true}, + )).toList().obs, + ); + expect( + mailboxDashBoardController.emailsInCurrentMailbox.every( + (presentationEmail) => presentationEmail.hasRead, + ), + true, + ); + + // act + mailboxDashBoardController.updateEmailFlagByEmailIds( + unreadEmailIds, + readAction: ReadActions.markAsUnread, + ); + + // assert + expect(mailboxDashBoardController.emailsInCurrentMailbox[0].hasRead, true); + expect(mailboxDashBoardController.emailsInCurrentMailbox[1].hasRead, false); + expect(mailboxDashBoardController.emailsInCurrentMailbox[2].hasRead, false); + }); + + test( + 'should mark emails as starred', + () { + // arrange + final starredEmailIds = emailIds.sublist(1); + when(mailboxDashBoardController.emailsInCurrentMailbox).thenReturn( + emailIds.map((emailId) => PresentationEmail( + id: emailId, + keywords: {}, + )).toList().obs, + ); + expect( + mailboxDashBoardController.emailsInCurrentMailbox.every( + (presentationEmail) => !presentationEmail.hasStarred, + ), + true, + ); + + // act + mailboxDashBoardController.updateEmailFlagByEmailIds( + starredEmailIds, + markStarAction: MarkStarAction.markStar, + ); + + // assert + expect(mailboxDashBoardController.emailsInCurrentMailbox[0].hasStarred, false); + expect(mailboxDashBoardController.emailsInCurrentMailbox[1].hasStarred, true); + expect(mailboxDashBoardController.emailsInCurrentMailbox[2].hasStarred, true); + }); + + test( + 'should mark emails as unstarred', + () { + // arrange + final unstarredEmailIds = emailIds.sublist(1); + when(mailboxDashBoardController.emailsInCurrentMailbox).thenReturn( + emailIds.map((emailId) => PresentationEmail( + id: emailId, + keywords: {KeyWordIdentifier.emailFlagged: true}, + )).toList().obs, + ); + expect( + mailboxDashBoardController.emailsInCurrentMailbox.every( + (presentationEmail) => presentationEmail.hasStarred, + ), + true, + ); + + // act + mailboxDashBoardController.updateEmailFlagByEmailIds( + unstarredEmailIds, + markStarAction: MarkStarAction.unMarkStar, + ); + + // assert + expect(mailboxDashBoardController.emailsInCurrentMailbox[0].hasStarred, true); + expect(mailboxDashBoardController.emailsInCurrentMailbox[1].hasStarred, false); + expect(mailboxDashBoardController.emailsInCurrentMailbox[2].hasStarred, false); + }); + }); +} \ No newline at end of file