From 8b20e9e1d453786ff67fa026e57fa116a16cc8a7 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 28 Sep 2023 11:12:34 +0700 Subject: [PATCH] TF-2118 Handle drop n drop file as attachment (cherry picked from commit cb1f15dcba46a15dbd5c5a8493593854855be331) --- .../presentation/composer_controller.dart | 41 ++++++++++++++++- .../extensions/file_upload_extension.dart | 45 +++++++++++++++++++ .../domain/model/upload_attachment.dart | 8 +++- .../domain/state/attachment_upload_state.dart | 9 ++-- .../controller/upload_controller.dart | 2 +- model/lib/upload/file_info.dart | 8 ++++ 6 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 lib/features/composer/presentation/extensions/file_upload_extension.dart diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 75b5b069c..47eda8425 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -49,6 +49,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/update_email_dra import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/file_upload_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart'; @@ -1839,10 +1840,10 @@ class ComposerController extends BaseController { onEditorFocusChange(true); } - void handleImageUploadSuccess( + void handleImageUploadSuccess ( BuildContext context, web_html_editor.FileUpload fileUpload - ) { + ) async { log('ComposerController::handleImageUploadSuccess:NAME: ${fileUpload.name} | TYPE: ${fileUpload.type} | SIZE: ${fileUpload.size}'); if (fileUpload.base64 == null) { _appToast.showToastErrorMessage( @@ -1853,6 +1854,15 @@ class ComposerController extends BaseController { } if (fileUpload.type == null) { + final fileInfo = await fileUpload.toFileInfo(); + if (fileInfo != null) { + _addAttachmentFromDragAndDrop(fileInfo: fileInfo); + } else if (context.mounted) { + _appToast.showToastErrorMessage( + context, + AppLocalizations.of(context).can_not_upload_this_file_as_attachments + ); + } return; } @@ -1864,6 +1874,16 @@ class ComposerController extends BaseController { type: mediaType, size: fileUpload.size, ); + } else { + final fileInfo = await fileUpload.toFileInfo(); + if (fileInfo != null) { + _addAttachmentFromDragAndDrop(fileInfo: fileInfo); + } else if (context.mounted) { + _appToast.showToastErrorMessage( + context, + AppLocalizations.of(context).can_not_upload_this_file_as_attachments + ); + } } } @@ -1895,6 +1915,23 @@ class ComposerController extends BaseController { ); } + void _addAttachmentFromDragAndDrop({required FileInfo fileInfo}) { + if (uploadController.hasEnoughMaxAttachmentSize(listFiles: [fileInfo])) { + _uploadAttachmentsAction([fileInfo]); + } else { + if (currentContext != null) { + showConfirmDialogAction( + currentContext!, + AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size( + filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), + AppLocalizations.of(currentContext!).got_it, + title: AppLocalizations.of(currentContext!).maximum_files_size, + hasCancelButton: false, + ); + } + } + } + FocusNode? getNextFocusOfToEmailAddress() { if (ccRecipientState.value == PrefixRecipientState.enabled) { return ccAddressFocusNode; diff --git a/lib/features/composer/presentation/extensions/file_upload_extension.dart b/lib/features/composer/presentation/extensions/file_upload_extension.dart new file mode 100644 index 000000000..55a9a3566 --- /dev/null +++ b/lib/features/composer/presentation/extensions/file_upload_extension.dart @@ -0,0 +1,45 @@ +import 'dart:convert' as convert; +import 'dart:typed_data' as type_data; + +import 'package:flutter/foundation.dart'; +import 'package:get/get_connect/http/src/request/request.dart'; +import 'package:html_editor_enhanced/utils/file_upload_model.dart'; +import 'package:model/upload/file_info.dart'; + +extension FileUploadExtension on FileUpload { + + String? get base64Data { + if (base64 != null) { + if (!base64!.contains(',')) { + return base64; + } + final listData = base64!.split(','); + if (listData.length < 2) { + return base64; + } + + final base64Origin = listData[1]; + return base64Origin; + } + return base64; + } + + Future toFileInfo() async { + if (base64Data != null) { + final bytesStream = await compute(convertBytesToStream, base64Data!); + return FileInfo.fromStream( + stream: bytesStream, + name: name, + size: size + ); + } else { + return null; + } + } + + static Stream> convertBytesToStream(String base64) { + type_data.Uint8List decodeBytes = convert.base64Decode(base64); + final bytesStream = BodyBytesStream.fromBytes(decodeBytes); + return bytesStream; + } +} \ No newline at end of file diff --git a/lib/features/upload/domain/model/upload_attachment.dart b/lib/features/upload/domain/model/upload_attachment.dart index 2dd3e248e..93fa86e8b 100644 --- a/lib/features/upload/domain/model/upload_attachment.dart +++ b/lib/features/upload/domain/model/upload_attachment.dart @@ -57,11 +57,15 @@ class UploadAttachment with EquatableMixin { if (attachment != null) { _updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo))); } else { - _updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId))); + _updateEvent(Left(ErrorAttachmentUploadState(uploadId: uploadTaskId))); } } catch (e) { logError('UploadAttachment::upload():ERROR: $e'); - _updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId))); + if (e is DioError && e.type == DioErrorType.cancel) { + _updateEvent(Left(CancelAttachmentUploadState(uploadTaskId))); + } else { + _updateEvent(Left(ErrorAttachmentUploadState(uploadId: uploadTaskId, exception: e))); + } } finally { await _progressStateController.close(); } diff --git a/lib/features/upload/domain/state/attachment_upload_state.dart b/lib/features/upload/domain/state/attachment_upload_state.dart index 89fad3396..b5f0ee2f5 100644 --- a/lib/features/upload/domain/state/attachment_upload_state.dart +++ b/lib/features/upload/domain/state/attachment_upload_state.dart @@ -51,13 +51,16 @@ class SuccessAttachmentUploadState extends Success { ]; } -class ErrorAttachmentUploadState extends Failure { +class ErrorAttachmentUploadState extends FeatureFailure { final UploadTaskId uploadId; - ErrorAttachmentUploadState(this.uploadId); + ErrorAttachmentUploadState({ + required this.uploadId, + dynamic exception + }) : super(exception: exception); @override - List get props => [uploadId]; + List get props => [uploadId, ...super.props]; } class CancelAttachmentUploadState extends Failure { diff --git a/lib/features/upload/presentation/controller/upload_controller.dart b/lib/features/upload/presentation/controller/upload_controller.dart index 01fc51863..60e2fedf8 100644 --- a/lib/features/upload/presentation/controller/upload_controller.dart +++ b/lib/features/upload/presentation/controller/upload_controller.dart @@ -258,7 +258,7 @@ class UploadController extends BaseController { if (currentContext != null && currentOverlayContext != null) { _appToast.showToastErrorMessage( currentOverlayContext!, - AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments, + '${AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments}. ${failure.exception ?? ''}', leadingSVGIconColor: Colors.white, leadingSVGIcon: _imagePaths.icAttachment); } diff --git a/model/lib/upload/file_info.dart b/model/lib/upload/file_info.dart index e655968a9..8c9e7ec0a 100644 --- a/model/lib/upload/file_info.dart +++ b/model/lib/upload/file_info.dart @@ -16,6 +16,14 @@ class FileInfo with EquatableMixin { return FileInfo('', '', 0); } + factory FileInfo.fromStream({ + required Stream> stream, + String? name, + int? size + }) { + return FileInfo(name ?? '', '', size ?? 0, readStream: stream); + } + String get fileExtension => fileName.split('.').last; String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/json; charset=UTF-8';