TF-61 Implement rich text on web
This commit is contained in:
@@ -19,6 +19,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachmen
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/session_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart';
|
||||
@@ -135,6 +136,7 @@ class ComposerBindings extends BaseBindings {
|
||||
@override
|
||||
void bindingsController() {
|
||||
Get.lazyPut(() => UploadController(Get.find<UploadAttachmentInteractor>()));
|
||||
Get.lazyPut(() => RichTextWebController());
|
||||
Get.lazyPut(() => ComposerController(
|
||||
Get.find<SendEmailInteractor>(),
|
||||
Get.find<GetAutoCompleteInteractor>(),
|
||||
@@ -148,11 +150,13 @@ class ComposerBindings extends BaseBindings {
|
||||
Get.find<UploadController>(),
|
||||
Get.find<RemoveComposerCacheOnWebInteractor>(),
|
||||
Get.find<SaveComposerCacheOnWebInteractor>(),
|
||||
Get.find<RichTextWebController>(),
|
||||
));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
Get.delete<UploadController>();
|
||||
Get.delete<RichTextWebController>();
|
||||
Get.delete<ComposerController>();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart' as editor_web;
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/identities/identity.dart';
|
||||
@@ -34,6 +33,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_as_drafts_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/update_email_drafts_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
|
||||
@@ -87,13 +87,13 @@ class ComposerController extends BaseController {
|
||||
final UploadController uploadController;
|
||||
final RemoveComposerCacheOnWebInteractor _removeComposerCacheOnWebInteractor;
|
||||
final SaveComposerCacheOnWebInteractor _saveComposerCacheOnWebInteractor;
|
||||
final RichTextWebController richTextWebController;
|
||||
|
||||
List<EmailAddress> listToEmailAddress = <EmailAddress>[];
|
||||
List<EmailAddress> listCcEmailAddress = <EmailAddress>[];
|
||||
List<EmailAddress> listBccEmailAddress = <EmailAddress>[];
|
||||
ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.tMailContact;
|
||||
HtmlEditorApi? htmlEditorApi;
|
||||
final htmlControllerBrowser = editor_web.HtmlEditorController(processNewLineAsBr: true);
|
||||
|
||||
final subjectEmailInputController = TextEditingController();
|
||||
final toEmailAddressController = TextEditingController();
|
||||
@@ -119,7 +119,7 @@ class ComposerController extends BaseController {
|
||||
screenDisplayMode.value == ScreenDisplayMode.minimize) {
|
||||
contentHtml = textEditorWeb ?? '';
|
||||
} else {
|
||||
contentHtml = await htmlControllerBrowser.getText();
|
||||
contentHtml = await richTextWebController.editorController.getText();
|
||||
}
|
||||
log('ComposerController::_getEmailBodyText():WEB: contentHtml: $contentHtml');
|
||||
final newContentHtml = contentHtml.removeEditorStartTag();
|
||||
@@ -155,6 +155,7 @@ class ComposerController extends BaseController {
|
||||
this.uploadController,
|
||||
this._removeComposerCacheOnWebInteractor,
|
||||
this._saveComposerCacheOnWebInteractor,
|
||||
this.richTextWebController,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -318,7 +319,7 @@ class ComposerController extends BaseController {
|
||||
}
|
||||
|
||||
void setFullScreenEditor() {
|
||||
htmlControllerBrowser.setFullScreen();
|
||||
richTextWebController.editorController.setFullScreen();
|
||||
}
|
||||
|
||||
String? _getHeaderEmailQuoted(BuildContext context, ComposerArguments arguments) {
|
||||
@@ -872,8 +873,8 @@ class ComposerController extends BaseController {
|
||||
}
|
||||
|
||||
void _updateTextForEditor() async {
|
||||
final textCurrent = await htmlControllerBrowser.getText();
|
||||
htmlControllerBrowser.setText(textCurrent);
|
||||
final textCurrent = await richTextWebController.editorController.getText();
|
||||
richTextWebController.editorController.setText(textCurrent);
|
||||
}
|
||||
|
||||
void deleteComposer() {
|
||||
@@ -1036,7 +1037,7 @@ class ComposerController extends BaseController {
|
||||
final signatureAsHtml = '--<br><br>${signature.value}';
|
||||
log('ComposerController::_applySignature(): $signatureAsHtml');
|
||||
if (BuildUtils.isWeb) {
|
||||
htmlControllerBrowser.insertSignature(signatureAsHtml);
|
||||
richTextWebController.editorController.insertSignature(signatureAsHtml);
|
||||
} else {
|
||||
htmlEditorApi?.insertSignature(signatureAsHtml);
|
||||
}
|
||||
@@ -1045,7 +1046,7 @@ class ComposerController extends BaseController {
|
||||
void _removeSignature() {
|
||||
log('ComposerController::_removeSignature():');
|
||||
if (BuildUtils.isWeb) {
|
||||
htmlControllerBrowser.removeSignature();
|
||||
richTextWebController.editorController.removeSignature();
|
||||
} else {
|
||||
htmlEditorApi?.removeSignature();
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:html_editor_enhanced/html_editor.dart' as editor_web;
|
||||
import 'package:html_editor_enhanced/html_editor.dart';
|
||||
import 'package:jmap_dart_client/jmap/identities/identity.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_file_composer_builder.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/email_address_input_builder.dart';
|
||||
@@ -21,7 +23,8 @@ import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_sta
|
||||
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
class ComposerView extends GetWidget<ComposerController>
|
||||
with AppLoaderMixin, RichTextButtonMixin {
|
||||
|
||||
final responsiveUtils = Get.find<ResponsiveUtils>();
|
||||
final imagePaths = Get.find<ImagePaths>();
|
||||
@@ -58,6 +61,7 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
Expanded(child: Column(
|
||||
children: [
|
||||
_buildAttachmentsWidget(context),
|
||||
_buildToolbarRichTextWidget(),
|
||||
_buildEditorForm(context)
|
||||
]
|
||||
)),
|
||||
@@ -162,11 +166,15 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
return Row(
|
||||
children: [
|
||||
buildIconWeb(
|
||||
minSize: 40,
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: SvgPicture.asset(imagePaths.icCloseMailbox, fit: BoxFit.fill),
|
||||
tooltip: AppLocalizations.of(context).saveAndClose,
|
||||
onTap: () => controller.saveEmailAsDrafts(context)),
|
||||
if (responsiveUtils.isDesktop(context))
|
||||
Obx(() => buildIconWeb(
|
||||
minSize: 40,
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: SvgPicture.asset(
|
||||
controller.screenDisplayMode.value == ScreenDisplayMode.fullScreen
|
||||
? imagePaths.icFullScreenExit
|
||||
@@ -178,6 +186,8 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
: ScreenDisplayMode.fullScreen))),
|
||||
if (responsiveUtils.isDesktop(context))
|
||||
buildIconWeb(
|
||||
minSize: 40,
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: SvgPicture.asset(imagePaths.icMinimize, fit: BoxFit.fill),
|
||||
tooltip: AppLocalizations.of(context).minimize,
|
||||
onTap: () => controller.displayScreenTypeComposerAction(ScreenDisplayMode.minimize)),
|
||||
@@ -284,6 +294,7 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
child: Column(
|
||||
children: [
|
||||
_buildAttachmentsWidget(context),
|
||||
_buildToolbarRichTextWidget(),
|
||||
_buildEditorForm(context)
|
||||
]
|
||||
))),
|
||||
@@ -474,12 +485,18 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
|
||||
Widget _buildListButton(BuildContext context) {
|
||||
return Transform(
|
||||
transform: Matrix4.translationValues(-10.0, 0.0, 0.0),
|
||||
transform: Matrix4.translationValues(-5.0, 0.0, 0.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: Row(children: [
|
||||
buildIconWeb(
|
||||
icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, color: AppColor.colorTextButton, fit: BoxFit.fill),
|
||||
minSize: 40,
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: SvgPicture.asset(imagePaths.icAttachmentsComposer,
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: AppColor.colorTextButton,
|
||||
fit: BoxFit.fill),
|
||||
tooltip: AppLocalizations.of(context).attach_file,
|
||||
onTap: () => controller.openFilePickerByType(context, FileType.any)),
|
||||
])
|
||||
@@ -546,46 +563,48 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
|
||||
Widget _buildHtmlEditor(BuildContext context, String initContent) {
|
||||
log('ComposerView::_buildHtmlEditor(): initContent: $initContent');
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: responsiveUtils.isMobile(context) ? 8 : 10),
|
||||
child: editor_web.HtmlEditor(
|
||||
key: const Key('composer_editor_web'),
|
||||
controller: controller.htmlControllerBrowser,
|
||||
htmlEditorOptions: const editor_web.HtmlEditorOptions(
|
||||
hint: '',
|
||||
darkMode: false,
|
||||
),
|
||||
blockQuotedContent: initContent,
|
||||
htmlToolbarOptions: const editor_web.HtmlToolbarOptions(
|
||||
toolbarPosition: editor_web.ToolbarPosition.custom),
|
||||
otherOptions: const editor_web.OtherOptions(height: 550),
|
||||
callbacks: editor_web.Callbacks(
|
||||
onBeforeCommand: (String? currentHtml) {
|
||||
log('ComposerView::_buildComposerEditor(): onBeforeCommand : $currentHtml');
|
||||
controller.setTextEditorWeb(currentHtml);
|
||||
}, onChangeContent: (String? changed) {
|
||||
log('ComposerView::_buildComposerEditor(): onChangeContent : $changed');
|
||||
controller.setTextEditorWeb(changed);
|
||||
}, onInit: () {
|
||||
log('ComposerView::_buildComposerEditor(): onInit');
|
||||
controller.setTextEditorWeb(initContent);
|
||||
controller.setFullScreenEditor();
|
||||
}, onFocus: () {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
controller.htmlControllerBrowser.setFocus();
|
||||
});
|
||||
}, onBlur: () {
|
||||
controller.onEditorFocusChange(false);
|
||||
}, onMouseDown: () {
|
||||
controller.onEditorFocusChange(true);
|
||||
}),
|
||||
)
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: responsiveUtils.isMobile(context) ? 8 : 10),
|
||||
child: HtmlEditor(
|
||||
key: const Key('composer_editor_web'),
|
||||
controller: controller.richTextWebController.editorController,
|
||||
htmlEditorOptions: const HtmlEditorOptions(
|
||||
hint: '',
|
||||
darkMode: false,
|
||||
),
|
||||
blockQuotedContent: initContent,
|
||||
htmlToolbarOptions: const HtmlToolbarOptions(
|
||||
toolbarType: ToolbarType.hide,
|
||||
defaultToolbarButtons: []),
|
||||
otherOptions: const OtherOptions(height: 550),
|
||||
callbacks: Callbacks(onBeforeCommand: (String? currentHtml) {
|
||||
log('ComposerView::_buildHtmlEditor(): onBeforeCommand : $currentHtml');
|
||||
controller.setTextEditorWeb(currentHtml);
|
||||
}, onChangeContent: (String? changed) {
|
||||
log('ComposerView::_buildHtmlEditor(): onChangeContent : $changed');
|
||||
controller.setTextEditorWeb(changed);
|
||||
}, onInit: () {
|
||||
log('ComposerView::_buildHtmlEditor(): onInit');
|
||||
controller.setTextEditorWeb(initContent);
|
||||
controller.setFullScreenEditor();
|
||||
}, onFocus: () {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
controller.richTextWebController.editorController.setFocus();
|
||||
});
|
||||
}, onBlur: () {
|
||||
controller.onEditorFocusChange(false);
|
||||
}, onMouseDown: () {
|
||||
controller.onEditorFocusChange(true);
|
||||
}, onChangeSelection: (settings) {
|
||||
controller.richTextWebController.onEditorSettingsChange(settings);
|
||||
}),
|
||||
)
|
||||
)
|
||||
);
|
||||
// });
|
||||
}
|
||||
|
||||
Widget _buildAttachmentsWidget(BuildContext context) {
|
||||
@@ -713,4 +732,18 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin {
|
||||
return constraints.maxHeight > 0 ? constraints.maxHeight * 0.4 : 150.0;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildToolbarRichTextWidget() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 20, top: 4, bottom: 8),
|
||||
child: Row(
|
||||
children: RichTextStyleType.values.map((textType) => Obx(() {
|
||||
return buildIconStyleText(
|
||||
path: textType.getIcon(imagePaths),
|
||||
isSelected: controller.richTextWebController.isTextStyleTypeSelected(textType),
|
||||
onTap: () => controller.richTextWebController.applyRichTextStyle(textType));
|
||||
})).toList()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/views/button/icon_button_web.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
mixin RichTextButtonMixin {
|
||||
|
||||
Widget buildIconStyleText({
|
||||
required String path,
|
||||
required bool? isSelected,
|
||||
required VoidCallback onTap,
|
||||
}){
|
||||
return buildIconWeb(
|
||||
icon: SvgPicture.asset(
|
||||
path,
|
||||
color: isSelected == true
|
||||
? Colors.black
|
||||
: AppColor.colorDividerMailbox,
|
||||
fit: BoxFit.fill),
|
||||
iconPadding: const EdgeInsets.all(4),
|
||||
colorFocus: Colors.white,
|
||||
minSize: 26,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user