From 9240768deb638934afb652d012e6fb73c6f333de Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 29 Sep 2022 18:58:41 +0700 Subject: [PATCH] TF-858 Add `HtmlEditor` for message of vacation on web --- .../mixin/rich_text_button_mixin.dart | 240 +++++++++++++++++- .../model/button_layout_type.dart | 6 + .../vacation_response_extension.dart | 3 +- .../model/vacation/vacation_message_type.dart | 20 ++ .../model/vacation/vacation_presentation.dart | 18 +- .../vacation/utils/vacation_utils.dart | 4 + .../vacation/vacation_controller.dart | 67 ++++- .../vacation_controller_bindings.dart | 7 + .../presentation/vacation/vacation_view.dart | 136 +++++++++- 9 files changed, 456 insertions(+), 45 deletions(-) create mode 100644 lib/features/composer/presentation/model/button_layout_type.dart create mode 100644 lib/features/manage_account/presentation/model/vacation/vacation_message_type.dart create mode 100644 lib/features/manage_account/presentation/vacation/utils/vacation_utils.dart 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 57c4a4a84..97531131c 100644 --- a/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart +++ b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart @@ -3,6 +3,17 @@ import 'package:core/core.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 { @@ -17,9 +28,33 @@ mixin RichTextButtonMixin { double? spacing, String tooltip = '', }){ - final buttonIcon = Tooltip( - message: tooltip, - child: Container( + late Widget buttonIcon; + if (tooltip.isNotEmpty) { + buttonIcon = Tooltip( + message: tooltip, + child: Container( + padding: padding ?? const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + decoration: BoxDecoration( + color: isSelected == true + ? AppColor.colorBackgroundWrapIconStyleCode + : Colors.white, + border: Border.all( + color: AppColor.colorBorderWrapIconStyleCode, + width: 0.5), + borderRadius: BorderRadius.circular(8)), + child: hasDropdown + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + icon, + if (spacing != null) SizedBox(width: spacing), + SvgPicture.asset(_imagePaths.icStyleArrowDown) + ]) + : icon, + ), + ); + } else { + buttonIcon = Container( padding: padding ?? const EdgeInsets.symmetric(horizontal: 5, vertical: 5), decoration: BoxDecoration( color: isSelected == true @@ -38,8 +73,8 @@ mixin RichTextButtonMixin { SvgPicture.asset(_imagePaths.icStyleArrowDown) ]) : icon, - ), - ); + ); + } if (onTap != null) { return InkWell( @@ -83,12 +118,15 @@ mixin RichTextButtonMixin { ? AppColor.colorDefaultRichTextButton : color; - return Tooltip( - child: SvgPicture.asset(path, - color: newColor?.withOpacity(opacity), - fit: BoxFit.fill), - message: tooltip, - ); + return tooltip?.isNotEmpty == true + ? Tooltip( + child: SvgPicture.asset(path, + color: newColor?.withOpacity(opacity), + fit: BoxFit.fill), + message: tooltip) + : SvgPicture.asset(path, + color: newColor?.withOpacity(opacity), + fit: BoxFit.fill); } Widget buildIcon({ @@ -134,4 +172,184 @@ mixin RichTextButtonMixin { message: tooltip, ); } + + 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), + color: AppColor.colorDefaultRichTextButton, + 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(), + 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(), + 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 new file mode 100644 index 000000000..5c70032c9 --- /dev/null +++ b/lib/features/composer/presentation/model/button_layout_type.dart @@ -0,0 +1,6 @@ + +enum ButtonLayoutType { + scrollHorizontal, + scrollVertical, + wrapParent +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/extensions/vacation_response_extension.dart b/lib/features/manage_account/presentation/extensions/vacation_response_extension.dart index 130a1b74b..9a1f0ca9f 100644 --- a/lib/features/manage_account/presentation/extensions/vacation_response_extension.dart +++ b/lib/features/manage_account/presentation/extensions/vacation_response_extension.dart @@ -23,7 +23,8 @@ extension VacationResponseExtension on VacationResponse { endTime: toDate?.value != null ? TimeOfDay.fromDateTime(toDate!.value.toUtc()) : null, - messageBody: textBody ?? htmlBody, + messagePlainText: textBody, + messageHtmlText: htmlBody, subject: subject, vacationStopEnabled: toDate != null ); diff --git a/lib/features/manage_account/presentation/model/vacation/vacation_message_type.dart b/lib/features/manage_account/presentation/model/vacation/vacation_message_type.dart new file mode 100644 index 000000000..412a38262 --- /dev/null +++ b/lib/features/manage_account/presentation/model/vacation/vacation_message_type.dart @@ -0,0 +1,20 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +enum VacationMessageType { + plainText, + htmlTemplate +} + +extension VacationMessageTypeExtension on VacationMessageType { + + String getTitle(BuildContext context) { + switch(this) { + case VacationMessageType.plainText: + return AppLocalizations.of(context).plain_text; + case VacationMessageType.htmlTemplate: + return AppLocalizations.of(context).html_template; + } + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/model/vacation/vacation_presentation.dart b/lib/features/manage_account/presentation/model/vacation/vacation_presentation.dart index d392267b6..f16c24a94 100644 --- a/lib/features/manage_account/presentation/model/vacation/vacation_presentation.dart +++ b/lib/features/manage_account/presentation/model/vacation/vacation_presentation.dart @@ -12,7 +12,8 @@ class VacationPresentation with EquatableMixin { final TimeOfDay? startTime; final DateTime? endDate; final TimeOfDay? endTime; - final String? messageBody; + final String? messagePlainText; + final String? messageHtmlText; final String? subject; final bool vacationStopEnabled; @@ -22,7 +23,8 @@ class VacationPresentation with EquatableMixin { this.startTime, this.endDate, this.endTime, - this.messageBody, + this.messagePlainText, + this.messageHtmlText, this.subject, this.vacationStopEnabled = false, }); @@ -37,7 +39,8 @@ class VacationPresentation with EquatableMixin { TimeOfDay? startTime, DateTime? endDate, TimeOfDay? endTime, - String? messageBody, + String? messagePlainText, + String? messageHtmlText, String? subject, bool? vacationStopEnabled, }) { @@ -47,7 +50,8 @@ class VacationPresentation with EquatableMixin { startTime: startTime ?? this.startTime, endDate: endDate ?? this.endDate, endTime: endTime ?? this.endTime, - messageBody: messageBody ?? this.messageBody, + messagePlainText: messagePlainText ?? this.messagePlainText, + messageHtmlText: messageHtmlText ?? this.messageHtmlText, subject: subject ?? this.subject, vacationStopEnabled: vacationStopEnabled ?? this.vacationStopEnabled ); @@ -75,7 +79,8 @@ class VacationPresentation with EquatableMixin { endDate, endTime, vacationStopEnabled, - messageBody, + messagePlainText, + messageHtmlText, subject, ]; } @@ -86,7 +91,8 @@ extension VacationPresentationExtension on VacationPresentation { isEnabled: isEnabled, fromDate: fromDate != null ? UTCDate(fromDate!) : null, toDate: toDate != null ? UTCDate(toDate!) : null, - textBody: messageBody, + textBody: messagePlainText, + htmlBody: messageHtmlText, subject: subject, ); } diff --git a/lib/features/manage_account/presentation/vacation/utils/vacation_utils.dart b/lib/features/manage_account/presentation/vacation/utils/vacation_utils.dart new file mode 100644 index 000000000..ef23072a7 --- /dev/null +++ b/lib/features/manage_account/presentation/vacation/utils/vacation_utils.dart @@ -0,0 +1,4 @@ + +class VacationUtils { + static const String vacationTagName = 'Vacation'; +} \ 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 331cae26e..492a579f6 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_controller.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_controller.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/vacation/vacation_response.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/empty_name_validator.dart'; import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; @@ -15,8 +16,11 @@ import 'package:tmail_ui_user/features/manage_account/presentation/extensions/va 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/vacation/date_type.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/vacation/vacation_message_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'; +import 'package:tmail_ui_user/features/manage_account/presentation/vacation/utils/vacation_utils.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_controller_bindings.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; @@ -26,6 +30,7 @@ class VacationController extends BaseController { final _appToast = Get.find(); final _imagePaths = Get.find(); final _settingController = Get.find(); + final _richTextControllerForWeb = Get.find(tag: VacationUtils.vacationTagName); final GetAllVacationInteractor _getAllVacationInteractor; final UpdateVacationInteractor _updateVacationInteractor; @@ -33,11 +38,14 @@ class VacationController extends BaseController { final vacationPresentation = VacationPresentation.initialize().obs; final errorMessageBody = Rxn(); + final vacationMessageType = Rx(VacationMessageType.plainText); final messageTextController = TextEditingController(); final subjectTextController = TextEditingController(); VacationResponse? currentVacation; + String? _vacationMessageHtmlText; + late Worker vacationWorker; VacationController( @@ -46,6 +54,10 @@ class VacationController extends BaseController { this._verifyNameInteractor ); + String? get vacationMessageHtmlText => _vacationMessageHtmlText; + + RichTextWebController get richTextControllerForWeb => _richTextControllerForWeb; + @override void onInit() { _initWorker(); @@ -80,9 +92,7 @@ class VacationController extends BaseController { if (vacation is VacationResponse) { currentVacation = vacation; final newVacationPresentation = currentVacation?.toVacationPresentation(); - vacationPresentation.value = newVacationPresentation ?? VacationPresentation.initialize(); - messageTextController.text = newVacationPresentation?.messageBody ?? ''; - subjectTextController.text = newVacationPresentation?.subject ?? ''; + _initializeValueForVacation(newVacationPresentation ?? VacationPresentation.initialize()); } }); } @@ -101,13 +111,19 @@ class VacationController extends BaseController { if (currentVacation != null) { final newVacationPresentation = currentVacation!.toVacationPresentation(); - vacationPresentation.value = newVacationPresentation; - messageTextController.text = newVacationPresentation.messageBody ?? ''; - subjectTextController.text = newVacationPresentation.subject ?? ''; + _initializeValueForVacation(newVacationPresentation); } } } + void _initializeValueForVacation(VacationPresentation newVacation) { + vacationPresentation.value = newVacation; + messageTextController.text = newVacation.messagePlainText ?? ''; + subjectTextController.text = newVacation.subject ?? ''; + updateMessageHtmlText(newVacation.messageHtmlText ?? ''); + _richTextControllerForWeb.editorController.setText(newVacation.messageHtmlText ?? ''); + } + bool get isVacationDeactivated => !vacationPresentation.value.isEnabled; bool get isVacationStopEnabled => vacationPresentation.value.vacationStopEnabled; @@ -121,7 +137,8 @@ class VacationController extends BaseController { DateTime? endDate, TimeOfDay? endTime, bool? vacationStopEnabled, - String? messageBody + String? messagePlainText, + String? messageHtmlText, }) { final currentVacation = vacationPresentation.value; final newVacation = currentVacation.copyWidth( @@ -131,7 +148,9 @@ class VacationController extends BaseController { endDate: endDate, endTime: endTime, vacationStopEnabled: vacationStopEnabled, - messageBody: messageBody); + messagePlainText: messagePlainText, + messageHtmlText: messageHtmlText + ); log('VacationController::updateVacationPresentation():newVacation: $newVacation'); vacationPresentation.value = newVacation; } @@ -241,8 +260,9 @@ class VacationController extends BaseController { return; } - final messageBody = messageTextController.text; - if (messageBody.isEmpty) { + final messagePlainText = messageTextController.text; + final messageHtmlText = _vacationMessageHtmlText ?? ''; + if (messagePlainText.isEmpty && messageHtmlText.isEmpty) { _appToast.showToastWithIcon( context, bgColor: AppColor.toastErrorBackgroundColor, @@ -254,7 +274,8 @@ class VacationController extends BaseController { final subjectVacation = subjectTextController.text; final newVacationPresentation = vacationPresentation.value.copyWidth( - messageBody: messageBody, + messagePlainText: messagePlainText, + messageHtmlText: messageHtmlText, subject: subjectVacation); log('VacationController::saveVacation(): newVacationPresentation: $newVacationPresentation'); final newVacationResponse = newVacationPresentation.toVacationResponse(); @@ -289,15 +310,32 @@ class VacationController extends BaseController { if (currentVacation != null) { final newVacationPresentation = currentVacation!.toVacationPresentation(); - vacationPresentation.value = newVacationPresentation; - messageTextController.text = newVacationPresentation.messageBody ?? ''; - subjectTextController.text = newVacationPresentation.subject ?? ''; + _initializeValueForVacation(newVacationPresentation); } _accountDashBoardController.updateVacationResponse(currentVacation); } } + void updateMessageHtmlText(String? text) => _vacationMessageHtmlText = text; + + Future _getMessageHtmlText() { + return _richTextControllerForWeb.editorController.getText(); + } + + void selectVacationMessageType(BuildContext context, VacationMessageType newMessageType) async { + if (newMessageType == VacationMessageType.plainText && !BuildUtils.isWeb) { + final messageHtml = await _getMessageHtmlText(); + updateMessageHtmlText(messageHtml); + } + clearFocusEditor(context); + vacationMessageType.value = newMessageType; + } + + void clearFocusEditor(BuildContext context) { + FocusScope.of(context).unfocus(); + } + void backToUniversalSettings() { _settingController.backToUniversalSettings(); } @@ -307,6 +345,7 @@ class VacationController extends BaseController { messageTextController.dispose(); subjectTextController.dispose(); vacationWorker.dispose(); + VacationControllerBindings().dispose(); super.onClose(); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/vacation/vacation_controller_bindings.dart b/lib/features/manage_account/presentation/vacation/vacation_controller_bindings.dart index 50c6e3722..1288fd15a 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_controller_bindings.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_controller_bindings.dart @@ -1,14 +1,17 @@ import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; +import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_vacation_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_vacation_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/vacation/utils/vacation_utils.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/vacation_controller.dart'; class VacationControllerBindings extends BaseBindings { @override void bindingsController() { + Get.lazyPut(() => RichTextWebController(), tag: VacationUtils.vacationTagName); Get.lazyPut(() => VacationController( Get.find(), Get.find(), @@ -35,4 +38,8 @@ class VacationControllerBindings extends BaseBindings { @override void bindingsRepositoryImpl() { } + + void dispose() { + Get.delete(tag: VacationUtils.vacationTagName); + } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/vacation/vacation_view.dart b/lib/features/manage_account/presentation/vacation/vacation_view.dart index caaab0a33..2c95df430 100644 --- a/lib/features/manage_account/presentation/vacation/vacation_view.dart +++ b/lib/features/manage_account/presentation/vacation/vacation_view.dart @@ -3,15 +3,21 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/widget/border_button_field.dart'; +import 'package:tmail_ui_user/features/base/widget/text_input_decoration_builder.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/manage_account/presentation/menu/settings_utils.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_message_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/main/localizations/app_localizations.dart'; +import 'package:html_editor_enhanced/html_editor.dart' as html_editor_browser; -class VacationView extends GetWidget { +class VacationView extends GetWidget with RichTextButtonMixin { final _responsiveUtils = Get.find(); final _imagePaths = Get.find(); @@ -257,18 +263,7 @@ class VacationView extends GetWidget { absorbing: controller.isVacationDeactivated, child: Opacity( opacity: controller.isVacationDeactivated ? 0.3 : 1.0, - child: TextInputFieldBuilder( - label: AppLocalizations.of(context).message, - hint: AppLocalizations.of(context).hintMessageBodyVacation, - minLines: 10, - maxLines: null, - inputType: TextInputType.multiline, - backgroundColor: Colors.white, - error: controller.isVacationDeactivated - ? null - : controller.errorMessageBody.value, - onChangeInputAction: (value) => controller.updateMessageBody(context, value), - editingController: controller.messageTextController), + child: _buildVacationMessage(context), ), )), const SizedBox(height: 24), @@ -360,4 +355,119 @@ class VacationView extends GetWidget { } } } + + Widget _buildVacationMessage(BuildContext context) { + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(children: [ + Expanded(child: Text(AppLocalizations.of(context).message, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: AppColor.colorContentEmail))), + _buildVacationMessageTypeButton(context, VacationMessageType.plainText), + _buildVacationMessageTypeButton(context, VacationMessageType.htmlTemplate), + ]), + const SizedBox(height: 8), + _buildMessageTextEditor(context) + ]); + } + + Widget _buildMessageTextEditor(BuildContext context) { + return Obx(() { + if (controller.vacationMessageType.value == VacationMessageType.plainText) { + return _buildMessagePlainTextEditor(context); + } else { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColor.colorInputBorderCreateMailbox), + color: Colors.white), + padding: const EdgeInsets.only(left: 12, right: 12, top: 12), + child: Column(children: [ + _buildMessageHtmlTextEditor(context), + Center(child: Obx(() { + return PointerInterceptor( + child: buildToolbarRichTextForWeb( + context, + controller.richTextControllerForWeb, + layoutType: ButtonLayoutType.scrollHorizontal), + ); + })) + ]), + ); + } + }); + } + + Widget _buildMessagePlainTextEditor(BuildContext context) { + return (TextFieldBuilder() + ..onChange((value) => controller.updateMessageBody(context, value)) + ..textInputAction(TextInputAction.next) + ..addController(controller.messageTextController) + ..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) + ..keyboardType(TextInputType.text) + ..minLines(10) + ..maxLines(null) + ..textDecoration((TextInputDecorationBuilder() + ..setContentPadding(const EdgeInsets.all(16)) + ..setHintText(AppLocalizations.of(context).hintMessageBodyVacation) + ..setFillColor(Colors.white) + ..setErrorText(controller.isVacationDeactivated + ? null + : controller.errorMessageBody.value)) + .build())) + .build(); + } + + Widget _buildMessageHtmlTextEditor(BuildContext context) { + if (BuildUtils.isWeb) { + 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, + customBodyCssStyle: bodyCssStyleForEditor), + blockQuotedContent: controller.vacationMessageHtmlText ?? '', + 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( + onInit: () { + controller.richTextControllerForWeb.setFullScreenEditor(); + }, onChangeSelection: (settings) { + controller.richTextControllerForWeb.onEditorSettingsChange(settings); + }, onChangeContent: (String? changed) { + controller.updateMessageHtmlText(changed); + }, onFocus: () { + FocusScope.of(context).unfocus(); + Future.delayed(const Duration(milliseconds: 500), () { + controller.richTextControllerForWeb.editorController.setFocus(); + }); + controller.richTextControllerForWeb.closeAllMenuPopup(); + } + ), + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget _buildVacationMessageTypeButton(BuildContext context, VacationMessageType messageType) { + return buildButtonWrapText( + messageType.getTitle(context), + textStyle: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: controller.vacationMessageType.value == messageType + ? AppColor.colorContentEmail + : AppColor.colorHintSearchBar), + bgColor: controller.vacationMessageType.value == messageType + ? AppColor.emailAddressChipColor + : Colors.transparent, + height: 35, + radius: 10, + onTap: () => controller.selectVacationMessageType(context, messageType)); + } } \ No newline at end of file