diff --git a/core/lib/presentation/utils/html_transformer/dom/blockquote_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/blockquote_transformers.dart deleted file mode 100644 index 7263afa02..000000000 --- a/core/lib/presentation/utils/html_transformer/dom/blockquote_transformers.dart +++ /dev/null @@ -1,24 +0,0 @@ - -import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; -import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; -import 'package:html/dom.dart'; - -class SetStyleBlockQuoteTransformer extends DomTransformer { - - const SetStyleBlockQuoteTransformer(); - - @override - void process(Document document, String message, TransformConfiguration configuration) { - final quoteElements = document.getElementsByTagName('blockquote'); - for (final quote in quoteElements) { - final style = quote.attributes['style']; - if (style == null) { - quote.attributes['style'] = ''' - margin: 0px 8px 0px 8px; - padding: 8px 16px 8px 16px; - border-left:5px solid #eee; - '''; - } - } - } -} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart index 94d4197f3..734a0bbcd 100644 --- a/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart +++ b/core/lib/presentation/utils/html_transformer/dom/image_transformers.dart @@ -12,23 +12,12 @@ class ImageTransformer extends DomTransformer { final imageElements = document.getElementsByTagName('img'); for (final imageElement in imageElements) { final src = imageElement.attributes['src']; - if (src != null) { - if (src.startsWith('http')) { - if (configuration.blockExternalImages) { - imageElement.attributes.remove('src'); - } else if (src.startsWith('http:')) { - // always at least enforce HTTPS images: - final url = src.substring('http:'.length); - imageElement.attributes['src'] = 'https:$url'; - } - } - } - final style = imageElement.attributes['style']; - if (style == null) { - imageElement.attributes['style'] = 'display: inline;max-width: 100%;height: auto;'; - } else { - imageElement.attributes['style'] = 'display: inline;max-width: 100%;height: auto;' + style; + 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;'; } } } \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/dom/table_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/table_transformers.dart deleted file mode 100644 index 7c3055e74..000000000 --- a/core/lib/presentation/utils/html_transformer/dom/table_transformers.dart +++ /dev/null @@ -1,36 +0,0 @@ - -import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; -import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; -import 'package:html/dom.dart'; - -class SetStyleTableTransformer extends DomTransformer { - - const SetStyleTableTransformer(); - - @override - void process(Document document, String message, TransformConfiguration configuration) { - final tableElements = document.getElementsByTagName('table'); - for (final table in tableElements) { - final style = table.attributes['style']; - if (style == null) { - table.attributes['style'] = 'width: 100%;max-width: 100%;border:1px solid #f0f0f0;border-collapse: collapse;border-spacing: 2px;'; - } - } - - final tdElements = document.getElementsByTagName('td'); - for (final tdTag in tdElements) { - final style = tdTag.attributes['style']; - if (style == null) { - tdTag.attributes['style'] = 'padding: 13px;margin: 0px;border:1px solid #f0f0f0;'; - } - } - - final thElements = document.getElementsByTagName('th'); - for (final thTag in thElements) { - final style = thTag.attributes['style']; - if (style == null) { - thTag.attributes['style'] = 'padding: 13px;margin: 0px;border:1px solid #f0f0f0;'; - } - } - } -} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/html_transform.dart b/core/lib/presentation/utils/html_transformer/html_transform.dart index 5dc89206b..29b72a798 100644 --- a/core/lib/presentation/utils/html_transformer/html_transform.dart +++ b/core/lib/presentation/utils/html_transformer/html_transform.dart @@ -4,39 +4,22 @@ import 'package:html/dom.dart'; class HtmlTransform { - final String _message; + final String _contentHtml; - HtmlTransform(this._message); + HtmlTransform(this._contentHtml); /// Transforms this message to HTML code. - /// - /// Set [blockExternalImages] to `true` in case external images should be blocked. - /// Optionally specify the [transformConfiguration] to control all aspects of the transformation - in that case other parameters are ignored. - String transformToHtml({ - bool? blockExternalImages, - TransformConfiguration? transformConfiguration, - }) { - final document = transformToDocument( - blockExternalImages: blockExternalImages, - transformConfiguration: transformConfiguration, - ); + /// Optionally specify the [transformConfiguration] to control all aspects of the transformation + /// - in that case other parameters are ignored. + String transformToHtml({TransformConfiguration? transformConfiguration}) { + final document = transformToDocument(transformConfiguration: transformConfiguration); return document.outerHtml; } /// Transforms this message to Document. - /// - /// Set [blockExternalImages] to `true` in case external images should be blocked. - /// Optionally specify the [transformConfiguration] to control all aspects of the transformation - in that case other parameters are ignored. - Document transformToDocument({ - bool? blockExternalImages, - int? maxImageWidth, - TransformConfiguration? transformConfiguration, - }) { - transformConfiguration ??= TransformConfiguration.create( - blockExternalImages: blockExternalImages, - maxImageWidth: maxImageWidth, - ); + Document transformToDocument({TransformConfiguration? transformConfiguration}) { + transformConfiguration ??= TransformConfiguration.create(); final transformer = MessageContentTransformer(transformConfiguration); - return transformer.toDocument(_message); + return transformer.toDocument(_contentHtml); } } \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/transform_configuration.dart b/core/lib/presentation/utils/html_transformer/transform_configuration.dart index 498ee4962..b30b789b2 100644 --- a/core/lib/presentation/utils/html_transformer/transform_configuration.dart +++ b/core/lib/presentation/utils/html_transformer/transform_configuration.dart @@ -1,11 +1,9 @@ -import 'package:core/presentation/utils/html_transformer/dom/blockquote_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/link_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/meta_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart'; import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; -import 'package:core/presentation/utils/html_transformer/dom/table_transformers.dart'; import 'package:core/presentation/utils/html_transformer/text/convert_tags_text_transformer.dart'; import 'package:core/presentation/utils/html_transformer/text/linebreak_text_transformer.dart'; import 'package:core/presentation/utils/html_transformer/text/links_text_transformer.dart'; @@ -14,12 +12,6 @@ import 'package:core/presentation/utils/html_transformer/base/text_transformer.d /// Contains the configuration for all transformations. class TransformConfiguration { - /// Should external images be blocked? - final bool blockExternalImages; - - /// The maximum width for embedded images. It make sense to limit this to reduce the generated HTML size. - final int? maxImageWidth; - /// The list of DOM transformers being used final List domTransformers; @@ -30,16 +22,12 @@ class TransformConfiguration { /// /// Compare [create] to have an easier to use building function const TransformConfiguration( - this.blockExternalImages, - this.maxImageWidth, this.domTransformers, this.textTransformers ); /// Provides easy access to a standard configuration that does not block external images. static const TransformConfiguration standardConfiguration = TransformConfiguration( - false, - standardMaxImageWidth, standardDomTransformers, standardTextTransformers ); @@ -61,8 +49,6 @@ class TransformConfiguration { : standardTextTransformers; maxImageWidth ??= standardMaxImageWidth; return TransformConfiguration( - blockExternalImages ?? false, - maxImageWidth, domTransformers, textTransformers ); @@ -75,8 +61,6 @@ class TransformConfiguration { RemoveScriptTransformer(), ImageTransformer(), EnsureRelationNoReferrerTransformer(), - SetStyleBlockQuoteTransformer(), - SetStyleTableTransformer(), ]; static const List standardTextTransformers = [ diff --git a/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart b/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart index aa791477b..68adc3967 100644 --- a/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart +++ b/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart @@ -1,55 +1,31 @@ -import 'package:core/presentation/utils/html_transformer/html_transform.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:url_launcher/url_launcher.dart' as launcher; -class _HtmlGenerationArguments { - final String message; - final bool blockExternalImages; - - const _HtmlGenerationArguments( - this.message, - this.blockExternalImages, - ); -} - -class _HtmlGenerationResult { - final String? html; - final String? errorDetails; - - const _HtmlGenerationResult.success(this.html) : errorDetails = null; - - const _HtmlGenerationResult.error(this.errorDetails) : this.html = null; -} - class HtmlContentViewer extends StatefulWidget { - final String message; - final bool blockExternalImages; - - /// Is notified about any errors that might occur - final void Function(Object? exception, StackTrace? stackTrace)? onError; + final String contentHtml; + final int minHeight; /// Register this callback if you want a reference to the [InAppWebViewController]. - final void Function(InAppWebViewController controller)? onWebViewCreated; + final void Function(InAppWebViewController controller)? onCreated; - final void Function(InAppWebViewController controller, Uri? uri)? onWebViewLoadStart; + final void Function(InAppWebViewController controller, Uri? uri)? onLoadStart; - final void Function(InAppWebViewController controller, Uri? uri)? onWebViewLoadStop; + final void Function(InAppWebViewController controller, Uri? uri)? onLoadStop; /// Handler for mailto: links final Future Function(Uri mailto)? mailtoDelegate; const HtmlContentViewer({ Key? key, - required this.message, - this.blockExternalImages = false, - this.onError, - this.onWebViewCreated, - this.onWebViewLoadStart, - this.onWebViewLoadStop, + required this.contentHtml, + this.minHeight = 100, + this.onCreated, + this.onLoadStart, + this.onLoadStop, this.mailtoDelegate, }) : super(key: key); @@ -59,123 +35,126 @@ class HtmlContentViewer extends StatefulWidget { class _HtmlContentViewState extends State { - String? _htmlData; - bool? _wereExternalImagesBlocked; - bool _isGenerating = false; - double? _webViewContentHeight = 1.0; + double? _documentHeight = 1.0; + double? _documentWidth = 1.0; + String? _initialPageContent; + late InAppWebViewController _webViewController; @override void initState() { - _generateHtml(widget.blockExternalImages); super.initState(); + _initialPageContent = _generateHtmlDocument(widget.contentHtml); } - void _generateHtml(bool blockExternalImages) async { - _wereExternalImagesBlocked = blockExternalImages; - _isGenerating = true; - final args = _HtmlGenerationArguments( - widget.message, - blockExternalImages, - ); - final result = await compute(_generateHtmlImpl, args); - _htmlData = result.html; - if (_htmlData == null) { - final onError = widget.onError; - if (onError != null) { - onError(result.errorDetails, null); - } - } - if (mounted) { - setState(() { - _isGenerating = false; - }); - } - } + String _generateHtmlDocument(String content) { + final htmlTemplate = ''' + + + + + + + + + +
$content
+ + + '''; + return htmlTemplate; } @override Widget build(BuildContext context) { - if (_isGenerating) { - return SizedBox.shrink(); - } - if (widget.blockExternalImages != _wereExternalImagesBlocked) { - _generateHtml(widget.blockExternalImages); - } - final size = MediaQuery.of(context).size; + _documentWidth = size.width - 30; return SizedBox( - height: _webViewContentHeight, - width: size.width, - child: Padding( - padding: EdgeInsets.only(top: 16), - child: _buildWebView(), - ), + height: _documentHeight, + width: _documentWidth, + child: _buildWebView(), ); } Widget _buildWebView() { - if (_htmlData == null) { + if (_initialPageContent == null || _initialPageContent?.isEmpty == true) { return Container(); } - return InAppWebView( - key: ValueKey(_htmlData), - initialData: InAppWebViewInitialData(data: _htmlData!), + key: ValueKey(_initialPageContent), + initialData: InAppWebViewInitialData(data: _initialPageContent!), initialOptions: InAppWebViewGroupOptions( crossPlatform: InAppWebViewOptions( useShouldOverrideUrlLoading: true, verticalScrollBarEnabled: false, - disableVerticalScroll: true, - transparentBackground: true, + disableVerticalScroll: false, + disableHorizontalScroll: false, + supportZoom: true, ), android: AndroidInAppWebViewOptions( + useWideViewPort: false, + loadWithOverviewMode: true, useHybridComposition: true, ), ios: IOSInAppWebViewOptions( - allowsInlineMediaPlayback: true, - enableViewportScale: true, + enableViewportScale: false, allowsLinkPreview: false - ) + ), ), onLoadStart: (controller, uri) { - if (widget.onWebViewLoadStart != null) { - widget.onWebViewLoadStart!(controller, uri); + if (widget.onLoadStart != null) { + widget.onLoadStart!(controller, uri); } }, - onWebViewCreated: widget.onWebViewCreated, + onWebViewCreated: _onWebViewCreated, onLoadStop: (controller, uri) async { - var scrollHeight = (await controller.evaluateJavascript(source: 'document.body.scrollHeight')); - if (scrollHeight != null) { - final scrollWidth = (await controller.evaluateJavascript(source: 'document.body.scrollWidth')); - final size = MediaQuery.of(context).size; - final containerWidth = size.width - 60.0; - if (scrollWidth > containerWidth) { - var scale = (containerWidth / scrollWidth); - if (scale < 0.2) { - scale = 0.2; - } - await controller.zoomBy(zoomFactor: scale, iosAnimated: true); - scrollHeight = (scrollHeight * scale).ceil(); - } + final scrollHeight = await _webViewController.evaluateJavascript(source: 'document.body.scrollHeight'); + if ((scrollHeight != null) && mounted && (scrollHeight + 30.0 > widget.minHeight)) { setState(() { - _webViewContentHeight = double.tryParse('${scrollHeight + 24}'); + _documentHeight = (scrollHeight + 30.0); }); } - - if (widget.onWebViewLoadStop != null) { - widget.onWebViewLoadStop!(controller, uri); + if (widget.onLoadStop != null) { + widget.onLoadStop!(controller, uri); + } + }, + onScrollChanged: (controller, x, y) { + if (y != 0) { + controller.scrollTo(x: 0, y: 0); } }, shouldOverrideUrlLoading: _shouldOverrideUrlLoading, @@ -185,6 +164,13 @@ class _HtmlContentViewState extends State { ); } + void _onWebViewCreated(InAppWebViewController controller) { + _webViewController = controller; + if (widget.onCreated != null) { + widget.onCreated!(_webViewController); + } + } + Future _shouldOverrideUrlLoading( InAppWebViewController controller, NavigationAction request diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 251946354..ab2306d6c 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -28,7 +28,6 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_addre import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/upload_mutiple_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; -import 'package:tmail_ui_user/features/email/presentation/constants/email_constants.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart'; @@ -215,18 +214,11 @@ class ComposerController extends BaseController { final headerEmailQuoted = getHeaderEmailQuoted(Localizations.localeOf(context).toLanguageTag()); final headerEmailQuotedAsHtml = headerEmailQuoted != null - ? AppLocalizations.of(context).header_email_quoted(headerEmailQuoted.value1, headerEmailQuoted.value2) - .addBlockTag('p', attribute: 'style=\"font-size:14px;font-style:italic;color:#182952;\"') - : ''; + ? AppLocalizations.of(context).header_email_quoted(headerEmailQuoted.value1, headerEmailQuoted.value2) + .addBlockTag('p', attribute: 'style=\"font-size:14px;font-style:italic;color:#182952;\"') + : ''; - var trustAsHtml = ''; - - if (composerArguments.value != null) { - final emailContents = composerArguments.value!.emailContents; - if (emailContents != null && emailContents.isNotEmpty) { - trustAsHtml = emailContents.first.content; - } - } + final trustAsHtml = composerArguments.value?.emailContent?.content ?? ''; final emailQuotedHtml = '


$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}
'; @@ -256,7 +248,7 @@ class ComposerController extends BaseController { EmailBodyPart( partId: generatePartId, blobId: generateBlobId, - type: MediaType.parse(EmailConstants.HTML_TEXT) + type: MediaType.parse('text/html') )}, bodyValues: { generatePartId: EmailBodyValue(emailBodyText, false, false) diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 188860a8e..c7c362b47 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -174,17 +174,23 @@ class ComposerView extends GetWidget { Widget _buildComposerEditor(BuildContext context) { return Obx(() { if (controller.composerArguments.value?.emailActionType == EmailActionType.compose) { - return PackagedHtmlEditor( + return HtmlEditor( key: Key('composer_editor'), minHeight: 400, + supportZoom: true, + disableHorizontalScroll: false, + disableVerticalScroll: false, onCreated: (editorApi) => controller.htmlEditorApi = editorApi, ); } else { final message = controller.getContentEmail(); return message.isNotEmpty - ? PackagedHtmlEditor( + ? HtmlEditor( key: Key('composer_editor'), minHeight: 400, + supportZoom: true, + disableHorizontalScroll: false, + disableVerticalScroll: false, onCreated: (editorApi) => controller.htmlEditorApi = editorApi, initialContent: message, ) diff --git a/lib/features/email/data/datasource/html_datasource.dart b/lib/features/email/data/datasource/html_datasource.dart new file mode 100644 index 000000000..4802ebe7a --- /dev/null +++ b/lib/features/email/data/datasource/html_datasource.dart @@ -0,0 +1,6 @@ + +import 'package:model/model.dart'; + +abstract class HtmlDataSource { + Future transformToHtml(EmailContent emailContent); +} \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/html_datasource_impl.dart b/lib/features/email/data/datasource_impl/html_datasource_impl.dart new file mode 100644 index 000000000..c8e04375b --- /dev/null +++ b/lib/features/email/data/datasource_impl/html_datasource_impl.dart @@ -0,0 +1,19 @@ +import 'package:model/email/email_content.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'; + +class HtmlDataSourceImpl extends HtmlDataSource { + + final HtmlAnalyzer _htmlAnalyzer; + + HtmlDataSourceImpl(this._htmlAnalyzer); + + @override + Future transformToHtml(EmailContent emailContent) { + return Future.sync(() async { + return await _htmlAnalyzer.transformToHtml(emailContent); + }).catchError((error) { + throw error; + }); + } +} \ No newline at end of file diff --git a/lib/features/email/data/local/html_analyzer.dart b/lib/features/email/data/local/html_analyzer.dart new file mode 100644 index 000000000..46c04fb9a --- /dev/null +++ b/lib/features/email/data/local/html_analyzer.dart @@ -0,0 +1,17 @@ + +import 'package:core/presentation/utils/html_transformer/html_transform.dart'; +import 'package:model/email/email_content_type.dart'; +import 'package:model/model.dart'; + +class HtmlAnalyzer { + + Future transformToHtml(EmailContent emailContent) async { + switch(emailContent.type) { + case EmailContentType.textHtml: + final htmlTransform = HtmlTransform(emailContent.content); + return htmlTransform.transformToHtml(); + default: + return emailContent.content; + } + } +} \ No newline at end of file diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 34f59dfb7..a1a4d0d3d 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -5,14 +5,16 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; class EmailRepositoryImpl extends EmailRepository { final EmailDataSource emailDataSource; + final HtmlDataSource _htmlDataSource; - EmailRepositoryImpl(this.emailDataSource); + EmailRepositoryImpl(this.emailDataSource, this._htmlDataSource); @override Future getEmailContent(AccountId accountId, EmailId emailId) { @@ -68,4 +70,9 @@ class EmailRepositoryImpl extends EmailRepository { ) { return emailDataSource.markAsStar(accountId, emails, markStarAction); } + + @override + Future transformEmailContentToHtml(EmailContent emailContent) { + return _htmlDataSource.transformToHtml(emailContent); + } } \ No newline at end of file diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index fb9ca9f64..c56dd6db3 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -34,4 +34,6 @@ abstract class EmailRepository { List emails, MarkStarAction markStarAction ); + + Future transformEmailContentToHtml(EmailContent emailContent); } \ No newline at end of file diff --git a/lib/features/email/domain/state/get_email_content_state.dart b/lib/features/email/domain/state/get_email_content_state.dart index 2726c39a8..d27e81241 100644 --- a/lib/features/email/domain/state/get_email_content_state.dart +++ b/lib/features/email/domain/state/get_email_content_state.dart @@ -2,13 +2,13 @@ import 'package:core/core.dart'; import 'package:model/model.dart'; class GetEmailContentSuccess extends UIState { - final List emailContents; + final EmailContent emailContent; final List attachments; - GetEmailContentSuccess(this.emailContents, this.attachments); + GetEmailContentSuccess(this.emailContent, this.attachments); @override - List get props => [emailContents, attachments]; + List get props => [emailContent, attachments]; } class GetEmailContentFailure extends FeatureFailure { diff --git a/lib/features/email/domain/usecases/get_email_content_interactor.dart b/lib/features/email/domain/usecases/get_email_content_interactor.dart index 60501dbdb..ae54f07dd 100644 --- a/lib/features/email/domain/usecases/get_email_content_interactor.dart +++ b/lib/features/email/domain/usecases/get_email_content_interactor.dart @@ -15,9 +15,20 @@ class GetEmailContentInteractor { try { yield Right(LoadingState()); final email = await emailRepository.getEmailContent(accountId, emailId); - yield Right(GetEmailContentSuccess( - email.emailContentList, - email.allAttachments)); + + if (email.emailContentList.isNotEmpty) { + final emailContent = email.emailContentList.first; + if (emailContent.type == EmailContentType.textHtml) { + final contentHtml = await emailRepository.transformEmailContentToHtml(emailContent); + yield Right(GetEmailContentSuccess( + EmailContent(emailContent.type, contentHtml), + email.allAttachments)); + } else { + yield Right(GetEmailContentSuccess(emailContent, email.allAttachments)); + } + } else { + yield Left(GetEmailContentFailure(null)); + } } catch (e) { yield Left(GetEmailContentFailure(e)); } diff --git a/lib/features/email/presentation/constants/email_constants.dart b/lib/features/email/presentation/constants/email_constants.dart deleted file mode 100644 index d3c8d15b1..000000000 --- a/lib/features/email/presentation/constants/email_constants.dart +++ /dev/null @@ -1,5 +0,0 @@ - -class EmailConstants { - static const HTML_TEXT = 'text/html'; - static const PLAIN_TEXT = 'text/plain'; -} \ No newline at end of file diff --git a/lib/features/email/presentation/email_bindings.dart b/lib/features/email/presentation/email_bindings.dart index 16fdcd87a..0d3c09408 100644 --- a/lib/features/email/presentation/email_bindings.dart +++ b/lib/features/email/presentation/email_bindings.dart @@ -3,7 +3,10 @@ import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; @@ -22,7 +25,11 @@ class EmailBindings extends Bindings { void dependencies() { Get.lazyPut(() => EmailDataSourceImpl(Get.find())); Get.lazyPut(() => Get.find()); - Get.lazyPut(() => EmailRepositoryImpl(Get.find())); + Get.lazyPut(() => HtmlDataSourceImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => EmailRepositoryImpl( + Get.find(), + Get.find())); Get.lazyPut(() => Get.find()); Get.put(SendEmailInteractor(Get.find())); Get.lazyPut(() => GetEmailContentInteractor(Get.find())); diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index b0ec46a01..7354d396c 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -49,7 +49,7 @@ class EmailController extends BaseController { final emailAddressExpandMode = ExpandMode.COLLAPSE.obs; final attachmentsExpandMode = ExpandMode.COLLAPSE.obs; - final emailContents = [].obs; + final emailContent = Rxn(); final attachments = [].obs; EmailId? _currentEmailId; @@ -129,9 +129,9 @@ class EmailController extends BaseController { } void _getEmailContentSuccess(GetEmailContentSuccess success) { - emailContents.value = success.emailContents; + emailContent.value = success.emailContent; attachments.value = success.attachments; - if (emailContents.isNotEmpty) { + if (emailContent.value != null && emailContent.value!.type == EmailContentType.textHtml) { dispatchState(Right(WebViewLoadingState())); } } @@ -139,7 +139,7 @@ class EmailController extends BaseController { void _clearEmailContent() { toggleDisplayEmailAddressAction(expandMode: ExpandMode.COLLAPSE); attachmentsExpandMode.value = ExpandMode.COLLAPSE; - emailContents.clear(); + emailContent.value = null; attachments.clear(); } @@ -347,24 +347,17 @@ class EmailController extends BaseController { && mailboxDashBoardController.selectedEmail.value != null; void backToThreadView() { - if (emailContents.isNotEmpty) { - dispatchState(Right(WebViewLoadCompletedState())); - } popBack(); } void pressEmailAction(EmailActionType emailActionType) { if (canComposeEmail()) { - if (emailContents.isNotEmpty) { - clearState(); - } - push( AppRoutes.COMPOSER, arguments: ComposerArguments( emailActionType: emailActionType, presentationEmail: mailboxDashBoardController.selectedEmail.value!, - emailContents: emailContents, + emailContent: emailContent.value, attachments: attachments, mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role, session: mailboxDashBoardController.sessionCurrent!, diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 1d2c18041..2555c0527 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -27,11 +27,6 @@ class EmailView extends GetView { @override Widget build(BuildContext context) { - emailController.viewState.value.map((success) { - if (success is WebViewLoadCompletedState && emailController.emailContents.isNotEmpty) { - emailController.dispatchState(Right(WebViewLoadingState())); - } - }); return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: AppColor.primaryLightColor, @@ -279,11 +274,27 @@ class EmailView extends GetView { } Widget _buildEmailContent() { - return Obx(() => emailController.emailContents.isNotEmpty - ? HtmlContentViewer( - message: emailController.emailContents.first.content, - onWebViewLoadStop: (webController, uri) => emailController.dispatchState(Right(WebViewLoadCompletedState())), - mailtoDelegate: (uri) async {}) - : SizedBox.shrink()); + return Obx(() { + if (emailController.emailContent.value != null) { + switch(emailController.emailContent.value!.type) { + case EmailContentType.textHtml: + return HtmlContentViewer( + contentHtml: emailController.emailContent.value!.content, + onLoadStart: (webController, uri) => emailController.dispatchState(Right(WebViewLoadingState())), + onLoadStop: (webController, uri) => emailController.dispatchState(Right(WebViewLoadCompletedState())), + mailtoDelegate: (uri) async {}); + case EmailContentType.textPlain: + return Padding( + padding: EdgeInsets.only(top: 16), + child: Text( + emailController.emailContent.value!.content, + style: TextStyle(fontSize: 14, color: AppColor.nameUserColor))); + case EmailContentType.other: + return SizedBox.shrink(); + } + } else { + return SizedBox.shrink(); + } + }); } } \ No newline at end of file diff --git a/lib/features/email/presentation/model/composer_arguments.dart b/lib/features/email/presentation/model/composer_arguments.dart index bb58e4fe5..8bb10728b 100644 --- a/lib/features/email/presentation/model/composer_arguments.dart +++ b/lib/features/email/presentation/model/composer_arguments.dart @@ -7,7 +7,7 @@ import 'package:model/model.dart'; class ComposerArguments with EquatableMixin { final EmailActionType emailActionType; final PresentationEmail? presentationEmail; - final List? emailContents; + final EmailContent? emailContent; final List? attachments; final Session session; final UserProfile userProfile; @@ -17,7 +17,7 @@ class ComposerArguments with EquatableMixin { ComposerArguments({ this.emailActionType = EmailActionType.compose, this.presentationEmail, - this.emailContents, + this.emailContent, this.attachments, this.mailboxRole, required this.session, @@ -29,7 +29,7 @@ class ComposerArguments with EquatableMixin { List get props => [ emailActionType, presentationEmail, - emailContents, + emailContent, attachments, mailboxRole, session, diff --git a/lib/features/thread/presentation/thread_bindings.dart b/lib/features/thread/presentation/thread_bindings.dart index 034d42224..6b98bc158 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -4,7 +4,10 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/caching/state_cache_client.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; @@ -48,7 +51,11 @@ class ThreadBindings extends Bindings { Get.lazyPut(() => ScrollController()); Get.lazyPut(() => EmailDataSourceImpl(Get.find())); Get.lazyPut(() => Get.find()); - Get.lazyPut(() => EmailRepositoryImpl(Get.find())); + Get.lazyPut(() => HtmlDataSourceImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => EmailRepositoryImpl( + Get.find(), + Get.find())); Get.lazyPut(() => Get.find()); Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find())); Get.lazyPut(() => MarkAsMultipleEmailReadInteractor(Get.find())); diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index 97d5ddc5a..02fcea767 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -3,6 +3,7 @@ import 'package:core/presentation/utils/app_toast.dart'; import 'package:device_info/device_info.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; class CoreBindings extends Bindings { @@ -35,6 +36,7 @@ class CoreBindings extends Bindings { void _bindingValidator() { Get.put(HtmlMessagePurifier()); + Get.put(HtmlAnalyzer()); } void _bindingToast() {