TF-858 Add HtmlEditor for message of vacation on web

This commit is contained in:
dab246
2022-09-29 18:58:41 +07:00
committed by Dat H. Pham
parent 3e23da1825
commit 9240768deb
9 changed files with 456 additions and 45 deletions
@@ -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
);
@@ -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;
}
}
}
@@ -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,
);
}
@@ -0,0 +1,4 @@
class VacationUtils {
static const String vacationTagName = 'Vacation';
}
@@ -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<AppToast>();
final _imagePaths = Get.find<ImagePaths>();
final _settingController = Get.find<SettingsController>();
final _richTextControllerForWeb = Get.find<RichTextWebController>(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<String>();
final vacationMessageType = Rx<VacationMessageType>(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<String?> _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();
}
}
@@ -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<GetAllVacationInteractor>(),
Get.find<UpdateVacationInteractor>(),
@@ -35,4 +38,8 @@ class VacationControllerBindings extends BaseBindings {
@override
void bindingsRepositoryImpl() {
}
void dispose() {
Get.delete<RichTextWebController>(tag: VacationUtils.vacationTagName);
}
}
@@ -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<VacationController> {
class VacationView extends GetWidget<VacationController> with RichTextButtonMixin {
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _imagePaths = Get.find<ImagePaths>();
@@ -257,18 +263,7 @@ class VacationView extends GetWidget<VacationController> {
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<VacationController> {
}
}
}
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));
}
}