TF-3514 Detect base64 image to transfer it to cid attachment
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -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 <img> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +54,14 @@ class ComposerRepositoryImpl extends ComposerRepository {
|
||||
String emailContent = createEmailRequest.emailContent;
|
||||
Set<EmailBodyPart> 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<Tuple2<String, Set<EmailBodyPart>>> _replaceImageBase64ToImageCID({
|
||||
required String emailContent,
|
||||
required Map<String, Attachment> inlineAttachments
|
||||
required Map<String, Attachment> 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)
|
||||
: {},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<DioClient>(tag: BindingTag.isolateTag),
|
||||
Get.find<Executor>(),
|
||||
Get.find<FileUtils>(),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsDataSourceImpl() {
|
||||
Get.lazyPut(() => AttachmentUploadDataSourceImpl(
|
||||
|
||||
@@ -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<CreateEmailRequest?> _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<dynamic> _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<dynamic> _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,
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
@@ -18,7 +18,8 @@ abstract class HtmlDataSource {
|
||||
|
||||
Future<Tuple2<String, Set<EmailBodyPart>>> replaceImageBase64ToImageCID({
|
||||
required String emailContent,
|
||||
required Map<String, Attachment> inlineAttachments
|
||||
required Map<String, Attachment> inlineAttachments,
|
||||
required Uri? uploadUri,
|
||||
});
|
||||
|
||||
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent});
|
||||
|
||||
@@ -42,12 +42,14 @@ class HtmlDataSourceImpl extends HtmlDataSource {
|
||||
@override
|
||||
Future<Tuple2<String, Set<EmailBodyPart>>> replaceImageBase64ToImageCID({
|
||||
required String emailContent,
|
||||
required Map<String, Attachment> inlineAttachments
|
||||
required Map<String, Attachment> inlineAttachments,
|
||||
required Uri? uploadUri,
|
||||
}) {
|
||||
return Future.sync(() async {
|
||||
return await _htmlAnalyzer.replaceImageBase64ToImageCID(
|
||||
emailContent: emailContent,
|
||||
inlineAttachments: inlineAttachments
|
||||
inlineAttachments: inlineAttachments,
|
||||
uploadUri: uploadUri,
|
||||
);
|
||||
}).catchError(_exceptionThrower.throwException);
|
||||
}
|
||||
|
||||
@@ -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<EmailContent> transformEmailContent(
|
||||
EmailContent emailContent,
|
||||
@@ -112,35 +123,71 @@ class HtmlAnalyzer {
|
||||
|
||||
Future<Tuple2<String, Set<EmailBodyPart>>> replaceImageBase64ToImageCID({
|
||||
required String emailContent,
|
||||
required Map<String, Attachment> inlineAttachments
|
||||
required Map<String, Attachment> 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<EmailBodyPart> inlineAttachmentsSet = {};
|
||||
final List<Future<void>> asyncTasks = [];
|
||||
|
||||
for (final imgTag in listImgTag) {
|
||||
late final LinkedHashMap<Object, String> 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<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) async {
|
||||
@@ -162,4 +209,32 @@ class HtmlAnalyzer {
|
||||
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: AFTER = $newContent');
|
||||
return newContent;
|
||||
}
|
||||
|
||||
Future<Attachment?> _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<Failure, Success>(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<Failure, Success>(GetEmailContentFromCacheSuccess(
|
||||
htmlEmailContent: detailedEmail.htmlEmailContent ?? '',
|
||||
attachments: detailedEmail.attachments ?? [],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<DioClient>(tag: BindingTag.isolateTag),
|
||||
Get.find<Executor>(),
|
||||
Get.find<FileUtils>()),
|
||||
tag: BindingTag.publicAssetBindingsTag);
|
||||
Get.lazyPut(
|
||||
() => PublicAssetApi(Get.find<HttpClient>(), Get.find<Uuid>()),
|
||||
tag: BindingTag.publicAssetBindingsTag);
|
||||
@@ -82,7 +73,7 @@ class PublicAssetBindings extends BaseBindings {
|
||||
void bindingsDataSourceImpl() {
|
||||
Get.lazyPut(
|
||||
() => AttachmentUploadDataSourceImpl(
|
||||
Get.find<FileUploader>(tag: BindingTag.publicAssetBindingsTag),
|
||||
Get.find<FileUploader>(),
|
||||
Get.find<Uuid>(),
|
||||
Get.find<RemoteExceptionThrower>()),
|
||||
tag: BindingTag.publicAssetBindingsTag);
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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<Attachment> uploadAttachment(
|
||||
UploadTaskId uploadId,
|
||||
StreamController<Either<Failure, Success>> onSendController,
|
||||
FileInfo fileInfo,
|
||||
Uri uploadUri,
|
||||
{CancelToken? cancelToken}
|
||||
{
|
||||
CancelToken? cancelToken,
|
||||
StreamController<Either<Failure, Success>>? 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 = <String, dynamic>{
|
||||
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<Attachment> _handleUploadAttachmentActionOnWeb(
|
||||
UploadTaskId uploadId,
|
||||
StreamController<Either<Failure, Success>> onSendController,
|
||||
FileInfo fileInfo,
|
||||
Uri uploadUri,
|
||||
{CancelToken? cancelToken}
|
||||
{
|
||||
CancelToken? cancelToken,
|
||||
StreamController<Either<Failure, Success>>? onSendController,
|
||||
}
|
||||
) async {
|
||||
final headerParam = _dioClient.getHeaders();
|
||||
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
|
||||
@@ -160,7 +174,8 @@ class FileUploader {
|
||||
|
||||
final mapExtra = <String, dynamic>{
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,5 @@ extension PlatformFileExtension on PlatformFile {
|
||||
fileSize: size,
|
||||
filePath: PlatformInfo.isWeb ? '' : path ?? '',
|
||||
bytes: bytes,
|
||||
readStream: readStream
|
||||
);
|
||||
}
|
||||
@@ -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<Object?> get props => [
|
||||
fileName,
|
||||
filePath,
|
||||
fileSize,
|
||||
mimeType,
|
||||
];
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -193,24 +193,24 @@ class UploadController extends BaseController {
|
||||
}
|
||||
|
||||
void initializeUploadAttachments(List<Attachment> 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<Attachment> 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<Attachment> 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<Attachment>>(
|
||||
[],
|
||||
(list, fileState) {
|
||||
final attachment = fileState?.attachment;
|
||||
if (attachment?.cid != null) {
|
||||
list.add(attachment!);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<FileInfo> get inlineAttachmentsPicked {
|
||||
if (_uploadingStateInlineFiles.uploadingStateFiles.isEmpty) {
|
||||
return List.empty();
|
||||
}
|
||||
return _uploadingStateInlineFiles.uploadingStateFiles
|
||||
.whereNotNull()
|
||||
.map((fileState) => fileState.file)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
return _uploadingStateInlineFiles.uploadingStateFiles.fold<List<FileInfo>>(
|
||||
[],
|
||||
(list, fileState) {
|
||||
final file = fileState?.file;
|
||||
if (file != null) {
|
||||
list.add(file);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Attachment> get mapInlineAttachments {
|
||||
final inlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles
|
||||
.whereNotNull()
|
||||
.map((fileState) => fileState.attachment)
|
||||
.whereNotNull()
|
||||
.where((attachment) => attachment.cid != null)
|
||||
.toList();
|
||||
final mapInlineAttachments = _uploadingStateInlineFiles.uploadingStateFiles.fold<Map<String, Attachment>>(
|
||||
{},
|
||||
(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;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class UploadFileStateList {
|
||||
return this;
|
||||
}
|
||||
|
||||
UploadFileStateList addAll(List<UploadFileState> elements) {
|
||||
UploadFileStateList addAll(Iterable<UploadFileState> elements) {
|
||||
_uploadingStateFiles.addAll(elements);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -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<OIDCHttpClient>(),
|
||||
Get.find<MailboxCacheManager>(),
|
||||
));
|
||||
Get.put(Executor());
|
||||
}
|
||||
|
||||
void _bindingInterceptors() {
|
||||
@@ -126,6 +129,11 @@ class NetworkBindings extends Bindings {
|
||||
Get.put(ServerSettingsAPI(Get.find<HttpClient>()));
|
||||
Get.put(WebSocketApi(Get.find<DioClient>()));
|
||||
Get.put(LinagoraEcosystemApi(Get.find<DioClient>()));
|
||||
Get.put(FileUploader(
|
||||
Get.find<DioClient>(),
|
||||
Get.find<Executor>(),
|
||||
Get.find<FileUtils>(),
|
||||
));
|
||||
}
|
||||
|
||||
void _bindingConnection() {
|
||||
@@ -140,7 +148,11 @@ class NetworkBindings extends Bindings {
|
||||
void _bindingTransformer() {
|
||||
Get.put(const HtmlEscape());
|
||||
Get.put(HtmlTransform(Get.find<DioClient>(), Get.find<HtmlEscape>()));
|
||||
Get.put(HtmlAnalyzer(Get.find<HtmlTransform>()));
|
||||
Get.put(HtmlAnalyzer(
|
||||
Get.find<HtmlTransform>(),
|
||||
Get.find<FileUploader>(),
|
||||
Get.find<Uuid>(),
|
||||
));
|
||||
}
|
||||
|
||||
void _bindingServices() {
|
||||
|
||||
@@ -87,7 +87,6 @@ class NetworkIsolateBindings extends Bindings {
|
||||
}
|
||||
|
||||
void _bindingIsolateWorker() {
|
||||
Get.put(Executor());
|
||||
Get.put(ThreadIsolateWorker(
|
||||
Get.find<ThreadAPI>(tag: BindingTag.isolateTag),
|
||||
Get.find<EmailAPI>(tag: BindingTag.isolateTag),
|
||||
|
||||
@@ -7,7 +7,6 @@ class FileInfo with EquatableMixin {
|
||||
final int fileSize;
|
||||
final String? filePath;
|
||||
final Uint8List? bytes;
|
||||
final Stream<List<int>>? 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,
|
||||
|
||||
@@ -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<TwakeAppManager>(),
|
||||
MockSpec<DownloadAllAttachmentsForWebInteractor>(),
|
||||
MockSpec<ExportAllAttachmentsInteractor>(),
|
||||
MockSpec<FileUploader>(),
|
||||
])
|
||||
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(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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<DioClient>(),
|
||||
MockSpec<Executor>(),
|
||||
MockSpec<FileUtils>(),
|
||||
MockSpec<FileUploader>(),
|
||||
MockSpec<RemoteExceptionThrower>(),
|
||||
MockSpec<DownloadClient>(),
|
||||
MockSpec<HtmlAnalyzer>(),
|
||||
@@ -163,6 +165,7 @@ void main() {
|
||||
Get.put<DioClient>(MockDioClient(), tag: BindingTag.isolateTag);
|
||||
Get.put<Executor>(MockExecutor());
|
||||
Get.put<FileUtils>(MockFileUtils());
|
||||
Get.put<FileUploader>(MockFileUploader());
|
||||
Get.put<RemoteExceptionThrower>(MockRemoteExceptionThrower());
|
||||
Get.put<DownloadClient>(MockDownloadClient());
|
||||
Get.put<HtmlAnalyzer>(MockHtmlAnalyzer());
|
||||
|
||||
Reference in New Issue
Block a user