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 097432e9e..0eda8c597 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 @@ -1,12 +1,9 @@ import 'dart:async'; -import 'dart:io'; -import 'dart:math'; +import 'dart:math' as math; -import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/core.dart'; import 'package:core/presentation/utils/html_transformer/html_event_action.dart'; -import 'package:core/presentation/utils/html_transformer/html_template.dart'; import 'package:core/presentation/utils/html_transformer/html_utils.dart'; -import 'package:core/utils/app_logger.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -14,38 +11,28 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:url_launcher/url_launcher.dart' as launcher; import 'package:url_launcher/url_launcher_string.dart'; -typedef OnScrollHorizontalEnd = Function(bool leftDirection); -typedef OnWebViewLoaded = Function(bool isScrollPageViewActivated); +typedef OnScrollHorizontalEndAction = Function(bool leftDirection); +typedef OnLoadWidthHtmlViewerAction = Function(bool isScrollPageViewActivated); +typedef OnMailtoDelegateAction = Future Function(Uri? uri); class HtmlContentViewer extends StatefulWidget { final String contentHtml; - final double? heightContent; - final OnScrollHorizontalEnd? onScrollHorizontalEnd; - final OnWebViewLoaded? onWebViewLoaded; + final double? initialWidth; final TextDirection? direction; - /// Register this callback if you want a reference to the [InAppWebViewController]. - final void Function(InAppWebViewController controller)? onCreated; - - /// Handler for mailto: links - final Future Function(Uri mailto)? mailtoDelegate; - - /// Handler for any non-media URLs that the user taps on the website. - /// - /// Returns `true` when the given `url` was handled. - final Future Function(Uri url)? urlLauncherDelegate; + final OnLoadWidthHtmlViewerAction? onLoadWidthHtmlViewer; + final OnMailtoDelegateAction? onMailtoDelegateAction; + final OnScrollHorizontalEndAction? onScrollHorizontalEnd; const HtmlContentViewer({ Key? key, required this.contentHtml, - this.heightContent, - this.onCreated, - this.onWebViewLoaded, - this.onScrollHorizontalEnd, - this.urlLauncherDelegate, - this.mailtoDelegate, + this.initialWidth, this.direction, + this.onLoadWidthHtmlViewer, + this.onMailtoDelegateAction, + this.onScrollHorizontalEnd }) : super(key: key); @override @@ -57,24 +44,39 @@ class _HtmlContentViewState extends State { static const double _minHeight = 100.0; static const double _offsetHeight = 30.0; - late double _actualHeight; - String? _htmlData; late InAppWebViewController _webViewController; - bool _isLoading = true; - bool _horizontalGestureActivated = false; + late double _actualHeight; + late Set> _gestureRecognizers; + + final _loadingBarNotifier = ValueNotifier(true); + + String? _htmlData; + + final _webViewSetting = InAppWebViewSettings( + transparentBackground: true, + verticalScrollBarEnabled: false, + ); @override void initState() { super.initState(); - if (Platform.isAndroid) { - _actualHeight = widget.heightContent ?? _minHeight; + _actualHeight = _minHeight; + if (PlatformInfo.isAndroid) { + _gestureRecognizers = { + Factory(() => LongPressGestureRecognizer()), + Factory(() => ScaleGestureRecognizer()), + }; } else { - _actualHeight = _minHeight; + _gestureRecognizers = { + Factory(() => LongPressGestureRecognizer()), + }; } - log('_HtmlContentViewState::initState():_actualHeight: $_actualHeight'); _htmlData = generateHtml( widget.contentHtml, direction: widget.direction, + javaScripts: PlatformInfo.isAndroid + ? HtmlUtils.scriptsHandleContentSizeChanged + : null ); } @@ -84,76 +86,73 @@ 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 ); } } @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - return Stack( - children: [ - if (_htmlData == null || _htmlData?.isEmpty == true) - const SizedBox.shrink() - else - SizedBox( - height: _actualHeight, - width: constraints.maxWidth, - child: InAppWebView( - key: ValueKey(_htmlData), - initialSettings: InAppWebViewSettings( - transparentBackground: true, - verticalScrollBarEnabled: false - ), - onWebViewCreated: (controller) async { - _webViewController = controller; - await controller.loadData(data: _htmlData ?? ''); - widget.onCreated?.call(controller); - }, - onLoadStop: _onLoadStop, - onContentSizeChanged: _onContentSizeChanged, - shouldOverrideUrlLoading: _shouldOverrideUrlLoading, - gestureRecognizers: { - Factory(() => LongPressGestureRecognizer()), - if (Platform.isIOS && _horizontalGestureActivated) - Factory(() => HorizontalDragGestureRecognizer()), - if (Platform.isAndroid) - Factory(() => ScaleGestureRecognizer()), - }, - onScrollChanged: (controller, x, y) => controller.scrollTo(x: 0, y: 0) - ), - ), - if (_isLoading) - const Align( - alignment: Alignment.topCenter, - child: SizedBox( - width: 30, - height: 30, - child: CupertinoActivityIndicator( - color: AppColor.colorLoading - ) - ) - ) - ], - ); - }); + return Stack(children: [ + if (_htmlData == null) + const SizedBox.shrink() + else + SizedBox( + height: _actualHeight, + width: widget.initialWidth, + child: InAppWebView( + key: ValueKey(_htmlData), + initialSettings: _webViewSetting, + onWebViewCreated: _onWebViewCreated, + onLoadStop: _onLoadStop, + onContentSizeChanged: _onContentSizeChanged, + shouldOverrideUrlLoading: _shouldOverrideUrlLoading, + gestureRecognizers: _gestureRecognizers, + onScrollChanged: (controller, x, y) => controller.scrollTo(x: 0, y: 0) + ) + ), + ValueListenableBuilder( + valueListenable: _loadingBarNotifier, + builder: (context, loading, child) { + if (loading) { + return const CupertinoLoadingWidget(isCenter: false); + } else { + return const SizedBox.shrink(); + } + } + ), + ]); } - void _onLoadStop(InAppWebViewController controller, WebUri? webUri) async { - await Future.wait([ - _setActualHeightView(), - _setActualWidthView(), - ]); + void _onWebViewCreated(InAppWebViewController controller) async { + log('_HtmlContentViewState::_onWebViewCreated:'); + _webViewController = controller; - _hideLoadingProgress(); + await controller.loadData(data: _htmlData ?? ''); controller.addJavaScriptHandler( handlerName: HtmlUtils.scrollEventJSChannelName, callback: _onHandleScrollEvent ); + + if (PlatformInfo.isAndroid) { + controller.addJavaScriptHandler( + handlerName: HtmlUtils.contentSizeChangedEventJSChannelName, + callback: _onHandleContentSizeChangedEvent + ); + } + } + + void _onLoadStop(InAppWebViewController controller, WebUri? webUri) async { + log('_HtmlContentViewState::_onLoadStop:'); + await _getActualSizeHtmlViewer(); + _loadingBarNotifier.value = false; } void _onContentSizeChanged( @@ -161,12 +160,12 @@ class _HtmlContentViewState extends State { Size oldContentSize, Size newContentSize ) async { - log('_HtmlContentViewState::_onContentSizeChanged:oldContentSize: $oldContentSize | newContentSize: $newContentSize'); - final maxContentHeight = max(oldContentSize.height, newContentSize.height); - if (!_isLoading && maxContentHeight > _actualHeight) { + final maxContentHeight = math.max(oldContentSize.height, newContentSize.height); + log('_HtmlContentViewState::_onContentSizeChanged:maxContentHeight: $maxContentHeight'); + if (maxContentHeight > _actualHeight && !_loadingBarNotifier.value && mounted) { log('_HtmlContentViewState::_onContentSizeChanged:HEIGHT_UPDATED: $maxContentHeight'); setState(() { - _actualHeight = maxContentHeight; + _actualHeight = maxContentHeight + _offsetHeight; }); } } @@ -181,64 +180,62 @@ class _HtmlContentViewState extends State { } } - Future _setActualHeightView() async { - if (Platform.isAndroid) { - await Future.delayed(const Duration(milliseconds: 1000)); - } - final scrollHeight = await _webViewController.getContentHeight(); - log('_HtmlContentViewState::_setActualHeightView():scrollHeight: $scrollHeight'); - if (mounted && - scrollHeight != null && - scrollHeight > 0 - ) { - final newHeight = scrollHeight + _offsetHeight; - log('_HtmlContentViewState::_setActualHeightView():newHeight: $newHeight'); + void _onHandleContentSizeChangedEvent(List parameters) async { + final maxContentHeight = await _webViewController.evaluateJavascript(source: 'document.body.scrollHeight'); + log('_HtmlContentViewState::_onHandleContentSizeChangedEvent:maxContentHeight: $maxContentHeight'); + if (maxContentHeight is num && maxContentHeight > _actualHeight && !_loadingBarNotifier.value && mounted) { + log('_HtmlContentViewState::_onHandleContentSizeChangedEvent:HEIGHT_UPDATED: $maxContentHeight'); setState(() { - _actualHeight = newHeight; - _isLoading = false; + _actualHeight = maxContentHeight + _offsetHeight; }); } } - Future _setActualWidthView() async { - final result = await Future.wait([ + Future _getActualSizeHtmlViewer() async { + final listSize = await Future.wait([ _webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0].scrollWidth'), - _webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0].offsetWidth') + _webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0].offsetWidth'), + _webViewController.evaluateJavascript(source: 'document.body.scrollHeight'), ]); - log('_HtmlContentViewState::_setActualWidthView():result: $result'); - if (result.length == 2) { - final scrollWidth = result[0]; - final offsetWidth = result[1]; - if (mounted && - scrollWidth != null && - offsetWidth != null && - scrollWidth is num && - offsetWidth is num - ) { - final isScrollActivated = scrollWidth.round() == offsetWidth.round(); - if (isScrollActivated) { - setState(() { - _horizontalGestureActivated = false; - }); - } else { - setState(() { - _horizontalGestureActivated = true; - }); + log('_HtmlContentViewState::_getActualSizeHtmlViewer():listSize: $listSize'); + Set>? newGestureRecognizers; + bool isScrollActivated = false; - await _webViewController.evaluateJavascript(source: HtmlUtils.runScriptsHandleScrollEvent); - } + if (listSize[0] is num && listSize[1] is num) { + final scrollWidth = listSize[0] as num; + final offsetWidth = listSize[1] as num; + isScrollActivated = scrollWidth.round() == offsetWidth.round(); - widget.onWebViewLoaded?.call(isScrollActivated); + if (!isScrollActivated && PlatformInfo.isIOS) { + newGestureRecognizers = { + Factory(() => LongPressGestureRecognizer()), + Factory(() => HorizontalDragGestureRecognizer()) + }; } } - } - void _hideLoadingProgress() { - if (mounted && _isLoading) { - setState(() { - _isLoading = false; - }); + if (listSize[2] is num) { + final scrollHeight = listSize[2] as num; + if (mounted && scrollHeight > 0) { + setState(() { + _actualHeight = scrollHeight + _offsetHeight; + if (newGestureRecognizers != null) { + _gestureRecognizers = newGestureRecognizers; + } + }); + } + } else { + if (mounted && newGestureRecognizers != null) { + setState(() { + _gestureRecognizers = newGestureRecognizers!; + }); + } } + + if (!isScrollActivated) { + await _webViewController.evaluateJavascript(source: HtmlUtils.runScriptsHandleScrollEvent); + } + widget.onLoadWidthHtmlViewer?.call(isScrollActivated); } Future _shouldOverrideUrlLoading( @@ -256,17 +253,12 @@ class _HtmlContentViewState extends State { } final requestUri = Uri.parse(url); - final mailtoHandler = widget.mailtoDelegate; + final mailtoHandler = widget.onMailtoDelegateAction; if (mailtoHandler != null && requestUri.isScheme('mailto')) { await mailtoHandler(requestUri); return NavigationActionPolicy.CANCEL; } - final urlDelegate = widget.urlLauncherDelegate; - if (urlDelegate != null) { - await urlDelegate(Uri.parse(url)); - return NavigationActionPolicy.CANCEL; - } if (await launcher.canLaunchUrl(Uri.parse(url))) { await launcher.launchUrl( Uri.parse(url), @@ -276,4 +268,10 @@ class _HtmlContentViewState extends State { return NavigationActionPolicy.CANCEL; } + + @override + void dispose() { + _loadingBarNotifier.dispose(); + super.dispose(); + } } \ No newline at end of file diff --git a/lib/features/email/presentation/controller/email_supervisor_controller.dart b/lib/features/email/presentation/controller/email_supervisor_controller.dart index 00879c312..cda09da80 100644 --- a/lib/features/email/presentation/controller/email_supervisor_controller.dart +++ b/lib/features/email/presentation/controller/email_supervisor_controller.dart @@ -1,5 +1,6 @@ import 'dart:collection'; import 'package:collection/collection.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -34,7 +35,7 @@ class EmailSupervisorController extends GetxController { @override void onInit() { super.onInit(); - updateScrollPhysicPageView(); + updateScrollPhysicPageView(false); } @override @@ -69,7 +70,7 @@ class EmailSupervisorController extends GetxController { } void onPageChanged(int index) { - updateScrollPhysicPageView(); + updateScrollPhysicPageView(false); mailboxDashBoardController.openEmailDetailedView(currentListEmail[index]); } @@ -122,7 +123,8 @@ class EmailSupervisorController extends GetxController { } } - void updateScrollPhysicPageView({bool isScrollPageViewActivated = false}) { + void updateScrollPhysicPageView(bool isScrollPageViewActivated) { + log('EmailSupervisorController::updateScrollPhysicPageView:isScrollPageViewActivated: $isScrollPageViewActivated'); if (PlatformInfo.isWeb || !isScrollPageViewActivated) { scrollPhysicsPageView.value = const NeverScrollableScrollPhysics(); } else { diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 715dc2abe..f36b186cc 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -121,105 +121,101 @@ class EmailView extends GetWidget { } }), Expanded( - child: LayoutBuilder(builder: (context, constraints) { - return Obx(() { - if (controller.emailSupervisorController.supportedPageView.isTrue) { - final currentListEmail = controller.emailSupervisorController.currentListEmail; - return PageView.builder( - physics: controller.emailSupervisorController.scrollPhysicsPageView.value, - itemCount: currentListEmail.length, - allowImplicitScrolling: true, - controller: controller.emailSupervisorController.pageController, - onPageChanged: controller.emailSupervisorController.onPageChanged, - itemBuilder: (context, index) { - final currentEmail = currentListEmail[index]; - if (PlatformInfo.isMobile) { - return SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: Obx(() => _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: controller.calendarEvent.value, - maxHeight: constraints.maxHeight - )) - ) - ); - } else { - return Obx(() { - final calendarEvent = controller.calendarEvent.value; - if (currentEmail.hasCalendarEvent && calendarEvent != null) { - return SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: calendarEvent, - emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), - ) + child: Obx(() { + if (controller.emailSupervisorController.supportedPageView.isTrue) { + final currentListEmail = controller.emailSupervisorController.currentListEmail; + return PageView.builder( + physics: controller.emailSupervisorController.scrollPhysicsPageView.value, + itemCount: currentListEmail.length, + allowImplicitScrolling: true, + controller: controller.emailSupervisorController.pageController, + onPageChanged: controller.emailSupervisorController.onPageChanged, + itemBuilder: (context, index) { + final currentEmail = currentListEmail[index]; + if (PlatformInfo.isMobile) { + return SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: Obx(() => _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: controller.calendarEvent.value, + )) + ) + ); + } else { + return Obx(() { + final calendarEvent = controller.calendarEvent.value; + if (currentEmail.hasCalendarEvent && calendarEvent != null) { + return SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: calendarEvent, + emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), ) - ); - } else { - return _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - ); - } - }); - } + ) + ); + } else { + return _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + ); + } + }); } + } + ); + } else { + if (PlatformInfo.isMobile) { + return SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: Obx(() => _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: controller.calendarEvent.value, + )) + ) ); } else { - if (PlatformInfo.isMobile) { - return SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: Obx(() => _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: controller.calendarEvent.value, - maxHeight: constraints.maxHeight - )) - ) - ); - } else { - return Obx(() { - final calendarEvent = controller.calendarEvent.value; - if (currentEmail.hasCalendarEvent && calendarEvent != null) { - return SingleChildScrollView( - physics : const ClampingScrollPhysics(), - child: Container( - width: double.infinity, - alignment: Alignment.center, - color: Colors.white, - child: _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - calendarEvent: calendarEvent, - emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), - ) + return Obx(() { + final calendarEvent = controller.calendarEvent.value; + if (currentEmail.hasCalendarEvent && calendarEvent != null) { + return SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + calendarEvent: calendarEvent, + emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(), ) - ); - } else { - return _buildEmailMessage( - context: context, - presentationEmail: currentEmail, - ); - } - }); - } + ) + ); + } else { + return _buildEmailMessage( + context: context, + presentationEmail: currentEmail, + ); + } + }); } - }); + } }), ), EmailViewBottomBarWidget( @@ -306,7 +302,6 @@ class EmailView extends GetWidget { required PresentationEmail presentationEmail, CalendarEvent? calendarEvent, List? emailAddressSender, - double? maxHeight, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -410,17 +405,16 @@ class EmailView extends GetWidget { vertical: EmailViewStyles.mobileContentVerticalMargin, horizontal: EmailViewStyles.mobileContentHorizontalMargin ), - child: HtmlContentViewer( - contentHtml: allEmailContents, - heightContent: maxHeight, - mailtoDelegate: controller.openMailToLink, - onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView, - onWebViewLoaded: (isScrollPageViewActivated) { - log('EmailView::_buildEmailContent(): isScrollPageViewActivated: $isScrollPageViewActivated'); - controller.emailSupervisorController.updateScrollPhysicPageView(isScrollPageViewActivated: isScrollPageViewActivated); - }, - direction: AppUtils.getCurrentDirection(context), - ), + child: LayoutBuilder(builder: (context, constraints) { + return HtmlContentViewer( + contentHtml: allEmailContents, + initialWidth: constraints.maxWidth, + direction: AppUtils.getCurrentDirection(context), + onMailtoDelegateAction: controller.openMailToLink, + onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView, + onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView, + ); + }) ); } } else { diff --git a/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart index 30dcf1d1e..1e5a046b4 100644 --- a/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart +++ b/lib/features/manage_account/presentation/profiles/identities/widgets/signature_builder.dart @@ -46,11 +46,13 @@ class SignatureBuilder extends StatelessWidget { direction: AppUtils.getCurrentDirection(context), ); } else { - return HtmlContentViewer( - contentHtml: signatureSelected, - heightContent: height, - direction: AppUtils.getCurrentDirection(context), - ); + return LayoutBuilder(builder: (context, constraints) { + return HtmlContentViewer( + contentHtml: signatureSelected, + initialWidth: constraints.maxWidth, + direction: AppUtils.getCurrentDirection(context), + ); + }); } } else { return SizedBox(width: width, height: height);