From 967015d0df91a60ffc45a010ba62c38ae8e0af95 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 16 Oct 2023 18:45:27 +0700 Subject: [PATCH] Add lazy loading for html tag use background-image Signed-off-by: dab246 (cherry picked from commit 7f1a3b22ee8973c6756534a2c7da3cc1964ea9de) --- .../base/dom_transformer.dart | 24 +++++++++++++ ...ing_for_background_image_transformers.dart | 29 ++++++++++++++++ .../utils/html_transformer/html_utils.dart | 23 +++++++++++++ .../transform_configuration.dart | 3 ++ .../html_content_viewer_widget.dart | 33 +++++++++--------- core/test/utils/html_utils_test.dart | 34 +++++++++++++++++++ 6 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 core/lib/presentation/utils/html_transformer/dom/add_lazy_loading_for_background_image_transformers.dart create mode 100644 core/test/utils/html_utils_test.dart diff --git a/core/lib/presentation/utils/html_transformer/base/dom_transformer.dart b/core/lib/presentation/utils/html_transformer/base/dom_transformer.dart index 66922d441..b09b285c2 100644 --- a/core/lib/presentation/utils/html_transformer/base/dom_transformer.dart +++ b/core/lib/presentation/utils/html_transformer/base/dom_transformer.dart @@ -1,5 +1,7 @@ import 'package:core/data/network/dio_client.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart'; import 'package:html/dom.dart'; /// Transforms the HTML DOM. @@ -22,4 +24,26 @@ abstract class DomTransformer { document.children.insert(0, Element.html('')); } } + + Tuple2? findImageUrlFromStyleTag(String style) { + try { + final regExp = RegExp(r'background-image:\s*url\(([^)]+)\).*?'); + final match = regExp.firstMatch(style); + if (match == null) { + return null; + } + + final backgroundImageUrl = match.group(0) ?? ''; + final imageUrl = match.group(1)?.replaceAll('\'', '').replaceAll('"', '') ?? ''; + log('DomTransformer::findImageUrlFromStyleTag:backgroundImageUrl: $backgroundImageUrl | imageUrl: $imageUrl'); + if (backgroundImageUrl.isNotEmpty && imageUrl.isNotEmpty) { + return Tuple2(backgroundImageUrl, imageUrl); + } else { + return null; + } + } catch (e) { + logError('DomTransformer::findImageUrlFromStyleTag:Exception: $e'); + return null; + } + } } \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/dom/add_lazy_loading_for_background_image_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/add_lazy_loading_for_background_image_transformers.dart new file mode 100644 index 000000000..44d047412 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/dom/add_lazy_loading_for_background_image_transformers.dart @@ -0,0 +1,29 @@ +import 'package:core/data/network/dio_client.dart'; +import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:html/dom.dart'; + +class AddLazyLoadingForBackgroundImageTransformer extends DomTransformer { + const AddLazyLoadingForBackgroundImageTransformer(); + + @override + Future process({ + required Document document, + required DioClient dioClient, + Map? mapUrlDownloadCID, + }) async { + final elements = document.querySelectorAll('[style*="background-image"]'); + log('AddLazyLoadingForBackgroundImageTagTransformer::process:elements: ${elements.length}'); + await Future.wait(elements.map((element) async { + var exStyle = element.attributes['style']; + final imageUrls = findImageUrlFromStyleTag(exStyle!); + if (imageUrls != null) { + exStyle = exStyle.replaceFirst(imageUrls.value1, ''); + element.attributes['style'] = exStyle; + element.attributes['data-src'] = imageUrls.value2; + element.attributes.addAll({'lazy': ''}); + log('AddLazyLoadingForBackgroundImageTagTransformer::process:NEW_ELEMENT: ${element.outerHtml}'); + } + })); + } +} diff --git a/core/lib/presentation/utils/html_transformer/html_utils.dart b/core/lib/presentation/utils/html_transformer/html_utils.dart index 4ae3f938c..abfd1f33a 100644 --- a/core/lib/presentation/utils/html_transformer/html_utils.dart +++ b/core/lib/presentation/utils/html_transformer/html_utils.dart @@ -72,6 +72,29 @@ class HtmlUtils { '''; + static const scriptsHandleLazyLoadingBackgroundImage = ''' + + '''; + static String customCssStyleHtmlEditor({TextDirection direction = TextDirection.ltr}) { if (PlatformInfo.isWeb) { return ''' diff --git a/core/lib/presentation/utils/html_transformer/transform_configuration.dart b/core/lib/presentation/utils/html_transformer/transform_configuration.dart index f581e3d4c..4ee39adee 100644 --- a/core/lib/presentation/utils/html_transformer/transform_configuration.dart +++ b/core/lib/presentation/utils/html_transformer/transform_configuration.dart @@ -2,6 +2,7 @@ import 'package:core/core.dart'; import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; +import 'package:core/presentation/utils/html_transformer/dom/add_lazy_loading_for_background_image_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/blockcode_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/blockquoted_transformers.dart'; @@ -48,6 +49,7 @@ class TransformConfiguration { const AddTargetBlankInTagATransformer(), const ImageTransformer(), const AddTooltipLinkTransformer(), + const AddLazyLoadingForBackgroundImageTransformer(), ] ); @@ -86,6 +88,7 @@ class TransformConfiguration { BlockCodeTransformer(), AddTargetBlankInTagATransformer(), ImageTransformer(), + AddLazyLoadingForBackgroundImageTransformer(), ]; 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 0eda8c597..88028f9d8 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 @@ -47,6 +47,7 @@ class _HtmlContentViewState extends State { late InAppWebViewController _webViewController; late double _actualHeight; late Set> _gestureRecognizers; + late String _customScripts; final _loadingBarNotifier = ValueNotifier(true); @@ -60,7 +61,6 @@ class _HtmlContentViewState extends State { @override void initState() { super.initState(); - _actualHeight = _minHeight; if (PlatformInfo.isAndroid) { _gestureRecognizers = { Factory(() => LongPressGestureRecognizer()), @@ -71,13 +71,12 @@ class _HtmlContentViewState extends State { Factory(() => LongPressGestureRecognizer()), }; } - _htmlData = generateHtml( - widget.contentHtml, - direction: widget.direction, - javaScripts: PlatformInfo.isAndroid - ? HtmlUtils.scriptsHandleContentSizeChanged - : null - ); + if (PlatformInfo.isAndroid) { + _customScripts = HtmlUtils.scriptsHandleLazyLoadingBackgroundImage + HtmlUtils.scriptsHandleContentSizeChanged; + } else { + _customScripts = HtmlUtils.scriptsHandleLazyLoadingBackgroundImage; + } + _initialData(); } @override @@ -86,17 +85,19 @@ class _HtmlContentViewState extends State { log('_HtmlContentViewState::didUpdateWidget():Old-Direction: ${oldWidget.direction} | Current-Direction: ${widget.direction}'); if (widget.contentHtml != oldWidget.contentHtml || widget.direction != oldWidget.direction) { - _actualHeight = _minHeight; - _htmlData = generateHtml( - widget.contentHtml, - direction: widget.direction, - javaScripts: PlatformInfo.isAndroid - ? HtmlUtils.scriptsHandleContentSizeChanged - : null - ); + _initialData(); } } + void _initialData() { + _actualHeight = _minHeight; + _htmlData = generateHtml( + widget.contentHtml, + direction: widget.direction, + javaScripts: _customScripts + ); + } + @override Widget build(BuildContext context) { return Stack(children: [ diff --git a/core/test/utils/html_utils_test.dart b/core/test/utils/html_utils_test.dart new file mode 100644 index 000000000..c1228052a --- /dev/null +++ b/core/test/utils/html_utils_test.dart @@ -0,0 +1,34 @@ +import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const imageTransformer = ImageTransformer(); + + group('findImageUrlFromStyleTag test', () { + test('Test findImageUrlFromStyleTag with valid input', () { + const style = 'background-image: url(\'example.com/image.jpg\');'; + final result = imageTransformer.findImageUrlFromStyleTag(style); + expect(result!.value1, 'background-image: url(\'example.com/image.jpg\')'); + expect(result.value2, 'example.com/image.jpg'); + }); + + test('Test findImageUrlFromStyleTag with valid input and no quotation marks', () { + const style = 'background-image: url(example.com/image.jpg);'; + final result = imageTransformer.findImageUrlFromStyleTag(style); + expect(result!.value1, 'background-image: url(example.com/image.jpg)'); + expect(result.value2, 'example.com/image.jpg'); + }); + + test('Test findImageUrlFromStyleTag with empty input', () { + const style = ''; + final result = imageTransformer.findImageUrlFromStyleTag(style); + expect(result, null); + }); + + test('Test findImageUrlFromStyleTag with invalid input', () { + const style = 'background-image: invalid-url(\'example.com/image.jpg\');'; + final result = imageTransformer.findImageUrlFromStyleTag(style); + expect(result, null); + }); + }); +} \ No newline at end of file