diff --git a/core/lib/presentation/views/quick_search/quick_search_input_form.dart b/core/lib/presentation/views/quick_search/quick_search_input_form.dart index b037a14f6..a7fe14cce 100644 --- a/core/lib/presentation/views/quick_search/quick_search_input_form.dart +++ b/core/lib/presentation/views/quick_search/quick_search_input_form.dart @@ -18,7 +18,10 @@ typedef ItemBuilder = Widget Function(BuildContext context, T itemData); typedef SuggestionSelectionCallback = void Function(T suggestion); typedef RecentSelectionCallback = void Function(R recent); typedef ErrorBuilder = Widget Function(BuildContext context, Object? error); -typedef ButtonActionBuilder = Widget Function(BuildContext context, dynamic action); +typedef ButtonActionBuilder = Widget Function( + BuildContext context, + dynamic action, + SuggestionsListState suggestionsListState); typedef ButtonActionCallback = void Function(dynamic action); typedef AnimationTransitionBuilder = Widget Function( @@ -315,8 +318,8 @@ class TypeAheadFieldQuickSearch extends StatefulWidget { final QuickSearchSuggestionsBoxDecoration suggestionsBoxDecoration; - /// Used to control the `_SuggestionsBox`. Allows manual control to - /// open, close, toggle, or resize the `_SuggestionsBox`. + /// Used to control the `SuggestionsBox`. Allows manual control to + /// open, close, toggle, or resize the `SuggestionsBox`. final QuickSearchSuggestionsBoxController? suggestionsBoxController; /// The duration to wait after the user stops typing before calling @@ -590,7 +593,7 @@ class _TypeAheadFieldQuickSearchState extends State @@ -648,7 +651,7 @@ class _TypeAheadFieldQuickSearchState extends State extends State( + final suggestionsList = SuggestionsList( suggestionsBox: _suggestionsBox, decoration: widget.suggestionsBoxDecoration, debounceDuration: widget.debounceDuration, @@ -905,8 +908,8 @@ class _TypeAheadFieldQuickSearchState extends State extends StatefulWidget { - final _SuggestionsBox? suggestionsBox; +class SuggestionsList extends StatefulWidget { + final SuggestionsBox? suggestionsBox; final TextEditingController? controller; final bool getImmediateSuggestions; final SuggestionSelectionCallback? onSuggestionSelected; @@ -942,7 +945,8 @@ class _SuggestionsList extends StatefulWidget { final SuggestionsCallback

? contactSuggestionCallback; final SuggestionSelectionCallback

? onContactSuggestionSelected; - const _SuggestionsList({ + const SuggestionsList({ + super.key, required this.suggestionsBox, this.controller, this.getImmediateSuggestions = false, @@ -981,10 +985,10 @@ class _SuggestionsList extends StatefulWidget { }); @override - _SuggestionsListState createState() => _SuggestionsListState(); + SuggestionsListState createState() => SuggestionsListState(); } -class _SuggestionsListState extends State<_SuggestionsList> +class SuggestionsListState extends State> with SingleTickerProviderStateMixin { Iterable? _suggestions; Iterable? _recentItems; @@ -998,7 +1002,7 @@ class _SuggestionsListState extends State<_SuggestionsList> late final ScrollController _scrollController = widget.scrollController ?? ScrollController(); - _SuggestionsListState() { + SuggestionsListState() { _controllerListener = () async { // If we came here because of a change in selected text, not because of // actual change in text @@ -1015,7 +1019,7 @@ class _SuggestionsListState extends State<_SuggestionsList> recentItems = await widget.fetchRecentActionCallback!(widget.controller!.text); } } catch (e) { - logError('_SuggestionsListState::_SuggestionsListState(): $e'); + logError('SuggestionsListState::SuggestionsListState(): $e'); } setState(() { @@ -1046,7 +1050,7 @@ class _SuggestionsListState extends State<_SuggestionsList> } @override - void didUpdateWidget(_SuggestionsList oldWidget) { + void didUpdateWidget(SuggestionsList oldWidget) { super.didUpdateWidget(oldWidget); widget.controller!.addListener(_controllerListener); _getSuggestions(); @@ -1215,7 +1219,7 @@ class _SuggestionsListState extends State<_SuggestionsList> if (listActionWidget != null) listActionWidget, if (loadingWidget != null) loadingWidget, if (widget.buttonShowAllResult != null && widget.controller?.text.isNotEmpty == true) - widget.buttonShowAllResult!(context, widget.controller?.text), + widget.buttonShowAllResult!(context, widget.controller?.text, this), if (listItemContactWidget.isNotEmpty) ... listItemContactWidget, if (listItemContactWidget.isNotEmpty && listItemSuggestionWidget.isNotEmpty) @@ -1253,7 +1257,7 @@ class _SuggestionsListState extends State<_SuggestionsList> if (listActionWidget != null) listActionWidget, if (loadingWidget != null) loadingWidget, if (widget.buttonShowAllResult != null && widget.controller?.text.isNotEmpty == true) - widget.buttonShowAllResult!(context, widget.controller?.text), + widget.buttonShowAllResult!(context, widget.controller?.text, this), if (_recentItems?.isNotEmpty == true && widget.itemRecentBuilder != null && widget.titleHeaderRecent != null) widget.titleHeaderRecent!, if (listItemRecent.isNotEmpty) @@ -1294,14 +1298,14 @@ class _SuggestionsListState extends State<_SuggestionsList> widget.buttonActionCallback?.call(action); invalidateSuggestions(); }, - child: widget.actionButtonBuilder!(context, action), + child: widget.actionButtonBuilder!(context, action, this), ), ), ); } else { return Padding( padding: const EdgeInsetsDirectional.only(end: 8, bottom: 8), - child: widget.actionButtonBuilder!(context, action) + child: widget.actionButtonBuilder!(context, action, this) ); } }) @@ -1732,7 +1736,7 @@ class QuickSearchTextFieldConfiguration { } } -class _SuggestionsBox { +class SuggestionsBox { static const int waitMetricsTimeoutMillis = 1000; static const double minOverlaySpace = 64.0; @@ -1751,7 +1755,7 @@ class _SuggestionsBox { double textBoxHeight = 100.0; late double directionUpOffset; - _SuggestionsBox( + SuggestionsBox( this.context, this.direction, this.autoFlipDirection, @@ -1950,7 +1954,7 @@ class _SuggestionsBox { /// Supply an instance of this class to the [TypeAhead.suggestionsBoxController] /// property to manually control the suggestions box class QuickSearchSuggestionsBoxController { - _SuggestionsBox? _suggestionsBox; + SuggestionsBox? _suggestionsBox; FocusNode? _effectiveFocusNode; /// Opens the suggestions box 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 b1a389a2e..898a5b5a3 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart @@ -446,13 +446,21 @@ class AdvancedFilterController extends BaseController { } void _updateFromField() { - listFromEmailAddress = List.from( - searchEmailFilter.from.map((address) => EmailAddress(null, address))); + if (searchEmailFilter.from.isNotEmpty) { + listFromEmailAddress = List.from( + searchEmailFilter.from.map((address) => EmailAddress(null, address))); + } else { + listFromEmailAddress.clear(); + } } void _updateToField() { - listToEmailAddress= List.from( - searchEmailFilter.to.map((address) => EmailAddress(null, address))); + if (searchEmailFilter.to.isNotEmpty) { + listToEmailAddress = List.from( + searchEmailFilter.to.map((address) => EmailAddress(null, address))); + } else { + listToEmailAddress.clear(); + } } void onSearchAction(BuildContext context) { 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 708f56902..d0e85765a 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -1579,10 +1579,6 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo _getAllIdentities(); } - void addFilterToSuggestionForm(QuickSearchFilter filter) { - searchController.addFilterToSuggestionForm(filter); - } - Future> quickSearchEmails(String query) async { if (sessionCurrent != null && accountId.value != null) { return searchController.quickSearchEmails( @@ -1733,7 +1729,7 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo : const None() ); - dispatchAction(StartSearchEmailAction(filter: QuickSearchFilter.folder)); + dispatchAction(StartSearchEmailAction()); } } @@ -1777,6 +1773,75 @@ class MailboxDashBoardController extends ReloadableController with UserSettingPo dispatchAction(StartSearchEmailAction()); } + void _deleteDateTimeSearchFilter() { + dispatchAction(ClearDateRangeToAdvancedSearch(EmailReceiveTimeType.allTime)); + searchController.updateFilterEmail( + emailReceiveTimeType: EmailReceiveTimeType.allTime, + startDateOption: const None(), + endDateOption: const None(), + beforeOption: const None() + ); + dispatchAction(StartSearchEmailAction()); + } + + void _deleteSortOrderSearchFilter() { + searchController.sortOrderFiltered.value = EmailSortOrderType.mostRecent; + searchController.updateFilterEmail( + sortOrderOption: EmailSortOrderType.mostRecent.getSortOrder()); + dispatchAction(StartSearchEmailAction()); + } + + void _deleteFromSearchFilter() { + searchController.updateFilterEmail(fromOption: const None()); + dispatchAction(StartSearchEmailAction(filter: QuickSearchFilter.from)); + } + + void _deleteToSearchFilter() { + searchController.updateFilterEmail(toOption: const None()); + dispatchAction(StartSearchEmailAction(filter: QuickSearchFilter.to)); + } + + void _deleteHasAttachmentSearchFilter() { + searchController.updateFilterEmail(hasAttachment: false); + dispatchAction(StartSearchEmailAction()); + } + + void _deleteFolderSearchFilter() { + searchController.updateFilterEmail( + mailboxOption: const None(), + beforeOption: const None(), + positionOption: searchController.sortOrderFiltered.value.isScrollByPosition() + ? const Some(0) + : const None() + ); + dispatchAction(StartSearchEmailAction()); + } + + void onDeleteSearchFilterAction(QuickSearchFilter searchFilter) { + switch(searchFilter) { + case QuickSearchFilter.dateTime: + _deleteDateTimeSearchFilter(); + break; + case QuickSearchFilter.sortBy: + _deleteSortOrderSearchFilter(); + break; + case QuickSearchFilter.from: + _deleteFromSearchFilter(); + break; + case QuickSearchFilter.hasAttachment: + _deleteHasAttachmentSearchFilter(); + break; + case QuickSearchFilter.to: + _deleteToSearchFilter(); + break; + case QuickSearchFilter.folder: + _deleteFolderSearchFilter(); + break; + default: + break; + } + } + bool isEmptyTrashBannerEnabledOnWeb( BuildContext context, PresentationMailbox? mailbox diff --git a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart index 3141caaed..fb7f7d146 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/search_controller.dart @@ -70,14 +70,16 @@ class SearchController extends BaseController with DateRangePickerMixin { searchEmailFilter.value = SearchEmailFilter.initial(); } - void addFilterToSuggestionForm(QuickSearchFilter filter) { - if (listFilterOnSuggestionForm.contains(filter)) { - listFilterOnSuggestionForm.remove(filter); - } else { - listFilterOnSuggestionForm.add(filter); + void addQuickSearchFilterToSuggestionSearchView(QuickSearchFilter searchFilter) { + if (!listFilterOnSuggestionForm.contains(searchFilter)) { + listFilterOnSuggestionForm.add(searchFilter); } } + void deleteQuickSearchFilterFromSuggestionSearchView(QuickSearchFilter searchFilter) { + listFilterOnSuggestionForm.remove(searchFilter); + } + Future> quickSearchEmails({ required Session session, required AccountId accountId, 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 7d917d57a..ca839eab0 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -19,6 +19,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dash 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/quick_search_filter.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/mailbox_dashboard_view_web_style.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; @@ -460,15 +461,15 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { controller: controller.listSearchFilterScrollController!, children: [ _buildQuickSearchFilterButton(context, QuickSearchFilter.folder), - const SizedBox(width: 8), + MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, _buildQuickSearchFilterButton(context, QuickSearchFilter.from), - const SizedBox(width: 8), + MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, _buildQuickSearchFilterButton(context, QuickSearchFilter.to), - const SizedBox(width: 8), + MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, _buildQuickSearchFilterButton(context, QuickSearchFilter.dateTime), - const SizedBox(width: 8), + MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, _buildQuickSearchFilterButton(context, QuickSearchFilter.hasAttachment), - const SizedBox(width: 8), + MailboxDashboardViewWebStyle.searchFilterSizeBoxMargin, ], ), ), @@ -484,8 +485,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } Widget _buildQuickSearchFilterButton( - BuildContext context, - QuickSearchFilter searchFilter + BuildContext context, + QuickSearchFilter searchFilter, ) { return Obx(() { final searchEmailFilter = controller.searchController.searchEmailFilter.value; @@ -503,6 +504,11 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { searchEmailFilter, sortOrderType); + EdgeInsetsGeometry? buttonPadding; + if (searchFilter != QuickSearchFilter.sortBy) { + buttonPadding = MailboxDashboardViewWebStyle.getSearchFilterButtonPadding(isSelected); + } + return SearchFilterButton( key: Key('${searchFilter.name}_search_filter_button'), searchFilter: searchFilter, @@ -516,12 +522,14 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { listAddressOfTo: listAddressOfTo, userName: userName, mailbox: mailbox, + buttonPadding: buttonPadding, onSelectSearchFilterAction: _onSelectSearchFilterAction, + onDeleteSearchFilterAction: controller.onDeleteSearchFilterAction, ); }); } - void _onSelectSearchFilterAction( + Future _onSelectSearchFilterAction( BuildContext context, QuickSearchFilter searchFilter, {RelativeRect? buttonPosition} diff --git a/lib/features/mailbox_dashboard/presentation/styles/mailbox_dashboard_view_web_style.dart b/lib/features/mailbox_dashboard/presentation/styles/mailbox_dashboard_view_web_style.dart new file mode 100644 index 000000000..cbfd2d891 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/styles/mailbox_dashboard_view_web_style.dart @@ -0,0 +1,14 @@ + +import 'package:flutter/cupertino.dart'; + +class MailboxDashboardViewWebStyle { + static const SizedBox searchFilterSizeBoxMargin = SizedBox(width: 8); + + static EdgeInsetsGeometry getSearchFilterButtonPadding(bool isSelected) { + if (isSelected) { + return const EdgeInsetsDirectional.only(start: 12, end: 4); + } else { + return const EdgeInsets.symmetric(horizontal: 12); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/styles/search_filter_button_style.dart b/lib/features/mailbox_dashboard/presentation/styles/search_filter_button_style.dart index 97abbe6aa..591e71ffa 100644 --- a/lib/features/mailbox_dashboard/presentation/styles/search_filter_button_style.dart +++ b/lib/features/mailbox_dashboard/presentation/styles/search_filter_button_style.dart @@ -5,9 +5,14 @@ class SearchFilterButtonStyle { static const double iconSize = 16; static const double deleteIconSize = 10; static const double spaceSize = 4; - static const double buttonSize = 35; - static const EdgeInsetsGeometry buttonPadding = EdgeInsets.symmetric(horizontal: 12, vertical: 8); + static EdgeInsetsGeometry getButtonPadding(bool isSelected) { + if (isSelected) { + return const EdgeInsetsDirectional.only(start: 12, end: 4, top: 4, bottom: 4); + } else { + return const EdgeInsets.symmetric(horizontal: 12, vertical: 8); + } + } static const EdgeInsetsGeometry elementPadding = EdgeInsetsDirectional.only(start: 8); static const BorderRadius borderRadius = BorderRadius.all(Radius.circular(10)); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart index 58127e75e..ba00140e8 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart @@ -15,6 +15,7 @@ typedef OnSelectSearchFilterAction = Function( BuildContext context, QuickSearchFilter searchFilter, {RelativeRect? buttonPosition}); +typedef OnDeleteSearchFilterAction = Function(QuickSearchFilter searchFilter); class SearchFilterButton extends StatelessWidget { @@ -32,7 +33,9 @@ class SearchFilterButton extends StatelessWidget { final UserName? userName; final PresentationMailbox? mailbox; final Color? backgroundColor; + final EdgeInsetsGeometry? buttonPadding; final OnSelectSearchFilterAction? onSelectSearchFilterAction; + final OnDeleteSearchFilterAction? onDeleteSearchFilterAction; const SearchFilterButton({ super.key, @@ -48,7 +51,9 @@ class SearchFilterButton extends StatelessWidget { this.userName, this.mailbox, this.backgroundColor, + this.buttonPadding, this.onSelectSearchFilterAction, + this.onDeleteSearchFilterAction, }); @override @@ -69,8 +74,7 @@ class SearchFilterButton extends StatelessWidget { decoration: BoxDecoration( borderRadius: SearchFilterButtonStyle.borderRadius, color: backgroundColor ?? searchFilter.getBackgroundColor(isFilterSelected: isSelected)), - padding: SearchFilterButtonStyle.buttonPadding, - height: SearchFilterButtonStyle.buttonSize, + padding: buttonPadding ?? SearchFilterButtonStyle.getButtonPadding(isSelected), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -104,9 +108,9 @@ class SearchFilterButton extends StatelessWidget { icon: imagePaths.icDeleteSelection, iconSize: SearchFilterButtonStyle.deleteIconSize, iconColor: AppColor.colorTextBody, - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(8), backgroundColor: Colors.transparent, - margin: SearchFilterButtonStyle.elementPadding, + onTapActionCallback: () => onDeleteSearchFilterAction?.call(searchFilter), ), ] ) diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart index b3e388ed1..d5dc1c5da 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart @@ -70,16 +70,19 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { QuickSearchFilter.last7Days, QuickSearchFilter.fromMe, ], - actionButtonBuilder: (context, filterAction) { + actionButtonBuilder: (context, filterAction, suggestionsListState) { if (filterAction is QuickSearchFilter) { - return buildListButtonForQuickSearchForm(context, filterAction); + return buildListButtonForQuickSearchForm( + context, + filterAction, + suggestionsListState); } else { return const SizedBox.shrink(); } }, buttonActionCallback: (filterAction) { if (filterAction is QuickSearchFilter) { - _dashBoardController.addFilterToSuggestionForm(filterAction); + _searchController.addQuickSearchFilterToSuggestionSearchView(filterAction); } }, listActionPadding: const EdgeInsets.only(left: 12, right: 12, top: 12, bottom: 6), @@ -94,7 +97,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { ) ) ), - buttonShowAllResult: (context, keyword) { + buttonShowAllResult: (context, keyword, _) { if (keyword is String) { return _buildShowAllResultButton(context, keyword); } else { @@ -221,7 +224,11 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { ); } - Widget buildListButtonForQuickSearchForm(BuildContext context, QuickSearchFilter searchFilter) { + Widget buildListButtonForQuickSearchForm( + BuildContext context, + QuickSearchFilter searchFilter, + SuggestionsListState suggestionsListState + ) { return Obx(() { final isSelected = searchFilter.isApplied(_searchController.listFilterOnSuggestionForm); @@ -230,6 +237,10 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin { imagePaths: _imagePaths, isSelected: isSelected, backgroundColor: searchFilter.getSuggestionBackgroundColor(isFilterSelected: isSelected), + onDeleteSearchFilterAction: (searchFilter) { + _searchController.deleteQuickSearchFilterFromSuggestionSearchView(searchFilter); + suggestionsListState.invalidateSuggestions(); + }, ); }); }