fix(android): use stable safe area insets to avoid unnecessary layout shift, closes #3670 (#3859)

This commit is contained in:
Huang Xin 2026-04-13 18:04:41 +08:00 committed by GitHub
parent e9d71b2936
commit 011ad18a02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 12 additions and 12 deletions

View file

@ -442,7 +442,6 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
if (windowInsets != null) {
val insets = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars() or
WindowInsetsCompat.Type.displayCutout()
)
val density = activity.resources.displayMetrics.density

View file

@ -2,6 +2,7 @@ import clsx from 'clsx';
import React, { useState, useRef, useEffect } from 'react';
import { IoChevronBack, IoChevronForward } from 'react-icons/io5';
import { useTranslation } from '@/hooks/useTranslation';
import { useKeyDownActions } from '@/hooks/useKeyDownActions';
import { Insets } from '@/types/misc';
import ZoomControls from './ZoomControls';
@ -39,6 +40,9 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
const imageRef = useRef<HTMLImageElement>(null);
const zoomLabelTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Escape (desktop) and Android Back key → close the viewer.
useKeyDownActions({ onCancel: onClose });
const hideZoomLabelAfterDelay = () => {
if (zoomLabelTimeoutRef.current) {
clearTimeout(zoomLabelTimeoutRef.current);
@ -79,10 +83,7 @@ const ImageViewer: React.FC<ImageViewerProps> = ({
const handleKeyDown = (e: React.KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape') {
onClose();
return;
}
// Escape is handled by useKeyDownActions (also covers Android Back key).
// Arrow key navigation
if (e.key === 'ArrowLeft' && onPrevious) {

View file

@ -1,6 +1,7 @@
import clsx from 'clsx';
import React, { useState, useRef, useEffect } from 'react';
import { useTranslation } from '@/hooks/useTranslation';
import { useKeyDownActions } from '@/hooks/useKeyDownActions';
import { Insets } from '@/types/misc';
import ZoomControls from './ZoomControls';
@ -27,6 +28,9 @@ const TableViewer: React.FC<TableViewerProps> = ({ gridInsets, html, isDarkMode,
const contentRef = useRef<HTMLDivElement>(null);
const zoomLabelTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Escape (desktop) and Android Back key → close the viewer.
useKeyDownActions({ onCancel: onClose });
const hideZoomLabelAfterDelay = () => {
if (zoomLabelTimeoutRef.current) {
clearTimeout(zoomLabelTimeoutRef.current);
@ -63,10 +67,7 @@ const TableViewer: React.FC<TableViewerProps> = ({ gridInsets, html, isDarkMode,
const handleKeyDown = (e: React.KeyboardEvent) => {
e.stopPropagation();
if (e.key === 'Escape') {
onClose();
return;
}
// Escape is handled by useKeyDownActions (also covers Android Back key).
const isCtrlOrCmd = e.ctrlKey || e.metaKey;

View file

@ -34,11 +34,10 @@ export const useSafeAreaInsets = () => {
const rootStyles = getComputedStyle(document.documentElement);
const hasCustomProperties = rootStyles.getPropertyValue('--safe-area-inset-top');
const isWebView139 = /Chrome\/139/.test(navigator.userAgent);
if (appService.isIOSApp && getOSPlatform() === 'macos') {
// for iPadOS use zero insets
updateInsets({ top: 0, right: 0, bottom: 0, left: 0 });
} else if ((appService.isAndroidApp && isWebView139) || appService.isIOSApp) {
} else if (appService.isAndroidApp || appService.isIOSApp) {
// safe-area-inset-* values in css are always 0px in some versions of webview 139
// due to https://issues.chromium.org/issues/40699457
getSafeAreaInsets().then((response) => {

View file

@ -776,7 +776,7 @@ export const transformStylesheet = (css: string, vw: number, vh: number, vertica
if (pxWidth > vw && !/max-width\s*:/.test(block)) {
block = block.replace(
/}$/,
' max-width: calc(var(--available-width) * 1px); box-sizing: border-box; }',
' width: 100%; max-width: calc(var(--available-width) * 1px); box-sizing: border-box; }',
);
return selector + block;
}