edit UI rich text for design

This commit is contained in:
ManhNTX
2022-07-26 06:53:50 +07:00
committed by Dat H. Pham
parent b43d00d00e
commit f77fd7b709
3 changed files with 153 additions and 103 deletions
@@ -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,