Apply new style modal profile UI for desktop

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-02 17:04:46 +07:00
committed by Dat H. Pham
parent 5744e4782d
commit 722a2d94e1
8 changed files with 197 additions and 189 deletions
@@ -262,6 +262,7 @@ extension AppColor on Color {
static const textSecondary = Color(0xFF1C1B1F); static const textSecondary = Color(0xFF1C1B1F);
static const profileMenuDivider = Color(0xFF1D192B); static const profileMenuDivider = Color(0xFF1D192B);
static const popupMenuItemHovered = Color(0xFFF8F8F8); static const popupMenuItemHovered = Color(0xFFF8F8F8);
static const secondaryContrastText = Color(0xFFFFFFFF);
static const mapGradientColor = [ static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)], [Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -5,6 +5,7 @@ class ConfirmDialogButton extends StatelessWidget {
final String label; final String label;
final Color? backgroundColor; final Color? backgroundColor;
final Color? textColor; final Color? textColor;
final Color? borderColor;
final VoidCallback? onTapAction; final VoidCallback? onTapAction;
const ConfirmDialogButton({ const ConfirmDialogButton({
@@ -12,18 +13,26 @@ class ConfirmDialogButton extends StatelessWidget {
required this.label, required this.label,
this.backgroundColor, this.backgroundColor,
this.textColor, this.textColor,
this.borderColor,
this.onTapAction, this.onTapAction,
}); });
@override @override
Widget build(BuildContext context) { 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( return TextButton(
style: TextButton.styleFrom( style: TextButton.styleFrom(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
overlayColor: Theme.of(context).colorScheme.outline.withOpacity(0.08), overlayColor: Theme.of(context).colorScheme.outline.withOpacity(0.08),
shape: const RoundedRectangleBorder( shape: outlineBorder,
borderRadius: BorderRadius.all(Radius.circular(100)),
),
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
), ),
onPressed: onTapAction, onPressed: onTapAction,
@@ -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,
),
],
);
}
}
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
class UserAvatarBuilder extends StatelessWidget { class UserAvatarBuilder extends StatelessWidget {
final String username; final String username;
final double? size; final double? size;
final TextStyle? textStyle;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
final VoidCallback? onTapAction; final VoidCallback? onTapAction;
@@ -12,6 +13,7 @@ class UserAvatarBuilder extends StatelessWidget {
Key? key, Key? key,
required this.username, required this.username,
this.size, this.size,
this.textStyle,
this.padding, this.padding,
this.onTapAction, this.onTapAction,
}) : super(key: key); }) : super(key: key);
@@ -19,9 +21,9 @@ class UserAvatarBuilder extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final avatarBuilder = AvatarBuilder() final avatarBuilder = AvatarBuilder()
..text(username.firstLetterToUpperCase) ..text(username)
..size(size ?? 32) ..size(size ?? 32)
..addTextStyle(Theme.of(context).textTheme.titleMedium?.copyWith( ..addTextStyle(textStyle ?? Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.white, color: Colors.white,
)) ))
..avatarColor(username.gradientColors); ..avatarColor(username.gradientColors);
@@ -13,6 +13,7 @@ import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@@ -1525,7 +1526,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
Get.dialog( Get.dialog(
PointerInterceptor( PointerInterceptor(
child: EmailAddressDialogBuilder( child: EmailAddressDialogBuilder(
emailAddress, imagePaths: imagePaths,
emailAddress: emailAddress,
onCloseDialogAction: () => popBack(), onCloseDialogAction: () => popBack(),
onCopyEmailAddressAction: (emailAddress) => copyEmailAddress(context, emailAddress), onCopyEmailAddressAction: (emailAddress) => copyEmailAddress(context, emailAddress),
onComposeEmailAction: (emailAddress) => composeEmailFromEmailAddress(emailAddress), onComposeEmailAction: (emailAddress) => composeEmailFromEmailAddress(emailAddress),
@@ -1538,8 +1540,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
void copyEmailAddress(BuildContext context, EmailAddress emailAddress) { void copyEmailAddress(BuildContext context, EmailAddress emailAddress) {
popBack(); Clipboard.setData(ClipboardData(text: emailAddress.emailAddress));
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); appToast.showToastSuccessMessage(
context,
AppLocalizations.of(context).email_address_copied_to_clipboard,
);
} }
void composeEmailFromEmailAddress(EmailAddress emailAddress) { void composeEmailFromEmailAddress(EmailAddress emailAddress) {
@@ -1,11 +1,15 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/core.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:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.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/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
typedef OnCloseDialogAction = void Function(); typedef OnCloseDialogAction = void Function();
typedef OnCopyEmailAddressDialogAction = void Function(EmailAddress); typedef OnCopyEmailAddressDialogAction = void Function(EmailAddress);
@@ -13,174 +17,129 @@ typedef OnComposeEmailDialogAction = void Function(EmailAddress);
typedef OnQuickCreatingRuleEmailDialogAction = void Function(EmailAddress); typedef OnQuickCreatingRuleEmailDialogAction = void Function(EmailAddress);
class EmailAddressDialogBuilder extends StatelessWidget { class EmailAddressDialogBuilder extends StatelessWidget {
final ImagePaths imagePaths;
final EmailAddress _emailAddress; final EmailAddress emailAddress;
final OnCloseDialogAction? onCloseDialogAction; final OnCloseDialogAction? onCloseDialogAction;
final OnCopyEmailAddressDialogAction? onCopyEmailAddressAction; final OnCopyEmailAddressDialogAction? onCopyEmailAddressAction;
final OnComposeEmailDialogAction? onComposeEmailAction; final OnComposeEmailDialogAction? onComposeEmailAction;
final OnQuickCreatingRuleEmailDialogAction? onQuickCreatingRuleEmailDialogAction; final OnQuickCreatingRuleEmailDialogAction?
onQuickCreatingRuleEmailDialogAction;
const EmailAddressDialogBuilder( const EmailAddressDialogBuilder({
this._emailAddress, { Key? key,
Key? key, required this.imagePaths,
this.onCloseDialogAction, required this.emailAddress,
this.onCopyEmailAddressAction, this.onCloseDialogAction,
this.onComposeEmailAction, this.onCopyEmailAddressAction,
this.onQuickCreatingRuleEmailDialogAction, this.onComposeEmailAction,
this.onQuickCreatingRuleEmailDialogAction,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final imagePaths = Get.find<ImagePaths>();
return Dialog( return Dialog(
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16))), borderRadius: BorderRadius.all(Radius.circular(16)),
insetPadding: const EdgeInsets.symmetric( ),
horizontal: 24.0,
vertical: 16.0),
child: Container( child: Container(
margin: EdgeInsets.zero, width: 383,
padding: EdgeInsets.zero, decoration: BoxDecoration(
width: 400,
decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16))), borderRadius: const BorderRadius.all(Radius.circular(16)),
child: Column( boxShadow: [
mainAxisSize: MainAxisSize.min, BoxShadow(
children: [ color: Colors.black.withOpacity(0.15),
Align( blurRadius: 8,
alignment: Alignment.centerRight, spreadRadius: 3,
child: TMailButtonWidget.fromIcon( offset: const Offset(0, 4),
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),
))
), ),
Material( BoxShadow(
color: Colors.transparent, color: Colors.black.withOpacity(0.3),
child: InkWell( blurRadius: 3,
onTap: () {}, spreadRadius: 0,
onLongPress: () { offset: const Offset(0, 1),
AppUtils.copyEmailAddressToClipboard(context, _emailAddress.emailAddress); ),
}, ],
borderRadius: const BorderRadius.all(Radius.circular(8)), ),
child: Padding( child: Stack(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), children: [
child: Text( Padding(
_emailAddress.emailAddress, padding:
textAlign: TextAlign.center, const EdgeInsets.symmetric(vertical: 24, horizontal: 32),
overflow: CommonTextStyle.defaultTextOverFlow, child: Column(
softWrap: CommonTextStyle.defaultSoftWrap, mainAxisSize: MainAxisSize.min,
maxLines: 2, children: [
style: const TextStyle( UserAvatarBuilder(
fontSize: 17, username: emailAddress.asString().firstLetterToUpperCase,
fontWeight: FontWeight.normal, size: 67,
color: AppColor.colorMessageConfirmDialog), 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),
),
),
],
), ),
), ),
), PositionedDirectional(
Padding(padding: const EdgeInsets.symmetric(horizontal: 16), top: 0,
child: Center(child: Material( end: 0,
borderRadius: BorderRadius.circular(20), child: TMailButtonWidget.fromIcon(
color: Colors.transparent, icon: imagePaths.icCloseDialog,
child: TextButton( iconSize: 24,
child: Text( iconColor: AppColor.m3Tertiary,
AppLocalizations.of(context).copy_email_address, padding: const EdgeInsets.all(10),
style: const TextStyle( borderRadius: 24,
fontSize: 13, backgroundColor: Colors.transparent,
color: AppColor.colorTextButton, onTapActionCallback: onCloseDialogAction,
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<Color>(
(Set<WidgetState> states) => AppColor.colorTextButton),
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> 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)),
) )
), ],
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<Color>(
(Set<WidgetState> states) => Colors.white),
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> 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)),
)
)
],
)
),
); );
} }
} }
@@ -1,3 +1,4 @@
import 'package:core/presentation/extensions/string_extension.dart';
import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/resources/image_paths.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.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!, onTapAction: openAppGridAction!,
), ),
UserAvatarBuilder( UserAvatarBuilder(
username: username, username: username.firstLetterToUpperCase,
padding: const EdgeInsetsDirectional.only(start: 4), padding: const EdgeInsetsDirectional.only(start: 4),
onTapAction: openSettingsAction, onTapAction: openSettingsAction,
), ),
@@ -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: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'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/copy_subaddress_widget.dart';
class ProfileLabelWidget extends StatelessWidget { class ProfileLabelWidget extends StatelessWidget {
@@ -21,26 +19,10 @@ class ProfileLabelWidget extends StatelessWidget {
return Container( return Container(
height: 40, height: 40,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: EmailAddressWithCopyWidget(
mainAxisSize: MainAxisSize.min, label: label,
children: [ copyLabelIcon: copyLabelIcon,
Flexible( onCopyButtonAction: onCopyButtonAction,
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,
),
],
), ),
); );
} }