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 97260b7d7..f373a8683 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 @@ -19,6 +19,7 @@ typedef OnLoadWidthHtmlViewerAction = Function(bool isScrollPageViewActivated); typedef OnMailtoDelegateAction = Future Function(Uri? uri); typedef OnPreviewEMLDelegateAction = Future Function(Uri? uri); typedef OnDownloadAttachmentDelegateAction = Future Function(Uri? uri); +typedef OnHtmlContentClippedAction = Function(bool isClipped); class HtmlContentViewer extends StatefulWidget { @@ -34,6 +35,7 @@ class HtmlContentViewer extends StatefulWidget { final OnScrollHorizontalEndAction? onScrollHorizontalEnd; final OnPreviewEMLDelegateAction? onPreviewEMLDelegateAction; final OnDownloadAttachmentDelegateAction? onDownloadAttachmentDelegateAction; + final OnHtmlContentClippedAction? onHtmlContentClippedAction; const HtmlContentViewer({ Key? key, @@ -48,6 +50,7 @@ class HtmlContentViewer extends StatefulWidget { this.onScrollHorizontalEnd, this.onPreviewEMLDelegateAction, this.onDownloadAttachmentDelegateAction, + this.onHtmlContentClippedAction, }) : super(key: key); @override @@ -57,6 +60,7 @@ class HtmlContentViewer extends StatefulWidget { class _HtmlContentViewState extends State { static const double _minHeight = 100.0; + static const double _iOSHtmlContentMaxHeight = 22000.0; static const double _offsetHeight = 30.0; late InAppWebViewController _webViewController; @@ -184,12 +188,25 @@ class _HtmlContentViewState extends State { Size oldContentSize, Size newContentSize ) async { + if (!mounted || _loadingBarNotifier.value) return; + 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'); + if (maxContentHeight <= _actualHeight) return; + + double currentHeight = maxContentHeight + _offsetHeight; + + if (PlatformInfo.isIOS) { + final isClipped = currentHeight > _iOSHtmlContentMaxHeight; + if (isClipped) { + widget.onHtmlContentClippedAction?.call(true); + } + currentHeight = currentHeight.clamp(_minHeight, _iOSHtmlContentMaxHeight); + } + + if (_actualHeight != currentHeight) { + log('_HtmlContentViewState::_onContentSizeChanged: currentHeight = $currentHeight'); setState(() { - _actualHeight = maxContentHeight + _offsetHeight; + _actualHeight = currentHeight; }); } } @@ -205,55 +222,84 @@ class _HtmlContentViewState extends State { } 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'); + if (!mounted || _loadingBarNotifier.value) return; + + final dynamic result = await _webViewController.evaluateJavascript(source: 'document.body.scrollHeight'); + if (result is! num) return; + + final double maxContentHeight = result.toDouble(); + if (maxContentHeight <= _actualHeight) return; + + double currentHeight = maxContentHeight + _offsetHeight; + + if (PlatformInfo.isIOS) { + final bool isClipped = currentHeight > _iOSHtmlContentMaxHeight; + if (isClipped) { + widget.onHtmlContentClippedAction?.call(true); + } + currentHeight = currentHeight.clamp(_minHeight, _iOSHtmlContentMaxHeight); + } + + if (_actualHeight != currentHeight) { + log('_HtmlContentViewState::_onHandleContentSizeChangedEvent: currentHeight = $currentHeight'); setState(() { - _actualHeight = maxContentHeight + _offsetHeight; + _actualHeight = currentHeight; }); } } 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.body.scrollHeight'), + if (!mounted) return; + + final List 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.body?.scrollHeight'), ]); - log('_HtmlContentViewState::_getActualSizeHtmlViewer():listSize: $listSize'); - Set>? newGestureRecognizers; + + log('_HtmlContentViewState::_getActualSizeHtmlViewer(): listSize: $listSize'); + bool isScrollActivated = false; + Set>? newGestureRecognizers; - if (listSize[0] is num && listSize[1] is num) { - final scrollWidth = listSize[0] as num; - final offsetWidth = listSize[1] as num; + 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()) + Factory(() => HorizontalDragGestureRecognizer()), }; } } - if (listSize[2] is num) { - final scrollHeight = listSize[2] as num; - if (mounted && scrollHeight > 0) { + if (scrollHeight != null && scrollHeight > 0) { + double currentHeight = scrollHeight + _offsetHeight; + + if (PlatformInfo.isIOS) { + final bool isClipped = currentHeight > _iOSHtmlContentMaxHeight; + if (isClipped) { + widget.onHtmlContentClippedAction?.call(true); + } + currentHeight = currentHeight.clamp(_minHeight, _iOSHtmlContentMaxHeight); + } + + if (_actualHeight != currentHeight || newGestureRecognizers != null) { + log('_HtmlContentViewState::_getActualSizeHtmlViewer: currentHeight = $currentHeight'); setState(() { - _actualHeight = scrollHeight + _offsetHeight; + _actualHeight = currentHeight; if (newGestureRecognizers != null) { _gestureRecognizers = newGestureRecognizers; } }); } - } else { - if (mounted && newGestureRecognizers != null) { - setState(() { - _gestureRecognizers = newGestureRecognizers!; - }); - } + } else if (newGestureRecognizers != null) { + setState(() { + _gestureRecognizers = newGestureRecognizers!; + }); } if (!isScrollActivated) { diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index e6d174e31..c879ae227 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -190,6 +190,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final attachmentsViewState = RxMap>(); final isEmailContentHidden = RxBool(false); final currentEmailLoaded = Rxn(); + final isEmailContentClipped = RxBool(false); EmailId? _currentEmailId; Identity? _identitySelected; @@ -750,6 +751,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { blobCalendarEvent.value = null; emailUnsubscribe.value = null; _identitySelected = null; + isEmailContentClipped.value = false; if (isEmailClosing) { emailLoadedViewState.value = Right(UIState.idle); viewState.value = Right(UIState.idle); @@ -2520,4 +2522,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin { return ''; } } + + void onHtmlContentClippedAction(bool isClipped) { + log('SingleEmailController::onHtmlContentClippedAction:isClipped = $isClipped'); + isEmailContentClipped.value = isClipped; + } } \ 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 92e19747f..cd34cdca6 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -390,7 +390,7 @@ class EmailView extends GetWidget { else if (presentationEmail.id == controller.currentEmail?.id) Obx(() { if (controller.emailContents.value != null) { - final allEmailContents = controller.emailContents.value ?? ''; + String allEmailContents = controller.emailContents.value ?? ''; if (PlatformInfo.isWeb) { return Expanded( @@ -429,29 +429,68 @@ class EmailView extends GetWidget { }), ), ); - } else if (PlatformInfo.isIOS - && !controller.responsiveUtils.isScreenWithShortestSide(context)) { + } else if (PlatformInfo.isIOS) { return Obx(() { - if (controller.isEmailContentHidden.isTrue) { + if (controller.isEmailContentHidden.isTrue && !controller.responsiveUtils.isScreenWithShortestSide(context)) { return const SizedBox.shrink(); } else { - return Padding( - padding: const EdgeInsetsDirectional.symmetric( - vertical: EmailViewStyles.mobileContentVerticalMargin, - horizontal: EmailViewStyles.mobileContentHorizontalMargin - ), - child: LayoutBuilder(builder: (context, constraints) { - return HtmlContentViewer( - contentHtml: allEmailContents, - initialWidth: constraints.maxWidth, - direction: AppUtils.getCurrentDirection(context), - contentPadding: 0, - useDefaultFont: true, - onMailtoDelegateAction: controller.openMailToLink, - onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView, - onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView, - ); - }) + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.symmetric( + vertical: EmailViewStyles.mobileContentVerticalMargin, + horizontal: EmailViewStyles.mobileContentHorizontalMargin, + ), + child: LayoutBuilder(builder: (context, constraints) { + return HtmlContentViewer( + contentHtml: allEmailContents, + initialWidth: constraints.maxWidth, + direction: AppUtils.getCurrentDirection(context), + contentPadding: 0, + useDefaultFont: true, + onMailtoDelegateAction: controller.openMailToLink, + onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView, + onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView, + onHtmlContentClippedAction: controller.onHtmlContentClippedAction, + ); + }), + ), + Obx(() { + if (controller.isEmailContentClipped.isTrue) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + AppLocalizations.of(context).messageClipped, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.steelGray400, + ), + ), + ), + TMailButtonWidget.fromText( + text: AppLocalizations.of(context).viewEntireMessage.toUpperCase(), + textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: AppColor.primaryColor, + fontSize: 14, + ), + margin: const EdgeInsetsDirectional.only( + start: 8, + end: 8, + bottom: 24, + ), + backgroundColor: Colors.transparent, + onTapActionCallback: () {}, + ), + ], + ); + } else { + return const SizedBox.shrink(); + } + }), + ], ); } }); @@ -471,6 +510,7 @@ class EmailView extends GetWidget { onMailtoDelegateAction: controller.openMailToLink, onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView, onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView, + onHtmlContentClippedAction: controller.onHtmlContentClippedAction, ); }) ); diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 0ce61ef51..b1b9fd0c5 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-02-20T19:41:05.121752", + "@@last_modified": "2025-03-31T03:40:41.454034", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -2962,6 +2962,12 @@ "placeholders_order": [], "placeholders": {} }, + "errorWhileFetchingSubaddress": "Error while fetching the subaddress", + "@errorWhileFetchingSubaddress": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, "connectedToTheInternet": "Connected to the internet", "@connectedToTheInternet": { "type": "text", @@ -4238,12 +4244,6 @@ "placeholders_order": [], "placeholders": {} }, - "messageWarningDialogWhenExpiredOIDCTokenAndReconnection": "Your session expired. We need to take you back to the login page in order to refresh it. You might want to save the email you are currently editing before we do so.", - "@messageWarningDialogWhenExpiredOIDCTokenAndReconnection": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "replyToList": "Reply to list", "@replyToList": { "type": "text", @@ -4375,5 +4375,17 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "messageClipped": "[Message clipped]", + "@messageClipped": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "viewEntireMessage": "View entire message", + "@viewEntireMessage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 67f2e516d..a3c395a69 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4597,4 +4597,18 @@ class AppLocalizations { name: 'exitFullscreen', ); } + + String get messageClipped { + return Intl.message( + '[Message clipped]', + name: 'messageClipped', + ); + } + + String get viewEntireMessage { + return Intl.message( + 'View entire message', + name: 'viewEntireMessage', + ); + } }