diff --git a/core/lib/presentation/utils/html_transformer/html_event_action.dart b/core/lib/presentation/utils/html_transformer/html_event_action.dart new file mode 100644 index 000000000..c1093ced7 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/html_event_action.dart @@ -0,0 +1,5 @@ + +class HtmlEventAction { + static const scrollRightEndAction = 'ScrollRightEndAction'; + static const scrollLeftEndAction = 'ScrollLeftEndAction'; +} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/html_utils.dart b/core/lib/presentation/utils/html_transformer/html_utils.dart new file mode 100644 index 000000000..c187bf6ea --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/html_utils.dart @@ -0,0 +1,68 @@ + +import 'package:core/presentation/utils/html_transformer/html_event_action.dart'; + +class HtmlUtils { + + static const scrollEventJSChannelName = 'ScrollEventListener'; + + static const runScriptsHandleScrollEvent = ''' + let contentElement = document.getElementsByClassName('tmail-content')[0]; + var xDown = null; + var yDown = null; + + contentElement.addEventListener('touchstart', handleTouchStart, false); + contentElement.addEventListener('touchmove', handleTouchMove, false); + + function getTouches(evt) { + return evt.touches || evt.originalEvent.touches; + } + + function handleTouchStart(evt) { + const firstTouch = getTouches(evt)[0]; + xDown = firstTouch.clientX; + yDown = firstTouch.clientY; + } + + function handleTouchMove(evt) { + if (!xDown || !yDown) { + return; + } + + var xUp = evt.touches[0].clientX; + var yUp = evt.touches[0].clientY; + + var xDiff = xDown - xUp; + var yDiff = yDown - yUp; + + if (Math.abs(xDiff) > Math.abs(yDiff)) { + let newScrollLeft = contentElement.scrollLeft; + let scrollWidth = contentElement.scrollWidth; + let offsetWidth = contentElement.offsetWidth; + let maxOffset = Math.round(scrollWidth - offsetWidth); + let scrollLeftRounded = Math.round(newScrollLeft); + + /* + console.log('newScrollLeft: ' + newScrollLeft); + console.log('scrollWidth: ' + scrollWidth); + console.log('offsetWidth: ' + offsetWidth); + console.log('maxOffset: ' + maxOffset); + console.log('scrollLeftRounded: ' + scrollLeftRounded); */ + + if (xDiff > 0) { + if (maxOffset === scrollLeftRounded || + maxOffset === (scrollLeftRounded + 1) || + maxOffset === (scrollLeftRounded - 1)) { + window.$scrollEventJSChannelName.postMessage('${HtmlEventAction.scrollRightEndAction}'); + } + } else { + if (scrollLeftRounded === 0) { + window.$scrollEventJSChannelName.postMessage('${HtmlEventAction.scrollLeftEndAction}'); + } + } + } + + xDown = null; + yDown = null; + } + '''; +} 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 083df6c73..23561a07f 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 @@ -3,6 +3,8 @@ import 'dart:io'; import 'dart:ui'; import 'package:core/core.dart'; +import 'package:core/presentation/utils/html_transformer/html_event_action.dart'; +import 'package:core/presentation/utils/html_transformer/html_utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -10,12 +12,16 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart' as launcher; import 'package:url_launcher/url_launcher_string.dart'; import 'package:webview_flutter/webview_flutter.dart'; -import 'dart:developer' as developer; + +typedef OnScrollHorizontalEnd = Function(bool leftDirection); +typedef OnWebViewLoaded = Function(bool isScrollPageViewActivated); class HtmlContentViewer extends StatefulWidget { final String contentHtml; final double heightContent; + final OnScrollHorizontalEnd? onScrollHorizontalEnd; + final OnWebViewLoaded? onWebViewLoaded; /// Register this callback if you want a reference to the [WebViewController]. final void Function(WebViewController controller)? onCreated; @@ -33,6 +39,8 @@ class HtmlContentViewer extends StatefulWidget { required this.contentHtml, required this.heightContent, this.onCreated, + this.onWebViewLoaded, + this.onScrollHorizontalEnd, this.urlLauncherDelegate, this.mailtoDelegate, }) : super(key: key); @@ -56,13 +64,7 @@ class _HtmlContentViewState extends State { super.initState(); actualHeight = widget.heightContent; maxHeightForAndroid = window.physicalSize.height; - log('_HtmlContentViewState::initState(): maxHeightForAndroid: $maxHeightForAndroid'); - _htmlData = _generateHtmlDocument(widget.contentHtml); - } - - String _generateHtmlDocument(String content) { - final htmlTemplate = generateHtml(content); - return htmlTemplate; + _htmlData = generateHtml(widget.contentHtml); } @override @@ -74,7 +76,11 @@ class _HtmlContentViewState extends State { height: actualHeight, width: constraints.maxWidth, child: _buildWebView()), - if (_isLoading) Align(alignment: Alignment.center, child: _buildLoadingView()) + if (_isLoading) + Align( + alignment: Alignment.center, + child: _buildLoadingView() + ) ], ); }); @@ -103,40 +109,95 @@ class _HtmlContentViewState extends State { await controller.loadHtmlString(htmlData, baseUrl: null); widget.onCreated?.call(controller); }, - onPageFinished: (url) async { - final scrollHeightText = await _webViewController.runJavascriptReturningResult('document.body.scrollHeight'); - final scrollHeight = double.tryParse(scrollHeightText); - developer.log('onPageFinished(): scrollHeightText: $scrollHeightText', name: 'HtmlContentViewer'); - if ((scrollHeight != null) && mounted) { - final scrollHeightWithBuffer = scrollHeight + 30.0; - if (scrollHeightWithBuffer > minHeight) { - setState(() { - //TODO: It hotfix for web_view crash on android device and waiting lib web_view update to fix this issue - if (Platform.isAndroid && scrollHeightWithBuffer > maxHeightForAndroid){ - actualHeight = maxHeightForAndroid; - } else { - actualHeight = scrollHeightWithBuffer; - } - _isLoading = false; - }); - } - } - if (mounted && _isLoading) { - setState(() { - _isLoading = false; - }); - } - }, + onPageFinished: _onPageFinished, zoomEnabled: false, navigationDelegate: _onNavigation, gestureRecognizers: { Factory(() => LongPressGestureRecognizer()), - if (Platform.isAndroid) - Factory(() => ScaleGestureRecognizer()), + Factory(() => ScaleGestureRecognizer()), }, + javascriptChannels: { + JavascriptChannel( + name: HtmlUtils.scrollEventJSChannelName, + onMessageReceived: _onHandleScrollEvent + ) + }, + gestureNavigationEnabled: true, + debuggingEnabled: true, ); } + void _onPageFinished(String url) async { + await Future.wait([ + _webViewController.runJavascript(HtmlUtils.runScriptsHandleScrollEvent), + _setActualHeightView(), + _setActualWidthView(), + ]); + + _hideLoadingProgress(); + } + + void _onHandleScrollEvent(JavascriptMessage javascriptMessage) { + log('_HtmlContentViewState::_onHandleScrollEvent():message: ${javascriptMessage.message}'); + if (javascriptMessage.message == HtmlEventAction.scrollRightEndAction) { + widget.onScrollHorizontalEnd?.call(false); + } else if (javascriptMessage.message == HtmlEventAction.scrollLeftEndAction) { + widget.onScrollHorizontalEnd?.call(true); + } + } + + Future _setActualHeightView() async { + final scrollHeightText = await _webViewController.runJavascriptReturningResult('document.body.scrollHeight'); + final scrollHeight = double.tryParse(scrollHeightText); + log('_HtmlContentViewState::_setActualHeightView(): scrollHeightText: $scrollHeightText'); + if (scrollHeight != null && mounted) { + final scrollHeightWithBuffer = scrollHeight + 30.0; + if (scrollHeightWithBuffer > minHeight) { + setState(() { + // It hotfix for web_view crash on android device and waiting lib web_view update to fix this issue + if (Platform.isAndroid && scrollHeightWithBuffer > maxHeightForAndroid){ + actualHeight = maxHeightForAndroid; + } else { + actualHeight = scrollHeightWithBuffer; + } + _isLoading = false; + }); + } + } + + return Future.value(null); + } + + Future _setActualWidthView() async { + final result = await Future.wait([ + _webViewController.runJavascriptReturningResult('document.getElementsByClassName("tmail-content")[0].scrollWidth'), + _webViewController.runJavascriptReturningResult('document.getElementsByClassName("tmail-content")[0].offsetWidth') + ]); + + if (result.length == 2) { + final scrollWidth = double.tryParse(result[0]); + final offsetWidth = double.tryParse(result[1]); + log('_HtmlContentViewState::_setActualWidthView():scrollWidth: $scrollWidth'); + log('_HtmlContentViewState::_setActualWidthView():offsetWidth: $offsetWidth'); + + if (scrollWidth != null && offsetWidth != null && mounted) { + final isScrollActivated = scrollWidth.round() == offsetWidth.round(); + log('_HtmlContentViewState::_setActualWidthView():isScrollActivated: $isScrollActivated'); + widget.onWebViewLoaded?.call(isScrollActivated); + } + } + + return Future.value(null); + } + + void _hideLoadingProgress() { + if (mounted && _isLoading) { + setState(() { + _isLoading = false; + }); + } + } + FutureOr _onNavigation(NavigationRequest navigation) async { if (navigation.isForMainFrame && navigation.url == 'about:blank') { return NavigationDecision.navigate; diff --git a/lib/features/email/presentation/controller/email_supervisor_controller.dart b/lib/features/email/presentation/controller/email_supervisor_controller.dart index e691fa898..5c2aa6d69 100644 --- a/lib/features/email/presentation/controller/email_supervisor_controller.dart +++ b/lib/features/email/presentation/controller/email_supervisor_controller.dart @@ -21,6 +21,7 @@ class EmailSupervisorController extends BaseController { final canGetNewerEmail = true.obs; final canGetOlderEmail = true.obs; final supportedPageView = RxBool(true); + final scrollPhysicsPageView = Rxn(); Rxn get selectedEmail => mailboxDashBoardController.selectedEmail; Session? get sessionCurrent => mailboxDashBoardController.sessionCurrent; @@ -34,6 +35,12 @@ class EmailSupervisorController extends BaseController { } } + @override + void onInit() { + super.onInit(); + updateScrollPhysicPageView(); + } + @override void onClose() { pageController?.dispose(); @@ -51,6 +58,7 @@ class EmailSupervisorController extends BaseController { } void onPageChanged(int index) { + updateScrollPhysicPageView(); mailboxDashBoardController.selectedEmail.value = listEmail[index]; } @@ -61,12 +69,31 @@ class EmailSupervisorController extends BaseController { void getNewerEmail() { currentIndexPageView = currentIndexPageView - 1; - pageController?.jumpToPage(currentIndexPageView); + _jumpToPage(); } void getOlderEmail() { currentIndexPageView = currentIndexPageView + 1; - pageController?.jumpToPage(currentIndexPageView); + _jumpToPage(); + } + + void _jumpToPage() { + if (BuildUtils.isWeb) { + pageController?.jumpToPage(currentIndexPageView); + } else { + pageController?.animateToPage( + currentIndexPageView, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInToLinear); + } + } + + void updateScrollPhysicPageView({bool isScrollPageViewActivated = false}) { + if (BuildUtils.isWeb || !isScrollPageViewActivated) { + scrollPhysicsPageView.value = const NeverScrollableScrollPhysics(); + } else { + scrollPhysicsPageView.value = null; + } } @override diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index edb63f511..fef55dff1 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -1116,4 +1116,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } } + + void toggleScrollPhysicsPagerView(bool leftDirection) { + log('SingleEmailController::toggleScrollPhysicsPagerView():leftDirection: $leftDirection'); + log('SingleEmailController::toggleScrollPhysicsPagerView():canGetOlderEmail: ${emailSupervisorController.canGetOlderEmail.isTrue}'); + log('SingleEmailController::toggleScrollPhysicsPagerView():canGetNewerEmail: ${emailSupervisorController.canGetNewerEmail.isTrue}'); + + if (leftDirection) { + if (emailSupervisorController.canGetNewerEmail.isTrue) { + emailSupervisorController.getNewerEmail(); + } + } else { + if (emailSupervisorController.canGetOlderEmail.isTrue) { + emailSupervisorController.getOlderEmail(); + } + } + } } \ 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 7a20ce0ab..1bd9bb77f 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -1,4 +1,17 @@ -import 'package:core/core.dart'; +import 'package:core/domain/extensions/datetime_extension.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/utils/icon_utils.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart'; +import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart'; +import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_web.dart'; +import 'package:core/presentation/views/image/avatar_builder.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/build_utils.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -102,18 +115,19 @@ class EmailView extends GetWidget } Widget _buildMultipleEmailView(List listEmails) { - log('EmailView::_buildMultipleEmailView(): '); - return PageView.builder( - physics: BuildUtils.isWeb ? const NeverScrollableScrollPhysics() : null, - itemCount: listEmails.length, - controller: controller.emailSupervisorController.pageController, - onPageChanged: controller.emailSupervisorController.onPageChanged, - itemBuilder: (context, index) => _buildSingleEmailView(context, listEmails[index]) + return Obx( + () => PageView.builder( + physics: controller.emailSupervisorController.scrollPhysicsPageView.value, + itemCount: listEmails.length, + allowImplicitScrolling: true, + controller: controller.emailSupervisorController.pageController, + onPageChanged: controller.emailSupervisorController.onPageChanged, + itemBuilder: (context, index) => _buildSingleEmailView(context, listEmails[index]) + ), ); } Widget _buildSingleEmailView(BuildContext context, PresentationEmail email) { - log('EmailView::_buildSingleEmailView(): '); return _buildEmailBody(context, email); } @@ -211,6 +225,7 @@ class EmailView extends GetWidget return _buildEmailMessage(context, email); } else { return SingleChildScrollView( + primary: true, physics : const ClampingScrollPhysics(), child: Container( margin: EdgeInsets.zero, @@ -748,9 +763,15 @@ class EmailView extends GetWidget mailtoDelegate: (uri) => controller.openMailToLink(uri)); } else { return HtmlContentViewer( - heightContent: responsiveUtils.getSizeScreenHeight(context), - contentHtml: allEmailContents, - mailtoDelegate: (uri) async => controller.openMailToLink(uri)); + heightContent: responsiveUtils.getSizeScreenHeight(context), + contentHtml: allEmailContents, + mailtoDelegate: (uri) async => controller.openMailToLink(uri), + onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView, + onWebViewLoaded: (isScrollPageViewActivated) { + log('EmailView::_buildEmailContent(): isScrollPageViewActivated: $isScrollPageViewActivated'); + controller.emailSupervisorController.updateScrollPhysicPageView(isScrollPageViewActivated: isScrollPageViewActivated); + }, + ); } } else { return const SizedBox.shrink();