Skip to content
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.getcapacitor.plugin;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.res.Configuration;
Expand Down Expand Up @@ -59,6 +60,8 @@ function capacitorSystemBarsCheckMetaViewport() {
private String currentStatusBarStyle = STYLE_DEFAULT;
private String currentGestureBarStyle = STYLE_DEFAULT;

private boolean navBarVisible = true;
Comment thread
cursor[bot] marked this conversation as resolved.

@Override
public void load() {
getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface");
Expand All @@ -76,7 +79,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);
}
}
}
}
);
Expand All @@ -103,8 +114,17 @@ private void initSystemBars() {
initSafeAreaCSSVariables();

getBridge().executeOnMainThread(() -> {
Window window = getActivity().getWindow();
WindowCompat.setDecorFitsSystemWindows(window, false);
Comment thread
cursor[bot] marked this conversation as resolved.
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());
});
}

Expand Down Expand Up @@ -144,43 +164,82 @@ 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 void initSafeAreaCSSVariables() {
WindowInsetsCompat insets;
// Only reached on API < 30 when the window reports no inset on any edge, so the
// choice is this approximation or a hardcoded 0. Lint rejects the internal
// resource because OEMs may change it; a wrong height still beats no inset.
@SuppressLint({ "InternalInsetResource", "DiscouragedApi" })
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;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
private void initSafeAreaCSSVariables() {
if (insetHandlingEnabled) {
View v = (View) this.getBridge().getWebView().getParent();
insets = ViewCompat.getRootWindowInsets(v);
} else {
insets = WindowInsetsCompat.CONSUMED;
}

if (insets != null) {
Insets safeAreaInsets = calcSafeAreaInsets(insets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
if (insets != null) {
Insets safeAreaInsets = calcSafeAreaInsets(insets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
}
}
}

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());
Expand All @@ -191,8 +250,10 @@ private void initWindowInsetsListener() {
// We need to correct for a possible shown IME
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);

Insets safeAreaInsets = calcSafeAreaInsets(insets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
if (hasViewportCover && insetHandlingEnabled) {
Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
}

return new WindowInsetsCompat.Builder(insets)
.setInsets(
Expand All @@ -207,15 +268,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`
Expand All @@ -224,8 +277,10 @@ private void initWindowInsetsListener() {
.setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
.build();

Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
if (insetHandlingEnabled) {
Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
}

return newInsets;
});
Expand Down Expand Up @@ -264,19 +319,20 @@ 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();
}

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));
}

Expand All @@ -293,6 +349,7 @@ private void setHidden(boolean hide, String bar) {
}
if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars());
navBarVisible = false;
}
return;
}
Expand All @@ -302,6 +359,7 @@ private void setHidden(boolean hide, String bar) {
}
if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
navBarVisible = true;
}
}

Expand Down
Loading