Apply new style modal profile UI for mobile

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-03 01:28:57 +07:00
committed by Dat H. Pham
parent 722a2d94e1
commit 497b6325a8
4 changed files with 237 additions and 327 deletions
@@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:better_open_file/better_open_file.dart' as open_file; import 'package:better_open_file/better_open_file.dart' as open_file;
import 'package:core/core.dart'; import 'package:core/core.dart';
@@ -1516,23 +1515,43 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void openEmailAddressDialog(BuildContext context, EmailAddress emailAddress) { void openEmailAddressDialog(BuildContext context, EmailAddress emailAddress) {
if (responsiveUtils.isScreenWithShortestSide(context)) { if (responsiveUtils.isScreenWithShortestSide(context)) {
(EmailAddressBottomSheetBuilder(context, imagePaths, emailAddress) Get.bottomSheet(
..addOnCloseContextMenuAction(() => popBack()) PointerInterceptor(
..addOnCopyEmailAddressAction((emailAddress) => copyEmailAddress(context, emailAddress)) child: EmailAddressBottomSheetBuilder(
..addOnComposeEmailAction((emailAddress) => composeEmailFromEmailAddress(emailAddress)) imagePaths: imagePaths,
..addOnQuickCreatingRuleEmailBottomSheetAction((emailAddress) => quickCreatingRule(context, emailAddress)) emailAddress: emailAddress,
).show(); onCloseDialogAction: popBack,
onCopyEmailAddressAction: (emailAddress) =>
copyEmailAddress(context, emailAddress),
onComposeEmailAction: composeEmailFromEmailAddress,
onQuickCreatingRuleEmailDialogAction: (emailAddress) =>
quickCreatingRule(context, emailAddress),
),
),
useRootNavigator: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.only(
topStart: Radius.circular(16.0),
topEnd: Radius.circular(16.0),
),
),
isScrollControlled: true,
enableDrag: false,
backgroundColor: Colors.transparent,
);
} else { } else {
Get.dialog( Get.dialog(
PointerInterceptor( PointerInterceptor(
child: EmailAddressDialogBuilder( child: EmailAddressDialogBuilder(
imagePaths: imagePaths, imagePaths: imagePaths,
emailAddress: emailAddress, emailAddress: emailAddress,
onCloseDialogAction: () => popBack(), onCloseDialogAction: popBack,
onCopyEmailAddressAction: (emailAddress) => copyEmailAddress(context, emailAddress), onCopyEmailAddressAction: (emailAddress) =>
onComposeEmailAction: (emailAddress) => composeEmailFromEmailAddress(emailAddress), copyEmailAddress(context, emailAddress),
onQuickCreatingRuleEmailDialogAction: (emailAddress) => quickCreatingRule(context, emailAddress) onComposeEmailAction: composeEmailFromEmailAddress,
) onQuickCreatingRuleEmailDialogAction: (emailAddress) =>
quickCreatingRule(context, emailAddress),
),
), ),
barrierColor: AppColor.colorDefaultCupertinoActionSheet, barrierColor: AppColor.colorDefaultCupertinoActionSheet,
); );
@@ -1,216 +1,65 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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:tmail_ui_user/features/email/presentation/widgets/email_address_detail_widget.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
typedef OnCloseBottomSheetAction = void Function(); class EmailAddressBottomSheetBuilder extends StatelessWidget {
typedef OnCopyEmailAddressBottomSheetAction = void Function(EmailAddress); final ImagePaths imagePaths;
typedef OnComposeEmailBottomSheetAction = void Function(EmailAddress); final EmailAddress emailAddress;
typedef OnQuickCreatingRuleEmailBottomSheetAction = void Function(EmailAddress); final OnCloseDialogAction? onCloseDialogAction;
final OnCopyEmailAddressDialogAction? onCopyEmailAddressAction;
final OnComposeEmailDialogAction? onComposeEmailAction;
final OnQuickCreatingRuleEmailDialogAction?
onQuickCreatingRuleEmailDialogAction;
class EmailAddressBottomSheetBuilder { const EmailAddressBottomSheetBuilder({
Key? key,
required this.imagePaths,
required this.emailAddress,
this.onCloseDialogAction,
this.onCopyEmailAddressAction,
this.onComposeEmailAction,
this.onQuickCreatingRuleEmailDialogAction,
}) : super(key: key);
final BuildContext _context; @override
final ImagePaths _imagePaths; Widget build(BuildContext context) {
final EmailAddress _emailAddress; return Container(
decoration: BoxDecoration(
OnCloseBottomSheetAction? _onCloseBottomSheetAction; color: Colors.white,
OnCopyEmailAddressBottomSheetAction? _onCopyEmailAddressAction; borderRadius: const BorderRadiusDirectional.only(
OnComposeEmailBottomSheetAction? _onComposeEmailAction; topStart: Radius.circular(16.0),
OnQuickCreatingRuleEmailBottomSheetAction? _creatingRuleEmailBottomSheetAction; topEnd: Radius.circular(16.0),
EmailAddressBottomSheetBuilder(
this._context,
this._imagePaths,
this._emailAddress
);
void addOnCloseContextMenuAction(OnCloseBottomSheetAction onCloseBottomSheetAction) {
_onCloseBottomSheetAction = onCloseBottomSheetAction;
}
void addOnCopyEmailAddressAction(OnCopyEmailAddressBottomSheetAction onCopyEmailAddressAction) {
_onCopyEmailAddressAction = onCopyEmailAddressAction;
}
void addOnComposeEmailAction(OnComposeEmailBottomSheetAction onComposeEmailAction) {
_onComposeEmailAction = onComposeEmailAction;
}
void addOnQuickCreatingRuleEmailBottomSheetAction(OnQuickCreatingRuleEmailBottomSheetAction creatingRuleEmailBottomSheetAction) {
_creatingRuleEmailBottomSheetAction = creatingRuleEmailBottomSheetAction;
}
RoundedRectangleBorder _shape() {
return const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0)));
}
BoxDecoration _decoration(BuildContext context) {
return const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0)));
}
void show() {
Get.bottomSheet(
PointerInterceptor(child: GestureDetector(
onTap: () => _onCloseBottomSheetAction?.call(),
child: SingleChildScrollView(
child: Container(
margin: EdgeInsets.zero,
decoration: _decoration(_context),
child: SafeArea(child: GestureDetector(
onTap: () => {},
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
padding: const EdgeInsets.only(top: 16, right: 16),
onPressed: () => _onCloseBottomSheetAction?.call(),
icon: SvgPicture.asset(
_imagePaths.icCircleClose,
width: 24,
height: 24,
fit: BoxFit.fill))),
(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: SelectableText(
_emailAddress.displayName,
textAlign: TextAlign.center,
maxLines: 2,
style: const TextStyle(
overflow: CommonTextStyle.defaultTextOverFlow,
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColor.colorNameEmail),
)),
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),
)
),
),
),
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<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: () => _creatingRuleEmailBottomSheetAction?.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)),
)
)
],
),
)),
),
), ),
)), boxShadow: [
useRootNavigator: true, BoxShadow(
shape: _shape(), color: Colors.black.withOpacity(0.15),
isScrollControlled: true, blurRadius: 8,
enableDrag: false, spreadRadius: 3,
backgroundColor: Colors.transparent offset: const Offset(0, 4),
),
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 3,
spreadRadius: 0,
offset: const Offset(0, 1),
),
],
),
child: SafeArea(
top: false,
left: false,
right: false,
child: EmailAddressDetailWidget(
imagePaths: imagePaths,
emailAddress: emailAddress,
onCloseDialogAction: onCloseDialogAction,
onCopyEmailAddressAction: onCopyEmailAddressAction,
onComposeEmailAction: onComposeEmailAction,
onQuickCreatingRuleEmailDialogAction:
onQuickCreatingRuleEmailDialogAction,
),
),
); );
} }
} }
@@ -0,0 +1,119 @@
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:jmap_dart_client/jmap/mail/email/email_address.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';
typedef OnCloseDialogAction = void Function();
typedef OnCopyEmailAddressDialogAction = void Function(EmailAddress);
typedef OnComposeEmailDialogAction = void Function(EmailAddress);
typedef OnQuickCreatingRuleEmailDialogAction = void Function(EmailAddress);
class EmailAddressDetailWidget extends StatelessWidget {
final ImagePaths imagePaths;
final EmailAddress emailAddress;
final OnCloseDialogAction? onCloseDialogAction;
final OnCopyEmailAddressDialogAction? onCopyEmailAddressAction;
final OnComposeEmailDialogAction? onComposeEmailAction;
final OnQuickCreatingRuleEmailDialogAction?
onQuickCreatingRuleEmailDialogAction;
const EmailAddressDetailWidget({
Key? key,
required this.imagePaths,
required this.emailAddress,
this.onCloseDialogAction,
this.onCopyEmailAddressAction,
this.onComposeEmailAction,
this.onQuickCreatingRuleEmailDialogAction,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return 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,
maxLines: 1,
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(
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,
),
),
],
);
}
}
@@ -1,15 +1,7 @@
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/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:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/extensions/email_address_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_detail_widget.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';
typedef OnCloseDialogAction = void Function(); typedef OnCloseDialogAction = void Function();
typedef OnCopyEmailAddressDialogAction = void Function(EmailAddress); typedef OnCopyEmailAddressDialogAction = void Function(EmailAddress);
@@ -42,104 +34,35 @@ class EmailAddressDialogBuilder extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(16)), borderRadius: BorderRadius.all(Radius.circular(16)),
), ),
child: Container( child: Container(
width: 383, width: 383,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(16)), borderRadius: const BorderRadius.all(Radius.circular(16)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.15), color: Colors.black.withOpacity(0.15),
blurRadius: 8, blurRadius: 8,
spreadRadius: 3, spreadRadius: 3,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.3), color: Colors.black.withOpacity(0.3),
blurRadius: 3, blurRadius: 3,
spreadRadius: 0, spreadRadius: 0,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
], ],
), ),
child: Stack( child: EmailAddressDetailWidget(
children: [ imagePaths: imagePaths,
Padding( emailAddress: emailAddress,
padding: onCloseDialogAction: onCloseDialogAction,
const EdgeInsets.symmetric(vertical: 24, horizontal: 32), onCopyEmailAddressAction: onCopyEmailAddressAction,
child: Column( onComposeEmailAction: onComposeEmailAction,
mainAxisSize: MainAxisSize.min, onQuickCreatingRuleEmailDialogAction:
children: [ onQuickCreatingRuleEmailDialogAction,
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),
),
),
],
),
),
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,
),
)
],
)),
); );
} }
} }