From 421fa321ba1cb7519e4c71c6a6ab4ea4728c2158 Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 21 May 2025 15:38:10 +0700 Subject: [PATCH] TF-3644 Handle scroll thread detail when cursor is in email content area Signed-off-by: dab246 --- .../html_content_viewer_on_web_widget.dart | 156 ++++++++++++------ .../email/presentation/email_view.dart | 5 +- .../get_thread_details_email_views.dart | 1 + 3 files changed, 114 insertions(+), 48 deletions(-) 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 4a7b6246f..a29f127fe 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 @@ -32,6 +32,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget { final bool allowResizeToDocumentSize; final bool keepWidthWhileLoading; + final ScrollController? scrollController; const HtmlContentViewerOnWeb({ Key? key, @@ -45,6 +46,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget { this.onClickHyperLinkAction, this.keepWidthWhileLoading = false, this.contentPadding, + this.scrollController, }) : super(key: key); @override @@ -65,10 +67,11 @@ class _HtmlContentViewerOnWebState extends State { String? _htmlData; bool _isLoading = true; double minHeight = 100; - late final StreamSubscription sizeListener; + late final StreamSubscription _onMessageSubscription; bool _iframeLoaded = false; static const String iframeOnLoadMessage = 'iframeHasBeenLoaded'; static const String onClickHyperLinkName = 'onClickHyperLink'; + static const String onScrollChangedEvent = 'onScrollChanged'; @override void initState() { @@ -77,64 +80,112 @@ class _HtmlContentViewerOnWebState extends State { _actualWidth = widget.widthContent; _createdViewId = _getRandString(10); _setUpWeb(); + _onMessageSubscription = html.window.onMessage.listen(_handleMessageEvent); + } - sizeListener = html.window.onMessage.listen((event) { - var data = json.decode(event.data); + void _handleMessageEvent(html.MessageEvent event) { + try { + final data = json.decode(event.data); - if (data['view'] != _createdViewId) return; + final viewId = data['view']; + if (viewId != _createdViewId) return; + + final type = data['type']; + if (_isScrollChangedEventTriggered(type)) { + _handleIframeOnScrollChangedListener(data, widget.scrollController!); + return; + } if (data['message'] == iframeOnLoadMessage) { _iframeLoaded = true; } - if (!_iframeLoaded) return; - if (data['type'] != null && data['type'].contains('toDart: htmlHeight')) { - final docHeight = data['height'] ?? _actualHeight; - if (docHeight != null && mounted) { - final scrollHeightWithBuffer = docHeight + 30.0; - if (scrollHeightWithBuffer > minHeight) { - setState(() { - _actualHeight = scrollHeightWithBuffer; - _isLoading = false; - }); - } - } - if (mounted && _isLoading) { - setState(() { - _isLoading = false; - }); - } + if (_isHtmlContentHeightEventTriggered(type)) { + _handleContentHeightEvent(data['height']); + } else if (_isHtmlContentWidthEventTriggered(type)) { + _handleContentWidthEvent(data['width']); + } else if (_isMailtoLinkEventTriggered(type)) { + _handleMailtoLinkEvent(data['url']); + } else if (_isHyperLinkEventTriggered(type)) { + _handleHyperLinkEvent(data['url']); } + } catch (e) { + logError('_HtmlContentViewerOnWebState::_handleMessageEvent:Exception = $e'); + } + } - if (data['type'] != null && data['type'].contains('toDart: htmlWidth') && !widget.keepWidthWhileLoading) { - final docWidth = data['width'] ?? _actualWidth; - if (docWidth != null && mounted) { - if (docWidth > _minWidth && widget.allowResizeToDocumentSize) { - setState(() { - _actualWidth = docWidth; - }); - } - } - } + bool _isScrollChangedEventTriggered(String? type) { + return widget.scrollController != null && + type?.contains('toDart: $onScrollChangedEvent') == true; + } - if (data['type'] != null && data['type'].contains('toDart: OpenLink')) { - final link = data['url']; - if (link != null && mounted) { - final urlString = link as String; - if (urlString.startsWith('mailto:')) { - widget.mailtoDelegate?.call(Uri.parse(urlString)); - } - } - } + void _handleIframeOnScrollChangedListener( + dynamic data, + 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); + } else { + controller.jumpTo(newOffset); + } + } - if (data['type'] != null && data['type'].contains('toDart: $onClickHyperLinkName')) { - final link = data['url'] as String?; - if (link != null && mounted) { - widget.onClickHyperLinkAction?.call(Uri.parse(link)); - } + bool _isHtmlContentHeightEventTriggered(String? type) => + type?.contains('toDart: htmlHeight') == true; + + void _handleContentHeightEvent(dynamic height) { + final docHeight = height ?? _actualHeight; + if (docHeight != null && mounted) { + final scrollHeightWithBuffer = docHeight + 30.0; + if (scrollHeightWithBuffer > minHeight) { + setState(() { + _actualHeight = scrollHeightWithBuffer; + _isLoading = false; + }); } - }); + } + if (mounted && _isLoading) { + setState(() { + _isLoading = false; + }); + } + } + + bool _isHtmlContentWidthEventTriggered(String? type) => + type?.contains('toDart: htmlWidth') == true && + !widget.keepWidthWhileLoading; + + void _handleContentWidthEvent(dynamic width) { + final docWidth = width ?? _actualWidth; + if (docWidth != null && mounted && + docWidth > _minWidth && + widget.allowResizeToDocumentSize) { + setState(() => _actualWidth = docWidth); + } + } + + bool _isMailtoLinkEventTriggered(String? type) => + type?.contains('toDart: OpenLink') == true; + + void _handleMailtoLinkEvent(dynamic url) { + if (url != null && mounted && url is String && url.startsWith('mailto:')) { + widget.mailtoDelegate?.call(Uri.tryParse(url)); + } + } + + bool _isHyperLinkEventTriggered(String? type) => + type?.contains('toDart: $onClickHyperLinkName') == true; + + void _handleHyperLinkEvent(dynamic url) { + if (url != null && mounted && url is String) { + widget.onClickHyperLinkAction?.call(Uri.tryParse(url)); + } } @override @@ -246,6 +297,17 @@ class _HtmlContentViewerOnWebState extends State { 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 + }), "*"); + }); + ''' : ''} '''; @@ -362,7 +424,7 @@ class _HtmlContentViewerOnWebState extends State { @override void dispose() { _htmlData = null; - sizeListener.cancel(); + _onMessageSubscription.cancel(); super.dispose(); } } \ No newline at end of file diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index f842101e9..e45f393a1 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -56,6 +56,7 @@ class EmailView extends GetWidget { this.isFirstEmailInThreadDetail = false, this.threadSubject, this.onToggleThreadDetailCollapseExpand, + this.scrollController, }); final bool isInsideThreadDetailView; @@ -63,7 +64,8 @@ class EmailView extends GetWidget { final bool isFirstEmailInThreadDetail; final String? threadSubject; final VoidCallback? onToggleThreadDetailCollapseExpand; - + final ScrollController? scrollController; + @override String? get tag => emailId?.id.value; @@ -388,6 +390,7 @@ class EmailView extends GetWidget { direction: AppUtils.getCurrentDirection(context), contentPadding: 0, useDefaultFont: true, + scrollController: scrollController, ), if (controller.mailboxDashBoardController.isDisplayedOverlayViewOnIFrame) PointerInterceptor( diff --git a/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart b/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart index 4da6a542e..e6f6c9205 100644 --- a/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart +++ b/lib/features/thread_detail/presentation/extension/get_thread_details_email_views.dart @@ -82,6 +82,7 @@ extension GetThreadDetailEmailViews on ThreadDetailController { onToggleThreadDetailCollapseExpand: () { toggleThreadDetailCollapeExpand(presentationEmail); }, + scrollController: scrollController, ), ); }).toList();