diff --git a/assets/images/ic_add_picture.svg b/assets/images/ic_add_picture.svg
new file mode 100644
index 000000000..9db70fafc
--- /dev/null
+++ b/assets/images/ic_add_picture.svg
@@ -0,0 +1,5 @@
+
diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart
index a58751a4b..39bc83c9e 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -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;
diff --git a/lib/features/composer/presentation/controller/rich_text_web_controller.dart b/lib/features/composer/presentation/controller/rich_text_web_controller.dart
index ccf47f7df..b35b34c4b 100644
--- a/lib/features/composer/presentation/controller/rich_text_web_controller.dart
+++ b/lib/features/composer/presentation/controller/rich_text_web_controller.dart
@@ -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(
+ '
'
+ );
+ } else {
+ logError("RichTextWebController::insertImageAsBase64: bytes is null");
+ }
+ }
+
@override
void onClose() {
menuParagraphController.dispose();
diff --git a/lib/features/composer/presentation/widgets/toolbar_rich_text_builder.dart b/lib/features/composer/presentation/widgets/toolbar_rich_text_builder.dart
index ebfc74875..a69618772 100644
--- a/lib/features/composer/presentation/widgets/toolbar_rich_text_builder.dart
+++ b/lib/features/composer/presentation/widgets/toolbar_rich_text_builder.dart
@@ -22,11 +22,13 @@ class ToolbarRichTextWebBuilder extends StatelessWidget with RichTextButtonMixin
final RichTextWebController richTextWebController;
final ImagePaths _imagePaths = Get.find();
final EdgeInsetsGeometry? padding;
+ final List? 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(
diff --git a/lib/features/identity_creator/presentation/extesions/size_extension.dart b/lib/features/identity_creator/presentation/extesions/size_extension.dart
new file mode 100644
index 000000000..21c56f87d
--- /dev/null
+++ b/lib/features/identity_creator/presentation/extesions/size_extension.dart
@@ -0,0 +1,5 @@
+
+extension SizeExtension on int {
+
+ int get kiloByteToBytes => this * 1024;
+}
\ No newline at end of file
diff --git a/lib/features/identity_creator/presentation/identity_creator_controller.dart b/lib/features/identity_creator/presentation/identity_creator_controller.dart
index 97876538c..2af63fcd4 100644
--- a/lib/features/identity_creator/presentation/identity_creator_controller.dart
+++ b/lib/features/identity_creator/presentation/identity_creator_controller.dart
@@ -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();
+ final _appToast = Get.find();
final noneEmailAddress = EmailAddress(null, 'None');
final listEmailAddressDefault = [].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);
+ }
+ }
}
\ No newline at end of file
diff --git a/lib/features/identity_creator/presentation/identity_creator_view.dart b/lib/features/identity_creator/presentation/identity_creator_view.dart
index dae98848b..b0dda3957 100644
--- a/lib/features/identity_creator/presentation/identity_creator_view.dart
+++ b/lib/features/identity_creator/presentation/identity_creator_view.dart
@@ -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 {
+class IdentityCreatorView extends GetWidget
+ with RichTextButtonMixin {
final _imagePaths = Get.find();
final _responsiveUtils = Get.find();
@@ -163,7 +164,7 @@ class IdentityCreatorView extends GetWidget {
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 {
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 {
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 {
}
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,
diff --git a/lib/features/identity_creator/presentation/utils/identity_creator_constants.dart b/lib/features/identity_creator/presentation/utils/identity_creator_constants.dart
new file mode 100644
index 000000000..8d7a6ccfb
--- /dev/null
+++ b/lib/features/identity_creator/presentation/utils/identity_creator_constants.dart
@@ -0,0 +1,4 @@
+
+class IdentityCreatorConstants {
+ static const int maxSizeIdentityInlineImage = 16; // Kilobyte
+}
\ No newline at end of file
diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb
index 6bb4b653e..b5af62273 100644
--- a/lib/l10n/intl_messages.arb
+++ b/lib/l10n/intl_messages.arb
@@ -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": {}
+ }
}
}
\ No newline at end of file
diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart
index a1f5d7799..98215ff52 100644
--- a/lib/main/localizations/app_localizations.dart
+++ b/lib/main/localizations/app_localizations.dart
@@ -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]);
+ }
}
\ No newline at end of file