Skip to content

Commit d01618c

Browse files
dab246hoangdat
authored andcommitted
TF-3601 Fix email view gone blank with large html content in iOS
Signed-off-by: dab246 <[email protected]>
1 parent 20107b4 commit d01618c

File tree

5 files changed

+177
-58
lines changed

5 files changed

+177
-58
lines changed

core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart

+76-30
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ typedef OnLoadWidthHtmlViewerAction = Function(bool isScrollPageViewActivated);
1919
typedef OnMailtoDelegateAction = Future<void> Function(Uri? uri);
2020
typedef OnPreviewEMLDelegateAction = Future<void> Function(Uri? uri);
2121
typedef OnDownloadAttachmentDelegateAction = Future<void> Function(Uri? uri);
22+
typedef OnHtmlContentClippedAction = Function(bool isClipped);
2223

2324
class HtmlContentViewer extends StatefulWidget {
2425

@@ -34,6 +35,7 @@ class HtmlContentViewer extends StatefulWidget {
3435
final OnScrollHorizontalEndAction? onScrollHorizontalEnd;
3536
final OnPreviewEMLDelegateAction? onPreviewEMLDelegateAction;
3637
final OnDownloadAttachmentDelegateAction? onDownloadAttachmentDelegateAction;
38+
final OnHtmlContentClippedAction? onHtmlContentClippedAction;
3739

3840
const HtmlContentViewer({
3941
Key? key,
@@ -48,6 +50,7 @@ class HtmlContentViewer extends StatefulWidget {
4850
this.onScrollHorizontalEnd,
4951
this.onPreviewEMLDelegateAction,
5052
this.onDownloadAttachmentDelegateAction,
53+
this.onHtmlContentClippedAction,
5154
}) : super(key: key);
5255

5356
@override
@@ -57,6 +60,7 @@ class HtmlContentViewer extends StatefulWidget {
5760
class _HtmlContentViewState extends State<HtmlContentViewer> {
5861

5962
static const double _minHeight = 100.0;
63+
static const double _iOSHtmlContentMaxHeight = 22000.0;
6064
static const double _offsetHeight = 30.0;
6165

6266
late InAppWebViewController _webViewController;
@@ -184,12 +188,25 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
184188
Size oldContentSize,
185189
Size newContentSize
186190
) async {
191+
if (!mounted || _loadingBarNotifier.value) return;
192+
187193
final maxContentHeight = math.max(oldContentSize.height, newContentSize.height);
188-
log('_HtmlContentViewState::_onContentSizeChanged:maxContentHeight: $maxContentHeight');
189-
if (maxContentHeight > _actualHeight && !_loadingBarNotifier.value && mounted) {
190-
log('_HtmlContentViewState::_onContentSizeChanged:HEIGHT_UPDATED: $maxContentHeight');
194+
if (maxContentHeight <= _actualHeight) return;
195+
196+
double currentHeight = maxContentHeight + _offsetHeight;
197+
198+
if (PlatformInfo.isIOS) {
199+
final isClipped = currentHeight > _iOSHtmlContentMaxHeight;
200+
if (isClipped) {
201+
widget.onHtmlContentClippedAction?.call(true);
202+
}
203+
currentHeight = currentHeight.clamp(_minHeight, _iOSHtmlContentMaxHeight);
204+
}
205+
206+
if (_actualHeight != currentHeight) {
207+
log('_HtmlContentViewState::_onContentSizeChanged: currentHeight = $currentHeight');
191208
setState(() {
192-
_actualHeight = maxContentHeight + _offsetHeight;
209+
_actualHeight = currentHeight;
193210
});
194211
}
195212
}
@@ -205,55 +222,84 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
205222
}
206223

207224
void _onHandleContentSizeChangedEvent(List<dynamic> parameters) async {
208-
final maxContentHeight = await _webViewController.evaluateJavascript(source: 'document.body.scrollHeight');
209-
log('_HtmlContentViewState::_onHandleContentSizeChangedEvent:maxContentHeight: $maxContentHeight');
210-
if (maxContentHeight is num && maxContentHeight > _actualHeight && !_loadingBarNotifier.value && mounted) {
211-
log('_HtmlContentViewState::_onHandleContentSizeChangedEvent:HEIGHT_UPDATED: $maxContentHeight');
225+
if (!mounted || _loadingBarNotifier.value) return;
226+
227+
final dynamic result = await _webViewController.evaluateJavascript(source: 'document.body.scrollHeight');
228+
if (result is! num) return;
229+
230+
final double maxContentHeight = result.toDouble();
231+
if (maxContentHeight <= _actualHeight) return;
232+
233+
double currentHeight = maxContentHeight + _offsetHeight;
234+
235+
if (PlatformInfo.isIOS) {
236+
final bool isClipped = currentHeight > _iOSHtmlContentMaxHeight;
237+
if (isClipped) {
238+
widget.onHtmlContentClippedAction?.call(true);
239+
}
240+
currentHeight = currentHeight.clamp(_minHeight, _iOSHtmlContentMaxHeight);
241+
}
242+
243+
if (_actualHeight != currentHeight) {
244+
log('_HtmlContentViewState::_onHandleContentSizeChangedEvent: currentHeight = $currentHeight');
212245
setState(() {
213-
_actualHeight = maxContentHeight + _offsetHeight;
246+
_actualHeight = currentHeight;
214247
});
215248
}
216249
}
217250

218251
Future<void> _getActualSizeHtmlViewer() async {
219-
final listSize = await Future.wait([
220-
_webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0].scrollWidth'),
221-
_webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0].offsetWidth'),
222-
_webViewController.evaluateJavascript(source: 'document.body.scrollHeight'),
252+
if (!mounted) return;
253+
254+
final List<dynamic> listSize = await Future.wait([
255+
_webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0]?.scrollWidth'),
256+
_webViewController.evaluateJavascript(source: 'document.getElementsByClassName("tmail-content")[0]?.offsetWidth'),
257+
_webViewController.evaluateJavascript(source: 'document.body?.scrollHeight'),
223258
]);
224-
log('_HtmlContentViewState::_getActualSizeHtmlViewer():listSize: $listSize');
225-
Set<Factory<OneSequenceGestureRecognizer>>? newGestureRecognizers;
259+
260+
log('_HtmlContentViewState::_getActualSizeHtmlViewer(): listSize: $listSize');
261+
226262
bool isScrollActivated = false;
263+
Set<Factory<OneSequenceGestureRecognizer>>? newGestureRecognizers;
227264

228-
if (listSize[0] is num && listSize[1] is num) {
229-
final scrollWidth = listSize[0] as num;
230-
final offsetWidth = listSize[1] as num;
231-
isScrollActivated = scrollWidth.round() == offsetWidth.round();
265+
final double? scrollWidth = listSize[0] is num ? (listSize[0] as num).toDouble() : null;
266+
final double? offsetWidth = listSize[1] is num ? (listSize[1] as num).toDouble() : null;
267+
final double? scrollHeight = listSize[2] is num ? (listSize[2] as num).toDouble() : null;
232268

269+
if (scrollWidth != null && offsetWidth != null) {
270+
isScrollActivated = scrollWidth.round() == offsetWidth.round();
233271
if (!isScrollActivated && PlatformInfo.isIOS) {
234272
newGestureRecognizers = {
235273
Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer(duration: _longPressGestureDurationIOS)),
236-
Factory<HorizontalDragGestureRecognizer>(() => HorizontalDragGestureRecognizer())
274+
Factory<HorizontalDragGestureRecognizer>(() => HorizontalDragGestureRecognizer()),
237275
};
238276
}
239277
}
240278

241-
if (listSize[2] is num) {
242-
final scrollHeight = listSize[2] as num;
243-
if (mounted && scrollHeight > 0) {
279+
if (scrollHeight != null && scrollHeight > 0) {
280+
double currentHeight = scrollHeight + _offsetHeight;
281+
282+
if (PlatformInfo.isIOS) {
283+
final bool isClipped = currentHeight > _iOSHtmlContentMaxHeight;
284+
if (isClipped) {
285+
widget.onHtmlContentClippedAction?.call(true);
286+
}
287+
currentHeight = currentHeight.clamp(_minHeight, _iOSHtmlContentMaxHeight);
288+
}
289+
290+
if (_actualHeight != currentHeight || newGestureRecognizers != null) {
291+
log('_HtmlContentViewState::_getActualSizeHtmlViewer: currentHeight = $currentHeight');
244292
setState(() {
245-
_actualHeight = scrollHeight + _offsetHeight;
293+
_actualHeight = currentHeight;
246294
if (newGestureRecognizers != null) {
247295
_gestureRecognizers = newGestureRecognizers;
248296
}
249297
});
250298
}
251-
} else {
252-
if (mounted && newGestureRecognizers != null) {
253-
setState(() {
254-
_gestureRecognizers = newGestureRecognizers!;
255-
});
256-
}
299+
} else if (newGestureRecognizers != null) {
300+
setState(() {
301+
_gestureRecognizers = newGestureRecognizers!;
302+
});
257303
}
258304

259305
if (!isScrollActivated) {

lib/features/email/presentation/controller/single_email_controller.dart

+7
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
190190
final attachmentsViewState = RxMap<Id, Either<Failure, Success>>();
191191
final isEmailContentHidden = RxBool(false);
192192
final currentEmailLoaded = Rxn<EmailLoaded>();
193+
final isEmailContentClipped = RxBool(false);
193194

194195
EmailId? _currentEmailId;
195196
Identity? _identitySelected;
@@ -750,6 +751,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
750751
blobCalendarEvent.value = null;
751752
emailUnsubscribe.value = null;
752753
_identitySelected = null;
754+
isEmailContentClipped.value = false;
753755
if (isEmailClosing) {
754756
emailLoadedViewState.value = Right(UIState.idle);
755757
viewState.value = Right(UIState.idle);
@@ -2520,4 +2522,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
25202522
return '';
25212523
}
25222524
}
2525+
2526+
void onHtmlContentClippedAction(bool isClipped) {
2527+
log('SingleEmailController::onHtmlContentClippedAction:isClipped = $isClipped');
2528+
isEmailContentClipped.value = isClipped;
2529+
}
25232530
}

lib/features/email/presentation/email_view.dart

+61-21
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ class EmailView extends GetWidget<SingleEmailController> {
390390
else if (presentationEmail.id == controller.currentEmail?.id)
391391
Obx(() {
392392
if (controller.emailContents.value != null) {
393-
final allEmailContents = controller.emailContents.value ?? '';
393+
String allEmailContents = controller.emailContents.value ?? '';
394394

395395
if (PlatformInfo.isWeb) {
396396
return Expanded(
@@ -429,29 +429,68 @@ class EmailView extends GetWidget<SingleEmailController> {
429429
}),
430430
),
431431
);
432-
} else if (PlatformInfo.isIOS
433-
&& !controller.responsiveUtils.isScreenWithShortestSide(context)) {
432+
} else if (PlatformInfo.isIOS) {
434433
return Obx(() {
435-
if (controller.isEmailContentHidden.isTrue) {
434+
if (controller.isEmailContentHidden.isTrue && !controller.responsiveUtils.isScreenWithShortestSide(context)) {
436435
return const SizedBox.shrink();
437436
} else {
438-
return Padding(
439-
padding: const EdgeInsetsDirectional.symmetric(
440-
vertical: EmailViewStyles.mobileContentVerticalMargin,
441-
horizontal: EmailViewStyles.mobileContentHorizontalMargin
442-
),
443-
child: LayoutBuilder(builder: (context, constraints) {
444-
return HtmlContentViewer(
445-
contentHtml: allEmailContents,
446-
initialWidth: constraints.maxWidth,
447-
direction: AppUtils.getCurrentDirection(context),
448-
contentPadding: 0,
449-
useDefaultFont: true,
450-
onMailtoDelegateAction: controller.openMailToLink,
451-
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
452-
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
453-
);
454-
})
437+
return Column(
438+
crossAxisAlignment: CrossAxisAlignment.start,
439+
children: [
440+
Padding(
441+
padding: const EdgeInsetsDirectional.symmetric(
442+
vertical: EmailViewStyles.mobileContentVerticalMargin,
443+
horizontal: EmailViewStyles.mobileContentHorizontalMargin,
444+
),
445+
child: LayoutBuilder(builder: (context, constraints) {
446+
return HtmlContentViewer(
447+
contentHtml: allEmailContents,
448+
initialWidth: constraints.maxWidth,
449+
direction: AppUtils.getCurrentDirection(context),
450+
contentPadding: 0,
451+
useDefaultFont: true,
452+
onMailtoDelegateAction: controller.openMailToLink,
453+
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
454+
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
455+
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
456+
);
457+
}),
458+
),
459+
Obx(() {
460+
if (controller.isEmailContentClipped.isTrue) {
461+
return Column(
462+
crossAxisAlignment: CrossAxisAlignment.start,
463+
children: [
464+
Padding(
465+
padding: const EdgeInsets.symmetric(horizontal: 16),
466+
child: Text(
467+
AppLocalizations.of(context).messageClipped,
468+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
469+
color: AppColor.steelGray400,
470+
),
471+
),
472+
),
473+
TMailButtonWidget.fromText(
474+
text: AppLocalizations.of(context).viewEntireMessage.toUpperCase(),
475+
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(
476+
color: AppColor.primaryColor,
477+
fontSize: 14,
478+
),
479+
margin: const EdgeInsetsDirectional.only(
480+
start: 8,
481+
end: 8,
482+
bottom: 24,
483+
),
484+
backgroundColor: Colors.transparent,
485+
onTapActionCallback: () {},
486+
),
487+
],
488+
);
489+
} else {
490+
return const SizedBox.shrink();
491+
}
492+
}),
493+
],
455494
);
456495
}
457496
});
@@ -471,6 +510,7 @@ class EmailView extends GetWidget<SingleEmailController> {
471510
onMailtoDelegateAction: controller.openMailToLink,
472511
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
473512
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
513+
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
474514
);
475515
})
476516
);

lib/l10n/intl_messages.arb

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"@@last_modified": "2025-02-20T19:41:05.121752",
2+
"@@last_modified": "2025-03-31T03:40:41.454034",
33
"initializing_data": "Initializing data...",
44
"@initializing_data": {
55
"type": "text",
@@ -2962,6 +2962,12 @@
29622962
"placeholders_order": [],
29632963
"placeholders": {}
29642964
},
2965+
"errorWhileFetchingSubaddress": "Error while fetching the subaddress",
2966+
"@errorWhileFetchingSubaddress": {
2967+
"type": "text",
2968+
"placeholders_order": [],
2969+
"placeholders": {}
2970+
},
29652971
"connectedToTheInternet": "Connected to the internet",
29662972
"@connectedToTheInternet": {
29672973
"type": "text",
@@ -4238,12 +4244,6 @@
42384244
"placeholders_order": [],
42394245
"placeholders": {}
42404246
},
4241-
"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.",
4242-
"@messageWarningDialogWhenExpiredOIDCTokenAndReconnection": {
4243-
"type": "text",
4244-
"placeholders_order": [],
4245-
"placeholders": {}
4246-
},
42474247
"replyToList": "Reply to list",
42484248
"@replyToList": {
42494249
"type": "text",
@@ -4375,5 +4375,17 @@
43754375
"type": "text",
43764376
"placeholders_order": [],
43774377
"placeholders": {}
4378+
},
4379+
"messageClipped": "[Message clipped]",
4380+
"@messageClipped": {
4381+
"type": "text",
4382+
"placeholders_order": [],
4383+
"placeholders": {}
4384+
},
4385+
"viewEntireMessage": "View entire message",
4386+
"@viewEntireMessage": {
4387+
"type": "text",
4388+
"placeholders_order": [],
4389+
"placeholders": {}
43784390
}
43794391
}

lib/main/localizations/app_localizations.dart

+14
Original file line numberDiff line numberDiff line change
@@ -4597,4 +4597,18 @@ class AppLocalizations {
45974597
name: 'exitFullscreen',
45984598
);
45994599
}
4600+
4601+
String get messageClipped {
4602+
return Intl.message(
4603+
'[Message clipped]',
4604+
name: 'messageClipped',
4605+
);
4606+
}
4607+
4608+
String get viewEntireMessage {
4609+
return Intl.message(
4610+
'View entire message',
4611+
name: 'viewEntireMessage',
4612+
);
4613+
}
46004614
}

0 commit comments

Comments
 (0)