TF-4084 Fix cannot read or scroll emails when opening them in Twake Mail on Android chrome mobile browser
This commit is contained in:
@@ -8,6 +8,7 @@ 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:universal_html/html.dart' as html;
|
||||
|
||||
@@ -87,6 +88,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
static const String iframeOnLoadMessage = 'iframeHasBeenLoaded';
|
||||
static const String onClickHyperLinkName = 'onClickHyperLink';
|
||||
static const String onScrollChangedEvent = 'onScrollChanged';
|
||||
static const String onScrollEndEvent = 'onScrollEnd';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -106,9 +108,12 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
if (viewId != _createdViewId) return;
|
||||
|
||||
final type = data['type'];
|
||||
if (_isScrollChangedEventTriggered(type)) {
|
||||
if (_isScrollingIsAvailable && _isScrollChangedEventTriggered(type)) {
|
||||
_handleIframeOnScrollChangedListener(data, widget.scrollController!);
|
||||
return;
|
||||
} else if (_isScrollingIsAvailable && _isScrollEndEventTriggered(type)) {
|
||||
_handleIframeOnScrollEndListener(data, widget.scrollController!);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data['message'] == iframeOnLoadMessage) {
|
||||
@@ -130,10 +135,17 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
}
|
||||
}
|
||||
|
||||
bool _isScrollChangedEventTriggered(String? type) {
|
||||
bool get _isScrollingIsAvailable {
|
||||
return widget.scrollController != null &&
|
||||
widget.scrollController?.hasClients == true &&
|
||||
type?.contains('toDart: $onScrollChangedEvent') == true;
|
||||
widget.scrollController?.hasClients == true;
|
||||
}
|
||||
|
||||
bool _isScrollChangedEventTriggered(String? type) {
|
||||
return type?.contains('toDart: $onScrollChangedEvent') == true;
|
||||
}
|
||||
|
||||
bool _isScrollEndEventTriggered(String? type) {
|
||||
return type?.contains('toDart: $onScrollEndEvent') == true;
|
||||
}
|
||||
|
||||
void _handleIframeOnScrollChangedListener(
|
||||
@@ -141,17 +153,51 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
ScrollController controller,
|
||||
) {
|
||||
final deltaY = data['deltaY'] ?? 0.0;
|
||||
final newOffset = controller.offset + deltaY;
|
||||
log('_HtmlContentViewerOnWebState::_handleIframeOnScrollChangedListener:deltaY = $deltaY | newOffset = $newOffset');
|
||||
if (newOffset < controller.position.minScrollExtent) {
|
||||
controller.jumpTo(controller.position.minScrollExtent);
|
||||
} else if (newOffset > controller.position.maxScrollExtent) {
|
||||
controller.jumpTo(controller.position.maxScrollExtent);
|
||||
final target = controller.offset + deltaY;
|
||||
|
||||
if (PlatformInfo.isWebTouchDevice) {
|
||||
final newOffset = target.clamp(
|
||||
controller.position.minScrollExtent,
|
||||
controller.position.maxScrollExtent,
|
||||
);
|
||||
|
||||
controller.animateTo(
|
||||
newOffset,
|
||||
duration: const Duration(milliseconds: 50),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
} else {
|
||||
controller.jumpTo(newOffset);
|
||||
if (target < controller.position.minScrollExtent) {
|
||||
controller.jumpTo(controller.position.minScrollExtent);
|
||||
} else if (target > controller.position.maxScrollExtent) {
|
||||
controller.jumpTo(controller.position.maxScrollExtent);
|
||||
} else {
|
||||
controller.jumpTo(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleIframeOnScrollEndListener(
|
||||
dynamic data,
|
||||
ScrollController controller,
|
||||
) {
|
||||
final velocity = data['velocity'] ?? 0.0;
|
||||
|
||||
final distance = velocity * 800;
|
||||
final target = controller.offset + distance;
|
||||
|
||||
final newOffset = target.clamp(
|
||||
controller.position.minScrollExtent,
|
||||
controller.position.maxScrollExtent,
|
||||
);
|
||||
|
||||
controller.animateTo(
|
||||
newOffset,
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.decelerate,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isHtmlContentHeightEventTriggered(String? type) =>
|
||||
type?.contains('toDart: htmlHeight') == true;
|
||||
|
||||
@@ -319,17 +365,6 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
|
||||
${!widget.autoAdjustHeight ? 'resizeObserver.observe(document.body);' : ''}
|
||||
}
|
||||
|
||||
${widget.scrollController != null ? '''
|
||||
window.addEventListener('wheel', function (event) {
|
||||
const deltaY = event.deltaY;
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
"view": "$_createdViewId",
|
||||
"type": "toDart: $onScrollChangedEvent",
|
||||
"deltaY": deltaY
|
||||
}), "*");
|
||||
});
|
||||
''' : ''}
|
||||
</script>
|
||||
''';
|
||||
|
||||
@@ -349,6 +384,17 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
HtmlInteraction.scriptsHandleLazyLoadingBackgroundImage,
|
||||
HtmlInteraction.generateNormalizeImageScript(widget.widthContent),
|
||||
if (widget.enableQuoteToggle) HtmlUtils.quoteToggleScript,
|
||||
if (widget.scrollController != null)
|
||||
PlatformInfo.isWebTouchDevice
|
||||
? HtmlInteraction.scriptsTouchEventListener(
|
||||
viewId: _createdViewId,
|
||||
onScrollChangedEvent: onScrollChangedEvent,
|
||||
onScrollEndEvent: onScrollEndEvent,
|
||||
)
|
||||
: HtmlInteraction.scriptsWheelEventListener(
|
||||
viewId: _createdViewId,
|
||||
onScrollChangedEvent: onScrollChangedEvent,
|
||||
),
|
||||
].join();
|
||||
|
||||
final htmlTemplate = HtmlUtils.generateHtmlDocument(
|
||||
|
||||
@@ -298,4 +298,84 @@ class HtmlInteraction {
|
||||
});
|
||||
</script>
|
||||
''';
|
||||
|
||||
static String scriptsWheelEventListener({
|
||||
required String viewId,
|
||||
required String onScrollChangedEvent,
|
||||
}) => '''
|
||||
<script type="text/javascript">
|
||||
function onWheel(e) {
|
||||
const deltaY = event.deltaY;
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
"view": "$viewId",
|
||||
"type": "toDart: $onScrollChangedEvent",
|
||||
"deltaY": deltaY
|
||||
}), "*");
|
||||
}
|
||||
|
||||
window.addEventListener('wheel', onWheel, { passive: true });
|
||||
|
||||
window.addEventListener('pagehide', (event) => {
|
||||
window.removeEventListener('wheel', onWheel);
|
||||
});
|
||||
</script>
|
||||
''';
|
||||
|
||||
static String scriptsTouchEventListener({
|
||||
required String viewId,
|
||||
required String onScrollChangedEvent,
|
||||
required String onScrollEndEvent,
|
||||
}) => '''
|
||||
<script type="text/javascript">
|
||||
let lastY = 0;
|
||||
let lastTime = 0;
|
||||
let velocity = 0;
|
||||
|
||||
function onTouchStart(e) {
|
||||
lastY = e.touches[0].clientY;
|
||||
lastTime = performance.now();
|
||||
velocity = 0;
|
||||
}
|
||||
|
||||
function onTouchMove(e) {
|
||||
const now = performance.now();
|
||||
const y = e.touches[0].clientY;
|
||||
const dy = lastY - y;
|
||||
const dt = now - lastTime;
|
||||
|
||||
if (dt > 0) {
|
||||
velocity = dy / dt; // px per ms
|
||||
velocity = Math.max(Math.min(velocity, 2), -2); // clamp velocity
|
||||
}
|
||||
|
||||
lastY = y;
|
||||
lastTime = now;
|
||||
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
view: "$viewId",
|
||||
type: "toDart: $onScrollChangedEvent",
|
||||
deltaY: dy,
|
||||
}), '*');
|
||||
}
|
||||
|
||||
function onTouchEnd(e) {
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
view: "$viewId",
|
||||
type: "toDart: $onScrollEndEvent",
|
||||
velocity: velocity,
|
||||
}), '*');
|
||||
}
|
||||
|
||||
window.addEventListener('touchstart', onTouchStart, { passive: true });
|
||||
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
||||
window.addEventListener('touchend', onTouchEnd, { passive: true });
|
||||
|
||||
window.addEventListener('pagehide', () => {
|
||||
window.removeEventListener('touchstart', onTouchStart);
|
||||
window.removeEventListener('touchmove', onTouchMove);
|
||||
window.removeEventListener('touchend', onTouchEnd);
|
||||
});
|
||||
</script>
|
||||
|
||||
''';
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/web_renderer/canvas_kit.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:universal_html/html.dart' as universal_html;
|
||||
|
||||
abstract class PlatformInfo {
|
||||
@visibleForTesting
|
||||
@@ -44,4 +45,25 @@ abstract class PlatformInfo {
|
||||
log('PlatformInfo::platformNameOS(): $platformName');
|
||||
return platformName;
|
||||
}
|
||||
|
||||
static String get userAgent =>
|
||||
isWeb ? universal_html.window.navigator.userAgent.toLowerCase() : '';
|
||||
|
||||
/// Web browser on a mobile phone
|
||||
static bool get isWebMobile =>
|
||||
isWeb && (userAgent.contains('iphone') ||
|
||||
(userAgent.contains('android') && userAgent.contains('mobile')));
|
||||
|
||||
/// Web browser on a tablet device
|
||||
static bool get isWebTablet =>
|
||||
isWeb && (userAgent.contains('ipad') ||
|
||||
(userAgent.contains('android') && !userAgent.contains('mobile')));
|
||||
|
||||
/// Web browser on a desktop (PC/Mac)
|
||||
static bool get isWebDesktop =>
|
||||
isWeb && !isWebMobile && !isWebTablet;
|
||||
|
||||
/// Touch devices
|
||||
static bool get isWebTouchDevice =>
|
||||
isWeb && (isWebMobile || isWebTablet);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user