TF-2118 Handle drop n drop file as attachment
(cherry picked from commit cb1f15dcba46a15dbd5c5a8493593854855be331)
This commit is contained in:
@@ -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_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/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/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/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/draggable_email_address.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||||
@@ -1839,10 +1840,10 @@ class ComposerController extends BaseController {
|
|||||||
onEditorFocusChange(true);
|
onEditorFocusChange(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleImageUploadSuccess(
|
void handleImageUploadSuccess (
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
web_html_editor.FileUpload fileUpload
|
web_html_editor.FileUpload fileUpload
|
||||||
) {
|
) async {
|
||||||
log('ComposerController::handleImageUploadSuccess:NAME: ${fileUpload.name} | TYPE: ${fileUpload.type} | SIZE: ${fileUpload.size}');
|
log('ComposerController::handleImageUploadSuccess:NAME: ${fileUpload.name} | TYPE: ${fileUpload.type} | SIZE: ${fileUpload.size}');
|
||||||
if (fileUpload.base64 == null) {
|
if (fileUpload.base64 == null) {
|
||||||
_appToast.showToastErrorMessage(
|
_appToast.showToastErrorMessage(
|
||||||
@@ -1853,6 +1854,15 @@ class ComposerController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileUpload.type == null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1864,6 +1874,16 @@ class ComposerController extends BaseController {
|
|||||||
type: mediaType,
|
type: mediaType,
|
||||||
size: fileUpload.size,
|
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() {
|
FocusNode? getNextFocusOfToEmailAddress() {
|
||||||
if (ccRecipientState.value == PrefixRecipientState.enabled) {
|
if (ccRecipientState.value == PrefixRecipientState.enabled) {
|
||||||
return ccAddressFocusNode;
|
return ccAddressFocusNode;
|
||||||
|
|||||||
@@ -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<FileInfo?> 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<List<int>> convertBytesToStream(String base64) {
|
||||||
|
type_data.Uint8List decodeBytes = convert.base64Decode(base64);
|
||||||
|
final bytesStream = BodyBytesStream.fromBytes(decodeBytes);
|
||||||
|
return bytesStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,11 +57,15 @@ class UploadAttachment with EquatableMixin {
|
|||||||
if (attachment != null) {
|
if (attachment != null) {
|
||||||
_updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo)));
|
_updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo)));
|
||||||
} else {
|
} else {
|
||||||
_updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId)));
|
_updateEvent(Left(ErrorAttachmentUploadState(uploadId: uploadTaskId)));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError('UploadAttachment::upload():ERROR: $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 {
|
} finally {
|
||||||
await _progressStateController.close();
|
await _progressStateController.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,13 +51,16 @@ class SuccessAttachmentUploadState extends Success {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorAttachmentUploadState extends Failure {
|
class ErrorAttachmentUploadState extends FeatureFailure {
|
||||||
final UploadTaskId uploadId;
|
final UploadTaskId uploadId;
|
||||||
|
|
||||||
ErrorAttachmentUploadState(this.uploadId);
|
ErrorAttachmentUploadState({
|
||||||
|
required this.uploadId,
|
||||||
|
dynamic exception
|
||||||
|
}) : super(exception: exception);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [uploadId];
|
List<Object?> get props => [uploadId, ...super.props];
|
||||||
}
|
}
|
||||||
|
|
||||||
class CancelAttachmentUploadState extends Failure {
|
class CancelAttachmentUploadState extends Failure {
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ class UploadController extends BaseController {
|
|||||||
if (currentContext != null && currentOverlayContext != null) {
|
if (currentContext != null && currentOverlayContext != null) {
|
||||||
_appToast.showToastErrorMessage(
|
_appToast.showToastErrorMessage(
|
||||||
currentOverlayContext!,
|
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,
|
leadingSVGIconColor: Colors.white,
|
||||||
leadingSVGIcon: _imagePaths.icAttachment);
|
leadingSVGIcon: _imagePaths.icAttachment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ class FileInfo with EquatableMixin {
|
|||||||
return FileInfo('', '', 0);
|
return FileInfo('', '', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory FileInfo.fromStream({
|
||||||
|
required Stream<List<int>> stream,
|
||||||
|
String? name,
|
||||||
|
int? size
|
||||||
|
}) {
|
||||||
|
return FileInfo(name ?? '', '', size ?? 0, readStream: stream);
|
||||||
|
}
|
||||||
|
|
||||||
String get fileExtension => fileName.split('.').last;
|
String get fileExtension => fileName.split('.').last;
|
||||||
|
|
||||||
String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/json; charset=UTF-8';
|
String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/json; charset=UTF-8';
|
||||||
|
|||||||
Reference in New Issue
Block a user