TF-3416 Implement preview EML attachment

This commit is contained in:
dab246
2025-01-13 10:02:50 +07:00
committed by Dat H. Pham
parent 7cbdfddb6d
commit d304083b2e
54 changed files with 1489 additions and 148 deletions
@@ -1,13 +1,23 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:core/data/model/print_attachment.dart';
import 'package:core/data/network/download/downloaded_response.dart';
import 'package:core/domain/extensions/datetime_extension.dart';
import 'package:core/presentation/extensions/html_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/file_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:core/utils/preview_eml_file_utils.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:filesize/filesize.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
@@ -19,11 +29,14 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart';
import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart';
import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/restore_deleted_message_request.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
@@ -34,6 +47,10 @@ class EmailDataSourceImpl extends EmailDataSource {
final EmailAPI emailAPI;
final ExceptionThrower _exceptionThrower;
final ExceptionThrower _sendEmailExceptionThrower = Get.find<SendEmailExceptionThrower>();
final PreviewEmlFileUtils _previewEmailUtils = Get.find<PreviewEmlFileUtils>();
final HtmlAnalyzer _htmlAnalyzer = Get.find<HtmlAnalyzer>();
final FileUtils _fileUtils = Get.find<FileUtils>();
final ImagePaths _imagePaths = Get.find<ImagePaths>();
EmailDataSourceImpl(this.emailAPI, this._exceptionThrower);
@@ -385,4 +402,125 @@ class EmailDataSourceImpl extends EmailDataSource {
return await emailAPI.parseEmailByBlobIds(accountId, blobIds);
}).catchError(_exceptionThrower.throwException);
}
@override
Future<String> generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) {
return Future.sync(() async {
final email = previewEmailEMLRequest.email;
final appLocalizations = previewEmailEMLRequest.appLocalizations;
final locale = previewEmailEMLRequest.locale.toLanguageTag();
final listAttachments = email
.allAttachments
.getListAttachmentsDisplayedOutside(email.htmlBodyAttachments);
final listInlineImages = email
.allAttachments
.listAttachmentsDisplayedInContent;
final mapCidImageDownloadUrl = listInlineImages.toMapCidImageDownloadUrl(
accountId: previewEmailEMLRequest.accountId,
downloadUrl: previewEmailEMLRequest.baseDownloadUrl,
);
final emailContentEscaped = await _transformEmailContent(
email.emailContentList,
mapCidImageDownloadUrl,
);
final sender = email.from?.isNotEmpty == true
? email.from!.first
: null;
final receiveTime = email.getReceivedAt(
newLocale: locale,
pattern: email.receivedAt?.value.toLocal().toPatternForPrinting(locale)
);
final sentTime = email.getSentAt(
newLocale: locale,
pattern: email.sentAt?.value.toLocal().toPatternForPrinting(locale)
);
final List<PrintAttachment> listPrintAttachment = [];
if (listAttachments.isNotEmpty) {
await Future.forEach<Attachment>(listAttachments, (attachment) async {
final iconBase64Data = await _fileUtils.convertImageAssetToBase64(
attachment.getIcon(_imagePaths));
final printAttachment = PrintAttachment(
iconBase64Data: iconBase64Data,
name: attachment.name.escapeLtGtHtmlString(),
size: filesize(attachment.size?.value),
);
listPrintAttachment.add(printAttachment);
});
}
final attachmentIconBase64Data = email.hasAttachment == true
? await _fileUtils.convertImageAssetToBase64(_imagePaths.icAttachment)
: '';
final previewEmlHtmlDocument = _previewEmailUtils.generatePreviewEml(
appName: appLocalizations.app_name,
userName: previewEmailEMLRequest.userName.value,
subjectPrefix: appLocalizations.subject,
subject: previewEmailEMLRequest.email.subject?.escapeLtGtHtmlString() ?? '',
emailContent: emailContentEscaped,
senderName: sender?.name.escapeLtGtHtmlString() ?? '',
senderEmailAddress: sender?.email ?? '',
dateTime: receiveTime.isNotEmpty ? receiveTime : sentTime,
fromPrefix: appLocalizations.from_email_address_prefix,
toPrefix: appLocalizations.to_email_address_prefix,
ccPrefix: appLocalizations.cc_email_address_prefix,
bccPrefix: appLocalizations.bcc_email_address_prefix,
replyToPrefix: appLocalizations.replyToEmailAddressPrefix,
titleAttachment: appLocalizations.attachments.toLowerCase(),
attachmentIcon: attachmentIconBase64Data,
toAddress: email.to?.listEmailAddressToString(isFullEmailAddress: true),
ccAddress: email.cc?.listEmailAddressToString(isFullEmailAddress: true),
bccAddress: email.bcc?.listEmailAddressToString(isFullEmailAddress: true),
replyToAddress: email.replyTo?.listEmailAddressToString(isFullEmailAddress: true),
listAttachment: listPrintAttachment,
);
return previewEmlHtmlDocument;
}).catchError(_exceptionThrower.throwException);
}
Future<String> _transformEmailContent(
List<EmailContent> emailContents,
Map<String, String> mapCidImageDownloadUrl,
) async {
try {
final transformConfiguration = PlatformInfo.isWeb
? TransformConfiguration.forPreviewEmailOnWeb()
: TransformConfiguration.forPreviewEmail();
final listEmailContent = await Future.wait(emailContents
.map((emailContent) async => await _htmlAnalyzer.transformEmailContent(
emailContent,
mapCidImageDownloadUrl,
transformConfiguration,
))
.toList());
return listEmailContent.asHtmlString;
} catch (e) {
logError('EmailDataSourceImpl::_transformEmailContent:Exception = $e');
return '';
}
}
@override
Future<void> sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) {
throw UnimplementedError();
}
@override
Future<String> getPreviewEmailEMLContentShared(String keyStored) {
throw UnimplementedError();
}
}
@@ -34,6 +34,7 @@ import 'package:tmail_ui_user/features/email/domain/extensions/detailed_email_hi
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/restore_deleted_message_request.dart';
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/offline_mode/extensions/list_sending_email_hive_cache_extension.dart';
@@ -531,4 +532,19 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
Future<List<Email>> parseEmailByBlobIds(AccountId accountId, Set<Id> blobIds) {
throw UnimplementedError();
}
@override
Future<String> generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) {
throw UnimplementedError();
}
@override
Future<void> sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) {
throw UnimplementedError();
}
@override
Future<String> getPreviewEmailEMLContentShared(String keyStored) {
throw UnimplementedError();
}
}
@@ -0,0 +1,224 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:core/data/network/download/downloaded_response.dart';
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:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/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/core/user_name.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/account/account_request.dart';
import 'package:model/download/download_task_id.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/mark_star_action.dart';
import 'package:model/email/read_actions.dart';
import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart';
import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart';
import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart';
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/preview_email_eml_request.dart';
import 'package:tmail_ui_user/features/email/domain/model/restore_deleted_message_request.dart';
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
class EmailLocalStorageDataSourceImpl extends EmailDataSource {
final LocalStorageManager _localStorageManager;
final ExceptionThrower _exceptionThrower;
EmailLocalStorageDataSourceImpl(
this._localStorageManager,
this._exceptionThrower,
);
@override
Future<bool> deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId, {CancelToken? cancelToken}) {
throw UnimplementedError();
}
@override
Future<({List<EmailId> emailIdsSuccess, Map<Id, SetError> mapErrors})> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List<EmailId> emailIds) {
throw UnimplementedError();
}
@override
Future<void> deleteMultipleSendingEmail(AccountId accountId, UserName userName, List<String> sendingIds) {
throw UnimplementedError();
}
@override
Future<void> deleteSendingEmail(AccountId accountId, UserName userName, String sendingId) {
throw UnimplementedError();
}
@override
Future<Uint8List> downloadAttachmentForWeb(DownloadTaskId taskId, Attachment attachment, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest, StreamController<Either<Failure, Success>> onReceiveController, {CancelToken? cancelToken}) {
throw UnimplementedError();
}
@override
Future<List<DownloadTaskId>> downloadAttachments(List<Attachment> attachments, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest) {
throw UnimplementedError();
}
@override
Future<DownloadedResponse> exportAttachment(Attachment attachment, AccountId accountId, String baseDownloadUrl, AccountRequest accountRequest, CancelToken cancelToken) {
throw UnimplementedError();
}
@override
Future<String> generatePreviewEmailEMLContent(PreviewEmailEMLRequest previewEmailEMLRequest) {
throw UnimplementedError();
}
@override
Future<List<SendingEmail>> getAllSendingEmails(AccountId accountId, UserName userName) {
throw UnimplementedError();
}
@override
Future<Email> getDetailedEmailById(Session session, AccountId accountId, EmailId emailId) {
throw UnimplementedError();
}
@override
Future<Email> getEmailContent(Session session, AccountId accountId, EmailId emailId, {Properties? additionalProperties}) {
throw UnimplementedError();
}
@override
Future<EmailRecoveryAction> getRestoredDeletedMessage(EmailRecoveryActionId emailRecoveryActionId) {
throw UnimplementedError();
}
@override
Future<Email> getStoredEmail(Session session, AccountId accountId, EmailId emailId) {
throw UnimplementedError();
}
@override
Future<DetailedEmail> getStoredNewEmail(Session session, AccountId accountId, EmailId emailId) {
throw UnimplementedError();
}
@override
Future<DetailedEmail> getStoredOpenedEmail(Session session, AccountId accountId, EmailId emailId) {
throw UnimplementedError();
}
@override
Future<SendingEmail> getStoredSendingEmail(AccountId accountId, UserName userName, String sendingId) {
throw UnimplementedError();
}
@override
Future<({List<EmailId> emailIdsSuccess, Map<Id, SetError> mapErrors})> markAsRead(Session session, AccountId accountId, List<EmailId> emailIds, ReadActions readActions) {
throw UnimplementedError();
}
@override
Future<({List<EmailId> emailIdsSuccess, Map<Id, SetError> mapErrors})> markAsStar(Session session, AccountId accountId, List<EmailId> emailIds, MarkStarAction markStarAction) {
throw UnimplementedError();
}
@override
Future<({List<EmailId> emailIdsSuccess, Map<Id, SetError> mapErrors})> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) {
throw UnimplementedError();
}
@override
Future<List<Email>> parseEmailByBlobIds(AccountId accountId, Set<Id> blobIds) {
throw UnimplementedError();
}
@override
Future<bool> removeEmailDrafts(Session session, AccountId accountId, EmailId emailId, {CancelToken? cancelToken}) {
throw UnimplementedError();
}
@override
Future<EmailRecoveryAction> restoreDeletedMessage(RestoredDeletedMessageRequest restoredDeletedMessageRequest) {
throw UnimplementedError();
}
@override
Future<Email> saveEmailAsDrafts(Session session, AccountId accountId, Email email, {CancelToken? cancelToken}) {
throw UnimplementedError();
}
@override
Future<void> sendEmail(Session session, AccountId accountId, EmailRequest emailRequest, {CreateNewMailboxRequest? mailboxRequest, CancelToken? cancelToken}) {
throw UnimplementedError();
}
@override
Future<void> sharePreviewEmailEMLContent(String keyStored, String previewEMLContent) {
return Future.sync(() async {
return _localStorageManager.save(keyStored, previewEMLContent);
}).catchError(_exceptionThrower.throwException);
}
@override
Future<void> storeDetailedNewEmail(Session session, AccountId accountId, DetailedEmail detailedEmail) {
throw UnimplementedError();
}
@override
Future<void> storeEmail(Session session, AccountId accountId, Email email) {
throw UnimplementedError();
}
@override
Future<void> storeEventAttendanceStatus(Session session, AccountId accountId, EmailId emailId, EventActionType eventActionType) {
throw UnimplementedError();
}
@override
Future<void> storeOpenedEmail(Session session, AccountId accountId, DetailedEmail detailedEmail) {
throw UnimplementedError();
}
@override
Future<SendingEmail> storeSendingEmail(AccountId accountId, UserName userName, SendingEmail sendingEmail) {
throw UnimplementedError();
}
@override
Future<void> unsubscribeMail(Session session, AccountId accountId, EmailId emailId) {
throw UnimplementedError();
}
@override
Future<Email> updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId, {CancelToken? cancelToken}) {
throw UnimplementedError();
}
@override
Future<List<SendingEmail>> updateMultipleSendingEmail(AccountId accountId, UserName userName, List<SendingEmail> newSendingEmails) {
throw UnimplementedError();
}
@override
Future<SendingEmail> updateSendingEmail(AccountId accountId, UserName userName, SendingEmail newSendingEmail) {
throw UnimplementedError();
}
@override
Future<String> getPreviewEmailEMLContentShared(String keyStored) {
return Future.sync(() async {
return _localStorageManager.get(keyStored);
}).catchError(_exceptionThrower.throwException);
}
}