From 5fe264c670c7ed46cc7550e50d08148a43f40018 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 13 Jun 2025 13:44:12 +0700 Subject: [PATCH] TF-3759 Update style popup context menu for email more action on thread view Signed-off-by: dab246 --- .../popup_context_menu_action_mixin.dart | 15 +- .../base/model/popup_menu_item_action.dart | 38 ++++ .../popup_menu_item_action_widget.dart | 59 ++++++ .../composer/presentation/composer_view.dart | 2 - .../email_action_type_extension.dart | 4 + .../presentation/styles/composer_style.dart | 1 - .../model/popup_menu_item_email_action.dart | 23 +++ .../thread/presentation/thread_view.dart | 170 +++++------------- 8 files changed, 173 insertions(+), 139 deletions(-) create mode 100644 lib/features/base/model/popup_menu_item_action.dart create mode 100644 lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart create mode 100644 lib/features/email/presentation/model/popup_menu_item_email_action.dart 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 c92ae9ccd..4d3514c4b 100644 --- a/lib/features/base/mixin/popup_context_menu_action_mixin.dart +++ b/lib/features/base/mixin/popup_context_menu_action_mixin.dart @@ -64,24 +64,23 @@ mixin PopupContextMenuActionMixin { }); } - void openPopupMenuAction( + Future openPopupMenuAction( BuildContext context, RelativeRect? position, List popupMenuItems, - { - double? radius, - } ) async { await showMenu( context: context, position: position ?? const RelativeRect.fromLTRB(16, 40, 16, 16), color: Colors.white, surfaceTintColor: Colors.white, - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(radius ?? 16)) + menuPadding: const EdgeInsets.only(top: 8, bottom: 8), + elevation: 8, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(6)), ), - items: popupMenuItems + constraints: const BoxConstraints(maxWidth: 220), + items: popupMenuItems, ); } diff --git a/lib/features/base/model/popup_menu_item_action.dart b/lib/features/base/model/popup_menu_item_action.dart new file mode 100644 index 000000000..eb64e5c12 --- /dev/null +++ b/lib/features/base/model/popup_menu_item_action.dart @@ -0,0 +1,38 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +typedef OnPopupMenuActionClick = void Function(PopupMenuItemAction action); + +abstract class PopupMenuItemAction with EquatableMixin { + final T action; + + PopupMenuItemAction(this.action); + + @override + List get props => [action]; + + String get actionName; + + String getActionNameWithLimitation({int limitCharacters = 22}) => + actionName.length > limitCharacters + ? '${actionName.substring(0, limitCharacters)}...' + : actionName; + + Color get actionNameColor => Colors.black; + + void onClick(OnPopupMenuActionClick callback) => callback(this); +} + +mixin OptionalPopupIcon { + String get actionIcon; + + Color get actionIconColor => AppColor.steelGrayA540; + + double get actionIconSize => 20.0; +} + +abstract class PopupMenuItemActionRequiredIcon + extends PopupMenuItemAction with OptionalPopupIcon { + PopupMenuItemActionRequiredIcon(super.action); +} \ No newline at end of file diff --git a/lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart b/lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart new file mode 100644 index 000000000..6e0087602 --- /dev/null +++ b/lib/features/base/widget/popup_menu/popup_menu_item_action_widget.dart @@ -0,0 +1,59 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart'; + +class PopupMenuItemActionWidget extends StatelessWidget { + final PopupMenuItemAction menuAction; + final OnPopupMenuActionClick menuActionClick; + + const PopupMenuItemActionWidget({ + super.key, + required this.menuAction, + required this.menuActionClick, + }); + + @override + Widget build(BuildContext context) { + Widget? iconWidget; + + if (menuAction is PopupMenuItemActionRequiredIcon) { + final specificMenuAction = menuAction as PopupMenuItemActionRequiredIcon; + iconWidget = Padding( + padding: const EdgeInsetsDirectional.only(end: 16), + child: SvgPicture.asset( + specificMenuAction.actionIcon, + width: specificMenuAction.actionIconSize, + height: specificMenuAction.actionIconSize, + colorFilter: specificMenuAction.actionIconColor.asFilter(), + fit: BoxFit.fill, + ), + ); + } + + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () => menuAction.onClick(menuActionClick), + child: Container( + height: 48, + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + if (iconWidget != null) iconWidget, + Expanded( + child: Text( + menuAction.getActionNameWithLimitation(), + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + maxLines: 1, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index da43bc714..07796c7a0 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -61,7 +61,6 @@ class ComposerView extends GetWidget { context, position, _createMoreOptionPopupItems(context), - radius: ComposerStyle.popupMenuRadius ); }, isNetworkConnectionAvailable: controller.isNetworkConnectionAvailable, @@ -87,7 +86,6 @@ class ComposerView extends GetWidget { context, position, _createMoreOptionPopupItems(context), - radius: ComposerStyle.popupMenuRadius ); }, isNetworkConnectionAvailable: controller.isNetworkConnectionAvailable, diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index 78adfa290..9753a714b 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -150,6 +150,8 @@ extension EmailActionTypeExtension on EmailActionType { return imagePaths.icDownloadAttachment; case EmailActionType.editAsNewEmail: return imagePaths.icEdit; + case EmailActionType.openInNewTab: + return imagePaths.icOpenInNewTab; default: return ''; } @@ -173,6 +175,8 @@ extension EmailActionTypeExtension on EmailActionType { return appLocalizations.downloadMessageAsEML; case EmailActionType.editAsNewEmail: return appLocalizations.editAsNewEmail; + case EmailActionType.openInNewTab: + return appLocalizations.openInNewTab; default: return ''; } diff --git a/lib/features/composer/presentation/styles/composer_style.dart b/lib/features/composer/presentation/styles/composer_style.dart index ec119fa5b..73cc5b519 100644 --- a/lib/features/composer/presentation/styles/composer_style.dart +++ b/lib/features/composer/presentation/styles/composer_style.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; class ComposerStyle { static const double radius = 28; static const double keyboardToolBarHeight = 200; - static const double popupMenuRadius = 8; static const double suggestionItemHeight = 60; static const double normalWidth = 600; static const double normalHeight = 525; diff --git a/lib/features/email/presentation/model/popup_menu_item_email_action.dart b/lib/features/email/presentation/model/popup_menu_item_email_action.dart new file mode 100644 index 000000000..42885a32e --- /dev/null +++ b/lib/features/email/presentation/model/popup_menu_item_email_action.dart @@ -0,0 +1,23 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:model/email/email_action_type.dart'; +import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class PopupMenuItemEmailAction + extends PopupMenuItemActionRequiredIcon { + final AppLocalizations appLocalizations; + final ImagePaths imagePaths; + + PopupMenuItemEmailAction( + super.action, + this.appLocalizations, + this.imagePaths, + ); + + @override + String get actionIcon => action.getIcon(imagePaths); + + @override + String get actionName => action.getTitle(appLocalizations); +} diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 2d3499b32..6908667a6 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -10,8 +10,10 @@ import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/clean_messages_banner.dart'; import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart'; +import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart'; +import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; @@ -622,23 +624,25 @@ class ThreadView extends GetWidget PresentationEmail presentationEmail, RelativeRect? position ) { + final mailboxContain = presentationEmail.mailboxContain; + final isDrafts = mailboxContain?.isDrafts ?? false; + final isChildOfTeamMailboxes = + mailboxContain?.isChildOfTeamMailboxes ?? false; + final isSpam = mailboxContain?.isSpam ?? false; + final isArchive = mailboxContain?.isArchive ?? false; + final isTemplates = mailboxContain?.isTemplates ?? false; + + final listEmailActions = [ + EmailActionType.openInNewTab, + if (!isDrafts && !isChildOfTeamMailboxes) + isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam, + if (!isArchive) EmailActionType.archiveMessage, + if (!isDrafts && !isTemplates) EmailActionType.editAsNewEmail, + ]; + + if (listEmailActions.isEmpty) return; + if (controller.responsiveUtils.isScreenWithShortestSide(context)) { - final mailboxContain = presentationEmail.mailboxContain; - - final listEmailActions = [ - EmailActionType.openInNewTab, - if (mailboxContain?.isDrafts == false && mailboxContain?.isChildOfTeamMailboxes == false) - mailboxContain?.isSpam == true - ? EmailActionType.unSpam - : EmailActionType.moveToSpam, - if (mailboxContain?.isArchive == false) - EmailActionType.archiveMessage, - if (mailboxContain?.isDrafts == false && mailboxContain?.isTemplates == false) - EmailActionType.editAsNewEmail, - ]; - - if (listEmailActions.isEmpty) return; - final contextMenuActions = listEmailActions .map((action) => ContextItemEmailAction( action, @@ -659,10 +663,31 @@ class ThreadView extends GetWidget }, ); } else { + final popupMenuActions = listEmailActions.map((actionType) { + return PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupMenuItemActionWidget( + menuAction: PopupMenuItemEmailAction( + actionType, + AppLocalizations.of(context), + controller.imagePaths, + ), + menuActionClick: (menuAction) { + popBack(); + controller.handleEmailActionType( + menuAction.action, + presentationEmail, + mailboxContain: mailboxContain, + ); + }, + ), + ); + }).toList(); + controller.openPopupMenuAction( context, position, - _popupMenuActionTile(context, presentationEmail) + popupMenuActions, ); } } @@ -737,117 +762,6 @@ class ThreadView extends GetWidget )); } - List _popupMenuActionTile(BuildContext context, PresentationEmail email) { - final mailboxContain = email.mailboxContain; - - return [ - _buildOpenInNewTabPopupMenuItem(context, email, mailboxContain), - if (mailboxContain?.isDrafts == false && mailboxContain?.isChildOfTeamMailboxes == false) - _buildMarkAsSpamPopupMenuItem(context, email, mailboxContain), - if (mailboxContain?.isArchive == false) - _buildArchiveMessagePopupMenuItem(context, email), - if (mailboxContain?.isDrafts == false && mailboxContain?.isTemplates == false) - _buildEditAsNewEmailPopupMenuItem(AppLocalizations.of(context), email), - ]; - } - - PopupMenuEntry _buildMarkAsSpamPopupMenuItem( - BuildContext context, - PresentationEmail email, - PresentationMailbox? mailboxContain - ) { - return PopupMenuItem( - padding: EdgeInsets.zero, - child: popupItem( - mailboxContain?.isSpam == true ? controller.imagePaths.icNotSpam : controller.imagePaths.icSpam, - mailboxContain?.isSpam == true - ? AppLocalizations.of(context).remove_from_spam - : AppLocalizations.of(context).mark_as_spam, - colorIcon: AppColor.colorTextButton, - styleName: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black - ), - onCallbackAction: () => controller.handleEmailActionType( - mailboxContain?.isSpam == true ? EmailActionType.unSpam : EmailActionType.moveToSpam, - email, - mailboxContain: mailboxContain, - ) - ) - ); - } - - PopupMenuEntry _buildOpenInNewTabPopupMenuItem( - BuildContext context, - PresentationEmail email, - PresentationMailbox? mailboxContain - ) { - return PopupMenuItem( - padding: EdgeInsets.zero, - child: popupItem( - controller.imagePaths.icOpenInNewTab, - AppLocalizations.of(context).openInNewTab, - colorIcon: AppColor.colorTextButton, - styleName: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black - ), - onCallbackAction: () { - popBack(); - controller.openEmailInNewTabAction(email); - } - ) - ); - } - - PopupMenuEntry _buildArchiveMessagePopupMenuItem( - BuildContext context, - PresentationEmail email - ) { - return PopupMenuItem( - padding: EdgeInsets.zero, - child: popupItem( - controller.imagePaths.icMailboxArchived, - AppLocalizations.of(context).archiveMessage, - colorIcon: AppColor.colorTextButton, - styleName: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black - ), - onCallbackAction: () { - popBack(); - controller.archiveMessage(context, email); - } - ) - ); - } - - PopupMenuEntry _buildEditAsNewEmailPopupMenuItem( - AppLocalizations appLocalizations, - PresentationEmail email, - ) { - return PopupMenuItem( - padding: EdgeInsets.zero, - child: popupItem( - controller.imagePaths.icEdit, - appLocalizations.editAsNewEmail, - colorIcon: AppColor.colorTextButton, - styleName: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: Colors.black - ), - onCallbackAction: () { - popBack(); - controller.editAsNewEmail(email); - } - ) - ); - } - Widget _buildMailboxActionProgressBanner(BuildContext context) { return Obx(() { return _MailboxActionProgressBanner(