From 25b26acc42988aa6278527b209d74dd179fd7d74 Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 27 Sep 2023 19:11:43 +0700 Subject: [PATCH] TF-2118 Handle drop n drop image as inline (cherry picked from commit 2eb6ad6a05224b3caafc59565fd5fa6bdd819870) --- .../extensions/media_type_extension.dart | 2 + .../presentation/composer_controller.dart | 57 +++++++++++++++++++ .../presentation/composer_view_web.dart | 27 +++++++++ .../controller/rich_text_web_controller.dart | 12 ++++ .../view/web/web_editor_view.dart | 16 ++++++ .../widgets/web/web_editor_widget.dart | 10 +++- 6 files changed, 123 insertions(+), 1 deletion(-) diff --git a/core/lib/domain/extensions/media_type_extension.dart b/core/lib/domain/extensions/media_type_extension.dart index 79e719af4..c2a331d2c 100644 --- a/core/lib/domain/extensions/media_type_extension.dart +++ b/core/lib/domain/extensions/media_type_extension.dart @@ -9,6 +9,8 @@ extension MediaTypeExtension on MediaType { bool isImageFile() => SupportedPreviewFileTypes.imageMimeTypes.contains(mimeType); + bool isImageValid() => type == 'image'; + bool isDocFile() => SupportedPreviewFileTypes.docMimeTypes.contains(mimeType); bool isPowerPointFile() => SupportedPreviewFileTypes.pptMimeTypes.contains(mimeType); diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 7583a7b26..75b5b069c 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:html_editor_enhanced/html_editor.dart' as web_html_editor; import 'package:http_parser/http_parser.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; @@ -1838,6 +1839,62 @@ class ComposerController extends BaseController { onEditorFocusChange(true); } + void handleImageUploadSuccess( + BuildContext context, + web_html_editor.FileUpload fileUpload + ) { + log('ComposerController::handleImageUploadSuccess:NAME: ${fileUpload.name} | TYPE: ${fileUpload.type} | SIZE: ${fileUpload.size}'); + if (fileUpload.base64 == null) { + _appToast.showToastErrorMessage( + context, + AppLocalizations.of(context).can_not_upload_this_file_as_attachments + ); + return; + } + + if (fileUpload.type == null) { + return; + } + + final mediaType = MediaType.parse(fileUpload.type!); + if (mediaType.isImageValid()) { + _addInlineImageFromDragAndDrop( + base64Data: fileUpload.base64!, + name: fileUpload.name, + type: mediaType, + size: fileUpload.size, + ); + } + } + + void _addInlineImageFromDragAndDrop({ + required String base64Data, + String? name, + MediaType? type, + int? size + }) { + log('ComposerController::_addInlineImageFromDragAndDrop:name: $name'); + richTextWebController.insertInlineImage( + base64Data: base64Data, + name: name, + type: type, + size: size, + ); + } + + void handleImageUploadFailure({ + required BuildContext context, + required web_html_editor.UploadError uploadError, + web_html_editor.FileUpload? fileUpload, + String? base64Str, + }) { + logError('ComposerController::handleImageUploadFailure:fileUpload: $fileUpload | uploadError: $uploadError'); + _appToast.showToastErrorMessage( + context, + '${AppLocalizations.of(context).can_not_upload_this_file_as_attachments}. (${uploadError.name})' + ); + } + FocusNode? getNextFocusOfToEmailAddress() { if (ccRecipientState.value == PrefixRecipientState.enabled) { return ccAddressFocusNode; diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index d0ed0815d..a6457b483 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -161,6 +161,15 @@ class ComposerView extends GetWidget { onUnFocus: controller.handleOnUnFocusHtmlEditorWeb, onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, onEditorSettings: controller.richTextWebController.onEditorSettingsChange, + onImageUploadSuccessAction: (fileUpload) => controller.handleImageUploadSuccess(context, fileUpload), + onImageUploadFailureAction: (fileUpload, base64Str, uploadError) { + return controller.handleImageUploadFailure( + context: context, + uploadError: uploadError, + fileUpload: fileUpload, + base64Str: base64Str, + ); + } ), ), Obx(() => Align( @@ -330,6 +339,15 @@ class ComposerView extends GetWidget { onUnFocus: controller.handleOnUnFocusHtmlEditorWeb, onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, onEditorSettings: controller.richTextWebController.onEditorSettingsChange, + onImageUploadSuccessAction: (fileUpload) => controller.handleImageUploadSuccess(context, fileUpload), + onImageUploadFailureAction: (fileUpload, base64Str, uploadError) { + return controller.handleImageUploadFailure( + context: context, + uploadError: uploadError, + fileUpload: fileUpload, + base64Str: base64Str, + ); + } ), ), )), @@ -523,6 +541,15 @@ class ComposerView extends GetWidget { onUnFocus: controller.handleOnUnFocusHtmlEditorWeb, onMouseDown: controller.handleOnMouseDownHtmlEditorWeb, onEditorSettings: controller.richTextWebController.onEditorSettingsChange, + onImageUploadSuccessAction: (fileUpload) => controller.handleImageUploadSuccess(context, fileUpload), + onImageUploadFailureAction: (fileUpload, base64Str, uploadError) { + return controller.handleImageUploadFailure( + context: context, + uploadError: uploadError, + fileUpload: fileUpload, + base64Str: base64Str, + ); + } ), ), )), 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 4ff2749c1..26a1a3ed9 100644 --- a/lib/features/composer/presentation/controller/rich_text_web_controller.dart +++ b/lib/features/composer/presentation/controller/rich_text_web_controller.dart @@ -8,6 +8,7 @@ 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'; +import 'package:http_parser/http_parser.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/code_view_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/dropdown_menu_font_status.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/formatting_options_state.dart'; @@ -185,6 +186,17 @@ class RichTextWebController extends BaseRichTextController { } } + void insertInlineImage({ + required String base64Data, + String? name, + MediaType? type, + int? size, + }) async { + log('RichTextWebController::insertInlineImage():name: $name | type: $type'); + final base64Uri = 'Inline Image TeamMail'; + editorController.insertHtml(base64Uri); + } + void applyNewFontStyle(FontNameType? newFont) { final fontSelected = newFont ?? FontNameType.sansSerif; selectedFontName.value = fontSelected; diff --git a/lib/features/composer/presentation/view/web/web_editor_view.dart b/lib/features/composer/presentation/view/web/web_editor_view.dart index 94bf1222d..ff7727b78 100644 --- a/lib/features/composer/presentation/view/web/web_editor_view.dart +++ b/lib/features/composer/presentation/view/web/web_editor_view.dart @@ -26,6 +26,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { final VoidCallback? onUnFocus; final OnMouseDownEditorAction? onMouseDown; final OnEditorSettingsChange? onEditorSettings; + final OnImageUploadSuccessAction? onImageUploadSuccessAction; + final OnImageUploadFailureAction? onImageUploadFailureAction; const WebEditorView({ super.key, @@ -39,6 +41,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { this.onUnFocus, this.onMouseDown, this.onEditorSettings, + this.onImageUploadSuccessAction, + this.onImageUploadFailureAction, }); @override @@ -61,6 +65,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onUnFocus: onUnFocus, onMouseDown: onMouseDown, onEditorSettings: onEditorSettings, + onImageUploadSuccessAction: onImageUploadSuccessAction, + onImageUploadFailureAction: onImageUploadFailureAction, ); case EmailActionType.editDraft: case EmailActionType.editSendingEmail: @@ -80,6 +86,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onUnFocus: onUnFocus, onMouseDown: onMouseDown, onEditorSettings: onEditorSettings, + onImageUploadSuccessAction: onImageUploadSuccessAction, + onImageUploadFailureAction: onImageUploadFailureAction, ), (success) { if (success is GetEmailContentLoading) { @@ -101,6 +109,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onUnFocus: onUnFocus, onMouseDown: onMouseDown, onEditorSettings: onEditorSettings, + onImageUploadSuccessAction: onImageUploadSuccessAction, + onImageUploadFailureAction: onImageUploadFailureAction, ); } } @@ -129,6 +139,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onUnFocus: onUnFocus, onMouseDown: onMouseDown, onEditorSettings: onEditorSettings, + onImageUploadSuccessAction: onImageUploadSuccessAction, + onImageUploadFailureAction: onImageUploadFailureAction, ); }, (success) { @@ -153,6 +165,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onUnFocus: onUnFocus, onMouseDown: onMouseDown, onEditorSettings: onEditorSettings, + onImageUploadSuccessAction: onImageUploadSuccessAction, + onImageUploadFailureAction: onImageUploadFailureAction, ); } } @@ -168,6 +182,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onUnFocus: onUnFocus, onMouseDown: onMouseDown, onEditorSettings: onEditorSettings, + onImageUploadSuccessAction: onImageUploadSuccessAction, + onImageUploadFailureAction: onImageUploadFailureAction, ); } } diff --git a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart index 354cdba5c..f2dd73275 100644 --- a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart +++ b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart @@ -8,6 +8,8 @@ typedef OnChangeContentEditorAction = Function(String? text); typedef OnInitialContentEditorAction = Function(String text); typedef OnMouseDownEditorAction = Function(BuildContext context); typedef OnEditorSettingsChange = Function(EditorSettings settings); +typedef OnImageUploadSuccessAction = Function(FileUpload fileUpload); +typedef OnImageUploadFailureAction = Function(FileUpload? fileUpload, String? base64Str, UploadError error); class WebEditorWidget extends StatefulWidget { @@ -20,6 +22,8 @@ class WebEditorWidget extends StatefulWidget { final VoidCallback? onUnFocus; final OnMouseDownEditorAction? onMouseDown; final OnEditorSettingsChange? onEditorSettings; + final OnImageUploadSuccessAction? onImageUploadSuccessAction; + final OnImageUploadFailureAction? onImageUploadFailureAction; const WebEditorWidget({ super.key, @@ -32,6 +36,8 @@ class WebEditorWidget extends StatefulWidget { this.onUnFocus, this.onMouseDown, this.onEditorSettings, + this.onImageUploadSuccessAction, + this.onImageUploadFailureAction, }); @override @@ -82,7 +88,9 @@ class _WebEditorState extends State { onBlur: widget.onUnFocus, onMouseDown: () => widget.onMouseDown?.call(context), onChangeSelection: widget.onEditorSettings, - onChangeCodeview: widget.onChangeContent + onChangeCodeview: widget.onChangeContent, + onImageUpload: widget.onImageUploadSuccessAction, + onImageUploadError: widget.onImageUploadFailureAction, ), ); }