diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index c38a71154..71d03e4a2 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -117,21 +117,16 @@ extension AppColor on Color { static const colorShadowComposerButton = Color(0x99007AFF); static const colorBackgroundTagFilter = Color(0xFF6D7885); static const colorDefaultRichTextButton = Color(0xFF99A2AD); - static const colorFocusRichTextButton = Color(0x146D7885); static const colorStyleBlockQuote = Color(0xFFEEEEEE); static const colorBorderStyleCode = Color(0xFFCCCCCC); static const colorBackgroundStyleCode = Color(0xFFF5F5F5); static const colorBorderWrapIconStyleCode = Color(0xFFE4E4E4); static const colorBackgroundWrapIconStyleCode = Color(0xFFF2F3F5); - static const colorSelectedRichTextButton = Color(0x99EBEDF0); - static const colorShadowDestinationPicker = Color(0x33000000); static const colorBackgroundSnackBar = Color(0xFF343438); static const colorBackgroundHeaderListRuleFilter = Color(0xFFFAF7F7); static const colorBorderListRuleFilter = Color(0xFFE7E8EC); static const colorBackgroundFieldConditionRulesFilter = Color(0xFFF2F3F5); static const colorDeletePermanentlyButton = Color(0xffE64646); - static const colorVacationSettingExplanation = Color(0xFF686E76); - static const colorBackgroundVacationSettingField = Color(0xFFF2F3F5); static const colorBackgroundNotificationVacationSetting= Color(0xFFFFF5C2); static const colorDivider = Color(0xFFE7E8EC); static const colorCloseButton = Color(0xFF818C99); diff --git a/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart b/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart index 397e5f915..3883d5dbf 100644 --- a/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/color_picker_dialog_builder.dart @@ -59,6 +59,7 @@ class ColorPickerDialogBuilder { actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), actionsAlignment: MainAxisAlignment.center, actionsOverflowButtonSpacing: 8, + backgroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), scrollable: true, elevation: 10, diff --git a/lib/features/base/widget/arrow_down_icon_border_button_widget.dart b/lib/features/base/widget/arrow_down_icon_border_button_widget.dart new file mode 100644 index 000000000..4ca5e47d9 --- /dev/null +++ b/lib/features/base/widget/arrow_down_icon_border_button_widget.dart @@ -0,0 +1,83 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class ArrowDownIconBorderButtonWidget extends StatelessWidget { + final ImagePaths imagePaths; + final String? icon; + final IconData? iconData; + final Color? iconColor; + final Color? backgroundColor; + final String? tooltipMessage; + final double? height; + final EdgeInsetsGeometry? padding; + final VoidCallback? onTap; + + const ArrowDownIconBorderButtonWidget({ + super.key, + required this.imagePaths, + this.icon, + this.iconData, + this.iconColor, + this.backgroundColor, + this.tooltipMessage, + this.height, + this.padding, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + if (icon == null && iconData == null) return const SizedBox.shrink(); + + Widget buttonIcon = Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all( + color: AppColor.m3Neutral90, + ), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + height: height, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (iconData != null) + Icon(iconData, color: iconColor) + else if (icon != null) + SvgPicture.asset( + icon!, + colorFilter: iconColor.asFilter(), + ), + SvgPicture.asset(imagePaths.icStyleArrowDown), + ], + ), + ); + + if (onTap != null) { + buttonIcon = Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTap, + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: buttonIcon, + ), + ); + } + + if (tooltipMessage != null) { + buttonIcon = Tooltip( + message: tooltipMessage, + child: buttonIcon, + ); + } + + if (padding != null) { + buttonIcon = Padding(padding: padding!, child: buttonIcon); + } + + return buttonIcon; + } +} diff --git a/lib/features/base/widget/border_button_field.dart b/lib/features/base/widget/border_button_field.dart deleted file mode 100644 index 433802d88..000000000 --- a/lib/features/base/widget/border_button_field.dart +++ /dev/null @@ -1,104 +0,0 @@ - -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/utils/style_utils.dart'; -import 'package:core/presentation/utils/theme_utils.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; - -typedef OnTapActionCallback = Function(T? value); - -class BorderButtonField extends StatelessWidget { - - final T? value; - final OnTapActionCallback? tapActionCallback; - final Widget? icon; - final TextStyle? textStyle; - final MouseCursor? mouseCursor; - final String? hintText; - final bool isEmpty; - final Color? backgroundColor; - final String? label; - - const BorderButtonField({ - super.key, - this.label, - this.value, - this.tapActionCallback, - this.icon, - this.textStyle, - this.mouseCursor, - this.hintText, - this.isEmpty = false, - this.backgroundColor, - }); - - @override - Widget build(BuildContext context) { - final buttonField = InkWell( - onTap: () => tapActionCallback?.call(value), - mouseCursor: mouseCursor, - child: Container( - height: 44, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: _getBorderColor(), - width: 0.5), - color: backgroundColor ?? Colors.white), - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row(children: [ - Expanded(child: Text( - _getName(context, value), - style: _getTextStyle(value), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - if (icon != null) icon! - ]), - ), - ); - if (label != null) { - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label!, style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail)), - const SizedBox(height: 8), - buttonField - ]); - } else { - return buttonField; - } - } - - TextStyle? _getTextStyle(T? value) { - if (hintText != null && value == null) { - return ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - color: AppColor.colorHintInputCreateMailbox); - } - return textStyle ?? ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black); - } - - String _getName(BuildContext context, T? value) { - if (value is DateTime) { - return value.formatDate(locale: Localizations.localeOf(context).toLanguageTag()); - } - if (value is TimeOfDay) { - return value.formatTime(context); - } - return hintText ?? ''; - } - - Color _getBorderColor() { - if (!isEmpty) { - return AppColor.colorInputBorderCreateMailbox; - } - return AppColor.colorInputBorderErrorVerifyName; - } -} \ No newline at end of file diff --git a/lib/features/base/widget/default_field/default_input_field_widget.dart b/lib/features/base/widget/default_field/default_input_field_widget.dart index f83207039..d2027adeb 100644 --- a/lib/features/base/widget/default_field/default_input_field_widget.dart +++ b/lib/features/base/widget/default_field/default_input_field_widget.dart @@ -9,6 +9,7 @@ typedef OnTextChange = void Function(String text); class DefaultInputFieldWidget extends StatelessWidget { final TextEditingController textEditingController; final String? hintText; + final FocusNode? focusNode; final OnTextChange? onTextChange; final OnTextSubmitted? onTextSubmitted; @@ -16,6 +17,7 @@ class DefaultInputFieldWidget extends StatelessWidget { super.key, required this.textEditingController, this.hintText, + this.focusNode, this.onTextChange, this.onTextSubmitted, }); @@ -29,6 +31,7 @@ class DefaultInputFieldWidget extends StatelessWidget { textStyle: ThemeUtils.textStyleBodyBody3( color: AppColor.m3SurfaceBackground, ), + focusNode: focusNode, onTextSubmitted: onTextSubmitted, onTextChange: onTextChange, decoration: InputDecoration( diff --git a/lib/features/base/widget/label_border_button_field.dart b/lib/features/base/widget/label_border_button_field.dart new file mode 100644 index 000000000..1158dd1c8 --- /dev/null +++ b/lib/features/base/widget/label_border_button_field.dart @@ -0,0 +1,111 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; + +typedef OnSelectValueAction = Function(T? value); + +class LabelBorderButtonField extends StatelessWidget { + final T? value; + final bool isEmpty; + final bool arrangeHorizontally; + final String label; + final String? hintText; + final double? horizontalSpacing; + final double? minWidth; + final OnSelectValueAction onSelectValueAction; + + const LabelBorderButtonField({ + super.key, + required this.value, + required this.label, + required this.onSelectValueAction, + this.isEmpty = false, + this.arrangeHorizontally = true, + this.hintText, + this.minWidth, + this.horizontalSpacing, + }); + + @override + Widget build(BuildContext context) { + Widget buttonField = Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () => onSelectValueAction(value), + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: Container( + height: 40, + constraints: BoxConstraints(minWidth: minWidth ?? 106), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all(color: _getBorderColor(), width: 1), + ), + padding: const EdgeInsets.symmetric(horizontal: 8), + alignment: Alignment.center, + child: Text( + _getName(context, value), + style: _getTextStyle(value), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ); + + if (arrangeHorizontally) { + buttonField = Row( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 83), + child: Text( + '$label:', + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), + ), + SizedBox(width: horizontalSpacing ?? 12), + buttonField, + ], + ); + } else { + buttonField = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '$label:', + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), + const SizedBox(height: 10), + buttonField, + ], + ); + } + + return buttonField; + } + + TextStyle? _getTextStyle(T? value) { + if (hintText != null && value == null) { + return ThemeUtils.textStyleBodyBody3(color: AppColor.steelGray400); + } else { + return ThemeUtils.textStyleBodyBody3(color: Colors.black); + } + } + + String _getName(BuildContext context, T? value) { + if (value is DateTime) { + return value.formatDate( + pattern: 'dd/MM/yyyy', + locale: Localizations.localeOf(context).toLanguageTag(), + ); + } + if (value is TimeOfDay) { + return value.formatTime(context); + } + return hintText ?? ''; + } + + Color _getBorderColor() => + isEmpty ? AppColor.redFF3347 : AppColor.m3Neutral90; +} diff --git a/lib/features/base/widget/label_input_field_builder.dart b/lib/features/base/widget/label_input_field_builder.dart new file mode 100644 index 000000000..569a2ae23 --- /dev/null +++ b/lib/features/base/widget/label_input_field_builder.dart @@ -0,0 +1,68 @@ +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_input_field_widget.dart'; + +class LabelInputFieldBuilder extends StatelessWidget { + final String label; + final TextEditingController textEditingController; + final String? errorText; + final String? hintText; + final FocusNode? focusNode; + final bool arrangeHorizontally; + final OnTextChange? onTextChange; + + const LabelInputFieldBuilder({ + super.key, + required this.label, + required this.textEditingController, + this.arrangeHorizontally = true, + this.hintText, + this.errorText, + this.focusNode, + this.onTextChange, + }); + + @override + Widget build(BuildContext context) { + Widget bodyWidget = ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 565), + child: DefaultInputFieldWidget( + textEditingController: textEditingController, + hintText: hintText, + focusNode: focusNode, + onTextChange: onTextChange, + ), + ); + + if (arrangeHorizontally) { + bodyWidget = Row( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 83), + child: Text( + '$label:', + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), + ), + const SizedBox(width: 12), + Flexible(child: bodyWidget), + ], + ); + } else { + bodyWidget = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '$label:', + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), + const SizedBox(height: 10), + bodyWidget, + ], + ); + } + + return bodyWidget; + } +} diff --git a/lib/features/base/widget/switch_label_button_widget.dart b/lib/features/base/widget/switch_label_button_widget.dart new file mode 100644 index 000000000..a86494d1c --- /dev/null +++ b/lib/features/base/widget/switch_label_button_widget.dart @@ -0,0 +1,51 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class SwitchLabelButtonWidget extends StatelessWidget { + final ImagePaths imagePaths; + final bool isActive; + final String label; + final VoidCallback onSwitchAction; + final EdgeInsetsGeometry? padding; + + const SwitchLabelButtonWidget({ + super.key, + required this.imagePaths, + required this.isActive, + required this.label, + required this.onSwitchAction, + this.padding, + }); + + @override + Widget build(BuildContext context) { + final bodyWidget = Row( + children: [ + InkWell( + onTap: onSwitchAction, + child: SvgPicture.asset( + isActive ? imagePaths.icSwitchOn : imagePaths.icSwitchOff, + fit: BoxFit.fill, + width: 52, + height: 32, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + label, + style: ThemeUtils.textStyleBodyBody2(color: Colors.black), + ), + ) + ], + ); + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } + } +} diff --git a/lib/features/base/widget/text_input_field_builder.dart b/lib/features/base/widget/text_input_field_builder.dart deleted file mode 100644 index e0b6affa5..000000000 --- a/lib/features/base/widget/text_input_field_builder.dart +++ /dev/null @@ -1,69 +0,0 @@ - -import 'package:core/core.dart'; -import 'package:core/utils/direction_utils.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/base/widget/text_input_decoration_builder.dart'; - -typedef OnChangeInputAction = Function(String value); - -class TextInputFieldBuilder extends StatelessWidget { - - final String? label; - final String? error; - final String? hint; - final TextEditingController? editingController; - final FocusNode? focusNode; - final TextInputType? inputType; - final bool isMandatory; - final int? minLines; - final int? maxLines; - final Color? backgroundColor; - final OnChangeInputAction? onChangeInputAction; - - const TextInputFieldBuilder({ - Key? key, - this.label, - this.hint, - this.error, - this.isMandatory = false, - this.editingController, - this.focusNode, - this.inputType, - this.minLines, - this.maxLines, - this.backgroundColor, - this.onChangeInputAction, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (label != null) - ...[ - Text(isMandatory ? '${label!}*' : label!, - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail)), - const SizedBox(height: 8) - ], - TextFieldBuilder( - onTextChange: onChangeInputAction, - textInputAction: TextInputAction.next, - controller: editingController, - focusNode: focusNode, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(color: Colors.black, fontSize: 16), - keyboardType: inputType ?? TextInputType.text, - textDirection: DirectionUtils.getDirectionByLanguage(context), - minLines: minLines, - maxLines: maxLines, - decoration: (TextInputDecorationBuilder() - ..setContentPadding(EdgeInsets.symmetric(vertical: PlatformInfo.isWeb ? 16 : 12, horizontal: 12)) - ..setHintText(hint) - ..setFillColor(backgroundColor) - ..setErrorText(error)) - .build(), - ) - ]); - } -} \ No newline at end of file diff --git a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart index b7646a0a5..205018f63 100644 --- a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart +++ b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart @@ -1,19 +1,9 @@ - -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart'; -import 'package:tmail_ui_user/features/base/widget/popup_menu_overlay_widget.dart'; -import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/button_layout_type.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu_font_status.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/font_name_type.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/order_list_type.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/paragraph_type.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart'; -import 'package:tmail_ui_user/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart'; mixin RichTextButtonMixin { @@ -131,34 +121,6 @@ mixin RichTextButtonMixin { fit: BoxFit.fill); } - Widget buildIcon({ - required String path, - Color? color, - double opacity = 1.0, - }){ - final newColor = color == Colors.white - ? AppColor.colorDefaultRichTextButton - : color; - - return SvgPicture.asset( - path, - colorFilter: newColor?.withValues(alpha: opacity).asFilter(), - fit: BoxFit.fill); - } - - Widget buildIconColorBackgroundTextWithoutTooltip({ - required IconData? iconData, - required Color? colorSelected, - double opacity = 1.0, - }){ - final newColor = colorSelected == Colors.white - ? AppColor.colorDefaultRichTextButton - : colorSelected; - return Icon(iconData, - color: (newColor ?? AppColor.colorDefaultRichTextButton).withValues(alpha: opacity), - size: 20); - } - Widget buildIconColorBackgroundText({ required IconData? iconData, required Color? colorSelected, @@ -175,187 +137,4 @@ mixin RichTextButtonMixin { size: 20), ); } - - Widget buildToolbarRichTextForWeb( - BuildContext context, - RichTextWebController richTextController, - {ButtonLayoutType layoutType = ButtonLayoutType.wrapParent} - ) { - late Widget parentRichTextButton; - - switch(layoutType) { - case ButtonLayoutType.scrollHorizontal: - parentRichTextButton = Container( - alignment: Alignment.center, - color: Colors.transparent, - height: 38, - child: ListView( - padding: EdgeInsets.zero, - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: listButtonActionForRichText(context, richTextController)), - ); - break; - case ButtonLayoutType.scrollVertical: - parentRichTextButton = ListView( - scrollDirection: Axis.horizontal, - children: listButtonActionForRichText(context, richTextController)); - break; - case ButtonLayoutType.wrapParent: - parentRichTextButton = Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - runSpacing: 8, - children: listButtonActionForRichText(context, richTextController) - ); - break; - } - - return Container( - alignment: Alignment.center, - color: Colors.transparent, - height: 50, - child: parentRichTextButton, - ); - } - - List listButtonActionForRichText( - BuildContext context, - RichTextWebController richTextController - ) { - return [ - DropDownMenuHeaderStyleWidget( - icon: buildWrapIconStyleText( - isSelected: richTextController.isMenuHeaderStyleOpen, - icon: SvgPicture.asset( - RichTextStyleType.headerStyle.getIcon(_imagePaths), - colorFilter: AppColor.colorDefaultRichTextButton.asFilter(), - fit: BoxFit.fill), - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), - tooltip: RichTextStyleType.headerStyle.getTooltipButton(context) - ), - items: HeaderStyleType.values, - onMenuStateChange: (isOpen) { - final newStatus = isOpen - ? DropdownMenuFontStatus.open - : DropdownMenuFontStatus.closed; - richTextController.menuHeaderStyleStatus.value = newStatus; - }, - onChanged: (newStyle) => richTextController.applyHeaderStyle(newStyle)), - Container( - width: 130, - padding: const EdgeInsets.only(left: 4.0, right: 4.0), - child: DropDownButtonWidget( - items: FontNameType.values, - itemSelected: richTextController.selectedFontName.value, - onChanged: (newFont) => richTextController.applyNewFontStyle(newFont), - onMenuStateChange: (isOpen) { - final newStatus = isOpen - ? DropdownMenuFontStatus.open - : DropdownMenuFontStatus.closed; - richTextController.menuFontStatus.value = newStatus; - }, - heightItem: 40, - sizeIconChecked: 16, - radiusButton: 8, - dropdownWidth: 200, - colorButton: richTextController.isMenuFontOpen - ? AppColor.colorBackgroundWrapIconStyleCode - : Colors.white, - iconArrowDown: SvgPicture.asset(_imagePaths.icStyleArrowDown), - tooltip: RichTextStyleType.fontName.getTooltipButton(context), - supportSelectionIcon: true)), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: buildWrapIconStyleText( - icon: buildIconWithTooltip( - path: RichTextStyleType.textColor.getIcon(_imagePaths), - color: richTextController.selectedTextColor.value, - tooltip: RichTextStyleType.textColor.getTooltipButton(context), - ), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.textColor)), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 7), - spacing: 3, - icon: buildIconColorBackgroundText( - iconData: RichTextStyleType.textBackgroundColor.getIconData(), - colorSelected: richTextController.selectedTextBackgroundColor.value, - tooltip: RichTextStyleType.textBackgroundColor.getTooltipButton(context), - ), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.textBackgroundColor)), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: buildWrapIconStyleText( - hasDropdown: false, - padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5), - icon: Wrap(children: [ - buildIconStyleText( - path: RichTextStyleType.bold.getIcon(_imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.bold), - tooltip: RichTextStyleType.bold.getTooltipButton(context), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.bold)), - buildIconStyleText( - path: RichTextStyleType.italic.getIcon(_imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.italic), - tooltip: RichTextStyleType.italic.getTooltipButton(context), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.italic)), - buildIconStyleText( - path: RichTextStyleType.underline.getIcon(_imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.underline), - tooltip: RichTextStyleType.underline.getTooltipButton(context), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.underline)), - buildIconStyleText( - path: RichTextStyleType.strikeThrough.getIcon(_imagePaths), - isSelected: richTextController.isTextStyleTypeSelected(RichTextStyleType.strikeThrough), - tooltip: RichTextStyleType.strikeThrough.getTooltipButton(context), - onTap: () => richTextController.applyRichTextStyle(context, RichTextStyleType.strikeThrough)) - ])), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: PopupMenuOverlayWidget( - controller: richTextController.menuParagraphController, - listButtonAction: ParagraphType.values - .map((paragraph) => paragraph.buildButtonWidget( - context, - _imagePaths, - (paragraph) => richTextController.applyParagraphType(paragraph))) - .toList(), - padding: const EdgeInsets.symmetric(horizontal: 5), - iconButton: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), - spacing: 3, - isSelected: richTextController.focusMenuParagraph.value, - icon: buildIconWithTooltip( - path: richTextController.selectedParagraph.value.getIcon(_imagePaths), - color: AppColor.colorDefaultRichTextButton, - tooltip: RichTextStyleType.paragraph.getTooltipButton(context))), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 4.0), - child: PopupMenuOverlayWidget( - controller: richTextController.menuOrderListController, - listButtonAction: OrderListType.values - .map((orderType) => orderType.buildButtonWidget( - context, - _imagePaths, - (orderType) => richTextController.applyOrderListType(orderType))) - .toList(), - padding: const EdgeInsets.symmetric(horizontal: 5), - iconButton: buildWrapIconStyleText( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), - spacing: 3, - isSelected: richTextController.focusMenuOrderList.value, - icon: buildIconWithTooltip( - path: richTextController.selectedOrderList.value.getIcon(_imagePaths), - color: AppColor.colorDefaultRichTextButton, - tooltip: RichTextStyleType.orderList.getTooltipButton(context))), - ), - ) - ]; - } } \ No newline at end of file diff --git a/lib/features/composer/presentation/model/button_layout_type.dart b/lib/features/composer/presentation/model/button_layout_type.dart deleted file mode 100644 index 5c70032c9..000000000 --- a/lib/features/composer/presentation/model/button_layout_type.dart +++ /dev/null @@ -1,6 +0,0 @@ - -enum ButtonLayoutType { - scrollHorizontal, - scrollVertical, - wrapParent -} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/vacation/vacation_controller.dart b/lib/features/manage_account/presentation/vacation/vacation_controller.dart index 5d20f3ee0..62c577412 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_controller.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_controller.dart @@ -13,6 +13,7 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_vac import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings/settings_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/account_menu_item.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/vacation/date_type.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/vacation/vacation_presentation.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/vacation/vacation_responder_status.dart'; @@ -41,6 +42,7 @@ class VacationController extends BaseController { RichTextWebController? richTextControllerForWeb; final ScrollController scrollController = ScrollController(); + final ScrollController richTextButtonScrollController = ScrollController(); VacationController(this._updateVacationInteractor); @@ -355,12 +357,17 @@ class VacationController extends BaseController { } } + void switchProfileSetting() { + _accountDashBoardController.selectAccountMenuItem(AccountMenuItem.profiles); + } + @override void onClose() { subjectTextFocusNode.removeListener(_onSubjectTextListener); subjectTextFocusNode.dispose(); subjectTextController.dispose(); scrollController.dispose(); + richTextButtonScrollController.dispose(); if (PlatformInfo.isWeb) { richTextControllerForWeb?.onClose(); } else { diff --git a/lib/features/manage_account/presentation/vacation/vacation_view.dart b/lib/features/manage_account/presentation/vacation/vacation_view.dart index 3f98227af..d66c71d89 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_view.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_view.dart @@ -2,38 +2,37 @@ import 'package:core/presentation/constants/constants_ui.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/keyboard_utils.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/utils/theme_utils.dart'; -import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:core/utils/html/html_template.dart'; import 'package:core/utils/html/html_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:html_editor_enhanced/html_editor.dart' as html_editor_browser; -import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:rich_text_composer/rich_text_composer.dart'; import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart'; -import 'package:tmail_ui_user/features/base/widget/border_button_field.dart'; -import 'package:tmail_ui_user/features/base/widget/text_input_field_builder.dart'; -import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/button_layout_type.dart'; +import 'package:tmail_ui_user/features/base/widget/label_border_button_field.dart'; +import 'package:tmail_ui_user/features/base/widget/switch_label_button_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/label_input_field_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/base/setting_detail_view_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings_utils.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/account_menu_item.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/vacation/date_type.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/vacation/vacation_responder_status.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/horizontal_toolbar_rich_text_widget.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_list_action_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/widgets/setting_explanation_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/widgets/setting_header_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class VacationView extends GetWidget with RichTextButtonMixin { +class VacationView extends GetWidget { - VacationView({Key? key}) : super(key: key); + const VacationView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -41,212 +40,209 @@ class VacationView extends GetWidget with RichTextButtonMixi physics: const ClampingScrollPhysics(), controller: controller.scrollController, child: Padding( - padding: SettingsUtils.getSettingContentPadding( + padding: _getScrollViewPadding( context, controller.responsiveUtils, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (!controller.responsiveUtils.isWebDesktop(context)) - const SettingExplanationWidget( - menuItem: AccountMenuItem.vacation, - padding: EdgeInsets.only(bottom: 24) - ), - Row(children: [ - Obx(() { - return InkWell( - onTap: () { - final newStatus = controller.isVacationDeactivated + Obx(() { + final isVacationDeactivated = controller.isVacationDeactivated; + + return SwitchLabelButtonWidget( + imagePaths: controller.imagePaths, + label: AppLocalizations.of(context).vacationSettingToggleButtonAutoReply, + isActive: !isVacationDeactivated, + padding: const EdgeInsets.only(top: 10, bottom: 12), + onSwitchAction: () { + final newStatus = isVacationDeactivated ? VacationResponderStatus.activated : VacationResponderStatus.deactivated; - controller.updateVacationPresentation(newStatus: newStatus); - }, - child: SvgPicture.asset( - controller.isVacationDeactivated - ? controller.imagePaths.icSwitchOff - : controller.imagePaths.icSwitchOn, - fit: BoxFit.fill, - width: 24, - height: 24 - ) - ); - }), - const SizedBox(width: 16), - Expanded( - child: Text( - AppLocalizations.of(context).vacationSettingToggleButtonAutoReply, - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 16, - height: 20 / 16, - fontWeight: FontWeight.normal, - color: Colors.black - ) - ), - ) - ]), - const SizedBox(height: 28), + controller.updateVacationPresentation(newStatus: newStatus); + }, + ); + }), Obx(() => AbsorbPointer( absorbing: controller.isVacationDeactivated, child: Opacity( opacity: controller.isVacationDeactivated ? 0.3 : 1.0, - child: controller.responsiveUtils.isPortraitMobile(context) + child: controller.responsiveUtils.isScreenWithShortestSide(context) ? Column(children: [ - BorderButtonField( + LabelBorderButtonField( label: AppLocalizations.of(context).startDate, value: controller.vacationPresentation.value.startDate, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: !controller.isVacationDeactivated && controller.vacationPresentation.value.startDateIsNull, + isEmpty: !controller.isVacationDeactivated && + controller.vacationPresentation.value.startDateIsNull, hintText: AppLocalizations.of(context).startDate, - tapActionCallback: (value) => controller.selectDate(context, DateType.start, value) + onSelectValueAction: (value) => controller.selectDate( + context, + DateType.start, + value, + ), ), const SizedBox(height: 18), - BorderButtonField( + LabelBorderButtonField( label: AppLocalizations.of(context).startTime, value: controller.vacationPresentation.value.startTime, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: !controller.isVacationDeactivated && controller.vacationPresentation.value.starTimeIsNull, - hintText: AppLocalizations.of(context).noStartTime, - tapActionCallback: (value) => controller.selectTime(context, DateType.start, value) + isEmpty: !controller.isVacationDeactivated && + controller.vacationPresentation.value.starTimeIsNull, + hintText: 'dd/mm/yyyy', + onSelectValueAction: (value) => controller.selectTime( + context, + DateType.start, + value, + ), ), ]) - : Row(children: [ - Expanded(child: BorderButtonField( - label: AppLocalizations.of(context).startDate, - value: controller.vacationPresentation.value.startDate, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: !controller.isVacationDeactivated && controller.vacationPresentation.value.startDateIsNull, - hintText: AppLocalizations.of(context).startDate, - tapActionCallback: (value) => controller.selectDate(context, DateType.start, value)) - ), - const SizedBox(width: 24), - Expanded(child: BorderButtonField( - label: AppLocalizations.of(context).startTime, - value: controller.vacationPresentation.value.startTime, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: !controller.isVacationDeactivated && controller.vacationPresentation.value.starTimeIsNull, - hintText: AppLocalizations.of(context).noStartTime, - tapActionCallback: (value) => controller.selectTime(context, DateType.start, value)) - ), - ]), + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + LabelBorderButtonField( + label: AppLocalizations.of(context).startDate, + value: controller.vacationPresentation.value.startDate, + isEmpty: !controller.isVacationDeactivated && + controller.vacationPresentation.value.startDateIsNull, + hintText: AppLocalizations.of(context).startDate, + onSelectValueAction: (value) => controller.selectDate( + context, + DateType.start, + value, + ), + ), + const SizedBox(width: 32), + LabelBorderButtonField( + label: AppLocalizations.of(context).startTime, + value: controller.vacationPresentation.value.startTime, + isEmpty: !controller.isVacationDeactivated && + controller.vacationPresentation.value.starTimeIsNull, + hintText: 'hh:min AM/PM', + horizontalSpacing: 8, + minWidth: 117, + onSelectValueAction: (value) => controller.selectTime( + context, + DateType.start, + value, + ), + ), + ], + ), ), )), - const SizedBox(height: 24), - Obx(() => AbsorbPointer( - absorbing: controller.isVacationDeactivated, - child: Opacity( - opacity: controller.isVacationDeactivated ? 0.3 : 1.0, - child: Row(children: [ - Obx(() => InkWell( - onTap: () { - final value = !controller.vacationPresentation.value.vacationStopEnabled; - controller.updateVacationPresentation(vacationStopEnabled: value); - }, - child: SvgPicture.asset( - controller.vacationPresentation.value.vacationStopEnabled - ? controller.imagePaths.icSwitchOn - : controller.imagePaths.icSwitchOff, - fit: BoxFit.fill, - width: 24, - height: 24 - ) - )), - const SizedBox(width: 16), - Expanded( - child: Text( - AppLocalizations.of(context).vacationStopsAt, - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ) - ), - ) - ]), - ) - )), - const SizedBox(height: 24), + const SizedBox(height: 37), + Obx( + () => AbsorbPointer( + absorbing: controller.isVacationDeactivated, + child: Opacity( + opacity: controller.isVacationDeactivated ? 0.3 : 1.0, + child: Obx(() { + final vacationStopEnabled = controller + .vacationPresentation.value.vacationStopEnabled; + + return SwitchLabelButtonWidget( + imagePaths: controller.imagePaths, + label: AppLocalizations.of(context).vacationStopsAt, + isActive: vacationStopEnabled, + onSwitchAction: () { + controller.updateVacationPresentation( + vacationStopEnabled: !vacationStopEnabled, + ); + }, + ); + }), + ), + ), + ), + const SizedBox(height: 12), Obx(() => AbsorbPointer( absorbing: !controller.canChangeEndDate, child: Opacity( opacity: !controller.canChangeEndDate ? 0.3 : 1.0, - child: controller.responsiveUtils.isPortraitMobile(context) + child: controller.responsiveUtils.isScreenWithShortestSide(context) ? Column(children: [ - BorderButtonField( + LabelBorderButtonField( label: AppLocalizations.of(context).endDate, value: controller.vacationPresentation.value.endDate, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: controller.canChangeEndDate && controller.vacationPresentation.value.endDateIsNull, - hintText: AppLocalizations.of(context).noEndDate, - tapActionCallback: (value) => controller.selectDate(context, DateType.end, value) + isEmpty: controller.canChangeEndDate && + controller.vacationPresentation.value.endDateIsNull, + hintText: 'dd/mm/yyyy', + onSelectValueAction: (value) => controller.selectDate( + context, + DateType.end, + value, + ), ), const SizedBox(height: 18), - BorderButtonField( + LabelBorderButtonField( label: AppLocalizations.of(context).endTime, value: controller.vacationPresentation.value.endTime, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: controller.canChangeEndDate && controller.vacationPresentation.value.endTimeIsNull, - hintText: AppLocalizations.of(context).noEndTime, - tapActionCallback: (value) => controller.selectTime(context, DateType.end, value) + isEmpty: controller.canChangeEndDate && + controller.vacationPresentation.value.endTimeIsNull, + hintText: 'hh:min AM/PM', + onSelectValueAction: (value) => controller.selectTime( + context, + DateType.end, + value, + ), ), ]) - : Row(children: [ - Expanded(child: BorderButtonField( - label: AppLocalizations.of(context).endDate, - value: controller.vacationPresentation.value.endDate, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: controller.canChangeEndDate && controller.vacationPresentation.value.endDateIsNull, - hintText: AppLocalizations.of(context).noEndDate, - tapActionCallback: (value) => controller.selectDate(context, DateType.end, value)) - ), - const SizedBox(width: 24), - Expanded(child: BorderButtonField( - label: AppLocalizations.of(context).endTime, - value: controller.vacationPresentation.value.endTime, - mouseCursor: SystemMouseCursors.text, - backgroundColor: AppColor.colorBackgroundVacationSettingField, - isEmpty: controller.canChangeEndDate && controller.vacationPresentation.value.endTimeIsNull, - hintText: AppLocalizations.of(context).noEndTime, - tapActionCallback: (value) => controller.selectTime(context, DateType.end, value)) - ), - ]), + : Row( + mainAxisSize: MainAxisSize.min, + children: [ + LabelBorderButtonField( + label: AppLocalizations.of(context).endDate, + value: controller.vacationPresentation.value.endDate, + isEmpty: controller.canChangeEndDate && controller.vacationPresentation.value.endDateIsNull, + hintText: 'dd/mm/yyyy', + onSelectValueAction: (value) => controller.selectDate( + context, + DateType.end, + value, + ), + ), + const SizedBox(width: 32), + LabelBorderButtonField( + label: AppLocalizations.of(context).endTime, + value: controller.vacationPresentation.value.endTime, + isEmpty: controller.canChangeEndDate && + controller.vacationPresentation.value.endTimeIsNull, + hintText: 'hh:min AM/PM', + horizontalSpacing: 8, + minWidth: 117, + onSelectValueAction: (value) => controller.selectTime( + context, + DateType.end, + value, + ), + ), + ], + ), ) )), - const SizedBox(height: 24), + const SizedBox(height: 12), Obx(() => AbsorbPointer( absorbing: controller.isVacationDeactivated, - child: controller.responsiveUtils.isPortraitMobile(context) + child: controller.responsiveUtils.isScreenWithShortestSide(context) ? Opacity( opacity: controller.isVacationDeactivated ? 0.3 : 1.0, - child: TextInputFieldBuilder( + child: LabelInputFieldBuilder( label: AppLocalizations.of(context).subject, - hint: AppLocalizations.of(context).hintSubjectInputVacationSetting, - editingController: controller.subjectTextController, + hintText: AppLocalizations.of(context).hintSubjectInputVacationSetting, + textEditingController: controller.subjectTextController, focusNode: controller.subjectTextFocusNode, ), ) - : Row(children: [ - Expanded(child: Opacity( - opacity: controller.isVacationDeactivated ? 0.3 : 1.0, - child: TextInputFieldBuilder( - label: AppLocalizations.of(context).subject, - hint: AppLocalizations.of(context).hintSubjectInputVacationSetting, - editingController: controller.subjectTextController, - focusNode: controller.subjectTextFocusNode, - ), - )), - const SizedBox(width: 24), - const Expanded(child: SizedBox.shrink()) - ]) + : Opacity( + opacity: controller.isVacationDeactivated ? 0.3 : 1.0, + child: LabelInputFieldBuilder( + label: AppLocalizations.of(context).subject, + hintText: AppLocalizations.of(context).hintSubjectInputVacationSetting, + textEditingController: controller.subjectTextController, + focusNode: controller.subjectTextFocusNode, + ), + ), )), - const SizedBox(height: 24), + const SizedBox(height: 12), Obx(() => AbsorbPointer( absorbing: controller.isVacationDeactivated, child: Opacity( @@ -254,9 +250,20 @@ class VacationView extends GetWidget with RichTextButtonMixi child: _buildVacationMessage(context), ), )), - const SizedBox(height: 24), - _buildListButtonAction(context), - const SizedBox(height: 24) + Container( + padding: const EdgeInsets.only(top: 22), + constraints: const BoxConstraints(maxWidth: 660), + child: VacationListActionWidget( + onCancelButtonAction: () { + if (controller.responsiveUtils.isWebDesktop(context)) { + controller.switchProfileSetting(); + } else { + controller.backToUniversalSettings(context); + } + }, + onConfirmButtonAction: () => controller.saveVacation(context), + ), + ), ] ), ), @@ -265,17 +272,46 @@ class VacationView extends GetWidget with RichTextButtonMixi if (PlatformInfo.isWeb) { return SettingDetailViewBuilder( responsiveUtils: controller.responsiveUtils, - onTapGestureDetector: () => controller.clearFocusEditor(context), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (controller.responsiveUtils.isWebDesktop(context)) - ...[ - const SettingHeaderWidget(menuItem: AccountMenuItem.vacation), - const Divider(height: 1, color: AppColor.colorDividerHeaderSetting), - ], - Expanded(child: vacationInputForm), - ], + child: Container( + color: SettingsUtils.getContentBackgroundColor( + context, + controller.responsiveUtils, + ), + decoration: SettingsUtils.getBoxDecorationForContent( + context, + controller.responsiveUtils, + ), + width: double.infinity, + padding: controller.responsiveUtils.isDesktop(context) + ? const EdgeInsetsDirectional.only( + top: 30, + start: 22, + ) + : null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (controller.responsiveUtils.isWebDesktop(context)) + SettingHeaderWidget( + menuItem: AccountMenuItem.vacation, + textStyle: ThemeUtils.textStyleInter600().copyWith( + color: Colors.black.withValues(alpha: 0.9), + ), + padding: const EdgeInsetsDirectional.only(bottom: 16, end: 22), + ) + else + const SettingExplanationWidget( + menuItem: AccountMenuItem.vacation, + padding: EdgeInsetsDirectional.only( + start: 16, + end: 16, + bottom: 16, + ), + isCenter: true, + ), + Expanded(child: vacationInputForm), + ], + ), ), ); } else { @@ -286,9 +322,9 @@ class VacationView extends GetWidget with RichTextButtonMixi child: Scaffold( backgroundColor: SettingsUtils.getBackgroundColor(context, controller.responsiveUtils), body: SafeArea( - left: controller.responsiveUtils.isPortraitMobile(context), + left: controller.responsiveUtils.isScreenWithShortestSide(context), top: false, - right: controller.responsiveUtils.isPortraitMobile(context), + right: controller.responsiveUtils.isScreenWithShortestSide(context), child: KeyboardRichText( richTextController: controller.richTextControllerForMobile!, keyBroadToolbar: RichTextKeyboardToolBar( @@ -353,158 +389,124 @@ class VacationView extends GetWidget with RichTextButtonMixi } } - Widget _buildListButtonAction(BuildContext context) { - if (controller.responsiveUtils.isWebDesktop(context)) { - return Align( - alignment: Alignment.centerRight, - child: buildTextButton( - AppLocalizations.of(context).saveChanges, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith( - color: Colors.white, - fontWeight: FontWeight.normal, - fontSize: 16 + Widget _buildVacationMessage(BuildContext context) { + if (controller.responsiveUtils.isScreenWithShortestSide(context)) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${AppLocalizations.of(context).message}:', + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all(color: AppColor.m3Neutral90), ), - width: 156, - height: 44, - radius: 10, - onTap: () => controller.saveVacation(context)), + padding: const EdgeInsetsDirectional.symmetric( + vertical: 8, + horizontal: 12, + ), + height: PlatformInfo.isMobile ? null : 236, + child: PlatformInfo.isMobile + ? _buildMessageHtmlTextEditor(context) + : Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible(child: _buildMessageHtmlTextEditor(context)), + HorizontalToolbarRichTextWidget( + richTextController: controller.richTextControllerForWeb!, + scrollController: controller.richTextButtonScrollController, + imagePaths: controller.imagePaths, + ), + ], + ), + ), + ], ); } else { - if (controller.responsiveUtils.isPortraitMobile(context)) { - return Row(children: [ - Expanded( - child: buildTextButton( - AppLocalizations.of(context).cancel, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontWeight: FontWeight.w500, - fontSize: 17, - color: AppColor.colorTextButton), - backgroundColor: AppColor.emailAddressChipColor, - width: 156, - height: 44, - radius: 10, - onTap: () => controller.backToUniversalSettings(context)), + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 83), + child: Text( + '${AppLocalizations.of(context).message}:', + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), ), const SizedBox(width: 12), - Expanded( - child: buildTextButton( - AppLocalizations.of(context).saveChanges, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith( - color: Colors.white, - fontWeight: FontWeight.normal, - fontSize: 16 - ), - width: 156, - height: 44, - radius: 10, - onTap: () => controller.saveVacation(context)), - ) - ]); - } else { - return Row(children: [ - const Spacer(), - buildTextButton( - AppLocalizations.of(context).cancel, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontWeight: FontWeight.w500, - fontSize: 17, - color: AppColor.colorTextButton), - backgroundColor: AppColor.emailAddressChipColor, - width: 156, - height: 44, - radius: 10, - onTap: () => controller.backToUniversalSettings(context)), - const SizedBox(width: 12), - buildTextButton( - AppLocalizations.of(context).saveChanges, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith( - color: Colors.white, - fontWeight: FontWeight.normal, - fontSize: 16 + Flexible( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all(color: AppColor.m3Neutral90), ), - width: 156, - height: 44, - radius: 10, - onTap: () => controller.saveVacation(context)) - ]); - } + constraints: const BoxConstraints(maxWidth: 565), + height: PlatformInfo.isMobile ? null : 236, + padding: EdgeInsetsDirectional.only( + bottom: PlatformInfo.isMobile ? 8 : 0, + start: 12, + end: 12, + ), + child: PlatformInfo.isMobile + ? _buildMessageHtmlTextEditor(context) + : Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible(child: _buildMessageHtmlTextEditor(context)), + const SizedBox(height: 8), + HorizontalToolbarRichTextWidget( + richTextController: controller.richTextControllerForWeb!, + scrollController: controller.richTextButtonScrollController, + imagePaths: controller.imagePaths, + ), + ], + ), + ), + ), + ], + ); } } - Widget _buildVacationMessage(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context).message, - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 14, - fontWeight: FontWeight.normal, - color: AppColor.colorContentEmail - ) - ), - const SizedBox(height: 8), - _buildMessageTextEditor(context) - ] - ); - } - - Widget _buildMessageTextEditor(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all(color: AppColor.colorInputBorderCreateMailbox), - color: Colors.white), - padding: const EdgeInsetsDirectional.only(start: 12, end: 12, top: 12), - child: Column(children: [ - _buildMessageHtmlTextEditor(context), - if (PlatformInfo.isWeb) - Center( - child: PointerInterceptor( - child: buildToolbarRichTextForWeb( - context, - controller.richTextControllerForWeb!, - layoutType: ButtonLayoutType.scrollHorizontal - ) - ) - ) - ]), - ); - } - Widget _buildMessageHtmlTextEditor(BuildContext context) { if (PlatformInfo.isWeb) { - return ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 300), - child: html_editor_browser.HtmlEditor( - key: const Key('vacation_message_html_text_editor_web'), - controller: controller.richTextControllerForWeb!.editorController, - htmlEditorOptions: html_editor_browser.HtmlEditorOptions( - hint: '', - darkMode: false, - cacheHTMLAssetOffline: true, - initialText: controller.vacationMessageHtmlText, - spellCheck: true, - customBodyCssStyle: HtmlUtils.customInlineBodyCssStyleHtmlEditor( - direction: AppUtils.getCurrentDirection(context), - ), - customInternalCSS: HtmlTemplate.webCustomInternalStyleCSS(), - ), - htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions( - toolbarType: html_editor_browser.ToolbarType.hide, - defaultToolbarButtons: []), - otherOptions: const html_editor_browser.OtherOptions(height: 150), - callbacks: html_editor_browser.Callbacks( - onChangeSelection: controller.richTextControllerForWeb?.onEditorSettingsChange, - onChangeContent: controller.updateMessageHtmlText, - onFocus: () { - KeyboardUtils.hideKeyboard(context); - Future.delayed(const Duration(milliseconds: 500), () { - controller.richTextControllerForWeb?.editorController.setFocus(); - }); - controller.richTextControllerForWeb?.closeAllMenuPopup(); - } + return html_editor_browser.HtmlEditor( + key: const Key('vacation_message_html_text_editor_web'), + controller: controller.richTextControllerForWeb!.editorController, + htmlEditorOptions: html_editor_browser.HtmlEditorOptions( + hint: '', + darkMode: false, + cacheHTMLAssetOffline: true, + initialText: controller.vacationMessageHtmlText, + spellCheck: true, + customBodyCssStyle: HtmlUtils.customInlineBodyCssStyleHtmlEditor( + direction: AppUtils.getCurrentDirection(context), + horizontalPadding: 0, ), + customInternalCSS: HtmlTemplate.webCustomInternalStyleCSS(), + ), + htmlToolbarOptions: const html_editor_browser.HtmlToolbarOptions( + toolbarType: html_editor_browser.ToolbarType.hide, + defaultToolbarButtons: [], + ), + otherOptions: const html_editor_browser.OtherOptions(height: 300), + callbacks: html_editor_browser.Callbacks( + onChangeSelection: + controller.richTextControllerForWeb?.onEditorSettingsChange, + onChangeContent: controller.updateMessageHtmlText, + onFocus: () { + KeyboardUtils.hideKeyboard(context); + Future.delayed(const Duration(milliseconds: 500), () { + controller.richTextControllerForWeb?.editorController.setFocus(); + }); + controller.richTextControllerForWeb?.closeAllMenuPopup(); + }, ), ); } else { @@ -519,4 +521,17 @@ class VacationView extends GetWidget with RichTextButtonMixi ); } } + + EdgeInsetsGeometry _getScrollViewPadding( + BuildContext context, + ResponsiveUtils responsiveUtils + ) { + if (responsiveUtils.isWebDesktop(context)) { + return const EdgeInsetsDirectional.only(end: 22, bottom: 30); + } else if (responsiveUtils.isMobile(context)) { + return const EdgeInsetsDirectional.symmetric(horizontal: 16); + } else { + return const EdgeInsetsDirectional.symmetric(horizontal: 32); + } + } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/vacation/widgets/horizontal_toolbar_rich_text_widget.dart b/lib/features/manage_account/presentation/vacation/widgets/horizontal_toolbar_rich_text_widget.dart new file mode 100644 index 000000000..22ecb12b7 --- /dev/null +++ b/lib/features/manage_account/presentation/vacation/widgets/horizontal_toolbar_rich_text_widget.dart @@ -0,0 +1,269 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/widget/arrow_down_icon_border_button_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/popup_menu_overlay_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart'; +import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu_font_status.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/font_name_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/order_list_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/paragraph_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/drop_down_menu_header_style_widget.dart'; + +class HorizontalToolbarRichTextWidget extends StatelessWidget { + final RichTextWebController richTextController; + final ScrollController scrollController; + final ImagePaths imagePaths; + + const HorizontalToolbarRichTextWidget({ + super.key, + required this.richTextController, + required this.scrollController, + required this.imagePaths, + }); + + @override + Widget build(BuildContext context) { + return Container( + alignment: AlignmentDirectional.bottomCenter, + height: 50, + child: ScrollbarListView( + scrollBehavior: ScrollConfiguration.of(context).copyWith( + physics: const BouncingScrollPhysics(), + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + PointerDeviceKind.trackpad, + }, + scrollbars: false, + ), + scrollController: scrollController, + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + controller: scrollController, + padding: const EdgeInsets.only(bottom: 8), + children: [ + DropDownMenuHeaderStyleWidget( + icon: Obx( + () { + return ArrowDownIconBorderButtonWidget( + imagePaths: imagePaths, + icon: RichTextStyleType.headerStyle.getIcon(imagePaths), + iconColor: AppColor.colorDefaultRichTextButton, + backgroundColor: richTextController.isMenuHeaderStyleOpen + ? AppColor.colorBackgroundWrapIconStyleCode + : null, + tooltipMessage: + RichTextStyleType.headerStyle.getTooltipButton(context), + ); + }, + ), + items: HeaderStyleType.values, + onMenuStateChange: (isOpen) { + final newStatus = isOpen + ? DropdownMenuFontStatus.open + : DropdownMenuFontStatus.closed; + richTextController.menuHeaderStyleStatus.value = newStatus; + }, + onChanged: richTextController.applyHeaderStyle, + ), + Container( + width: 130, + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Obx( + () { + return DropDownButtonWidget( + items: FontNameType.values, + itemSelected: richTextController.selectedFontName.value, + onChanged: richTextController.applyNewFontStyle, + onMenuStateChange: (isOpen) { + final newStatus = isOpen + ? DropdownMenuFontStatus.open + : DropdownMenuFontStatus.closed; + richTextController.menuFontStatus.value = newStatus; + }, + heightItem: 40, + sizeIconChecked: 16, + radiusButton: 8, + dropdownWidth: 200, + colorButton: richTextController.isMenuFontOpen + ? AppColor.colorBackgroundWrapIconStyleCode + : Colors.white, + iconArrowDown: SvgPicture.asset(imagePaths.icStyleArrowDown), + tooltip: RichTextStyleType.fontName.getTooltipButton(context), + supportSelectionIcon: true, + ); + }, + ), + ), + Center( + child: Obx( + () { + final textColor = richTextController + .selectedTextColor + .value; + + return ArrowDownIconBorderButtonWidget( + imagePaths: imagePaths, + icon: RichTextStyleType.textColor.getIcon(imagePaths), + iconColor: textColor.toInt() == Colors.white.toInt() || + textColor.toInt() == 0 + ? AppColor.colorDefaultRichTextButton + : textColor, + tooltipMessage: RichTextStyleType.textColor.getTooltipButton( + context, + ), + height: 40, + padding: const EdgeInsetsDirectional.only(end: 4.0), + onTap: () => richTextController.applyRichTextStyle( + context, + RichTextStyleType.textColor, + ), + ); + }, + ), + ), + Center( + child: Obx( + () { + final backgroundColor = richTextController + .selectedTextBackgroundColor + .value; + + return ArrowDownIconBorderButtonWidget( + imagePaths: imagePaths, + iconData: RichTextStyleType.textBackgroundColor.getIconData(), + iconColor: backgroundColor.toInt() == Colors.white.toInt() + || backgroundColor.toInt() == 0 + ? AppColor.colorDefaultRichTextButton + : backgroundColor, + tooltipMessage: RichTextStyleType.textBackgroundColor.getTooltipButton( + context, + ), + height: 40, + padding: const EdgeInsetsDirectional.only(end: 4.0), + onTap: () => richTextController.applyRichTextStyle( + context, + RichTextStyleType.textBackgroundColor, + ), + ); + }, + ), + ), + Center( + child: Container( + margin: const EdgeInsetsDirectional.only(end: 4.0), + decoration: BoxDecoration( + border: Border.all( + color: AppColor.m3Neutral90, + ), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + height: 40, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + RichTextStyleType.bold, + RichTextStyleType.italic, + RichTextStyleType.underline, + RichTextStyleType.strikeThrough, + ].map((style) { + return Obx( + () { + final isSelected = richTextController + .isTextStyleTypeSelected(style); + + return TMailButtonWidget.fromIcon( + icon: style.getIcon(imagePaths), + iconColor: isSelected + ? Colors.black + : AppColor.gray99A2AD, + backgroundColor: Colors.transparent, + padding: const EdgeInsets.all(5), + tooltipMessage: style.getTooltipButton(context), + onTapActionCallback: () => + richTextController.applyRichTextStyle(context, style), + ); + }, + ); + }).toList(), + ), + ), + ), + Center( + child: Padding( + padding: const EdgeInsetsDirectional.only(end: 4.0), + child: PopupMenuOverlayWidget( + controller: richTextController.menuParagraphController, + listButtonAction: ParagraphType.values.map((paragraph) { + return paragraph.buildButtonWidget( + context, + imagePaths, + richTextController.applyParagraphType, + ); + }).toList(), + padding: const EdgeInsets.symmetric(horizontal: 5), + iconButton: Obx( + () { + return ArrowDownIconBorderButtonWidget( + imagePaths: imagePaths, + icon: richTextController.selectedParagraph.value + .getIcon(imagePaths), + backgroundColor: richTextController.focusMenuParagraph.isTrue + ? AppColor.colorBackgroundWrapIconStyleCode + : null, + iconColor: AppColor.colorDefaultRichTextButton, + tooltipMessage: + RichTextStyleType.paragraph.getTooltipButton(context), + ); + }, + ), + ), + ), + ), + Center( + child: Padding( + padding: const EdgeInsetsDirectional.only(end: 4.0), + child: PopupMenuOverlayWidget( + controller: richTextController.menuOrderListController, + listButtonAction: OrderListType.values.map((orderType) { + return orderType.buildButtonWidget( + context, + imagePaths, + richTextController.applyOrderListType, + ); + }).toList(), + padding: const EdgeInsets.symmetric(horizontal: 5), + iconButton: Obx( + () { + return ArrowDownIconBorderButtonWidget( + imagePaths: imagePaths, + icon: richTextController.selectedOrderList.value + .getIcon(imagePaths), + backgroundColor: richTextController.focusMenuOrderList.isTrue + ? AppColor.colorBackgroundWrapIconStyleCode + : null, + iconColor: AppColor.colorDefaultRichTextButton, + tooltipMessage: + RichTextStyleType.orderList.getTooltipButton(context), + ); + }, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/manage_account/presentation/vacation/widgets/vacation_list_action_widget.dart b/lib/features/manage_account/presentation/vacation/widgets/vacation_list_action_widget.dart new file mode 100644 index 000000000..915354c89 --- /dev/null +++ b/lib/features/manage_account/presentation/vacation/widgets/vacation_list_action_widget.dart @@ -0,0 +1,48 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/dialog/confirm_dialog_button.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class VacationListActionWidget extends StatelessWidget { + final VoidCallback onConfirmButtonAction; + final VoidCallback onCancelButtonAction; + + const VacationListActionWidget({ + super.key, + required this.onConfirmButtonAction, + required this.onCancelButtonAction, + }); + + @override + Widget build(BuildContext context) { + final appLocalizations = AppLocalizations.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: Container( + constraints: const BoxConstraints(minWidth: 67), + height: 48, + child: ConfirmDialogButton( + label: appLocalizations.cancel, + onTapAction: onCancelButtonAction, + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: Container( + constraints: const BoxConstraints(minWidth: 139), + height: 48, + child: ConfirmDialogButton( + label: appLocalizations.saveChanges, + backgroundColor: AppColor.primaryMain, + textColor: Colors.white, + onTapAction: onConfirmButtonAction, + ), + ), + ), + ], + ); + } +} diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c85826561..938fffd0b 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-07-25T12:21:42.514040", + "@@last_modified": "2025-08-08T16:24:37.279860", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -2034,24 +2034,6 @@ "placeholders_order": [], "placeholders": {} }, - "noStartTime": "No start time", - "@noStartTime": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "noEndTime": "No end time", - "@noEndTime": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "noEndDate": "No end date", - "@noEndDate": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "errorMessageWhenStartDateVacationIsEmpty": "Please enter a valid start date", "@errorMessageWhenStartDateVacationIsEmpty": { "type": "text", diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 28c5b89bf..bf22e666c 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -2076,27 +2076,6 @@ class AppLocalizations { ); } - String get noStartTime { - return Intl.message( - 'No start time', - name: 'noStartTime', - ); - } - - String get noEndTime { - return Intl.message( - 'No end time', - name: 'noEndTime', - ); - } - - String get noEndDate { - return Intl.message( - 'No end date', - name: 'noEndDate', - ); - } - String get errorMessageWhenStartDateVacationIsEmpty { return Intl.message( 'Please enter a valid start date',