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 b335b4b1c..c77efda72 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -59,6 +59,8 @@ function capacitorSystemBarsCheckMetaViewport() { private String currentStatusBarStyle = STYLE_DEFAULT; private String currentGestureBarStyle = STYLE_DEFAULT; + private boolean navBarVisible = true; + @Override public void load() { getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface"); @@ -76,7 +78,15 @@ protected void handleOnStart() { @Override public void onPageCommitVisible(WebView view, String url) { super.onPageCommitVisible(view, url); - getBridge().getWebView().requestApplyInsets(); + View parentView = (View) getBridge().getWebView().getParent(); + ViewCompat.requestApplyInsets(parentView); + if (insetHandlingEnabled) { + WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(parentView); + if (rootInsets != null) { + Insets safeArea = calcSafeAreaInsets(rootInsets); + injectSafeAreaCSS(safeArea.top, safeArea.right, safeArea.bottom, safeArea.left); + } + } } } ); @@ -103,8 +113,17 @@ private void initSystemBars() { initSafeAreaCSSVariables(); getBridge().executeOnMainThread(() -> { + Window window = getActivity().getWindow(); + WindowCompat.setDecorFitsSystemWindows(window, false); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + window.setNavigationBarColor(android.graphics.Color.TRANSPARENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.setNavigationBarContrastEnforced(false); + } + } setStyle(style, ""); setHidden(hidden, ""); + ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent()); }); } @@ -144,27 +163,64 @@ public void setAnimation(final PluginCall call) { call.resolve(); } + @Override + protected void handleOnResume() { + super.handleOnResume(); + getBridge().executeOnMainThread(() -> { + Window window = getActivity().getWindow(); + WindowCompat.setDecorFitsSystemWindows(window, false); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + window.setNavigationBarColor(android.graphics.Color.TRANSPARENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.setNavigationBarContrastEnforced(false); + } + } + setStyle(currentGestureBarStyle, BAR_GESTURE_BAR); + setStyle(currentStatusBarStyle, BAR_STATUS_BAR); + ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent()); + }); + } + @JavascriptInterface public void onDOMReady() { getActivity().runOnUiThread(() -> { this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> { hasViewportCover = res.equals("true"); - - getBridge().getWebView().requestApplyInsets(); + ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent()); }); }); } private Insets calcSafeAreaInsets(WindowInsetsCompat insets) { Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); - if (insets.isVisible(WindowInsetsCompat.Type.ime())) { + + int bottom = safeArea.bottom; + + if (bottom == 0 + && Build.VERSION.SDK_INT < Build.VERSION_CODES.R + && safeArea.left == 0 && safeArea.right == 0) { // skip if nav bar is on a side (landscape) + bottom = getNavBarHeightFromResources(); + } + + boolean imeVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + ? insets.isVisible(WindowInsetsCompat.Type.ime()) + : insets.getInsets(WindowInsetsCompat.Type.ime()).bottom > 0; + + if (imeVisible) { return Insets.of(safeArea.left, safeArea.top, safeArea.right, 0); } - return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom); + return Insets.of(safeArea.left, safeArea.top, safeArea.right, bottom); + } + + private int getNavBarHeightFromResources() { + if (!navBarVisible) return 0; + android.content.res.Resources res = getActivity().getResources(); + int heightId = res.getIdentifier("navigation_bar_height", "dimen", "android"); + return heightId > 0 ? res.getDimensionPixelSize(heightId) : 0; } private void initSafeAreaCSSVariables() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { + if (insetHandlingEnabled) { View v = (View) this.getBridge().getWebView().getParent(); WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v); if (insets != null) { @@ -176,6 +232,10 @@ private void initSafeAreaCSSVariables() { private void initWindowInsetsListener() { ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> { + // getRootWindowInsets() bypasses AppCompat's intermediate view consuming the bottom inset on API < 30 + WindowInsetsCompat rawInsets = ViewCompat.getRootWindowInsets(v); + WindowInsetsCompat safeAreaSource = (rawInsets != null) ? rawInsets : insets; + boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover; Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); @@ -186,8 +246,8 @@ private void initWindowInsetsListener() { // 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); + if (hasViewportCover && insetHandlingEnabled) { + Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource); injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); } @@ -204,15 +264,7 @@ private void initWindowInsetsListener() { .build(); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - // We need to correct for a possible shown IME - v.setPadding( - systemBarsInsets.left, - systemBarsInsets.top, - systemBarsInsets.right, - keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom - ); - } + v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets // So we have to explicitly set insets to `0` @@ -221,8 +273,8 @@ private void initWindowInsetsListener() { .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); + if (insetHandlingEnabled) { + Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource); injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); } @@ -263,6 +315,7 @@ private void injectSafeAreaCSS(int top, int right, int bottom, int left) { } private void setStyle(String style, String bar) { + String requestedStyle = style; if (style.equals(STYLE_DEFAULT)) { style = getStyleForTheme(); } @@ -270,12 +323,12 @@ private void setStyle(String style, String bar) { Window window = getActivity().getWindow(); WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.getDecorView()); if (bar.isEmpty() || bar.equals(BAR_STATUS_BAR)) { - currentStatusBarStyle = style; + currentStatusBarStyle = requestedStyle; windowInsetsControllerCompat.setAppearanceLightStatusBars(!style.equals(STYLE_DARK)); } if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) { - currentGestureBarStyle = style; + currentGestureBarStyle = requestedStyle; windowInsetsControllerCompat.setAppearanceLightNavigationBars(!style.equals(STYLE_DARK)); } @@ -292,6 +345,7 @@ private void setHidden(boolean hide, String bar) { } if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) { windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars()); + navBarVisible = false; } return; } @@ -301,6 +355,7 @@ private void setHidden(boolean hide, String bar) { } if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) { windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars()); + navBarVisible = true; } }