From 537889f9343623b0852c2fe2744a2f2840c23da1 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 2 Jun 2025 01:40:13 +0700 Subject: [PATCH] TF-3760 Change style of input dialog on web Signed-off-by: dab246 --- .../extensions/color_extension.dart | 2 + core/lib/presentation/utils/theme_utils.dart | 18 + .../views/button/tmail_button_widget.dart | 7 + .../container/tmail_container_widget.dart | 3 + .../dialog/edit_text_dialog_builder.dart | 360 +++++++++++------- .../base/base_mailbox_controller.dart | 50 +-- .../mixin/message_dialog_action_mixin.dart | 32 ++ 7 files changed, 297 insertions(+), 175 deletions(-) diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 4a7f17f79..ed126a51b 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -245,9 +245,11 @@ extension AppColor on Color { static const blue400 = Color(0xFF80BDFF); static const m3Tertiary = Color(0xFF8C9CAF); static const m3Neutral70 = Color(0xFFAEAAAE); + static const m3Neutral90 = Color(0xFFE6E1E5); static const grayBackgroundColor = Color(0xFFF3F6F9); static const m3SurfaceBackground = Color(0xFF1C1B1F); static const warningColor = Color(0xFFFFC107); + static const primaryMain = Color(0xFF0A84FF); 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 4d9a5d2a6..18b1e8f3c 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -202,6 +202,15 @@ class ThemeUtils { color: color, ); + static const textStyleM3HeadlineSmall = TextStyle( + fontFamily: ConstantsUI.fontApp, + fontWeight: FontWeight.w600, + letterSpacing: 0.0, + fontSize: 24, + height: 32 / 24, + color: AppColor.m3SurfaceBackground, + ); + static TextStyle textStyleInter700({ Color? color, double? fontSize, @@ -214,6 +223,15 @@ class ThemeUtils { color: color, ); + static TextStyle textStyleM3LabelLarge({Color? color}) => TextStyle( + fontFamily: ConstantsUI.fontApp, + fontWeight: FontWeight.w500, + letterSpacing: 0.1, + fontSize: 14, + height: 20 / 14, + color: color ??AppColor.primaryMain, + ); + static TextSelectionThemeData get _textSelectionTheme { return const TextSelectionThemeData( cursorColor: AppColor.primaryColor, diff --git a/core/lib/presentation/views/button/tmail_button_widget.dart b/core/lib/presentation/views/button/tmail_button_widget.dart index a42a2de2e..ac9f78023 100644 --- a/core/lib/presentation/views/button/tmail_button_widget.dart +++ b/core/lib/presentation/views/button/tmail_button_widget.dart @@ -42,6 +42,7 @@ class TMailButtonWidget extends StatelessWidget { final bool isLoading; final Color? hoverColor; final TextOverflow? textOverflow; + final Alignment? alignment; const TMailButtonWidget({ super.key, @@ -78,6 +79,7 @@ class TMailButtonWidget extends StatelessWidget { this.isLoading = false, this.hoverColor, this.textOverflow, + this.alignment, }); factory TMailButtonWidget.fromIcon({ @@ -104,6 +106,7 @@ class TMailButtonWidget extends StatelessWidget { List? boxShadow, EdgeInsetsGeometry? margin, Color? hoverColor, + Alignment? alignment, }) { return TMailButtonWidget( key: key, @@ -130,6 +133,7 @@ class TMailButtonWidget extends StatelessWidget { boxShadow: boxShadow, margin: margin, hoverColor: hoverColor, + alignment: alignment, ); } @@ -157,6 +161,7 @@ class TMailButtonWidget extends StatelessWidget { int? maxLines, Color? hoverColor, TextOverflow? textOverflow, + Alignment? alignment, }) { return TMailButtonWidget( key: key, @@ -182,6 +187,7 @@ class TMailButtonWidget extends StatelessWidget { maxLines: maxLines, hoverColor: hoverColor, textOverflow: textOverflow, + alignment: alignment, ); } @@ -371,6 +377,7 @@ class TMailButtonWidget extends StatelessWidget { boxShadow: boxShadow, border: border, hoverColor: hoverColor, + alignment: alignment, child: childWidget, ); } diff --git a/core/lib/presentation/views/container/tmail_container_widget.dart b/core/lib/presentation/views/container/tmail_container_widget.dart index e19df69f3..70974fba2 100644 --- a/core/lib/presentation/views/container/tmail_container_widget.dart +++ b/core/lib/presentation/views/container/tmail_container_widget.dart @@ -23,6 +23,7 @@ class TMailContainerWidget extends StatelessWidget { final List? boxShadow; final BoxBorder? border; final Color? hoverColor; + final Alignment? alignment; const TMailContainerWidget({ super.key, @@ -43,6 +44,7 @@ class TMailContainerWidget extends StatelessWidget { this.margin, this.border, this.hoverColor, + this.alignment, }); @override @@ -61,6 +63,7 @@ class TMailContainerWidget extends StatelessWidget { maxHeight: maxHeight, minWidth: minWidth, ), + alignment: alignment, padding: padding ?? const EdgeInsetsDirectional.all(8), child: child, ); diff --git a/core/lib/presentation/views/dialog/edit_text_dialog_builder.dart b/core/lib/presentation/views/dialog/edit_text_dialog_builder.dart index c6caf3a53..357949b00 100644 --- a/core/lib/presentation/views/dialog/edit_text_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/edit_text_dialog_builder.dart @@ -1,175 +1,243 @@ - import 'dart:async'; - -import 'package:core/core.dart'; +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:core/presentation/views/text/text_field_builder.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -typedef OnConfirmButtonEditDialogAction = void Function(String); -typedef SetErrorStringEditDialog = String? Function(String); +typedef OnInputDialogPositiveButtonAction = void Function(String); +typedef OnInputDialogNegativeButtonAction = void Function(); +typedef OnInputDialogInputErrorChangedAction = String? Function(String); +typedef OnInputDialogCloseButtonAction = void Function(); -class EditTextDialogBuilder { +class EditTextDialogBuilder extends StatefulWidget { + final String title; + final String value; + final String positiveText; + final String negativeText; + final String? closeIcon; + final OnInputDialogPositiveButtonAction onPositiveButtonAction; + final OnInputDialogNegativeButtonAction? onNegativeButtonAction; + final OnInputDialogInputErrorChangedAction? onInputErrorChanged; + final OnInputDialogCloseButtonAction? onCloseButtonAction; + + const EditTextDialogBuilder({ + super.key, + required this.title, + required this.value, + required this.positiveText, + required this.negativeText, + required this.onPositiveButtonAction, + this.onNegativeButtonAction, + this.onInputErrorChanged, + this.closeIcon, + this.onCloseButtonAction, + }); + + @override + State createState() => _EditTextDialogBuilderState(); +} + +class _EditTextDialogBuilderState extends State { late TextEditingController _textController; - - Key? _key; - String _title = ''; - String _hintText = ''; - String _confirmText = ''; - String _cancelText = ''; - - OnConfirmButtonEditDialogAction? _onConfirmButtonAction; - - SetErrorStringEditDialog? _setErrorString; + late FocusNode _focusNode; String? _error; Timer? _debounce; - EditTextDialogBuilder(); - - void key(Key key) { - _key = key; + @override + void initState() { + super.initState(); + _textController = TextEditingController(text: widget.value) + ..selection = TextSelection( + baseOffset: 0, + extentOffset: widget.value.length, + ); + _focusNode = FocusNode(); } - void title(String title) { - _title = title; - } - - void hintText(String hintText) { - _hintText = hintText; - } - - void cancelText(String cancelText) { - _cancelText = cancelText; - } - - void onConfirmButtonAction(String confirmText, OnConfirmButtonEditDialogAction? onConfirmButtonAction) { - _confirmText = confirmText; - _onConfirmButtonAction = onConfirmButtonAction; - } - - void setTextController(TextEditingController textEditingController) { - _textController = textEditingController; - } - - void setTextSelection(TextSelection textSelection, {required String value}) { - _textController = TextEditingController.fromValue(TextEditingValue(text: value, selection: textSelection)); - } - - void setErrorString(SetErrorStringModelSheets setErrorString) { - _setErrorString = setErrorString; - } - - void _onTextChanged(String name, StateSetter setState) { - if (_debounce?.isActive ?? false) _debounce?.cancel(); - _debounce = Timer(const Duration(milliseconds: 500), () { - setState(() { - _error = (_setErrorString != null) ? _setErrorString!(name) : ''; - }); - }); - } - - void _onConfirmButtonPress(BuildContext context) { - if (_error == null || (_error != null && _error!.isEmpty)) { - Get.back(); - _onConfirmButtonAction?.call(_textController.text); + @override + void didUpdateWidget(EditTextDialogBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.value != widget.value) { + _textController.text = widget.value; + _textController.selection = TextSelection( + baseOffset: 0, + extentOffset: widget.value.length, + ); } } - void _onCancelButtonPress(BuildContext context) { - Get.back(); + @override + void dispose() { + _textController.dispose(); + _focusNode.dispose(); _debounce?.cancel(); + super.dispose(); } - Widget build() { + @override + Widget build(BuildContext context) { return Dialog( - key: _key, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))), - insetPadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), - child: Container( - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - width: 400, - decoration: const BoxDecoration( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + child: GestureDetector( + onTap: _focusNode.unfocus, + behavior: HitTestBehavior.translucent, + child: Container( + width: 383, + decoration: const BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20))), - child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - return Container( - padding: const EdgeInsets.only(left: 30, right: 30, top: 30, bottom: 24), - child: Wrap( - children: [ - Text( - _title, - style: const TextStyle(fontSize: 20, color: AppColor.colorNameEmail, fontWeight: FontWeight.w700), - textAlign: TextAlign.center), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + child: Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 48, + alignment: Alignment.center, + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsetsDirectional.only( + start: 24, + end: 28, + ), + child: Text( + widget.title, + style: ThemeUtils.textStyleM3HeadlineSmall, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + const SizedBox(height: 20), Padding( - padding: const EdgeInsets.only(top: 20), - child: TextFormFieldBuilder( - keyboardType: TextInputType.visiblePassword, - onTextChange: (value) => _onTextChanged(value, setState), - autoFocus: true, - controller: _textController, - decoration: InputDecoration( - errorText: _error, - enabledBorder: const UnderlineInputBorder(borderSide: BorderSide(color: AppColor.colorDividerMailbox)), - hintText: _hintText), - ) + padding: const EdgeInsetsDirectional.only( + start: 24, + end: 28, + top: 8, + bottom: 8, + ), + child: TextFieldBuilder( + controller: _textController, + focusNode: _focusNode, + autoFocus: true, + maxLines: 1, + textStyle: ThemeUtils.textStyleBodyBody3( + color: AppColor.m3SurfaceBackground, + ), + onTextChange: _onTextChanged, + decoration: InputDecoration( + contentPadding: const EdgeInsetsDirectional.only( + start: 12, + end: 8, + top: 8, + bottom: 8, + ), + enabledBorder: _buildBorder(AppColor.m3Neutral90), + border: _buildBorder(AppColor.m3Neutral90), + focusedBorder: _buildBorder(AppColor.primaryMain), + errorBorder: _buildBorder(AppColor.colorErrorState), + focusedErrorBorder: _buildBorder( + AppColor.colorErrorState, + ), + errorStyle: ThemeUtils.textStyleBodyBody3( + color: AppColor.colorErrorState, + ), + errorText: _error, + ), + ), ), Padding( - padding: const EdgeInsets.only(left: 16, right: 16, top: 24), - child: Row( - children: [ - Expanded( - child: _buildButton( - name: _cancelText, - bgColor: AppColor.colorContentEmail, - action: () => _onCancelButtonPress(context)) - ), - const SizedBox(width: 20), - Expanded( - child: _buildButton( - name: _confirmText, - bgColor: (_error == null || (_error != null && _error!.isEmpty)) - ? AppColor.colorTextButton - : AppColor.colorDisableMailboxCreateButton, - nameColor: (_error == null || (_error != null && _error!.isEmpty)) - ? Colors.white - : AppColor.colorDisableMailboxCreateButton, - action: () => _onConfirmButtonPress(context)) - ) - ] - ) - ) + padding: const EdgeInsetsDirectional.only( + start: 24, + end: 28, + top: 25, + bottom: 25, + ), + child: Row( + children: [ + const Spacer(), + TMailButtonWidget.fromText( + text: widget.negativeText, + textStyle: ThemeUtils.textStyleM3LabelLarge(), + maxLines: 1, + alignment: Alignment.center, + height: 48, + borderRadius: 100, + padding: const EdgeInsets.symmetric(horizontal: 10), + backgroundColor: Colors.transparent, + onTapActionCallback: _onNegativeAction, + ), + const SizedBox(width: 8), + TMailButtonWidget.fromText( + text: widget.positiveText, + textStyle: ThemeUtils.textStyleM3LabelLarge( + color: Colors.white, + ), + maxLines: 1, + alignment: Alignment.center, + height: 48, + borderRadius: 100, + padding: const EdgeInsets.symmetric(horizontal: 32), + backgroundColor: AppColor.primaryMain, + onTapActionCallback: _onPositiveAction, + ), + ], + ), + ), ], - ) - ); - }) + ), + if (_showCloseButton()) _buildCloseButton(), + ], + ), + ), ), ); } - Widget _buildButton({ - String? name, Color? nameColor, Color? bgColor, Function? action - }) { - return SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - onPressed: () => action?.call(), - style: ButtonStyle( - foregroundColor: WidgetStateProperty.resolveWith( - (Set states) => bgColor ?? AppColor.colorTextButton), - backgroundColor: WidgetStateProperty.resolveWith( - (Set states) => bgColor ?? AppColor.colorTextButton), - shape: WidgetStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton), - )), - padding: WidgetStateProperty.resolveWith( - (Set states) => const EdgeInsets.symmetric(horizontal: 16)), - elevation: WidgetStateProperty.resolveWith((Set states) => 0)), - child: Text(name ?? '', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: nameColor ?? Colors.white)), - ) + InputBorder _buildBorder(Color color) => OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: color), + ); + + void _onTextChanged(String value) { + _debounce?.cancel(); + _debounce = Timer(const Duration(milliseconds: 500), () { + if (!mounted) return; + setState(() { + _error = widget.onInputErrorChanged?.call(value); + }); + }); + } + + void _onPositiveAction() { + if (_error?.isNotEmpty != true) { + widget.onPositiveButtonAction(_textController.text); + } + } + + void _onNegativeAction() { + _debounce?.cancel(); + widget.onNegativeButtonAction?.call(); + } + + bool _showCloseButton() => + widget.onCloseButtonAction != null && widget.closeIcon != null; + + Widget _buildCloseButton() { + return PositionedDirectional( + top: 0, + end: 0, + child: TMailButtonWidget.fromIcon( + icon: widget.closeIcon!, + iconSize: 24, + iconColor: AppColor.m3Tertiary, + padding: const EdgeInsets.all(12), + borderRadius: 24, + backgroundColor: Colors.transparent, + onTapActionCallback: widget.onCloseButtonAction, + ), ); } } diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart index e77170e69..9b6e109d3 100644 --- a/lib/features/base/base_mailbox_controller.dart +++ b/lib/features/base/base_mailbox_controller.dart @@ -3,7 +3,6 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart'; import 'package:core/presentation/views/dialog/confirmation_dialog_builder.dart'; -import 'package:core/presentation/views/dialog/edit_text_dialog_builder.dart'; import 'package:core/presentation/views/modal_sheets/edit_text_modal_sheet_builder.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; @@ -379,34 +378,27 @@ abstract class BaseMailboxController extends BaseController { ))) ).show(context); } else { - Get.dialog( - PointerInterceptor(child: (EditTextDialogBuilder() - ..key(const Key('rename_mailbox_dialog')) - ..title(AppLocalizations.of(context).renameFolder) - ..cancelText(AppLocalizations.of(context).cancel) - ..setErrorString((value) { - return verifyMailboxNameAction( - context, - value, - listMailboxName, - MailboxActions.rename - ); - }) - ..setTextController(TextEditingController.fromValue( - TextEditingValue( - text: presentationMailbox.name?.name ?? '', - selection: TextSelection( - baseOffset: 0, - extentOffset: presentationMailbox.name?.name.length ?? 0 - ) - )) - ) - ..onConfirmButtonAction( - AppLocalizations.of(context).rename, - (value) => onRenameMailboxAction(presentationMailbox, MailboxName(value)) - ) - ).build()), - barrierColor: AppColor.colorDefaultCupertinoActionSheet, + showInputDialogAction( + key: const Key('rename_mailbox_dialog'), + context: context, + title: AppLocalizations.of(context).renameFolder, + value: presentationMailbox.name?.name ?? '', + negativeText: AppLocalizations.of(context).cancel, + positiveText: AppLocalizations.of(context).rename, + closeIcon: imagePaths.icComposerClose, + onPositiveButtonAction: (value) { + onRenameMailboxAction(presentationMailbox, MailboxName(value)); + popBack(); + }, + onNegativeButtonAction: popBack, + onInputErrorChanged: (value) { + return verifyMailboxNameAction( + context, + value, + listMailboxName, + MailboxActions.rename, + ); + }, ); } } diff --git a/lib/features/base/mixin/message_dialog_action_mixin.dart b/lib/features/base/mixin/message_dialog_action_mixin.dart index 5509175db..c9f5ec945 100644 --- a/lib/features/base/mixin/message_dialog_action_mixin.dart +++ b/lib/features/base/mixin/message_dialog_action_mixin.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart'; import 'package:core/presentation/views/dialog/confirmation_dialog_builder.dart'; +import 'package:core/presentation/views/dialog/edit_text_dialog_builder.dart'; import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -222,4 +223,35 @@ mixin MessageDialogActionMixin { } } } + + Future showInputDialogAction({ + required BuildContext context, + required String title, + required String value, + required String positiveText, + required String negativeText, + required OnInputDialogPositiveButtonAction onPositiveButtonAction, + Key? key, + String? closeIcon, + OnInputDialogNegativeButtonAction? onNegativeButtonAction, + OnInputDialogInputErrorChangedAction? onInputErrorChanged, + }) async { + return await Get.dialog( + PointerInterceptor( + child: EditTextDialogBuilder( + key: key, + title: AppLocalizations.of(context).renameFolder, + value: value, + positiveText: positiveText, + negativeText: negativeText, + closeIcon: closeIcon, + onInputErrorChanged: onInputErrorChanged, + onPositiveButtonAction: onPositiveButtonAction, + onNegativeButtonAction: onNegativeButtonAction, + onCloseButtonAction: closeIcon != null ? popBack : null, + ), + ), + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + ); + } } \ No newline at end of file