Add lazy loading for html tag use background-image

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit 7f1a3b22ee8973c6756534a2c7da3cc1964ea9de)
This commit is contained in:
dab246
2023-10-16 18:45:27 +07:00
committed by Dat H. Pham
parent 72ba6d4145
commit 967015d0df
6 changed files with 130 additions and 16 deletions
@@ -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('<head></head>'));
}
}
Tuple2<String, String>? 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;
}
}
}
@@ -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<void> process({
required Document document,
required DioClient dioClient,
Map<String, String>? 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}');
}
}));
}
}
@@ -72,6 +72,29 @@ class HtmlUtils {
</script>
''';
static const scriptsHandleLazyLoadingBackgroundImage = '''
<script>
const lazyImages = document.querySelectorAll('[lazy]');
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const lazyImage = entry.target;
const src = lazyImage.dataset.src;
lazyImage.tagName.toLowerCase() === 'img'
? lazyImage.src = src
: lazyImage.style.backgroundImage = "url(\'" + src + "\')";
lazyImage.removeAttribute('lazy');
observer.unobserve(lazyImage);
}
});
});
lazyImages.forEach((lazyImage) => {
lazyImageObserver.observe(lazyImage);
});
</script>
''';
static String customCssStyleHtmlEditor({TextDirection direction = TextDirection.ltr}) {
if (PlatformInfo.isWeb) {
return '''
@@ -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<TextTransformer> standardTextTransformers = [
@@ -47,6 +47,7 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
late InAppWebViewController _webViewController;
late double _actualHeight;
late Set<Factory<OneSequenceGestureRecognizer>> _gestureRecognizers;
late String _customScripts;
final _loadingBarNotifier = ValueNotifier(true);
@@ -60,7 +61,6 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
@override
void initState() {
super.initState();
_actualHeight = _minHeight;
if (PlatformInfo.isAndroid) {
_gestureRecognizers = {
Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()),
@@ -71,13 +71,12 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
Factory<LongPressGestureRecognizer>(() => 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<HtmlContentViewer> {
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: [
+34
View File
@@ -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);
});
});
}