TF-2644 Handle total size exceeded maximum size when drag & drop multiple file to composer
This commit is contained in:
@@ -27,10 +27,7 @@ import 'package:worker_manager/worker_manager.dart' as worker;
|
||||
class FileUploader {
|
||||
|
||||
static const String uploadAttachmentExtraKey = 'upload-attachment';
|
||||
static const String platformExtraKey = 'platform';
|
||||
static const String bytesExtraKey = 'bytes';
|
||||
static const String typeExtraKey = 'type';
|
||||
static const String sizeExtraKey = 'size';
|
||||
static const String streamDataExtraKey = 'streamData';
|
||||
static const String filePathExtraKey = 'path';
|
||||
|
||||
final DioClient _dioClient;
|
||||
@@ -94,10 +91,7 @@ class FileUploader {
|
||||
|
||||
final mapExtra = <String, dynamic>{
|
||||
uploadAttachmentExtraKey: {
|
||||
platformExtraKey: 'mobile',
|
||||
filePathExtraKey: argsUpload.mobileFileUpload.filePath,
|
||||
typeExtraKey: argsUpload.mobileFileUpload.mimeType,
|
||||
sizeExtraKey: argsUpload.mobileFileUpload.fileSize,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,7 +103,7 @@ class FileUploader {
|
||||
),
|
||||
data: File(argsUpload.mobileFileUpload.filePath).openRead(),
|
||||
onSendProgress: (count, total) {
|
||||
log('FileUploader::_handleUploadAttachmentAction():onSendProgress: [${argsUpload.uploadId.id}] = $count');
|
||||
log('FileUploader::_handleUploadAttachmentAction():onSendProgress: FILE[${argsUpload.uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
|
||||
sendPort.send(
|
||||
UploadingAttachmentUploadState(
|
||||
argsUpload.uploadId,
|
||||
@@ -139,10 +133,7 @@ class FileUploader {
|
||||
|
||||
final mapExtra = <String, dynamic>{
|
||||
uploadAttachmentExtraKey: {
|
||||
platformExtraKey: 'web',
|
||||
bytesExtraKey: fileInfo.bytes,
|
||||
typeExtraKey: fileInfo.mimeType,
|
||||
sizeExtraKey: fileInfo.fileSize,
|
||||
streamDataExtraKey: BodyBytesStream.fromBytes(fileInfo.bytes!),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +146,7 @@ class FileUploader {
|
||||
data: BodyBytesStream.fromBytes(fileInfo.bytes!),
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: (count, total) {
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [${uploadId.id}] = $count');
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
|
||||
onSendController.add(
|
||||
Right(UploadingAttachmentUploadState(
|
||||
uploadId,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
class PickFileCanceledException implements Exception {}
|
||||
@@ -3,5 +3,15 @@ import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/mobile_file_upload.dart';
|
||||
|
||||
extension FileInfoExtension on FileInfo {
|
||||
MobileFileUpload toMobileFileUpload() => MobileFileUpload(fileName, filePath, fileSize, mimeType);
|
||||
MobileFileUpload toMobileFileUpload() => MobileFileUpload(fileName, filePath!, fileSize, mimeType);
|
||||
|
||||
FileInfo withInline() => FileInfo(
|
||||
fileName: fileName,
|
||||
fileSize: fileSize,
|
||||
filePath: filePath,
|
||||
bytes: bytes,
|
||||
readStream: readStream,
|
||||
type: type,
|
||||
isInline: true,
|
||||
isShared: isShared);
|
||||
}
|
||||
@@ -3,5 +3,5 @@ import 'package:model/upload/file_info.dart';
|
||||
extension ListFileInfoExtension on List<FileInfo> {
|
||||
List<int> get listSize => map((file) => file.fileSize).toList();
|
||||
|
||||
num get totalSize => listSize.reduce((sum, size) => sum + size);
|
||||
num get totalSize => listSize.isEmpty ? 0 : listSize.reduce((sum, size) => sum + size);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:core/domain/extensions/media_type_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
extension MediaTypeExtension on MediaType {
|
||||
String getIcon(ImagePaths imagePaths, {String? fileName}) {
|
||||
if (validatePDFIcon(fileName: fileName) == true) {
|
||||
return imagePaths.icFilePdf;
|
||||
} else if (isDocFile()) {
|
||||
return imagePaths.icFileDocx;
|
||||
} else if (isExcelFile()) {
|
||||
return imagePaths.icFileXlsx;
|
||||
} else if (isPowerPointFile()) {
|
||||
return imagePaths.icFilePptx;
|
||||
} else if (isPdfFile()) {
|
||||
return imagePaths.icFilePdf;
|
||||
} else if (isZipFile()) {
|
||||
return imagePaths.icFileZip;
|
||||
} else if (isImageFile()) {
|
||||
return imagePaths.icFilePng;
|
||||
} else {
|
||||
return imagePaths.icFileEPup;
|
||||
}
|
||||
}
|
||||
|
||||
bool validatePDFIcon({required String? fileName}) => mimeType == 'application/pdf' ||
|
||||
(mimeType == 'application/octet-stream' && fileName?.endsWith('.pdf') == true);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
extension PlatformFileExtension on PlatformFile {
|
||||
FileInfo toFileInfo() => FileInfo(
|
||||
fileName: name,
|
||||
fileSize: size,
|
||||
filePath: PlatformInfo.isWeb ? '' : path ?? '',
|
||||
bytes: bytes,
|
||||
readStream: readStream
|
||||
);
|
||||
}
|
||||
@@ -31,15 +31,11 @@ class SuccessAttachmentUploadState extends Success {
|
||||
final UploadTaskId uploadId;
|
||||
final Attachment attachment;
|
||||
final FileInfo fileInfo;
|
||||
final bool fromFileShared;
|
||||
|
||||
SuccessAttachmentUploadState(
|
||||
this.uploadId,
|
||||
this.attachment,
|
||||
this.fileInfo,
|
||||
{
|
||||
this.fromFileShared = false
|
||||
}
|
||||
this.fileInfo
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -47,7 +43,6 @@ class SuccessAttachmentUploadState extends Success {
|
||||
uploadId,
|
||||
attachment,
|
||||
fileInfo,
|
||||
fromFileShared,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
class LocalFilePickerLoading extends LoadingState {}
|
||||
|
||||
class LocalFilePickerSuccess extends UIState {
|
||||
final List<FileInfo> pickedFiles;
|
||||
|
||||
@@ -14,6 +16,4 @@ class LocalFilePickerSuccess extends UIState {
|
||||
class LocalFilePickerFailure extends FeatureFailure {
|
||||
|
||||
LocalFilePickerFailure(dynamic exception) : super(exception: exception);
|
||||
}
|
||||
|
||||
class LocalFilePickerCancel extends FeatureFailure {}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
|
||||
class LocalImagePickerLoading extends LoadingState {}
|
||||
|
||||
class LocalImagePickerSuccess extends UIState {
|
||||
final FileInfo fileInfo;
|
||||
|
||||
LocalImagePickerSuccess(this.fileInfo);
|
||||
|
||||
@override
|
||||
List<Object> get props => [fileInfo];
|
||||
}
|
||||
|
||||
class LocalImagePickerFailure extends FeatureFailure {
|
||||
|
||||
LocalImagePickerFailure(dynamic exception) : super(exception: exception);
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/platform_file_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
|
||||
|
||||
class LocalFilePickerInteractor {
|
||||
@@ -13,23 +14,22 @@ class LocalFilePickerInteractor {
|
||||
|
||||
Stream<Either<Failure, Success>> execute({FileType fileType = FileType.any}) async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(LocalFilePickerLoading());
|
||||
|
||||
final filesResult = await FilePicker.platform.pickFiles(
|
||||
type: fileType,
|
||||
allowMultiple: true,
|
||||
withData: PlatformInfo.isWeb
|
||||
withData: PlatformInfo.isWeb,
|
||||
withReadStream: PlatformInfo.isMobile
|
||||
);
|
||||
if (filesResult != null && filesResult.files.isNotEmpty) {
|
||||
final fileInfoResults = filesResult.files
|
||||
.map((platformFile) => FileInfo(
|
||||
platformFile.name,
|
||||
PlatformInfo.isWeb ? '' : platformFile.path ?? '',
|
||||
platformFile.size,
|
||||
bytes: PlatformInfo.isWeb ? platformFile.bytes : null
|
||||
))
|
||||
.toList();
|
||||
yield Right<Failure, Success>(LocalFilePickerSuccess(fileInfoResults));
|
||||
|
||||
if (filesResult?.files.isNotEmpty == true) {
|
||||
final listFileInfo = filesResult!.files
|
||||
.map((platformFile) => platformFile.toFileInfo())
|
||||
.toList();
|
||||
yield Right<Failure, Success>(LocalFilePickerSuccess(listFileInfo));
|
||||
} else {
|
||||
yield Left<Failure, Success>(LocalFilePickerCancel());
|
||||
yield Left<Failure, Success>(LocalFilePickerFailure(PickFileCanceledException()));
|
||||
}
|
||||
} catch (exception) {
|
||||
yield Left<Failure, Success>(LocalFilePickerFailure(exception));
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/platform_file_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/state/local_image_picker_state.dart';
|
||||
|
||||
class LocalImagePickerInteractor {
|
||||
|
||||
LocalImagePickerInteractor();
|
||||
|
||||
Stream<Either<Failure, Success>> execute() async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(LocalImagePickerLoading());
|
||||
|
||||
final filePickerResult = await FilePicker.platform.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: false,
|
||||
withReadStream: PlatformInfo.isMobile,
|
||||
withData: PlatformInfo.isWeb
|
||||
);
|
||||
|
||||
if (filePickerResult?.files.isNotEmpty == true) {
|
||||
final fileInfo = filePickerResult!.files.first.toFileInfo();
|
||||
yield Right<Failure, Success>(LocalImagePickerSuccess(fileInfo));
|
||||
} else {
|
||||
yield Left<Failure, Success>(LocalImagePickerFailure(PickFileCanceledException()));
|
||||
}
|
||||
} catch (exception) {
|
||||
yield Left<Failure, Success>(LocalImagePickerFailure(exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,13 @@ import 'package:get/get.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:model/extensions/attachment_extension.dart';
|
||||
import 'package:model/extensions/list_attachment_extension.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||
import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
|
||||
import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_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/presentation/extensions/upload_attachment_extension.dart';
|
||||
@@ -80,7 +80,7 @@ class UploadController extends BaseController {
|
||||
failure.uploadId,
|
||||
(currentState) => currentState?.copyWith(uploadStatus: UploadFileStatus.uploadFailed));
|
||||
deleteFileUploaded(failure.uploadId);
|
||||
_handleUploadAttachmentsFailure(failure);
|
||||
_showToastMessageWhenUploadAttachmentsFailure(failure);
|
||||
}
|
||||
},
|
||||
(success) {
|
||||
@@ -115,7 +115,7 @@ class UploadController extends BaseController {
|
||||
);
|
||||
|
||||
_refreshListUploadAttachmentState();
|
||||
_handleUploadAttachmentsSuccess(success);
|
||||
_showToastMessageWhenUploadAttachmentsSuccess(success);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -162,7 +162,7 @@ class UploadController extends BaseController {
|
||||
);
|
||||
|
||||
final uploadFileState = _uploadingStateInlineFiles.getUploadFileStateById(success.uploadId);
|
||||
log('UploadController::_handleProgressUploadInlineImageStateStream:uploadId: ${uploadFileState?.uploadTaskId} | fromFileShared: ${uploadFileState?.fromFileShared}');
|
||||
log('UploadController::_handleProgressUploadInlineImageStateStream:uploadId: ${uploadFileState?.uploadTaskId} | fromFileShared: ${uploadFileState?.file?.isShared}');
|
||||
|
||||
_uploadingStateInlineFiles.updateElementByUploadTaskId(
|
||||
success.uploadId,
|
||||
@@ -179,7 +179,6 @@ class UploadController extends BaseController {
|
||||
success.uploadId,
|
||||
inlineAttachment,
|
||||
success.fileInfo,
|
||||
fromFileShared: uploadFileState?.fromFileShared ?? false
|
||||
);
|
||||
_handleUploadInlineAttachmentsSuccess(newUploadSuccess);
|
||||
}
|
||||
@@ -203,27 +202,24 @@ class UploadController extends BaseController {
|
||||
_refreshListUploadAttachmentState();
|
||||
}
|
||||
|
||||
Future<void> justUploadAttachmentsAction(List<FileInfo> uploadFiles, Uri uploadUri) {
|
||||
Future<void> justUploadAttachmentsAction({
|
||||
required List<FileInfo> uploadFiles,
|
||||
required Uri uploadUri,
|
||||
}) {
|
||||
return Future.forEach<FileInfo>(uploadFiles, (uploadFile) async {
|
||||
await uploadFileAction(uploadFile, uploadUri);
|
||||
await uploadFileAction(uploadFile: uploadFile, uploadUri: uploadUri);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> uploadFileAction(
|
||||
FileInfo uploadFile,
|
||||
Uri uploadUri,
|
||||
{
|
||||
bool isInline = false,
|
||||
bool fromFileShared = false,
|
||||
}
|
||||
) {
|
||||
log('UploadController::_uploadFile():fileName: ${uploadFile.fileName} | isInline: $isInline | fromFileShared: $fromFileShared');
|
||||
Future<void> uploadFileAction({
|
||||
required FileInfo uploadFile,
|
||||
required Uri uploadUri,
|
||||
}) {
|
||||
log('UploadController::_uploadFile():fileName: ${uploadFile.fileName} | mimeType: ${uploadFile.mimeType} | isInline: ${uploadFile.isInline} | fromFileShared: ${uploadFile.isShared}');
|
||||
consumeState(_uploadAttachmentInteractor.execute(
|
||||
uploadFile,
|
||||
uploadUri,
|
||||
cancelToken: CancelToken(),
|
||||
isInline: isInline,
|
||||
fromFileShared: fromFileShared
|
||||
));
|
||||
return Future.value();
|
||||
}
|
||||
@@ -244,6 +240,16 @@ class UploadController extends BaseController {
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<FileInfo> get attachmentsPicked {
|
||||
if (listUploadAttachments.isEmpty) {
|
||||
return List.empty();
|
||||
}
|
||||
return listUploadAttachments
|
||||
.map((fileState) => fileState.file)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
}
|
||||
|
||||
Set<EmailBodyPart>? generateAttachments() {
|
||||
if (attachmentsUploaded.isEmpty) {
|
||||
return null;
|
||||
@@ -254,7 +260,7 @@ class UploadController extends BaseController {
|
||||
.toSet();
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentsFailure(ErrorAttachmentUploadState failure) {
|
||||
void _showToastMessageWhenUploadAttachmentsFailure(ErrorAttachmentUploadState failure) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
@@ -264,7 +270,7 @@ class UploadController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentsSuccess(SuccessAttachmentUploadState success) {
|
||||
void _showToastMessageWhenUploadAttachmentsSuccess(SuccessAttachmentUploadState success) {
|
||||
if (currentContext != null && currentOverlayContext != null && _uploadingStateFiles.allSuccess) {
|
||||
appToast.showToastSuccessMessage(
|
||||
currentOverlayContext!,
|
||||
@@ -275,7 +281,7 @@ class UploadController extends BaseController {
|
||||
}
|
||||
|
||||
bool isExceededMaxSizeAttachmentsPerEmail({num totalSizePreparedFiles = 0}) {
|
||||
final currentTotalSize = attachmentsUploaded.totalSize + inlineAttachmentsUploaded.totalSize + totalSizePreparedFiles;
|
||||
final currentTotalSize = attachmentsPicked.totalSize + inlineAttachmentsPicked.totalSize + totalSizePreparedFiles;
|
||||
final maxSizeAttachmentsPerEmail = _mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value;
|
||||
log('UploadController::isExceededMaxSizeAttachmentsPerEmail(): currentTotalSize = $currentTotalSize | maxSizeAttachmentsPerEmail = $maxSizeAttachmentsPerEmail');
|
||||
if (maxSizeAttachmentsPerEmail != null) {
|
||||
@@ -285,16 +291,17 @@ class UploadController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
bool isExceededMaxSizeFilesAttachedInComposer({num totalSizePreparedFiles = 0}) {
|
||||
final currentTotalSizeAttachments = attachmentsUploaded.totalSize + totalSizePreparedFiles;
|
||||
const maximumBytesSizeFileAttachedInComposer = AppConfig.maximumMegabytesSizeFileAttachedInComposer * 1024 * 1024;
|
||||
bool isExceededWarningAttachmentFileSizeInComposer({num totalSizePreparedFiles = 0}) {
|
||||
final currentTotalSizeAttachments = attachmentsPicked.totalSize + totalSizePreparedFiles;
|
||||
const maximumBytesSizeFileAttachedInComposer = AppConfig.warningAttachmentFileSizeInMegabytes * 1024 * 1024;
|
||||
log('UploadController::isExceededMaxSizeFilesAttachedInComposer(): currentTotalSizeAttachments = $currentTotalSizeAttachments | maximumBytesSizeFileAttachedInComposer = $maximumBytesSizeFileAttachedInComposer');
|
||||
return currentTotalSizeAttachments > maximumBytesSizeFileAttachedInComposer;
|
||||
}
|
||||
|
||||
void validateTotalSizeAttachmentsBeforeUpload({
|
||||
required num totalSizePreparedFiles,
|
||||
VoidCallback? callbackAction
|
||||
num? totalSizePreparedFilesWithDispositionAttachment,
|
||||
VoidCallback? onValidationSuccess
|
||||
}) {
|
||||
log('UploadController::_validateTotalSizeAttachmentsBeforeUpload: totalSizePreparedFiles = $totalSizePreparedFiles');
|
||||
if (isExceededMaxSizeAttachmentsPerEmail(totalSizePreparedFiles: totalSizePreparedFiles)) {
|
||||
@@ -307,7 +314,7 @@ class UploadController extends BaseController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isExceededMaxSizeFilesAttachedInComposer(totalSizePreparedFiles: totalSizePreparedFiles)) {
|
||||
if (isExceededWarningAttachmentFileSizeInComposer(totalSizePreparedFiles: totalSizePreparedFilesWithDispositionAttachment ?? totalSizePreparedFiles)) {
|
||||
if (currentContext == null) {
|
||||
log('UploadController::_validateTotalSizeAttachmentsBeforeUpload: CONTEXT IS NULL');
|
||||
return;
|
||||
@@ -315,12 +322,34 @@ class UploadController extends BaseController {
|
||||
|
||||
_showWarningDialogWhenExceededMaxSizeFilesAttachedInComposer(
|
||||
context: currentContext!,
|
||||
confirmAction: callbackAction
|
||||
confirmAction: () async {
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
onValidationSuccess
|
||||
);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
callbackAction?.call();
|
||||
onValidationSuccess?.call();
|
||||
}
|
||||
|
||||
void validateTotalSizeInlineAttachmentsBeforeUpload({
|
||||
required num totalSizePreparedFiles,
|
||||
VoidCallback? onValidationSuccess
|
||||
}) {
|
||||
if (isExceededMaxSizeAttachmentsPerEmail(totalSizePreparedFiles: totalSizePreparedFiles)) {
|
||||
if (currentContext == null) {
|
||||
log('UploadController::validateTotalSizeInlineAttachmentsBeforeUpload: CONTEXT IS NULL');
|
||||
return;
|
||||
}
|
||||
|
||||
_showConfirmDialogWhenExceededMaxSizeAttachmentsPerEmail(context: currentContext!);
|
||||
return;
|
||||
}
|
||||
|
||||
onValidationSuccess?.call();
|
||||
}
|
||||
|
||||
void _showConfirmDialogWhenExceededMaxSizeAttachmentsPerEmail({required BuildContext context}) {
|
||||
@@ -340,7 +369,7 @@ class UploadController extends BaseController {
|
||||
showConfirmDialogAction(
|
||||
context,
|
||||
title: '',
|
||||
AppLocalizations.of(context).messageWarningDialogWhenExceedMaximumFileSizeComposer,
|
||||
AppLocalizations.of(context).warningMessageWhenExceedGenerallySizeInComposer,
|
||||
AppLocalizations.of(context).continueAction,
|
||||
cancelTitle: AppLocalizations.of(context).cancel,
|
||||
alignCenter: true,
|
||||
@@ -394,6 +423,17 @@ class UploadController extends BaseController {
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<FileInfo> get inlineAttachmentsPicked {
|
||||
if (_uploadingStateInlineFiles.uploadingStateFiles.isEmpty) {
|
||||
return List.empty();
|
||||
}
|
||||
return _uploadingStateInlineFiles.uploadingStateFiles
|
||||
.whereNotNull()
|
||||
.map((fileState) => fileState.file)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
}
|
||||
|
||||
Map<String, Attachment> get mapInlineAttachments {
|
||||
final inlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles
|
||||
.whereNotNull()
|
||||
@@ -415,42 +455,47 @@ class UploadController extends BaseController {
|
||||
return mapInlineAttachments;
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentFailure(UploadAttachmentFailure failure) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
failure.fileInfo.isInline == true
|
||||
? AppLocalizations.of(currentContext!).thisImageCannotBeAdded
|
||||
: AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
|
||||
leadingSVGIconColor: Colors.white,
|
||||
leadingSVGIcon: failure.fileInfo.isInline == true
|
||||
? imagePaths.icInsertImage
|
||||
: imagePaths.icAttachment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleUploadAttachmentSuccess(UploadAttachmentSuccess success) async {
|
||||
if (success.uploadAttachment.fileInfo.isInline == true) {
|
||||
_uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState());
|
||||
await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
} else {
|
||||
_uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
|
||||
await _progressUploadStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
_refreshListUploadAttachmentState();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleFailureViewState(Failure failure) async {
|
||||
super.handleFailureViewState(failure);
|
||||
void handleFailureViewState(Failure failure) {
|
||||
if (failure is UploadAttachmentFailure) {
|
||||
if (failure.isInline) {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).thisImageCannotBeAdded,
|
||||
leadingSVGIconColor: Colors.white,
|
||||
leadingSVGIcon: imagePaths.icInsertImage);
|
||||
}
|
||||
} else {
|
||||
if (currentContext != null && currentOverlayContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
currentOverlayContext!,
|
||||
AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
|
||||
leadingSVGIconColor: Colors.white,
|
||||
leadingSVGIcon: imagePaths.icAttachment);
|
||||
}
|
||||
}
|
||||
_handleUploadAttachmentFailure(failure);
|
||||
} else {
|
||||
super.handleFailureViewState(failure);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSuccessViewState(Success success) async {
|
||||
super.handleSuccessViewState(success);
|
||||
if (success is UploadAttachmentSuccess) {
|
||||
if (success.isInline) {
|
||||
_uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState(fromFileShared: success.fromFileShared));
|
||||
await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
} else {
|
||||
_uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
|
||||
await _progressUploadStateStreamGroup.add(success.uploadAttachment.progressState);
|
||||
_refreshListUploadAttachmentState();
|
||||
}
|
||||
_handleUploadAttachmentSuccess(success);
|
||||
} else {
|
||||
super.handleSuccessViewState(success);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,11 @@ import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_sta
|
||||
|
||||
extension UploadAttachmentExtension on UploadAttachment {
|
||||
|
||||
UploadFileState toUploadFileState({bool fromFileShared = false}) {
|
||||
UploadFileState toUploadFileState() {
|
||||
return UploadFileState(
|
||||
uploadTaskId,
|
||||
file: fileInfo,
|
||||
cancelToken: cancelToken,
|
||||
fromFileShared: fromFileShared,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/extensions/media_type_extension.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
|
||||
|
||||
@@ -17,7 +18,6 @@ class UploadFileState with EquatableMixin {
|
||||
final int uploadingProgress;
|
||||
final Attachment? attachment;
|
||||
final CancelToken? cancelToken;
|
||||
final bool fromFileShared;
|
||||
|
||||
UploadFileState(
|
||||
this.uploadTaskId,
|
||||
@@ -27,7 +27,6 @@ class UploadFileState with EquatableMixin {
|
||||
this.uploadingProgress = 0,
|
||||
this.attachment,
|
||||
this.cancelToken,
|
||||
this.fromFileShared = false,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -38,7 +37,6 @@ class UploadFileState with EquatableMixin {
|
||||
int? uploadingProgress,
|
||||
Attachment? attachment,
|
||||
CancelToken? cancelToken,
|
||||
bool? fromFileShared,
|
||||
}) {
|
||||
return UploadFileState(
|
||||
uploadTaskId ?? this.uploadTaskId,
|
||||
@@ -47,7 +45,6 @@ class UploadFileState with EquatableMixin {
|
||||
uploadingProgress: uploadingProgress ?? this.uploadingProgress,
|
||||
attachment: attachment ?? this.attachment,
|
||||
cancelToken: cancelToken ?? this.cancelToken,
|
||||
fromFileShared: fromFileShared ?? this.fromFileShared
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,11 +69,16 @@ class UploadFileState with EquatableMixin {
|
||||
double get percentUploading => uploadingProgress / 100;
|
||||
|
||||
String getIcon(ImagePaths imagePaths) {
|
||||
var mediaType = attachment?.type;
|
||||
if (mediaType == null && file != null) {
|
||||
mediaType = MediaType.parse(file!.mimeType);
|
||||
try {
|
||||
MediaType? mediaType = attachment?.type;
|
||||
if (mediaType == null && file != null) {
|
||||
mediaType = MediaType.parse(file!.mimeType);
|
||||
}
|
||||
return mediaType?.getIcon(imagePaths, fileName: fileName) ?? imagePaths.icFileEPup;
|
||||
} catch (e) {
|
||||
logError('UploadFileState::getIcon: Exception: $e');
|
||||
return imagePaths.icFileEPup;
|
||||
}
|
||||
return attachment?.getIcon(imagePaths, fileMediaType: mediaType) ?? imagePaths.icFileEPup;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -87,6 +89,5 @@ class UploadFileState with EquatableMixin {
|
||||
uploadingProgress,
|
||||
attachment,
|
||||
cancelToken,
|
||||
fromFileShared,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user