diff --git a/lib/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart b/lib/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart index 5798c6716..c39e3c83c 100644 --- a/lib/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart +++ b/lib/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -135,4 +136,11 @@ extension PresentationMailboxExtension on PresentationMailbox { AppRoutes.dashboard, router: NavigationRouter(mailboxId: id) ); + + String? get filterKeyword { + if (isFavorite) { + return KeyWordIdentifier.emailFlagged.value; + } + return null; + } } \ No newline at end of file diff --git a/lib/features/thread/domain/model/email_filter.dart b/lib/features/thread/domain/model/email_filter.dart index 3ec21aaef..5243aa37b 100644 --- a/lib/features/thread/domain/model/email_filter.dart +++ b/lib/features/thread/domain/model/email_filter.dart @@ -12,6 +12,18 @@ class EmailFilter with EquatableMixin { EmailFilter({this.filter, this.filterOption, this.mailboxId}); + EmailFilter copyWith({ + Filter? filter, + FilterMessageOption? filterOption, + MailboxId? mailboxId, + }) { + return EmailFilter( + filter: filter ?? this.filter, + filterOption: filterOption ?? this.filterOption, + mailboxId: mailboxId ?? this.mailboxId, + ); + } + @override List get props => [filter, filterOption, mailboxId]; } \ No newline at end of file diff --git a/lib/features/thread/presentation/extensions/handle_email_filter_extension.dart b/lib/features/thread/presentation/extensions/handle_email_filter_extension.dart new file mode 100644 index 000000000..3112f3d52 --- /dev/null +++ b/lib/features/thread/presentation/extensions/handle_email_filter_extension.dart @@ -0,0 +1,26 @@ +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; +import 'package:tmail_ui_user/features/thread/presentation/filters/mailbox_filter_builder.dart'; +import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; + +extension HandleEmailFilterExtension on ThreadController { + MailboxFilterBuilder get _mailboxFilterBuilder => MailboxFilterBuilder( + selectedMailbox: selectedMailbox, + filterMessageOption: + mailboxDashBoardController.filterMessageOption.value, + ); + + EmailFilter getEmailFilterForLoadMailbox() { + return _mailboxFilterBuilder.buildEmailFilterForLoadMailbox(); + } + + Filter getFilterConditionForLoadMailbox({PresentationEmail? oldestEmail}) { + return _mailboxFilterBuilder.buildFilterCondition(oldestEmail: oldestEmail); + } + + EmailFilterCondition getFilterCondition() { + return _mailboxFilterBuilder.buildDefaultMailboxFilter(); + } +} diff --git a/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart b/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart index d7369c210..cecedca91 100644 --- a/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart +++ b/lib/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart @@ -8,6 +8,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/state/clean_and_get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; +import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; diff --git a/lib/features/thread/presentation/filters/mailbox_filter_builder.dart b/lib/features/thread/presentation/filters/mailbox_filter_builder.dart new file mode 100644 index 000000000..94b08cd3d --- /dev/null +++ b/lib/features/thread/presentation/filters/mailbox_filter_builder.dart @@ -0,0 +1,134 @@ +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart'; +import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; +import 'package:model/extensions/presentation_mailbox_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; + +class MailboxFilterBuilder { + final PresentationMailbox? selectedMailbox; + final FilterMessageOption filterMessageOption; + + const MailboxFilterBuilder({ + required this.selectedMailbox, + required this.filterMessageOption, + }); + + MailboxId? get _mailboxId => selectedMailbox?.id; + + EmailFilter buildEmailFilterForLoadMailbox() { + final filterCondition = buildFilterCondition(); + + if (selectedMailbox?.isVirtualFolder == true) { + return EmailFilter(filter: filterCondition); + } + + return EmailFilter( + filter: filterCondition, + filterOption: filterMessageOption, + mailboxId: _mailboxId, + ); + } + + Filter buildFilterCondition({PresentationEmail? oldestEmail}) { + final before = oldestEmail?.receivedAt; + + if (selectedMailbox?.isFavorite == true) { + return _buildFavoriteMailboxFilter(before: before); + } + + if (selectedMailbox?.isActionRequired == true) { + return _buildActionRequiredMailboxFilter(before: before); + } + + return buildDefaultMailboxFilter(before: before); + } + + Filter _buildFavoriteMailboxFilter({UTCDate? before}) { + switch (filterMessageOption) { + case FilterMessageOption.unread: + return EmailFilterCondition( + notKeyword: KeyWordIdentifier.emailSeen.value, + hasKeyword: KeyWordIdentifier.emailFlagged.value, + before: before, + ); + + case FilterMessageOption.attachments: + return EmailFilterCondition( + hasAttachment: true, + hasKeyword: KeyWordIdentifier.emailFlagged.value, + before: before, + ); + + default: + return EmailFilterCondition( + hasKeyword: KeyWordIdentifier.emailFlagged.value, + before: before, + ); + } + } + + Filter _buildActionRequiredMailboxFilter({UTCDate? before}) { + switch (filterMessageOption) { + case FilterMessageOption.starred: + return LogicFilterOperator( + Operator.AND, + { + EmailFilterCondition( + hasKeyword: KeyWordIdentifier.emailFlagged.value, + before: before, + ), + EmailFilterCondition( + notKeyword: KeyWordIdentifier.emailSeen.value, + hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, + before: before, + ), + }, + ); + + case FilterMessageOption.attachments: + return EmailFilterCondition( + hasAttachment: true, + notKeyword: KeyWordIdentifier.emailSeen.value, + hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, + before: before, + ); + + default: + return EmailFilterCondition( + notKeyword: KeyWordIdentifier.emailSeen.value, + hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, + before: before, + ); + } + } + + EmailFilterCondition buildDefaultMailboxFilter({UTCDate? before}) { + switch (filterMessageOption) { + case FilterMessageOption.all: + return EmailFilterCondition(before: before); + + case FilterMessageOption.unread: + return EmailFilterCondition( + notKeyword: KeyWordIdentifier.emailSeen.value, + before: before, + ); + + case FilterMessageOption.attachments: + return EmailFilterCondition(hasAttachment: true, before: before); + + case FilterMessageOption.starred: + return EmailFilterCondition( + hasKeyword: KeyWordIdentifier.emailFlagged.value, + before: before, + ); + } + } +} diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 45755d9c7..fc1275322 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -9,15 +9,10 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/filter/filter.dart' show Filter; -import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart'; -import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; -import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; @@ -45,7 +40,6 @@ 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/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/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; @@ -65,6 +59,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_i import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/refresh_thread_detail_extension.dart'; @@ -604,141 +599,6 @@ class ThreadController extends BaseController with EmailActionController { } } - EmailFilter getEmailFilterForLoadMailbox({PresentationEmail? oldestEmail}) { - if (selectedMailbox?.isVirtualFolder == true) { - return EmailFilter( - filter: getFilterConditionForLoadMailbox(oldestEmail: oldestEmail), - ); - } else { - return EmailFilter( - filter: getFilterConditionForLoadMailbox(oldestEmail: oldestEmail), - filterOption: mailboxDashBoardController.filterMessageOption.value, - mailboxId: selectedMailboxId, - ); - } - } - - Filter getFilterConditionForLoadMailbox({ - PresentationEmail? oldestEmail, - }) { - final mailbox = selectedMailbox; - final filterOption = - mailboxDashBoardController.filterMessageOption.value; - final before = oldestEmail?.receivedAt; - - if (mailbox?.isFavorite == true) { - return _buildFavoriteMailboxFilter( - filterOption: filterOption, - before: before, - ); - } - - if (mailbox?.isActionRequired == true) { - return _buildActionRequiredMailboxFilter( - filterOption: filterOption, - before: before, - ); - } - - return getFilterCondition( - mailboxIdSelected: selectedMailboxId, - oldestEmail: oldestEmail, - ); - } - - Filter _buildFavoriteMailboxFilter({ - required FilterMessageOption filterOption, - UTCDate? before, - }) { - switch (filterOption) { - case FilterMessageOption.unread: - return EmailFilterCondition( - notKeyword: KeyWordIdentifier.emailSeen.value, - hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: before, - ); - - case FilterMessageOption.attachments: - return EmailFilterCondition( - hasAttachment: true, - hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: before, - ); - - default: - return EmailFilterCondition( - hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: before, - ); - } - } - - Filter _buildActionRequiredMailboxFilter({ - required FilterMessageOption filterOption, - UTCDate? before, - }) { - switch (filterOption) { - case FilterMessageOption.starred: - return LogicFilterOperator( - Operator.AND, - { - EmailFilterCondition( - hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: before, - ), - EmailFilterCondition( - notKeyword: KeyWordIdentifier.emailSeen.value, - hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, - before: before, - ), - }, - ); - - case FilterMessageOption.attachments: - return EmailFilterCondition( - hasAttachment: true, - notKeyword: KeyWordIdentifier.emailSeen.value, - hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, - before: before, - ); - - default: - return EmailFilterCondition( - notKeyword: KeyWordIdentifier.emailSeen.value, - hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, - before: before, - ); - } - } - - EmailFilterCondition getFilterCondition({PresentationEmail? oldestEmail, MailboxId? mailboxIdSelected}) { - switch(mailboxDashBoardController.filterMessageOption.value) { - case FilterMessageOption.all: - return EmailFilterCondition( - inMailbox: mailboxIdSelected, - before: oldestEmail?.receivedAt - ); - case FilterMessageOption.unread: - return EmailFilterCondition( - inMailbox: mailboxIdSelected, - notKeyword: KeyWordIdentifier.emailSeen.value, - before: oldestEmail?.receivedAt - ); - case FilterMessageOption.attachments: - return EmailFilterCondition( - inMailbox: mailboxIdSelected, - hasAttachment: true, - before: oldestEmail?.receivedAt - ); - case FilterMessageOption.starred: - return EmailFilterCondition( - inMailbox: mailboxIdSelected, - hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: oldestEmail?.receivedAt - ); - } - } - void refreshAllEmail() { if (searchController.isSearchEmailRunning) { consumeState(Stream.value(Right(SearchingState()))); diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index f786db1b7..d73f37438 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -12,16 +12,17 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:rxdart/subjects.dart'; import 'package:tmail_ui_user/features/base/extensions/handle_mailbox_action_type_extension.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/delete_email_permanently_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_restored_deleted_message_interactor.dart'; @@ -72,23 +73,22 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_e import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; -import 'package:tmail_ui_user/features/base/model/filter_filter.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'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; +import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart' + if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/usecases/delete_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/usecases/get_all_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/usecases/store_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; -import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/clean_and_get_emails_in_mailbox_interactor.dart'; @@ -103,11 +103,10 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_emai import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; -import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart' - if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart'; import 'package:tmail_ui_user/main/utils/toast_manager.dart'; import 'package:tmail_ui_user/main/utils/twake_app_manager.dart'; import 'package:uuid/uuid.dart'; @@ -485,10 +484,7 @@ void main() { testSession, testAccountId, limit: ThreadConstants.defaultLimit, sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(), - emailFilter: EmailFilter( - filter: EmailFilterCondition(inMailbox: testMailboxId), - filterOption: FilterMessageOption.all, - mailboxId: testMailboxId), + emailFilter: threadController.getEmailFilterForLoadMailbox(), getLatestChanges: false, propertiesCreated: ThreadConstants.propertiesDefault, propertiesUpdated: ThreadConstants.propertiesUpdatedDefault)); @@ -546,10 +542,7 @@ void main() { testSession, testAccountId, limit: ThreadConstants.defaultLimit, sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(), - emailFilter: EmailFilter( - filter: EmailFilterCondition(inMailbox: testMailboxId), - filterOption: FilterMessageOption.all, - mailboxId: testMailboxId), + emailFilter: threadController.getEmailFilterForLoadMailbox(), getLatestChanges: false, propertiesCreated: ThreadConstants.propertiesDefault, propertiesUpdated: ThreadConstants.propertiesUpdatedDefault diff --git a/test/features/search/verify_before_time_in_search_email_filter_test.dart b/test/features/search/verify_before_time_in_search_email_filter_test.dart index 458cbb533..d50bb3a5e 100644 --- a/test/features/search/verify_before_time_in_search_email_filter_test.dart +++ b/test/features/search/verify_before_time_in_search_email_filter_test.dart @@ -21,6 +21,7 @@ import 'package:rxdart/rxdart.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart'; +import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/delete_email_permanently_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_restored_deleted_message_interactor.dart'; @@ -55,7 +56,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_email_sort_order_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart'; @@ -70,7 +70,6 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/get_all_sen import 'package:tmail_ui_user/features/sending_queue/domain/usecases/store_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; -import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; @@ -86,6 +85,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_emai import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; @@ -95,7 +95,6 @@ import 'package:uuid/uuid.dart'; import '../../fixtures/account_fixtures.dart'; import '../../fixtures/session_fixtures.dart'; - import 'verify_before_time_in_search_email_filter_test.mocks.dart'; mockControllerCallback() => InternalFinalCallback(callback: () {}); @@ -858,11 +857,7 @@ void main() { AccountFixtures.aliceAccountId, ), propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - emailFilter: EmailFilter( - filter: threadController.getFilterCondition(mailboxIdSelected: threadController.selectedMailboxId), - filterOption: mailboxDashboardController.filterMessageOption.value, - mailboxId: threadController.selectedMailboxId, - ), + emailFilter: threadController.getEmailFilterForLoadMailbox(), )).called(1); verify(mockSearchEmailInteractor.execute( @@ -1026,11 +1021,7 @@ void main() { AccountFixtures.aliceAccountId, ), propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - emailFilter: EmailFilter( - filter: threadController.getFilterCondition(mailboxIdSelected: threadController.selectedMailboxId), - filterOption: mailboxDashboardController.filterMessageOption.value, - mailboxId: threadController.selectedMailboxId, - ), + emailFilter: threadController.getEmailFilterForLoadMailbox(), )).called(1); verify(mockSearchEmailInteractor.execute( @@ -1139,11 +1130,7 @@ void main() { AccountFixtures.aliceAccountId, ), propertiesUpdated: ThreadConstants.propertiesUpdatedDefault, - emailFilter: EmailFilter( - filter: threadController.getFilterCondition(mailboxIdSelected: threadController.selectedMailboxId), - filterOption: mailboxDashboardController.filterMessageOption.value, - mailboxId: threadController.selectedMailboxId, - ), + emailFilter: threadController.getEmailFilterForLoadMailbox(), )).called(1); verify(mockSearchEmailInteractor.execute( diff --git a/test/features/thread/presentation/filters/mailbox_filter_builder_test.dart b/test/features/thread/presentation/filters/mailbox_filter_builder_test.dart new file mode 100644 index 000000000..7a0782cda --- /dev/null +++ b/test/features/thread/presentation/filters/mailbox_filter_builder_test.dart @@ -0,0 +1,370 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart'; +import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; +import 'package:tmail_ui_user/features/thread/presentation/filters/mailbox_filter_builder.dart'; + +void main() { + PresentationMailbox mailbox({ + bool favorite = false, + bool actionRequired = false, + }) { + if (favorite) { + return PresentationMailbox.favoriteFolder; + } else if (actionRequired) { + return PresentationMailbox.actionRequiredFolder; + } else { + return PresentationMailbox( + MailboxId(Id('inbox')), + name: MailboxName('Inbox'), + role: PresentationMailbox.roleInbox, + ); + } + } + + UTCDate beforeDate() => UTCDate(DateTime.utc(2024, 1, 1)); + + group('buildEmailFilterForLoadMailbox', () { + test( + 'ActionRequired mailbox → returns EmailFilter without mailboxId and filterOption', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.all, + ); + + final result = builder.buildEmailFilterForLoadMailbox(); + + expect(result.filter, isA()); + expect(result.mailboxId, isNull); + expect(result.filterOption, isNull); + }, + ); + + test( + 'Normal mailbox → returns EmailFilter with filterOption applied', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.unread, + ); + + final result = builder.buildEmailFilterForLoadMailbox(); + + expect(result.filterOption, FilterMessageOption.unread); + }, + ); + }); + + group('Favorite mailbox - buildFilterCondition', () { + test( + 'Unread filter → flagged AND not seen', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(favorite: true), + filterMessageOption: FilterMessageOption.unread, + ); + + final filter = builder.buildFilterCondition( + oldestEmail: null, + ) as EmailFilterCondition; + + expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + }, + ); + + test( + 'Attachments filter → hasAttachment AND flagged', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(favorite: true), + filterMessageOption: FilterMessageOption.attachments, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasAttachment, true); + expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); + }, + ); + + test( + 'Starred filter → flagged only', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(favorite: true), + filterMessageOption: FilterMessageOption.starred, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); + expect(filter.notKeyword, isNull); + }, + ); + }); + + group('ActionRequired mailbox - buildFilterCondition', () { + test( + 'Starred filter → AND(flagged, needsAction AND not seen)', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.starred, + ); + + final filter = builder.buildFilterCondition(); + + expect(filter, isA()); + + final logic = filter as LogicFilterOperator; + expect(logic.operator, Operator.AND); + expect(logic.conditions.length, 2); + }, + ); + + test( + 'Attachments filter → hasAttachment AND needsAction AND not seen', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.attachments, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasAttachment, true); + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + expect( + filter.hasKeyword, + KeyWordIdentifierExtension.needsActionMail.value, + ); + }, + ); + + test( + 'Unread filter → needsAction AND not seen', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.unread, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + expect( + filter.hasKeyword, + KeyWordIdentifierExtension.needsActionMail.value, + ); + }, + ); + }); + + group('Default mailbox - buildFilterCondition', () { + test( + 'All filter → no keyword or attachment constraints', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.all, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasKeyword, isNull); + expect(filter.notKeyword, isNull); + expect(filter.hasAttachment, isNull); + }, + ); + + test( + 'Unread filter → not seen', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.unread, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + }, + ); + + test( + 'Attachments filter → hasAttachment', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.attachments, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasAttachment, true); + }, + ); + + test( + 'Starred filter → flagged', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.starred, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); + }, + ); + }); + + group('Pagination (before) handling', () { + test( + 'Oldest email provided → before date propagated into filter', + () { + final before = beforeDate(); + + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.all, + ); + + final filter = builder.buildFilterCondition( + oldestEmail: PresentationEmail(receivedAt: before), + ) as EmailFilterCondition; + + expect(filter.before, before); + }, + ); + }); + + group('MailboxFilterBuilder - edge cases and regression', () { + test( + 'No selectedMailbox → treated as default mailbox', + () { + const builder = MailboxFilterBuilder( + selectedMailbox: null, + filterMessageOption: FilterMessageOption.unread, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + }, + ); + + test( + 'Favorite mailbox + All filter → flagged only', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(favorite: true), + filterMessageOption: FilterMessageOption.all, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); + expect(filter.notKeyword, isNull); + }, + ); + + test( + 'ActionRequired mailbox + All filter → needsAction AND not seen', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.all, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + expect( + filter.hasKeyword, + KeyWordIdentifierExtension.needsActionMail.value, + ); + }, + ); + + test( + 'Favorite mailbox takes precedence over ActionRequired', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox( + favorite: true, + actionRequired: true, + ), + filterMessageOption: FilterMessageOption.unread, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); + expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value); + }, + ); + + test( + 'Before date propagated into all conditions of LogicFilterOperator', + () { + final before = beforeDate(); + + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.starred, + ); + + final filter = builder.buildFilterCondition( + oldestEmail: PresentationEmail(receivedAt: before), + ) as LogicFilterOperator; + + for (final condition in filter.conditions) { + final emailFilter = condition as EmailFilterCondition; + expect(emailFilter.before, before); + } + }, + ); + + test( + 'buildEmailFilterForLoadMailbox uses buildFilterCondition result', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(), + filterMessageOption: FilterMessageOption.unread, + ); + + final emailFilter = builder.buildEmailFilterForLoadMailbox(); + + final condition = emailFilter.filter as EmailFilterCondition; + expect(condition.notKeyword, KeyWordIdentifier.emailSeen.value); + }, + ); + + test( + 'Filter never contains same keyword in both hasKeyword and notKeyword', + () { + final builder = MailboxFilterBuilder( + selectedMailbox: mailbox(actionRequired: true), + filterMessageOption: FilterMessageOption.unread, + ); + + final filter = builder.buildFilterCondition() as EmailFilterCondition; + + expect(filter.hasKeyword == filter.notKeyword, false); + }, + ); + }); +}