TF-105 [BUG] Fix long line should be wrapped

This commit is contained in:
dab246
2021-10-12 16:51:30 +07:00
committed by Dat H. Pham
parent cb2c889fe2
commit dd5e8e4519
22 changed files with 243 additions and 286 deletions
@@ -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;
''';
}
}
}
}
@@ -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;';
}
}
}
@@ -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;';
}
}
}
}
@@ -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);
}
}
@@ -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<DomTransformer> 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<TextTransformer> standardTextTransformers = [
@@ -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<HtmlContentViewer> {
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 = '''
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
#editor {
outline: 0px solid transparent;
min-height: ${widget.minHeight}px;
min-width: 300px;
color: #182952;
font-family: verdana;
}
blockquote {
font: normal helvetica, verdana;
margin-top: 10px;
margin-bottom: 10px;
margin-left: 20px;
padding-left: 15px;
border-left: 5px solid #eee;
}
table {
width: 100%;
max-width: 100%;
}
td {
padding: 13px;
margin: 0px;
}
th {
padding: 13px;
margin: 0px;
}
</style>
<script>
var documentHeight;
static _HtmlGenerationResult _generateHtmlImpl(_HtmlGenerationArguments args) {
try {
final htmlTransform = HtmlTransform(args.message);
final html = htmlTransform.transformToHtml(
blockExternalImages: args.blockExternalImages,
);
return _HtmlGenerationResult.success(html);
} catch (e, s) {
String errorDetails = e.toString() + '\n\n' + s.toString();
return _HtmlGenerationResult.error(errorDetails);
}
function onLoaded() {
documentHeight = document.body.scrollHeight;
document.execCommand("styleWithCSS", false, true);
}
</script>
</head>
<body onload="onLoaded();">
<div id="editor">$content</div>
</body>
</html>
''';
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<HtmlContentViewer> {
);
}
void _onWebViewCreated(InAppWebViewController controller) {
_webViewController = controller;
if (widget.onCreated != null) {
widget.onCreated!(_webViewController);
}
}
Future<NavigationActionPolicy> _shouldOverrideUrlLoading(
InAppWebViewController controller,
NavigationAction request
@@ -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 = '</br></br></br>$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}</br>';
@@ -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)
@@ -174,17 +174,23 @@ class ComposerView extends GetWidget<ComposerController> {
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,
)
@@ -0,0 +1,6 @@
import 'package:model/model.dart';
abstract class HtmlDataSource {
Future<String> transformToHtml(EmailContent emailContent);
}
@@ -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<String> transformToHtml(EmailContent emailContent) {
return Future.sync(() async {
return await _htmlAnalyzer.transformToHtml(emailContent);
}).catchError((error) {
throw error;
});
}
}
@@ -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<String> transformToHtml(EmailContent emailContent) async {
switch(emailContent.type) {
case EmailContentType.textHtml:
final htmlTransform = HtmlTransform(emailContent.content);
return htmlTransform.transformToHtml();
default:
return emailContent.content;
}
}
}
@@ -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<Email> getEmailContent(AccountId accountId, EmailId emailId) {
@@ -68,4 +70,9 @@ class EmailRepositoryImpl extends EmailRepository {
) {
return emailDataSource.markAsStar(accountId, emails, markStarAction);
}
@override
Future<String> transformEmailContentToHtml(EmailContent emailContent) {
return _htmlDataSource.transformToHtml(emailContent);
}
}
@@ -34,4 +34,6 @@ abstract class EmailRepository {
List<Email> emails,
MarkStarAction markStarAction
);
Future<String> transformEmailContentToHtml(EmailContent emailContent);
}
@@ -2,13 +2,13 @@ import 'package:core/core.dart';
import 'package:model/model.dart';
class GetEmailContentSuccess extends UIState {
final List<EmailContent> emailContents;
final EmailContent emailContent;
final List<Attachment> attachments;
GetEmailContentSuccess(this.emailContents, this.attachments);
GetEmailContentSuccess(this.emailContent, this.attachments);
@override
List<Object?> get props => [emailContents, attachments];
List<Object?> get props => [emailContent, attachments];
}
class GetEmailContentFailure extends FeatureFailure {
@@ -15,9 +15,20 @@ class GetEmailContentInteractor {
try {
yield Right<Failure, Success>(LoadingState());
final email = await emailRepository.getEmailContent(accountId, emailId);
yield Right<Failure, Success>(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<Failure, Success>(GetEmailContentSuccess(
EmailContent(emailContent.type, contentHtml),
email.allAttachments));
} else {
yield Right<Failure, Success>(GetEmailContentSuccess(emailContent, email.allAttachments));
}
} else {
yield Left(GetEmailContentFailure(null));
}
} catch (e) {
yield Left(GetEmailContentFailure(e));
}
@@ -1,5 +0,0 @@
class EmailConstants {
static const HTML_TEXT = 'text/html';
static const PLAIN_TEXT = 'text/plain';
}
@@ -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<EmailAPI>()));
Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>());
Get.lazyPut(() => EmailRepositoryImpl(Get.find<EmailDataSource>()));
Get.lazyPut(() => HtmlDataSourceImpl(Get.find<HtmlAnalyzer>()));
Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>());
Get.lazyPut(() => EmailRepositoryImpl(
Get.find<EmailDataSource>(),
Get.find<HtmlDataSource>()));
Get.lazyPut<EmailRepository>(() => Get.find<EmailRepositoryImpl>());
Get.put(SendEmailInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => GetEmailContentInteractor(Get.find<EmailRepository>()));
@@ -49,7 +49,7 @@ class EmailController extends BaseController {
final emailAddressExpandMode = ExpandMode.COLLAPSE.obs;
final attachmentsExpandMode = ExpandMode.COLLAPSE.obs;
final emailContents = <EmailContent>[].obs;
final emailContent = Rxn<EmailContent>();
final attachments = <Attachment>[].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!,
+22 -11
View File
@@ -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();
}
});
}
}
@@ -7,7 +7,7 @@ import 'package:model/model.dart';
class ComposerArguments with EquatableMixin {
final EmailActionType emailActionType;
final PresentationEmail? presentationEmail;
final List<EmailContent>? emailContents;
final EmailContent? emailContent;
final List<Attachment>? 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<Object?> get props => [
emailActionType,
presentationEmail,
emailContents,
emailContent,
attachments,
mailboxRole,
session,
@@ -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<EmailAPI>()));
Get.lazyPut<EmailDataSource>(() => Get.find<EmailDataSourceImpl>());
Get.lazyPut(() => EmailRepositoryImpl(Get.find<EmailDataSource>()));
Get.lazyPut(() => HtmlDataSourceImpl(Get.find<HtmlAnalyzer>()));
Get.lazyPut<HtmlDataSource>(() => Get.find<HtmlDataSourceImpl>());
Get.lazyPut(() => EmailRepositoryImpl(
Get.find<EmailDataSource>(),
Get.find<HtmlDataSource>()));
Get.lazyPut<EmailRepository>(() => Get.find<EmailRepositoryImpl>());
Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MarkAsMultipleEmailReadInteractor(Get.find<EmailRepository>()));
@@ -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() {