You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
6.5 KiB
226 lines
6.5 KiB
/**
|
|
* Safe Area Inset Injection for Android WebView
|
|
*
|
|
* This script injects safe area inset values into CSS environment variables
|
|
* when running in Android WebView, since Android doesn't natively support
|
|
* CSS env(safe-area-inset-*) variables like iOS does.
|
|
*/
|
|
|
|
// Check if we're running in Android WebView with Capacitor
|
|
const isAndroidWebView = () => {
|
|
// Check if we're on iOS - if so, skip this script entirely
|
|
const isIOS =
|
|
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
|
|
if (isIOS) {
|
|
return false;
|
|
}
|
|
|
|
// Check if we're on Android
|
|
const isAndroid = /Android/.test(navigator.userAgent);
|
|
|
|
// Check if we have Capacitor (required for Android WebView)
|
|
const hasCapacitor = window.Capacitor !== undefined;
|
|
|
|
// Only run on Android with Capacitor
|
|
return isAndroid && hasCapacitor;
|
|
};
|
|
|
|
// Wait for Capacitor to be available
|
|
const waitForCapacitor = () => {
|
|
return new Promise((resolve) => {
|
|
if (window.Capacitor) {
|
|
resolve(window.Capacitor);
|
|
return;
|
|
}
|
|
|
|
// Wait for Capacitor to be available
|
|
const checkCapacitor = () => {
|
|
if (window.Capacitor) {
|
|
resolve(window.Capacitor);
|
|
} else {
|
|
setTimeout(checkCapacitor, 100);
|
|
}
|
|
};
|
|
|
|
checkCapacitor();
|
|
});
|
|
};
|
|
|
|
// Inject safe area inset values into CSS custom properties
|
|
const injectSafeAreaInsets = async () => {
|
|
try {
|
|
// Wait for Capacitor to be available
|
|
const Capacitor = await waitForCapacitor();
|
|
|
|
// Try to get safe area insets using StatusBar plugin (which is already available)
|
|
|
|
let top = 0,
|
|
bottom = 0,
|
|
left = 0,
|
|
right = 0;
|
|
|
|
try {
|
|
// Use StatusBar plugin to get status bar height
|
|
if (Capacitor.Plugins.StatusBar) {
|
|
const statusBarInfo = await Capacitor.Plugins.StatusBar.getInfo();
|
|
// Status bar height is typically the top safe area inset
|
|
top = statusBarInfo.overlays ? 0 : statusBarInfo.height || 0;
|
|
}
|
|
} catch (error) {
|
|
// Status bar info not available, will use fallback
|
|
}
|
|
|
|
// Detect navigation bar and gesture bar heights
|
|
const detectNavigationBar = () => {
|
|
const screenHeight = window.screen.height;
|
|
const screenWidth = window.screen.width;
|
|
const windowHeight = window.innerHeight;
|
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
|
|
// Calculate navigation bar height
|
|
let navBarHeight = 0;
|
|
|
|
// Method 1: Direct comparison (most reliable)
|
|
if (windowHeight < screenHeight) {
|
|
navBarHeight = screenHeight - windowHeight;
|
|
}
|
|
|
|
// Method 2: Check for gesture navigation indicators
|
|
if (navBarHeight === 0) {
|
|
// Look for common gesture navigation patterns
|
|
const isTallDevice = screenHeight > 2000;
|
|
const isModernDevice = screenHeight > 1800;
|
|
const hasHighDensity = devicePixelRatio >= 2.5;
|
|
|
|
if (isTallDevice && hasHighDensity) {
|
|
// Modern gesture-based device
|
|
navBarHeight = 12; // Typical gesture bar height
|
|
} else if (isModernDevice) {
|
|
// Modern device with traditional navigation
|
|
navBarHeight = 48; // Traditional navigation bar height
|
|
}
|
|
}
|
|
|
|
// Method 3: Check visual viewport (more accurate for WebView)
|
|
if (navBarHeight === 0) {
|
|
if (window.visualViewport) {
|
|
const visualHeight = window.visualViewport.height;
|
|
|
|
if (visualHeight < windowHeight) {
|
|
navBarHeight = windowHeight - visualHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Method 4: Device-specific estimation based on screen dimensions
|
|
if (navBarHeight === 0) {
|
|
// Common Android navigation bar heights in pixels
|
|
const commonNavBarHeights = {
|
|
"1080x2400": 48, // Common 1080p devices
|
|
"1440x3200": 64, // QHD devices
|
|
"720x1600": 32, // HD devices
|
|
};
|
|
|
|
const resolution = `${screenWidth}x${screenHeight}`;
|
|
const estimatedHeight = commonNavBarHeights[resolution];
|
|
|
|
if (estimatedHeight) {
|
|
navBarHeight = estimatedHeight;
|
|
} else {
|
|
// Fallback: estimate based on screen height
|
|
navBarHeight = screenHeight > 2000 ? 48 : 32;
|
|
}
|
|
}
|
|
|
|
return navBarHeight;
|
|
};
|
|
|
|
// Get navigation bar height
|
|
bottom = detectNavigationBar();
|
|
|
|
// If we still don't have a top value, estimate it
|
|
if (top === 0) {
|
|
const screenHeight = window.screen.height;
|
|
// Common status bar heights: 24dp (48px) for most devices, 32dp (64px) for some
|
|
top = screenHeight > 1920 ? 64 : 48;
|
|
}
|
|
|
|
// Left/right safe areas are rare on Android
|
|
left = 0;
|
|
right = 0;
|
|
|
|
// Create CSS custom properties
|
|
const style = document.createElement("style");
|
|
style.textContent = `
|
|
:root {
|
|
--safe-area-inset-top: ${top}px;
|
|
--safe-area-inset-bottom: ${bottom}px;
|
|
--safe-area-inset-left: ${left}px;
|
|
--safe-area-inset-right: ${right}px;
|
|
}
|
|
`;
|
|
|
|
// Inject the style into the document head
|
|
document.head.appendChild(style);
|
|
|
|
// Also set CSS environment variables if supported
|
|
if (CSS.supports("env(safe-area-inset-top)")) {
|
|
document.documentElement.style.setProperty(
|
|
"--env-safe-area-inset-top",
|
|
`${top}px`,
|
|
);
|
|
document.documentElement.style.setProperty(
|
|
"--env-safe-area-inset-bottom",
|
|
`${bottom}px`,
|
|
);
|
|
document.documentElement.style.setProperty(
|
|
"--env-safe-area-inset-left",
|
|
`${left}px`,
|
|
);
|
|
document.documentElement.style.setProperty(
|
|
"--env-safe-area-inset-right",
|
|
`${right}px`,
|
|
);
|
|
}
|
|
} catch (error) {
|
|
// Error injecting safe area insets, will use fallback values
|
|
}
|
|
};
|
|
|
|
// Initialize when DOM is ready
|
|
const initializeSafeArea = () => {
|
|
// Check if we should run this script at all
|
|
if (!isAndroidWebView()) {
|
|
return;
|
|
}
|
|
|
|
// Add a small delay to ensure WebView is fully initialized
|
|
setTimeout(() => {
|
|
injectSafeAreaInsets();
|
|
}, 100);
|
|
};
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", initializeSafeArea);
|
|
} else {
|
|
initializeSafeArea();
|
|
}
|
|
|
|
// Re-inject on orientation change (only on Android)
|
|
window.addEventListener("orientationchange", () => {
|
|
if (isAndroidWebView()) {
|
|
setTimeout(() => injectSafeAreaInsets(), 100);
|
|
}
|
|
});
|
|
|
|
// Re-inject on resize (only on Android)
|
|
window.addEventListener("resize", () => {
|
|
if (isAndroidWebView()) {
|
|
setTimeout(() => injectSafeAreaInsets(), 100);
|
|
}
|
|
});
|
|
|
|
// Export for use in other modules
|
|
export { injectSafeAreaInsets, isAndroidWebView };
|
|
|