Skip to content

Commit 94d9f70

Browse files
riderxstormbeforesunsetbeeCapacitor+ Botcursoragent
authored
chore: sync upstream PR ionic-team#8454 - Fix navigation bar overlay in edge-to-edge mode on Android (#67)
* Enhance SystemBars with navigation bar visibility Added navigation bar visibility handling and adjusted safe area calculations. Updated insets handling for improved layout compatibility. * fix(SystemBars): add handleOnResume to manage navigation bar appearance * fix(android): silence InternalInsetResource lint on nav bar height fallback Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com> * fix(android): also suppress DiscouragedApi on nav bar height fallback Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com> --------- Co-authored-by: Storm <rivanfebrian123@gmail.com> Co-authored-by: Capacitor+ Bot <bot@capgo.app> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent fd8d209 commit 94d9f70

1 file changed

Lines changed: 89 additions & 31 deletions

File tree

android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.getcapacitor.plugin;
22

3+
import android.annotation.SuppressLint;
34
import android.content.Context;
45
import android.content.pm.PackageInfo;
56
import android.content.res.Configuration;
@@ -59,6 +60,8 @@ function capacitorSystemBarsCheckMetaViewport() {
5960
private String currentStatusBarStyle = STYLE_DEFAULT;
6061
private String currentGestureBarStyle = STYLE_DEFAULT;
6162

63+
private boolean navBarVisible = true;
64+
6265
@Override
6366
public void load() {
6467
getBridge().getWebView().addJavascriptInterface(this, "CapacitorSystemBarsAndroidInterface");
@@ -76,7 +79,15 @@ protected void handleOnStart() {
7679
@Override
7780
public void onPageCommitVisible(WebView view, String url) {
7881
super.onPageCommitVisible(view, url);
79-
getBridge().getWebView().requestApplyInsets();
82+
View parentView = (View) getBridge().getWebView().getParent();
83+
ViewCompat.requestApplyInsets(parentView);
84+
if (insetHandlingEnabled) {
85+
WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(parentView);
86+
if (rootInsets != null) {
87+
Insets safeArea = calcSafeAreaInsets(rootInsets);
88+
injectSafeAreaCSS(safeArea.top, safeArea.right, safeArea.bottom, safeArea.left);
89+
}
90+
}
8091
}
8192
}
8293
);
@@ -103,8 +114,17 @@ private void initSystemBars() {
103114
initSafeAreaCSSVariables();
104115

105116
getBridge().executeOnMainThread(() -> {
117+
Window window = getActivity().getWindow();
118+
WindowCompat.setDecorFitsSystemWindows(window, false);
119+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
120+
window.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
121+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
122+
window.setNavigationBarContrastEnforced(false);
123+
}
124+
}
106125
setStyle(style, "");
107126
setHidden(hidden, "");
127+
ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
108128
});
109129
}
110130

@@ -144,43 +164,82 @@ public void setAnimation(final PluginCall call) {
144164
call.resolve();
145165
}
146166

167+
@Override
168+
protected void handleOnResume() {
169+
super.handleOnResume();
170+
getBridge().executeOnMainThread(() -> {
171+
Window window = getActivity().getWindow();
172+
WindowCompat.setDecorFitsSystemWindows(window, false);
173+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
174+
window.setNavigationBarColor(android.graphics.Color.TRANSPARENT);
175+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
176+
window.setNavigationBarContrastEnforced(false);
177+
}
178+
}
179+
setStyle(currentGestureBarStyle, BAR_GESTURE_BAR);
180+
setStyle(currentStatusBarStyle, BAR_STATUS_BAR);
181+
ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
182+
});
183+
}
184+
147185
@JavascriptInterface
148186
public void onDOMReady() {
149187
getActivity().runOnUiThread(() -> {
150188
this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
151189
hasViewportCover = res.equals("true");
152-
153-
getBridge().getWebView().requestApplyInsets();
190+
ViewCompat.requestApplyInsets((View) getBridge().getWebView().getParent());
154191
});
155192
});
156193
}
157194

158195
private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
159196
Insets safeArea = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
160-
if (insets.isVisible(WindowInsetsCompat.Type.ime())) {
197+
198+
int bottom = safeArea.bottom;
199+
200+
if (bottom == 0 && Build.VERSION.SDK_INT < Build.VERSION_CODES.R && safeArea.left == 0 && safeArea.right == 0) {
201+
// skip if nav bar is on a side (landscape)
202+
bottom = getNavBarHeightFromResources();
203+
}
204+
205+
boolean imeVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
206+
? insets.isVisible(WindowInsetsCompat.Type.ime())
207+
: insets.getInsets(WindowInsetsCompat.Type.ime()).bottom > 0;
208+
209+
if (imeVisible) {
161210
return Insets.of(safeArea.left, safeArea.top, safeArea.right, 0);
162211
}
163-
return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
212+
return Insets.of(safeArea.left, safeArea.top, safeArea.right, bottom);
164213
}
165214

166-
private void initSafeAreaCSSVariables() {
167-
WindowInsetsCompat insets;
215+
// Only reached on API < 30 when the window reports no inset on any edge, so the
216+
// choice is this approximation or a hardcoded 0. Lint rejects the internal
217+
// resource because OEMs may change it; a wrong height still beats no inset.
218+
@SuppressLint({ "InternalInsetResource", "DiscouragedApi" })
219+
private int getNavBarHeightFromResources() {
220+
if (!navBarVisible) return 0;
221+
android.content.res.Resources res = getActivity().getResources();
222+
int heightId = res.getIdentifier("navigation_bar_height", "dimen", "android");
223+
return heightId > 0 ? res.getDimensionPixelSize(heightId) : 0;
224+
}
168225

169-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
226+
private void initSafeAreaCSSVariables() {
227+
if (insetHandlingEnabled) {
170228
View v = (View) this.getBridge().getWebView().getParent();
171-
insets = ViewCompat.getRootWindowInsets(v);
172-
} else {
173-
insets = WindowInsetsCompat.CONSUMED;
174-
}
175-
176-
if (insets != null) {
177-
Insets safeAreaInsets = calcSafeAreaInsets(insets);
178-
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
229+
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
230+
if (insets != null) {
231+
Insets safeAreaInsets = calcSafeAreaInsets(insets);
232+
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
233+
}
179234
}
180235
}
181236

182237
private void initWindowInsetsListener() {
183238
ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
239+
// getRootWindowInsets() bypasses AppCompat's intermediate view consuming the bottom inset on API < 30
240+
WindowInsetsCompat rawInsets = ViewCompat.getRootWindowInsets(v);
241+
WindowInsetsCompat safeAreaSource = (rawInsets != null) ? rawInsets : insets;
242+
184243
boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
185244

186245
Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
@@ -191,8 +250,10 @@ private void initWindowInsetsListener() {
191250
// We need to correct for a possible shown IME
192251
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
193252

194-
Insets safeAreaInsets = calcSafeAreaInsets(insets);
195-
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
253+
if (hasViewportCover && insetHandlingEnabled) {
254+
Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource);
255+
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
256+
}
196257

197258
return new WindowInsetsCompat.Builder(insets)
198259
.setInsets(
@@ -207,15 +268,7 @@ private void initWindowInsetsListener() {
207268
.build();
208269
}
209270

210-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
211-
// We need to correct for a possible shown IME
212-
v.setPadding(
213-
systemBarsInsets.left,
214-
systemBarsInsets.top,
215-
systemBarsInsets.right,
216-
keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
217-
);
218-
}
271+
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
219272

220273
// Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
221274
// So we have to explicitly set insets to `0`
@@ -224,8 +277,10 @@ private void initWindowInsetsListener() {
224277
.setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
225278
.build();
226279

227-
Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
228-
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
280+
if (insetHandlingEnabled) {
281+
Insets safeAreaInsets = calcSafeAreaInsets(safeAreaSource);
282+
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
283+
}
229284

230285
return newInsets;
231286
});
@@ -264,19 +319,20 @@ private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
264319
}
265320

266321
private void setStyle(String style, String bar) {
322+
String requestedStyle = style;
267323
if (style.equals(STYLE_DEFAULT)) {
268324
style = getStyleForTheme();
269325
}
270326

271327
Window window = getActivity().getWindow();
272328
WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
273329
if (bar.isEmpty() || bar.equals(BAR_STATUS_BAR)) {
274-
currentStatusBarStyle = style;
330+
currentStatusBarStyle = requestedStyle;
275331
windowInsetsControllerCompat.setAppearanceLightStatusBars(!style.equals(STYLE_DARK));
276332
}
277333

278334
if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
279-
currentGestureBarStyle = style;
335+
currentGestureBarStyle = requestedStyle;
280336
windowInsetsControllerCompat.setAppearanceLightNavigationBars(!style.equals(STYLE_DARK));
281337
}
282338

@@ -293,6 +349,7 @@ private void setHidden(boolean hide, String bar) {
293349
}
294350
if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
295351
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars());
352+
navBarVisible = false;
296353
}
297354
return;
298355
}
@@ -302,6 +359,7 @@ private void setHidden(boolean hide, String bar) {
302359
}
303360
if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
304361
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
362+
navBarVisible = true;
305363
}
306364
}
307365

0 commit comments

Comments
 (0)