TF-3760 Change style of input dialog on web

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-06-02 01:40:13 +07:00
committed by Dat H. Pham
parent 25c2eb0ace
commit 537889f934
7 changed files with 297 additions and 175 deletions
@@ -245,9 +245,11 @@ extension AppColor on Color {
static const blue400 = Color(0xFF80BDFF); static const blue400 = Color(0xFF80BDFF);
static const m3Tertiary = Color(0xFF8C9CAF); static const m3Tertiary = Color(0xFF8C9CAF);
static const m3Neutral70 = Color(0xFFAEAAAE); static const m3Neutral70 = Color(0xFFAEAAAE);
static const m3Neutral90 = Color(0xFFE6E1E5);
static const grayBackgroundColor = Color(0xFFF3F6F9); static const grayBackgroundColor = Color(0xFFF3F6F9);
static const m3SurfaceBackground = Color(0xFF1C1B1F); static const m3SurfaceBackground = Color(0xFF1C1B1F);
static const warningColor = Color(0xFFFFC107); static const warningColor = Color(0xFFFFC107);
static const primaryMain = Color(0xFF0A84FF);
static const mapGradientColor = [ static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)], [Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -202,6 +202,15 @@ class ThemeUtils {
color: color, 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({ static TextStyle textStyleInter700({
Color? color, Color? color,
double? fontSize, double? fontSize,
@@ -214,6 +223,15 @@ class ThemeUtils {
color: color, 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 { static TextSelectionThemeData get _textSelectionTheme {
return const TextSelectionThemeData( return const TextSelectionThemeData(
cursorColor: AppColor.primaryColor, cursorColor: AppColor.primaryColor,
@@ -42,6 +42,7 @@ class TMailButtonWidget extends StatelessWidget {
final bool isLoading; final bool isLoading;
final Color? hoverColor; final Color? hoverColor;
final TextOverflow? textOverflow; final TextOverflow? textOverflow;
final Alignment? alignment;
const TMailButtonWidget({ const TMailButtonWidget({
super.key, super.key,
@@ -78,6 +79,7 @@ class TMailButtonWidget extends StatelessWidget {
this.isLoading = false, this.isLoading = false,
this.hoverColor, this.hoverColor,
this.textOverflow, this.textOverflow,
this.alignment,
}); });
factory TMailButtonWidget.fromIcon({ factory TMailButtonWidget.fromIcon({
@@ -104,6 +106,7 @@ class TMailButtonWidget extends StatelessWidget {
List<BoxShadow>? boxShadow, List<BoxShadow>? boxShadow,
EdgeInsetsGeometry? margin, EdgeInsetsGeometry? margin,
Color? hoverColor, Color? hoverColor,
Alignment? alignment,
}) { }) {
return TMailButtonWidget( return TMailButtonWidget(
key: key, key: key,
@@ -130,6 +133,7 @@ class TMailButtonWidget extends StatelessWidget {
boxShadow: boxShadow, boxShadow: boxShadow,
margin: margin, margin: margin,
hoverColor: hoverColor, hoverColor: hoverColor,
alignment: alignment,
); );
} }
@@ -157,6 +161,7 @@ class TMailButtonWidget extends StatelessWidget {
int? maxLines, int? maxLines,
Color? hoverColor, Color? hoverColor,
TextOverflow? textOverflow, TextOverflow? textOverflow,
Alignment? alignment,
}) { }) {
return TMailButtonWidget( return TMailButtonWidget(
key: key, key: key,
@@ -182,6 +187,7 @@ class TMailButtonWidget extends StatelessWidget {
maxLines: maxLines, maxLines: maxLines,
hoverColor: hoverColor, hoverColor: hoverColor,
textOverflow: textOverflow, textOverflow: textOverflow,
alignment: alignment,
); );
} }
@@ -371,6 +377,7 @@ class TMailButtonWidget extends StatelessWidget {
boxShadow: boxShadow, boxShadow: boxShadow,
border: border, border: border,
hoverColor: hoverColor, hoverColor: hoverColor,
alignment: alignment,
child: childWidget, child: childWidget,
); );
} }
@@ -23,6 +23,7 @@ class TMailContainerWidget extends StatelessWidget {
final List<BoxShadow>? boxShadow; final List<BoxShadow>? boxShadow;
final BoxBorder? border; final BoxBorder? border;
final Color? hoverColor; final Color? hoverColor;
final Alignment? alignment;
const TMailContainerWidget({ const TMailContainerWidget({
super.key, super.key,
@@ -43,6 +44,7 @@ class TMailContainerWidget extends StatelessWidget {
this.margin, this.margin,
this.border, this.border,
this.hoverColor, this.hoverColor,
this.alignment,
}); });
@override @override
@@ -61,6 +63,7 @@ class TMailContainerWidget extends StatelessWidget {
maxHeight: maxHeight, maxHeight: maxHeight,
minWidth: minWidth, minWidth: minWidth,
), ),
alignment: alignment,
padding: padding ?? const EdgeInsetsDirectional.all(8), padding: padding ?? const EdgeInsetsDirectional.all(8),
child: child, child: child,
); );
@@ -1,175 +1,243 @@
import 'dart:async'; import 'dart:async';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/core.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:flutter/material.dart';
import 'package:get/get.dart';
typedef OnConfirmButtonEditDialogAction = void Function(String); typedef OnInputDialogPositiveButtonAction = void Function(String);
typedef SetErrorStringEditDialog = String? 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<EditTextDialogBuilder> createState() => _EditTextDialogBuilderState();
}
class _EditTextDialogBuilderState extends State<EditTextDialogBuilder> {
late TextEditingController _textController; late TextEditingController _textController;
late FocusNode _focusNode;
Key? _key;
String _title = '';
String _hintText = '';
String _confirmText = '';
String _cancelText = '';
OnConfirmButtonEditDialogAction? _onConfirmButtonAction;
SetErrorStringEditDialog? _setErrorString;
String? _error; String? _error;
Timer? _debounce; Timer? _debounce;
EditTextDialogBuilder(); @override
void initState() {
void key(Key key) { super.initState();
_key = key; _textController = TextEditingController(text: widget.value)
..selection = TextSelection(
baseOffset: 0,
extentOffset: widget.value.length,
);
_focusNode = FocusNode();
} }
void title(String title) { @override
_title = title; void didUpdateWidget(EditTextDialogBuilder oldWidget) {
} super.didUpdateWidget(oldWidget);
if (oldWidget.value != widget.value) {
void hintText(String hintText) { _textController.text = widget.value;
_hintText = hintText; _textController.selection = TextSelection(
} baseOffset: 0,
extentOffset: widget.value.length,
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);
} }
} }
void _onCancelButtonPress(BuildContext context) { @override
Get.back(); void dispose() {
_textController.dispose();
_focusNode.dispose();
_debounce?.cancel(); _debounce?.cancel();
super.dispose();
} }
Widget build() { @override
Widget build(BuildContext context) {
return Dialog( return Dialog(
key: _key, shape: const RoundedRectangleBorder(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))), borderRadius: BorderRadius.all(Radius.circular(16)),
insetPadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), ),
child: Container( child: GestureDetector(
margin: EdgeInsets.zero, onTap: _focusNode.unfocus,
padding: EdgeInsets.zero, behavior: HitTestBehavior.translucent,
width: 400, child: Container(
decoration: const BoxDecoration( width: 383,
decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))), borderRadius: BorderRadius.all(Radius.circular(16)),
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { ),
return Container( child: Stack(
padding: const EdgeInsets.only(left: 30, right: 30, top: 30, bottom: 24), children: [
child: Wrap( Column(
children: <Widget>[ mainAxisSize: MainAxisSize.min,
Text( children: [
_title, Container(
style: const TextStyle(fontSize: 20, color: AppColor.colorNameEmail, fontWeight: FontWeight.w700), height: 48,
textAlign: TextAlign.center), 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(
padding: const EdgeInsets.only(top: 20), padding: const EdgeInsetsDirectional.only(
child: TextFormFieldBuilder( start: 24,
keyboardType: TextInputType.visiblePassword, end: 28,
onTextChange: (value) => _onTextChanged(value, setState), top: 8,
autoFocus: true, bottom: 8,
controller: _textController, ),
decoration: InputDecoration( child: TextFieldBuilder(
errorText: _error, controller: _textController,
enabledBorder: const UnderlineInputBorder(borderSide: BorderSide(color: AppColor.colorDividerMailbox)), focusNode: _focusNode,
hintText: _hintText), 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(
padding: const EdgeInsets.only(left: 16, right: 16, top: 24), padding: const EdgeInsetsDirectional.only(
child: Row( start: 24,
children: [ end: 28,
Expanded( top: 25,
child: _buildButton( bottom: 25,
name: _cancelText, ),
bgColor: AppColor.colorContentEmail, child: Row(
action: () => _onCancelButtonPress(context)) children: [
), const Spacer(),
const SizedBox(width: 20), TMailButtonWidget.fromText(
Expanded( text: widget.negativeText,
child: _buildButton( textStyle: ThemeUtils.textStyleM3LabelLarge(),
name: _confirmText, maxLines: 1,
bgColor: (_error == null || (_error != null && _error!.isEmpty)) alignment: Alignment.center,
? AppColor.colorTextButton height: 48,
: AppColor.colorDisableMailboxCreateButton, borderRadius: 100,
nameColor: (_error == null || (_error != null && _error!.isEmpty)) padding: const EdgeInsets.symmetric(horizontal: 10),
? Colors.white backgroundColor: Colors.transparent,
: AppColor.colorDisableMailboxCreateButton, onTapActionCallback: _onNegativeAction,
action: () => _onConfirmButtonPress(context)) ),
) 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({ InputBorder _buildBorder(Color color) => OutlineInputBorder(
String? name, Color? nameColor, Color? bgColor, Function? action borderRadius: const BorderRadius.all(Radius.circular(10)),
}) { borderSide: BorderSide(width: 1, color: color),
return SizedBox( );
width: double.infinity,
height: 48, void _onTextChanged(String value) {
child: ElevatedButton( _debounce?.cancel();
onPressed: () => action?.call(), _debounce = Timer(const Duration(milliseconds: 500), () {
style: ButtonStyle( if (!mounted) return;
foregroundColor: WidgetStateProperty.resolveWith<Color>( setState(() {
(Set<WidgetState> states) => bgColor ?? AppColor.colorTextButton), _error = widget.onInputErrorChanged?.call(value);
backgroundColor: WidgetStateProperty.resolveWith<Color>( });
(Set<WidgetState> states) => bgColor ?? AppColor.colorTextButton), });
shape: WidgetStateProperty.all(RoundedRectangleBorder( }
borderRadius: BorderRadius.circular(8),
side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton), void _onPositiveAction() {
)), if (_error?.isNotEmpty != true) {
padding: WidgetStateProperty.resolveWith<EdgeInsets>( widget.onPositiveButtonAction(_textController.text);
(Set<WidgetState> states) => const EdgeInsets.symmetric(horizontal: 16)), }
elevation: WidgetStateProperty.resolveWith<double>((Set<WidgetState> states) => 0)), }
child: Text(name ?? '',
textAlign: TextAlign.center, void _onNegativeAction() {
style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: nameColor ?? Colors.white)), _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,
),
); );
} }
} }
+21 -29
View File
@@ -3,7 +3,6 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.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/bottom_popup/confirmation_dialog_action_sheet_builder.dart';
import 'package:core/presentation/views/dialog/confirmation_dialog_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/presentation/views/modal_sheets/edit_text_modal_sheet_builder.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
@@ -379,34 +378,27 @@ abstract class BaseMailboxController extends BaseController {
))) )))
).show(context); ).show(context);
} else { } else {
Get.dialog( showInputDialogAction(
PointerInterceptor(child: (EditTextDialogBuilder() key: const Key('rename_mailbox_dialog'),
..key(const Key('rename_mailbox_dialog')) context: context,
..title(AppLocalizations.of(context).renameFolder) title: AppLocalizations.of(context).renameFolder,
..cancelText(AppLocalizations.of(context).cancel) value: presentationMailbox.name?.name ?? '',
..setErrorString((value) { negativeText: AppLocalizations.of(context).cancel,
return verifyMailboxNameAction( positiveText: AppLocalizations.of(context).rename,
context, closeIcon: imagePaths.icComposerClose,
value, onPositiveButtonAction: (value) {
listMailboxName, onRenameMailboxAction(presentationMailbox, MailboxName(value));
MailboxActions.rename popBack();
); },
}) onNegativeButtonAction: popBack,
..setTextController(TextEditingController.fromValue( onInputErrorChanged: (value) {
TextEditingValue( return verifyMailboxNameAction(
text: presentationMailbox.name?.name ?? '', context,
selection: TextSelection( value,
baseOffset: 0, listMailboxName,
extentOffset: presentationMailbox.name?.name.length ?? 0 MailboxActions.rename,
) );
)) },
)
..onConfirmButtonAction(
AppLocalizations.of(context).rename,
(value) => onRenameMailboxAction(presentationMailbox, MailboxName(value))
)
).build()),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
); );
} }
} }
@@ -4,6 +4,7 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.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/bottom_popup/confirmation_dialog_action_sheet_builder.dart';
import 'package:core/presentation/views/dialog/confirmation_dialog_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:core/utils/platform_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -222,4 +223,35 @@ mixin MessageDialogActionMixin {
} }
} }
} }
Future<dynamic> 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,
);
}
} }