diff --git a/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart b/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart
index 2f66dc8f7..2c23c6959 100644
--- a/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart
+++ b/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart
@@ -8,6 +8,7 @@ import 'package:core/utils/app_logger.dart';
import 'package:core/utils/html/html_interaction.dart';
import 'package:core/utils/html/html_template.dart';
import 'package:core/utils/html/html_utils.dart';
+import 'package:core/utils/platform_info.dart';
import 'package:flutter/cupertino.dart';
import 'package:universal_html/html.dart' as html;
@@ -87,6 +88,7 @@ class _HtmlContentViewerOnWebState extends State
static const String iframeOnLoadMessage = 'iframeHasBeenLoaded';
static const String onClickHyperLinkName = 'onClickHyperLink';
static const String onScrollChangedEvent = 'onScrollChanged';
+ static const String onScrollEndEvent = 'onScrollEnd';
@override
void initState() {
@@ -106,9 +108,12 @@ class _HtmlContentViewerOnWebState extends State
if (viewId != _createdViewId) return;
final type = data['type'];
- if (_isScrollChangedEventTriggered(type)) {
+ if (_isScrollingIsAvailable && _isScrollChangedEventTriggered(type)) {
_handleIframeOnScrollChangedListener(data, widget.scrollController!);
return;
+ } else if (_isScrollingIsAvailable && _isScrollEndEventTriggered(type)) {
+ _handleIframeOnScrollEndListener(data, widget.scrollController!);
+ return;
}
if (data['message'] == iframeOnLoadMessage) {
@@ -130,10 +135,17 @@ class _HtmlContentViewerOnWebState extends State
}
}
- bool _isScrollChangedEventTriggered(String? type) {
+ bool get _isScrollingIsAvailable {
return widget.scrollController != null &&
- widget.scrollController?.hasClients == true &&
- type?.contains('toDart: $onScrollChangedEvent') == true;
+ widget.scrollController?.hasClients == true;
+ }
+
+ bool _isScrollChangedEventTriggered(String? type) {
+ return type?.contains('toDart: $onScrollChangedEvent') == true;
+ }
+
+ bool _isScrollEndEventTriggered(String? type) {
+ return type?.contains('toDart: $onScrollEndEvent') == true;
}
void _handleIframeOnScrollChangedListener(
@@ -141,17 +153,51 @@ class _HtmlContentViewerOnWebState extends State
ScrollController controller,
) {
final deltaY = data['deltaY'] ?? 0.0;
- final newOffset = controller.offset + deltaY;
- log('_HtmlContentViewerOnWebState::_handleIframeOnScrollChangedListener:deltaY = $deltaY | newOffset = $newOffset');
- if (newOffset < controller.position.minScrollExtent) {
- controller.jumpTo(controller.position.minScrollExtent);
- } else if (newOffset > controller.position.maxScrollExtent) {
- controller.jumpTo(controller.position.maxScrollExtent);
+ final target = controller.offset + deltaY;
+
+ if (PlatformInfo.isWebTouchDevice) {
+ final newOffset = target.clamp(
+ controller.position.minScrollExtent,
+ controller.position.maxScrollExtent,
+ );
+
+ controller.animateTo(
+ newOffset,
+ duration: const Duration(milliseconds: 50),
+ curve: Curves.linear,
+ );
} else {
- controller.jumpTo(newOffset);
+ if (target < controller.position.minScrollExtent) {
+ controller.jumpTo(controller.position.minScrollExtent);
+ } else if (target > controller.position.maxScrollExtent) {
+ controller.jumpTo(controller.position.maxScrollExtent);
+ } else {
+ controller.jumpTo(target);
+ }
}
}
+ void _handleIframeOnScrollEndListener(
+ dynamic data,
+ ScrollController controller,
+ ) {
+ final velocity = data['velocity'] ?? 0.0;
+
+ final distance = velocity * 800;
+ final target = controller.offset + distance;
+
+ final newOffset = target.clamp(
+ controller.position.minScrollExtent,
+ controller.position.maxScrollExtent,
+ );
+
+ controller.animateTo(
+ newOffset,
+ duration: const Duration(milliseconds: 600),
+ curve: Curves.decelerate,
+ );
+ }
+
bool _isHtmlContentHeightEventTriggered(String? type) =>
type?.contains('toDart: htmlHeight') == true;
@@ -319,17 +365,6 @@ class _HtmlContentViewerOnWebState extends State
${!widget.autoAdjustHeight ? 'resizeObserver.observe(document.body);' : ''}
}
-
- ${widget.scrollController != null ? '''
- window.addEventListener('wheel', function (event) {
- const deltaY = event.deltaY;
- window.parent.postMessage(JSON.stringify({
- "view": "$_createdViewId",
- "type": "toDart: $onScrollChangedEvent",
- "deltaY": deltaY
- }), "*");
- });
- ''' : ''}
''';
@@ -349,6 +384,17 @@ class _HtmlContentViewerOnWebState extends State
HtmlInteraction.scriptsHandleLazyLoadingBackgroundImage,
HtmlInteraction.generateNormalizeImageScript(widget.widthContent),
if (widget.enableQuoteToggle) HtmlUtils.quoteToggleScript,
+ if (widget.scrollController != null)
+ PlatformInfo.isWebTouchDevice
+ ? HtmlInteraction.scriptsTouchEventListener(
+ viewId: _createdViewId,
+ onScrollChangedEvent: onScrollChangedEvent,
+ onScrollEndEvent: onScrollEndEvent,
+ )
+ : HtmlInteraction.scriptsWheelEventListener(
+ viewId: _createdViewId,
+ onScrollChangedEvent: onScrollChangedEvent,
+ ),
].join();
final htmlTemplate = HtmlUtils.generateHtmlDocument(
diff --git a/core/lib/utils/html/html_interaction.dart b/core/lib/utils/html/html_interaction.dart
index 1936bffd7..b3141baf1 100644
--- a/core/lib/utils/html/html_interaction.dart
+++ b/core/lib/utils/html/html_interaction.dart
@@ -298,4 +298,84 @@ class HtmlInteraction {
});
''';
+
+ static String scriptsWheelEventListener({
+ required String viewId,
+ required String onScrollChangedEvent,
+ }) => '''
+
+ ''';
+
+ static String scriptsTouchEventListener({
+ required String viewId,
+ required String onScrollChangedEvent,
+ required String onScrollEndEvent,
+ }) => '''
+
+
+ ''';
}
\ No newline at end of file
diff --git a/core/lib/utils/platform_info.dart b/core/lib/utils/platform_info.dart
index b9eaec8ea..fd0427de2 100644
--- a/core/lib/utils/platform_info.dart
+++ b/core/lib/utils/platform_info.dart
@@ -1,6 +1,7 @@
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/web_renderer/canvas_kit.dart';
import 'package:flutter/foundation.dart';
+import 'package:universal_html/html.dart' as universal_html;
abstract class PlatformInfo {
@visibleForTesting
@@ -44,4 +45,25 @@ abstract class PlatformInfo {
log('PlatformInfo::platformNameOS(): $platformName');
return platformName;
}
+
+ static String get userAgent =>
+ isWeb ? universal_html.window.navigator.userAgent.toLowerCase() : '';
+
+ /// Web browser on a mobile phone
+ static bool get isWebMobile =>
+ isWeb && (userAgent.contains('iphone') ||
+ (userAgent.contains('android') && userAgent.contains('mobile')));
+
+ /// Web browser on a tablet device
+ static bool get isWebTablet =>
+ isWeb && (userAgent.contains('ipad') ||
+ (userAgent.contains('android') && !userAgent.contains('mobile')));
+
+ /// Web browser on a desktop (PC/Mac)
+ static bool get isWebDesktop =>
+ isWeb && !isWebMobile && !isWebTablet;
+
+ /// Touch devices
+ static bool get isWebTouchDevice =>
+ isWeb && (isWebMobile || isWebTablet);
}