From 5e3f4458c5e0456b1ee0ab98fb7dd9da1c794770 Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 30 Jul 2025 19:22:03 +0700 Subject: [PATCH] TF-2948 Apply new identity view for mobile Signed-off-by: dab246 --- .../html_content_viewer_widget.dart | 230 +++++++++++------- .../identity_creator_form_bottom_view.dart | 7 +- 2 files changed, 143 insertions(+), 94 deletions(-) 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 1ada91dec..a78e8b89c 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 @@ -5,11 +5,12 @@ import 'package:core/presentation/constants/constants_ui.dart'; import 'package:core/presentation/views/loading/cupertino_loading_widget.dart'; 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:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:url_launcher/url_launcher.dart' as launcher; import 'package:url_launcher/url_launcher_string.dart'; @@ -30,10 +31,12 @@ class HtmlContentViewer extends StatefulWidget { final double? contentPadding; final bool useDefaultFont; final double? maxHtmlContentHeight; - final double minHtmlContentHeight; + final double htmlContentMinHeight; final double offsetHtmlContentHeight; final bool keepAlive; final bool enableQuoteToggle; + final bool disableScrolling; + final double? maxViewHeight; final OnLoadWidthHtmlViewerAction? onLoadWidthHtmlViewer; final OnMailtoDelegateAction? onMailtoDelegateAction; @@ -47,13 +50,15 @@ class HtmlContentViewer extends StatefulWidget { required this.contentHtml, this.initialWidth, this.direction, - this.minHtmlContentHeight = ConstantsUI.htmlContentMinHeight, + this.htmlContentMinHeight = ConstantsUI.htmlContentMinHeight, this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight, this.keepAlive = false, this.enableQuoteToggle = false, this.keepWidthWhileLoading = false, this.contentPadding, this.useDefaultFont = false, + this.disableScrolling = false, + this.maxViewHeight, this.maxHtmlContentHeight, this.onLoadWidthHtmlViewer, this.onMailtoDelegateAction, @@ -72,45 +77,39 @@ class HtmlContentViewState extends State with AutomaticKeepAl late InAppWebViewController _webViewController; late double _actualHeight; late Set> _gestureRecognizers; - late StringBuffer _customScriptsBuilder; + late InAppWebViewSettings _webViewSetting; final _loadingBarNotifier = ValueNotifier(true); String? _htmlData; - final _webViewSetting = InAppWebViewSettings( - transparentBackground: true, - verticalScrollBarEnabled: false, - supportZoom: false, - ); - @visibleForTesting InAppWebViewController get webViewController => _webViewController; @override void initState() { super.initState(); - if (PlatformInfo.isAndroid) { - _gestureRecognizers = { - Factory(() => LongPressGestureRecognizer()), + _webViewSetting = InAppWebViewSettings( + transparentBackground: true, + verticalScrollBarEnabled: false, + supportZoom: false, + disableHorizontalScroll: widget.disableScrolling, + disableVerticalScroll: widget.disableScrolling, + horizontalScrollBarEnabled: !widget.disableScrolling, + ); + + _gestureRecognizers = { + Factory( + () => LongPressGestureRecognizer( + duration: PlatformInfo.isAndroid + ? null + : _longPressGestureDurationIOS, + ), + ), + if (PlatformInfo.isAndroid && !widget.disableScrolling) Factory(() => ScaleGestureRecognizer()), - }; - } else { - _gestureRecognizers = { - Factory(() => LongPressGestureRecognizer(duration: _longPressGestureDurationIOS)), - }; - } - _customScriptsBuilder = StringBuffer(); - _customScriptsBuilder.write(HtmlInteraction.scriptsHandleLazyLoadingBackgroundImage); - if (widget.enableQuoteToggle) { - _customScriptsBuilder.write(HtmlUtils.quoteToggleScript); - } - if (widget.initialWidth != null) { - _customScriptsBuilder.write(HtmlInteraction.generateNormalizeImageScript(widget.initialWidth!)); - } - if (PlatformInfo.isAndroid) { - _customScriptsBuilder.write(HtmlInteraction.scriptsHandleContentSizeChanged); - } + }; + _initialData(); } @@ -125,16 +124,33 @@ class HtmlContentViewState extends State with AutomaticKeepAl } void _initialData() { - _actualHeight = widget.minHtmlContentHeight; - _htmlData = HtmlUtils.generateHtmlDocument( - content: widget.enableQuoteToggle + _actualHeight = widget.htmlContentMinHeight; + + final processedContent = widget.enableQuoteToggle ? HtmlUtils.addQuoteToggle(widget.contentHtml) - : widget.contentHtml, + : widget.contentHtml; + + final combinedCss = [ + if (widget.enableQuoteToggle) HtmlUtils.quoteToggleStyle, + if (widget.disableScrolling) HtmlTemplate.disableScrollingStyleCSS, + ].join(); + + final combinedScripts = [ + HtmlInteraction.scriptsHandleLazyLoadingBackgroundImage, + if (widget.enableQuoteToggle) HtmlUtils.quoteToggleScript, + if (widget.initialWidth != null) + HtmlInteraction.generateNormalizeImageScript(widget.initialWidth!), + if (PlatformInfo.isAndroid) + HtmlInteraction.scriptsHandleContentSizeChanged, + ].join(); + + _htmlData = HtmlUtils.generateHtmlDocument( + content: processedContent, direction: widget.direction, - javaScripts: _customScriptsBuilder.toString(), + javaScripts: combinedScripts, + styleCSS: combinedCss, contentPadding: widget.contentPadding, useDefaultFont: widget.useDefaultFont, - styleCSS: widget.enableQuoteToggle ? HtmlUtils.quoteToggleStyle : null ); } @@ -142,23 +158,20 @@ class HtmlContentViewState extends State with AutomaticKeepAl Widget build(BuildContext context) { super.build(context); final child = 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) + if (_htmlData?.trim().isNotEmpty == true) + if (widget.maxViewHeight != null) + Container( + height: _actualHeight, + width: widget.initialWidth, + constraints: BoxConstraints(maxHeight: widget.maxViewHeight!), + child: _buildWebView(), ) - ), + else + SizedBox( + height: _actualHeight, + width: widget.initialWidth, + child: _buildWebView(), + ), ValueListenableBuilder( valueListenable: _loadingBarNotifier, builder: (context, loading, child) { @@ -171,23 +184,42 @@ class HtmlContentViewState extends State with AutomaticKeepAl ), ]); - if (!widget.keepWidthWhileLoading) return child; - return SizedBox( - width: widget.initialWidth, - child: child, + if (!widget.keepWidthWhileLoading) { + return child; + } else { + return SizedBox(width: widget.initialWidth, child: child); + } + } + + Widget _buildWebView() { + return InAppWebView( + key: ValueKey(_htmlData), + initialSettings: _webViewSetting, + onWebViewCreated: _onWebViewCreated, + onLoadStop: _onLoadStop, + onContentSizeChanged: _onContentSizeChanged, + shouldOverrideUrlLoading: _shouldOverrideUrlLoading, + gestureRecognizers: _gestureRecognizers, + onScrollChanged: widget.disableScrolling ? null : _onScrollChanged, ); } + void _onScrollChanged(InAppWebViewController controller, int x, int y) { + controller.scrollTo(x: 0, y: 0); + } + void _onWebViewCreated(InAppWebViewController controller) async { log('_HtmlContentViewState::_onWebViewCreated:'); _webViewController = controller; await controller.loadData(data: _htmlData ?? ''); - controller.addJavaScriptHandler( - handlerName: HtmlInteraction.scrollEventJSChannelName, - callback: _onHandleScrollEvent - ); + if (!widget.disableScrolling) { + controller.addJavaScriptHandler( + handlerName: HtmlInteraction.scrollEventJSChannelName, + callback: _onHandleScrollEvent, + ); + } if (PlatformInfo.isAndroid) { controller.addJavaScriptHandler( @@ -230,12 +262,8 @@ class HtmlContentViewState extends State with AutomaticKeepAl double currentHeight = maxContentHeight + widget.offsetHtmlContentHeight; if (PlatformInfo.isIOS && widget.maxHtmlContentHeight != null) { - final bool isClipped = currentHeight > widget.maxHtmlContentHeight!; - if (isClipped) { - widget.onHtmlContentClippedAction?.call(true); - } - currentHeight = currentHeight.clamp( - widget.minHtmlContentHeight, + currentHeight = _reStandardizeHeight( + currentHeight, widget.maxHtmlContentHeight!, ); } @@ -259,58 +287,74 @@ class HtmlContentViewState extends State with AutomaticKeepAl log('_HtmlContentViewState::_getActualSizeHtmlViewer(): listSize: $listSize'); - bool isScrollActivated = false; - Set>? newGestureRecognizers; - final double? scrollWidth = listSize[0] is num ? (listSize[0] as num).toDouble() : null; final double? offsetWidth = listSize[1] is num ? (listSize[1] as num).toDouble() : null; final double? scrollHeight = listSize[2] is num ? (listSize[2] as num).toDouble() : null; - if (scrollWidth != null && offsetWidth != null) { - isScrollActivated = scrollWidth.round() == offsetWidth.round(); - if (!isScrollActivated && PlatformInfo.isIOS) { - newGestureRecognizers = { - Factory(() => LongPressGestureRecognizer(duration: _longPressGestureDurationIOS)), - Factory(() => HorizontalDragGestureRecognizer()), - }; - } - } + bool isContentFullyVisible = scrollWidth != null && + offsetWidth != null && + scrollWidth.round() == offsetWidth.round(); + + final isIOSScrollingEnabled = !isContentFullyVisible && PlatformInfo.isIOS; + log('_HtmlContentViewState::_getActualSizeHtmlViewer: isIOSScrollingEnabled = $isIOSScrollingEnabled | isContentFullyVisible = $isContentFullyVisible'); if (scrollHeight != null && scrollHeight > 0) { double currentHeight = scrollHeight + widget.offsetHtmlContentHeight; if (PlatformInfo.isIOS && widget.maxHtmlContentHeight != null) { - final bool isClipped = currentHeight > widget.maxHtmlContentHeight!; - if (isClipped) { - widget.onHtmlContentClippedAction?.call(true); - } - currentHeight = currentHeight.clamp( - widget.minHtmlContentHeight, + currentHeight = _reStandardizeHeight( + currentHeight, widget.maxHtmlContentHeight!, ); } + log('_HtmlContentViewState::_getActualSizeHtmlViewer: currentHeight = $currentHeight'); - if (_actualHeight != currentHeight || newGestureRecognizers != null) { - log('_HtmlContentViewState::_getActualSizeHtmlViewer: currentHeight = $currentHeight'); + if (_actualHeight != currentHeight || isIOSScrollingEnabled) { setState(() { _actualHeight = currentHeight; - if (newGestureRecognizers != null) { - _gestureRecognizers = newGestureRecognizers; + if (isIOSScrollingEnabled && !widget.disableScrolling) { + _gestureRecognizers = _iOSGestureRecognizersWithScrolling; } }); } - } else if (newGestureRecognizers != null) { + } else if (isIOSScrollingEnabled && !widget.disableScrolling) { setState(() { - _gestureRecognizers = newGestureRecognizers!; + _gestureRecognizers = _iOSGestureRecognizersWithScrolling; }); } - if (!isScrollActivated) { - await _webViewController.evaluateJavascript(source: HtmlInteraction.runScriptsHandleScrollEvent); + if (!isContentFullyVisible && !widget.disableScrolling) { + await _webViewController.evaluateJavascript( + source: HtmlInteraction.runScriptsHandleScrollEvent, + ); + + widget.onLoadWidthHtmlViewer?.call(isContentFullyVisible); } - widget.onLoadWidthHtmlViewer?.call(isScrollActivated); } + double _reStandardizeHeight(double currentHeight, double maxHtmlContentHeight) { + final bool isClipped = currentHeight > maxHtmlContentHeight; + if (isClipped) { + widget.onHtmlContentClippedAction?.call(true); + } + + return currentHeight.clamp( + widget.htmlContentMinHeight, + maxHtmlContentHeight, + ); + } + + Set> get _iOSGestureRecognizersWithScrolling => { + Factory( + () => LongPressGestureRecognizer( + duration: _longPressGestureDurationIOS, + ), + ), + Factory( + () => HorizontalDragGestureRecognizer(), + ), + }; + Future _shouldOverrideUrlLoading( InAppWebViewController controller, NavigationAction navigationAction diff --git a/lib/features/identity_creator/presentation/widgets/identity_creator_form_bottom_view.dart b/lib/features/identity_creator/presentation/widgets/identity_creator_form_bottom_view.dart index 8a2954c5a..6583b9f1e 100644 --- a/lib/features/identity_creator/presentation/widgets/identity_creator_form_bottom_view.dart +++ b/lib/features/identity_creator/presentation/widgets/identity_creator_form_bottom_view.dart @@ -1,6 +1,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/views/checkbox/custom_icon_labeled_checkbox.dart'; import 'package:core/presentation/views/dialog/confirm_dialog_button.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart'; @@ -82,7 +83,11 @@ class IdentityCreatorFormBottomView extends StatelessWidget { if (isMobile) { return Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 17), + padding: EdgeInsetsDirectional.only( + start: 17, + end: 17, + bottom: PlatformInfo.isMobile ? 24 : 0, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,