diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart index 2aca68711..2c07f18ef 100644 --- a/lib/features/base/base_mailbox_controller.dart +++ b/lib/features/base/base_mailbox_controller.dart @@ -37,6 +37,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentat import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories_expand_mode.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; @@ -62,6 +63,7 @@ typedef OnMoveFolderContentActionCallback = void Function( ); typedef DeleteMailboxActionCallback = void Function(PresentationMailbox mailbox); typedef AllowSubaddressingActionCallback = void Function(MailboxId, Map?>?, MailboxActions); +typedef OnUpdateMailboxCollectionCallback = MailboxCollection Function(MailboxCollection); abstract class BaseMailboxController extends BaseController with ExpandFolderTriggerScrollableMixin { @@ -87,40 +89,62 @@ abstract class BaseMailboxController extends BaseController List allMailboxes = []; + MailboxCollection get currentMailboxCollection => MailboxCollection( + allMailboxes: allMailboxes, + defaultTree: defaultMailboxTree.value, + personalTree: personalMailboxTree.value, + teamMailboxTree: teamMailboxesTree.value, + ); + Future buildTree( - List allMailbox, - {MailboxId? mailboxIdSelected} - ) async { - final recordTree = await _treeBuilder.generateMailboxTreeInUI( + List allMailbox, { + MailboxId? mailboxIdSelected, + OnUpdateMailboxCollectionCallback? onUpdateMailboxCollectionCallback, + }) async { + MailboxCollection mailboxCollection = + await _treeBuilder.generateMailboxTreeInUI( allMailboxes: allMailbox, - currentDefaultTree: defaultMailboxTree.value, - currentPersonalTree: personalMailboxTree.value, - currentTeamMailboxTree: teamMailboxesTree.value, + currentCollection: currentMailboxCollection, mailboxIdSelected: mailboxIdSelected, ); - defaultMailboxTree.firstRebuild = true; - personalMailboxTree.firstRebuild = true; - teamMailboxesTree.firstRebuild = true; - defaultMailboxTree.value = recordTree.defaultTree; - personalMailboxTree.value = recordTree.personalTree; - teamMailboxesTree.value = recordTree.teamMailboxTree; - allMailboxes = recordTree.allMailboxes; + + if (onUpdateMailboxCollectionCallback != null) { + mailboxCollection = onUpdateMailboxCollectionCallback(mailboxCollection); + } + + updateMailboxTree(mailboxCollection: mailboxCollection); } - Future refreshTree(List allMailbox) async { - final recordTree = await _treeBuilder.generateMailboxTreeInUIAfterRefreshChanges( + Future refreshTree( + List allMailbox, { + OnUpdateMailboxCollectionCallback? onUpdateMailboxCollectionCallback, + }) async { + MailboxCollection mailboxCollection = + await _treeBuilder.generateMailboxTreeInUIAfterRefreshChanges( allMailboxes: allMailbox, - currentDefaultTree: defaultMailboxTree.value, - currentPersonalTree: personalMailboxTree.value, - currentTeamMailboxTree: teamMailboxesTree.value, + currentCollection: currentMailboxCollection, ); - defaultMailboxTree.firstRebuild = true; - personalMailboxTree.firstRebuild = true; - teamMailboxesTree.firstRebuild = true; - defaultMailboxTree.value = recordTree.defaultTree; - personalMailboxTree.value = recordTree.personalTree; - teamMailboxesTree.value = recordTree.teamMailboxTree; - allMailboxes = allMailbox; + + if (onUpdateMailboxCollectionCallback != null) { + mailboxCollection = onUpdateMailboxCollectionCallback(mailboxCollection); + } + + updateMailboxTree(mailboxCollection: mailboxCollection); + } + + void updateMailboxTree({ + required MailboxCollection mailboxCollection, + bool isRefreshTrigger = true, + }) { + if (isRefreshTrigger) { + defaultMailboxTree.firstRebuild = true; + personalMailboxTree.firstRebuild = true; + teamMailboxesTree.firstRebuild = true; + } + defaultMailboxTree.value = mailboxCollection.defaultTree; + personalMailboxTree.value = mailboxCollection.personalTree; + teamMailboxesTree.value = mailboxCollection.teamMailboxTree; + allMailboxes = mailboxCollection.allMailboxes; } void syncAllMailboxWithDisplayName(BuildContext context) { @@ -675,10 +699,15 @@ abstract class BaseMailboxController extends BaseController } } - void autoCreateVirtualFolder(bool isAINeedsActionEnabled) { - addFavoriteFolderToMailboxList(); + bool get isAINeedsActionEnabled => false; + + MailboxCollection updateMailboxCollection(MailboxCollection mailboxCollection) { + MailboxCollection updated = addFavoriteFolderToMailboxList( + mailboxCollection: mailboxCollection, + ); if (isAINeedsActionEnabled) { - addActionRequiredFolder(); + updated = addActionRequiredFolder(mailboxCollection: updated); } + return updated; } } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart b/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart index 08b42fb8b..fe2a7d14d 100644 --- a/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart +++ b/lib/features/mailbox/presentation/extensions/handle_action_required_tab_extension.dart @@ -3,15 +3,26 @@ import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/base/base_mailbox_controller.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/list_mailbox_node_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; extension HandleActionRequiredTabExtension on BaseMailboxController { - void addActionRequiredFolder() { + MailboxCollection addActionRequiredFolder({ + required MailboxCollection mailboxCollection, + }) { final folder = _buildActionRequiredFolder(); - _addToDefaultMailboxTree(folder); - _addToAllMailboxes(folder); + return mailboxCollection.copyWith( + defaultTree: _addToDefaultMailboxTree( + folder: folder, + currentDefaultTree: mailboxCollection.defaultTree, + ), + allMailboxes: _addToAllMailboxes( + folder: folder, + currentAllMailboxes: mailboxCollection.allMailboxes, + ), + ); } PresentationMailbox _buildActionRequiredFolder() { @@ -23,43 +34,72 @@ extension HandleActionRequiredTabExtension on BaseMailboxController { ); } - void _addToDefaultMailboxTree(PresentationMailbox folder) { - final root = defaultMailboxTree.value.root; + MailboxTree _addToDefaultMailboxTree({ + required PresentationMailbox folder, + required MailboxTree currentDefaultTree, + }) { + final root = currentDefaultTree.root; final children = List.from(root.childrenItems ?? []); + if (children.any((node) => node.item.id == folder.id)) { + return currentDefaultTree; + } children.insertAfterStarredOrInbox(MailboxNode(folder)); - defaultMailboxTree.value = MailboxTree( - root.copyWith(children: children), + return MailboxTree(root.copyWith(children: children)); + } + + List _addToAllMailboxes({ + required PresentationMailbox folder, + required List currentAllMailboxes, + }) { + if (_allMailboxesContains( + id: folder.id, + currentAllMailboxes: currentAllMailboxes, + )) { + return currentAllMailboxes; + } + return [...currentAllMailboxes, folder]; + } + + bool _allMailboxesContains({ + required MailboxId id, + required List currentAllMailboxes, + }) { + return currentAllMailboxes.any((mailbox) => mailbox.id == id); + } + + MailboxCollection removeActionRequiredFolder({ + required MailboxCollection mailboxCollection, + }) { + return mailboxCollection.copyWith( + defaultTree: _removeFromDefaultMailboxTree( + folderId: PresentationMailbox.actionRequiredFolder.id, + currentDefaultTree: mailboxCollection.defaultTree, + ), + allMailboxes: _removeFromAllMailboxes( + folderId: PresentationMailbox.actionRequiredFolder.id, + currentAllMailboxes: mailboxCollection.allMailboxes, + ), ); } - void _addToAllMailboxes(PresentationMailbox folder) { - if (_allMailboxesContains(folder.id)) return; - allMailboxes.add(folder); - } - - bool _allMailboxesContains(MailboxId id) { - return allMailboxes.any((mailbox) => mailbox.id == id); - } - - void removeActionRequiredFolder() { - final folder = PresentationMailbox.actionRequiredFolder; - _removeFromDefaultMailboxTree(folder.id); - _removeFromAllMailboxes(folder.id); - } - - void _removeFromDefaultMailboxTree(MailboxId folderId) { - final root = defaultMailboxTree.value.root; + MailboxTree _removeFromDefaultMailboxTree({ + required MailboxId folderId, + required MailboxTree currentDefaultTree, + }) { + final root = currentDefaultTree.root; final children = List.from(root.childrenItems ?? []) ..removeWhere((node) => node.item.id == folderId); - - defaultMailboxTree.value = MailboxTree( - root.copyWith(children: children), - ); + return MailboxTree(root.copyWith(children: children)); } - void _removeFromAllMailboxes(MailboxId folderId) { - allMailboxes.removeWhere((mailbox) => mailbox.id == folderId); + List _removeFromAllMailboxes({ + required MailboxId folderId, + required List currentAllMailboxes, + }) { + return currentAllMailboxes + .where((mailbox) => mailbox.id != folderId) + .toList(); } } diff --git a/lib/features/mailbox/presentation/extensions/handle_favorite_tab_extension.dart b/lib/features/mailbox/presentation/extensions/handle_favorite_tab_extension.dart index 373b96311..3d45480c4 100644 --- a/lib/features/mailbox/presentation/extensions/handle_favorite_tab_extension.dart +++ b/lib/features/mailbox/presentation/extensions/handle_favorite_tab_extension.dart @@ -2,12 +2,15 @@ import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/base/base_mailbox_controller.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/list_mailbox_node_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; extension HandleFavoriteTabExtension on BaseMailboxController { - void addFavoriteFolderToMailboxList() { + MailboxCollection addFavoriteFolderToMailboxList({ + required MailboxCollection mailboxCollection, + }) { PresentationMailbox favoriteFolder = PresentationMailbox.favoriteFolder; if (currentContext != null) { favoriteFolder = favoriteFolder.copyWith( @@ -15,16 +18,28 @@ extension HandleFavoriteTabExtension on BaseMailboxController { ); } - _addFavoriteFolderToDefaultMailboxTree(favoriteFolder); - _addFavoriteFolderToAllMailboxes(favoriteFolder); + final newDefaultTree = _addFavoriteFolderToDefaultMailboxTree( + favoriteFolder: favoriteFolder, + defaultTree: mailboxCollection.defaultTree, + ); + final newAllMailboxes = _addFavoriteFolderToAllMailboxes( + favoriteFolder: favoriteFolder, + allMailboxes: mailboxCollection.allMailboxes, + ); + + return mailboxCollection.copyWith( + defaultTree: newDefaultTree, + allMailboxes: newAllMailboxes, + ); } - void _addFavoriteFolderToDefaultMailboxTree( - PresentationMailbox favoriteFolder, - ) { - final defaultMailboxNode = defaultMailboxTree.value.root; - List currentDefaultFolders = - defaultMailboxNode.childrenItems ?? []; + MailboxTree _addFavoriteFolderToDefaultMailboxTree({ + required PresentationMailbox favoriteFolder, + required MailboxTree defaultTree, + }) { + final defaultMailboxNode = defaultTree.root; + final currentDefaultFolders = + List.from(defaultMailboxNode.childrenItems ?? []); if (currentDefaultFolders.isEmpty) { currentDefaultFolders.add(MailboxNode(favoriteFolder)); @@ -32,17 +47,20 @@ extension HandleFavoriteTabExtension on BaseMailboxController { currentDefaultFolders.insertAfterInbox(MailboxNode(favoriteFolder)); } - defaultMailboxTree.value = MailboxTree( + return MailboxTree( defaultMailboxNode.copyWith(children: currentDefaultFolders), ); } - void _addFavoriteFolderToAllMailboxes(PresentationMailbox favoriteFolder) { + List _addFavoriteFolderToAllMailboxes({ + required PresentationMailbox favoriteFolder, + required List allMailboxes, + }) { final alreadyExists = allMailboxes.any( (mailbox) => mailbox.id == favoriteFolder.id, ); - if (alreadyExists) return; + if (alreadyExists) return allMailboxes; - allMailboxes.add(favoriteFolder); + return [...allMailboxes, favoriteFolder]; } } diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 11dfc1e39..400db9329 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -257,8 +257,9 @@ class MailboxController extends BaseMailboxController toastManager: toastManager, ); } else if (failure is CreateDefaultMailboxFailure) { - autoCreateVirtualFolder( - mailboxDashBoardController.isAINeedsActionEnabled, + updateMailboxTree( + mailboxCollection: updateMailboxCollection(currentMailboxCollection), + isRefreshTrigger: false, ); } else { super.handleFailureViewState(failure); @@ -271,8 +272,9 @@ class MailboxController extends BaseMailboxController viewState.value.fold( (failure) { if (failure is GetAllMailboxFailure) { - autoCreateVirtualFolder( - mailboxDashBoardController.isAINeedsActionEnabled, + updateMailboxTree( + mailboxCollection: updateMailboxCollection(currentMailboxCollection), + isRefreshTrigger: false, ); mailboxDashBoardController.updateRefreshAllMailboxState(Left(RefreshAllMailboxFailure())); showRetryToast(failure); @@ -303,38 +305,7 @@ class MailboxController extends BaseMailboxController _handleNavigationRouteParameters ); - ever(mailboxDashBoardController.mailboxUIAction, (action) { - if (action is SelectMailboxDefaultAction) { - _switchBackToMailboxDefault(); - mailboxDashBoardController.clearMailboxUIAction(); - } else if (action is RefreshChangeMailboxAction) { - _refreshMailboxChanges(newState: action.newState); - } else if (action is OpenMailboxAction) { - if (currentContext != null) { - _handleOpenMailbox(currentContext!, action.presentationMailbox); - if (action.presentationMailbox.role == PresentationMailbox.roleInbox) { - _autoScrollToTopMailboxList(); - } - } - mailboxDashBoardController.clearMailboxUIAction(); - } else if (action is SystemBackToInboxAction) { - _disableAllSearchEmail(); - _switchBackToMailboxDefault(); - mailboxDashBoardController.clearMailboxUIAction(); - } else if (action is RefreshAllMailboxAction) { - refreshAllMailbox(); - mailboxDashBoardController.clearMailboxUIAction(); - } else if (action is AutoCreateActionRequiredFolderMailboxAction) { - addActionRequiredFolder(); - mailboxDashBoardController.clearMailboxUIAction(); - } else if (action is AutoRemoveActionRequiredFolderMailboxAction) { - removeActionRequiredFolder(); - mailboxDashBoardController.clearMailboxUIAction(); - if (selectedMailbox?.isActionRequired == true) { - _switchBackToMailboxDefault(); - } - } - }); + ever(mailboxDashBoardController.mailboxUIAction, _handleMailboxUIAction); ever(mailboxDashBoardController.viewState, (viewState) { final reactionState = viewState.getOrElse(() => UIState.idle); @@ -452,6 +423,57 @@ class MailboxController extends BaseMailboxController }); } + void _handleMailboxUIAction(MailboxUIAction? action) { + if (action is SelectMailboxDefaultAction) { + _switchBackToMailboxDefault(); + mailboxDashBoardController.clearMailboxUIAction(); + } else if (action is RefreshChangeMailboxAction) { + _refreshMailboxChanges(newState: action.newState); + } else if (action is OpenMailboxAction) { + _onOpenMailboxAction(action); + } else if (action is SystemBackToInboxAction) { + _disableAllSearchEmail(); + _switchBackToMailboxDefault(); + mailboxDashBoardController.clearMailboxUIAction(); + } else if (action is RefreshAllMailboxAction) { + refreshAllMailbox(); + mailboxDashBoardController.clearMailboxUIAction(); + } else if (action is AutoCreateActionRequiredFolderMailboxAction) { + updateMailboxTree( + mailboxCollection: addActionRequiredFolder( + mailboxCollection: currentMailboxCollection, + ), + isRefreshTrigger: false, + ); + mailboxDashBoardController.clearMailboxUIAction(); + } else if (action is AutoRemoveActionRequiredFolderMailboxAction) { + _onAutoRemoveActionRequiredFolderMailboxAction(); + } + } + + void _onOpenMailboxAction(OpenMailboxAction action) { + if (currentContext != null) { + _handleOpenMailbox(currentContext!, action.presentationMailbox); + if (action.presentationMailbox.role == PresentationMailbox.roleInbox) { + _autoScrollToTopMailboxList(); + } + } + mailboxDashBoardController.clearMailboxUIAction(); + } + + void _onAutoRemoveActionRequiredFolderMailboxAction() { + updateMailboxTree( + mailboxCollection: removeActionRequiredFolder( + mailboxCollection: currentMailboxCollection, + ), + isRefreshTrigger: false, + ); + mailboxDashBoardController.clearMailboxUIAction(); + if (selectedMailbox?.isActionRequired == true) { + _switchBackToMailboxDefault(); + } + } + void _handleMarkEmailsAsReadOrUnread({ required MailboxId? affectedMailboxId, int? readCount, @@ -614,14 +636,14 @@ class MailboxController extends BaseMailboxController .mailboxList .listSubscribedMailboxesAndDefaultMailboxes; - await refreshTree(listMailboxDisplayed.withoutVirtualMailbox); + await refreshTree( + listMailboxDisplayed.withoutVirtualMailbox, + onUpdateMailboxCollectionCallback: updateMailboxCollection, + ); if (currentContext != null) { syncAllMailboxWithDisplayName(currentContext!); } - autoCreateVirtualFolder( - mailboxDashBoardController.isAINeedsActionEnabled, - ); _setMapMailbox(); _setOutboxMailbox(); _selectSelectedMailboxDefault(); @@ -632,6 +654,10 @@ class MailboxController extends BaseMailboxController } } + @override + bool get isAINeedsActionEnabled => + mailboxDashBoardController.isAINeedsActionEnabled; + void _setMapMailbox() { final mapDefaultMailboxIdByRole = { for (var mailboxNode in defaultMailboxTree.value.root.childrenItems ?? List.empty()) @@ -706,8 +732,9 @@ class MailboxController extends BaseMailboxController .toList(); if (listRoleMissing.isEmpty || accountId == null || session == null) { - autoCreateVirtualFolder( - mailboxDashBoardController.isAINeedsActionEnabled, + updateMailboxTree( + mailboxCollection: updateMailboxCollection(currentMailboxCollection), + isRefreshTrigger: false, ); return; } @@ -726,8 +753,9 @@ class MailboxController extends BaseMailboxController Future _handleCreateDefaultFolderIfMissingSuccess(CreateDefaultMailboxAllSuccess success) async { if (success.listMailbox.isEmpty) { - autoCreateVirtualFolder( - mailboxDashBoardController.isAINeedsActionEnabled, + updateMailboxTree( + mailboxCollection: updateMailboxCollection(currentMailboxCollection), + isRefreshTrigger: false, ); return; } @@ -743,13 +771,13 @@ class MailboxController extends BaseMailboxController allMailboxes.add(mailbox.toPresentationMailbox()); } - await buildTree(allMailboxes.withoutVirtualMailbox); + await buildTree( + allMailboxes.withoutVirtualMailbox, + onUpdateMailboxCollectionCallback: updateMailboxCollection, + ); if (currentContext != null) { syncAllMailboxWithDisplayName(currentContext!); } - autoCreateVirtualFolder( - mailboxDashBoardController.isAINeedsActionEnabled, - ); _setMapMailbox(); _setOutboxMailbox(); } @@ -1339,7 +1367,10 @@ class MailboxController extends BaseMailboxController currentMailboxState = success.currentMailboxState; log('MailboxController::_handleGetAllMailboxSuccess:currentMailboxState: $currentMailboxState'); final listMailboxDisplayed = success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes; - await buildTree(listMailboxDisplayed.withoutVirtualMailbox); + await buildTree( + listMailboxDisplayed.withoutVirtualMailbox, + onUpdateMailboxCollectionCallback: updateMailboxCollection, + ); if (currentContext != null) { syncAllMailboxWithDisplayName(currentContext!); } diff --git a/lib/features/mailbox/presentation/model/mailbox_collection.dart b/lib/features/mailbox/presentation/model/mailbox_collection.dart new file mode 100644 index 000000000..1e50bda6b --- /dev/null +++ b/lib/features/mailbox/presentation/model/mailbox_collection.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.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/mailbox/presentation/model/mailbox_tree.dart'; + +class MailboxCollection with EquatableMixin { + final List allMailboxes; + final MailboxTree defaultTree; + final MailboxTree personalTree; + final MailboxTree teamMailboxTree; + + const MailboxCollection({ + required this.allMailboxes, + required this.defaultTree, + required this.personalTree, + required this.teamMailboxTree, + }); + + factory MailboxCollection.empty() => MailboxCollection( + allMailboxes: const [], + defaultTree: MailboxTree(MailboxNode.root()), + personalTree: MailboxTree(MailboxNode.root()), + teamMailboxTree: MailboxTree(MailboxNode.root()), + ); + + MailboxCollection copyWith({ + List? allMailboxes, + MailboxTree? defaultTree, + MailboxTree? personalTree, + MailboxTree? teamMailboxTree, + }) { + return MailboxCollection( + allMailboxes: allMailboxes ?? this.allMailboxes, + defaultTree: defaultTree ?? this.defaultTree, + personalTree: personalTree ?? this.personalTree, + teamMailboxTree: teamMailboxTree ?? this.teamMailboxTree, + ); + } + + @override + List get props => [ + allMailboxes, + defaultTree, + personalTree, + teamMailboxTree, + ]; +} diff --git a/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart b/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart index d749a8126..2c1fdaed6 100644 --- a/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart +++ b/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart @@ -8,6 +8,7 @@ import 'package:model/mailbox/expand_mode.dart'; import 'package:model/mailbox/mailbox_state.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_collection.dart'; import 'mailbox_node.dart'; import 'mailbox_tree.dart'; @@ -37,16 +38,9 @@ class TreeBuilder { return tree; } - Future<({ - List allMailboxes, - MailboxTree defaultTree, - MailboxTree personalTree, - MailboxTree teamMailboxTree - })> generateMailboxTreeInUI({ + Future generateMailboxTreeInUI({ required List allMailboxes, - required MailboxTree currentDefaultTree, - required MailboxTree currentPersonalTree, - required MailboxTree currentTeamMailboxTree, + required MailboxCollection currentCollection, MailboxId? mailboxIdSelected, MailboxId? mailboxIdExpanded, }) async { @@ -61,9 +55,7 @@ class TreeBuilder { for (var mailbox in allMailboxes) { final currentMailboxNode = findExistingNode( id: mailbox.id, - currentDefaultTree: currentDefaultTree, - currentPersonalTree: currentPersonalTree, - currentTeamMailboxTree: currentTeamMailboxTree, + currentCollection: currentCollection, ); final isDeactivated = mailbox.id == mailboxIdSelected; @@ -85,19 +77,12 @@ class TreeBuilder { final parentNode = parentId != null ? mailboxDictionary[parentId] : null; if (parentNode != null) { - if (parentNode.nodeState == MailboxState.deactivated) { - currentNode.updateItem(mailbox.withMailboxSate(MailboxState.deactivated)); - currentNode.updateNodeState(MailboxState.deactivated); - } + _propagateDeactivationIfNeeded(parentNode, currentNode, mailbox); parentNode.addChildNode(currentNode); - sortByMailboxNameNodeChildren(parentNode); } else { - final targetTree = mailbox.hasRole() - ? newDefaultTree - : (mailbox.isPersonal ? newPersonalTree : newTeamMailboxTree); + final targetTree = _resolveTargetTree(mailbox, newDefaultTree, newPersonalTree, newTeamMailboxTree); targetTree.root.addChildNode(currentNode); - sortByMailboxNameNodeChildren(targetTree.root); } @@ -106,7 +91,7 @@ class TreeBuilder { sortNodeChildren(newDefaultTree.root); - return ( + return MailboxCollection( allMailboxes: newAllMailboxes, defaultTree: newDefaultTree, personalTree: newPersonalTree, @@ -114,15 +99,9 @@ class TreeBuilder { ); } - Future<({ - MailboxTree defaultTree, - MailboxTree personalTree, - MailboxTree teamMailboxTree - })> generateMailboxTreeInUIAfterRefreshChanges({ + Future generateMailboxTreeInUIAfterRefreshChanges({ required List allMailboxes, - required MailboxTree currentDefaultTree, - required MailboxTree currentPersonalTree, - required MailboxTree currentTeamMailboxTree, + required MailboxCollection currentCollection, }) async { final Map mailboxDictionary = HashMap(); @@ -133,9 +112,7 @@ class TreeBuilder { for (var mailbox in allMailboxes) { final currentMailboxNode = findExistingNode( id: mailbox.id, - currentDefaultTree: currentDefaultTree, - currentPersonalTree: currentPersonalTree, - currentTeamMailboxTree: currentTeamMailboxTree, + currentCollection: currentCollection, ); final newMailboxNode = MailboxNode( @@ -158,18 +135,16 @@ class TreeBuilder { parentNode.addChildNode(currentNode); sortByMailboxNameNodeChildren(parentNode); } else { - final targetTree = mailbox.hasRole() - ? newDefaultTree - : (mailbox.isPersonal ? newPersonalTree : newTeamMailboxTree); + final targetTree = _resolveTargetTree(mailbox, newDefaultTree, newPersonalTree, newTeamMailboxTree); targetTree.root.addChildNode(currentNode); - sortByMailboxNameNodeChildren(targetTree.root); } } sortNodeChildren(newDefaultTree.root); - return ( + return MailboxCollection( + allMailboxes: allMailboxes, defaultTree: newDefaultTree, personalTree: newPersonalTree, teamMailboxTree: newTeamMailboxTree, @@ -189,12 +164,30 @@ class TreeBuilder { MailboxNode? findExistingNode({ required MailboxId id, - required MailboxTree currentDefaultTree, - required MailboxTree currentPersonalTree, - required MailboxTree currentTeamMailboxTree, + required MailboxCollection currentCollection, }) { - return currentDefaultTree.findNode((node) => node.item.id == id) ?? - currentPersonalTree.findNode((node) => node.item.id == id) ?? - currentTeamMailboxTree.findNode((node) => node.item.id == id); + return currentCollection.defaultTree.findNode((node) => node.item.id == id) ?? + currentCollection.personalTree.findNode((node) => node.item.id == id) ?? + currentCollection.teamMailboxTree.findNode((node) => node.item.id == id); + } + + MailboxTree _resolveTargetTree( + PresentationMailbox mailbox, + MailboxTree defaultTree, + MailboxTree personalTree, + MailboxTree teamMailboxTree, + ) { + if (mailbox.hasRole()) return defaultTree; + return mailbox.isPersonal ? personalTree : teamMailboxTree; + } + + void _propagateDeactivationIfNeeded( + MailboxNode parentNode, + MailboxNode currentNode, + PresentationMailbox mailbox, + ) { + if (parentNode.nodeState != MailboxState.deactivated) return; + currentNode.updateItem(mailbox.withMailboxSate(MailboxState.deactivated)); + currentNode.updateNodeState(MailboxState.deactivated); } } \ No newline at end of file diff --git a/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart b/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart index 83a7923d2..43c304380 100644 --- a/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart +++ b/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart @@ -6,6 +6,7 @@ import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/expand_folder_trigger_scrollable_mixin.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/expand_mode_extension.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; @@ -77,9 +78,12 @@ class MailboxCreatorController extends BaseController Future _buildMailboxTree(MailboxCreatorArguments arguments) async { final recordTree = await _treeBuilder.generateMailboxTreeInUI( allMailboxes: arguments.listMailboxes, - currentDefaultTree: defaultMailboxTree.value, - currentPersonalTree: personalMailboxTree.value, - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection( + allMailboxes: const [], + defaultTree: defaultMailboxTree.value, + personalTree: personalMailboxTree.value, + teamMailboxTree: MailboxTree(MailboxNode.root()), + ), ); personalMailboxTree.value = recordTree.personalTree; defaultMailboxTree.value = recordTree.defaultTree; diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index 05689eb43..ab81c8376 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -165,13 +165,19 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa void handleSuccessViewState(Success success) async { if (success is GetAllMailboxSuccess) { currentMailboxState = success.currentMailboxState; - await buildTree(success.mailboxList); + await buildTree( + success.mailboxList, + onUpdateMailboxCollectionCallback: updateMailboxCollection, + ); if (currentContext != null) { syncAllMailboxWithDisplayName(currentContext!); } } else if (success is RefreshChangesAllMailboxSuccess) { currentMailboxState = success.currentMailboxState; - await refreshTree(success.mailboxList); + await refreshTree( + success.mailboxList, + onUpdateMailboxCollectionCallback: updateMailboxCollection, + ); if (currentContext != null) { syncAllMailboxWithDisplayName(currentContext!); } @@ -213,19 +219,17 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa super.onDone(); viewState.value.fold((failure) { if (failure is GetAllMailboxFailure) { - autoCreateVirtualFolder( - dashboardController.isAINeedsActionEnabled, + updateMailboxTree( + mailboxCollection: updateMailboxCollection(currentMailboxCollection), + isRefreshTrigger: false, ); } - }, (success) { - if (success is GetAllMailboxSuccess) { - autoCreateVirtualFolder( - dashboardController.isAINeedsActionEnabled, - ); - } - }); + }, (success) {}); } + @override + bool get isAINeedsActionEnabled => dashboardController.isAINeedsActionEnabled; + void _initializeDebounceTimeTextSearchChange() { _deBouncerTime = Debouncer( const Duration(milliseconds: 300), diff --git a/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart b/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart index f6ceb539d..622d7d832 100644 --- a/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart +++ b/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart @@ -8,6 +8,7 @@ import 'package:model/mailbox/expand_mode.dart'; import 'package:model/mailbox/mailbox_state.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_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; @@ -256,9 +257,7 @@ void main() { final generatedTree = await TreeBuilder().generateMailboxTreeInUI( allMailboxes: testCase, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), ); expect( @@ -323,9 +322,7 @@ void main() { () async { final generatedTrees = await TreeBuilder().generateMailboxTreeInUI( allMailboxes: filteredMailboxes, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), ); expectNoVirtualFoldersRecursively( @@ -350,9 +347,7 @@ void main() { final generatedTrees = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges( allMailboxes: filteredMailboxes, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), ); expectNoVirtualFoldersRecursively( @@ -392,9 +387,7 @@ void main() { final generatedTree = await TreeBuilder().generateMailboxTreeInUI( allMailboxes: filtered, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), ); final children = generatedTree.defaultTree.root.childrenItems ?? []; @@ -437,9 +430,7 @@ void main() { final generatedTree = await TreeBuilder().generateMailboxTreeInUI( allMailboxes: testCaseWithVirtualFolders, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), ); final children = generatedTree.defaultTree.root.childrenItems ?? []; @@ -484,9 +475,7 @@ void main() { final result = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges( allMailboxes: mailboxes, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), ); expect(result.defaultTree.root.childrenItems?.length, equals(1)); @@ -542,9 +531,12 @@ void main() { final result = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges( allMailboxes: [mailbox], - currentDefaultTree: currentDefaultTree, - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection( + allMailboxes: const [], + defaultTree: currentDefaultTree, + personalTree: MailboxTree(MailboxNode.root()), + teamMailboxTree: MailboxTree(MailboxNode.root()), + ), ); final newNode = result.defaultTree.root.childrenItems?.first; @@ -561,6 +553,128 @@ void main() { }); }); + group('MailboxCollection allMailboxes field', () { + final inbox = PresentationMailbox( + MailboxId(Id('inbox')), + name: MailboxName('Inbox'), + role: Role('inbox'), + ); + final sent = PresentationMailbox( + MailboxId(Id('sent')), + name: MailboxName('Sent'), + role: Role('sent'), + ); + + test( + 'generateMailboxTreeInUIAfterRefreshChanges: allMailboxes excludes virtual folders from previous collection', + () async { + final virtualFavorite = PresentationMailbox.favoriteFolder; + final previousDefaultTree = MailboxTree(MailboxNode.root()) + ..root.addChildNode(MailboxNode(virtualFavorite)); + + final previousCollection = MailboxCollection( + allMailboxes: [inbox, virtualFavorite], + defaultTree: previousDefaultTree, + personalTree: MailboxTree(MailboxNode.root()), + teamMailboxTree: MailboxTree(MailboxNode.root()), + ); + + final result = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges( + allMailboxes: [inbox, sent], + currentCollection: previousCollection, + ); + + expect( + result.allMailboxes.every((m) => !m.isVirtualFolder), + isTrue, + reason: 'Virtual folders from previousCollection must not bleed into result.allMailboxes', + ); + expect(result.allMailboxes.length, equals(2)); + }, + ); + + test( + 'generateMailboxTreeInUIAfterRefreshChanges: deleted server mailboxes are excluded from result', + () async { + final deleted = PresentationMailbox( + MailboxId(Id('deleted')), + name: MailboxName('Old Folder'), + ); + final previousDefaultTree = MailboxTree(MailboxNode.root()) + ..root.addChildNode(MailboxNode(deleted)); + + final previousCollection = MailboxCollection( + allMailboxes: [inbox, deleted], + defaultTree: previousDefaultTree, + personalTree: MailboxTree(MailboxNode.root()), + teamMailboxTree: MailboxTree(MailboxNode.root()), + ); + + final result = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges( + allMailboxes: [inbox], + currentCollection: previousCollection, + ); + + expect(result.allMailboxes.length, equals(1)); + expect( + result.allMailboxes.any((m) => m.id == deleted.id), + isFalse, + reason: 'Mailbox removed from server must not appear in result', + ); + }, + ); + + test( + 'generateMailboxTreeInUI: deleted server mailboxes are excluded from result', + () async { + final deleted = PresentationMailbox( + MailboxId(Id('deleted')), + name: MailboxName('Old Folder'), + ); + final previousDefaultTree = MailboxTree(MailboxNode.root()) + ..root.addChildNode(MailboxNode(deleted)); + + final previousCollection = MailboxCollection( + allMailboxes: [inbox, deleted], + defaultTree: previousDefaultTree, + personalTree: MailboxTree(MailboxNode.root()), + teamMailboxTree: MailboxTree(MailboxNode.root()), + ); + + final result = await TreeBuilder().generateMailboxTreeInUI( + allMailboxes: [inbox], + currentCollection: previousCollection, + ); + + expect(result.allMailboxes.length, equals(1)); + expect( + result.allMailboxes.any((m) => m.id == deleted.id), + isFalse, + reason: 'Mailbox removed from server must not appear in result', + ); + }, + ); + + test( + 'generateMailboxTreeInUI: selected mailbox in allMailboxes has state deactivated', + () async { + final result = await TreeBuilder().generateMailboxTreeInUI( + allMailboxes: [inbox, sent], + currentCollection: MailboxCollection.empty(), + mailboxIdSelected: inbox.id, + ); + + final selectedInResult = result.allMailboxes.firstWhere((m) => m.id == inbox.id); + expect( + selectedInResult.state, + equals(MailboxState.deactivated), + reason: 'Selected mailbox must be deactivated in allMailboxes', + ); + expect(result.allMailboxes.length, equals(2)); + }, + ); + }); + group('Cascading Deactivation (generateMailboxTreeInUI only)', () { test('Selecting a parent deactivates it and cascades to its children', () async { @@ -589,9 +703,7 @@ void main() { final result = await TreeBuilder().generateMailboxTreeInUI( allMailboxes: mailboxes, - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), mailboxIdSelected: parentId, ); 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 484ef911b..2f4ff8457 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 @@ -60,6 +60,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_mailbox import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_multiple_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view_web.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; @@ -816,14 +817,12 @@ void main() { when( treeBuilder.generateMailboxTreeInUI( allMailboxes: anyNamed('allMailboxes'), - currentDefaultTree: anyNamed('currentDefaultTree'), - currentPersonalTree: anyNamed('currentPersonalTree'), - currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'), + currentCollection: anyNamed('currentCollection'), mailboxIdSelected: anyNamed('mailboxIdSelected'), mailboxIdExpanded: anyNamed('mailboxIdExpanded'), ), ).thenAnswer( - (_) async => ( + (_) async => MailboxCollection( allMailboxes: currentMailboxList, defaultTree: defaultTree, personalTree: personalTree, @@ -865,9 +864,7 @@ void main() { verify( treeBuilder.generateMailboxTreeInUI( allMailboxes: anyNamed('allMailboxes'), - currentDefaultTree: anyNamed('currentDefaultTree'), - currentPersonalTree: anyNamed('currentPersonalTree'), - currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'), + currentCollection: anyNamed('currentCollection'), mailboxIdSelected: anyNamed('mailboxIdSelected'), mailboxIdExpanded: anyNamed('mailboxIdExpanded'), ), @@ -958,14 +955,12 @@ void main() { when( treeBuilder.generateMailboxTreeInUI( allMailboxes: anyNamed('allMailboxes'), - currentDefaultTree: anyNamed('currentDefaultTree'), - currentPersonalTree: anyNamed('currentPersonalTree'), - currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'), + currentCollection: anyNamed('currentCollection'), mailboxIdSelected: anyNamed('mailboxIdSelected'), mailboxIdExpanded: anyNamed('mailboxIdExpanded'), ), ).thenAnswer( - (_) async => ( + (_) async => MailboxCollection( allMailboxes: currentMailboxList, defaultTree: defaultTree, personalTree: personalTree, @@ -1008,9 +1003,7 @@ void main() { verify( treeBuilder.generateMailboxTreeInUI( allMailboxes: anyNamed('allMailboxes'), - currentDefaultTree: anyNamed('currentDefaultTree'), - currentPersonalTree: anyNamed('currentPersonalTree'), - currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'), + currentCollection: anyNamed('currentCollection'), mailboxIdSelected: anyNamed('mailboxIdSelected'), mailboxIdExpanded: anyNamed('mailboxIdExpanded'), ), diff --git a/test/features/rule_filter_creator/presentation/rule_filter_creator_controller_test.dart b/test/features/rule_filter_creator/presentation/rule_filter_creator_controller_test.dart index 171379a32..fd69f7fbc 100644 --- a/test/features/rule_filter_creator/presentation/rule_filter_creator_controller_test.dart +++ b/test/features/rule_filter_creator/presentation/rule_filter_creator_controller_test.dart @@ -32,6 +32,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oi import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; @@ -211,11 +212,9 @@ void main() { when(mockTreeBuilder.generateMailboxTreeInUI( allMailboxes: [spamMailbox], - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), )).thenAnswer((_) async { - return ( + return MailboxCollection( allMailboxes: [spamMailbox], defaultTree: MailboxTree(MailboxNode(spamMailbox)), personalTree: MailboxTree(MailboxNode.root()), @@ -292,11 +291,9 @@ void main() { when(mockTreeBuilder.generateMailboxTreeInUI( allMailboxes: [mailboxA], - currentDefaultTree: MailboxTree(MailboxNode.root()), - currentPersonalTree: MailboxTree(MailboxNode.root()), - currentTeamMailboxTree: MailboxTree(MailboxNode.root()), + currentCollection: MailboxCollection.empty(), )).thenAnswer((_) async { - return ( + return MailboxCollection( allMailboxes: [mailboxA], defaultTree: MailboxTree(MailboxNode.root()), personalTree: MailboxTree(MailboxNode(mailboxA)),