TF-1323 Fix can not attach image on mobile and web

This commit is contained in:
dab246
2023-02-06 10:16:36 +07:00
committed by Dat Vu
parent 10ba3867d8
commit 459b1995a7
16 changed files with 317 additions and 163 deletions
@@ -16,7 +16,7 @@ class ComposerRepositoryImpl extends ComposerRepository {
this._composerDataSource); this._composerDataSource);
@override @override
UploadAttachment uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}) { Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}) {
return _attachmentUploadDataSource.uploadAttachment(fileInfo, uploadUri, cancelToken: cancelToken); return _attachmentUploadDataSource.uploadAttachment(fileInfo, uploadUri, cancelToken: cancelToken);
} }
@@ -1,9 +1,9 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:model/model.dart'; import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
abstract class ComposerRepository { abstract class ComposerRepository {
UploadAttachment uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}); Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken});
Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress}); Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress});
} }
@@ -0,0 +1,25 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
class UploadAttachmentSuccess extends UIState {
final UploadAttachment uploadAttachment;
final bool isInline;
UploadAttachmentSuccess(this.uploadAttachment, {this.isInline = false});
@override
List<Object?> get props => [uploadAttachment, isInline];
}
class UploadAttachmentFailure extends FeatureFailure {
final dynamic exception;
final bool isInline;
UploadAttachmentFailure(this.exception, {this.isInline = false});
@override
List<Object> get props => [exception, isInline];
}
@@ -1,14 +1,31 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:model/model.dart'; import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart'; import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
class UploadAttachmentInteractor { class UploadAttachmentInteractor {
final ComposerRepository _composerRepository; final ComposerRepository _composerRepository;
UploadAttachmentInteractor(this._composerRepository); UploadAttachmentInteractor(this._composerRepository);
UploadAttachment execute(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}) { Stream<Either<Failure, Success>> execute(
return _composerRepository.uploadAttachment(fileInfo, uploadUri, cancelToken: cancelToken); FileInfo fileInfo,
Uri uploadUri, {
CancelToken? cancelToken,
bool isInline = false
}) async* {
try {
final uploadAttachment = await _composerRepository.uploadAttachment(
fileInfo,
uploadUri,
cancelToken: cancelToken
);
yield Right<Failure, Success>(UploadAttachmentSuccess(uploadAttachment, isInline: isInline));
} catch (e) {
yield Left<Failure, Success>(UploadAttachmentFailure(e, isInline: isInline));
}
} }
} }
@@ -51,6 +51,7 @@ import 'package:tmail_ui_user/features/upload/presentation/controller/upload_con
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
import 'package:uuid/uuid.dart';
import 'package:worker_manager/worker_manager.dart'; import 'package:worker_manager/worker_manager.dart';
class ComposerBindings extends BaseBindings { class ComposerBindings extends BaseBindings {
@@ -67,7 +68,11 @@ class ComposerBindings extends BaseBindings {
@override @override
void bindingsDataSourceImpl() { void bindingsDataSourceImpl() {
Get.lazyPut(() => AttachmentUploadDataSourceImpl(Get.find<FileUploader>())); Get.lazyPut(() => AttachmentUploadDataSourceImpl(
Get.find<FileUploader>(),
Get.find<Uuid>(),
Get.find<RemoteExceptionThrower>()
));
Get.lazyPut(() => ComposerDataSourceImpl(Get.find<DownloadClient>(), Get.find<RemoteExceptionThrower>())); Get.lazyPut(() => ComposerDataSourceImpl(Get.find<DownloadClient>(), Get.find<RemoteExceptionThrower>()));
Get.lazyPut(() => ContactDataSourceImpl(Get.find<CacheExceptionThrower>())); Get.lazyPut(() => ContactDataSourceImpl(Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => MailboxDataSourceImpl( Get.lazyPut(() => MailboxDataSourceImpl(
@@ -1445,7 +1445,7 @@ class ComposerController extends BaseController {
final accountId = mailboxDashBoardController.accountId.value; final accountId = mailboxDashBoardController.accountId.value;
if (session != null && accountId != null) { if (session != null && accountId != null) {
final uploadUri = session.getUploadUri(accountId); final uploadUri = session.getUploadUri(accountId);
uploadController.uploadInlineImage(pickedFile, uploadUri); uploadController.uploadFileAction(pickedFile, uploadUri, isInline: true);
} }
} else { } else {
if (currentContext != null) { if (currentContext != null) {
@@ -4,5 +4,5 @@ import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
abstract class AttachmentUploadDataSource { abstract class AttachmentUploadDataSource {
UploadAttachment uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}); Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken});
} }
@@ -5,29 +5,29 @@ import 'package:tmail_ui_user/features/upload/data/datasource/attachment_upload_
import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class AttachmentUploadDataSourceImpl extends AttachmentUploadDataSource { class AttachmentUploadDataSourceImpl extends AttachmentUploadDataSource {
final FileUploader _fileUploader; final FileUploader _fileUploader;
final Uuid _uuid;
final ExceptionThrower _exceptionThrower;
AttachmentUploadDataSourceImpl(this._fileUploader); AttachmentUploadDataSourceImpl(this._fileUploader, this._uuid, this._exceptionThrower);
@override @override
UploadAttachment uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}) { Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}) {
final attachmentUploadId = _generateAttachmentUploadId(); return Future.sync(() {
final uploadAttachment = UploadAttachment( return UploadAttachment(
attachmentUploadId, UploadTaskId(_uuid.v4()),
fileInfo, fileInfo,
uploadUri, uploadUri,
_fileUploader, _fileUploader,
cancelToken: cancelToken) cancelToken: cancelToken
..upload(); )..upload();
}).catchError((error) {
return uploadAttachment; _exceptionThrower.throwException(error);
} });
UploadTaskId _generateAttachmentUploadId() {
return UploadTaskId(const Uuid().v4());
} }
} }
@@ -3,13 +3,18 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:core/core.dart'; import 'package:core/data/network/dio_client.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/build_utils.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:model/email/attachment.dart'; import 'package:model/email/attachment.dart';
import 'package:model/upload/file_info.dart'; import 'package:model/upload/file_info.dart';
import 'package:model/upload/upload_response.dart'; import 'package:model/upload/upload_response.dart';
import 'package:tmail_ui_user/features/upload/data/model/upload_file_arguments.dart'; import 'package:tmail_ui_user/features/upload/data/model/upload_file_arguments.dart';
import 'package:tmail_ui_user/features/upload/domain/exceptions/upload_exception.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.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/attachment_upload_state.dart';
import 'package:worker_manager/worker_manager.dart' as worker; import 'package:worker_manager/worker_manager.dart' as worker;
@@ -36,21 +41,24 @@ class FileUploader {
uploadUri, uploadUri,
cancelToken: cancelToken); cancelToken: cancelToken);
} else { } else {
final attachmentUploaded = await _isolateExecutor.execute( return await _isolateExecutor.execute(
arg1: UploadFileArguments( arg1: UploadFileArguments(
_dioClient, _dioClient,
uploadId, uploadId,
fileInfo, fileInfo,
uploadUri, uploadUri,
cancelToken: cancelToken), cancelToken: cancelToken
fun1: _handleUploadAttachmentAction, ),
notification: (value) { fun1: _handleUploadAttachmentAction,
if (value is Success) { notification: (value) {
log('FileUploader::uploadAttachment(): onUpdateProgress: $value'); if (value is Success) {
onSendController.add(Right(value)); log('FileUploader::uploadAttachment(): onUpdateProgress: $value');
} onSendController.add(Right(value));
}); }
return attachmentUploaded; }
)
.then((value) => value)
.catchError((error) => throw error);
} }
} }
@@ -58,80 +66,107 @@ class FileUploader {
UploadFileArguments argsUpload, UploadFileArguments argsUpload,
worker.TypeSendPort sendPort worker.TypeSendPort sendPort
) async { ) async {
try { final dioClient = argsUpload.dioClient;
final dioClient = argsUpload.dioClient; final fileInfo = argsUpload.fileInfo;
final fileInfo = argsUpload.fileInfo; final uploadUri = argsUpload.uploadUri;
final uploadUri = argsUpload.uploadUri; final cancelToken = argsUpload.cancelToken;
final cancelToken = argsUpload.cancelToken;
final headerParam = dioClient.getHeaders(); final resultJson = await _invokeRequestToServer(
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType; dioClient,
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize; uploadUri,
fileInfo,
final resultJson = await argsUpload.dioClient.post( cancelToken: cancelToken,
Uri.decodeFull(uploadUri.toString()), onSendProgress: (count, total) {
options: Options(headers: headerParam), log('FileUploader::_handleUploadAttachmentAction():onSendProgress: [${argsUpload.uploadId.id}] = $count');
data: fileInfo.readStream ?? File(fileInfo.filePath).openRead(), sendPort.send(
cancelToken: cancelToken, UploadingAttachmentUploadState(
onSendProgress: (progress, total) { argsUpload.uploadId,
log('FileUploader::_handleUploadAttachmentAction():onSendProgress: [${argsUpload.uploadId.id}] = $progress'); count,
sendPort.send(UploadingAttachmentUploadState( fileInfo.fileSize
argsUpload.uploadId, )
progress, );
fileInfo.fileSize));
});
if (cancelToken?.isCancelled == true) {
log('FileUploader::_handleUploadAttachmentAction(): cancelToken');
return null;
} }
);
final decodeJson = resultJson is Map ? resultJson : jsonDecode(resultJson); log('FileUploader::_handleUploadAttachmentAction():resultJson: $resultJson');
final uploadResponse = UploadResponse.fromJson(decodeJson); if (cancelToken?.isCancelled == true) {
log('FileUploader::_handleUploadAttachmentAction(): ${uploadResponse.toString()}'); log('FileUploader::_handleUploadAttachmentAction(): upload is cancelled');
return uploadResponse.toAttachment(fileInfo.fileName);
} catch (e) {
log('FileUploader::_handleUploadAttachmentAction(): ERROR: $e');
return null; return null;
} }
return _parsingResponse(resultJson: resultJson, fileName: fileInfo.fileName);
} }
Future<Attachment?> _handleUploadAttachmentActionOnWeb( Future<Attachment?> _handleUploadAttachmentActionOnWeb(
UploadTaskId uploadId, UploadTaskId uploadId,
StreamController<Either<Failure, Success>> onSendController, StreamController<Either<Failure, Success>> onSendController,
FileInfo fileInfo, FileInfo fileInfo,
Uri uploadUri, Uri uploadUri,
{CancelToken? cancelToken} {CancelToken? cancelToken}
) async { ) async {
try { final resultJson = await _invokeRequestToServer(
final headerParam = _dioClient.getHeaders(); _dioClient,
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType; uploadUri,
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize; fileInfo,
cancelToken: cancelToken,
final resultJson = await _dioClient.post( onSendProgress: (count, total) {
Uri.decodeFull(uploadUri.toString()), log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [${uploadId.id}] = $count');
options: Options(headers: headerParam), onSendController.add(
data: fileInfo.readStream, Right(UploadingAttachmentUploadState(
cancelToken: cancelToken, uploadId,
onSendProgress: (progress, total) { count,
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [$uploadId] = $progress'); fileInfo.fileSize
onSendController.add(Right(UploadingAttachmentUploadState( ))
uploadId, progress, fileInfo.fileSize))); );
}
);
if (cancelToken?.isCancelled == true) {
log('FileUploader::_handleUploadAttachmentAction(): cancelToken');
return null;
} }
);
log('FileUploader::_handleUploadAttachmentActionOnWeb():resultJson: $resultJson');
if (cancelToken?.isCancelled == true) {
log('FileUploader::_handleUploadAttachmentActionOnWeb(): upload is cancelled');
return null;
}
return _parsingResponse(resultJson: resultJson, fileName: fileInfo.fileName);
}
static Future<dynamic> _invokeRequestToServer(
DioClient dioClient,
Uri uploadUri,
FileInfo fileInfo, {
CancelToken? cancelToken,
ProgressCallback? onSendProgress
}) {
final headerParam = dioClient.getHeaders();
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize;
final data = fileInfo.readStream ?? File(fileInfo.filePath).openRead();
if (cancelToken?.isCancelled == true) {
log('FileUploader::_invokeRequestToServer(): upload is cancelled');
return Future.value();
}
return dioClient.post(
Uri.decodeFull(uploadUri.toString()),
options: Options(headers: headerParam),
data: data,
cancelToken: cancelToken,
onSendProgress: onSendProgress
);
}
static Attachment? _parsingResponse({dynamic resultJson, required String fileName}) {
log('FileUploader::_parsingResponse():resultJson: $resultJson');
if (resultJson != null) {
final decodeJson = resultJson is Map ? resultJson : jsonDecode(resultJson); final decodeJson = resultJson is Map ? resultJson : jsonDecode(resultJson);
final uploadResponse = UploadResponse.fromJson(decodeJson); final uploadResponse = UploadResponse.fromJson(decodeJson);
log('FileUploader::_handleUploadAttachmentActionOnWeb(): ${uploadResponse.toString()}'); log('FileUploader::_parsingResponse():uploadResponse: ${uploadResponse.toString()}');
return uploadResponse.toAttachment(fileInfo.fileName); return uploadResponse.toAttachment(fileName);
} catch (e) { } else {
log('FileUploader::_handleUploadAttachmentActionOnWeb(): $e'); logError('FileUploader::_parsingResponse(): DataResponseIsNullException');
return null; throw DataResponseIsNullException();
} }
} }
} }
@@ -0,0 +1 @@
class DataResponseIsNullException implements Exception {}
@@ -12,7 +12,7 @@ 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/attachment_upload_state.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
class UploadAttachment extends Equatable { class UploadAttachment with EquatableMixin {
final UploadTaskId uploadTaskId; final UploadTaskId uploadTaskId;
final FileInfo fileInfo; final FileInfo fileInfo;
@@ -35,31 +35,44 @@ class UploadAttachment extends Equatable {
_progressStateController.add(flowUploadState); _progressStateController.add(flowUploadState);
} }
Future<void> upload() async { void upload() async {
log('UploadFile::upload(): $uploadTaskId'); try {
_updateEvent(Right(PendingAttachmentUploadState(uploadTaskId, 0, fileInfo.fileSize))); log('UploadFile::upload(): $uploadTaskId');
_updateEvent(Right(PendingAttachmentUploadState(uploadTaskId, 0, fileInfo.fileSize)));
final attachment = await fileUploader.uploadAttachment( final attachment = await fileUploader.uploadAttachment(
uploadTaskId, uploadTaskId,
_progressStateController, _progressStateController,
fileInfo, fileInfo,
uploadUri, uploadUri,
cancelToken: cancelToken); cancelToken: cancelToken
);
if (cancelToken?.isCancelled == true) { if (cancelToken?.isCancelled == true) {
_updateEvent(Left(CancelAttachmentUploadState(uploadTaskId))); _updateEvent(Left(CancelAttachmentUploadState(uploadTaskId)));
await _progressStateController.close(); await _progressStateController.close();
return; return;
} }
if (attachment != null) { if (attachment != null) {
_updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo))); _updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment, fileInfo)));
} else { } else {
_updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId)));
}
} catch (e) {
logError('UploadAttachment::upload():ERROR: $e');
_updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId))); _updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId)));
} finally {
await _progressStateController.close();
} }
await _progressStateController.close();
} }
@override @override
List<Object?> get props => [uploadTaskId, fileInfo, uploadUri]; List<Object?> get props => [
uploadTaskId,
fileInfo,
uploadUri,
fileUploader,
cancelToken
];
} }
@@ -15,10 +15,13 @@ import 'package:model/email/attachment.dart';
import 'package:model/extensions/attachment_extension.dart'; import 'package:model/extensions/attachment_extension.dart';
import 'package:model/extensions/list_attachment_extension.dart'; import 'package:model/extensions/list_attachment_extension.dart';
import 'package:model/upload/file_info.dart'; import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/base/base_controller.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/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/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.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/attachment_upload_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/extensions/upload_attachment_extension.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart'; import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state_list.dart'; import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state_list.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart'; import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
@@ -26,7 +29,7 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class UploadController extends GetxController { class UploadController extends BaseController {
final _imagePaths = Get.find<ImagePaths>(); final _imagePaths = Get.find<ImagePaths>();
final _appToast = Get.find<AppToast>(); final _appToast = Get.find<AppToast>();
@@ -125,6 +128,14 @@ class UploadController extends GetxController {
if (failure is ErrorAttachmentUploadState) { if (failure is ErrorAttachmentUploadState) {
uploadInlineViewState.value = Left(failure); uploadInlineViewState.value = Left(failure);
_deleteInlineFileUploaded(failure.uploadId); _deleteInlineFileUploaded(failure.uploadId);
if (currentContext != null && currentOverlayContext != null) {
_appToast.showToastWithIcon(currentOverlayContext!,
message: AppLocalizations.of(currentContext!).thisImageCannotBeAdded,
textColor: AppColor.toastErrorBackgroundColor,
iconColor: AppColor.toastErrorBackgroundColor,
icon: _imagePaths.icInsertImage
);
}
} }
}, },
(success) { (success) {
@@ -185,28 +196,21 @@ class UploadController extends GetxController {
_refreshListUploadAttachmentState(); _refreshListUploadAttachmentState();
} }
Future<void> justUploadAttachmentsAction(List<FileInfo> uploadFiles, Uri uploadUri) async { Future<void> justUploadAttachmentsAction(List<FileInfo> uploadFiles, Uri uploadUri) {
return Future.forEach<FileInfo>(uploadFiles, (uploadFile) async { return Future.forEach<FileInfo>(uploadFiles, (uploadFile) async {
await _uploadFile(uploadFile, uploadUri); await uploadFileAction(uploadFile, uploadUri);
}); });
} }
Future<void> _uploadFile(FileInfo uploadFile, Uri uploadUri) async { Future<void> uploadFileAction(FileInfo uploadFile, Uri uploadUri, {bool isInline = false}) {
log('UploadAttachmentManager::_uploadFile(): ${uploadFile.fileName}'); log('UploadController::_uploadFile():fileName: ${uploadFile.fileName}');
consumeState(_uploadAttachmentInteractor.execute(
final cancelToken = CancelToken(); uploadFile,
final uploadAttachment = _uploadAttachmentInteractor.execute( uploadUri,
uploadFile, uploadUri, cancelToken: CancelToken(),
cancelToken: cancelToken); isInline: isInline
));
_uploadingStateFiles.add(UploadFileState( return Future.value();
uploadAttachment.uploadTaskId,
file: uploadFile,
cancelToken: cancelToken));
await _progressUploadStateStreamGroup.add(uploadAttachment.progressState);
_refreshListUploadAttachmentState();
} }
void _refreshListUploadAttachmentState() { void _refreshListUploadAttachmentState() {
@@ -258,19 +262,19 @@ class UploadController extends GetxController {
bool hasEnoughMaxAttachmentSize({List<FileInfo>? listFiles}) { bool hasEnoughMaxAttachmentSize({List<FileInfo>? listFiles}) {
final currentTotalAttachmentsSize = attachmentsUploaded.totalSize(); final currentTotalAttachmentsSize = attachmentsUploaded.totalSize();
final totalInlineAttachmentsSize = inlineAttachmentsUploaded.totalSize(); final totalInlineAttachmentsSize = inlineAttachmentsUploaded.totalSize();
log('ComposerController::_validateAttachmentsSize(): $currentTotalAttachmentsSize'); log('UploadController::_validateAttachmentsSize(): $currentTotalAttachmentsSize');
log('ComposerController::_validateAttachmentsSize(): totalInlineAttachmentsSize: $totalInlineAttachmentsSize'); log('UploadController::_validateAttachmentsSize(): totalInlineAttachmentsSize: $totalInlineAttachmentsSize');
num uploadedTotalSize = 0; num uploadedTotalSize = 0;
if (listFiles != null && listFiles.isNotEmpty) { if (listFiles != null && listFiles.isNotEmpty) {
final uploadedListSize = listFiles.map((file) => file.fileSize).toList(); final uploadedListSize = listFiles.map((file) => file.fileSize).toList();
uploadedTotalSize = uploadedListSize.reduce((sum, size) => sum + size); uploadedTotalSize = uploadedListSize.reduce((sum, size) => sum + size);
log('ComposerController::_validateAttachmentsSize(): uploadedTotalSize: $uploadedTotalSize'); log('UploadController::_validateAttachmentsSize(): uploadedTotalSize: $uploadedTotalSize');
} }
final totalSizeReadyToUpload = currentTotalAttachmentsSize + final totalSizeReadyToUpload = currentTotalAttachmentsSize +
totalInlineAttachmentsSize + totalInlineAttachmentsSize +
uploadedTotalSize; uploadedTotalSize;
log('ComposerController::_validateAttachmentsSize(): totalSizeReadyToUpload: $totalSizeReadyToUpload'); log('UploadController::_validateAttachmentsSize(): totalSizeReadyToUpload: $totalSizeReadyToUpload');
final maxSizeAttachmentsPerEmail = _mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value; final maxSizeAttachmentsPerEmail = _mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value;
if (maxSizeAttachmentsPerEmail != null) { if (maxSizeAttachmentsPerEmail != null) {
@@ -285,23 +289,6 @@ class UploadController extends GetxController {
.every((uploadFile) => uploadFile.uploadStatus.completed); .every((uploadFile) => uploadFile.uploadStatus.completed);
} }
Future<void> uploadInlineImage(FileInfo uploadFile, Uri uploadUri) async {
log('UploadAttachmentManager::uploadInlineImage(): ${uploadFile.fileName}');
final cancelToken = CancelToken();
final uploadAttachment = _uploadAttachmentInteractor.execute(
uploadFile,
uploadUri,
cancelToken: cancelToken);
_uploadingStateInlineFiles.add(UploadFileState(
uploadAttachment.uploadTaskId,
file: uploadFile,
cancelToken: cancelToken));
await _progressUploadInlineImageStateStreamGroup.add(uploadAttachment.progressState);
}
void _deleteInlineFileUploaded(UploadTaskId uploadId) { void _deleteInlineFileUploaded(UploadTaskId uploadId) {
_uploadingStateInlineFiles.deleteElementByUploadTaskId(uploadId); _uploadingStateInlineFiles.deleteElementByUploadTaskId(uploadId);
} }
@@ -350,4 +337,48 @@ class UploadController extends GetxController {
return mapInlineAttachments; return mapInlineAttachments;
} }
@override
void onDone() {
viewState.value.fold(_handleFailureViewState, _handleSuccessViewState);
}
void _handleFailureViewState(Failure failure) async {
logError('UploadController::_handleFailureViewState():failure: $failure');
if (failure is UploadAttachmentFailure) {
if (failure.isInline) {
if (currentContext != null && currentOverlayContext != null) {
_appToast.showToastWithIcon(currentOverlayContext!,
message: AppLocalizations.of(currentContext!).thisImageCannotBeAdded,
textColor: AppColor.toastErrorBackgroundColor,
iconColor: AppColor.toastErrorBackgroundColor,
icon: _imagePaths.icInsertImage
);
}
} else {
if (currentContext != null && currentOverlayContext != null) {
_appToast.showToastWithIcon(currentOverlayContext!,
message: AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments,
textColor: AppColor.toastErrorBackgroundColor,
iconColor: AppColor.toastErrorBackgroundColor,
icon: _imagePaths.icAttachment
);
}
}
}
}
void _handleSuccessViewState(Success success) async {
log('UploadController::_handleSuccessViewState():success: $success');
if (success is UploadAttachmentSuccess) {
if (success.isInline) {
_uploadingStateInlineFiles.add(success.uploadAttachment.toUploadFileState());
await _progressUploadInlineImageStateStreamGroup.add(success.uploadAttachment.progressState);
} else {
_uploadingStateFiles.add(success.uploadAttachment.toUploadFileState());
await _progressUploadStateStreamGroup.add(success.uploadAttachment.progressState);
_refreshListUploadAttachmentState();
}
}
}
} }
@@ -0,0 +1,14 @@
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
extension UploadAttachmentExtension on UploadAttachment {
UploadFileState toUploadFileState() {
return UploadFileState(
uploadTaskId,
file: fileInfo,
cancelToken: cancelToken
);
}
}
+7 -1
View File
@@ -1,5 +1,5 @@
{ {
"@@last_modified": "2023-02-03T11:24:09.273116", "@@last_modified": "2023-02-03T16:28:53.514637",
"initializing_data": "Initializing data...", "initializing_data": "Initializing data...",
"@initializing_data": { "@initializing_data": {
"type": "text", "type": "text",
@@ -2679,5 +2679,11 @@
"type": "text", "type": "text",
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
},
"thisImageCannotBeAdded": "This image cannot be added.",
"@thisImageCannotBeAdded": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
} }
} }
@@ -2754,7 +2754,7 @@ class AppLocalizations {
name: 'saveEmailAsDraftFailureWithSetErrorTypeOverQuota', name: 'saveEmailAsDraftFailureWithSetErrorTypeOverQuota',
); );
} }
String get mailBoxes { String get mailBoxes {
return Intl.message( return Intl.message(
'Mailboxes', 'Mailboxes',
@@ -2772,4 +2772,11 @@ class AppLocalizations {
'Hide mailbox', 'Hide mailbox',
name: 'hideMailBoxes'); name: 'hideMailBoxes');
} }
String get thisImageCannotBeAdded {
return Intl.message(
'This image cannot be added.',
name: 'thisImageCannotBeAdded'
);
}
} }
+1 -1
View File
@@ -1,8 +1,8 @@
import 'package:core/core.dart';
import 'package:core/data/model/query/query_parameter.dart'; import 'package:core/data/model/query/query_parameter.dart';
import 'package:core/data/network/config/service_path.dart'; import 'package:core/data/network/config/service_path.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/build_utils.dart';
import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';