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