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

/**
* 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 };