From 722a2d94e1628c17db16704671ab4ada40b4498e Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 2 Jul 2025 17:04:46 +0700 Subject: [PATCH] Apply new style modal profile UI for desktop Signed-off-by: dab246 --- .../extensions/color_extension.dart | 1 + .../views/dialog/confirm_dialog_button.dart | 15 +- .../email_address_with_copy_widget.dart | 49 ++++ .../base/widget/user_avatar_builder.dart | 6 +- .../controller/single_email_controller.dart | 11 +- .../widgets/email_address_dialog_builder.dart | 273 ++++++++---------- .../presentation/widgets/mailbox_app_bar.dart | 3 +- .../profile_setting/profile_label_widget.dart | 28 +- 8 files changed, 197 insertions(+), 189 deletions(-) create mode 100644 lib/features/base/widget/email_address_with_copy_widget.dart diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index c6b7297e7..0a4df1a2f 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -262,6 +262,7 @@ extension AppColor on Color { static const textSecondary = Color(0xFF1C1B1F); static const profileMenuDivider = Color(0xFF1D192B); static const popupMenuItemHovered = Color(0xFFF8F8F8); + static const secondaryContrastText = Color(0xFFFFFFFF); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/views/dialog/confirm_dialog_button.dart b/core/lib/presentation/views/dialog/confirm_dialog_button.dart index 05fe59af4..2ad39afeb 100644 --- a/core/lib/presentation/views/dialog/confirm_dialog_button.dart +++ b/core/lib/presentation/views/dialog/confirm_dialog_button.dart @@ -5,6 +5,7 @@ class ConfirmDialogButton extends StatelessWidget { final String label; final Color? backgroundColor; final Color? textColor; + final Color? borderColor; final VoidCallback? onTapAction; const ConfirmDialogButton({ @@ -12,18 +13,26 @@ class ConfirmDialogButton extends StatelessWidget { required this.label, this.backgroundColor, this.textColor, + this.borderColor, this.onTapAction, }); @override Widget build(BuildContext context) { + final outlineBorder = borderColor != null + ? RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(100)), + side: BorderSide(width: 1, color: borderColor!), + ) + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(100)), + ); + return TextButton( style: TextButton.styleFrom( backgroundColor: backgroundColor, overlayColor: Theme.of(context).colorScheme.outline.withOpacity(0.08), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(100)), - ), + shape: outlineBorder, padding: const EdgeInsets.symmetric(horizontal: 10), ), onPressed: onTapAction, diff --git a/lib/features/base/widget/email_address_with_copy_widget.dart b/lib/features/base/widget/email_address_with_copy_widget.dart new file mode 100644 index 000000000..e2c79b5d5 --- /dev/null +++ b/lib/features/base/widget/email_address_with_copy_widget.dart @@ -0,0 +1,49 @@ + +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 EmailAddressWithCopyWidget extends StatelessWidget { + final String label; + final String copyLabelIcon; + final OnCopyButtonAction onCopyButtonAction; + final TextStyle? textStyle; + final Color? copyIconColor; + + const EmailAddressWithCopyWidget({ + super.key, + required this.label, + required this.copyLabelIcon, + required this.onCopyButtonAction, + this.textStyle, + this.copyIconColor, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: SelectableText( + label, + style: textStyle ?? ThemeUtils.textStyleM3BodyMedium, + maxLines: 1, + ), + ), + if (label.isNotEmpty) + TMailButtonWidget.fromIcon( + icon: copyLabelIcon, + backgroundColor: Colors.transparent, + iconSize: 20, + iconColor: copyIconColor ?? + AppColor.textSecondary.withOpacity(0.48), + padding: const EdgeInsets.all(5), + onTapActionCallback: onCopyButtonAction, + ), + ], + ); + } +} diff --git a/lib/features/base/widget/user_avatar_builder.dart b/lib/features/base/widget/user_avatar_builder.dart index e72f80d9d..902373933 100644 --- a/lib/features/base/widget/user_avatar_builder.dart +++ b/lib/features/base/widget/user_avatar_builder.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; class UserAvatarBuilder extends StatelessWidget { final String username; final double? size; + final TextStyle? textStyle; final EdgeInsetsGeometry? padding; final VoidCallback? onTapAction; @@ -12,6 +13,7 @@ class UserAvatarBuilder extends StatelessWidget { Key? key, required this.username, this.size, + this.textStyle, this.padding, this.onTapAction, }) : super(key: key); @@ -19,9 +21,9 @@ class UserAvatarBuilder extends StatelessWidget { @override Widget build(BuildContext context) { final avatarBuilder = AvatarBuilder() - ..text(username.firstLetterToUpperCase) + ..text(username) ..size(size ?? 32) - ..addTextStyle(Theme.of(context).textTheme.titleMedium?.copyWith( + ..addTextStyle(textStyle ?? Theme.of(context).textTheme.titleMedium?.copyWith( color: Colors.white, )) ..avatarColor(username.gradientColors); diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 4a720079f..66e3961a4 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -13,6 +13,7 @@ import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -1525,7 +1526,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { Get.dialog( PointerInterceptor( child: EmailAddressDialogBuilder( - emailAddress, + imagePaths: imagePaths, + emailAddress: emailAddress, onCloseDialogAction: () => popBack(), onCopyEmailAddressAction: (emailAddress) => copyEmailAddress(context, emailAddress), onComposeEmailAction: (emailAddress) => composeEmailFromEmailAddress(emailAddress), @@ -1538,8 +1540,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } void copyEmailAddress(BuildContext context, EmailAddress emailAddress) { - popBack(); - AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); + Clipboard.setData(ClipboardData(text: emailAddress.emailAddress)); + appToast.showToastSuccessMessage( + context, + AppLocalizations.of(context).email_address_copied_to_clipboard, + ); } void composeEmailFromEmailAddress(EmailAddress emailAddress) { diff --git a/lib/features/email/presentation/widgets/email_address_dialog_builder.dart b/lib/features/email/presentation/widgets/email_address_dialog_builder.dart index 7356c4aba..b2638dbba 100644 --- a/lib/features/email/presentation/widgets/email_address_dialog_builder.dart +++ b/lib/features/email/presentation/widgets/email_address_dialog_builder.dart @@ -1,11 +1,15 @@ - -import 'package:core/core.dart'; +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/theme_utils.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:core/presentation/views/dialog/confirm_dialog_button.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; -import 'package:model/model.dart'; +import 'package:model/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/base/widget/email_address_with_copy_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/user_avatar_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/utils/app_utils.dart'; typedef OnCloseDialogAction = void Function(); typedef OnCopyEmailAddressDialogAction = void Function(EmailAddress); @@ -13,174 +17,129 @@ typedef OnComposeEmailDialogAction = void Function(EmailAddress); typedef OnQuickCreatingRuleEmailDialogAction = void Function(EmailAddress); class EmailAddressDialogBuilder extends StatelessWidget { - - final EmailAddress _emailAddress; + final ImagePaths imagePaths; + final EmailAddress emailAddress; final OnCloseDialogAction? onCloseDialogAction; final OnCopyEmailAddressDialogAction? onCopyEmailAddressAction; final OnComposeEmailDialogAction? onComposeEmailAction; - final OnQuickCreatingRuleEmailDialogAction? onQuickCreatingRuleEmailDialogAction; + final OnQuickCreatingRuleEmailDialogAction? + onQuickCreatingRuleEmailDialogAction; - const EmailAddressDialogBuilder( - this._emailAddress, { - Key? key, - this.onCloseDialogAction, - this.onCopyEmailAddressAction, - this.onComposeEmailAction, - this.onQuickCreatingRuleEmailDialogAction, + const EmailAddressDialogBuilder({ + Key? key, + required this.imagePaths, + required this.emailAddress, + this.onCloseDialogAction, + this.onCopyEmailAddressAction, + this.onComposeEmailAction, + this.onQuickCreatingRuleEmailDialogAction, }) : super(key: key); @override Widget build(BuildContext context) { - final imagePaths = Get.find(); - return Dialog( shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(16))), - insetPadding: const EdgeInsets.symmetric( - horizontal: 24.0, - vertical: 16.0), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), child: Container( - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - width: 400, - decoration: const BoxDecoration( + width: 383, + decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(16))), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Align( - alignment: Alignment.centerRight, - child: TMailButtonWidget.fromIcon( - onTapActionCallback: onCloseDialogAction, - icon: imagePaths.icCircleClose, - backgroundColor: Colors.transparent, - margin: const EdgeInsetsDirectional.only(top: 8, end: 8), - iconSize: 24, - ) - ), - Padding(padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center(child: (AvatarBuilder() - ..text(_emailAddress.asString().firstLetterToUpperCase) - ..size(64) - ..addTextStyle(const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 23, - color: Colors.white)) - ..avatarColor(_emailAddress.avatarColors)) - .build())), - if (_emailAddress.displayName.isNotEmpty) - Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - top: 16), - child: Center(child: SelectableText( - _emailAddress.displayName, - textAlign: TextAlign.center, - maxLines: 2, - style: const TextStyle( - overflow: CommonTextStyle.defaultTextOverFlow, - fontSize: 20, - fontWeight: FontWeight.w600, - color: AppColor.colorNameEmail), - )) + borderRadius: const BorderRadius.all(Radius.circular(16)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 8, + spreadRadius: 3, + offset: const Offset(0, 4), ), - Material( - color: Colors.transparent, - child: InkWell( - onTap: () {}, - onLongPress: () { - AppUtils.copyEmailAddressToClipboard(context, _emailAddress.emailAddress); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), - child: Text( - _emailAddress.emailAddress, - textAlign: TextAlign.center, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - maxLines: 2, - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.normal, - color: AppColor.colorMessageConfirmDialog), - ) + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 3, + spreadRadius: 0, + offset: const Offset(0, 1), + ), + ], + ), + child: Stack( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(vertical: 24, horizontal: 32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + UserAvatarBuilder( + username: emailAddress.asString().firstLetterToUpperCase, + size: 67, + textStyle: ThemeUtils.textStyleInter500().copyWith( + fontSize: 33.5, + height: 50.25 / 33.5, + letterSpacing: 0.31, + color: AppColor.secondaryContrastText, + ), + ), + const SizedBox(height: 24), + if (emailAddress.displayName.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: SelectableText( + emailAddress.displayName, + textAlign: TextAlign.center, + style: ThemeUtils.textStyleM3HeadlineSmall.copyWith( + color: AppColor.textPrimary, + ), + ), + ), + EmailAddressWithCopyWidget( + label: emailAddress.emailAddress, + copyLabelIcon: imagePaths.icCopy, + onCopyButtonAction: () => + onCopyEmailAddressAction?.call(emailAddress), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + height: 48, + child: ConfirmDialogButton( + label: AppLocalizations.of(context).quickCreatingRule, + backgroundColor: Colors.white, + textColor: AppColor.primaryMain, + borderColor: AppColor.primaryMain, + onTapAction: () => + onQuickCreatingRuleEmailDialogAction?.call(emailAddress), + ), + ), + const SizedBox(height: 14), + SizedBox( + width: double.infinity, + height: 48, + child: ConfirmDialogButton( + label: AppLocalizations.of(context).compose_email, + backgroundColor: AppColor.primaryMain, + textColor: Colors.white, + onTapAction: () => + onComposeEmailAction?.call(emailAddress), + ), + ), + ], ), ), - ), - Padding(padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center(child: Material( - borderRadius: BorderRadius.circular(20), - color: Colors.transparent, - child: TextButton( - child: Text( - AppLocalizations.of(context).copy_email_address, - style: const TextStyle( - fontSize: 13, - color: AppColor.colorTextButton, - fontWeight: FontWeight.normal), - ), - onPressed: () => onCopyEmailAddressAction?.call(_emailAddress) - ) - ))), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.only(left: 24, right: 24, bottom: 12), - child: SizedBox( - key: const Key('quick_creating_rule_email_button'), - width: double.infinity, - height: 44, - child: TextButton( - style: ButtonStyle( - foregroundColor: WidgetStateProperty.resolveWith( - (Set states) => AppColor.colorTextButton), - backgroundColor: WidgetStateProperty.resolveWith( - (Set states) => AppColor.colorItemEmailSelectedDesktop), - shape: WidgetStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - width: 0, - color: AppColor.colorItemEmailSelectedDesktop)))), - child: Text( - AppLocalizations.of(context).quickCreatingRule, - style: const TextStyle( - fontSize: 17, - color: AppColor.colorTextButton, - fontWeight: FontWeight.w500)), - onPressed: () => onQuickCreatingRuleEmailDialogAction?.call(_emailAddress)), + PositionedDirectional( + top: 0, + end: 0, + child: TMailButtonWidget.fromIcon( + icon: imagePaths.icCloseDialog, + iconSize: 24, + iconColor: AppColor.m3Tertiary, + padding: const EdgeInsets.all(10), + borderRadius: 24, + backgroundColor: Colors.transparent, + onTapActionCallback: onCloseDialogAction, + ), ) - ), - Padding( - padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24), - child: SizedBox( - key: const Key('compose_email_button'), - width: double.infinity, - height: 44, - child: TextButton( - style: ButtonStyle( - foregroundColor: WidgetStateProperty.resolveWith( - (Set states) => Colors.white), - backgroundColor: WidgetStateProperty.resolveWith( - (Set states) => AppColor.colorTextButton), - shape: WidgetStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - width: 0, - color: AppColor.colorTextButton)))), - child: Text( - AppLocalizations.of(context).compose_email, - style: const TextStyle( - fontSize: 17, - color: Colors.white, - fontWeight: FontWeight.w500)), - onPressed: () => onComposeEmailAction?.call(_emailAddress)), - ) - ) - ], - ) - ), + ], + )), ); } } diff --git a/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart b/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart index 7aa048ad5..f902658b8 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart @@ -1,3 +1,4 @@ +import 'package:core/presentation/extensions/string_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.dart'; @@ -48,7 +49,7 @@ class MailboxAppBar extends StatelessWidget { onTapAction: openAppGridAction!, ), UserAvatarBuilder( - username: username, + username: username.firstLetterToUpperCase, padding: const EdgeInsetsDirectional.only(start: 4), onTapAction: openSettingsAction, ), 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 index 200e9aaf2..d7c9120d2 100644 --- 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 @@ -1,7 +1,5 @@ -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/base/widget/email_address_with_copy_widget.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/copy_subaddress_widget.dart'; class ProfileLabelWidget extends StatelessWidget { @@ -21,26 +19,10 @@ class ProfileLabelWidget extends StatelessWidget { 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, - ), - ], + child: EmailAddressWithCopyWidget( + label: label, + copyLabelIcon: copyLabelIcon, + onCopyButtonAction: onCopyButtonAction, ), ); }