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