edit UI rich text for design
This commit is contained in:
@@ -3,7 +3,7 @@ import 'package:core/presentation/extensions/color_extension.dart';
|
|||||||
import 'package:core/presentation/resources/image_paths.dart';
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
import 'package:core/presentation/utils/style_utils.dart';
|
import 'package:core/presentation/utils/style_utils.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:enough_html_editor/enough_html_editor.dart';
|
import 'package:enough_html_editor/enough_html_editor.dart' as enough_html_editor;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -40,7 +40,7 @@ class DropDownButtonWidget<T> extends StatelessWidget {
|
|||||||
this.radiusButton = 10,
|
this.radiusButton = 10,
|
||||||
this.opacity = 1.0,
|
this.opacity = 1.0,
|
||||||
this.iconArrowDown,
|
this.iconArrowDown,
|
||||||
this.colorButton,
|
this.colorButton = Colors.white,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -157,7 +157,7 @@ class DropDownButtonWidget<T> extends StatelessWidget {
|
|||||||
if (item is FontNameType) {
|
if (item is FontNameType) {
|
||||||
return item.fontFamily;
|
return item.fontFamily;
|
||||||
}
|
}
|
||||||
if (item is SafeFont) {
|
if (item is enough_html_editor.SafeFont) {
|
||||||
return item.name;
|
return item.name;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import 'package:jmap_dart_client/jmap/identities/identity.dart';
|
|||||||
import 'package:model/model.dart';
|
import 'package:model/model.dart';
|
||||||
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
|
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart';
|
import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.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/mixin/rich_text_button_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu_font_status.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu_font_status.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';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_file_composer_builder.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';
|
import 'package:tmail_ui_user/features/composer/presentation/widgets/email_address_input_builder.dart';
|
||||||
|
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
|
||||||
import 'package:tmail_ui_user/features/upload/presentation/extensions/list_upload_file_state_extension.dart';
|
import 'package:tmail_ui_user/features/upload/presentation/extensions/list_upload_file_state_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
|
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
|
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
|
||||||
@@ -401,28 +403,8 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin, Ri
|
|||||||
const Divider(color: AppColor.colorDividerComposer, height: 1),
|
const Divider(color: AppColor.colorDividerComposer, height: 1),
|
||||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildListButton(context)),
|
Padding(padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildListButton(context)),
|
||||||
const Divider(color: AppColor.colorDividerComposer, height: 1),
|
const Divider(color: AppColor.colorDividerComposer, height: 1),
|
||||||
Obx(() {
|
_buildAttachmentsWidget(context),
|
||||||
final uploadAttachments = controller.uploadController.listUploadAttachments;
|
_buildComposerEditor(context),
|
||||||
if (uploadAttachments.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
} else {
|
|
||||||
return Column(children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: _buildAttachmentsTitle(context,
|
|
||||||
uploadAttachments,
|
|
||||||
controller.expandModeAttachments.value)),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
|
|
||||||
child: _buildAttachmentsList(context,
|
|
||||||
uploadAttachments,
|
|
||||||
controller.expandModeAttachments.value))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 20),
|
|
||||||
child: _buildComposerEditor(context)),
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -463,83 +445,121 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin, Ri
|
|||||||
padding: const EdgeInsets.only(left: 60, right: 25),
|
padding: const EdgeInsets.only(left: 60, right: 25),
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
_buildToolbarRichTextWidget(context),
|
_buildToolbarRichTextWidget(context),
|
||||||
const SizedBox(height: 8),
|
_buildAttachmentsWidget(context),
|
||||||
Obx(() {
|
_buildInlineLoadingView(),
|
||||||
final uploadAttachments = controller.uploadController.listUploadAttachments;
|
_buildComposerEditor(context),
|
||||||
if (uploadAttachments.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
} else {
|
|
||||||
return Column(children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: _buildAttachmentsTitle(context,
|
|
||||||
uploadAttachments,
|
|
||||||
controller.expandModeAttachments.value)),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
|
|
||||||
child: _buildAttachmentsList(context,
|
|
||||||
uploadAttachments,
|
|
||||||
controller.expandModeAttachments.value))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 20, top: 10),
|
|
||||||
child: _buildComposerEditor(context)),
|
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildAttachmentsWidget(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final uploadAttachments = controller.uploadController.listUploadAttachments;
|
||||||
|
if (uploadAttachments.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
} else {
|
||||||
|
return Column(children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: _buildAttachmentsTitle(context,
|
||||||
|
uploadAttachments,
|
||||||
|
controller.expandModeAttachments.value)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8, left: 16, right: 16),
|
||||||
|
child: _buildAttachmentsList(context,
|
||||||
|
uploadAttachments,
|
||||||
|
controller.expandModeAttachments.value))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInlineLoadingView() {
|
||||||
|
return Obx(() => controller.uploadController.uploadInlineViewState.value.fold(
|
||||||
|
(failure) => const SizedBox.shrink(),
|
||||||
|
(success) {
|
||||||
|
if (success is UploadingAttachmentUploadState || success is DownloadingImageAsBase64) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(5),
|
||||||
|
child: loadingWidgetWithSizeColor(
|
||||||
|
size: 30,
|
||||||
|
color: AppColor.primaryColor));
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildToolbarRichTextWidget(BuildContext context) {
|
Widget _buildToolbarRichTextWidget(BuildContext context) {
|
||||||
return Container(
|
return Obx(() => Container(
|
||||||
padding: const EdgeInsets.only(left: 20, top: 15, bottom: 8),
|
padding: const EdgeInsets.only(left: 20, top: 8, bottom: 8),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: RichTextStyleType.values.map((textType) => Obx(() {
|
children: [
|
||||||
switch(textType) {
|
Container(
|
||||||
case RichTextStyleType.textColor:
|
width: 200,
|
||||||
return buildIconColorText(
|
padding: const EdgeInsets.only(right: 2),
|
||||||
iconData: textType.getIconData(),
|
child: DropDownButtonWidget<SafeFont>(
|
||||||
colorSelected: controller.richTextMobileTabletController.selectedTextColor.value,
|
items: SafeFont.values,
|
||||||
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, textType));
|
itemSelected: controller.richTextMobileTabletController.selectedFontName.value,
|
||||||
case RichTextStyleType.textBackgroundColor:
|
onChanged: (newFont) => controller.richTextMobileTabletController.applyNewFontStyle(newFont),
|
||||||
return Padding(
|
onMenuStateChange: (isOpen) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
final newStatus = isOpen
|
||||||
child: buildIconColorBackgroundText(
|
? DropdownMenuFontStatus.open
|
||||||
iconData: textType.getIconData(),
|
: DropdownMenuFontStatus.closed;
|
||||||
colorSelected: controller.richTextMobileTabletController.selectedTextBackgroundColor.value,
|
controller.richTextMobileTabletController.menuFontStatus = newStatus;
|
||||||
tooltip: textType.getTooltipButton(context),
|
},
|
||||||
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, textType)),
|
heightItem: 38,
|
||||||
);
|
sizeIconChecked: 16,
|
||||||
case RichTextStyleType.fontName:
|
radiusButton: 5,
|
||||||
return Container(
|
colorButton: Colors.white,
|
||||||
width: 200,
|
supportSelectionIcon: true)),
|
||||||
padding: const EdgeInsets.only(right: 2),
|
Padding(
|
||||||
child: DropDownButtonWidget<SafeFont>(
|
padding: const EdgeInsets.only(left: 8),
|
||||||
items: SafeFont.values,
|
child: buildWrapIconStyleText(
|
||||||
itemSelected: controller.richTextMobileTabletController.selectedFontName.value,
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5),
|
||||||
onChanged: (newFont) => controller.richTextMobileTabletController.applyNewFontStyle(newFont),
|
icon: buildIconColorBackgroundTextWithoutTooltip(
|
||||||
onMenuStateChange: (isOpen) {
|
iconData: RichTextStyleType.textColor.getIconData(),
|
||||||
final newStatus = isOpen
|
colorSelected: controller.richTextMobileTabletController.selectedTextColor.value,
|
||||||
? DropdownMenuFontStatus.open
|
),
|
||||||
: DropdownMenuFontStatus.closed;
|
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, RichTextStyleType.textColor)),
|
||||||
controller.richTextMobileTabletController.menuFontStatus = newStatus;
|
),
|
||||||
},
|
Padding(
|
||||||
heightItem: 35,
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
sizeIconChecked: 16,
|
child: buildWrapIconStyleText(
|
||||||
radiusButton: 5,
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5),
|
||||||
supportSelectionIcon: true));
|
icon: buildIconColorBackgroundTextWithoutTooltip(
|
||||||
default:
|
iconData: RichTextStyleType.textBackgroundColor.getIconData(),
|
||||||
return buildIconStyleText(
|
colorSelected: controller.richTextMobileTabletController.selectedTextBackgroundColor.value,
|
||||||
path: textType.getIcon(imagePaths),
|
),
|
||||||
isSelected: controller.richTextMobileTabletController.isTextStyleTypeSelected(textType),
|
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, RichTextStyleType.textBackgroundColor)),
|
||||||
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, textType));
|
),
|
||||||
}
|
buildWrapIconStyleText(
|
||||||
})).toList()
|
hasDropdown: false,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 5),
|
||||||
|
icon: Wrap(children: [
|
||||||
|
buildIconStyleText(
|
||||||
|
path: RichTextStyleType.bold.getIcon(imagePaths),
|
||||||
|
isSelected: controller.richTextMobileTabletController.isTextStyleTypeSelected(RichTextStyleType.bold),
|
||||||
|
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, RichTextStyleType.bold)),
|
||||||
|
buildIconStyleText(
|
||||||
|
path: RichTextStyleType.italic.getIcon(imagePaths),
|
||||||
|
isSelected: controller.richTextMobileTabletController.isTextStyleTypeSelected(RichTextStyleType.italic),
|
||||||
|
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, RichTextStyleType.italic)),
|
||||||
|
buildIconStyleText(
|
||||||
|
path: RichTextStyleType.underline.getIcon(imagePaths),
|
||||||
|
isSelected: controller.richTextMobileTabletController.isTextStyleTypeSelected(RichTextStyleType.underline),
|
||||||
|
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, RichTextStyleType.underline)),
|
||||||
|
buildIconStyleText(
|
||||||
|
path: RichTextStyleType.strikeThrough.getIcon(imagePaths),
|
||||||
|
isSelected: controller.richTextMobileTabletController.isTextStyleTypeSelected(RichTextStyleType.strikeThrough),
|
||||||
|
onTap: () => controller.richTextMobileTabletController.applyRichTextStyle(context, RichTextStyleType.strikeThrough))
|
||||||
|
])),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -592,14 +612,17 @@ class ComposerView extends GetWidget<ComposerController> with AppLoaderMixin, Ri
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHtmlEditor(String initialContent) {
|
Widget _buildHtmlEditor(String initialContent) {
|
||||||
return HtmlEditor(
|
return Padding(
|
||||||
key: const Key('composer_editor'),
|
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 20, top: 10),
|
||||||
minHeight: 550,
|
child: HtmlEditor(
|
||||||
initialContent: initialContent,
|
key: const Key('composer_editor'),
|
||||||
onCreated: (editorApi) {
|
minHeight: 550,
|
||||||
controller.richTextMobileTabletController.htmlEditorApi = editorApi;
|
initialContent: initialContent,
|
||||||
controller.richTextMobileTabletController.listenHtmlEditorApi();
|
onCreated: (editorApi) {
|
||||||
});
|
controller.richTextMobileTabletController.htmlEditorApi = editorApi;
|
||||||
|
controller.richTextMobileTabletController.listenHtmlEditorApi();
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAttachmentsTitle(
|
Widget _buildAttachmentsTitle(
|
||||||
|
|||||||
@@ -80,6 +80,33 @@ mixin RichTextButtonMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildIcon({
|
||||||
|
required String path,
|
||||||
|
Color? color,
|
||||||
|
double opacity = 1.0,
|
||||||
|
}){
|
||||||
|
final newColor = color == Colors.white
|
||||||
|
? AppColor.colorDefaultRichTextButton
|
||||||
|
: color;
|
||||||
|
|
||||||
|
return SvgPicture.asset(path,
|
||||||
|
color: newColor?.withOpacity(opacity),
|
||||||
|
fit: BoxFit.fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildIconColorBackgroundTextWithoutTooltip({
|
||||||
|
required IconData? iconData,
|
||||||
|
required Color? colorSelected,
|
||||||
|
double opacity = 1.0,
|
||||||
|
}){
|
||||||
|
final newColor = colorSelected == Colors.white
|
||||||
|
? AppColor.colorDefaultRichTextButton
|
||||||
|
: colorSelected;
|
||||||
|
return Icon(iconData,
|
||||||
|
color: (newColor ?? AppColor.colorDefaultRichTextButton).withOpacity(opacity),
|
||||||
|
size: 20);
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildIconColorBackgroundText({
|
Widget buildIconColorBackgroundText({
|
||||||
required IconData? iconData,
|
required IconData? iconData,
|
||||||
required Color? colorSelected,
|
required Color? colorSelected,
|
||||||
|
|||||||
Reference in New Issue
Block a user