TF-106 Fix display inlined image CIDs of emails
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>(),
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
+3
-2
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user