From 2fdab039da7d32d9bb7c244f13af779e1b219c29 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 13 Jun 2025 20:06:15 +0700 Subject: [PATCH] TF-3759 Update style popup context menu for profile setting menu Signed-off-by: dab246 --- core/lib/core.dart | 1 - .../extensions/color_extension.dart | 3 + core/lib/presentation/utils/theme_utils.dart | 9 ++ .../popup_menu/popup_menu_item_widget.dart | 51 ------- .../base/model/popup_menu_item_action.dart | 3 + .../popup_menu_item_action_widget.dart | 2 +- .../mailbox_dashboard_controller.dart | 26 +--- ...e_setting_action_type_click_extension.dart | 20 +++ .../mailbox_dashboard_view_web.dart | 12 +- .../mixin/user_setting_popup_menu_mixin.dart | 74 --------- ...menu_item_profile_setting_type_action.dart | 30 ++++ .../profile_setting_action_type.dart | 26 ++++ .../navigation_bar/navigation_bar_widget.dart | 58 +++---- .../profile_setting/profile_label_widget.dart | 47 ++++++ .../profile_setting/profile_setting_icon.dart | 110 +++++++++++++ .../profile_setting_menu_overlay.dart | 105 +++++++++++++ ...e_setting_action_type_click_extension.dart | 19 +++ .../manage_account_dashboard_controller.dart | 18 +-- .../manage_account_dashboard_view.dart | 144 +++++++++--------- 19 files changed, 479 insertions(+), 279 deletions(-) delete mode 100644 core/lib/presentation/views/popup_menu/popup_menu_item_widget.dart create mode 100644 lib/features/mailbox_dashboard/presentation/extensions/handle_profile_setting_action_type_click_extension.dart delete mode 100644 lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart create mode 100644 lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart create mode 100644 lib/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_label_widget.dart create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_icon.dart create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart create mode 100644 lib/features/manage_account/presentation/extensions/handle_profile_setting_action_type_click_extension.dart diff --git a/core/lib/core.dart b/core/lib/core.dart index aa82d2b9b..4a47e5aee 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -87,7 +87,6 @@ export 'presentation/views/bottom_popup/cupertino_action_sheet_builder.dart'; export 'presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart'; export 'presentation/views/modal_sheets/edit_text_modal_sheet_builder.dart'; export 'presentation/views/search/search_bar_view.dart'; -export 'presentation/views/popup_menu/popup_menu_item_widget.dart'; export 'presentation/views/quick_search/quick_search_input_form.dart'; export 'presentation/views/toast/toast_position.dart'; export 'presentation/views/toast/tmail_toast.dart'; diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 4b8a0c9be..dfc78168c 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -258,6 +258,9 @@ extension AppColor on Color { static const folderDivider = Color(0xFFE4E8EC); static const gray424244 = Color(0xFF424244); static const redFF3347 = Color(0xFFFF3347); + static const gray686E76 = Color(0xFF686E76); + static const textSecondary = Color(0xFF1C1B1F); + static const profileMenuDivider = Color(0xFF1D192B); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index 85b99dc05..c262334f6 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -270,6 +270,15 @@ class ThemeUtils { color: AppColor.m3SurfaceBackground, ); + static final TextStyle textStyleM3BodyMedium = TextStyle( + fontFamily: ConstantsUI.fontApp, + fontWeight: FontWeight.w500, + letterSpacing: 0.25, + fontSize: 14, + height: 20 / 14, + color: AppColor.textSecondary.withOpacity(0.48), + ); + static TextStyle textStyleAppShortcut() => const TextStyle( fontFamily: ConstantsUI.fontApp, fontWeight: FontWeight.normal, diff --git a/core/lib/presentation/views/popup_menu/popup_menu_item_widget.dart b/core/lib/presentation/views/popup_menu/popup_menu_item_widget.dart deleted file mode 100644 index e59c0f90d..000000000 --- a/core/lib/presentation/views/popup_menu/popup_menu_item_widget.dart +++ /dev/null @@ -1,51 +0,0 @@ - -import 'package:core/core.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -@immutable -class PopupMenuItemWidget extends StatelessWidget { - - final Function onTapCallback; - final String icon; - final String name; - final Color? iconColor; - final String? iconSelection; - - const PopupMenuItemWidget( - this.icon, - this.name, - this.onTapCallback, - {Key? key, - this.iconSelection, - this.iconColor - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => onTapCallback.call(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: SizedBox( - child: Row(children: [ - SvgPicture.asset( - icon, - width: 20, - height: 20, - fit: BoxFit.fill, - colorFilter: iconColor.asFilter() - ), - const SizedBox(width: 12), - Expanded(child: Text(name, - style: const TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.w500))), - if (iconSelection != null) - Padding( - padding: const EdgeInsets.only(left: 12), - child: SvgPicture.asset(iconSelection!, width: 16, height: 16, fit: BoxFit.fill)), - ]) - ), - ) - ); - } -} \ No newline at end of file diff --git a/lib/features/base/model/popup_menu_item_action.dart b/lib/features/base/model/popup_menu_item_action.dart index cd3576a7e..b12120dcc 100644 --- a/lib/features/base/model/popup_menu_item_action.dart +++ b/lib/features/base/model/popup_menu_item_action.dart @@ -16,6 +16,9 @@ abstract class PopupMenuItemAction with EquatableMixin { Color get actionNameColor => Colors.black; + EdgeInsetsGeometry get itemPadding => + const EdgeInsets.symmetric(horizontal: 12); + void onClick(OnPopupMenuActionClick callback) => callback(this); } 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 index 4cb1d1836..33ed57296 100644 --- 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 @@ -41,7 +41,7 @@ class PopupMenuItemActionWidget extends StatelessWidget { child: Container( height: 48, width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: menuAction.itemPadding, child: Row( children: [ if (iconWidget != null) iconWidget, 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 2c4c217c2..f3e33d653 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -112,7 +112,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/reopen_composer_cache_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/set_error_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart'; @@ -190,7 +189,7 @@ import 'package:server_settings/server_settings/tmail_server_settings_extension. import 'package:uuid/uuid.dart'; class MailboxDashBoardController extends ReloadableController - with UserSettingPopupMenuMixin, ContactSupportMixin { + with ContactSupportMixin { final RemoveEmailDraftsInteractor _removeEmailDraftsInteractor = Get.find(); final EmailReceiveManager _emailReceiveManager = Get.find(); @@ -3148,29 +3147,6 @@ class MailboxDashBoardController extends ReloadableController List get listIdentities => _identities ?? []; - void handleClickAvatarAction(BuildContext context, RelativeRect position) { - openPopupMenuAction( - context, - position, - popupMenuUserSettingActionTile( - context, - sessionCurrent?.getOwnEmailAddressOrEmpty(), - onLogoutAction: () { - popBack(); - logout( - context, - sessionCurrent, - accountId.value - ); - }, - onSettingAction: () { - popBack(); - goToSettings(); - } - ) - ); - } - bool validateNoEmailsInTrashAndSpamFolder() { return selectedMailbox.value != null && (selectedMailbox.value!.isTrash || selectedMailbox.value!.isSpam) diff --git a/lib/features/mailbox_dashboard/presentation/extensions/handle_profile_setting_action_type_click_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/handle_profile_setting_action_type_click_extension.dart new file mode 100644 index 000000000..3b6d34a3b --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/handle_profile_setting_action_type_click_extension.dart @@ -0,0 +1,20 @@ + +import 'package:flutter/cupertino.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/profile_setting/profile_setting_action_type.dart'; + +extension HandleProfileSettingActionTypeClickExtension on MailboxDashBoardController { + void handleProfileSettingActionTypeClick({ + required BuildContext context, + required ProfileSettingActionType actionType, + }) { + switch (actionType) { + case ProfileSettingActionType.signOut: + logout(context, sessionCurrent, accountId.value); + break; + case ProfileSettingActionType.manageAccount: + goToSettings(); + break; + } + } +} \ No newline at end of file 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 ad28598e2..b68731f3e 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -17,8 +17,10 @@ import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view_web.dar import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/base_mailbox_dashboard_view.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_drawer_changed_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_profile_setting_action_type_click_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.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/quick_search_filter.dart'; @@ -66,17 +68,21 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { return NavigationBarWidget( imagePaths: controller.imagePaths, accountId: accountId, - avatarUserName: controller.getOwnEmailAddress().firstCharacterToUpperCase, + ownEmailAddress: controller.getOwnEmailAddress(), contactSupportCapability: accountId != null ? controller.sessionCurrent?.getContactSupportCapability(accountId) : null, searchForm: SearchInputFormWidget(), appGridController: controller.appGridDashboardController, + settingActionTypes: ProfileSettingActionType.values, onTapApplicationLogoAction: controller.redirectToInboxAction, - onTapAvatarAction: (position) => - controller.handleClickAvatarAction(context, position), onTapContactSupportAction: (contactSupport) => controller.onGetHelpOrReportBug(contactSupport), + onProfileSettingActionTypeClick: (actionType) => + controller.handleProfileSettingActionTypeClick( + context: context, + actionType: actionType, + ), ); }), Expanded(child: Row(children: [ diff --git a/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart b/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart deleted file mode 100644 index 3cb76da20..000000000 --- a/lib/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart +++ /dev/null @@ -1,74 +0,0 @@ - -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/views/popup_menu/popup_menu_item_widget.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -mixin UserSettingPopupMenuMixin { - final _imagePaths = Get.find(); - - List popupMenuUserSettingActionTile( - BuildContext context, - String? currentUserEmail, - { - Function? onLogoutAction, - Function? onSettingAction - } - ) { - return [ - if(currentUserEmail != null) - PopupMenuItem( - enabled: false, - padding: EdgeInsets.zero, - child: SizedBox( - width: 300, - child: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 20), - title: SelectableText( - currentUserEmail, - maxLines: 1, - style: const TextStyle( - fontSize: 15, - color: AppColor.colorHintSearchBar, - fontWeight: FontWeight.normal - ) - ) - ), - ) - ), - if (onSettingAction != null) - ...[ - const PopupMenuDivider(height: 0.5), - PopupMenuItem( - padding: EdgeInsets.zero, - child: _settingAction(context, onSettingAction) - ), - const PopupMenuDivider(height: 0.5), - ], - if (onLogoutAction != null) - ...[ - const PopupMenuDivider(height: 0.5), - PopupMenuItem( - padding: EdgeInsets.zero, - child: _logoutAction(context, onLogoutAction) - ), - ] - ]; - } - - Widget _settingAction(BuildContext context, Function? onCallBack) { - return PopupMenuItemWidget( - _imagePaths.icSetting, - AppLocalizations.of(context).manage_account, - () => onCallBack?.call()); - } - - Widget _logoutAction(BuildContext context, Function? onCallBack) { - return PopupMenuItemWidget( - _imagePaths.icLogout, - AppLocalizations.of(context).sign_out, - () => onCallBack?.call()); - } -} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart b/lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart new file mode 100644 index 000000000..4520ef661 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart @@ -0,0 +1,30 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class PopupMenuItemProfileSettingTypeAction + extends PopupMenuItemActionRequiredIcon { + final AppLocalizations appLocalizations; + final ImagePaths imagePaths; + + PopupMenuItemProfileSettingTypeAction( + super.action, + this.appLocalizations, + this.imagePaths, + ); + + @override + String get actionIcon => action.getIcon(imagePaths); + + @override + String get actionName => action.getTitle(appLocalizations); + + @override + Color get actionIconColor => AppColor.gray686E76; + + @override + EdgeInsetsGeometry get itemPadding => const EdgeInsets.symmetric(horizontal: 16); +} diff --git a/lib/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart b/lib/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart new file mode 100644 index 000000000..1ec339762 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart @@ -0,0 +1,26 @@ + +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +enum ProfileSettingActionType { + manageAccount, + signOut; + + String getTitle(AppLocalizations appLocalizations) { + switch(this) { + case ProfileSettingActionType.manageAccount: + return appLocalizations.manage_account; + case ProfileSettingActionType.signOut: + return appLocalizations.sign_out; + } + } + + String getIcon(ImagePaths imagePaths) { + switch(this) { + case ProfileSettingActionType.manageAccount: + return imagePaths.icSetting; + case ProfileSettingActionType.signOut: + return imagePaths.icLogout; + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart index 4f2b5cca1..824f2386e 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart @@ -4,7 +4,6 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; -import 'package:core/presentation/views/image/avatar_builder.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -12,32 +11,37 @@ import 'package:model/support/contact_support_capability.dart'; import 'package:tmail_ui_user/features/base/mixin/contact_support_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/navigation_bar_style.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_icon.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class NavigationBarWidget extends StatelessWidget { final ImagePaths imagePaths; final AccountId? accountId; - final String avatarUserName; + final String ownEmailAddress; final ContactSupportCapability? contactSupportCapability; final Widget? searchForm; final AppGridDashboardController? appGridController; + final List settingActionTypes; final VoidCallback? onTapApplicationLogoAction; - final OnTapAvatarActionWithPositionClick? onTapAvatarAction; final OnTapContactSupportAction? onTapContactSupportAction; + final OnProfileSettingActionTypeClick onProfileSettingActionTypeClick; const NavigationBarWidget({ super.key, required this.imagePaths, required this.accountId, - required this.avatarUserName, + required this.ownEmailAddress, + required this.onProfileSettingActionTypeClick, this.contactSupportCapability, this.searchForm, this.appGridController, + this.settingActionTypes = const [], this.onTapApplicationLogoAction, - this.onTapAvatarAction, this.onTapContactSupportAction, }); @@ -91,23 +95,11 @@ class NavigationBarWidget extends StatelessWidget { return const SizedBox.shrink(); }), const SizedBox(width: 16), - if (accountId != null) - (AvatarBuilder() - ..text(avatarUserName) - ..backgroundColor(Colors.white) - ..textColor(Colors.black) - ..context(context) - ..size(48) - ..addOnTapAvatarActionWithPositionClick(onTapAvatarAction) - ..addBoxShadows([ - const BoxShadow( - color: AppColor.colorShadowBgContentEmail, - spreadRadius: 1, - blurRadius: 1, - offset: Offset(0, 0.5) - ) - ]) - ).build() + ProfileSettingIcon( + ownEmailAddress: ownEmailAddress, + settingActionTypes: settingActionTypes, + onProfileSettingActionTypeClick: onProfileSettingActionTypeClick, + ), ] ); })) @@ -134,23 +126,11 @@ class NavigationBarWidget extends StatelessWidget { return const SizedBox.shrink(); }), const SizedBox(width: 16), - if (accountId != null) - (AvatarBuilder() - ..text(avatarUserName) - ..backgroundColor(Colors.white) - ..textColor(Colors.black) - ..context(context) - ..size(48) - ..addOnTapAvatarActionWithPositionClick(onTapAvatarAction) - ..addBoxShadows([ - const BoxShadow( - color: AppColor.colorShadowBgContentEmail, - spreadRadius: 1, - blurRadius: 1, - offset: Offset(0, 0.5) - ) - ]) - ).build() + ProfileSettingIcon( + ownEmailAddress: ownEmailAddress, + settingActionTypes: settingActionTypes, + onProfileSettingActionTypeClick: onProfileSettingActionTypeClick, + ), ] ]), ); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_label_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_label_widget.dart new file mode 100644 index 000000000..200e9aaf2 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_label_widget.dart @@ -0,0 +1,47 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/copy_subaddress_widget.dart'; + +class ProfileLabelWidget extends StatelessWidget { + final String label; + final String copyLabelIcon; + final OnCopyButtonAction onCopyButtonAction; + + const ProfileLabelWidget({ + super.key, + required this.label, + required this.copyLabelIcon, + required this.onCopyButtonAction, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SelectableText( + label, + style: ThemeUtils.textStyleM3BodyMedium, + maxLines: 1, + ), + ), + if (label.isNotEmpty) + TMailButtonWidget.fromIcon( + icon: copyLabelIcon, + backgroundColor: Colors.transparent, + iconSize: 20, + iconColor: AppColor.textSecondary.withOpacity(0.48), + padding: const EdgeInsets.all(5), + onTapActionCallback: onCopyButtonAction, + ), + ], + ), + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_icon.dart b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_icon.dart new file mode 100644 index 000000000..db0e2d00a --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_icon.dart @@ -0,0 +1,110 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/extensions/string_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/app_toast.dart'; +import 'package:core/presentation/views/image/avatar_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/utils/app_utils.dart'; + +class ProfileSettingIcon extends StatefulWidget { + final String ownEmailAddress; + final List settingActionTypes; + final OnProfileSettingActionTypeClick onProfileSettingActionTypeClick; + + const ProfileSettingIcon({ + super.key, + required this.ownEmailAddress, + required this.settingActionTypes, + required this.onProfileSettingActionTypeClick, + }); + + @override + State createState() => _ProfileSettingIconState(); +} + +class _ProfileSettingIconState extends State { + final ValueNotifier _isExpandedNotifier = ValueNotifier(false); + final _imagePaths = ImagePaths(); + final _appToast = AppToast(); + + @override + void dispose() { + _isExpandedNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: _isExpandedNotifier, + builder: (context, isExpanded, child) { + return PortalTarget( + visible: isExpanded, + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _toggleOpenMenu, + ), + child: PortalTarget( + anchor: Aligned( + follower: AppUtils.isDirectionRTL(context) + ? Alignment.topLeft + : Alignment.topRight, + target: AppUtils.isDirectionRTL(context) + ? Alignment.bottomLeft + : Alignment.bottomRight, + offset: const Offset(0, 8), + ), + portalFollower: ProfileSettingMenuOverlay( + ownEmailAddress: widget.ownEmailAddress, + settingActionTypes: widget.settingActionTypes, + imagePaths: _imagePaths, + onCopyButtonAction: () => _onCopyProfileNameAction( + context, + widget.ownEmailAddress, + ), + onProfileSettingActionTypeClick: (actionType) { + _toggleOpenMenu(); + widget.onProfileSettingActionTypeClick(actionType); + }, + onCloseProfileSettingMenuAction: _toggleOpenMenu, + ), + visible: isExpanded, + child: (AvatarBuilder() + ..text(widget.ownEmailAddress.firstCharacterToUpperCase) + ..backgroundColor(Colors.white) + ..textColor(Colors.black) + ..context(context) + ..size(48) + ..addOnTapActionClick(_toggleOpenMenu) + ..addBoxShadows([ + const BoxShadow( + color: AppColor.colorShadowBgContentEmail, + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 0.5), + ) + ])) + .build(), + ), + ); + }, + ); + } + + void _toggleOpenMenu() { + _isExpandedNotifier.value = !_isExpandedNotifier.value; + } + + void _onCopyProfileNameAction(BuildContext context, String profileName) { + Clipboard.setData(ClipboardData(text: profileName)); + _appToast.showToastSuccessMessage( + context, + AppLocalizations.of(context).email_address_copied_to_clipboard, + ); + } +} 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 new file mode 100644 index 000000000..78d8e1212 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_setting_menu_overlay.dart @@ -0,0 +1,105 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/copy_subaddress_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/popup_menu_item_profile_setting_type_action.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/profile_setting/profile_label_widget.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnProfileSettingActionTypeClick = void Function( + ProfileSettingActionType); +typedef OnCloseProfileSettingMenuAction = void Function(); + +class ProfileSettingMenuOverlay extends StatelessWidget { + final String ownEmailAddress; + final List settingActionTypes; + final ImagePaths imagePaths; + final OnCopyButtonAction onCopyButtonAction; + final OnProfileSettingActionTypeClick onProfileSettingActionTypeClick; + final OnCloseProfileSettingMenuAction onCloseProfileSettingMenuAction; + + const ProfileSettingMenuOverlay({ + Key? key, + required this.ownEmailAddress, + required this.settingActionTypes, + required this.imagePaths, + required this.onCopyButtonAction, + required this.onProfileSettingActionTypeClick, + required this.onCloseProfileSettingMenuAction, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: FocusManager.instance.primaryFocus?.unfocus, + child: Container( + width: 260, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(14)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 2, + spreadRadius: 0, + ), + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 6, + spreadRadius: 2, + offset: const Offset(0, 2), + ), + ], + ), + child: Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 32), + ProfileLabelWidget( + label: ownEmailAddress, + copyLabelIcon: imagePaths.icCopy, + onCopyButtonAction: onCopyButtonAction, + ), + const SizedBox(height: 4), + Divider( + color: AppColor.profileMenuDivider.withOpacity(0.12), + height: 1, + ), + ...settingActionTypes + .map((actionType) => PopupMenuItemActionWidget( + menuAction: PopupMenuItemProfileSettingTypeAction( + actionType, + AppLocalizations.of(context), + imagePaths, + ), + menuActionClick: (menuAction) { + onProfileSettingActionTypeClick(menuAction.action); + }, + )) + .toList(), + ], + ), + PositionedDirectional( + top: 6, + end: 6, + child: TMailButtonWidget.fromIcon( + icon: imagePaths.icComposerClose, + iconSize: 22, + iconColor: AppColor.m3Tertiary, + padding: const EdgeInsets.all(4), + borderRadius: 24, + backgroundColor: Colors.transparent, + onTapActionCallback: onCloseProfileSettingMenuAction, + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/manage_account/presentation/extensions/handle_profile_setting_action_type_click_extension.dart b/lib/features/manage_account/presentation/extensions/handle_profile_setting_action_type_click_extension.dart new file mode 100644 index 000000000..301f2f9f6 --- /dev/null +++ b/lib/features/manage_account/presentation/extensions/handle_profile_setting_action_type_click_extension.dart @@ -0,0 +1,19 @@ + +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart'; + +extension HandleProfileSettingActionTypeClickExtension on ManageAccountDashBoardController { + void handleProfileSettingActionTypeClick({ + required BuildContext context, + required ProfileSettingActionType actionType, + }) { + switch (actionType) { + case ProfileSettingActionType.signOut: + logout(context, sessionCurrent, accountId.value); + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart index 55f4bbae9..c521f8e7c 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_controller.dart @@ -15,7 +15,6 @@ import 'package:rule_filter/rule_filter/capability_rule_filter.dart'; import 'package:server_settings/server_settings/capability_server_settings.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/user_setting_popup_menu_mixin.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/export_trace_log_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_vacation_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/update_vacation_state.dart'; @@ -43,7 +42,7 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; -class ManageAccountDashBoardController extends ReloadableController with UserSettingPopupMenuMixin { +class ManageAccountDashBoardController extends ReloadableController { GetAllVacationInteractor? _getAllVacationInteractor; UpdateVacationInteractor? _updateVacationInteractor; @@ -380,21 +379,6 @@ class ManageAccountDashBoardController extends ReloadableController with UserSet return false; } - void handleClickAvatarAction(BuildContext context, RelativeRect position) { - openPopupMenuAction( - context, - position, - popupMenuUserSettingActionTile( - context, - getOwnEmailAddress(), - onLogoutAction: () { - popBack(); - logout(context, sessionCurrent, accountId.value); - } - ) - ); - } - int get minInputLengthAutocomplete { if (sessionCurrent == null || accountId.value == null) { return AppConfig.defaultMinInputLengthAutocomplete; diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart index 2dfafaf99..3dd4d4a88 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_view.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_view.dart @@ -1,11 +1,13 @@ import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/extensions/string_extension.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/profile_setting/profile_setting_action_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/email_rules/email_rules_view.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/extensions/handle_profile_setting_action_type_click_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_view.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/language_and_region_view.dart'; @@ -25,73 +27,79 @@ class ManageAccountDashBoardView extends GetWidget FocusScope.of(context).unfocus(), - child: ResponsiveWidget( - responsiveUtils: controller.responsiveUtils, - desktop: Column(children: [ - Obx(() => NavigationBarWidget( - imagePaths: controller.imagePaths, - accountId: controller.accountId.value, - avatarUserName: controller.getOwnEmailAddress().firstCharacterToUpperCase, - onTapApplicationLogoAction: () => - controller.backToMailboxDashBoard(context: context), - onTapAvatarAction: (position) => - controller.handleClickAvatarAction(context, position), - )), - Expanded(child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - width: ResponsiveUtils.defaultSizeMenu, - child: ManageAccountMenuView() - ), - Expanded(child: ColoredBox( - color: AppColor.colorBgDesktop, - child: Column(children: [ - Obx(() { - if (controller.vacationResponse.value?.vacationResponderIsValid == true) { - return VacationNotificationMessageWidget( - margin: const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16), - fromAccountDashBoard: true, - vacationResponse: controller.vacationResponse.value!, - actionGotoVacationSetting: !controller.inVacationSettings() - ? () => controller.selectAccountMenuItem(AccountMenuItem.vacation) - : null, - actionEndNow: controller.disableVacationResponder); - } else if ((controller.vacationResponse.value?.vacationResponderIsWaiting == true - || controller.vacationResponse.value?.vacationResponderIsStopped == true) - && controller.accountMenuItemSelected.value == AccountMenuItem.vacation) { - return VacationNotificationMessageWidget( - margin: const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16), - fromAccountDashBoard: true, - vacationResponse: controller.vacationResponse.value!, - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), - leadingIcon: const Padding( - padding: EdgeInsetsDirectional.only(end: 12), - child: Icon(Icons.timer, size: 20), - ) - ); - } else { - return const SizedBox.shrink(); - } - }), - Expanded(child: _viewDisplayedOfAccountMenuItem()) - ]), - )) - ], - )) - ]), - mobile: SafeArea( - child: SettingsView( - closeAction: () => - controller.backToMailboxDashBoard(context: context)), - ) + return Portal( + child: Scaffold( + backgroundColor: controller.responsiveUtils.isWebDesktop(context) + ? AppColor.colorBgDesktop + : Colors.white, + drawerEnableOpenDragGesture: false, + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: ResponsiveWidget( + responsiveUtils: controller.responsiveUtils, + desktop: Column(children: [ + Obx(() => NavigationBarWidget( + imagePaths: controller.imagePaths, + accountId: controller.accountId.value, + ownEmailAddress: controller.getOwnEmailAddress(), + onTapApplicationLogoAction: () => + controller.backToMailboxDashBoard(context: context), + settingActionTypes: const [ProfileSettingActionType.signOut], + onProfileSettingActionTypeClick: (actionType) => + controller.handleProfileSettingActionTypeClick( + context: context, + actionType: actionType, + ), + )), + Expanded(child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: ResponsiveUtils.defaultSizeMenu, + child: ManageAccountMenuView() + ), + Expanded(child: ColoredBox( + color: AppColor.colorBgDesktop, + child: Column(children: [ + Obx(() { + if (controller.vacationResponse.value?.vacationResponderIsValid == true) { + return VacationNotificationMessageWidget( + margin: const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16), + fromAccountDashBoard: true, + vacationResponse: controller.vacationResponse.value!, + actionGotoVacationSetting: !controller.inVacationSettings() + ? () => controller.selectAccountMenuItem(AccountMenuItem.vacation) + : null, + actionEndNow: controller.disableVacationResponder); + } else if ((controller.vacationResponse.value?.vacationResponderIsWaiting == true + || controller.vacationResponse.value?.vacationResponderIsStopped == true) + && controller.accountMenuItemSelected.value == AccountMenuItem.vacation) { + return VacationNotificationMessageWidget( + margin: const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16), + fromAccountDashBoard: true, + vacationResponse: controller.vacationResponse.value!, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + leadingIcon: const Padding( + padding: EdgeInsetsDirectional.only(end: 12), + child: Icon(Icons.timer, size: 20), + ) + ); + } else { + return const SizedBox.shrink(); + } + }), + Expanded(child: _viewDisplayedOfAccountMenuItem()) + ]), + )) + ], + )) + ]), + mobile: SafeArea( + child: SettingsView( + closeAction: () => + controller.backToMailboxDashBoard(context: context)), + ) + ), ), ), );