TF-733 Apply change text color to body composer on web
This commit is contained in:
@@ -158,4 +158,40 @@ Widget buildButtonWrapText(String name, {
|
|||||||
color: Colors.white)),
|
color: Colors.white)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
Widget buildButtonWrapText(String name, {
|
||||||
|
TextStyle? textStyle,
|
||||||
|
Color? bgColor,
|
||||||
|
Color? borderColor,
|
||||||
|
double? radius,
|
||||||
|
double? height,
|
||||||
|
EdgeInsets? padding,
|
||||||
|
IconWebCallback? onTap
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
height: height ?? 40,
|
||||||
|
padding: padding,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => onTap?.call(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||||
|
(Set<MaterialState> states) => bgColor ?? AppColor.colorTextButton),
|
||||||
|
shape: MaterialStateProperty.all(RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(radius ?? 8),
|
||||||
|
side: BorderSide(
|
||||||
|
width: borderColor != null ? 1 : 0,
|
||||||
|
color: borderColor ?? bgColor ?? AppColor.colorTextButton))),
|
||||||
|
padding: MaterialStateProperty.resolveWith<EdgeInsets>(
|
||||||
|
(Set<MaterialState> states) => const EdgeInsets.symmetric(horizontal: 16)),
|
||||||
|
elevation: MaterialStateProperty.resolveWith<double>(
|
||||||
|
(Set<MaterialState> states) => 0)),
|
||||||
|
child: Text(name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: textStyle ??
|
||||||
|
const TextStyle(
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white)),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ class ComposerView extends GetWidget<ComposerController>
|
|||||||
Expanded(child: Column(
|
Expanded(child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildAttachmentsWidget(context),
|
_buildAttachmentsWidget(context),
|
||||||
_buildToolbarRichTextWidget(),
|
_buildToolbarRichTextWidget(context),
|
||||||
_buildInlineLoadingView(),
|
_buildInlineLoadingView(),
|
||||||
_buildEditorForm(context)
|
_buildEditorForm(context)
|
||||||
]
|
]
|
||||||
@@ -299,7 +299,7 @@ class ComposerView extends GetWidget<ComposerController>
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildAttachmentsWidget(context),
|
_buildAttachmentsWidget(context),
|
||||||
_buildToolbarRichTextWidget(),
|
_buildToolbarRichTextWidget(context),
|
||||||
_buildInlineLoadingView(),
|
_buildInlineLoadingView(),
|
||||||
_buildEditorForm(context)
|
_buildEditorForm(context)
|
||||||
]
|
]
|
||||||
@@ -734,15 +734,25 @@ class ComposerView extends GetWidget<ComposerController>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildToolbarRichTextWidget() {
|
Widget _buildToolbarRichTextWidget(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 20, top: 4, bottom: 8),
|
padding: const EdgeInsets.only(left: 20, top: 4, bottom: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: RichTextStyleType.values.map((textType) => Obx(() {
|
children: RichTextStyleType.values.map((textType) => Obx(() {
|
||||||
return buildIconStyleText(
|
switch(textType) {
|
||||||
path: textType.getIcon(imagePaths),
|
case RichTextStyleType.textColor:
|
||||||
isSelected: controller.richTextWebController.isTextStyleTypeSelected(textType),
|
return buildIconColorText(
|
||||||
onTap: () => controller.richTextWebController.applyRichTextStyle(textType));
|
iconData: textType.getIconData(),
|
||||||
|
colorSelected: controller.richTextWebController.selectedTextColor.value,
|
||||||
|
tooltip: textType.getTooltipButton(context),
|
||||||
|
onTap: () => controller.richTextWebController.applyRichTextStyle(context, textType));
|
||||||
|
default:
|
||||||
|
return buildIconStyleText(
|
||||||
|
path: textType.getIcon(imagePaths),
|
||||||
|
isSelected: controller.richTextWebController.isTextStyleTypeSelected(textType),
|
||||||
|
tooltip: textType.getTooltipButton(context),
|
||||||
|
onTap: () => controller.richTextWebController.applyRichTextStyle(context, textType));
|
||||||
|
}
|
||||||
})).toList()
|
})).toList()
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
import 'package:core/presentation/views/dialog/color_picker_dialog_builder.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
|
|
||||||
|
abstract class BaseRichTextController extends GetxController {
|
||||||
|
|
||||||
|
void openMenuSelectColor(
|
||||||
|
BuildContext context,
|
||||||
|
Color currentColor,
|
||||||
|
{Function(Color?)? onSelectColor}
|
||||||
|
) async {
|
||||||
|
await ColorPickerDialogBuilder(
|
||||||
|
context,
|
||||||
|
currentColor,
|
||||||
|
title: AppLocalizations.of(context).chooseAColor,
|
||||||
|
textActionSetColor: AppLocalizations.of(context).setColor,
|
||||||
|
textActionResetDefault: AppLocalizations.of(context).resetToDefault,
|
||||||
|
textActionCancel: AppLocalizations.of(context).cancel,
|
||||||
|
cancelActionCallback: () => popBack(),
|
||||||
|
setColorActionCallback: (selectedColor) {
|
||||||
|
onSelectColor?.call(selectedColor);
|
||||||
|
popBack();
|
||||||
|
}
|
||||||
|
).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,18 +4,22 @@ import 'package:dartz/dartz.dart';
|
|||||||
import 'package:html/parser.dart' show parse;
|
import 'package:html/parser.dart' show parse;
|
||||||
|
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:html_editor_enhanced/html_editor.dart';
|
import 'package:html_editor_enhanced/html_editor.dart';
|
||||||
import 'package:model/email/attachment.dart';
|
import 'package:model/email/attachment.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/presentation/controller/base_rich_text_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/rich_text_style_type.dart';
|
||||||
|
|
||||||
class RichTextWebController extends GetxController {
|
class RichTextWebController extends BaseRichTextController {
|
||||||
|
|
||||||
final editorController = HtmlEditorController(processNewLineAsBr: true);
|
final editorController = HtmlEditorController(processNewLineAsBr: true);
|
||||||
|
|
||||||
final listTextStyleApply = RxList<RichTextStyleType>();
|
final listTextStyleApply = RxList<RichTextStyleType>();
|
||||||
|
final selectedTextColor = Colors.black.obs;
|
||||||
|
|
||||||
void onEditorSettingsChange(EditorSettings settings) {
|
void onEditorSettingsChange(EditorSettings settings) {
|
||||||
log('RichTextWebController::onEditorSettingsChange():');
|
log('RichTextWebController::onEditorSettingsChange():');
|
||||||
@@ -37,9 +41,32 @@ class RichTextWebController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyRichTextStyle(RichTextStyleType textStyleType) {
|
void applyRichTextStyle(BuildContext context, RichTextStyleType textStyleType) {
|
||||||
editorController.execCommand(textStyleType.commandAction);
|
switch(textStyleType) {
|
||||||
_selectTextStyleType(textStyleType);
|
case RichTextStyleType.textColor:
|
||||||
|
openMenuSelectColor(
|
||||||
|
context,
|
||||||
|
selectedTextColor.value,
|
||||||
|
onSelectColor: (selectedColor) {
|
||||||
|
final newColor = selectedColor ?? Colors.black;
|
||||||
|
final colorAsString = (newColor.value & 0xFFFFFF)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(6, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
log('RichTextWebController::applyRichTextStyle(): color: $newColor');
|
||||||
|
log('RichTextWebController::applyRichTextStyle(): colorAsString: $colorAsString');
|
||||||
|
selectedTextColor.value = newColor;
|
||||||
|
editorController.execCommand(
|
||||||
|
textStyleType.commandAction,
|
||||||
|
argument: colorAsString);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
editorController.execCommand(textStyleType.commandAction);
|
||||||
|
_selectTextStyleType(textStyleType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _selectTextStyleType(RichTextStyleType textStyleType) {
|
void _selectTextStyleType(RichTextStyleType textStyleType) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mixin RichTextButtonMixin {
|
|||||||
required String path,
|
required String path,
|
||||||
required bool? isSelected,
|
required bool? isSelected,
|
||||||
required VoidCallback onTap,
|
required VoidCallback onTap,
|
||||||
|
String? tooltip
|
||||||
}){
|
}){
|
||||||
return buildIconWeb(
|
return buildIconWeb(
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset(
|
||||||
@@ -21,6 +22,23 @@ mixin RichTextButtonMixin {
|
|||||||
iconPadding: const EdgeInsets.all(4),
|
iconPadding: const EdgeInsets.all(4),
|
||||||
colorFocus: Colors.white,
|
colorFocus: Colors.white,
|
||||||
minSize: 26,
|
minSize: 26,
|
||||||
|
tooltip: tooltip,
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildIconColorText({
|
||||||
|
required IconData? iconData,
|
||||||
|
required Color? colorSelected,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
String? tooltip
|
||||||
|
}){
|
||||||
|
return buildIconWeb(
|
||||||
|
icon: Icon(iconData, color: colorSelected ?? Colors.black, size: 20),
|
||||||
|
iconPadding: const EdgeInsets.all(4),
|
||||||
|
colorFocus: Colors.white,
|
||||||
|
minSize: 20,
|
||||||
|
tooltip: tooltip,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
|
||||||
import 'package:core/presentation/resources/image_paths.dart';
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
|
||||||
enum RichTextStyleType {
|
enum RichTextStyleType {
|
||||||
bold,
|
bold,
|
||||||
italic,
|
italic,
|
||||||
underline,
|
underline,
|
||||||
strikeThrough;
|
strikeThrough,
|
||||||
|
textColor;
|
||||||
|
|
||||||
String get commandAction {
|
String get commandAction {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
@@ -17,6 +20,8 @@ enum RichTextStyleType {
|
|||||||
return 'underline';
|
return 'underline';
|
||||||
case strikeThrough:
|
case strikeThrough:
|
||||||
return 'strikeThrough';
|
return 'strikeThrough';
|
||||||
|
case textColor:
|
||||||
|
return 'foreColor';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -36,4 +41,30 @@ enum RichTextStyleType {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData? getIconData() {
|
||||||
|
switch (this) {
|
||||||
|
case textColor:
|
||||||
|
return Icons.format_color_text;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTooltipButton(BuildContext context) {
|
||||||
|
switch (this) {
|
||||||
|
case bold:
|
||||||
|
return AppLocalizations.of(context).formatBold;
|
||||||
|
case italic:
|
||||||
|
return AppLocalizations.of(context).formatItalic;
|
||||||
|
case underline:
|
||||||
|
return AppLocalizations.of(context).formatUnderline;
|
||||||
|
case strikeThrough:
|
||||||
|
return AppLocalizations.of(context).formatStrikethrough;
|
||||||
|
case textColor:
|
||||||
|
return AppLocalizations.of(context).formatTextColor;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"@@last_modified": "2022-07-18T16:31:40.598846",
|
"@@last_modified": "2022-07-21T13:16:06.832687",
|
||||||
"initializing_data": "Initializing data...",
|
"initializing_data": "Initializing data...",
|
||||||
"@initializing_data": {
|
"@initializing_data": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -1615,5 +1615,53 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"chooseAColor": "Choose a color",
|
||||||
|
"@chooseAColor": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"resetToDefault": "Reset to default",
|
||||||
|
"@resetToDefault": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"setColor": "Set color",
|
||||||
|
"@setColor": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"formatBold": "Bold",
|
||||||
|
"@formatBold": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"formatItalic": "Italic",
|
||||||
|
"@formatItalic": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"formatUnderline": "Underline",
|
||||||
|
"@formatUnderline": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"formatStrikethrough": "Strikethrough",
|
||||||
|
"@formatStrikethrough": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"formatTextColor": "Text Color",
|
||||||
|
"@formatTextColor": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1659,4 +1659,52 @@ class AppLocalizations {
|
|||||||
'Please input either an image or an image URL, not both',
|
'Please input either an image or an image URL, not both',
|
||||||
name: 'insertImageErrorDuplicate');
|
name: 'insertImageErrorDuplicate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get chooseAColor {
|
||||||
|
return Intl.message(
|
||||||
|
'Choose a color',
|
||||||
|
name: 'chooseAColor');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get resetToDefault {
|
||||||
|
return Intl.message(
|
||||||
|
'Reset to default',
|
||||||
|
name: 'resetToDefault');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get setColor {
|
||||||
|
return Intl.message(
|
||||||
|
'Set color',
|
||||||
|
name: 'setColor');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get formatBold {
|
||||||
|
return Intl.message(
|
||||||
|
'Bold',
|
||||||
|
name: 'formatBold');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get formatItalic {
|
||||||
|
return Intl.message(
|
||||||
|
'Italic',
|
||||||
|
name: 'formatItalic');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get formatUnderline {
|
||||||
|
return Intl.message(
|
||||||
|
'Underline',
|
||||||
|
name: 'formatUnderline');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get formatStrikethrough {
|
||||||
|
return Intl.message(
|
||||||
|
'Strikethrough',
|
||||||
|
name: 'formatStrikethrough');
|
||||||
|
}
|
||||||
|
|
||||||
|
String get formatTextColor {
|
||||||
|
return Intl.message(
|
||||||
|
'Text Color',
|
||||||
|
name: 'formatTextColor');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user