diff --git a/core/lib/data/network/download/download_client.dart b/core/lib/data/network/download/download_client.dart index 31ad60439..b8be90520 100644 --- a/core/lib/data/network/download/download_client.dart +++ b/core/lib/data/network/download/download_client.dart @@ -40,15 +40,18 @@ class DownloadClient { String fileExtension, String fileName, { - Uint8List? bytesData, + String? filePath, double? maxWidth, bool? compress, } ) async { try { - if (bytesData == null) { + Uint8List? bytesData; + if (filePath == null || filePath.isEmpty) { log('DownloadClient::downloadImageAsBase64(): bytesData is NULL'); bytesData = await _dioClient.get(url, options: Options(responseType: ResponseType.bytes)); + } else { + bytesData = await File(filePath).readAsBytes(); } if (bytesData == null) { diff --git a/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart b/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart index 2575533da..deef7c58e 100644 --- a/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart +++ b/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart @@ -27,7 +27,7 @@ class ComposerDataSourceImpl extends ComposerDataSource { cid, fileInfo.fileExtension, fileInfo.fileName, - bytesData: fileInfo.bytes, + filePath: fileInfo.filePath, maxWidth: maxWidth, compress: compress); }).catchError(_exceptionThrower.throwException); diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 17ec069fa..2e16fac8e 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -1750,17 +1750,17 @@ class ComposerController extends BaseController { Future _selectFromFile() async { final filePickerResult = await FilePicker.platform.pickFiles( - type: FileType.image, - withData: PlatformInfo.isMobile, - withReadStream: PlatformInfo.isWeb); - final platformFile = filePickerResult?.files.single; - if (platformFile != null) { + type: FileType.image, + withData: PlatformInfo.isWeb + ); + if (filePickerResult?.files.isNotEmpty == true) { + PlatformFile platformFile = filePickerResult!.files.first; final fileSelected = FileInfo( - platformFile.name, - PlatformInfo.isWeb ? '' : platformFile.path ?? '', - platformFile.size, - bytes: platformFile.bytes, - readStream: platformFile.readStream); + platformFile.name, + PlatformInfo.isWeb ? '' : platformFile.path ?? '', + platformFile.size, + bytes: PlatformInfo.isWeb ? platformFile.bytes : null, + ); return InlineImage(ImageSource.local, fileInfo: fileSelected); } diff --git a/lib/features/composer/presentation/extensions/file_upload_extension.dart b/lib/features/composer/presentation/extensions/file_upload_extension.dart index 55a9a3566..3f6e26c25 100644 --- a/lib/features/composer/presentation/extensions/file_upload_extension.dart +++ b/lib/features/composer/presentation/extensions/file_upload_extension.dart @@ -2,7 +2,6 @@ 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'; @@ -26,9 +25,9 @@ extension FileUploadExtension on FileUpload { Future toFileInfo() async { if (base64Data != null) { - final bytesStream = await compute(convertBytesToStream, base64Data!); - return FileInfo.fromStream( - stream: bytesStream, + final bytes = await compute(convertBase64ToBytes, base64Data!); + return FileInfo.fromBytes( + bytes: bytes, name: name, size: size ); @@ -37,9 +36,8 @@ extension FileUploadExtension on FileUpload { } } - static Stream> convertBytesToStream(String base64) { + static Uint8List convertBase64ToBytes(String base64) { type_data.Uint8List decodeBytes = convert.base64Decode(base64); - final bytesStream = BodyBytesStream.fromBytes(decodeBytes); - return bytesStream; + return decodeBytes; } } \ No newline at end of file diff --git a/lib/features/login/data/network/config/authorization_interceptors.dart b/lib/features/login/data/network/config/authorization_interceptors.dart index e555c397d..aa3aa3f03 100644 --- a/lib/features/login/data/network/config/authorization_interceptors.dart +++ b/lib/features/login/data/network/config/authorization_interceptors.dart @@ -1,8 +1,10 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:core/utils/app_logger.dart'; import 'package:dio/dio.dart'; +import 'package:get/get_connect/http/src/request/request.dart'; import 'package:model/account/personal_account.dart'; import 'package:model/account/authentication_type.dart'; import 'package:model/oidc/oidc_configuration.dart'; @@ -12,6 +14,7 @@ import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuratio import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart'; +import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; class AuthorizationInterceptors extends QueuedInterceptorsWrapper { @@ -77,6 +80,7 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { @override void onError(DioError err, ErrorInterceptorHandler handler) async { logError('AuthorizationInterceptors::onError(): $err'); + final requestOptions = err.requestOptions; final extraInRequest = requestOptions.extra; var retries = extraInRequest[RETRY_KEY] ?? 0; @@ -110,16 +114,36 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { ]); _updateNewToken(newToken.toToken()); - final requestOptions = err.requestOptions; - requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token); + if (extraInRequest.containsKey(FileUploader.uploadAttachmentExtraKey)) { + final uploadExtra = extraInRequest[FileUploader.uploadAttachmentExtraKey]; - final response = await _dio.fetch(requestOptions); - return handler.resolve(response); + requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token); + requestOptions.headers[HttpHeaders.contentTypeHeader] = uploadExtra[FileUploader.typeExtraKey]; + requestOptions.headers[HttpHeaders.contentLengthHeader] = uploadExtra[FileUploader.sizeExtraKey]; + + final newOptions = Options( + method: requestOptions.method, + headers: requestOptions.headers, + ); + + final response = await _dio.request( + requestOptions.path, + data: BodyBytesStream.fromBytes(uploadExtra[FileUploader.bytesExtraKey]), + queryParameters: requestOptions.queryParameters, + options: newOptions, + ); + + return handler.resolve(response); + } else { + requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(newToken.token); + + final response = await _dio.fetch(requestOptions); + return handler.resolve(response); + } } else if (_validateToRetry(err, retries)) { log('AuthorizationInterceptors::onError:>> _validateToRetry | retries: $retries'); retries++; - final requestOptions = err.requestOptions; requestOptions.headers[HttpHeaders.authorizationHeader] = _getTokenAsBearerHeader(_token!.token); requestOptions.extra = {RETRY_KEY: retries}; diff --git a/lib/features/upload/data/network/file_uploader.dart b/lib/features/upload/data/network/file_uploader.dart index 271b43fa0..349c7c4cc 100644 --- a/lib/features/upload/data/network/file_uploader.dart +++ b/lib/features/upload/data/network/file_uploader.dart @@ -10,6 +10,7 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; +import 'package:get/get_connect/http/src/request/request.dart'; import 'package:model/email/attachment.dart'; import 'package:model/upload/file_info.dart'; import 'package:model/upload/upload_response.dart'; @@ -22,6 +23,12 @@ 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'; + final DioClient _dioClient; final worker.Executor _isolateExecutor; @@ -104,12 +111,23 @@ class FileUploader { final headerParam = _dioClient.getHeaders(); headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType; headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize; - final data = fileInfo.readStream; + + final mapExtra = { + uploadAttachmentExtraKey: { + platformExtraKey: 'web', + bytesExtraKey: fileInfo.bytes, + typeExtraKey: fileInfo.mimeType, + sizeExtraKey: fileInfo.fileSize, + } + }; final resultJson = await _dioClient.post( Uri.decodeFull(uploadUri.toString()), - options: Options(headers: headerParam), - data: data, + options: Options( + headers: headerParam, + extra: mapExtra + ), + data: BodyBytesStream.fromBytes(fileInfo.bytes!), cancelToken: cancelToken, onSendProgress: (count, total) { log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [${uploadId.id}] = $count'); diff --git a/lib/features/upload/domain/usecases/local_file_picker_interactor.dart b/lib/features/upload/domain/usecases/local_file_picker_interactor.dart index faf13c2e3..c61789267 100644 --- a/lib/features/upload/domain/usecases/local_file_picker_interactor.dart +++ b/lib/features/upload/domain/usecases/local_file_picker_interactor.dart @@ -1,9 +1,10 @@ -import 'package:core/core.dart'; +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:flutter/foundation.dart'; -import 'package:model/model.dart'; +import 'package:model/upload/file_info.dart'; import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart'; class LocalFilePickerInteractor { @@ -12,14 +13,20 @@ class LocalFilePickerInteractor { Stream> execute({FileType fileType = FileType.any}) async* { try { - final filesResult = await FilePicker.platform.pickFiles(type: fileType, allowMultiple: true, withReadStream: true); + final filesResult = await FilePicker.platform.pickFiles( + type: fileType, + allowMultiple: true, + withData: PlatformInfo.isWeb + ); if (filesResult != null && filesResult.files.isNotEmpty) { final fileInfoResults = filesResult.files .map((platformFile) => FileInfo( platformFile.name, - kIsWeb ? '' : platformFile.path ?? '', + PlatformInfo.isWeb ? '' : platformFile.path ?? '', platformFile.size, - readStream: platformFile.readStream)).toList(); + bytes: PlatformInfo.isWeb ? platformFile.bytes : null + )) + .toList(); yield Right(LocalFilePickerSuccess(fileInfoResults)); } else { yield Left(LocalFilePickerCancel()); diff --git a/lib/features/upload/presentation/controller/upload_controller.dart b/lib/features/upload/presentation/controller/upload_controller.dart index 60e2fedf8..c1635f595 100644 --- a/lib/features/upload/presentation/controller/upload_controller.dart +++ b/lib/features/upload/presentation/controller/upload_controller.dart @@ -265,7 +265,6 @@ class UploadController extends BaseController { } void _handleUploadAttachmentsSuccess(SuccessAttachmentUploadState success) { - log('UploadController::_handleUploadAttachmentsSuccess(): $success'); if (currentContext != null && currentOverlayContext != null && _uploadingStateFiles.allSuccess) { _appToast.showToastSuccessMessage( currentOverlayContext!, diff --git a/model/lib/upload/file_info.dart b/model/lib/upload/file_info.dart index 60580b06b..924f5e86e 100644 --- a/model/lib/upload/file_info.dart +++ b/model/lib/upload/file_info.dart @@ -7,21 +7,20 @@ class FileInfo with EquatableMixin { final String fileName; final String filePath; final int fileSize; - final Stream>? readStream; final Uint8List? bytes; - FileInfo(this.fileName, this.filePath, this.fileSize, {this.readStream, this.bytes}); + FileInfo(this.fileName, this.filePath, this.fileSize, {this.bytes}); factory FileInfo.empty() { return FileInfo('', '', 0); } - factory FileInfo.fromStream({ - required Stream> stream, + factory FileInfo.fromBytes({ + required Uint8List bytes, String? name, int? size }) { - return FileInfo(name ?? '', '', size ?? 0, readStream: stream); + return FileInfo(name ?? '', '', size ?? 0, bytes: bytes); } String get fileExtension => fileName.split('.').last; @@ -29,5 +28,5 @@ class FileInfo with EquatableMixin { String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/octet-stream'; @override - List get props => [fileName, filePath, fileSize, readStream, bytes]; + List get props => [fileName, filePath, fileSize, bytes]; } \ No newline at end of file