From bc0f6a4ac6c057729d95df882cd7c8f369a404aa Mon Sep 17 00:00:00 2001 From: Dat Vu Date: Fri, 3 Apr 2026 12:16:03 +0700 Subject: [PATCH] TF-4356 Add filter event mail in search (#4357) * Add `Events` filter checkbox in Advanced Search form * Perform search email by `Events` filter in Advanced Search form * Add `Event` quick search button in Search view * Use `keywords` replace to `headers` for search event filter --- .../advanced_filter_controller.dart | 48 ++++++++++++++----- .../controller/input_field_focus_manager.dart | 3 ++ .../mailbox_dashboard_controller.dart | 1 + ...select_search_filter_action_extension.dart | 18 +++++-- .../mailbox_dashboard_view_web.dart | 13 ++++- .../model/search/quick_search_filter.dart | 10 ++++ ...vanced_search_filter_form_bottom_view.dart | 21 ++++++++ .../update_search_filter_extension.dart | 15 +++++- .../presentation/search_email_controller.dart | 25 ++++++++-- .../email/presentation/search_email_view.dart | 10 +++- lib/l10n/intl_messages.arb | 6 +++ lib/main/localizations/app_localizations.dart | 7 +++ .../keyword_identifier_extension.dart | 1 + 13 files changed, 154 insertions(+), 24 deletions(-) diff --git a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart index e68544c25..1b95e2869 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart @@ -37,6 +37,7 @@ class AdvancedFilterController extends BaseController { final hasAttachment = false.obs; final isStarred = false.obs; final isUnread = false.obs; + final hasEvents = false.obs; final startDate = Rxn(); final endDate = Rxn(); final sortOrderType = SearchEmailFilter.defaultSortOrder.obs; @@ -188,10 +189,12 @@ class AdvancedFilterController extends BaseController { final unreadOption = Some(isUnread.value); - final hasKeywordOption = option( - isStarred.isTrue, - {KeyWordIdentifier.emailFlagged.value}, - ); + final listKeywords = { + if(isStarred.isTrue) KeyWordIdentifier.emailFlagged.value, + if(hasEvents.isTrue) KeyWordIdentifierExtension.eventsMail.value, + }; + final hasKeywordOption = + optionOf(listKeywords.isNotEmpty ? listKeywords : null); final labelOption = optionOf(selectedLabel.value); @@ -296,6 +299,9 @@ class AdvancedFilterController extends BaseController { isStarred.value = _memorySearchFilter.hasKeyword .contains(KeyWordIdentifier.emailFlagged.value); + hasEvents.value = _memorySearchFilter.hasKeyword + .contains(KeyWordIdentifierExtension.eventsMail.value); + if (_memorySearchFilter.from.isEmpty) { listFromEmailAddress.clear(); } else { @@ -486,6 +492,7 @@ class AdvancedFilterController extends BaseController { hasAttachment.value = false; isUnread.value = false; isStarred.value = false; + hasEvents.value = false; selectedFolderName.value = null; listFromEmailAddress.clear(); listToEmailAddress.clear(); @@ -568,21 +575,36 @@ class AdvancedFilterController extends BaseController { void onStarredCheckboxChanged(bool? isChecked) { isStarred.value = isChecked ?? false; - final listHasKeywordFiltered = _memorySearchFilter.hasKeyword; - if (isStarred.isTrue) { - listHasKeywordFiltered.add(KeyWordIdentifier.emailFlagged.value); - } else { - listHasKeywordFiltered.remove(KeyWordIdentifier.emailFlagged.value); - } - _updateMemorySearchFilter( - hasKeywordOption: Some(listHasKeywordFiltered), + _updateKeywordsSearchFilter( + isStarred.isTrue, + KeyWordIdentifier.emailFlagged, ); } void onUnreadCheckboxChanged(bool? isChecked) { isUnread.value = isChecked ?? false; _updateMemorySearchFilter( - unreadOption: isStarred.isTrue ? const Some(true) : const None(), + unreadOption: isUnread.isTrue ? const Some(true) : const None(), + ); + } + + void _updateKeywordsSearchFilter(bool isChecked, KeyWordIdentifier keyword) { + final listHasKeywordFiltered = _memorySearchFilter.hasKeyword; + if (isChecked) { + listHasKeywordFiltered.add(keyword.value); + } else { + listHasKeywordFiltered.remove(keyword.value); + } + _updateMemorySearchFilter( + hasKeywordOption: Some(listHasKeywordFiltered), + ); + } + + void onEventsCheckboxChanged(bool? isChecked) { + hasEvents.value = isChecked ?? false; + _updateKeywordsSearchFilter( + hasEvents.isTrue, + KeyWordIdentifierExtension.eventsMail, ); } diff --git a/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart b/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart index c414905a5..693baa261 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/input_field_focus_manager.dart @@ -12,6 +12,7 @@ class InputFieldFocusManager { late FocusNode attachmentCheckboxFocusNode; late FocusNode starredCheckboxFocusNode; late FocusNode unreadCheckboxFocusNode; + late FocusNode eventsCheckboxFocusNode; InputFieldFocusManager() { fromFieldFocusNode = FocusNode(); @@ -23,6 +24,7 @@ class InputFieldFocusManager { attachmentCheckboxFocusNode = FocusNode(); starredCheckboxFocusNode = FocusNode(); unreadCheckboxFocusNode = FocusNode(); + eventsCheckboxFocusNode = FocusNode(); } factory InputFieldFocusManager.initial() { @@ -39,5 +41,6 @@ class InputFieldFocusManager { attachmentCheckboxFocusNode.dispose(); starredCheckboxFocusNode.dispose(); unreadCheckboxFocusNode.dispose(); + eventsCheckboxFocusNode.dispose(); } } \ No newline at end of file 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 1eba33487..705bab7ba 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -2394,6 +2394,7 @@ class MailboxDashBoardController extends ReloadableController case QuickSearchFilter.starred: case QuickSearchFilter.unread: case QuickSearchFilter.labels: + case QuickSearchFilter.events: deleteQuickSearchFilter(filter: searchFilter); break; default: diff --git a/lib/features/mailbox_dashboard/presentation/extensions/select_search_filter_action_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/select_search_filter_action_extension.dart index a7662a0a6..6454797b2 100644 --- a/lib/features/mailbox_dashboard/presentation/extensions/select_search_filter_action_extension.dart +++ b/lib/features/mailbox_dashboard/presentation/extensions/select_search_filter_action_extension.dart @@ -1,14 +1,15 @@ import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:labels/model/label.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart'; extension SelectSearchFilterActionExtension on MailboxDashBoardController { - void selectStarredSearchFilter() { + void selectKeywordSearchFilter(KeyWordIdentifier keyword) { final listHasKeywordFiltered = searchController.listHasKeywordFiltered; - listHasKeywordFiltered.add(KeyWordIdentifier.emailFlagged.value); + listHasKeywordFiltered.add(keyword.value); searchController.updateFilterEmail( hasKeywordOption: Some(listHasKeywordFiltered), ); @@ -26,12 +27,18 @@ extension SelectSearchFilterActionExtension on MailboxDashBoardController { searchController.updateFilterEmail( hasKeywordOption: Some(listHasKeywordFiltered), ); - dispatchAction(StartSearchEmailAction()); } void deleteUnreadSearchFilter() { searchController.updateFilterEmail(unreadOption: const None()); - dispatchAction(StartSearchEmailAction()); + } + + void deleteEventsSearchFilter() { + final listHasKeywordFiltered = searchController.listHasKeywordFiltered; + listHasKeywordFiltered.remove(KeyWordIdentifierExtension.eventsMail.value); + searchController.updateFilterEmail( + hasKeywordOption: Some(listHasKeywordFiltered), + ); } void deleteQuickSearchFilter({required QuickSearchFilter filter}) { @@ -45,6 +52,9 @@ extension SelectSearchFilterActionExtension on MailboxDashBoardController { case QuickSearchFilter.unread: deleteUnreadSearchFilter(); break; + case QuickSearchFilter.events: + deleteEventsSearchFilter(); + break; default: break; } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index c7f748f8e..d76a53173 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -6,6 +6,7 @@ import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/extensions/session_extension.dart'; import 'package:tmail_ui_user/features/base/widget/clean_messages_banner.dart'; @@ -698,6 +699,11 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { _buildQuickSearchFilterButton(context, QuickSearchFilter.starred), MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, _buildQuickSearchFilterButton(context, QuickSearchFilter.unread), + MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, + _buildQuickSearchFilterButton( + context, + QuickSearchFilter.events, + ), ], ), ), @@ -833,11 +839,16 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { controller.selectFolderSearchFilter(); break; case QuickSearchFilter.starred: - controller.selectStarredSearchFilter(); + controller.selectKeywordSearchFilter(KeyWordIdentifier.emailFlagged); break; case QuickSearchFilter.unread: controller.selectUnreadSearchFilter(); break; + case QuickSearchFilter.events: + controller.selectKeywordSearchFilter( + KeyWordIdentifierExtension.eventsMail, + ); + break; case QuickSearchFilter.labels: final listLabels = controller.labelController.labels; final selectedLabel = controller.searchController.labelFiltered; diff --git a/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart b/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart index b3f21b3d5..d4bc9338d 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart @@ -7,6 +7,7 @@ import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:labels/extensions/label_extension.dart'; import 'package:labels/model/label.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; @@ -20,6 +21,7 @@ enum QuickSearchFilter { fromMe, starred, unread, + events, sortBy, dateTime, from, @@ -67,6 +69,8 @@ enum QuickSearchFilter { return AppLocalizations.of(context).unread; case QuickSearchFilter.labels: return label?.safeDisplayName ?? AppLocalizations.of(context).allLabels; + case QuickSearchFilter.events: + return AppLocalizations.of(context).events; } } @@ -94,6 +98,8 @@ enum QuickSearchFilter { return isSelected ? imagePaths.icSelectedSB : imagePaths.icUnread; case QuickSearchFilter.labels: return imagePaths.icTag; + case QuickSearchFilter.events: + return imagePaths.icCalendarEvent; } } @@ -162,6 +168,10 @@ enum QuickSearchFilter { return searchFilter.unread; case QuickSearchFilter.labels: return searchFilter.label != null; + case QuickSearchFilter.events: + return searchFilter + .hasKeyword + .contains(KeyWordIdentifierExtension.eventsMail.value) == true; } } diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart index d93c8ed55..e1699b47a 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart @@ -39,6 +39,10 @@ class AdvancedSearchFilterFormBottomView extends GetWidget CustomIconLabeledCheckbox( + label: AppLocalizations.of(context).events, + svgIconPath: controller.imagePaths.icCheckboxUnselected, + selectedSvgIconPath: controller.imagePaths.icCheckboxSelected, + focusNode: currentFocusNode, + gap: 8.0, + value: controller.hasEvents.value, + onChanged: controller.onEventsCheckboxChanged, + ), + ); + } + void _onClickCancelButton() { controller.clearSearchFilter(); popBack(); diff --git a/lib/features/search/email/presentation/extension/update_search_filter_extension.dart b/lib/features/search/email/presentation/extension/update_search_filter_extension.dart index c30dc72d0..6f81760f8 100644 --- a/lib/features/search/email/presentation/extension/update_search_filter_extension.dart +++ b/lib/features/search/email/presentation/extension/update_search_filter_extension.dart @@ -1,5 +1,7 @@ import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:labels/model/label.dart'; +import 'package:model/extensions/keyword_identifier_extension.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/quick_search_filter.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart'; @@ -12,7 +14,18 @@ extension UpdateSearchFilterExtension on SearchEmailController { updateSimpleSearchFilter(hasAttachmentOption: const None()); break; case QuickSearchFilter.starred: - updateSimpleSearchFilter(hasKeywordOption: const None()); + final keywords = Set.from(listHasKeywordFiltered) + ..remove(KeyWordIdentifier.emailFlagged.value); + updateSimpleSearchFilter( + hasKeywordOption: keywords.isEmpty ? const None() : Some(keywords), + ); + break; + case QuickSearchFilter.events: + final keywords = Set.from(listHasKeywordFiltered) + ..remove(KeyWordIdentifierExtension.eventsMail.value); + updateSimpleSearchFilter( + hasKeywordOption: keywords.isEmpty ? const None() : Some(keywords), + ); break; case QuickSearchFilter.unread: updateSimpleSearchFilter(unreadOption: const None()); diff --git a/lib/features/search/email/presentation/search_email_controller.dart b/lib/features/search/email/presentation/search_email_controller.dart index bd54e9a9a..62289190c 100644 --- a/lib/features/search/email/presentation/search_email_controller.dart +++ b/lib/features/search/email/presentation/search_email_controller.dart @@ -24,6 +24,7 @@ import 'package:model/email/prefix_email_address.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/email/read_actions.dart'; import 'package:model/extensions/email_address_extension.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/list_presentation_email_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; @@ -640,10 +641,10 @@ class SearchEmailController extends BaseController _searchEmailAction(); } - void selectStarredSearchFilter() { + void selectKeywordsSearchFilter(KeyWordIdentifier keyword) { final listKeyword = listHasKeywordFiltered; - if (!listKeyword.contains(KeyWordIdentifier.emailFlagged.value)) { - listKeyword.add(KeyWordIdentifier.emailFlagged.value); + if (!listKeyword.contains(keyword.value)) { + listKeyword.add(keyword.value); } _updateSimpleSearchFilter(hasKeywordOption: Some(listKeyword)); _searchEmailAction(); @@ -1090,6 +1091,9 @@ class SearchEmailController extends BaseController case QuickSearchFilter.unread: _deleteUnreadSearchFilter(); break; + case QuickSearchFilter.events: + _deleteEventsSearchFilter(); + break; case QuickSearchFilter.labels: deleteQuickSearchFilter(filter: QuickSearchFilter.labels); break; @@ -1140,7 +1144,11 @@ class SearchEmailController extends BaseController } void _deleteStarredSearchFilter() { - _updateSimpleSearchFilter(hasKeywordOption: const None()); + final keywords = {...listHasKeywordFiltered} + ..remove(KeyWordIdentifier.emailFlagged.value); + _updateSimpleSearchFilter( + hasKeywordOption: keywords.isEmpty ? const None() : Some(keywords), + ); _searchEmailAction(); } @@ -1149,6 +1157,15 @@ class SearchEmailController extends BaseController _searchEmailAction(); } + void _deleteEventsSearchFilter() { + final keywords = {...listHasKeywordFiltered} + ..remove(KeyWordIdentifierExtension.eventsMail.value); + _updateSimpleSearchFilter( + hasKeywordOption: keywords.isEmpty ? const None() : Some(keywords), + ); + _searchEmailAction(); + } + void clearAllSearchFilterApplied() { textInputSearchController.clear(); currentSearchText.value = ''; diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index 26064414d..c42b62c2d 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -11,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:labels/model/label.dart'; import 'package:model/model.dart'; @@ -302,6 +303,8 @@ class SearchEmailView extends GetWidget SearchEmailViewStyle.searchFilterSizeBoxMargin, _buildSearchFilterButton(context, QuickSearchFilter.unread), SearchEmailViewStyle.searchFilterSizeBoxMargin, + _buildSearchFilterButton(context, QuickSearchFilter.events), + SearchEmailViewStyle.searchFilterSizeBoxMargin, _buildSearchFilterButton(context, QuickSearchFilter.sortBy), ], ); @@ -412,11 +415,16 @@ class SearchEmailView extends GetWidget controller.selectMailboxForSearchFilter(controller.mailboxFiltered); break; case QuickSearchFilter.starred: - controller.selectStarredSearchFilter(); + controller.selectKeywordsSearchFilter(KeyWordIdentifier.emailFlagged); break; case QuickSearchFilter.unread: controller.selectUnreadSearchFilter(); break; + case QuickSearchFilter.events: + controller.selectKeywordsSearchFilter( + KeyWordIdentifierExtension.eventsMail, + ); + break; case QuickSearchFilter.labels: final listLabels = controller.mailboxDashBoardController.labelController.labels; final selectedLabel = controller.labelFiltered; diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 5216a394a..1cf1f8c46 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -5459,5 +5459,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "events": "Events", + "@events": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 61f2497d2..b388af26a 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -5776,4 +5776,11 @@ class AppLocalizations { name: 'labelDescriptionHintText', ); } + + String get events { + return Intl.message( + 'Events', + name: 'events', + ); + } } diff --git a/model/lib/extensions/keyword_identifier_extension.dart b/model/lib/extensions/keyword_identifier_extension.dart index e9752a240..c9ec938d7 100644 --- a/model/lib/extensions/keyword_identifier_extension.dart +++ b/model/lib/extensions/keyword_identifier_extension.dart @@ -6,6 +6,7 @@ import 'package:model/email/read_actions.dart'; extension KeyWordIdentifierExtension on KeyWordIdentifier { static final unsubscribeMail = KeyWordIdentifier('\$unsubscribe'); static final needsActionMail = KeyWordIdentifier('needs-action'); + static final eventsMail = KeyWordIdentifier('event'); String generatePath() => '${PatchObject.keywordsProperty}/$value';