diff --git a/assets/images/ic_style_bold.svg b/assets/images/ic_style_bold.svg
new file mode 100644
index 000000000..cfc08f1c9
--- /dev/null
+++ b/assets/images/ic_style_bold.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/ic_style_italic.svg b/assets/images/ic_style_italic.svg
new file mode 100644
index 000000000..222753ded
--- /dev/null
+++ b/assets/images/ic_style_italic.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/ic_style_underline.svg b/assets/images/ic_style_underline.svg
new file mode 100644
index 000000000..aa7a8506e
--- /dev/null
+++ b/assets/images/ic_style_underline.svg
@@ -0,0 +1,3 @@
+
diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart
index 1a8f5ebe4..6eb5c819a 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -128,6 +128,9 @@ class ImagePaths {
String get icCloseAdvancedSearch => _getImagePath('ic_close_advanced_search.svg');
String get icLanguage => _getImagePath('ic_language.svg');
String get icChecked => _getImagePath('ic_checked.svg');
+ String get icStyleBold => _getImagePath('ic_style_bold.svg');
+ String get icStyleItalic => _getImagePath('ic_style_italic.svg');
+ String get icStyleUnderline => _getImagePath('ic_style_underline.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
diff --git a/core/lib/presentation/views/button/icon_button_web.dart b/core/lib/presentation/views/button/icon_button_web.dart
index 7280becb6..d476ccf36 100644
--- a/core/lib/presentation/views/button/icon_button_web.dart
+++ b/core/lib/presentation/views/button/icon_button_web.dart
@@ -13,12 +13,16 @@ Widget buildIconWeb({
double? iconSize,
double? splashRadius,
double? minSize,
+ Color? colorSelected,
+ Color? colorFocus,
+ ShapeBorder? shapeBorder,
}) {
return Material(
- color: Colors.transparent,
- shape: CircleBorder(),
+ color: colorSelected ?? Colors.transparent,
+ shape: shapeBorder ?? CircleBorder(),
child: IconButton(
icon: icon,
+ focusColor: colorFocus,
iconSize: iconSize,
constraints: minSize != null ? BoxConstraints(minWidth: minSize, minHeight: minSize) : null,
padding: iconPadding ?? EdgeInsets.all(8.0),
diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart
index e46e5eff7..3686ce093 100644
--- a/lib/features/composer/presentation/composer_bindings.dart
+++ b/lib/features/composer/presentation/composer_bindings.dart
@@ -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()));
+ Get.lazyPut(() => RichTextWebController());
Get.lazyPut(() => ComposerController(
Get.find(),
Get.find(),
@@ -148,11 +150,13 @@ class ComposerBindings extends BaseBindings {
Get.find(),
Get.find(),
Get.find(),
+ Get.find(),
));
}
void dispose() {
Get.delete();
+ Get.delete();
Get.delete();
}
}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart
index 320d03ba9..de0cebcaf 100644
--- a/lib/features/composer/presentation/composer_controller.dart
+++ b/lib/features/composer/presentation/composer_controller.dart
@@ -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 listToEmailAddress = [];
List listCcEmailAddress = [];
List listBccEmailAddress = [];
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 = '--
${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();
}
diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart
index e20a84ce8..ca94557ca 100644
--- a/lib/features/composer/presentation/composer_view_web.dart
+++ b/lib/features/composer/presentation/composer_view_web.dart
@@ -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 with AppLoaderMixin {
+class ComposerView extends GetWidget
+ with AppLoaderMixin, RichTextButtonMixin {
final responsiveUtils = Get.find();
final imagePaths = Get.find();
@@ -58,6 +61,7 @@ class ComposerView extends GetWidget with AppLoaderMixin {
Expanded(child: Column(
children: [
_buildAttachmentsWidget(context),
+ _buildToolbarRichTextWidget(),
_buildEditorForm(context)
]
)),
@@ -162,11 +166,15 @@ class ComposerView extends GetWidget 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 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 with AppLoaderMixin {
child: Column(
children: [
_buildAttachmentsWidget(context),
+ _buildToolbarRichTextWidget(),
_buildEditorForm(context)
]
))),
@@ -474,12 +485,18 @@ class ComposerView extends GetWidget 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 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 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()
+ ),
+ );
+ }
}
\ 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
new file mode 100644
index 000000000..24ebda281
--- /dev/null
+++ b/lib/features/composer/presentation/mixin/rich_text_button_mixin.dart
@@ -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,
+ );
+ }
+}
\ No newline at end of file