TF-106 Fix display inlined image CIDs of emails

This commit is contained in:
dab246
2021-10-18 14:46:11 +07:00
committed by Dat H. Pham
parent 1432cb0209
commit 60ffa5ace0
25 changed files with 202 additions and 81 deletions
+1
View File
@@ -10,6 +10,7 @@ export 'presentation/extensions/html_extension.dart';
// Exceptions // Exceptions
export 'domain/exceptions/download_file_exception.dart'; export 'domain/exceptions/download_file_exception.dart';
export 'data/extensions/options_extensions.dart';
// Utils // Utils
export 'presentation/utils/theme_utils.dart'; export 'presentation/utils/theme_utils.dart';
@@ -0,0 +1,12 @@
import 'package:dio/dio.dart';
extension OptionsExtension on Options {
Options appendHeaders(Map<String, dynamic> additionalHeaders) {
if (this.headers != null) {
this.headers?.addAll(additionalHeaders);
} else {
this.headers = additionalHeaders;
}
return this;
}
}
+19 -4
View File
@@ -1,4 +1,7 @@
import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:core/core.dart';
class DioClient { class DioClient {
static const jmapHeader = 'application/json;jmapVersion=rfc-8621'; static const jmapHeader = 'application/json;jmapVersion=rfc-8621';
@@ -16,10 +19,13 @@ class DioClient {
CancelToken? cancelToken, CancelToken? cancelToken,
ProgressCallback? onReceiveProgress, ProgressCallback? onReceiveProgress,
}) async { }) async {
final newOptions = options?.appendHeaders({HttpHeaders.acceptHeader : jmapHeader})
?? Options(headers: {HttpHeaders.acceptHeader : jmapHeader}) ;
return await _dio.get( return await _dio.get(
path, path,
queryParameters: queryParameters, queryParameters: queryParameters,
options: options, options: newOptions,
cancelToken: cancelToken, cancelToken: cancelToken,
onReceiveProgress: onReceiveProgress) onReceiveProgress: onReceiveProgress)
.then((value) => value.data) .then((value) => value.data)
@@ -35,10 +41,13 @@ class DioClient {
ProgressCallback? onSendProgress, ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress, ProgressCallback? onReceiveProgress,
}) async { }) async {
final newOptions = options?.appendHeaders({HttpHeaders.acceptHeader : jmapHeader})
?? Options(headers: {HttpHeaders.acceptHeader : jmapHeader}) ;
return await _dio.post(path, return await _dio.post(path,
data: data, data: data,
queryParameters: queryParameters, queryParameters: queryParameters,
options: options, options: newOptions,
cancelToken: cancelToken, cancelToken: cancelToken,
onSendProgress: onSendProgress, onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress) onReceiveProgress: onReceiveProgress)
@@ -53,11 +62,14 @@ class DioClient {
Options? options, Options? options,
CancelToken? cancelToken, CancelToken? cancelToken,
}) async { }) async {
final newOptions = options?.appendHeaders({HttpHeaders.acceptHeader : jmapHeader})
?? Options(headers: {HttpHeaders.acceptHeader : jmapHeader}) ;
return await _dio.delete( return await _dio.delete(
path, path,
data: data, data: data,
queryParameters: queryParameters, queryParameters: queryParameters,
options: options, options: newOptions,
cancelToken: cancelToken) cancelToken: cancelToken)
.then((value) => value.data) .then((value) => value.data)
.catchError((error) => throw(error)); .catchError((error) => throw(error));
@@ -72,11 +84,14 @@ class DioClient {
ProgressCallback? onSendProgress, ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress ProgressCallback? onReceiveProgress
}) async { }) async {
final newOptions = options?.appendHeaders({HttpHeaders.acceptHeader : jmapHeader})
?? Options(headers: {HttpHeaders.acceptHeader : jmapHeader}) ;
return await _dio.put( return await _dio.put(
path, path,
data: data, data: data,
queryParameters: queryParameters, queryParameters: queryParameters,
options: options, options: newOptions,
cancelToken: cancelToken, cancelToken: cancelToken,
onSendProgress: onSendProgress, onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress) onReceiveProgress: onReceiveProgress)
@@ -1,4 +1,5 @@
import 'package:core/core.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
@@ -10,7 +11,13 @@ abstract class DomTransformer {
/// Uses the `DOM` [document] and specified [message] to transform the `document`. /// Uses the `DOM` [document] and specified [message] to transform the `document`.
/// ///
/// All changes will be visible to subsequent transformers. /// All changes will be visible to subsequent transformers.
void process(Document document, String message, TransformConfiguration configuration); Future<void> process(
Document document,
String message,
Map<String, String>? mapUrlDownloadCID,
TransformConfiguration configuration,
DioClient dioClient
);
/// Adds a HEAD element if necessary /// Adds a HEAD element if necessary
void ensureDocumentHeadIsAvailable(Document document) { void ensureDocumentHeadIsAvailable(Document document) {
@@ -1,4 +1,8 @@
import 'dart:convert';
import 'package:core/core.dart';
import 'package:dio/dio.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
@@ -8,16 +12,44 @@ class ImageTransformer extends DomTransformer {
const ImageTransformer(); const ImageTransformer();
@override @override
void process(Document document, String message, TransformConfiguration configuration) { Future<void> process(
Document document,
String message,
Map<String, String>? mapUrlDownloadCID,
TransformConfiguration configuration,
DioClient dioClient
) async {
final imageElements = document.getElementsByTagName('img'); final imageElements = document.getElementsByTagName('img');
for (final imageElement in imageElements) {
final src = imageElement.attributes['src']; await Future.wait(imageElements.map((imageElement) async {
if (src != null && src.startsWith('http:')) {
// always at least enforce HTTPS images:
final url = src.substring('http:'.length);
imageElement.attributes['src'] = 'https:$url';
}
imageElement.attributes['style'] = 'display: inline;max-width: 100%;height: auto;'; imageElement.attributes['style'] = 'display: inline;max-width: 100%;height: auto;';
final src = imageElement.attributes['src'];
if (src != null
&& src.isNotEmpty
&& src.startsWith('cid:')
&& mapUrlDownloadCID != null
) {
final cid = src.replaceFirst('cid:', '').trim();
final cidUrlDownload = mapUrlDownloadCID[cid];
if (cidUrlDownload != null && cidUrlDownload.isNotEmpty) {
final imgBase64 = await loadAsyncNetworkImageToBase64(dioClient, cidUrlDownload);
if (imgBase64 != null && imgBase64.isNotEmpty) {
imageElement.attributes['src'] = 'data:image/jpeg;base64,$imgBase64';
}
}
}
}));
}
Future<String?> loadAsyncNetworkImageToBase64(DioClient dioClient, String imageUrl) async {
try {
final response = await dioClient.get(
imageUrl,
options: Options(responseType: ResponseType.bytes));
return base64Encode(response);
} catch (e) {
return imageUrl;
} }
} }
} }
@@ -1,4 +1,5 @@
import 'package:core/data/network/dio_client.dart';
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
@@ -8,10 +9,16 @@ class EnsureRelationNoReferrerTransformer extends DomTransformer {
const EnsureRelationNoReferrerTransformer(); const EnsureRelationNoReferrerTransformer();
@override @override
void process(Document document, String message, TransformConfiguration configuration) { Future<void> process(
Document document,
String message,
Map<String, String>? mapUrlDownloadCID,
TransformConfiguration configuration,
DioClient dioClient
) async {
final linkElements = document.getElementsByTagName('a'); final linkElements = document.getElementsByTagName('a');
for (final linkElement in linkElements) { await Future.wait(linkElements.map((linkElement) async {
linkElement.attributes['rel'] = 'noopener noreferrer'; linkElement.attributes['rel'] = 'noopener noreferrer';
} }));
} }
} }
@@ -1,4 +1,5 @@
import 'package:core/data/network/dio_client.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
@@ -13,11 +14,18 @@ class ViewPortTransformer extends DomTransformer {
const ViewPortTransformer(); const ViewPortTransformer();
@override @override
void process(Document document, String message, TransformConfiguration configuration) { Future<void> process(
Document document,
String message,
Map<String, String>? mapUrlDownloadCID,
TransformConfiguration configuration,
DioClient dioClient
) async {
final metaElements = document.getElementsByTagName('meta'); final metaElements = document.getElementsByTagName('meta');
var viewportNeedsToBeAdded = true; var viewportNeedsToBeAdded = true;
var contentTypeNeedsToBeAdded = true; var contentTypeNeedsToBeAdded = true;
for (final metaElement in metaElements) {
await Future.wait(metaElements.map((metaElement) async {
if (metaElement.attributes['name'] == 'viewport') { if (metaElement.attributes['name'] == 'viewport') {
viewportNeedsToBeAdded = false; viewportNeedsToBeAdded = false;
metaElement.attributes['content'] = 'width=device-width, initial-scale=1.0'; metaElement.attributes['content'] = 'width=device-width, initial-scale=1.0';
@@ -30,7 +38,8 @@ class ViewPortTransformer extends DomTransformer {
metaElement.attributes['content'] = 'text/html; charset=utf-8'; metaElement.attributes['content'] = 'text/html; charset=utf-8';
} }
} }
} }));
if (contentTypeNeedsToBeAdded) { if (contentTypeNeedsToBeAdded) {
ensureDocumentHeadIsAvailable(document); ensureDocumentHeadIsAvailable(document);
document.head!.append(_contentTypeMetaElement); document.head!.append(_contentTypeMetaElement);
@@ -1,4 +1,5 @@
import 'package:core/data/network/dio_client.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
@@ -8,10 +9,16 @@ class RemoveScriptTransformer extends DomTransformer {
const RemoveScriptTransformer(); const RemoveScriptTransformer();
@override @override
void process(Document document, String message, TransformConfiguration configuration) { Future<void> process(
Document document,
String message,
Map<String, String>? mapUrlDownloadCID,
TransformConfiguration configuration,
DioClient dioClient
) async {
final scriptElements = document.getElementsByTagName('script'); final scriptElements = document.getElementsByTagName('script');
for (final scriptElement in scriptElements) { await Future.wait(scriptElements.map((scriptElement) async {
scriptElement.remove(); scriptElement.remove();
} }));
} }
} }
@@ -1,3 +1,4 @@
import 'package:core/core.dart';
import 'package:core/presentation/utils/html_transformer/message_content_transformer.dart'; import 'package:core/presentation/utils/html_transformer/message_content_transformer.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
@@ -5,21 +6,27 @@ import 'package:html/dom.dart';
class HtmlTransform { class HtmlTransform {
final String _contentHtml; final String _contentHtml;
Map<String, String>? mapUrlDownloadCID;
DioClient dioClient;
HtmlTransform(this._contentHtml); HtmlTransform(
this._contentHtml,
this.dioClient,
this.mapUrlDownloadCID
);
/// Transforms this message to HTML code. /// Transforms this message to HTML code.
/// Optionally specify the [transformConfiguration] to control all aspects of the transformation /// Optionally specify the [transformConfiguration] to control all aspects of the transformation
/// - in that case other parameters are ignored. /// - in that case other parameters are ignored.
String transformToHtml({TransformConfiguration? transformConfiguration}) { Future<String> transformToHtml({TransformConfiguration? transformConfiguration}) async {
final document = transformToDocument(transformConfiguration: transformConfiguration); final document = await transformToDocument(transformConfiguration: transformConfiguration);
return document.outerHtml; return document.outerHtml;
} }
/// Transforms this message to Document. /// Transforms this message to Document.
Document transformToDocument({TransformConfiguration? transformConfiguration}) { Future<Document> transformToDocument({TransformConfiguration? transformConfiguration}) async {
transformConfiguration ??= TransformConfiguration.create(); transformConfiguration ??= TransformConfiguration.create();
final transformer = MessageContentTransformer(transformConfiguration); final transformer = MessageContentTransformer(transformConfiguration, dioClient);
return transformer.toDocument(_contentHtml); return await transformer.toDocument(_contentHtml, mapUrlDownloadCID);
} }
} }
@@ -1,3 +1,4 @@
import 'package:core/core.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:html/parser.dart' show parse; import 'package:html/parser.dart' show parse;
@@ -6,23 +7,24 @@ import 'package:html/parser.dart' show parse;
class MessageContentTransformer { class MessageContentTransformer {
/// The configuration used for the transformation /// The configuration used for the transformation
final TransformConfiguration configuration; final TransformConfiguration configuration;
final DioClient dioClient;
MessageContentTransformer(this.configuration); MessageContentTransformer(this.configuration, this.dioClient);
void transformDocument(Document document, String message) { Future<void> transformDocument(
for (final domTransformer in configuration.domTransformers) { Document document,
domTransformer.process(document, message, configuration); String message,
} Map<String, String>? mapUrlDownloadCID,
) async {
await Future.wait(configuration.domTransformers.map((domTransformer) async {
await domTransformer.process(document, message, mapUrlDownloadCID, configuration, dioClient);
}));
} }
Document toDocument(String message) { Future<Document> toDocument(String message, Map<String, String>? mapUrlDownloadCID) async {
var html = message; var html = message;
final document = parse(html); final document = parse(html);
transformDocument(document, message); await transformDocument(document, message, mapUrlDownloadCID);
return document; return document;
} }
String toHtml(String message) {
return toDocument(message).outerHtml;
}
} }
@@ -2,7 +2,6 @@
import 'dart:io'; import 'dart:io';
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:dio/dio.dart';
import 'package:model/model.dart'; import 'package:model/model.dart';
class ComposerAPI { class ComposerAPI {
@@ -14,14 +13,7 @@ class ComposerAPI {
Future<UploadResponse> uploadAttachment(UploadRequest uploadRequest) async { Future<UploadResponse> uploadAttachment(UploadRequest uploadRequest) async {
final resultJson = await _dioClient.post( final resultJson = await _dioClient.post(
Uri.decodeFull(uploadRequest.uploadUrl.toString()), Uri.decodeFull(uploadRequest.uploadUrl.toString()),
options: Options(headers: _buildHeaderRequestParam()),
data: File(uploadRequest.fileInfo.filePath).readAsBytesSync()); data: File(uploadRequest.fileInfo.filePath).readAsBytesSync());
return UploadResponse.fromJson(resultJson); return UploadResponse.fromJson(resultJson);
} }
Map<String, dynamic> _buildHeaderRequestParam() {
final headerParam = _dioClient.getHeaders();
headerParam[HttpHeaders.acceptHeader] = DioClient.jmapHeader;
return headerParam;
}
} }
@@ -2,5 +2,8 @@
import 'package:model/model.dart'; import 'package:model/model.dart';
abstract class HtmlDataSource { abstract class HtmlDataSource {
Future<EmailContent> transformEmailContent(EmailContent emailContent); Future<EmailContent> transformEmailContent(
EmailContent emailContent,
Map<String, String> mapUrlDownloadCID
);
} }
@@ -1,17 +1,22 @@
import 'package:model/email/email_content.dart'; import 'package:core/core.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart';
import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
class HtmlDataSourceImpl extends HtmlDataSource { class HtmlDataSourceImpl extends HtmlDataSource {
final HtmlAnalyzer _htmlAnalyzer; final HtmlAnalyzer _htmlAnalyzer;
final DioClient _dioClient;
HtmlDataSourceImpl(this._htmlAnalyzer); HtmlDataSourceImpl(this._htmlAnalyzer, this._dioClient);
@override @override
Future<EmailContent> transformEmailContent(EmailContent emailContent) { Future<EmailContent> transformEmailContent(
EmailContent emailContent,
Map<String, String>? mapUrlDownloadCID
) {
return Future.sync(() async { return Future.sync(() async {
return await _htmlAnalyzer.transformEmailContent(emailContent); return await _htmlAnalyzer.transformEmailContent(emailContent, mapUrlDownloadCID, _dioClient);
}).catchError((error) { }).catchError((error) {
throw error; throw error;
}); });
@@ -1,15 +1,23 @@
import 'package:core/core.dart';
import 'package:core/presentation/utils/html_transformer/html_transform.dart'; import 'package:core/presentation/utils/html_transformer/html_transform.dart';
import 'package:model/email/email_content_type.dart'; import 'package:model/email/email_content_type.dart';
import 'package:model/model.dart'; import 'package:model/model.dart';
class HtmlAnalyzer { class HtmlAnalyzer {
Future<EmailContent> transformEmailContent(EmailContent emailContent) async { Future<EmailContent> transformEmailContent(
EmailContent emailContent,
Map<String, String>? mapUrlDownloadCID,
DioClient dioClient
) async {
switch(emailContent.type) { switch(emailContent.type) {
case EmailContentType.textHtml: case EmailContentType.textHtml:
final htmlTransform = HtmlTransform(emailContent.content); final htmlTransform = HtmlTransform(
final htmlContent = htmlTransform.transformToHtml(); emailContent.content,
dioClient,
mapUrlDownloadCID);
final htmlContent = await htmlTransform.transformToHtml();
return EmailContent(emailContent.type, htmlContent); return EmailContent(emailContent.type, htmlContent);
default: default:
return emailContent; return emailContent;
@@ -72,10 +72,19 @@ class EmailRepositoryImpl extends EmailRepository {
} }
@override @override
Future<List<EmailContent>> transformEmailContent(List<EmailContent> emailContents) async { Future<List<EmailContent>> transformEmailContent(
List<EmailContent> emailContents,
List<Attachment> attachmentInlines,
String? baseUrlDownload,
AccountId accountId
) async {
final mapUrlDownloadCID = Map<String, String>.fromIterable(
attachmentInlines,
key: (attachment) => attachment.cid!,
value: (attachment) => attachment.getDownloadUrl(baseUrlDownload, accountId));
return await Future.wait(emailContents return await Future.wait(emailContents
.map((emailContent) async { .map((emailContent) async {
return await _htmlDataSource.transformEmailContent(emailContent); return await _htmlDataSource.transformEmailContent(emailContent, mapUrlDownloadCID);
}) })
.toList()); .toList());
} }
@@ -35,5 +35,10 @@ abstract class EmailRepository {
MarkStarAction markStarAction MarkStarAction markStarAction
); );
Future<List<EmailContent>> transformEmailContent(List<EmailContent> emailContents); Future<List<EmailContent>> transformEmailContent(
List<EmailContent> emailContents,
List<Attachment> attachmentInlines,
String? baseUrlDownload,
AccountId accountId
);
} }
@@ -11,13 +11,17 @@ class GetEmailContentInteractor {
GetEmailContentInteractor(this.emailRepository); GetEmailContentInteractor(this.emailRepository);
Stream<Either<Failure, Success>> execute(AccountId accountId, EmailId emailId) async* { Stream<Either<Failure, Success>> execute(AccountId accountId, EmailId emailId, String? baseDownloadUrl) async* {
try { try {
yield Right<Failure, Success>(LoadingState()); yield Right<Failure, Success>(LoadingState());
final email = await emailRepository.getEmailContent(accountId, emailId); final email = await emailRepository.getEmailContent(accountId, emailId);
if (email.emailContentList.isNotEmpty) { if (email.emailContentList.isNotEmpty) {
final newEmailContents = await emailRepository.transformEmailContent(email.emailContentList); final newEmailContents = await emailRepository.transformEmailContent(
email.emailContentList,
email.allAttachments.attachmentWithDispositionInlines,
baseDownloadUrl,
accountId);
yield Right<Failure, Success>(GetEmailContentSuccess(newEmailContents, email.allAttachments)); yield Right<Failure, Success>(GetEmailContentSuccess(newEmailContents, email.allAttachments));
} else { } else {
yield Left(GetEmailContentFailure(null)); yield Left(GetEmailContentFailure(null));
@@ -25,14 +25,17 @@ class EmailBindings extends Bindings {
void dependencies() { void dependencies() {
Get.lazyPut(() => EmailDataSourceImpl(Get.find<EmailAPI>())); Get.lazyPut(() => EmailDataSourceImpl(Get.find<EmailAPI>()));
Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>()); Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>());
Get.lazyPut(() => HtmlDataSourceImpl(Get.find<HtmlAnalyzer>())); Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<DioClient>()));
Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>()); Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>());
Get.lazyPut(() => EmailRepositoryImpl( Get.lazyPut(() => EmailRepositoryImpl(
Get.find<EmailDataSource>(), Get.find<EmailDataSource>(),
Get.find<HtmlDataSource>())); Get.find<HtmlDataSource>()));
Get.lazyPut<EmailRepository>(() => Get.find<EmailRepositoryImpl>()); Get.lazyPut<EmailRepository>(() => Get.find<EmailRepositoryImpl>());
Get.put(SendEmailInteractor(Get.find<EmailRepository>())); Get.put(SendEmailInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => GetEmailContentInteractor(Get.find<EmailRepository>())); Get.lazyPut(() => GetEmailContentInteractor(
Get.find<EmailRepository>()));
Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find<EmailRepository>())); Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => CredentialRepositoryImpl(Get.find<SharedPreferences>())); Get.lazyPut(() => CredentialRepositoryImpl(Get.find<SharedPreferences>()));
Get.lazyPut<CredentialRepository>(() => Get.find<CredentialRepositoryImpl>()); Get.lazyPut<CredentialRepository>(() => Get.find<CredentialRepositoryImpl>());
@@ -88,7 +88,8 @@ class EmailController extends BaseController {
} }
void _getEmailContentAction(AccountId accountId, EmailId emailId) async { void _getEmailContentAction(AccountId accountId, EmailId emailId) async {
consumeState(_getEmailContentInteractor.execute(accountId, emailId)); final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl();
consumeState(_getEmailContentInteractor.execute(accountId, emailId, baseDownloadUrl));
} }
@override @override
@@ -11,7 +11,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/app_bar_mail_w
import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/attachments_place_holder_loading_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachments_place_holder_loading_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/list_attachment_extension.dart'; import 'package:model/extensions/list_attachment_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_item_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_item_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/sender_and_receiver_information_tile_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/sender_and_receiver_information_tile_builder.dart';
@@ -51,7 +51,9 @@ class ThreadBindings extends Bindings {
Get.lazyPut(() => ScrollController()); Get.lazyPut(() => ScrollController());
Get.lazyPut(() => EmailDataSourceImpl(Get.find<EmailAPI>())); Get.lazyPut(() => EmailDataSourceImpl(Get.find<EmailAPI>()));
Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>()); Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>());
Get.lazyPut(() => HtmlDataSourceImpl(Get.find<HtmlAnalyzer>())); Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<DioClient>()));
Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>()); Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>());
Get.lazyPut(() => EmailRepositoryImpl( Get.lazyPut(() => EmailRepositoryImpl(
Get.find<EmailDataSource>(), Get.find<EmailDataSource>(),
+2
View File
@@ -27,6 +27,8 @@ class Attachment with EquatableMixin {
this.disposition, this.disposition,
}); });
bool cidNotEmpty() => cid != null && cid!.isNotEmpty;
String getDownloadUrl(String baseDownloadUrl, AccountId accountId) { String getDownloadUrl(String baseDownloadUrl, AccountId accountId) {
final downloadUriTemplate = UriTemplate('$baseDownloadUrl'); final downloadUriTemplate = UriTemplate('$baseDownloadUrl');
final downloadUri = downloadUriTemplate.expand({ final downloadUri = downloadUriTemplate.expand({
-14
View File
@@ -1,6 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:model/email/email_content_type.dart'; import 'package:model/email/email_content_type.dart';
import 'package:model/model.dart'; import 'package:model/model.dart';
@@ -11,19 +10,6 @@ class EmailContent with EquatableMixin {
EmailContent(this.type, this.content); EmailContent(this.type, this.content);
bool hasImageInlineWithCid() => content.contains('cid:');
String getContentHasInlineAttachment(String baseDownloadUrl, AccountId accountId, List<Attachment> attachments) {
var contentValid = content;
attachments.forEach((attachment) {
if(attachment.cid != null) {
final urlDownloadImage = attachment.getDownloadUrl(baseDownloadUrl, accountId);
contentValid = content.replaceAll('cid:${attachment.cid}', '$urlDownloadImage');
}
});
return contentValid;
}
@override @override
List<Object?> get props => [type, content]; List<Object?> get props => [type, content];
} }
@@ -13,10 +13,11 @@ extension ListAttachmentExtension on List<Attachment> {
} }
List<Attachment> get attachmentsWithDispositionAttachment { List<Attachment> get attachmentsWithDispositionAttachment {
return where((element) => element.disposition == ContentDisposition.attachment).toList(); return where((attachment) => attachment.disposition == ContentDisposition.attachment).toList();
} }
List<Attachment> get attachmentWithDispositionInlines { List<Attachment> get attachmentWithDispositionInlines {
return where((element) => element.disposition == ContentDisposition.inline).toList(); return where((attachment) => attachment.disposition == ContentDisposition.inline && attachment.cidNotEmpty())
.toList();
} }
} }
+1
View File
@@ -52,6 +52,7 @@ export 'extensions/list_mailbox_extension.dart';
export 'extensions/list_extension.dart'; export 'extensions/list_extension.dart';
export 'extensions/list_email_extension.dart'; export 'extensions/list_email_extension.dart';
export 'extensions/media_type_extension.dart'; export 'extensions/media_type_extension.dart';
export 'extensions/list_attachment_extension.dart';
// Download // Download
export 'download/download_task_id.dart'; export 'download/download_task_id.dart';