forked from akai/readest
110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
import { useCallback, useRef, useEffect } from 'react';
|
|
import { useEnv } from '@/context/EnvContext';
|
|
import { useThemeStore } from '@/store/themeStore';
|
|
import { Insets } from '@/types/misc';
|
|
import { getSafeAreaInsets } from '@/utils/bridge';
|
|
import { getOSPlatform } from '@/utils/misc';
|
|
|
|
export const useSafeAreaInsets = () => {
|
|
const { appService } = useEnv();
|
|
const currentInsets = useRef({ top: 0, right: 0, bottom: 0, left: 0 });
|
|
|
|
const { updateSafeAreaInsets } = useThemeStore();
|
|
|
|
const updateInsets = (insets: Insets) => {
|
|
const { top, right, bottom, left } = currentInsets.current;
|
|
if (
|
|
insets.top !== top ||
|
|
insets.right !== right ||
|
|
insets.bottom !== bottom ||
|
|
insets.left !== left
|
|
) {
|
|
currentInsets.current = insets;
|
|
updateSafeAreaInsets(insets);
|
|
}
|
|
};
|
|
|
|
const onUpdateInsets = useCallback(() => {
|
|
if (!appService) return;
|
|
|
|
if (!appService.hasSafeAreaInset) {
|
|
updateInsets(currentInsets.current);
|
|
return;
|
|
}
|
|
|
|
const rootStyles = getComputedStyle(document.documentElement);
|
|
const hasCustomProperties = rootStyles.getPropertyValue('--safe-area-inset-top');
|
|
if (appService.isIOSApp && getOSPlatform() === 'macos') {
|
|
// for iPadOS use zero insets
|
|
updateInsets({ top: 0, right: 0, bottom: 0, left: 0 });
|
|
} 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) => {
|
|
if (response.error) {
|
|
console.error('Error getting safe area insets from native bridge:', response.error);
|
|
} else {
|
|
const insets = {
|
|
top: Math.round(response.top),
|
|
right: Math.round(response.right),
|
|
bottom: Math.round(response.bottom),
|
|
left: Math.round(response.left),
|
|
};
|
|
updateInsets(insets);
|
|
}
|
|
});
|
|
} else if (hasCustomProperties) {
|
|
const top = parseFloat(rootStyles.getPropertyValue('--safe-area-inset-top')) || 0;
|
|
const right = parseFloat(rootStyles.getPropertyValue('--safe-area-inset-right')) || 0;
|
|
const bottom = parseFloat(rootStyles.getPropertyValue('--safe-area-inset-bottom')) || 0;
|
|
const left = parseFloat(rootStyles.getPropertyValue('--safe-area-inset-left')) || 0;
|
|
const insets = {
|
|
top: Math.round(top),
|
|
right: Math.round(right),
|
|
bottom: Math.round(bottom),
|
|
left: Math.round(left),
|
|
};
|
|
|
|
updateInsets(insets);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [appService]);
|
|
|
|
useEffect(() => {
|
|
onUpdateInsets();
|
|
|
|
// Listen for orientation changes
|
|
if (window.screen?.orientation) {
|
|
window.screen.orientation.addEventListener('change', onUpdateInsets);
|
|
} else {
|
|
window.addEventListener('orientationchange', onUpdateInsets);
|
|
}
|
|
|
|
// Listen for visibility changes (app returning from background)
|
|
const handleVisibilityChange = () => {
|
|
if (document.visibilityState === 'visible') {
|
|
onUpdateInsets();
|
|
}
|
|
};
|
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
|
|
// Listen for window focus (additional safeguard for iOS)
|
|
const handleFocus = () => {
|
|
onUpdateInsets();
|
|
};
|
|
window.addEventListener('focus', handleFocus);
|
|
|
|
return () => {
|
|
if (window.screen?.orientation) {
|
|
window.screen.orientation.removeEventListener('change', onUpdateInsets);
|
|
} else {
|
|
window.removeEventListener('orientationchange', onUpdateInsets);
|
|
}
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
window.removeEventListener('focus', handleFocus);
|
|
};
|
|
}, [onUpdateInsets]);
|
|
|
|
return { onUpdateInsets };
|
|
};
|