diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java index 3f04adf976..c84923ca8b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -7,7 +7,6 @@ import android.os.Build; import android.util.TypedValue; import android.view.View; -import android.view.ViewGroup; import android.view.Window; import android.webkit.JavascriptInterface; import android.webkit.WebView; @@ -36,6 +35,11 @@ public class SystemBars extends Plugin { static final String INSETS_HANDLING_CSS = "css"; static final String INSETS_HANDLING_DISABLE = "disable"; + // https://issues.chromium.org/issues/40699457 + private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140; + // https://issues.chromium.org/issues/457682720 + private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144; + static final String viewportMetaJSFunction = """ function capacitorSystemBarsCheckMetaViewport() { const meta = document.querySelectorAll("meta[name=viewport]"); @@ -94,7 +98,7 @@ private void initSystemBars() { } initWindowInsetsListener(); - initSafeAreaInsets(); + initSafeAreaCSSVariables(); getBridge().executeOnMainThread(() -> { setStyle(style, ""); @@ -157,7 +161,7 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) { return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom); } - private void initSafeAreaInsets() { + private void initSafeAreaCSSVariables() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { View v = (View) this.getBridge().getWebView().getParent(); WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v); @@ -169,41 +173,57 @@ private void initSafeAreaInsets() { } private void initWindowInsetsListener() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { - ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { - boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139; + ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { + boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover; + + Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); + Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); + boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (hasViewportCover) { + if (shouldPassthroughInsets) { + // We need to correct for a possible shown IME + v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { Insets safeAreaInsets = calcSafeAreaInsets(insets); injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); } - if (hasBrokenWebViewVersion) { - if (hasViewportCover && v.hasWindowFocus() && v.isShown()) { - boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - if (keyboardVisible) { - Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); - setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom)); - } else { - setViewMargins(v, Insets.NONE); - } - - return WindowInsetsCompat.CONSUMED; - } - } + return new WindowInsetsCompat.Builder(insets) + .setInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), + Insets.of( + systemBarsInsets.left, + systemBarsInsets.top, + systemBarsInsets.right, + getBottomInset(systemBarsInsets, keyboardVisible) + ) + ) + .build(); + } - return insets; - }); - } - } + // We need to correct for a possible shown IME + v.setPadding( + systemBarsInsets.left, + systemBarsInsets.top, + systemBarsInsets.right, + keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom + ); + + // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets + // So we have to explicitly set insets to `0` + // See: https://issues.chromium.org/issues/461332423 + WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets) + .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0)) + .build(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { + Insets safeAreaInsets = calcSafeAreaInsets(newInsets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); + } - private void setViewMargins(View v, Insets insets) { - ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); - mlp.leftMargin = insets.left; - mlp.bottomMargin = insets.bottom; - mlp.rightMargin = insets.right; - mlp.topMargin = insets.top; - v.setLayoutParams(mlp); + return newInsets; + }); } private void injectSafeAreaCSS(int top, int right, int bottom, int left) { @@ -305,4 +325,18 @@ private Integer getWebViewMajorVersion() { return 0; } + + private int getBottomInset(Insets systemBarsInsets, boolean keyboardVisible) { + if (getWebViewMajorVersion() < WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX) { + // This is a workaround for webview versions that have a bug + // that causes the bottom inset to be incorrect if the IME is visible + // See: https://issues.chromium.org/issues/457682720 + + if (keyboardVisible) { + return 0; + } + } + + return systemBarsInsets.bottom; + } } diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index 6612eec777..5eacf5e654 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -713,7 +713,7 @@ export interface PluginsConfig { * * `css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview. * - * `disable` = Disable all inset handling. + * `disable` = Disable CSS variables injection. * * @default "css" */ diff --git a/core/system-bars.md b/core/system-bars.md index c7654063bc..67275077af 100644 --- a/core/system-bars.md +++ b/core/system-bars.md @@ -73,7 +73,7 @@ const setStatusBarAnimation = async () => { ## Configuration | Prop | Type | Description | Default | | ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ | -| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable all inset handling. | css | +| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable CSS variables injection. | css | | **`style`** | string | The style of the text and icons of the system bars. | DEFAULT | | **`hidden`** | boolean | Hide the system bars on start. | false | | **`animation`** | string | The type of status bar animation used when showing or hiding. This option is only supported on iOS. | FADE |