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
@@ -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]);
}
}