Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HOT-FIX] Fix cannot copy & paste base64 image as inline image in composer web #3097

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions lib/features/composer/presentation/composer_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
Expand Down Expand Up @@ -86,6 +87,7 @@ import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_alway
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
import 'package:tmail_ui_user/features/upload/domain/extensions/file_info_extension.dart';
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_upload_extension.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
Expand Down Expand Up @@ -2136,11 +2138,7 @@ class ComposerController extends BaseController with DragDropFileMixin implement
required DropDoneDetails details,
required double maxWidth
}) async {
if (responsiveUtils.isMobile(context)) {
maxWithEditor = maxWidth - 40;
} else {
maxWithEditor = maxWidth - 70;
}
_setUpMaxWidthInlineImage(context: context, maxWidth: maxWidth);

final listFileInfo = await onDragDone(context: context, details: details);

Expand All @@ -2152,13 +2150,9 @@ class ComposerController extends BaseController with DragDropFileMixin implement
return;
}

final listAttachments = listFileInfo
.where((fileInfo) => fileInfo.isInline != true)
.toList();

uploadController.validateTotalSizeAttachmentsBeforeUpload(
totalSizePreparedFiles: listFileInfo.totalSize,
totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
totalSizePreparedFilesWithDispositionAttachment: listFileInfo.listAttachmentFiles.totalSize,
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
);
}
Expand Down Expand Up @@ -2341,4 +2335,43 @@ class ComposerController extends BaseController with DragDropFileMixin implement
await _saveComposerCacheOnWebAction();
}
}

void _setUpMaxWidthInlineImage({
required BuildContext context,
required double maxWidth
}) {
if (responsiveUtils.isMobile(context)) {
maxWithEditor = maxWidth - 40;
} else {
maxWithEditor = maxWidth - 70;
}
}

void handleOnPasteImageSuccessAction({
required BuildContext context,
required double maxWidth,
required List<FileUpload> listFileUpload
}) {
log('ComposerController::handleOnPasteImageSuccessAction: listFileUpload = ${listFileUpload.length}');
_setUpMaxWidthInlineImage(context: context, maxWidth: maxWidth);

final listFileInfo = listFileUpload.toListFileInfo();

uploadController.validateTotalSizeAttachmentsBeforeUpload(
totalSizePreparedFiles: listFileInfo.totalSize,
onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
);
}

void handleOnPasteImageFailureAction({
required BuildContext context,
List<FileUpload>? listFileUpload,
String? base64,
required UploadError uploadError
}) {
logError('ComposerController::handleOnPasteImageFailureAction: $uploadError');
appToast.showToastErrorMessage(
context,
AppLocalizations.of(context).thisImageCannotBePastedIntoTheEditor);
}
}
36 changes: 36 additions & 0 deletions lib/features/composer/presentation/composer_view_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,18 @@ class ComposerView extends GetWidget<ComposerController> {
width: constraints.maxWidth,
height: constraints.maxHeight,
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
context: context,
maxWidth: constraintsEditor.maxWidth,
listFileUpload: listFileUpload
),
onPasteImageFailureAction: (listFileUpload, base64, uploadError) =>
controller.handleOnPasteImageFailureAction(
context: context,
listFileUpload: listFileUpload,
base64: base64,
uploadError: uploadError
),
)),
),
),
Expand Down Expand Up @@ -430,6 +442,18 @@ class ComposerView extends GetWidget<ComposerController> {
width: constraints.maxWidth,
height: constraints.maxHeight,
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
context: context,
maxWidth: constraintsEditor.maxWidth,
listFileUpload: listFileUpload
),
onPasteImageFailureAction: (listFileUpload, base64, uploadError) =>
controller.handleOnPasteImageFailureAction(
context: context,
listFileUpload: listFileUpload,
base64: base64,
uploadError: uploadError
),
);
}),
),
Expand Down Expand Up @@ -688,6 +712,18 @@ class ComposerView extends GetWidget<ComposerController> {
width: constraints.maxWidth,
height: constraints.maxHeight,
onDragEnter: controller.handleOnDragEnterHtmlEditorWeb,
onPasteImageSuccessAction: (listFileUpload) => controller.handleOnPasteImageSuccessAction(
context: context,
maxWidth: constraintsBody.maxWidth,
listFileUpload: listFileUpload
),
onPasteImageFailureAction: (listFileUpload, base64, uploadError) =>
controller.handleOnPasteImageFailureAction(
context: context,
listFileUpload: listFileUpload,
base64: base64,
uploadError: uploadError
),
)),
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import 'dart:convert' as convert;
import 'dart:typed_data' as type_data;

import 'package:flutter/foundation.dart';
import 'package:html_editor_enhanced/utils/file_upload_model.dart';
import 'package:model/upload/file_info.dart';

Expand All @@ -23,22 +20,17 @@ extension FileUploadExtension on FileUpload {
return base64;
}

Future<FileInfo?> toFileInfo() async {
FileInfo? toFileInfo() {
if (base64Data != null) {
final bytes = await compute(convertBase64ToBytes, base64Data!);
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
return FileInfo.fromBytes(
bytes: bytes,
bytes: convert.base64Decode(base64Data!),
name: name,
size: size,
type: type,
isInline: true
);
} else {
return null;
}
}

static Uint8List convertBase64ToBytes(String base64) {
type_data.Uint8List decodeBytes = convert.base64Decode(base64);
return decodeBytes;
}
}
18 changes: 16 additions & 2 deletions lib/features/composer/presentation/view/web/web_editor_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_c
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';

typedef OnDragEnterListener = Function(List<dynamic>? types);

class WebEditorView extends StatelessWidget with EditorViewMixin {

final HtmlEditorController editorController;
Expand All @@ -33,6 +31,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
final double? width;
final double? height;
final OnDragEnterListener? onDragEnter;
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
final OnPasteImageFailureAction? onPasteImageFailureAction;

const WebEditorView({
super.key,
Expand All @@ -50,6 +50,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
this.width,
this.height,
this.onDragEnter,
this.onPasteImageSuccessAction,
this.onPasteImageFailureAction,
});

@override
Expand All @@ -76,6 +78,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
width: width,
height: height,
onDragEnter: onDragEnter,
onPasteImageSuccessAction: onPasteImageSuccessAction,
onPasteImageFailureAction: onPasteImageFailureAction,
);
case EmailActionType.editDraft:
case EmailActionType.editSendingEmail:
Expand All @@ -101,6 +105,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
width: width,
height: height,
onDragEnter: onDragEnter,
onPasteImageSuccessAction: onPasteImageSuccessAction,
onPasteImageFailureAction: onPasteImageFailureAction,
),
(success) {
if (success is GetEmailContentLoading || success is RestoringEmailInlineImages) {
Expand All @@ -126,6 +132,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
width: width,
height: height,
onDragEnter: onDragEnter,
onPasteImageSuccessAction: onPasteImageSuccessAction,
onPasteImageFailureAction: onPasteImageFailureAction,
);
}
}
Expand Down Expand Up @@ -158,6 +166,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
width: width,
height: height,
onDragEnter: onDragEnter,
onPasteImageSuccessAction: onPasteImageSuccessAction,
onPasteImageFailureAction: onPasteImageFailureAction,
);
},
(success) {
Expand Down Expand Up @@ -186,6 +196,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
width: width,
height: height,
onDragEnter: onDragEnter,
onPasteImageSuccessAction: onPasteImageSuccessAction,
onPasteImageFailureAction: onPasteImageFailureAction,
);
}
}
Expand All @@ -205,6 +217,8 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
width: width,
height: height,
onDragEnter: onDragEnter,
onPasteImageSuccessAction: onPasteImageSuccessAction,
onPasteImageFailureAction: onPasteImageFailureAction,
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import 'package:core/utils/app_logger.dart';
import 'package:core/utils/html/html_utils.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/web/web_editor_view.dart';
import 'package:universal_html/html.dart' hide VoidCallback;

typedef OnChangeContentEditorAction = Function(String? text);
typedef OnInitialContentEditorAction = Function(String text);
typedef OnMouseDownEditorAction = Function(BuildContext context);
typedef OnEditorSettingsChange = Function(EditorSettings settings);
typedef OnEditorTextSizeChanged = Function(int? size);
typedef OnDragEnterListener = Function(List<dynamic>? types);
typedef OnPasteImageSuccessAction = Function(List<FileUpload> listFileUpload);
typedef OnPasteImageFailureAction = Function(
List<FileUpload>? listFileUpload,
String? base64,
UploadError uploadError);

class WebEditorWidget extends StatefulWidget {

Expand All @@ -29,6 +34,8 @@ class WebEditorWidget extends StatefulWidget {
final double? width;
final double? height;
final OnDragEnterListener? onDragEnter;
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
final OnPasteImageFailureAction? onPasteImageFailureAction;

const WebEditorWidget({
super.key,
Expand All @@ -45,6 +52,8 @@ class WebEditorWidget extends StatefulWidget {
this.width,
this.height,
this.onDragEnter,
this.onPasteImageSuccessAction,
this.onPasteImageFailureAction,
});

@override
Expand Down Expand Up @@ -179,6 +188,8 @@ class _WebEditorState extends State<WebEditorWidget> {
),
onDragEnter: widget.onDragEnter,
onDragLeave: (_) {},
onImageUpload: widget.onPasteImageSuccessAction,
onImageUploadError: widget.onPasteImageFailureAction,
),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import 'package:model/upload/file_info.dart';

extension ListFileInfoExtension on List<FileInfo> {
Expand All @@ -7,4 +6,6 @@ extension ListFileInfoExtension on List<FileInfo> {
num get totalSize => listSize.isEmpty ? 0 : listSize.reduce((sum, size) => sum + size);

List<FileInfo> get listInlineFiles => where((fileInfo) => fileInfo.isInline == true).toList();

List<FileInfo> get listAttachmentFiles => where((fileInfo) => fileInfo.isInline != true).toList();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:collection/collection.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/file_upload_extension.dart';

extension ListFileUploadExtension on List<FileUpload> {
List<FileInfo> toListFileInfo() => map((fileUpload) => fileUpload.toFileInfo()).whereNotNull().toList();
}
8 changes: 7 additions & 1 deletion lib/l10n/intl_messages.arb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"@@last_modified": "2024-08-20T10:36:35.487743",
"@@last_modified": "2024-08-19T19:20:23.876948",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
Expand Down Expand Up @@ -3987,5 +3987,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"thisImageCannotBePastedIntoTheEditor": "This image cannot be pasted into the editor.",
"@thisImageCannotBePastedIntoTheEditor": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
10 changes: 8 additions & 2 deletions lib/main/localizations/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4159,13 +4159,13 @@ class AppLocalizations {
}

String get emptySpamFolderFailed {
return Intl.message(''
return Intl.message(
'Empty spam folder failed',
name: 'emptySpamFolderFailed');
}

String get markAsSpamFailed {
return Intl.message(''
return Intl.message(
'Mark as spam failed',
name: 'markAsSpamFailed');
}
Expand All @@ -4176,4 +4176,10 @@ class AppLocalizations {
name: 'canNotUploadFileToSignature'
);
}

String get thisImageCannotBePastedIntoTheEditor {
return Intl.message(
'This image cannot be pasted into the editor.',
name: 'thisImageCannotBePastedIntoTheEditor');
}
}
4 changes: 3 additions & 1 deletion model/lib/upload/file_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ class FileInfo with EquatableMixin {
String? name,
int? size,
String? type,
bool? isInline,
}) {
return FileInfo(
fileName: name ?? '',
fileSize: size ?? 0,
bytes: bytes,
type: type
type: type,
isInline: isInline,
);
}

Expand Down
Loading
Loading