diff --git a/core/lib/utils/string_convert.dart b/core/lib/utils/string_convert.dart index 217d0a6fe..1a229d583 100644 --- a/core/lib/utils/string_convert.dart +++ b/core/lib/utils/string_convert.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:core/utils/app_logger.dart'; import 'package:core/domain/exceptions/string_exception.dart'; +import 'package:http_parser/http_parser.dart'; class StringConvert { static const String separatorPattern = r'[ ,;]+'; @@ -73,4 +74,29 @@ class StringConvert { static String toUrlScheme(String hostScheme) { return '$hostScheme://'; } + + static Uint8List convertBase64ImageTagToBytes(String base64ImageTag) { + if (!base64ImageTag.contains('base64,')) { + throw ArgumentError('The string is not valid Base64 data from an tag.'); + } + + final base64Data = base64ImageTag.split(',').last; + + return base64Decode(base64Data); + } + + static MediaType? getMediaTypeFromBase64ImageTag(String base64ImageTag) { + try { + if (!base64ImageTag.startsWith("data:") || !base64ImageTag.contains(";base64,")) { + return null; + } + + final mimeType = base64ImageTag.split(";")[0].split(":")[1]; + log('StringConvert::getMediaTypeFromBase64ImageTag:mimeType = $mimeType'); + return MediaType.parse(mimeType); + } catch (e) { + logError('StringConvert::getMimeTypeFromBase64ImageTag:Exception = $e'); + return null; + } + } } diff --git a/lib/features/composer/data/repository/composer_repository_impl.dart b/lib/features/composer/data/repository/composer_repository_impl.dart index dfaf649e6..6cfc4f016 100644 --- a/lib/features/composer/data/repository/composer_repository_impl.dart +++ b/lib/features/composer/data/repository/composer_repository_impl.dart @@ -54,15 +54,14 @@ class ComposerRepositoryImpl extends ComposerRepository { String emailContent = createEmailRequest.emailContent; Set emailAttachments = Set.from(createEmailRequest.createAttachments()); - if (createEmailRequest.inlineAttachments?.isNotEmpty == true) { - final tupleContentInlineAttachments = await _replaceImageBase64ToImageCID( - emailContent: emailContent, - inlineAttachments: createEmailRequest.inlineAttachments! - ); + final tupleContentInlineAttachments = await _replaceImageBase64ToImageCID( + emailContent: emailContent, + inlineAttachments: createEmailRequest.inlineAttachments ?? {}, + uploadUri: createEmailRequest.uploadUri, + ); - emailContent = tupleContentInlineAttachments.value1; - emailAttachments.addAll(tupleContentInlineAttachments.value2); - } + emailContent = tupleContentInlineAttachments.value1; + emailAttachments.addAll(tupleContentInlineAttachments.value2); emailContent = await _removeCollapsedExpandedSignatureEffect(emailContent: emailContent); @@ -83,18 +82,23 @@ class ComposerRepositoryImpl extends ComposerRepository { Future>> _replaceImageBase64ToImageCID({ required String emailContent, - required Map inlineAttachments + required Map inlineAttachments, + required Uri? uploadUri, }) { try { return _htmlDataSource.replaceImageBase64ToImageCID( emailContent: emailContent, - inlineAttachments: inlineAttachments); + inlineAttachments: inlineAttachments, + uploadUri: uploadUri, + ); } catch (e) { logError('ComposerRepositoryImpl::_replaceImageBase64ToImageCID: Exception: $e'); return Future.value( Tuple2( emailContent, - inlineAttachments.values.toList().toEmailBodyPart(charset: Constant.base64Charset) + inlineAttachments.isNotEmpty + ? inlineAttachments.values.toList().toEmailBodyPart(charset: Constant.base64Charset) + : {}, ) ); } diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index 24f8cd7f5..5ab28cdf1 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -65,29 +65,13 @@ import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart'; import 'package:tmail_ui_user/features/upload/domain/usecases/local_image_picker_interactor.dart'; import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.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/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; import 'package:uuid/uuid.dart'; -import 'package:worker_manager/worker_manager.dart'; class ComposerBindings extends BaseBindings { - @override - void dependencies() { - _bindingsUtils(); - super.dependencies(); - } - - void _bindingsUtils() { - Get.lazyPut(() => FileUploader( - Get.find(tag: BindingTag.isolateTag), - Get.find(), - Get.find(), - )); - } - @override void bindingsDataSourceImpl() { Get.lazyPut(() => AttachmentUploadDataSourceImpl( diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 277ef6019..442cfc6eb 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -20,6 +20,7 @@ import 'package:html/parser.dart'; import 'package:html_editor_enhanced/html_editor.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; @@ -474,24 +475,35 @@ class ComposerController extends BaseController mailboxDashBoardController.sessionCurrent!.username); } + Uri? _getUploadUriFromSession(Session session, AccountId accountId) { + try { + return session.getUploadUri(accountId, jmapUrl: dynamicUrlInterceptors.jmapUrl); + } catch (e) { + logError('ComposerController::_getUploadUriFromSession:Exception = $e'); + return null; + } + } + Future _generateCreateEmailRequest() async { - if (composerArguments.value == null || - mailboxDashBoardController.sessionCurrent == null || - mailboxDashBoardController.accountId.value == null - ) { + final arguments = composerArguments.value; + final session = mailboxDashBoardController.sessionCurrent; + final accountId = mailboxDashBoardController.accountId.value; + + if (arguments == null || session == null || accountId == null) { log('ComposerController::_generateCreateEmailRequest: SESSION or ACCOUNT_ID or ARGUMENTS is NULL'); return null; } final emailContent = await getContentInEditor(); - + final uploadUri = _getUploadUriFromSession(session, accountId); + return CreateEmailRequest( - session: mailboxDashBoardController.sessionCurrent!, - accountId: mailboxDashBoardController.accountId.value!, - emailActionType: composerArguments.value!.emailActionType, + session: session, + accountId: accountId, + emailActionType: arguments.emailActionType, subject: subjectEmail.value ?? '', emailContent: emailContent, - fromSender: composerArguments.value!.presentationEmail?.from ?? {}, + fromSender: arguments.presentationEmail?.from ?? {}, toRecipients: listToEmailAddress.toSet(), ccRecipients: listCcEmailAddress.toSet(), bccRecipients: listBccEmailAddress.toSet(), @@ -505,12 +517,13 @@ class ComposerController extends BaseController sentMailboxId: getSentMailboxIdForComposer(), draftsMailboxId: getDraftMailboxIdForComposer(), draftsEmailId: getDraftEmailId(), - answerForwardEmailId: composerArguments.value!.presentationEmail?.id, - unsubscribeEmailId: composerArguments.value!.previousEmailId, - messageId: composerArguments.value!.messageId, - references: composerArguments.value!.references, - emailSendingQueue: composerArguments.value!.sendingEmail, - displayMode: screenDisplayMode.value + answerForwardEmailId: arguments.presentationEmail?.id, + unsubscribeEmailId: arguments.previousEmailId, + messageId: arguments.messageId, + references: arguments.references, + emailSendingQueue: arguments.sendingEmail, + displayMode: screenDisplayMode.value, + uploadUri: uploadUri, ); } @@ -1043,10 +1056,11 @@ class ComposerController extends BaseController } void _handleSendMessages(BuildContext context) async { - if (composerArguments.value == null || - mailboxDashBoardController.sessionCurrent == null || - mailboxDashBoardController.accountId.value == null - ) { + final arguments = composerArguments.value; + final session = mailboxDashBoardController.sessionCurrent; + final accountId = mailboxDashBoardController.accountId.value; + + if (arguments == null || session == null || accountId == null) { log('ComposerController::_handleSendMessages: SESSION or ACCOUNT_ID or ARGUMENTS is NULL'); _sendButtonState = ButtonState.enabled; _closeComposerAction(closeOverlays: true); @@ -1058,9 +1072,14 @@ class ComposerController extends BaseController } final emailContent = await getContentInEditor(); + final uploadUri = _getUploadUriFromSession(session, accountId); final cancelToken = CancelToken(); final resultState = await _showSendingMessageDialog( + session: session, + accountId: accountId, + arguments: arguments, emailContent: emailContent, + uploadUri: uploadUri, cancelToken: cancelToken ); log('ComposerController::_handleSendMessages: resultState = $resultState'); @@ -1080,18 +1099,22 @@ class ComposerController extends BaseController } Future _showSendingMessageDialog({ + required Session session, + required AccountId accountId, + required ComposerArguments arguments, required String emailContent, - CancelToken? cancelToken + required Uri? uploadUri, + CancelToken? cancelToken, }) { final childWidget = PointerInterceptor( child: SendingMessageDialogView( createEmailRequest: CreateEmailRequest( - session: mailboxDashBoardController.sessionCurrent!, - accountId: mailboxDashBoardController.accountId.value!, - emailActionType: composerArguments.value!.emailActionType, + session: session, + accountId: accountId, + emailActionType: arguments.emailActionType, subject: subjectEmail.value ?? '', emailContent: emailContent, - fromSender: composerArguments.value!.presentationEmail?.from ?? {}, + fromSender: arguments.presentationEmail?.from ?? {}, toRecipients: listToEmailAddress.toSet(), ccRecipients: listCcEmailAddress.toSet(), bccRecipients: listBccEmailAddress.toSet(), @@ -1104,12 +1127,13 @@ class ComposerController extends BaseController outboxMailboxId: getOutboxMailboxIdForComposer(), sentMailboxId: getSentMailboxIdForComposer(), draftsEmailId: getDraftEmailId(), - answerForwardEmailId: composerArguments.value!.presentationEmail?.id, - unsubscribeEmailId: composerArguments.value!.previousEmailId, - messageId: composerArguments.value!.messageId, - references: composerArguments.value!.references, - emailSendingQueue: composerArguments.value!.sendingEmail, - displayMode: screenDisplayMode.value + answerForwardEmailId: arguments.presentationEmail?.id, + unsubscribeEmailId: arguments.previousEmailId, + messageId: arguments.messageId, + references: arguments.references, + emailSendingQueue: arguments.sendingEmail, + displayMode: screenDisplayMode.value, + uploadUri: uploadUri, ), createNewAndSendEmailInteractor: _createNewAndSendEmailInteractor, onCancelSendingEmailAction: _handleCancelSendingMessage, @@ -1350,9 +1374,13 @@ class ComposerController extends BaseController _saveToDraftButtonState = ButtonState.disabled; - if (composerArguments.value == null || - mailboxDashBoardController.sessionCurrent == null || - mailboxDashBoardController.accountId.value == null || + final arguments = composerArguments.value; + final session = mailboxDashBoardController.sessionCurrent; + final accountId = mailboxDashBoardController.accountId.value; + + if (arguments == null || + session == null || + accountId == null || getDraftMailboxIdForComposer() == null ) { log('ComposerController::handleClickSaveAsDraftsButton: SESSION or ACCOUNT_ID or ARGUMENTS is NULL'); @@ -1361,9 +1389,14 @@ class ComposerController extends BaseController } final emailContent = await getContentInEditor(); + final uploadUri = _getUploadUriFromSession(session, accountId); final cancelToken = CancelToken(); final resultState = await _showSavingMessageToDraftsDialog( + session: session, + accountId: accountId, + arguments: arguments, emailContent: emailContent, + uploadUri: uploadUri, draftEmailId: _emailIdEditing, cancelToken: cancelToken ); @@ -2291,9 +2324,13 @@ class ComposerController extends BaseController } void _handleSaveMessageToDraft(BuildContext context) async { - if (composerArguments.value == null || - mailboxDashBoardController.sessionCurrent == null || - mailboxDashBoardController.accountId.value == null || + final arguments = composerArguments.value; + final session = mailboxDashBoardController.sessionCurrent; + final accountId = mailboxDashBoardController.accountId.value; + + if (arguments == null || + session == null || + accountId == null || getDraftMailboxIdForComposer() == null ) { log('ComposerController::_handleSaveMessageToDraft: SESSION or ACCOUNT_ID or ARGUMENTS is NULL'); @@ -2305,11 +2342,16 @@ class ComposerController extends BaseController popBack(); final emailContent = await getContentInEditor(); + final uploadUri = _getUploadUriFromSession(session, accountId); final draftEmailId = getDraftEmailId(); log('ComposerController::_handleSaveMessageToDraft: draftEmailId = $draftEmailId'); final cancelToken = CancelToken(); final resultState = await _showSavingMessageToDraftsDialog( + session: session, + accountId: accountId, + arguments: arguments, emailContent: emailContent, + uploadUri: uploadUri, draftEmailId: draftEmailId, cancelToken: cancelToken ); @@ -2346,19 +2388,23 @@ class ComposerController extends BaseController } Future _showSavingMessageToDraftsDialog({ + required Session session, + required AccountId accountId, + required ComposerArguments arguments, required String emailContent, + required Uri? uploadUri, EmailId? draftEmailId, CancelToken? cancelToken, }) { final childWidget = PointerInterceptor( child: SavingMessageDialogView( createEmailRequest: CreateEmailRequest( - session: mailboxDashBoardController.sessionCurrent!, - accountId: mailboxDashBoardController.accountId.value!, - emailActionType: composerArguments.value!.emailActionType, + session: session, + accountId: accountId, + emailActionType: arguments.emailActionType, subject: subjectEmail.value ?? '', emailContent: emailContent, - fromSender: composerArguments.value!.presentationEmail?.from ?? {}, + fromSender: arguments.presentationEmail?.from ?? {}, toRecipients: listToEmailAddress.toSet(), ccRecipients: listCcEmailAddress.toSet(), bccRecipients: listBccEmailAddress.toSet(), @@ -2371,12 +2417,13 @@ class ComposerController extends BaseController sentMailboxId: getSentMailboxIdForComposer(), draftsMailboxId: getDraftMailboxIdForComposer(), draftsEmailId: draftEmailId, - answerForwardEmailId: composerArguments.value!.presentationEmail?.id, - unsubscribeEmailId: composerArguments.value!.previousEmailId, - messageId: composerArguments.value!.messageId, - references: composerArguments.value!.references, - emailSendingQueue: composerArguments.value!.sendingEmail, - displayMode: screenDisplayMode.value + answerForwardEmailId: arguments.presentationEmail?.id, + unsubscribeEmailId: arguments.previousEmailId, + messageId: arguments.messageId, + references: arguments.references, + emailSendingQueue: arguments.sendingEmail, + displayMode: screenDisplayMode.value, + uploadUri: uploadUri, ), createNewAndSaveEmailToDraftsInteractor: _createNewAndSaveEmailToDraftsInteractor, onCancelSavingEmailToDraftsAction: _handleCancelSavingMessageToDrafts, diff --git a/lib/features/composer/presentation/model/create_email_request.dart b/lib/features/composer/presentation/model/create_email_request.dart index 4ce4d9efb..aee27e6f9 100644 --- a/lib/features/composer/presentation/model/create_email_request.dart +++ b/lib/features/composer/presentation/model/create_email_request.dart @@ -38,6 +38,7 @@ class CreateEmailRequest with EquatableMixin { final MessageIdsHeaderValue? references; final SendingEmail? emailSendingQueue; final ScreenDisplayMode displayMode; + final Uri? uploadUri; CreateEmailRequest({ required this.session, @@ -64,7 +65,8 @@ class CreateEmailRequest with EquatableMixin { this.messageId, this.references, this.emailSendingQueue, - this.displayMode = ScreenDisplayMode.normal + this.displayMode = ScreenDisplayMode.normal, + this.uploadUri, }); @override @@ -94,5 +96,6 @@ class CreateEmailRequest with EquatableMixin { references, emailSendingQueue, displayMode, + uploadUri, ]; } \ No newline at end of file diff --git a/lib/features/email/data/datasource/html_datasource.dart b/lib/features/email/data/datasource/html_datasource.dart index 354577462..ec9220ed7 100644 --- a/lib/features/email/data/datasource/html_datasource.dart +++ b/lib/features/email/data/datasource/html_datasource.dart @@ -18,7 +18,8 @@ abstract class HtmlDataSource { Future>> replaceImageBase64ToImageCID({ required String emailContent, - required Map inlineAttachments + required Map inlineAttachments, + required Uri? uploadUri, }); Future removeCollapsedExpandedSignatureEffect({required String emailContent}); diff --git a/lib/features/email/data/datasource_impl/html_datasource_impl.dart b/lib/features/email/data/datasource_impl/html_datasource_impl.dart index 5d695d739..e5e02faa7 100644 --- a/lib/features/email/data/datasource_impl/html_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/html_datasource_impl.dart @@ -42,12 +42,14 @@ class HtmlDataSourceImpl extends HtmlDataSource { @override Future>> replaceImageBase64ToImageCID({ required String emailContent, - required Map inlineAttachments + required Map inlineAttachments, + required Uri? uploadUri, }) { return Future.sync(() async { return await _htmlAnalyzer.replaceImageBase64ToImageCID( emailContent: emailContent, - inlineAttachments: inlineAttachments + inlineAttachments: inlineAttachments, + uploadUri: uploadUri, ); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/email/data/local/html_analyzer.dart b/lib/features/email/data/local/html_analyzer.dart index 97ab0d843..ebc30749a 100644 --- a/lib/features/email/data/local/html_analyzer.dart +++ b/lib/features/email/data/local/html_analyzer.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:collection/collection.dart'; import 'package:core/data/constants/constant.dart'; import 'package:core/presentation/utils/html_transformer/html_transform.dart'; @@ -5,6 +7,7 @@ import 'package:core/presentation/utils/html_transformer/text/persist_preformatt import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/string_convert.dart'; import 'package:dartz/dartz.dart'; import 'package:html/parser.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; @@ -12,13 +15,21 @@ import 'package:model/email/attachment.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/email_content_type.dart'; import 'package:model/extensions/attachment_extension.dart'; +import 'package:model/upload/file_info.dart'; +import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; +import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; +import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; +import 'package:uuid/uuid.dart'; class HtmlAnalyzer { + static const String cidPrefixKey = 'cid:'; final HtmlTransform _htmlTransform; + final FileUploader _fileUploader; + final Uuid _uuid; - HtmlAnalyzer(this._htmlTransform); + HtmlAnalyzer(this._htmlTransform, this._fileUploader, this._uuid); Future transformEmailContent( EmailContent emailContent, @@ -112,35 +123,71 @@ class HtmlAnalyzer { Future>> replaceImageBase64ToImageCID({ required String emailContent, - required Map inlineAttachments + required Map inlineAttachments, + required Uri? uploadUri, }) async { final document = parse(emailContent); - final listImgTag = document.querySelectorAll('img[src^="data:image/"][id^="cid:"]'); + final listImgTag = document.querySelectorAll('img[src^="data:image/"]'); + log('HtmlAnalyzer::replaceImageBase64ToImageCID:listImgTagLength = ${listImgTag.length} | inlineAttachments = ${inlineAttachments.length}'); - final listInlineAttachment = await Future.wait(listImgTag.map((imgTag) async { - final idImg = imgTag.attributes['id']; - final cid = idImg!.replaceFirst('cid:', '').trim(); - imgTag.attributes['src'] = 'cid:$cid'; - imgTag.attributes.remove('id'); - return cid; - })).then((listCid) { - final listInlineAttachment = listCid - .map((cid) { - if (inlineAttachments.containsKey(cid)) { - return inlineAttachments[cid]!.toEmailBodyPart(charset: Constant.base64Charset); - } else { - return null; - } - }) - .whereNotNull() - .toSet(); + if (listImgTag.isEmpty) { + return Tuple2( + emailContent, + inlineAttachments.isNotEmpty + ? inlineAttachments.values.toList().toEmailBodyPart(charset: Constant.base64Charset) + : {}, + ); + } - return listInlineAttachment; - }); + final Set inlineAttachmentsSet = {}; + final List> asyncTasks = []; + + for (final imgTag in listImgTag) { + late final LinkedHashMap attributes = imgTag.attributes; + final idImg = attributes['id']; + final imageSrc = attributes['src']; + + if (imageSrc?.isEmpty ?? true) continue; + + if (idImg?.startsWith(cidPrefixKey) == true) { + final cid = idImg!.substring(cidPrefixKey.length).trim(); + attributes['src'] = '$cidPrefixKey$cid'; + attributes.remove('id'); + + final attachment = inlineAttachments[cid]; + log('HtmlAnalyzer::replaceImageBase64ToImageCID:attachment = $attachment'); + if (attachment != null) { + inlineAttachmentsSet.add(attachment.toEmailBodyPart(charset: Constant.base64Charset)); + } + continue; + } + + if (uploadUri == null) continue; + + asyncTasks.add(_retrieveAttachmentFromUpload( + uploadUri: uploadUri, + base64ImageTag: imageSrc!, + ).then((newAttachment) { + if (newAttachment == null) return; + + final newInlineAttachment = newAttachment.toAttachmentWithDisposition( + disposition: ContentDisposition.inline, + cid: _uuid.v1(), + ); + final newCid = newInlineAttachment.cid; + inlineAttachments[newCid!] = newInlineAttachment; + attributes['src'] = '$cidPrefixKey$newCid'; + + inlineAttachmentsSet.add(newInlineAttachment.toEmailBodyPart(charset: Constant.base64Charset)); + })); + } + + if (asyncTasks.isNotEmpty) { + await Future.wait(asyncTasks); + } final newContent = document.body?.innerHtml ?? emailContent; - - return Tuple2(newContent, listInlineAttachment); + return Tuple2(newContent, inlineAttachmentsSet); } Future removeCollapsedExpandedSignatureEffect({required String emailContent}) async { @@ -162,4 +209,32 @@ class HtmlAnalyzer { log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: AFTER = $newContent'); return newContent; } + + Future _retrieveAttachmentFromUpload({ + required Uri uploadUri, + required String base64ImageTag, + }) async { + try { + final imageBytes = StringConvert.convertBase64ImageTagToBytes(base64ImageTag); + final mediaType = StringConvert.getMediaTypeFromBase64ImageTag(base64ImageTag); + log('HtmlAnalyzer::_retrieveAttachmentFromUpload: mimeType = ${mediaType?.mimeType} | imageBytesLength = ${imageBytes.length}'); + final generateId = _uuid.v1(); + final fileInfo = FileInfo.fromBytes( + bytes: imageBytes, + name: '$generateId.${mediaType?.subtype ?? 'png'}', + type: mediaType?.mimeType, + ); + + final attachment = await _fileUploader.uploadAttachment( + UploadTaskId(generateId), + fileInfo, + uploadUri, + ); + log('HtmlAnalyzer::_retrieveAttachmentFromUpload:Attachment = $attachment'); + return attachment; + } catch (e) { + logError('HtmlAnalyzer::_retrieveAttachmentFromUpload:Exception = $e'); + return null; + } + } } \ No newline at end of file diff --git a/lib/features/email/domain/extensions/detailed_email_hive_cache_extension.dart b/lib/features/email/domain/extensions/detailed_email_hive_cache_extension.dart index 564648a13..f6eb30ff7 100644 --- a/lib/features/email/domain/extensions/detailed_email_hive_cache_extension.dart +++ b/lib/features/email/domain/extensions/detailed_email_hive_cache_extension.dart @@ -13,6 +13,7 @@ extension DetailedEmailHiveCacheExtension on DetailedEmailHiveCache { emailId: EmailId(Id(emailId)), createdTime: timeSaved, attachments: attachments?.toAttachment(), + emailContentPath: emailContentPath, headers: headers?.toSetEmailHeader(), keywords: keywords != null ? Map.fromIterables(keywords!.keys.map((value) => KeyWordIdentifier(value)), keywords!.values) diff --git a/lib/features/email/domain/usecases/get_email_content_interactor.dart b/lib/features/email/domain/usecases/get_email_content_interactor.dart index 19622cbc5..90fba7772 100644 --- a/lib/features/email/domain/usecases/get_email_content_interactor.dart +++ b/lib/features/email/domain/usecases/get_email_content_interactor.dart @@ -68,7 +68,7 @@ class GetEmailContentInteractor { additionalProperties: additionalProperties); final listAttachments = email.allAttachments.getListAttachmentsDisplayedOutside(email.htmlBodyAttachments); final listInlineImages = email.allAttachments.listAttachmentsDisplayedInContent; - + log('GetEmailContentInteractor::_getContentEmailFromServer: listAttachments = ${listAttachments.length} | listInlineImages = ${listInlineImages.length}'); if (email.emailContentList.isNotEmpty) { final mapCidImageDownloadUrl = listInlineImages.toMapCidImageDownloadUrl( accountId: accountId, @@ -109,8 +109,8 @@ class GetEmailContentInteractor { {Properties? additionalProperties} ) async* { try { - log('GetEmailContentInteractor::_getStoredOpenedEmail(): CALLED'); final detailedEmail = await emailRepository.getStoredOpenedEmail(session, accountId, emailId); + log('GetEmailContentInteractor::_getStoredOpenedEmail: attachments = ${detailedEmail.attachments?.length} | inlineImages = ${detailedEmail.inlineImages?.length}'); yield Right(GetEmailContentFromCacheSuccess( htmlEmailContent: detailedEmail.htmlEmailContent ?? '', attachments: detailedEmail.attachments ?? [], @@ -144,8 +144,8 @@ class GetEmailContentInteractor { {Properties? additionalProperties} ) async* { try { - log('GetEmailContentInteractor::_getStoredNewEmail():CALLED'); final detailedEmail = await emailRepository.getStoredNewEmail(session, accountId, emailId); + log('GetEmailContentInteractor::_getStoredNewEmail: attachments = ${detailedEmail.attachments?.length} | inlineImages = ${detailedEmail.inlineImages?.length}'); yield Right(GetEmailContentFromCacheSuccess( htmlEmailContent: detailedEmail.htmlEmailContent ?? '', attachments: detailedEmail.attachments ?? [], diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 9edd158a7..6aa704c31 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -686,6 +686,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { emailId: currentEmail!.id!, createdTime: currentEmail?.receivedAt?.value ?? DateTime.now(), attachments: success.attachments, + inlineImages: success.inlineImages, headers: success.emailCurrent?.headers, keywords: success.emailCurrent?.keywords, htmlEmailContent: success.htmlEmailContent, diff --git a/lib/features/public_asset/presentation/public_asset_bindings.dart b/lib/features/public_asset/presentation/public_asset_bindings.dart index 714e28d71..691e5b31e 100644 --- a/lib/features/public_asset/presentation/public_asset_bindings.dart +++ b/lib/features/public_asset/presentation/public_asset_bindings.dart @@ -1,7 +1,5 @@ -import 'package:core/data/network/dio_client.dart'; import 'package:core/data/network/download/download_client.dart'; import 'package:core/utils/application_manager.dart'; -import 'package:core/utils/file_utils.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; @@ -29,7 +27,6 @@ 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/remote_exception_thrower.dart'; import 'package:uuid/uuid.dart'; -import 'package:worker_manager/worker_manager.dart'; class PublicAssetBindings extends BaseBindings { PublicAssetBindings(this._publicAssetArguments); @@ -38,12 +35,6 @@ class PublicAssetBindings extends BaseBindings { @override void dependencies() { - Get.lazyPut( - () => FileUploader( - Get.find(tag: BindingTag.isolateTag), - Get.find(), - Get.find()), - tag: BindingTag.publicAssetBindingsTag); Get.lazyPut( () => PublicAssetApi(Get.find(), Get.find()), tag: BindingTag.publicAssetBindingsTag); @@ -82,7 +73,7 @@ class PublicAssetBindings extends BaseBindings { void bindingsDataSourceImpl() { Get.lazyPut( () => AttachmentUploadDataSourceImpl( - Get.find(tag: BindingTag.publicAssetBindingsTag), + Get.find(), Get.find(), Get.find()), tag: BindingTag.publicAssetBindingsTag); diff --git a/lib/features/upload/data/model/upload_file_arguments.dart b/lib/features/upload/data/model/upload_file_arguments.dart index 3ec427fb8..426296f41 100644 --- a/lib/features/upload/data/model/upload_file_arguments.dart +++ b/lib/features/upload/data/model/upload_file_arguments.dart @@ -2,8 +2,8 @@ import 'package:core/data/network/dio_client.dart'; import 'package:core/utils/file_utils.dart'; import 'package:equatable/equatable.dart'; +import 'package:model/upload/file_info.dart'; import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger.dart'; -import 'package:tmail_ui_user/features/upload/domain/model/mobile_file_upload.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; class UploadFileArguments with EquatableMixin { @@ -11,7 +11,7 @@ class UploadFileArguments with EquatableMixin { final DioClient dioClient; final FileUtils fileUtils; final UploadTaskId uploadId; - final MobileFileUpload mobileFileUpload; + final FileInfo fileInfo; final Uri uploadUri; final RootIsolateToken isolateToken; @@ -19,7 +19,7 @@ class UploadFileArguments with EquatableMixin { this.dioClient, this.fileUtils, this.uploadId, - this.mobileFileUpload, + this.fileInfo, this.uploadUri, this.isolateToken, ); @@ -29,7 +29,7 @@ class UploadFileArguments with EquatableMixin { dioClient, fileUtils, uploadId, - mobileFileUpload, + fileInfo, uploadUri, isolateToken, ]; diff --git a/lib/features/upload/data/network/file_uploader.dart b/lib/features/upload/data/network/file_uploader.dart index a424357fb..754054d1a 100644 --- a/lib/features/upload/data/network/file_uploader.dart +++ b/lib/features/upload/data/network/file_uploader.dart @@ -19,7 +19,6 @@ import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_me import 'package:tmail_ui_user/features/caching/config/hive_cache_config.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/extensions/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/main/exceptions/isolate_exception.dart'; @@ -43,31 +42,33 @@ class FileUploader { Future uploadAttachment( UploadTaskId uploadId, - StreamController> onSendController, FileInfo fileInfo, Uri uploadUri, - {CancelToken? cancelToken} + { + CancelToken? cancelToken, + StreamController>? onSendController, + } ) async { if (PlatformInfo.isWeb) { return _handleUploadAttachmentActionOnWeb( uploadId, - onSendController, fileInfo, uploadUri, - cancelToken: cancelToken); + cancelToken: cancelToken, + onSendController: onSendController, + ); } else { final rootIsolateToken = RootIsolateToken.instance; if (rootIsolateToken == null) { throw CanNotGetRootIsolateToken(); } - final mobileFileUpload = fileInfo.toMobileFileUpload(); return await _isolateExecutor.execute( arg1: UploadFileArguments( _dioClient, _fileUtils, uploadId, - mobileFileUpload, + fileInfo, uploadUri, rootIsolateToken, ), @@ -75,7 +76,7 @@ class FileUploader { notification: (value) { if (value is Success) { log('FileUploader::uploadAttachment(): onUpdateProgress: $value'); - onSendController.add(Right(value)); + onSendController?.add(Right(value)); } } ) @@ -94,12 +95,15 @@ class FileUploader { await HiveCacheConfig.instance.setUp(); final headerParam = argsUpload.dioClient.getHeaders(); - headerParam[HttpHeaders.contentTypeHeader] = argsUpload.mobileFileUpload.mimeType; - headerParam[HttpHeaders.contentLengthHeader] = argsUpload.mobileFileUpload.fileSize; + headerParam[HttpHeaders.contentTypeHeader] = argsUpload.fileInfo.mimeType; + headerParam[HttpHeaders.contentLengthHeader] = argsUpload.fileInfo.fileSize; final mapExtra = { uploadAttachmentExtraKey: { - filePathExtraKey: argsUpload.mobileFileUpload.filePath, + if (argsUpload.fileInfo.filePath?.isNotEmpty == true) + filePathExtraKey: argsUpload.fileInfo.filePath, + if (argsUpload.fileInfo.bytes?.isNotEmpty == true) + streamDataExtraKey: BodyBytesStream.fromBytes(argsUpload.fileInfo.bytes!), } }; @@ -109,31 +113,39 @@ class FileUploader { headers: headerParam, extra: mapExtra ), - data: File(argsUpload.mobileFileUpload.filePath).openRead(), + data: argsUpload.fileInfo.filePath?.isNotEmpty == true + ? File(argsUpload.fileInfo.filePath!).openRead() + : argsUpload.fileInfo.bytes != null + ? BodyBytesStream.fromBytes(argsUpload.fileInfo.bytes!) + : null, onSendProgress: (count, total) { log('FileUploader::_handleUploadAttachmentAction():onSendProgress: FILE[${argsUpload.uploadId.id}] : { PROGRESS = $count | TOTAL = $total}'); sendPort.send( UploadingAttachmentUploadState( argsUpload.uploadId, count, - argsUpload.mobileFileUpload.fileSize + argsUpload.fileInfo.fileSize ) ); } ); log('FileUploader::_handleUploadAttachmentAction(): RESULT_JSON = $resultJson'); - if (argsUpload.mobileFileUpload.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) { - final fileCharset = await argsUpload.fileUtils.getCharsetFromBytes( - File(argsUpload.mobileFileUpload.filePath).readAsBytesSync() - ); + if (argsUpload.fileInfo.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) { + final fileBytes = argsUpload.fileInfo.filePath?.isNotEmpty == true + ? File(argsUpload.fileInfo.filePath!).readAsBytesSync() + : argsUpload.fileInfo.bytes; + + final fileCharset = fileBytes != null + ? await argsUpload.fileUtils.getCharsetFromBytes(fileBytes) + : null; return _parsingResponse( resultJson: resultJson, - fileName: argsUpload.mobileFileUpload.fileName, - fileCharset: fileCharset.toLowerCase()); + fileName: argsUpload.fileInfo.fileName, + fileCharset: fileCharset?.toLowerCase()); } else { return _parsingResponse( resultJson: resultJson, - fileName: argsUpload.mobileFileUpload.fileName); + fileName: argsUpload.fileInfo.fileName); } } on DioError catch (exception) { logError('FileUploader::_handleUploadAttachmentAction():DioError: $exception'); @@ -149,10 +161,12 @@ class FileUploader { Future _handleUploadAttachmentActionOnWeb( UploadTaskId uploadId, - StreamController> onSendController, FileInfo fileInfo, Uri uploadUri, - {CancelToken? cancelToken} + { + CancelToken? cancelToken, + StreamController>? onSendController, + } ) async { final headerParam = _dioClient.getHeaders(); headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType; @@ -160,7 +174,8 @@ class FileUploader { final mapExtra = { uploadAttachmentExtraKey: { - streamDataExtraKey: BodyBytesStream.fromBytes(fileInfo.bytes!), + if (fileInfo.bytes?.isNotEmpty == true) + streamDataExtraKey: BodyBytesStream.fromBytes(fileInfo.bytes!), } }; @@ -174,7 +189,7 @@ class FileUploader { cancelToken: cancelToken, onSendProgress: (count, total) { log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}'); - onSendController.add( + onSendController?.add( Right(UploadingAttachmentUploadState( uploadId, count, diff --git a/lib/features/upload/domain/extensions/file_info_extension.dart b/lib/features/upload/domain/extensions/file_info_extension.dart index 45ab539d1..a7a5bef5d 100644 --- a/lib/features/upload/domain/extensions/file_info_extension.dart +++ b/lib/features/upload/domain/extensions/file_info_extension.dart @@ -1,17 +1,13 @@ import 'package:file_picker/file_picker.dart'; 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); - FileInfo withInline() => FileInfo( fileName: fileName, fileSize: fileSize, filePath: filePath, bytes: bytes, - readStream: readStream, type: type, isInline: true, isShared: isShared); @@ -21,6 +17,5 @@ extension FileInfoExtension on FileInfo { path: filePath, size: fileSize, bytes: bytes, - readStream: readStream, ); } \ No newline at end of file diff --git a/lib/features/upload/domain/extensions/platform_file_extension.dart b/lib/features/upload/domain/extensions/platform_file_extension.dart index f24da3baa..19fc8137a 100644 --- a/lib/features/upload/domain/extensions/platform_file_extension.dart +++ b/lib/features/upload/domain/extensions/platform_file_extension.dart @@ -8,6 +8,5 @@ extension PlatformFileExtension on PlatformFile { fileSize: size, filePath: PlatformInfo.isWeb ? '' : path ?? '', bytes: bytes, - readStream: readStream ); } \ No newline at end of file diff --git a/lib/features/upload/domain/model/mobile_file_upload.dart b/lib/features/upload/domain/model/mobile_file_upload.dart deleted file mode 100644 index 72c63b3da..000000000 --- a/lib/features/upload/domain/model/mobile_file_upload.dart +++ /dev/null @@ -1,24 +0,0 @@ - -import 'package:equatable/equatable.dart'; - -class MobileFileUpload with EquatableMixin { - final String fileName; - final String filePath; - final int fileSize; - final String mimeType; - - MobileFileUpload( - this.fileName, - this.filePath, - this.fileSize, - this.mimeType - ); - - @override - List get props => [ - fileName, - filePath, - fileSize, - mimeType, - ]; -} \ 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 eece6dcb2..7088c88c0 100644 --- a/lib/features/upload/domain/model/upload_attachment.dart +++ b/lib/features/upload/domain/model/upload_attachment.dart @@ -42,10 +42,10 @@ class UploadAttachment with EquatableMixin { final attachment = await fileUploader.uploadAttachment( uploadTaskId, - _progressStateController, fileInfo, uploadUri, - cancelToken: cancelToken + cancelToken: cancelToken, + onSendController: _progressStateController, ); log('UploadAttachment::upload: ATTACHMENT_UPLOADED = $attachment'); diff --git a/lib/features/upload/presentation/controller/upload_controller.dart b/lib/features/upload/presentation/controller/upload_controller.dart index f7d63ee9e..f107aad90 100644 --- a/lib/features/upload/presentation/controller/upload_controller.dart +++ b/lib/features/upload/presentation/controller/upload_controller.dart @@ -193,24 +193,24 @@ class UploadController extends BaseController { } void initializeUploadAttachments(List attachments) { - final listUploadFilesState = attachments - .map((attachment) => UploadFileState( - UploadTaskId(attachment.blobId!.value), - uploadStatus: UploadFileStatus.succeed, - attachment: attachment)) - .toList(); - _uploadingStateFiles.addAll(listUploadFilesState); + _uploadingStateFiles.addAll( + attachments.map((attachment) => UploadFileState( + UploadTaskId(attachment.blobId!.value), + uploadStatus: UploadFileStatus.succeed, + attachment: attachment + )) + ); _refreshListUploadAttachmentState(); } void initializeUploadInlineAttachments(List inlineAttachments) { - final listUploadInlineImagesState = inlineAttachments - .map((inlineAttachment) => UploadFileState( - UploadTaskId(inlineAttachment.blobId!.value), - uploadStatus: UploadFileStatus.succeed, - attachment: inlineAttachment)) - .toList(); - _uploadingStateInlineFiles.addAll(listUploadInlineImagesState); + _uploadingStateInlineFiles.addAll( + inlineAttachments.map((inlineAttachment) => UploadFileState( + UploadTaskId(inlineAttachment.blobId!.value), + uploadStatus: UploadFileStatus.succeed, + attachment: inlineAttachment, + )) + ); _refreshListUploadAttachmentState(); } @@ -428,45 +428,44 @@ class UploadController extends BaseController { } List get inlineAttachmentsUploaded { - if (_uploadingStateInlineFiles.uploadingStateFiles.isEmpty) { - return List.empty(); - } - return _uploadingStateInlineFiles.uploadingStateFiles - .whereNotNull() - .map((fileState) => fileState.attachment) - .whereNotNull() - .where((attachment) => attachment.cid != null) - .toList(); + return _uploadingStateInlineFiles.uploadingStateFiles.fold>( + [], + (list, fileState) { + final attachment = fileState?.attachment; + if (attachment?.cid != null) { + list.add(attachment!); + } + return list; + }, + ); } List get inlineAttachmentsPicked { - if (_uploadingStateInlineFiles.uploadingStateFiles.isEmpty) { - return List.empty(); - } - return _uploadingStateInlineFiles.uploadingStateFiles - .whereNotNull() - .map((fileState) => fileState.file) - .whereNotNull() - .toList(); + return _uploadingStateInlineFiles.uploadingStateFiles.fold>( + [], + (list, fileState) { + final file = fileState?.file; + if (file != null) { + list.add(file); + } + return list; + }, + ); } Map get mapInlineAttachments { - final inlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles - .whereNotNull() - .map((fileState) => fileState.attachment) - .whereNotNull() - .where((attachment) => attachment.cid != null) - .toList(); + final mapInlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles.fold>( + {}, + (map, fileState) { + final attachment = fileState?.attachment; + if (attachment?.cid != null) { + map[attachment!.cid!] = attachment; + } + return map; + }, + ); - log('UploadController::mapInlineAttachments(): $inlineAttachments'); - - if (inlineAttachments.isEmpty) { - return {}; - } - - final mapInlineAttachments = { - for (var item in inlineAttachments) item.cid!: item - }; + log('UploadController::mapInlineAttachments(): Found ${mapInlineAttachments.length} inline attachments.'); return mapInlineAttachments; } diff --git a/lib/features/upload/presentation/model/upload_file_state_list.dart b/lib/features/upload/presentation/model/upload_file_state_list.dart index 9529455db..57272e884 100644 --- a/lib/features/upload/presentation/model/upload_file_state_list.dart +++ b/lib/features/upload/presentation/model/upload_file_state_list.dart @@ -18,7 +18,7 @@ class UploadFileStateList { return this; } - UploadFileStateList addAll(List elements) { + UploadFileStateList addAll(Iterable elements) { _uploadingStateFiles.addAll(elements); return this; } diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index 4b8ed746c..09a31a510 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -37,10 +37,12 @@ import 'package:tmail_ui_user/features/push_notification/data/network/web_socket import 'package:tmail_ui_user/features/quotas/data/network/quotas_api.dart'; import 'package:tmail_ui_user/features/server_settings/data/network/server_settings_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; +import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/send_email_exception_thrower.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; import 'package:uuid/uuid.dart'; +import 'package:worker_manager/worker_manager.dart'; class NetworkBindings extends Bindings { @@ -84,6 +86,7 @@ class NetworkBindings extends Bindings { Get.find(), Get.find(), )); + Get.put(Executor()); } void _bindingInterceptors() { @@ -126,6 +129,11 @@ class NetworkBindings extends Bindings { Get.put(ServerSettingsAPI(Get.find())); Get.put(WebSocketApi(Get.find())); Get.put(LinagoraEcosystemApi(Get.find())); + Get.put(FileUploader( + Get.find(), + Get.find(), + Get.find(), + )); } void _bindingConnection() { @@ -140,7 +148,11 @@ class NetworkBindings extends Bindings { void _bindingTransformer() { Get.put(const HtmlEscape()); Get.put(HtmlTransform(Get.find(), Get.find())); - Get.put(HtmlAnalyzer(Get.find())); + Get.put(HtmlAnalyzer( + Get.find(), + Get.find(), + Get.find(), + )); } void _bindingServices() { diff --git a/lib/main/bindings/network/network_isolate_binding.dart b/lib/main/bindings/network/network_isolate_binding.dart index e1ca8614d..47a3ba2ce 100644 --- a/lib/main/bindings/network/network_isolate_binding.dart +++ b/lib/main/bindings/network/network_isolate_binding.dart @@ -87,7 +87,6 @@ class NetworkIsolateBindings extends Bindings { } void _bindingIsolateWorker() { - Get.put(Executor()); Get.put(ThreadIsolateWorker( Get.find(tag: BindingTag.isolateTag), Get.find(tag: BindingTag.isolateTag), diff --git a/model/lib/upload/file_info.dart b/model/lib/upload/file_info.dart index e2bcad149..2b797b487 100644 --- a/model/lib/upload/file_info.dart +++ b/model/lib/upload/file_info.dart @@ -7,7 +7,6 @@ class FileInfo with EquatableMixin { final int fileSize; final String? filePath; final Uint8List? bytes; - final Stream>? readStream; final String? type; final bool? isInline; final bool? isShared; @@ -17,7 +16,6 @@ class FileInfo with EquatableMixin { required this.fileSize, this.filePath, this.bytes, - this.readStream, this.type, this.isInline, this.isShared, @@ -32,7 +30,7 @@ class FileInfo with EquatableMixin { }) { return FileInfo( fileName: name ?? '', - fileSize: size ?? 0, + fileSize: size ?? bytes.length, bytes: bytes, type: type, isInline: isInline, @@ -59,7 +57,6 @@ class FileInfo with EquatableMixin { filePath, fileSize, bytes, - readStream, type, isInline, isShared, diff --git a/test/features/email/presentation/controller/single_email_controller_test.dart b/test/features/email/presentation/controller/single_email_controller_test.dart index aaa10a95e..a6e38d6b7 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -57,6 +57,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; +import 'package:tmail_ui_user/features/upload/data/network/file_uploader.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/utils/toast_manager.dart'; @@ -114,6 +115,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -405,10 +407,14 @@ void main() { final calendarEventRepository = CalendarEventRepositoryImpl( {DataSourceType.network :calendarEventDataSource}, HtmlDataSourceImpl( - HtmlAnalyzer(HtmlTransform( - MockDioClient(), - const HtmlEscape(), - )), + HtmlAnalyzer( + HtmlTransform( + MockDioClient(), + const HtmlEscape(), + ), + MockFileUploader(), + MockUuid(), + ), CacheExceptionThrower(), ), ); diff --git a/test/features/identity_creator/presentation/identity_creator_controller_test.dart b/test/features/identity_creator/presentation/identity_creator_controller_test.dart index 86977d763..8bb4a67f9 100644 --- a/test/features/identity_creator/presentation/identity_creator_controller_test.dart +++ b/test/features/identity_creator/presentation/identity_creator_controller_test.dart @@ -45,6 +45,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/profiles/iden import 'package:tmail_ui_user/features/public_asset/domain/model/public_assets_in_identity_arguments.dart'; import 'package:tmail_ui_user/features/public_asset/domain/usecase/create_public_asset_interactor.dart'; import 'package:tmail_ui_user/features/public_asset/domain/usecase/delete_public_assets_interactor.dart'; +import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/universal_import/html_stub.dart'; @@ -78,6 +79,7 @@ import 'identity_creator_controller_test.mocks.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -163,6 +165,7 @@ void main() { Get.put(MockDioClient(), tag: BindingTag.isolateTag); Get.put(MockExecutor()); Get.put(MockFileUtils()); + Get.put(MockFileUploader()); Get.put(MockRemoteExceptionThrower()); Get.put(MockDownloadClient()); Get.put(MockHtmlAnalyzer());