diff --git a/lib/features/base/mixin/popup_context_menu_action_mixin.dart b/lib/features/base/mixin/popup_context_menu_action_mixin.dart index 61ed947af..40c0e9965 100644 --- a/lib/features/base/mixin/popup_context_menu_action_mixin.dart +++ b/lib/features/base/mixin/popup_context_menu_action_mixin.dart @@ -79,7 +79,7 @@ mixin PopupContextMenuActionMixin { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(6)), ), - constraints: const BoxConstraints(maxWidth: 300), + constraints: const BoxConstraints(maxWidth: 300, minWidth: 178), items: popupMenuItems, ); } diff --git a/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart index 78d8e1212..8d13036f2 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart @@ -82,6 +82,7 @@ class ProfileSettingMenuOverlay extends StatelessWidget { }, )) .toList(), + const SizedBox(height: 8), ], ), PositionedDirectional( diff --git a/lib/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart index 1928d55dd..288bd0faa 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart @@ -106,9 +106,9 @@ class _FilterMessageButtonState extends State { ); } else { return Material( + key: _buttonKey, type: MaterialType.transparency, child: InkWell( - key: _buttonKey, onTapDown: (details) => _onTapDownAction(context, details), borderRadius: FilterMessageButtonStyle.borderRadius, child: childItem, 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 cdb693224..2ddd346fb 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 @@ -11,13 +11,11 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/search_filter_button_style.dart'; typedef OnSelectSearchFilterAction = Function( - BuildContext context, - QuickSearchFilter searchFilter, - {RelativeRect? buttonPosition}); + BuildContext context, QuickSearchFilter searchFilter, + {RelativeRect? buttonPosition}); typedef OnDeleteSearchFilterAction = Function(QuickSearchFilter searchFilter); -class SearchFilterButton extends StatelessWidget { - +class SearchFilterButton extends StatefulWidget { static const int _titleCharactersMaximum = 20; final QuickSearchFilter searchFilter; @@ -55,128 +53,197 @@ class SearchFilterButton extends StatelessWidget { this.onDeleteSearchFilterAction, }); + @override + State createState() => _SearchFilterButtonState(); +} + +class _SearchFilterButtonState extends State { + final GlobalKey _buttonKey = GlobalKey(); + final layerLink = LayerLink(); + @override Widget build(BuildContext context) { - final filterTitle = searchFilter.getTitle( + final filterTitle = widget.searchFilter.getTitle( context, - receiveTimeType: receiveTimeType, - startDate: startDate, - endDate: startDate, - sortOrderType: sortOrderType, - listAddressOfFrom: listAddressOfFrom, - mailbox: mailbox, - listAddressOfTo: listAddressOfTo, + receiveTimeType: widget.receiveTimeType, + startDate: widget.startDate, + endDate: widget.startDate, + sortOrderType: widget.sortOrderType, + listAddressOfFrom: widget.listAddressOfFrom, + mailbox: widget.mailbox, + listAddressOfTo: widget.listAddressOfTo, ); final deleteButtonWidget = TMailButtonWidget.fromIcon( - icon: imagePaths.icDeleteSelection, + icon: widget.imagePaths.icDeleteSelection, iconSize: SearchFilterButtonStyle.deleteIconSize, iconColor: AppColor.colorTextBody, padding: const EdgeInsets.all(8), backgroundColor: Colors.transparent, - onTapActionCallback: () => onDeleteSearchFilterAction?.call(searchFilter), + onTapActionCallback: () => widget.onDeleteSearchFilterAction?.call( + widget.searchFilter, + ), ); final listComponentsWidget = [ SvgPicture.asset( - searchFilter.getIcon(imagePaths, isSelected: isSelected), - width: SearchFilterButtonStyle.iconSize, - height: SearchFilterButtonStyle.iconSize, - colorFilter: searchFilter.getIconColor(isSelected: isSelected).asFilter(), - fit: BoxFit.fill - ), + widget.searchFilter.getIcon( + widget.imagePaths, + isSelected: widget.isSelected, + ), + width: SearchFilterButtonStyle.iconSize, + height: SearchFilterButtonStyle.iconSize, + colorFilter: widget.searchFilter + .getIconColor( + isSelected: widget.isSelected, + ) + .asFilter(), + fit: BoxFit.fill), const SizedBox(width: SearchFilterButtonStyle.spaceSize), Text( - filterTitle.length > _titleCharactersMaximum - ? '${filterTitle.substring(0, _titleCharactersMaximum)}...' - : filterTitle, + filterTitle.length > SearchFilterButton._titleCharactersMaximum + ? '${filterTitle.substring( + 0, + SearchFilterButton._titleCharactersMaximum, + )}...' + : filterTitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: SearchFilterButtonStyle.titleStyle, ), - if (searchFilter.isArrowDownIconSupported()) + if (widget.searchFilter.isArrowDownIconSupported()) Padding( padding: SearchFilterButtonStyle.elementPadding, child: SvgPicture.asset( - imagePaths.icDropDown, + widget.imagePaths.icDropDown, width: SearchFilterButtonStyle.iconSize, height: SearchFilterButtonStyle.iconSize, colorFilter: AppColor.colorTextBody.asFilter(), - fit: BoxFit.fill + fit: BoxFit.fill, ), ), ]; - final childItem = _buildContainerForComponents( - children: [ - ...listComponentsWidget, - if (isSelected) deleteButtonWidget, - ] - ); - - if (onSelectSearchFilterAction == null) { - return childItem; - } - - if (!searchFilter.isOnTapWithPositionActionSupported(context, responsiveUtils)) { - return Material( - type: MaterialType.transparency, - child: InkWell( - onTap: !searchFilter.isOnTapWithPositionActionSupported(context, responsiveUtils) - ? () => _onTapAction(context) - : null, - onTapDown: searchFilter.isOnTapWithPositionActionSupported(context, responsiveUtils) && !isSelected - ? (details) => _onTapDownAction(context, details) - : null, - borderRadius: SearchFilterButtonStyle.borderRadius, - child: childItem - ), + if (widget.onSelectSearchFilterAction == null) { + return _buildContainerForComponents( + children: [ + ...listComponentsWidget, + if (widget.isSelected) deleteButtonWidget, + ], ); - } - - return _buildContainerForComponents( - children: [ - InkWell( - onTapDown: (details) => _onTapDownAction(context, details), - child: Row( - mainAxisSize: MainAxisSize.min, - children: listComponentsWidget + } else if (!_isOnTapWithPositionActionSupported()) { + return CompositedTransformTarget( + link: layerLink, + child: Material( + key: _buttonKey, + type: MaterialType.transparency, + child: InkWell( + onTap: !_isOnTapWithPositionActionSupported() + ? () => _onTapAction(context) + : null, + onTapDown: _isOnTapWithPositionActionSupported() && !widget.isSelected + ? (details) => _onTapDownAction(context, details) + : null, + borderRadius: SearchFilterButtonStyle.borderRadius, + child: _buildContainerForComponents( + children: [ + ...listComponentsWidget, + if (widget.isSelected) deleteButtonWidget, + ], + ), ), ), - if (isSelected) deleteButtonWidget, - ] + ); + } else { + return _buildContainerForComponents( + key: _buttonKey, + children: [ + InkWell( + onTapDown: (details) => _onTapDownAction(context, details), + child: Row( + mainAxisSize: MainAxisSize.min, + children: listComponentsWidget, + ), + ), + if (widget.isSelected) deleteButtonWidget, + ], + ); + } + } + + bool _isOnTapWithPositionActionSupported() { + return widget.searchFilter.isOnTapWithPositionActionSupported( + context, + widget.responsiveUtils, ); } - Widget _buildContainerForComponents({required List children}) { + Widget _buildContainerForComponents({ + required List children, + Key? key, + }) { return Container( + key: key, decoration: BoxDecoration( borderRadius: SearchFilterButtonStyle.borderRadius, - color: backgroundColor ?? searchFilter.getBackgroundColor( - isSelected: isSelected - ) + color: widget.backgroundColor ?? + widget.searchFilter + .getBackgroundColor(isSelected: widget.isSelected), ), - padding: buttonPadding ?? SearchFilterButtonStyle.getButtonPadding(isSelected), + padding: widget.buttonPadding ?? + SearchFilterButtonStyle.getButtonPadding( + widget.isSelected, + ), child: Row( mainAxisSize: MainAxisSize.min, - children: children - ) + children: children, + ), ); } void _onTapAction(BuildContext context) { - onSelectSearchFilterAction?.call(context, searchFilter); + widget.onSelectSearchFilterAction?.call(context, widget.searchFilter); } void _onTapDownAction(BuildContext context, TapDownDetails details) { - final screenSize = MediaQuery.sizeOf(context); - final offset = details.globalPosition; - final position = RelativeRect.fromLTRB( - offset.dx, - offset.dy, - screenSize.width - offset.dx, - screenSize.height - offset.dy); + RelativeRect? position; - onSelectSearchFilterAction?.call(context, searchFilter, buttonPosition: position); + final buttonBox = + _buttonKey.currentContext?.findRenderObject() as RenderBox?; + final overlayBox = + Overlay.maybeOf(context)?.context.findRenderObject() as RenderBox?; + + if (buttonBox == null || overlayBox == null) { + final screenSize = MediaQuery.sizeOf(context); + final offset = details.globalPosition; + position = RelativeRect.fromLTRB( + offset.dx, + offset.dy, + screenSize.width - offset.dx, + screenSize.height - offset.dy, + ); + } else { + final buttonPosition = buttonBox.localToGlobal( + Offset.zero, + ancestor: overlayBox, + ); + final buttonSize = buttonBox.size; + + position = RelativeRect.fromRect( + Rect.fromLTWH( + buttonPosition.dx + buttonSize.width, + buttonPosition.dy + buttonSize.height + 8, + 0, + 0, + ), + Offset.zero & overlayBox.size, + ); + } + + widget.onSelectSearchFilterAction?.call( + context, + widget.searchFilter, + buttonPosition: position, + ); } -} \ No newline at end of file +} diff --git a/lib/features/search/email/presentation/model/popup_menu_item_date_filter_action.dart b/lib/features/search/email/presentation/model/popup_menu_item_date_filter_action.dart new file mode 100644 index 000000000..2e346123e --- /dev/null +++ b/lib/features/search/email/presentation/model/popup_menu_item_date_filter_action.dart @@ -0,0 +1,23 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class PopupMenuItemDateFilterAction + extends PopupMenuItemActionRequiredSelectedIcon { + final AppLocalizations appLocalizations; + final ImagePaths imagePaths; + + PopupMenuItemDateFilterAction( + super.action, + super.selectedAction, + this.appLocalizations, + this.imagePaths, + ); + + @override + String get actionName => action.getTitleByAppLocalizations(appLocalizations); + + @override + String get selectedIcon => imagePaths.icFilterSelected; +} diff --git a/lib/features/search/email/presentation/model/popup_menu_item_sort_order_type_action.dart b/lib/features/search/email/presentation/model/popup_menu_item_sort_order_type_action.dart new file mode 100644 index 000000000..b4ccd3eaa --- /dev/null +++ b/lib/features/search/email/presentation/model/popup_menu_item_sort_order_type_action.dart @@ -0,0 +1,23 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class PopupMenuItemSortOrderTypeAction + extends PopupMenuItemActionRequiredSelectedIcon { + final AppLocalizations appLocalizations; + final ImagePaths imagePaths; + + PopupMenuItemSortOrderTypeAction( + super.action, + super.selectedAction, + this.appLocalizations, + this.imagePaths, + ); + + @override + String get actionName => action.getTitleByAppLocalizations(appLocalizations); + + @override + String get selectedIcon => imagePaths.icFilterSelected; +} diff --git a/lib/features/search/email/presentation/search_email_controller.dart b/lib/features/search/email/presentation/search_email_controller.dart index ede96668d..d46546ff4 100644 --- a/lib/features/search/email/presentation/search_email_controller.dart +++ b/lib/features/search/email/presentation/search_email_controller.dart @@ -633,8 +633,6 @@ class SearchEmailController extends BaseController } void selectReceiveTimeQuickSearchFilter(BuildContext context, EmailReceiveTimeType emailReceiveTimeType) { - popBack(); - if (emailReceiveTimeType == EmailReceiveTimeType.customRange) { showMultipleViewDateRangePicker( context, @@ -664,7 +662,6 @@ class SearchEmailController extends BaseController } void selectSortOrderQuickSearchFilter(BuildContext context, EmailSortOrderType sortOrderType) { - popBack(); emailSortOrderType.value = sortOrderType; _updateSimpleSearchFilter(sortOrderTypeOption: Some(sortOrderType)); _searchEmailAction(context); diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index f4992fa0c..7a5d1d8c2 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -12,6 +12,7 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart'; import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; @@ -24,13 +25,13 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/se import 'package:tmail_ui_user/features/search/email/presentation/extension/handle_email_more_action_extension.dart'; import 'package:tmail_ui_user/features/search/email/presentation/model/context_item_receive_time_type_action.dart'; import 'package:tmail_ui_user/features/search/email/presentation/model/context_item_sort_order_type_action.dart'; +import 'package:tmail_ui_user/features/search/email/presentation/model/popup_menu_item_date_filter_action.dart'; +import 'package:tmail_ui_user/features/search/email/presentation/model/popup_menu_item_sort_order_type_action.dart'; import 'package:tmail_ui_user/features/search/email/presentation/model/search_more_state.dart'; import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart'; import 'package:tmail_ui_user/features/search/email/presentation/styles/search_email_view_style.dart'; import 'package:tmail_ui_user/features/search/email/presentation/utils/search_email_utils.dart'; import 'package:tmail_ui_user/features/search/email/presentation/widgets/app_bar_selection_mode.dart'; -import 'package:tmail_ui_user/features/search/email/presentation/widgets/email_receive_time_action_tile_widget.dart'; -import 'package:tmail_ui_user/features/search/email/presentation/widgets/email_sort_by_action_tile_widget.dart'; import 'package:tmail_ui_user/features/search/email/presentation/widgets/empty_search_email_widget.dart'; import 'package:tmail_ui_user/features/search/email/presentation/widgets/search_email_loading_bar_widget.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; @@ -39,6 +40,7 @@ import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_til import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart' if (dart.library.html) 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_web_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class SearchEmailView extends GetWidget with AppLoaderMixin { @@ -337,18 +339,28 @@ class SearchEmailView extends GetWidget } void _openPopupMenuDateFilter(BuildContext context, RelativeRect position) { - controller.openPopupMenuAction( - context, - position, - _popupMenuEmailReceiveTimeType( - context, - controller.emailReceiveTimeType.value, - (receiveTime) => controller.selectReceiveTimeQuickSearchFilter( - context, - receiveTime - ) - ) - ); + final popupMenuItems = EmailReceiveTimeType.values.map((timeType) { + return PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupMenuItemActionWidget( + menuAction: PopupMenuItemDateFilterAction( + timeType, + controller.emailReceiveTimeType.value, + AppLocalizations.of(context), + controller.imagePaths, + ), + menuActionClick: (menuAction) { + popBack(); + controller.selectReceiveTimeQuickSearchFilter( + context, + menuAction.action, + ); + }, + ), + ); + }).toList(); + + controller.openPopupMenuAction(context, position, popupMenuItems); } void _openContextMenuDateFilter(BuildContext context) { @@ -366,6 +378,7 @@ class SearchEmailView extends GetWidget context: context, itemActions: contextMenuActions, onContextMenuActionClick: (menuAction) { + popBack(); controller.selectReceiveTimeQuickSearchFilter( context, menuAction.action, @@ -375,15 +388,28 @@ class SearchEmailView extends GetWidget } void _openPopupMenuSortFilter(BuildContext context, RelativeRect position) { - controller.openPopupMenuAction( - context, - position, - _popupMenuEmailSortOrderType( - context, - controller.emailSortOrderType.value, - controller.selectSortOrderQuickSearchFilter - ) - ); + final popupMenuItems = EmailSortOrderType.values.map((sortType) { + return PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupMenuItemActionWidget( + menuAction: PopupMenuItemSortOrderTypeAction( + sortType, + controller.emailSortOrderType.value, + AppLocalizations.of(context), + controller.imagePaths, + ), + menuActionClick: (menuAction) { + popBack(); + controller.selectSortOrderQuickSearchFilter( + context, + menuAction.action, + ); + }, + ), + ); + }).toList(); + + controller.openPopupMenuAction(context, position, popupMenuItems); } void _openContextMenuSortFilter(BuildContext context) { @@ -409,39 +435,6 @@ class SearchEmailView extends GetWidget ); } - List _popupMenuEmailReceiveTimeType( - BuildContext context, - EmailReceiveTimeType? receiveTimeSelected, - Function(EmailReceiveTimeType)? onCallBack - ) { - return EmailReceiveTimeType.values - .map((timeType) => PopupMenuItem( - padding: EdgeInsets.zero, - child: EmailReceiveTimeActionTileWidget( - receiveTimeSelected: receiveTimeSelected, - receiveTimeType: timeType, - onCallBack: onCallBack - ))) - .toList(); - } - - List _popupMenuEmailSortOrderType( - BuildContext context, - EmailSortOrderType? sortTypeSelected, - Function(BuildContext, EmailSortOrderType)? onCallBack - ) { - return EmailSortOrderType.values - .map((sortType) => PopupMenuItem( - padding: EdgeInsets.zero, - child: EmailSortByActionTitleWidget( - sortType: sortType, - imagePaths: controller.imagePaths, - onSortOrderSelected: onCallBack, - sortTypeSelected: sortTypeSelected, - ), - )).toList(); - } - Widget _buildShowAllResultSearchButton(BuildContext context, String textSearch) { return Material( color: Colors.transparent, diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 38853e3a5..8cf34003e 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -904,13 +904,7 @@ class ThreadController extends BaseController with EmailActionController { mailboxDashBoardController.listEmailSelected.clear(); } - void closeFilterMessageActionSheet() { - popBack(); - } - void filterMessagesAction(FilterMessageOption filterOption) { - popBack(); - final newFilterOption = mailboxDashBoardController.filterMessageOption.value == filterOption ? FilterMessageOption.all : filterOption;