TF-1486 Add insert image in signature on web

(cherry picked from commit 50f0e54b2a47fe2e857372f56293d1c1800ded2b)
This commit is contained in:
dab246
2023-07-12 19:04:32 +07:00
committed by Dat Vu
parent 2f4f8beec4
commit d072f6a243
10 changed files with 115 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 6C16 2.68629 18.6863 0 22 0C25.3137 0 28 2.68629 28 6C28 9.31371 25.3137 12 22 12C18.6863 12 16 9.31371 16 6ZM21 3C21 2.44772 21.4477 2 22 2C22.5523 2 23 2.44772 23 3V5H25C25.5523 5 26 5.44772 26 6C26 6.55228 25.5523 7 25 7H23V9C23 9.55229 22.5523 10 22 10C21.4477 10 21 9.55229 21 9V7H19C18.4477 7 18 6.55228 18 6C18 5.44771 18.4477 5 19 5H21V3Z" fill="#99A2AD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 4C15 3.44772 14.5523 3 14 3H11.6917C9.10224 3 7.87375 3.23723 6.59786 3.91958C5.44582 4.53571 4.53571 5.44582 3.91958 6.59786C3.23723 7.87375 3 9.10224 3 11.6917V16.3083C3 18.8978 3.23723 20.1262 3.91958 21.4021C4.53571 22.5542 5.44582 23.4643 6.59786 24.0804C7.87375 24.7628 9.10224 25 11.6917 25H16.3083C18.8978 25 20.1262 24.7628 21.4021 24.0804C22.5542 23.4643 23.4643 22.5542 24.0804 21.4021C24.7628 20.1262 25 18.8978 25 16.3083V14C25 13.4477 24.5523 13 24 13C23.4477 13 23 13.4477 23 14V16.3083C23 16.6356 22.9964 16.9349 22.9887 17.2102L18.5236 13.2517C18.0972 12.8738 17.4417 12.9278 17.0831 13.3706L12.8612 18.5826L10.6402 16.7318C10.2429 16.4007 9.65861 16.4272 9.29289 16.7929L5.66305 20.4209C5.17229 19.4876 5 18.5652 5 16.3083V11.6917C5 9.40417 5.177 8.4876 5.68321 7.54106C6.11293 6.73755 6.73755 6.11293 7.54106 5.68321C8.4876 5.177 9.40417 5 11.6917 5H14C14.5523 5 15 4.55228 15 4ZM13.7771 20.6294L17.9791 15.4419L22.6781 19.6082C22.5829 19.9121 22.4637 20.1842 22.3168 20.4589C21.8871 21.2624 21.2624 21.8871 20.4589 22.3168C19.5124 22.823 18.5958 23 16.3083 23H11.6917C9.40417 23 8.4876 22.823 7.54106 22.3168C7.33681 22.2076 7.14411 22.0857 6.96323 21.9516L10.0614 18.8528L12.3598 20.7682C12.7883 21.1253 13.426 21.0628 13.7771 20.6294Z" fill="#99A2AD"/>
<path d="M12 10.5C12 9.67157 11.3284 9 10.5 9C9.67157 9 9 9.67157 9 10.5C9 11.3284 9.67157 12 10.5 12C11.3284 12 12 11.3284 12 10.5Z" fill="#99A2AD"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -193,6 +193,7 @@ class ImagePaths {
String get icArrowBottom => _getImagePath('ic_arrow_bottom.svg');
String get icArrowLeft => _getImagePath('ic_arrow_left.svg');
String get icArrowRight => _getImagePath('ic_arrow_right.svg');
String get icAddPicture => _getImagePath('ic_add_picture.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
@@ -1,7 +1,10 @@
import 'dart:convert';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:custom_pop_up_menu/custom_pop_up_menu.dart';
import 'package:core/utils/app_logger.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart';
@@ -257,6 +260,17 @@ class RichTextWebController extends BaseRichTextController {
menuOrderListController.hideMenu();
}
void insertImageAsBase64({required PlatformFile platformFile}) {
if (platformFile.bytes != null) {
final base64Data = base64Encode(platformFile.bytes!);
editorController.insertHtml(
'<img src="data:image/${platformFile.extension};base64,$base64Data" data-filename="${platformFile.name}" alt="Image in my signature" style="max-width: 100%"/>'
);
} else {
logError("RichTextWebController::insertImageAsBase64: bytes is null");
}
}
@override
void onClose() {
menuParagraphController.dispose();
@@ -22,11 +22,13 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin
final RichTextWebController richTextWebController;
final ImagePaths _imagePaths = Get.find<ImagePaths>();
final EdgeInsetsGeometry? padding;
final List<Widget>? extendedOption;
ToolbarRichTextWebBuilder({
Key? key,
required this.richTextWebController,
this.padding,
this.extendedOption,
}) : super(key: key);
@override
@@ -42,6 +44,8 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin
crossAxisAlignment: WrapCrossAlignment.center,
runSpacing: 8,
children: [
if (extendedOption?.isNotEmpty == true)
...extendedOption!,
AbsorbPointer(
absorbing: codeViewEnabled,
child: DropDownMenuHeaderStyleWidget(
@@ -0,0 +1,5 @@
extension SizeExtension on int {
int get kiloByteToBytes => this * 1024;
}
@@ -1,9 +1,10 @@
import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/presentation/utils/keyboard_utils.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
@@ -20,7 +21,9 @@ import 'package:model/user/user_profile.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/extesions/size_extension.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/identity_creator_arguments.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/utils/identity_creator_constants.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/email_address_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/empty_name_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart';
@@ -34,6 +37,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/extensions/id
import 'package:tmail_ui_user/features/manage_account/presentation/model/identity_action_type.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/utils/identity_utils.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:uuid/uuid.dart';
@@ -44,6 +48,7 @@ class IdentityCreatorController extends BaseController {
final IdentityUtils _identityUtils;
final _uuid = Get.find<Uuid>();
final _appToast = Get.find<AppToast>();
final noneEmailAddress = EmailAddress(null, 'None');
final listEmailAddressDefault = <EmailAddress>[].obs;
@@ -302,12 +307,14 @@ class IdentityCreatorController extends BaseController {
final error = _getErrorInputNameString(context);
if (error?.isNotEmpty == true) {
errorNameIdentity.value = error;
inputNameIdentityFocusNode.requestFocus();
return;
}
final errorBcc = _getErrorInputAddressString(context);
if (errorBcc?.isNotEmpty == true) {
errorBccIdentity.value = errorBcc;
inputBccIdentityFocusNode.requestFocus();
return;
}
@@ -456,4 +463,41 @@ class IdentityCreatorController extends BaseController {
);
}
}
void pickImage(BuildContext context) async {
final filePickerResult = await FilePicker.platform.pickFiles(
type: FileType.image,
withData: true
);
if (context.mounted) {
final platformFile = filePickerResult?.files.single;
if (platformFile != null) {
_insertInlineImage(context, platformFile);
} else {
_appToast.showToastErrorMessage(
context,
AppLocalizations.of(context).cannotSelectThisImage
);
}
} else {
logError("IdentityCreatorController::pickImage: context is unmounted");
}
}
bool _isExceedMaxSizeInlineImage(int fileSize) =>
fileSize > IdentityCreatorConstants.maxSizeIdentityInlineImage.kiloByteToBytes;
void _insertInlineImage(BuildContext context, PlatformFile platformFile) {
if (_isExceedMaxSizeInlineImage(platformFile.size)) {
_appToast.showToastErrorMessage(
context,
AppLocalizations.of(context).pleaseChooseAnImageSizeCorrectly(
IdentityCreatorConstants.maxSizeIdentityInlineImage
)
);
} else {
richTextWebController.insertImageAsBase64(platformFile: platformFile);
}
}
}
@@ -8,7 +8,6 @@ import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/responsive/responsive_widget.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -18,6 +17,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/rich_text_button_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/toolbar_rich_text_builder.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_drop_list_field_builder.dart';
@@ -30,7 +30,8 @@ import 'package:tmail_ui_user/features/manage_account/presentation/model/identit
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
class IdentityCreatorView extends GetWidget<IdentityCreatorController>
with RichTextButtonMixin {
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
@@ -163,7 +164,7 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
controller.errorNameIdentity.value,
AppLocalizations.of(context).required,
editingController: controller.inputNameIdentityController,
focusNode: PlatformInfo.isWeb ? null : controller.inputNameIdentityFocusNode,
focusNode: controller.inputNameIdentityFocusNode,
isMandatory: true,
onChangeInputNameAction: (value) => controller.updateNameIdentity(context, value)
)),
@@ -195,7 +196,7 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
AppLocalizations.of(context).bcc_to,
controller.errorBccIdentity.value,
controller.inputBccIdentityController,
focusNode: PlatformInfo.isWeb ? null : controller.inputBccIdentityFocusNode,
focusNode: controller.inputBccIdentityFocusNode,
onSelectedSuggestionAction: (newEmailAddress) {
controller.inputBccIdentityController.text = newEmailAddress?.email ?? '';
controller.updateBccOfIdentity(newEmailAddress);
@@ -306,7 +307,20 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
if (PlatformInfo.isWeb)
ToolbarRichTextWebBuilder(
richTextWebController: controller.richTextWebController,
padding: const EdgeInsets.only(bottom: 12)
padding: const EdgeInsets.only(bottom: 12),
extendedOption: [
Padding(
padding: const EdgeInsetsDirectional.only(end: 4.0),
child: buildWrapIconStyleText(
icon: buildIconWithTooltip(
path: _imagePaths.icAddPicture,
tooltip: AppLocalizations.of(context).insertImage
),
hasDropdown: false,
onTap: () => controller.pickImage(context)
),
),
]
),
htmlEditor,
],
@@ -314,7 +328,6 @@ class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
}
Widget _buildHtmlEditorWeb(BuildContext context, String initContent) {
log('IdentityCreatorView::_buildHtmlEditorWeb(): initContent: $initContent');
return html_editor_browser.HtmlEditor(
key: const Key('identity_create_editor_web'),
controller: controller.richTextWebController.editorController,
@@ -0,0 +1,4 @@
class IdentityCreatorConstants {
static const int maxSizeIdentityInlineImage = 16; // Kilobyte
}
+11 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2023-07-07T16:07:02.053606",
"@@last_modified": "2023-07-12T18:53:48.611802",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -2965,5 +2965,15 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"pleaseChooseAnImageSizeCorrectly": "Please choose an image size <= {maxSize}KB",
"@pleaseChooseAnImageSizeCorrectly": {
"type": "text",
"placeholders_order": [
"maxSize"
],
"placeholders": {
"maxSize": {}
}
}
}
@@ -3064,4 +3064,11 @@ class AppLocalizations {
name: 'archiveMailboxDisplayName',
);
}
String pleaseChooseAnImageSizeCorrectly(int maxSize) {
return Intl.message(
'Please choose an image size <= ${maxSize}KB',
name: 'pleaseChooseAnImageSizeCorrectly',
args: [maxSize]);
}
}