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 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)],
@@ -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,
@@ -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>? 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,
);
}
@@ -23,6 +23,7 @@ class TMailContainerWidget extends StatelessWidget {
final List<BoxShadow>? 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,
);
@@ -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<EditTextDialogBuilder> createState() => _EditTextDialogBuilderState();
}
class _EditTextDialogBuilderState extends State<EditTextDialogBuilder> {
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: <Widget>[
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<Color>(
(Set<WidgetState> states) => bgColor ?? AppColor.colorTextButton),
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),
)),
padding: WidgetStateProperty.resolveWith<EdgeInsets>(
(Set<WidgetState> states) => const EdgeInsets.symmetric(horizontal: 16)),
elevation: WidgetStateProperty.resolveWith<double>((Set<WidgetState> 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,
),
);
}
}
+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/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,
);
},
);
}
}
@@ -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<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,
);
}
}