capacitor-plugin-status-bar 2.0.14 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,13 +4,14 @@ import android.app.Activity;
4
4
  import android.graphics.Color;
5
5
  import android.os.Build;
6
6
  import android.util.Log;
7
+ import android.view.Gravity;
7
8
  import android.view.View;
8
9
  import android.view.ViewGroup;
9
10
  import android.view.Window;
10
11
  import android.view.WindowInsets;
11
12
  import android.view.WindowInsetsController;
13
+ import android.view.WindowManager;
12
14
  import android.widget.FrameLayout;
13
- import android.view.Gravity;
14
15
 
15
16
  import androidx.annotation.ColorInt;
16
17
  import androidx.annotation.Nullable;
@@ -18,49 +19,54 @@ import androidx.core.graphics.ColorUtils;
18
19
  import androidx.core.view.ViewCompat;
19
20
  import androidx.core.view.WindowCompat;
20
21
  import androidx.core.view.WindowInsetsCompat;
22
+ import androidx.core.view.WindowInsetsControllerCompat;
21
23
 
22
- import com.getcapacitor.Plugin;
23
-
24
- /**
25
- * Android Status Bar utilities with Android 10-15+ (API 29-35+) support.
26
- * Supports:
27
- * - API 29 (Android 10): Uses deprecated SYSTEM_UI_FLAG for backward
28
- * compatibility
29
- * - API 30+ (Android 11+): Uses modern WindowInsetsController API
30
- * - API 35+ (Android 15+): Fully compatible with edge-to-edge display
31
- * enforcement
32
- */
33
- public class CapacitorStatusBar extends Plugin {
24
+ public class CapacitorStatusBar {
34
25
  private static final String TAG = "CapacitorStatusBar";
35
26
  private static final String STATUS_BAR_OVERLAY_TAG = "capacitor_status_bar_overlay";
36
27
  private static final String NAV_BAR_OVERLAY_TAG = "capacitor_navigation_bar_overlay";
37
28
 
38
- // Store current state to preserve colors when hiding/showing
39
29
  private String currentStyle = "LIGHT";
40
30
  private String currentColorHex = null;
41
31
  private int currentStatusBarColor = Color.BLACK;
42
32
  private int currentNavBarColor = Color.BLACK;
33
+ private boolean statusBarHidden = false;
34
+ private boolean navBarHidden = false;
35
+
36
+ private boolean isAndroid15OrAbove() {
37
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
38
+ }
43
39
 
44
- // Note: load() is not called since CapacitorStatusBar is instantiated via new
45
- // CapacitorStatusBar()
46
- // from CapacitorStatusBarPlugin, not registered as a Capacitor plugin itself.
47
- // All initialization happens via ensureEdgeToEdgeConfigured() called from
48
- // CapacitorStatusBarPlugin.load().
40
+ private boolean supportsInsetsController() {
41
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
42
+ }
43
+
44
+ private boolean supportsContrastControl() {
45
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
46
+ }
47
+
48
+ private void applyLegacyContrastPolicy(Window window) {
49
+ if (!isAndroid15OrAbove() && supportsContrastControl()) {
50
+ window.setStatusBarContrastEnforced(false);
51
+ window.setNavigationBarContrastEnforced(false);
52
+ }
53
+ }
49
54
 
50
55
  /**
51
- * Ensures edge-to-edge is properly configured for Android 12+.
52
- * Sets up a unified insets listener on the decorView that handles
53
- * overlay view sizing and WebView padding so content stays within
54
- * the safe area.
55
- *
56
- * @param activity The activity to configure
57
- * @param webView The Capacitor WebView to apply safe-area padding to (may be null)
56
+ * Configure layout behavior for the current API level. Called once from
57
+ * CapacitorStatusBarPlugin.load().
58
58
  */
59
- public void ensureEdgeToEdgeConfigured(Activity activity, @Nullable View webView) {
59
+ public void ensureEdgeToEdgeConfigured(Activity activity) {
60
60
  Window window = activity.getWindow();
61
61
  View decorView = window.getDecorView();
62
+ configureWebview(window, decorView);
63
+ }
64
+
65
+ private void configureWebview(Window window, View decorView) {
66
+ if (isAndroid15OrAbove()) {
62
67
 
63
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
68
+ // Android 15+: edge-to-edge. Overlay views paint the bar regions;
69
+ // JS gets real insets.
64
70
  WindowCompat.setDecorFitsSystemWindows(window, false);
65
71
 
66
72
  window.setStatusBarColor(Color.TRANSPARENT);
@@ -73,19 +79,15 @@ public class CapacitorStatusBar extends Plugin {
73
79
  int navBottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
74
80
  boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
75
81
 
76
- // Size status bar overlay
77
82
  View statusOverlay = ((ViewGroup) v).findViewWithTag(STATUS_BAR_OVERLAY_TAG);
78
83
  if (statusOverlay != null) {
79
84
  ViewGroup.LayoutParams params = statusOverlay.getLayoutParams();
80
85
  if (params.height != top) {
81
86
  params.height = top;
82
87
  statusOverlay.setLayoutParams(params);
83
- Log.d(TAG, "insetsListener: status bar overlay height=" + top);
84
88
  }
85
89
  }
86
90
 
87
- // Size navigation bar overlay — collapse it when the keyboard is
88
- // open since the nav bar is hidden behind the IME.
89
91
  int overlayBottom = imeVisible ? 0 : navBottom;
90
92
  View navOverlay = ((ViewGroup) v).findViewWithTag(NAV_BAR_OVERLAY_TAG);
91
93
  if (navOverlay != null) {
@@ -93,31 +95,6 @@ public class CapacitorStatusBar extends Plugin {
93
95
  if (params.height != overlayBottom) {
94
96
  params.height = overlayBottom;
95
97
  navOverlay.setLayoutParams(params);
96
- Log.d(TAG, "insetsListener: nav bar overlay height=" + overlayBottom
97
- + " (imeVisible=" + imeVisible + ")");
98
- }
99
- }
100
-
101
- // Inset the WebView via layout margins so it never renders behind
102
- // system bars. When the keyboard is open the nav bar is behind the
103
- // IME, so drop the bottom margin to avoid a gap above the keyboard.
104
- if (webView != null
105
- && webView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
106
- ViewGroup.MarginLayoutParams mlp =
107
- (ViewGroup.MarginLayoutParams) webView.getLayoutParams();
108
- int left = insets.getInsets(WindowInsetsCompat.Type.systemBars()).left;
109
- int right = insets.getInsets(WindowInsetsCompat.Type.systemBars()).right;
110
- int bottomMargin = imeVisible ? 0 : navBottom;
111
- if (mlp.topMargin != top || mlp.bottomMargin != bottomMargin
112
- || mlp.leftMargin != left || mlp.rightMargin != right) {
113
- mlp.topMargin = top;
114
- mlp.bottomMargin = bottomMargin;
115
- mlp.leftMargin = left;
116
- mlp.rightMargin = right;
117
- webView.setLayoutParams(mlp);
118
- Log.d(TAG, "insetsListener: webView margins t=" + top
119
- + " b=" + bottomMargin + " l=" + left + " r=" + right
120
- + " (imeVisible=" + imeVisible + ")");
121
98
  }
122
99
  }
123
100
 
@@ -125,193 +102,237 @@ public class CapacitorStatusBar extends Plugin {
125
102
  });
126
103
 
127
104
  decorView.requestApplyInsets();
128
- Log.d(TAG, "ensureEdgeToEdgeConfigured: edge-to-edge with overlay views, API=" + Build.VERSION.SDK_INT);
105
+ Log.d(TAG, "ensureEdgeToEdgeConfigured: edge-to-edge (API " + Build.VERSION.SDK_INT + ")");
129
106
  } else {
130
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
131
- window.setStatusBarContrastEnforced(false);
132
- window.setNavigationBarContrastEnforced(false);
133
- }
107
+ // API < 35: keep WebView inside the safe area. System draws bars.
108
+ WindowCompat.setDecorFitsSystemWindows(window, true);
109
+
110
+ // Ensure the system draws bar backgrounds with the colors we set
111
+ // (not a translucent scrim).
112
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
113
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
114
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
134
115
 
135
- Log.d(TAG, "ensureEdgeToEdgeConfigured: contrast enforcement disabled only, API=" + Build.VERSION.SDK_INT);
116
+ applyLegacyContrastPolicy(window);
117
+ Log.d(TAG, "ensureEdgeToEdgeConfigured: fitted layout (API " + Build.VERSION.SDK_INT + ")");
136
118
  }
137
119
  }
138
120
 
139
121
  public void setOverlaysWebView(Activity activity, boolean overlay) {
140
- // No-op on Android. Exposed for API parity with iOS.
141
122
  Log.d(TAG, "setOverlaysWebView: no-op on Android, overlay=" + overlay);
142
123
  }
143
124
 
144
125
  public void showStatusBar(Activity activity, boolean animated) {
145
- Log.d(TAG, "showStatusBar: animated=" + animated + ", currentStyle=" + currentStyle + ", API="
146
- + Build.VERSION.SDK_INT);
126
+ Log.d(TAG, "showStatusBar: animated=" + animated + ", API=" + Build.VERSION.SDK_INT);
147
127
  Window window = activity.getWindow();
148
128
  View decorView = window.getDecorView();
129
+ statusBarHidden = false;
130
+ navBarHidden = false;
149
131
 
150
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
151
- // API 30+ (Android 11+) - Use WindowInsetsController
152
- WindowInsetsController controller = window.getInsetsController();
153
- if (controller != null) {
154
- Log.d(TAG, "showStatusBar: showing status bar (API 30+)");
155
- controller.show(WindowInsets.Type.statusBars());
156
- controller.setSystemBarsBehavior(
157
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
158
- } else {
159
- Log.w(TAG, "showStatusBar: WindowInsetsController is null");
160
- }
161
- } else {
162
- // API 29 (Android 10) - Use system UI visibility flags (deprecated but
163
- // necessary)
164
- Log.d(TAG, "showStatusBar: showing using system UI flags (API 29)");
165
- int flags = decorView.getSystemUiVisibility();
166
- flags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
167
- decorView.setSystemUiVisibility(flags);
132
+ if (!isAndroid15OrAbove()) {
133
+ WindowCompat.setDecorFitsSystemWindows(window, true);
134
+ }
135
+
136
+ // Clear fullscreen flag so bars can appear again.
137
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
138
+
139
+ WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(window, decorView);
140
+ controller.show(WindowInsetsCompat.Type.statusBars());
141
+ controller.show(WindowInsetsCompat.Type.navigationBars());
142
+
143
+ // Restore overlays so colors reappear on both legacy and edge-to-edge.
144
+ ViewGroup dv = (ViewGroup) decorView;
145
+ View statusOverlay = dv.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
146
+ if (statusOverlay != null) {
147
+ statusOverlay.setVisibility(View.VISIBLE);
148
+ }
149
+ View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
150
+ if (navOverlay != null) {
151
+ navOverlay.setVisibility(View.VISIBLE);
168
152
  }
169
153
 
170
- // Reapply the stored colors and style
171
154
  reapplyCurrentStyle(activity);
172
155
  }
173
156
 
174
157
  public void hideStatusBar(Activity activity) {
158
+ Log.d(TAG, "hideStatusBar: API=" + Build.VERSION.SDK_INT);
175
159
  Window window = activity.getWindow();
176
160
  View decorView = window.getDecorView();
161
+ statusBarHidden = true;
162
+ navBarHidden = true;
177
163
 
178
- Log.d(TAG, "hideStatusBar: hiding status bar");
164
+ // Edge-to-edge so the WebView extends into the freed bar regions.
165
+ WindowCompat.setDecorFitsSystemWindows(window, false);
179
166
 
180
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
181
- // API 30+ (Android 11+) - Use WindowInsetsController
182
- WindowInsetsController controller = window.getInsetsController();
183
- if (controller != null) {
184
- Log.d(TAG, "hideStatusBar: hiding status bar (API 30+)");
185
- controller.hide(WindowInsets.Type.statusBars());
186
- controller.setSystemBarsBehavior(
187
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
188
- } else {
189
- Log.w(TAG, "hideStatusBar: WindowInsetsController is null");
190
- }
191
- } else {
192
- // API 29 (Android 10) - Use system UI visibility flags (deprecated but
193
- // necessary)
194
- Log.d(TAG, "hideStatusBar: hiding using system UI flags (API 29)");
167
+ // Clear flags that can block the bars from hiding.
168
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
169
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
170
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
171
+
172
+ ViewGroup dv = (ViewGroup) decorView;
173
+ View statusOverlay = dv.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
174
+ if (statusOverlay != null) {
175
+ statusOverlay.setVisibility(View.GONE);
176
+ }
177
+ View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
178
+ if (navOverlay != null) {
179
+ navOverlay.setVisibility(View.GONE);
180
+ }
181
+
182
+ hideSystemBarsNow(window, decorView);
183
+
184
+ window.setStatusBarColor(Color.TRANSPARENT);
185
+ window.setNavigationBarColor(Color.TRANSPARENT);
186
+
187
+ // Re-apply on next frame to beat any post-layout reapplication from
188
+ // the framework or other plugins that may re-show bars.
189
+ decorView.post(() -> hideSystemBarsNow(window, decorView));
190
+ }
191
+
192
+ private void hideSystemBarsNow(Window window, View decorView) {
193
+ WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(window, decorView);
194
+ controller.setSystemBarsBehavior(
195
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
196
+ controller.hide(WindowInsetsCompat.Type.statusBars());
197
+ controller.hide(WindowInsetsCompat.Type.navigationBars());
198
+
199
+ // Belt-and-suspenders: on API 30–34, set FLAG_FULLSCREEN as a fallback
200
+ // in case the controller call is ignored by theme/manifest state.
201
+ if (!isAndroid15OrAbove()) {
202
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
203
+ }
204
+
205
+ // Legacy path (API < 30): also toggle system UI visibility flags.
206
+ if (!supportsInsetsController()) {
195
207
  decorView.setSystemUiVisibility(
196
208
  decorView.getSystemUiVisibility()
197
209
  | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
198
210
  | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
199
211
  | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
200
- | View.SYSTEM_UI_FLAG_FULLSCREEN);
212
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
213
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
214
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
201
215
  }
202
-
203
- // Make the status bar overlay transparent so content shows through
204
- makeStatusBarBackgroundTransparent(activity);
205
216
  }
206
217
 
207
218
  public void setStyle(Activity activity, String style, @Nullable String colorHex) {
208
219
  Log.d(TAG, "setStyle: style=" + style + ", colorHex=" + colorHex);
209
220
  Window window = activity.getWindow();
210
221
 
211
- // Store the current style and color for later reapplication
222
+ if (!isAndroid15OrAbove()) {
223
+ statusBarHidden = false;
224
+ navBarHidden = false;
225
+ ensureLegacyOpaqueSystemBars(window);
226
+ }
227
+
212
228
  currentStyle = style;
213
229
  currentColorHex = colorHex;
214
230
 
215
- // Determine icon appearance based on style
216
231
  boolean lightBackground;
217
- if ("LIGHT".equalsIgnoreCase(style)) {
232
+ int statusBarColor;
233
+ int navBarColor;
234
+
235
+ if ("CUSTOM".equalsIgnoreCase(style) && colorHex != null) {
236
+ int color = parseColorOrDefault(colorHex, Color.BLACK);
237
+ lightBackground = isEffectiveLightColor(color);
238
+ statusBarColor = color;
239
+ navBarColor = color;
240
+ } else if ("LIGHT".equalsIgnoreCase(style)) {
218
241
  lightBackground = true;
242
+ statusBarColor = Color.WHITE;
243
+ navBarColor = Color.WHITE;
219
244
  } else if ("DARK".equalsIgnoreCase(style)) {
220
245
  lightBackground = false;
221
- } else if ("CUSTOM".equalsIgnoreCase(style)) {
222
- // CUSTOM: Derive icon color from provided custom color
223
- int parsed = parseColorOrDefault(colorHex, Color.BLACK);
224
- lightBackground = isEffectiveLightColor(parsed);
246
+ statusBarColor = Color.BLACK;
247
+ navBarColor = Color.BLACK;
225
248
  } else {
226
- // Default: Auto-detect based on system theme (follow device theme)
227
249
  boolean isSystemDarkMode = isSystemInDarkMode(activity);
228
250
  lightBackground = !isSystemDarkMode;
251
+ statusBarColor = isSystemDarkMode ? Color.BLACK : Color.WHITE;
252
+ navBarColor = statusBarColor;
229
253
  }
230
254
 
231
- // Apply background colors first
232
- if ("CUSTOM".equalsIgnoreCase(style) && colorHex != null) {
233
- int color = parseColorOrDefault(colorHex, lightBackground ? Color.WHITE : Color.BLACK);
234
- currentStatusBarColor = color;
235
- currentNavBarColor = color;
236
- applyStatusBarBackground(activity, color);
237
- applyNavigationBarBackground(activity, color);
238
- } else if ("LIGHT".equalsIgnoreCase(style)) {
239
- currentStatusBarColor = Color.WHITE;
240
- currentNavBarColor = Color.WHITE;
241
- applyStatusBarBackground(activity, Color.WHITE);
242
- applyNavigationBarBackground(activity, Color.WHITE);
243
- } else if ("DARK".equalsIgnoreCase(style)) {
244
- currentStatusBarColor = Color.BLACK;
245
- currentNavBarColor = Color.BLACK;
246
- applyStatusBarBackground(activity, Color.BLACK);
247
- applyNavigationBarBackground(activity, Color.BLACK);
248
- } else {
249
- // Default: Auto-detect based on system theme
250
- boolean isSystemDarkMode = isSystemInDarkMode(activity);
251
- int themeColor = isSystemDarkMode ? Color.BLACK : Color.WHITE;
252
- currentStatusBarColor = themeColor;
253
- currentNavBarColor = themeColor;
254
- applyStatusBarBackground(activity, themeColor);
255
- applyNavigationBarBackground(activity, themeColor);
256
- }
257
-
258
- // Set icon appearance AFTER background operations.
259
- // On Android 13 (API 33), applyStatusBarBackground triggers
260
- // requestApplyInsets()
261
- // which causes the WindowInsetsController to reset setSystemBarsAppearance.
262
- // Calling setLightStatusBarIcons last ensures the icon style is not overridden.
255
+ currentStatusBarColor = statusBarColor;
256
+ currentNavBarColor = navBarColor;
257
+
258
+ if (!isAndroid15OrAbove()) {
259
+ // Clear any translucent scrim the framework/theme may have applied,
260
+ // and ensure the system draws bar backgrounds with our colors.
261
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
262
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
263
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
264
+ }
265
+
266
+ applyLegacyContrastPolicy(window);
263
267
  setLightStatusBarIcons(window, lightBackground);
268
+ applyStatusBarBackground(activity, statusBarColor);
269
+ applyNavigationBarBackground(activity, navBarColor);
270
+
271
+ applyLegacyContrastPolicy(window);
272
+
273
+ if (!isAndroid15OrAbove()) {
274
+ // Re-apply on the next frame to win against same-tick framework/plugin updates.
275
+ window.getDecorView().post(() -> {
276
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
277
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
278
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
279
+ applyLegacyContrastPolicy(window);
280
+ window.setStatusBarColor(statusBarColor);
281
+ window.setNavigationBarColor(navBarColor);
282
+ setLightStatusBarIcons(window, lightBackground);
283
+ });
284
+ }
264
285
  }
265
286
 
266
- /**
267
- * Get the safe area insets.
268
- * Returns the insets for status bar, navigation bar, and notch areas.
269
- *
270
- * @param activity The activity to get the insets from
271
- * @return A map containing top, bottom, left, and right inset values in dp
272
- * (density-independent pixels)
273
- */
274
287
  public java.util.Map<String, Integer> getSafeAreaInsets(Activity activity) {
275
- Log.d(TAG, "getSafeAreaInsets");
276
288
  java.util.Map<String, Integer> insets = new java.util.HashMap<>();
277
289
  float density = activity.getResources().getDisplayMetrics().density;
278
-
279
290
  View decorView = activity.getWindow().getDecorView();
280
291
  WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(decorView);
281
292
 
282
- if (windowInsets != null) {
283
- // Use WindowInsetsCompat for accurate, type-specific insets across all API
284
- // levels
285
- androidx.core.graphics.Insets statusBars = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars());
286
- androidx.core.graphics.Insets navBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
287
- androidx.core.graphics.Insets displayCutout = windowInsets
288
- .getInsets(WindowInsetsCompat.Type.displayCutout());
289
-
290
- int topPx = Math.max(statusBars.top, displayCutout.top);
291
- int bottomPx = Math.max(navBars.bottom, displayCutout.bottom);
292
- int leftPx = Math.max(navBars.left, displayCutout.left);
293
- int rightPx = Math.max(navBars.right, displayCutout.right);
294
-
295
- // Convert physical pixels to dp (CSS pixels) for the WebView layer
296
- insets.put("top", Math.round(topPx / density));
297
- insets.put("bottom", Math.round(bottomPx / density));
298
- insets.put("left", Math.round(leftPx / density));
299
- insets.put("right", Math.round(rightPx / density));
300
-
301
- Log.d(TAG, "getSafeAreaInsets: topPx=" + topPx + " bottomPx=" + bottomPx
302
- + " density=" + density
303
- + " -> top=" + insets.get("top") + "dp"
304
- + " bottom=" + insets.get("bottom") + "dp"
305
- + " left=" + insets.get("left") + "dp"
306
- + " right=" + insets.get("right") + "dp");
307
- } else {
293
+ if (windowInsets == null) {
308
294
  insets.put("top", 0);
309
295
  insets.put("bottom", 0);
310
296
  insets.put("left", 0);
311
297
  insets.put("right", 0);
312
298
  Log.w(TAG, "getSafeAreaInsets: windowInsets is null");
299
+ return insets;
300
+ }
301
+
302
+ androidx.core.graphics.Insets statusBars = windowInsets
303
+ .getInsetsIgnoringVisibility(WindowInsetsCompat.Type.statusBars());
304
+ androidx.core.graphics.Insets navBars = windowInsets
305
+ .getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars());
306
+ androidx.core.graphics.Insets displayCutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
307
+
308
+ int topPx;
309
+ int bottomPx;
310
+ int leftPx;
311
+ int rightPx;
312
+
313
+ if (isAndroid15OrAbove()) {
314
+ topPx = Math.max(statusBars.top, displayCutout.top);
315
+ bottomPx = Math.max(navBars.bottom, displayCutout.bottom);
316
+ leftPx = Math.max(navBars.left, displayCutout.left);
317
+ rightPx = Math.max(navBars.right, displayCutout.right);
318
+ } else {
319
+ topPx = statusBarHidden ? statusBars.top : 0;
320
+ bottomPx = navBarHidden ? navBars.bottom : 0;
321
+ leftPx = 0;
322
+ rightPx = 0;
313
323
  }
314
324
 
325
+ insets.put("top", Math.round(topPx / density));
326
+ insets.put("bottom", Math.round(bottomPx / density));
327
+ insets.put("left", Math.round(leftPx / density));
328
+ insets.put("right", Math.round(rightPx / density));
329
+
330
+ Log.d(TAG, "getSafeAreaInsets: top=" + insets.get("top") + "px"
331
+ + " bottom=" + insets.get("bottom") + "px"
332
+ + " left=" + insets.get("left") + "px"
333
+ + " right=" + insets.get("right") + "px"
334
+ + " (statusHidden=" + statusBarHidden + ", navHidden=" + navBarHidden + ")");
335
+
315
336
  return insets;
316
337
  }
317
338
 
@@ -319,26 +340,33 @@ public class CapacitorStatusBar extends Plugin {
319
340
  Log.d(TAG, "showNavigationBar: animated=" + animated + ", API=" + Build.VERSION.SDK_INT);
320
341
  Window window = activity.getWindow();
321
342
  View decorView = window.getDecorView();
343
+ navBarHidden = false;
344
+
345
+ if (!isAndroid15OrAbove() && !statusBarHidden) {
346
+ WindowCompat.setDecorFitsSystemWindows(window, true);
347
+ }
322
348
 
323
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
349
+ if (supportsInsetsController()) {
324
350
  WindowInsetsController controller = window.getInsetsController();
325
351
  if (controller != null) {
326
- Log.d(TAG, "showNavigationBar: showing navigation bar (API 30+)");
327
352
  controller.show(WindowInsets.Type.navigationBars());
328
353
  controller.setSystemBarsBehavior(
329
354
  WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
330
- } else {
331
- Log.w(TAG, "showNavigationBar: WindowInsetsController is null");
332
355
  }
333
356
  } else {
334
- Log.d(TAG, "showNavigationBar: showing using system UI flags (API 29)");
335
357
  int flags = decorView.getSystemUiVisibility();
336
358
  flags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
337
359
  flags &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
360
+ flags &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
338
361
  decorView.setSystemUiVisibility(flags);
339
362
  }
340
363
 
341
- // Restore navigation bar background color
364
+ ViewGroup dv = (ViewGroup) decorView;
365
+ View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
366
+ if (navOverlay != null) {
367
+ navOverlay.setVisibility(View.VISIBLE);
368
+ }
369
+
342
370
  applyNavigationBarBackground(activity, currentNavBarColor);
343
371
  }
344
372
 
@@ -346,169 +374,150 @@ public class CapacitorStatusBar extends Plugin {
346
374
  Log.d(TAG, "hideNavigationBar: animation=" + animation + ", API=" + Build.VERSION.SDK_INT);
347
375
  Window window = activity.getWindow();
348
376
  View decorView = window.getDecorView();
377
+ navBarHidden = true;
349
378
 
350
- String animationType = animation != null ? animation.toLowerCase() : "slide";
351
-
352
- if ("fade".equals(animationType)) {
353
- Log.d(TAG, "hideNavigationBar: fade mode - hiding navigation bar with transparent background");
379
+ if (!isAndroid15OrAbove()) {
380
+ WindowCompat.setDecorFitsSystemWindows(window, false);
381
+ }
354
382
 
355
- // Hide the navigation bar icons
356
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
357
- WindowInsetsController controller = window.getInsetsController();
358
- if (controller != null) {
359
- controller.hide(WindowInsets.Type.navigationBars());
360
- controller.setSystemBarsBehavior(
361
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
362
- }
363
- } else {
364
- decorView.setSystemUiVisibility(
365
- decorView.getSystemUiVisibility()
366
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
367
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
368
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
369
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
383
+ if (supportsInsetsController()) {
384
+ WindowInsetsController controller = window.getInsetsController();
385
+ if (controller != null) {
386
+ controller.hide(WindowInsets.Type.navigationBars());
387
+ controller.setSystemBarsBehavior(
388
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
370
389
  }
390
+ } else {
391
+ decorView.setSystemUiVisibility(
392
+ decorView.getSystemUiVisibility()
393
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
394
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
395
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
396
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
397
+ }
371
398
 
372
- // Make background transparent
373
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
374
- ViewGroup dv = (ViewGroup) decorView;
375
- View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
376
- if (navOverlay != null) {
377
- navOverlay.setBackgroundColor(Color.TRANSPARENT);
378
- }
379
- } else {
380
- window.setNavigationBarColor(Color.TRANSPARENT);
381
- }
382
- } else if ("slide".equals(animationType)) {
383
- Log.d(TAG, "hideNavigationBar: slide mode - hiding navigation bar completely");
384
-
385
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
386
- WindowInsetsController controller = window.getInsetsController();
387
- if (controller != null) {
388
- controller.hide(WindowInsets.Type.navigationBars());
389
- controller.setSystemBarsBehavior(
390
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
391
- } else {
392
- Log.w(TAG, "hideNavigationBar: WindowInsetsController is null");
393
- }
394
- } else {
395
- Log.d(TAG, "hideNavigationBar: hiding using system UI flags (API 29)");
396
- decorView.setSystemUiVisibility(
397
- decorView.getSystemUiVisibility()
398
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
399
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
400
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
401
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
402
- }
399
+ ViewGroup dv = (ViewGroup) decorView;
400
+ View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
401
+ if (navOverlay != null) {
402
+ navOverlay.setVisibility(View.GONE);
403
+ }
403
404
 
404
- // Make navigation bar overlay transparent
405
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
406
- ViewGroup dv = (ViewGroup) decorView;
407
- View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
408
- if (navOverlay != null) {
409
- navOverlay.setBackgroundColor(Color.TRANSPARENT);
410
- }
411
- } else {
412
- window.setNavigationBarColor(Color.TRANSPARENT);
413
- }
414
- } else {
415
- Log.w(TAG, "hideNavigationBar: unknown animation type '" + animationType + "', defaulting to slide");
416
- hideNavigationBar(activity, "slide");
405
+ if (!isAndroid15OrAbove()) {
406
+ window.setNavigationBarColor(Color.TRANSPARENT);
417
407
  }
418
408
  }
419
409
 
420
- /**
421
- * Reapply the current style and colors after showing bars.
422
- * This ensures colors are preserved when hiding and then showing.
423
- */
424
410
  private void reapplyCurrentStyle(Activity activity) {
425
- Log.d(TAG, "reapplyCurrentStyle: style=" + currentStyle + ", colorHex=" + currentColorHex);
426
411
  Window window = activity.getWindow();
427
412
 
428
- // Reapply icon appearance
413
+ if (!isAndroid15OrAbove() && !statusBarHidden && !navBarHidden) {
414
+ ensureLegacyOpaqueSystemBars(window);
415
+ }
416
+
417
+ boolean light;
429
418
  if ("LIGHT".equalsIgnoreCase(currentStyle)) {
430
- setLightStatusBarIcons(window, true);
419
+ light = true;
431
420
  } else if ("DARK".equalsIgnoreCase(currentStyle)) {
432
- setLightStatusBarIcons(window, false);
421
+ light = false;
433
422
  } else if ("CUSTOM".equalsIgnoreCase(currentStyle)) {
434
- int parsed = parseColorOrDefault(currentColorHex, Color.BLACK);
435
- boolean isLight = isEffectiveLightColor(parsed);
436
- setLightStatusBarIcons(window, isLight);
423
+ light = isEffectiveLightColor(parseColorOrDefault(currentColorHex, Color.BLACK));
437
424
  } else {
438
- // Default: Auto-detect based on system theme
439
- boolean isSystemDarkMode = isSystemInDarkMode(activity);
440
- setLightStatusBarIcons(window, !isSystemDarkMode);
425
+ light = !isSystemInDarkMode(activity);
441
426
  }
442
427
 
443
- // Reapply colors
428
+ applyLegacyContrastPolicy(window);
429
+ setLightStatusBarIcons(window, light);
444
430
  applyStatusBarBackground(activity, currentStatusBarColor);
445
431
  applyNavigationBarBackground(activity, currentNavBarColor);
432
+
433
+ applyLegacyContrastPolicy(window);
434
+ }
435
+
436
+ private void ensureLegacyOpaqueSystemBars(Window window) {
437
+ WindowCompat.setDecorFitsSystemWindows(window, true);
438
+
439
+ // Ensure bar backgrounds are drawn opaquely on API < 35.
440
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
441
+ | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
442
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
443
+
444
+ if (supportsInsetsController()) {
445
+ WindowInsetsController controller = window.getInsetsController();
446
+ if (controller != null) {
447
+ controller.setSystemBarsBehavior(
448
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
449
+ controller.show(WindowInsets.Type.systemBars());
450
+ }
451
+ }
452
+
453
+ if (!supportsInsetsController()) {
454
+ View decorView = window.getDecorView();
455
+ int flags = decorView.getSystemUiVisibility();
456
+ flags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
457
+ flags &= ~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
458
+ flags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
459
+ flags &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
460
+ flags &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
461
+ decorView.setSystemUiVisibility(flags);
462
+ }
463
+
464
+ applyLegacyContrastPolicy(window);
465
+
466
+ window.getDecorView().requestApplyInsets();
446
467
  }
447
468
 
448
469
  private void setLightStatusBarIcons(Window window, boolean light) {
449
- Log.d(TAG, "setLightStatusBarIcons: light=" + light + ", API=" + Build.VERSION.SDK_INT);
450
470
  View decorView = window.getDecorView();
451
471
 
452
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
453
- // API 30+ - Use WindowInsetsController
472
+ if (supportsInsetsController()) {
454
473
  WindowInsetsController controller = window.getInsetsController();
455
474
  if (controller == null) {
456
- Log.w(TAG, "setLightStatusBarIcons: WindowInsetsController is null");
457
475
  return;
458
476
  }
459
477
  int mask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
460
478
  | WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
461
479
  controller.setSystemBarsAppearance(light ? mask : 0, mask);
462
- Log.d(TAG, "setLightStatusBarIcons: applied using WindowInsetsController (API 30+)");
463
480
  } else {
464
481
  int flags = decorView.getSystemUiVisibility();
465
482
  if (light) {
466
- // Light background -> dark icons
467
483
  flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
468
484
  flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
469
- Log.d(TAG, "setLightStatusBarIcons: set light icons (dark text) (API 29)");
470
485
  } else {
471
- // Dark background -> light icons
472
486
  flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
473
487
  flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
474
- Log.d(TAG, "setLightStatusBarIcons: set dark icons (light text) (API 29)");
475
488
  }
476
489
  decorView.setSystemUiVisibility(flags);
477
490
  }
478
491
  }
479
492
 
480
493
  private void applyStatusBarBackground(Activity activity, @ColorInt int color) {
481
- Log.d(TAG, "applyStatusBarBackground: color=#" + Integer.toHexString(color) + ", API=" + Build.VERSION.SDK_INT);
482
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
483
- // API 31+: edge-to-edge is enabled, setStatusBarColor() is unreliable.
484
- // Use an overlay View drawn on top of the status bar area.
485
- ensureStatusBarOverlay(activity, color);
486
- } else {
487
- // API 29–30: no edge-to-edge, native API works reliably.
488
- activity.getWindow().setStatusBarColor(color);
494
+ Window window = activity.getWindow();
495
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
496
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
497
+ if (supportsContrastControl()) {
498
+ window.setStatusBarContrastEnforced(false);
489
499
  }
500
+ // Paint status bar transparent; our overlay view paints the color so the
501
+ // framework's contrast scrim cannot affect it.
502
+ window.setStatusBarColor(Color.TRANSPARENT);
503
+ ensureStatusBarOverlay(activity, color);
490
504
  }
491
505
 
492
506
  private void applyNavigationBarBackground(Activity activity, @ColorInt int color) {
493
- Log.d(TAG, "applyNavigationBarBackground: color=#" + Integer.toHexString(color) + ", API="
494
- + Build.VERSION.SDK_INT);
495
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
496
- // API 31+: edge-to-edge is enabled, setNavigationBarColor() is unreliable
497
- // (especially gesture navigation which ignores it entirely).
498
- // Use an overlay View drawn at the bottom of the screen.
499
- ensureNavBarOverlay(activity, color);
500
- } else {
501
- // API 29–30: no edge-to-edge, native API works for button navigation.
502
- activity.getWindow().setNavigationBarColor(color);
507
+ Window window = activity.getWindow();
508
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
509
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
510
+ if (supportsContrastControl()) {
511
+ window.setNavigationBarContrastEnforced(false);
503
512
  }
513
+ window.setNavigationBarColor(Color.TRANSPARENT);
514
+ ensureNavBarOverlay(activity, color);
504
515
  }
505
516
 
506
517
  private void ensureStatusBarOverlay(Activity activity, @ColorInt int color) {
507
- Log.d(TAG, "ensureStatusBarOverlay: color=#" + Integer.toHexString(color));
508
518
  ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
509
519
  View existing = decorView.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
510
520
 
511
- // Get current status bar height synchronously for immediate sizing
512
521
  int initialHeight = 0;
513
522
  WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(decorView);
514
523
  if (rootInsets != null) {
@@ -516,7 +525,6 @@ public class CapacitorStatusBar extends Plugin {
516
525
  }
517
526
 
518
527
  if (existing == null) {
519
- Log.d(TAG, "ensureStatusBarOverlay: creating new overlay, initialHeight=" + initialHeight);
520
528
  View overlay = new View(activity);
521
529
  overlay.setTag(STATUS_BAR_OVERLAY_TAG);
522
530
  overlay.setBackgroundColor(color);
@@ -527,38 +535,21 @@ public class CapacitorStatusBar extends Plugin {
527
535
  overlay.setLayoutParams(lp);
528
536
 
529
537
  decorView.addView(overlay);
530
- // Sizing updates are handled by the unified listener in
531
- // ensureEdgeToEdgeConfigured
532
538
  decorView.requestApplyInsets();
533
539
  } else {
534
- Log.d(TAG, "ensureStatusBarOverlay: updating existing overlay");
535
540
  existing.setBackgroundColor(color);
536
- // Update height if it was 0 (listener hadn't fired yet)
537
541
  ViewGroup.LayoutParams params = existing.getLayoutParams();
538
542
  if (params.height == 0 && initialHeight > 0) {
539
543
  params.height = initialHeight;
540
544
  existing.setLayoutParams(params);
541
- Log.d(TAG, "ensureStatusBarOverlay: fixed height to " + initialHeight);
542
545
  }
543
546
  }
544
547
  }
545
548
 
546
- private void removeStatusBarOverlayIfPresent(Activity activity) {
547
- Log.d(TAG, "removeStatusBarOverlayIfPresent");
548
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
549
- View existing = decorView.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
550
- if (existing != null) {
551
- Log.d(TAG, "removeStatusBarOverlayIfPresent: removing overlay");
552
- decorView.removeView(existing);
553
- }
554
- }
555
-
556
549
  private void ensureNavBarOverlay(Activity activity, @ColorInt int color) {
557
- Log.d(TAG, "ensureNavBarOverlay: color=#" + Integer.toHexString(color));
558
550
  ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
559
551
  View existing = decorView.findViewWithTag(NAV_BAR_OVERLAY_TAG);
560
552
 
561
- // Get current nav bar height synchronously for immediate sizing
562
553
  int initialHeight = 0;
563
554
  WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(decorView);
564
555
  if (rootInsets != null) {
@@ -566,7 +557,6 @@ public class CapacitorStatusBar extends Plugin {
566
557
  }
567
558
 
568
559
  if (existing == null) {
569
- Log.d(TAG, "ensureNavBarOverlay: creating new overlay, initialHeight=" + initialHeight);
570
560
  View overlay = new View(activity);
571
561
  overlay.setTag(NAV_BAR_OVERLAY_TAG);
572
562
  overlay.setBackgroundColor(color);
@@ -578,65 +568,24 @@ public class CapacitorStatusBar extends Plugin {
578
568
  overlay.setLayoutParams(lp);
579
569
 
580
570
  decorView.addView(overlay);
581
- // Sizing updates are handled by the unified listener in
582
- // ensureEdgeToEdgeConfigured
583
571
  decorView.requestApplyInsets();
584
572
  } else {
585
- Log.d(TAG, "ensureNavBarOverlay: updating existing overlay");
586
573
  existing.setBackgroundColor(color);
587
- // Update height if it was 0 (listener hadn't fired yet)
588
574
  ViewGroup.LayoutParams params = existing.getLayoutParams();
589
575
  if (params.height == 0 && initialHeight > 0) {
590
576
  params.height = initialHeight;
591
577
  existing.setLayoutParams(params);
592
- Log.d(TAG, "ensureNavBarOverlay: fixed height to " + initialHeight);
593
578
  }
594
579
  }
595
580
  }
596
581
 
597
- private void removeNavBarOverlayIfPresent(Activity activity) {
598
- Log.d(TAG, "removeNavBarOverlayIfPresent");
599
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
600
- View existing = decorView.findViewWithTag(NAV_BAR_OVERLAY_TAG);
601
- if (existing != null) {
602
- Log.d(TAG, "removeNavBarOverlayIfPresent: removing overlay");
603
- decorView.removeView(existing);
604
- }
605
- }
606
-
607
- /**
608
- * Makes the status bar and navigation bar backgrounds transparent.
609
- * This allows content to show through when the bars are hidden.
610
- */
611
- private void makeStatusBarBackgroundTransparent(Activity activity) {
612
- Log.d(TAG, "makeStatusBarBackgroundTransparent: API=" + Build.VERSION.SDK_INT);
613
- Window window = activity.getWindow();
614
- ViewGroup decorView = (ViewGroup) window.getDecorView();
615
-
616
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
617
- // API 31+: edge-to-edge uses overlay view for status bar
618
- View statusBarOverlay = decorView.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
619
- if (statusBarOverlay != null) {
620
- statusBarOverlay.setBackgroundColor(Color.TRANSPARENT);
621
- Log.d(TAG, "makeStatusBarBackgroundTransparent: status bar overlay made transparent");
622
- }
623
- } else {
624
- // API 29–30: no edge-to-edge, native window API
625
- window.setStatusBarColor(Color.TRANSPARENT);
626
- Log.d(TAG, "makeStatusBarBackgroundTransparent: set native status bar color to transparent");
627
- }
628
- }
629
-
630
582
  @ColorInt
631
583
  private int parseColorOrDefault(@Nullable String color, @ColorInt int def) {
632
584
  if (color == null) {
633
- Log.d(TAG, "parseColorOrDefault: color is null, using default");
634
585
  return def;
635
586
  }
636
587
  try {
637
- int parsed = parseHexColor(color);
638
- Log.d(TAG, "parseColorOrDefault: parsed color=" + color + " -> #" + Integer.toHexString(parsed));
639
- return parsed;
588
+ return parseHexColor(color);
640
589
  } catch (IllegalArgumentException ex) {
641
590
  Log.w(TAG, "parseColorOrDefault: invalid color=" + color + ", using default");
642
591
  return def;
@@ -644,12 +593,7 @@ public class CapacitorStatusBar extends Plugin {
644
593
  }
645
594
 
646
595
  /**
647
- * Parse hex color string similar to iOS implementation.
648
- * Handles both 6-digit (#RRGGBB) and 8-digit (#RRGGBBAA) formats.
649
- *
650
- * @param hex The hex color string (with or without # prefix)
651
- * @return The parsed color as an integer
652
- * @throws IllegalArgumentException if the color format is invalid
596
+ * Parse hex color string. Handles #RRGGBB and #RRGGBBAA formats.
653
597
  */
654
598
  private int parseHexColor(String hex) throws IllegalArgumentException {
655
599
  String hexSanitized = hex.trim().replaceFirst("^#", "");
@@ -662,15 +606,12 @@ public class CapacitorStatusBar extends Plugin {
662
606
  long rgb = Long.parseLong(hexSanitized, 16);
663
607
 
664
608
  if (hexSanitized.length() == 6) {
665
- // 6-digit format: #RRGGBB (opaque)
666
609
  return (int) (0xFF000000L | rgb);
667
610
  } else {
668
- // 8-digit format: #RRGGBBAA
669
611
  int r = (int) ((rgb & 0xFF000000L) >> 24);
670
612
  int g = (int) ((rgb & 0x00FF0000L) >> 16);
671
613
  int b = (int) ((rgb & 0x0000FF00L) >> 8);
672
614
  int a = (int) (rgb & 0x000000FFL);
673
-
674
615
  return Color.argb(a, r, g, b);
675
616
  }
676
617
  } catch (NumberFormatException e) {
@@ -678,64 +619,33 @@ public class CapacitorStatusBar extends Plugin {
678
619
  }
679
620
  }
680
621
 
681
- /**
682
- * Calculate effective brightness considering alpha channel.
683
- * For transparent colors, we assume they will be blended over a white
684
- * background.
685
- *
686
- * @param color The color to analyze
687
- * @return true if the effective color appears light, false if dark
688
- */
689
622
  private boolean isEffectiveLightColor(@ColorInt int color) {
690
623
  int alpha = Color.alpha(color);
691
624
 
692
625
  if (alpha == 255) {
693
- // Fully opaque - use standard luminance calculation
694
626
  return ColorUtils.calculateLuminance(color) > 0.5;
695
627
  }
696
628
 
697
- // For transparent colors, calculate effective color when blended over white
698
- // background
699
629
  float alphaRatio = alpha / 255.0f;
700
630
  int r = Color.red(color);
701
631
  int g = Color.green(color);
702
632
  int b = Color.blue(color);
703
633
 
704
- // Blend with white background (255, 255, 255)
705
634
  int effectiveR = (int) (r * alphaRatio + 255 * (1 - alphaRatio));
706
635
  int effectiveG = (int) (g * alphaRatio + 255 * (1 - alphaRatio));
707
636
  int effectiveB = (int) (b * alphaRatio + 255 * (1 - alphaRatio));
708
637
 
709
- int effectiveColor = Color.rgb(effectiveR, effectiveG, effectiveB);
710
- return ColorUtils.calculateLuminance(effectiveColor) > 0.5;
638
+ return ColorUtils.calculateLuminance(Color.rgb(effectiveR, effectiveG, effectiveB)) > 0.5;
711
639
  }
712
640
 
713
- /**
714
- * Apply default status bar style based on system theme.
715
- * Automatically detects if the device is in light or dark mode and applies the
716
- * appropriate style.
717
- *
718
- * @param activity The activity to apply the style to
719
- */
720
641
  public void applyDefaultStyle(Activity activity) {
721
642
  boolean isDarkMode = isSystemInDarkMode(activity);
722
- String style = isDarkMode ? "DARK" : "LIGHT";
723
- Log.d(TAG, "applyDefaultStyle: detected system theme=" + (isDarkMode ? "dark" : "light") + ", applying style="
724
- + style);
725
- setStyle(activity, style, null);
643
+ setStyle(activity, isDarkMode ? "DARK" : "LIGHT", null);
726
644
  }
727
645
 
728
- /**
729
- * Check if the system is currently in dark mode.
730
- *
731
- * @param activity The activity to check the configuration from
732
- * @return true if system is in dark mode, false otherwise
733
- */
734
646
  private boolean isSystemInDarkMode(Activity activity) {
735
647
  int nightModeFlags = activity.getResources().getConfiguration().uiMode
736
648
  & android.content.res.Configuration.UI_MODE_NIGHT_MASK;
737
- boolean isDarkMode = nightModeFlags == android.content.res.Configuration.UI_MODE_NIGHT_YES;
738
- Log.d(TAG, "isSystemInDarkMode: " + isDarkMode);
739
- return isDarkMode;
649
+ return nightModeFlags == android.content.res.Configuration.UI_MODE_NIGHT_YES;
740
650
  }
741
651
  }
@@ -1,7 +1,5 @@
1
1
  package com.cap.plugins.statusbar;
2
2
 
3
- import android.view.View;
4
-
5
3
  import com.getcapacitor.Plugin;
6
4
  import com.getcapacitor.PluginCall;
7
5
  import com.getcapacitor.PluginMethod;
@@ -15,8 +13,7 @@ public class CapacitorStatusBarPlugin extends Plugin {
15
13
  public void load() {
16
14
  super.load();
17
15
  getActivity().runOnUiThread(() -> {
18
- View webView = getBridge().getWebView();
19
- implementation.ensureEdgeToEdgeConfigured(getActivity(), webView);
16
+ implementation.ensureEdgeToEdgeConfigured(getActivity());
20
17
  implementation.applyDefaultStyle(getActivity());
21
18
  });
22
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-plugin-status-bar",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "Capacitor plugin for managing the status bar style, visibility, and color on iOS and Android. Control overlay modes, background colors, and appearance for native mobile applications.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",