capacitor-plugin-status-bar 2.0.13 → 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
+ }
39
+
40
+ private boolean supportsInsetsController() {
41
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
42
+ }
43
43
 
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().
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,255 +95,244 @@ 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
 
124
- return WindowInsetsCompat.CONSUMED;
101
+ return insets;
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);
134
109
 
135
- Log.d(TAG, "ensureEdgeToEdgeConfigured: contrast enforcement disabled only, API=" + Build.VERSION.SDK_INT);
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);
115
+
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 system bars (API 30+)");
155
- // Show both status and navigation bars together
156
- controller.show(WindowInsets.Type.systemBars());
157
- // Set behavior for transient bars (user can swipe to reveal)
158
- controller.setSystemBarsBehavior(
159
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
160
- } else {
161
- Log.w(TAG, "showStatusBar: WindowInsetsController is null");
162
- }
163
- } else {
164
- // API 29 (Android 10) - Use system UI visibility flags (deprecated but
165
- // necessary)
166
- Log.d(TAG, "showStatusBar: showing using system UI flags (API 29)");
167
- // Set to visible state - clear all immersive flags
168
- decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
132
+ if (!isAndroid15OrAbove()) {
133
+ WindowCompat.setDecorFitsSystemWindows(window, true);
169
134
  }
170
135
 
171
- // Reapply the stored colors and style instead of removing them
172
- reapplyCurrentStyle(activity);
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());
173
142
 
174
- // Restore the overlay backgrounds to their original colors
175
- restoreStatusBarBackground(activity);
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);
152
+ }
176
153
 
177
- // Also show the navigation bar
178
- showNavigationBar(activity, true);
154
+ reapplyCurrentStyle(activity);
179
155
  }
180
156
 
181
157
  public void hideStatusBar(Activity activity) {
158
+ Log.d(TAG, "hideStatusBar: API=" + Build.VERSION.SDK_INT);
182
159
  Window window = activity.getWindow();
183
160
  View decorView = window.getDecorView();
161
+ statusBarHidden = true;
162
+ navBarHidden = true;
184
163
 
185
- // Slide mode: Hide status bar and navigation bar completely (current behavior)
186
- Log.d(TAG, "hideStatusBar: slide mode - hiding bars completely");
164
+ // Edge-to-edge so the WebView extends into the freed bar regions.
165
+ WindowCompat.setDecorFitsSystemWindows(window, false);
187
166
 
188
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
189
- // API 30+ (Android 11+) - Use WindowInsetsController
190
- WindowInsetsController controller = window.getInsetsController();
191
- if (controller != null) {
192
- Log.d(TAG, "hideStatusBar: hiding system bars (API 30+)");
193
- // Hide both status and navigation bars together
194
- controller.hide(WindowInsets.Type.systemBars());
195
- // Set behavior for immersive mode (user can swipe to reveal temporarily)
196
- controller.setSystemBarsBehavior(
197
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
198
- } else {
199
- Log.w(TAG, "hideStatusBar: WindowInsetsController is null");
200
- }
201
- } else {
202
- // API 29 (Android 10) - Use system UI visibility flags (deprecated but
203
- // necessary)
204
- Log.d(TAG, "hideStatusBar: hiding using system UI flags (API 29)");
205
- // Use immersive sticky mode with proper layout flags for Android 10
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()) {
206
207
  decorView.setSystemUiVisibility(
207
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
208
+ decorView.getSystemUiVisibility()
209
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
208
210
  | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
209
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
210
211
  | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
211
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
212
- | 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);
213
215
  }
214
-
215
- // Make the overlay backgrounds transparent so content shows through
216
- makeStatusBarBackgroundTransparent(activity);
217
216
  }
218
217
 
219
218
  public void setStyle(Activity activity, String style, @Nullable String colorHex) {
220
219
  Log.d(TAG, "setStyle: style=" + style + ", colorHex=" + colorHex);
221
220
  Window window = activity.getWindow();
222
221
 
223
- // Store the current style and color for later reapplication
222
+ if (!isAndroid15OrAbove()) {
223
+ statusBarHidden = false;
224
+ navBarHidden = false;
225
+ ensureLegacyOpaqueSystemBars(window);
226
+ }
227
+
224
228
  currentStyle = style;
225
229
  currentColorHex = colorHex;
226
230
 
227
- // Determine icon appearance based on style
228
231
  boolean lightBackground;
229
- 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)) {
230
241
  lightBackground = true;
242
+ statusBarColor = Color.WHITE;
243
+ navBarColor = Color.WHITE;
231
244
  } else if ("DARK".equalsIgnoreCase(style)) {
232
245
  lightBackground = false;
233
- } else if ("CUSTOM".equalsIgnoreCase(style)) {
234
- // CUSTOM: Derive icon color from provided custom color
235
- int parsed = parseColorOrDefault(colorHex, Color.BLACK);
236
- lightBackground = isEffectiveLightColor(parsed);
246
+ statusBarColor = Color.BLACK;
247
+ navBarColor = Color.BLACK;
237
248
  } else {
238
- // Default: Auto-detect based on system theme (follow device theme)
239
249
  boolean isSystemDarkMode = isSystemInDarkMode(activity);
240
250
  lightBackground = !isSystemDarkMode;
251
+ statusBarColor = isSystemDarkMode ? Color.BLACK : Color.WHITE;
252
+ navBarColor = statusBarColor;
241
253
  }
242
254
 
243
- // Apply background colors first
244
- if ("CUSTOM".equalsIgnoreCase(style) && colorHex != null) {
245
- int color = parseColorOrDefault(colorHex, lightBackground ? Color.WHITE : Color.BLACK);
246
- currentStatusBarColor = color;
247
- currentNavBarColor = color;
248
- applyStatusBarBackground(activity, color);
249
- applyNavigationBarBackground(activity, color);
250
- } else if ("LIGHT".equalsIgnoreCase(style)) {
251
- currentStatusBarColor = Color.WHITE;
252
- currentNavBarColor = Color.WHITE;
253
- applyStatusBarBackground(activity, Color.WHITE);
254
- applyNavigationBarBackground(activity, Color.WHITE);
255
- } else if ("DARK".equalsIgnoreCase(style)) {
256
- currentStatusBarColor = Color.BLACK;
257
- currentNavBarColor = Color.BLACK;
258
- applyStatusBarBackground(activity, Color.BLACK);
259
- applyNavigationBarBackground(activity, Color.BLACK);
260
- } else {
261
- // Default: Auto-detect based on system theme
262
- boolean isSystemDarkMode = isSystemInDarkMode(activity);
263
- int themeColor = isSystemDarkMode ? Color.BLACK : Color.WHITE;
264
- currentStatusBarColor = themeColor;
265
- currentNavBarColor = themeColor;
266
- applyStatusBarBackground(activity, themeColor);
267
- applyNavigationBarBackground(activity, themeColor);
268
- }
269
-
270
- // Set icon appearance AFTER background operations.
271
- // On Android 13 (API 33), applyStatusBarBackground triggers
272
- // requestApplyInsets()
273
- // which causes the WindowInsetsController to reset setSystemBarsAppearance.
274
- // Calling setLightStatusBarIcons last ensures the icon style is not overridden.
275
- setLightStatusBarIcons(window, lightBackground);
276
- }
255
+ currentStatusBarColor = statusBarColor;
256
+ currentNavBarColor = navBarColor;
277
257
 
278
- /**
279
- * Set the window background color.
280
- *
281
- * @param activity The activity to apply the background color to
282
- * @param colorHex The hex color string (e.g., "#FFFFFF" or "#FF5733")
283
- */
284
- public void setBackground(Activity activity, @Nullable String colorHex) {
285
- Log.d(TAG, "setBackground: colorHex=" + colorHex);
286
-
287
- if (colorHex == null) {
288
- Log.w(TAG, "setBackground: colorHex is null");
289
- return;
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);
290
264
  }
291
265
 
292
- int color = parseColorOrDefault(colorHex, Color.WHITE);
293
- applyWindowBackground(activity, color);
266
+ applyLegacyContrastPolicy(window);
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
+ }
294
285
  }
295
286
 
296
- /**
297
- * Get the safe area insets.
298
- * Returns the insets for status bar, navigation bar, and notch areas.
299
- *
300
- * @param activity The activity to get the insets from
301
- * @return A map containing top, bottom, left, and right inset values in dp
302
- * (density-independent pixels)
303
- */
304
287
  public java.util.Map<String, Integer> getSafeAreaInsets(Activity activity) {
305
- Log.d(TAG, "getSafeAreaInsets");
306
288
  java.util.Map<String, Integer> insets = new java.util.HashMap<>();
307
289
  float density = activity.getResources().getDisplayMetrics().density;
308
-
309
290
  View decorView = activity.getWindow().getDecorView();
310
291
  WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(decorView);
311
292
 
312
- if (windowInsets != null) {
313
- // Use WindowInsetsCompat for accurate, type-specific insets across all API
314
- // levels
315
- androidx.core.graphics.Insets statusBars = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars());
316
- androidx.core.graphics.Insets navBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
317
- androidx.core.graphics.Insets displayCutout = windowInsets
318
- .getInsets(WindowInsetsCompat.Type.displayCutout());
319
-
320
- int topPx = Math.max(statusBars.top, displayCutout.top);
321
- int bottomPx = Math.max(navBars.bottom, displayCutout.bottom);
322
- int leftPx = Math.max(navBars.left, displayCutout.left);
323
- int rightPx = Math.max(navBars.right, displayCutout.right);
324
-
325
- // Convert physical pixels to dp (CSS pixels) for the WebView layer
326
- insets.put("top", Math.round(topPx / density));
327
- insets.put("bottom", Math.round(bottomPx / density));
328
- insets.put("left", Math.round(leftPx / density));
329
- insets.put("right", Math.round(rightPx / density));
330
-
331
- Log.d(TAG, "getSafeAreaInsets: topPx=" + topPx + " bottomPx=" + bottomPx
332
- + " density=" + density
333
- + " -> top=" + insets.get("top") + "dp"
334
- + " bottom=" + insets.get("bottom") + "dp"
335
- + " left=" + insets.get("left") + "dp"
336
- + " right=" + insets.get("right") + "dp");
337
- } else {
293
+ if (windowInsets == null) {
338
294
  insets.put("top", 0);
339
295
  insets.put("bottom", 0);
340
296
  insets.put("left", 0);
341
297
  insets.put("right", 0);
342
298
  Log.w(TAG, "getSafeAreaInsets: windowInsets is null");
299
+ return insets;
343
300
  }
344
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;
323
+ }
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
+
345
336
  return insets;
346
337
  }
347
338
 
@@ -349,26 +340,33 @@ public class CapacitorStatusBar extends Plugin {
349
340
  Log.d(TAG, "showNavigationBar: animated=" + animated + ", API=" + Build.VERSION.SDK_INT);
350
341
  Window window = activity.getWindow();
351
342
  View decorView = window.getDecorView();
343
+ navBarHidden = false;
352
344
 
353
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
345
+ if (!isAndroid15OrAbove() && !statusBarHidden) {
346
+ WindowCompat.setDecorFitsSystemWindows(window, true);
347
+ }
348
+
349
+ if (supportsInsetsController()) {
354
350
  WindowInsetsController controller = window.getInsetsController();
355
351
  if (controller != null) {
356
- Log.d(TAG, "showNavigationBar: showing navigation bar (API 30+)");
357
352
  controller.show(WindowInsets.Type.navigationBars());
358
353
  controller.setSystemBarsBehavior(
359
354
  WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
360
- } else {
361
- Log.w(TAG, "showNavigationBar: WindowInsetsController is null");
362
355
  }
363
356
  } else {
364
- Log.d(TAG, "showNavigationBar: showing using system UI flags (API 29)");
365
357
  int flags = decorView.getSystemUiVisibility();
366
358
  flags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
367
359
  flags &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
360
+ flags &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
368
361
  decorView.setSystemUiVisibility(flags);
369
362
  }
370
363
 
371
- // 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
+
372
370
  applyNavigationBarBackground(activity, currentNavBarColor);
373
371
  }
374
372
 
@@ -376,169 +374,150 @@ public class CapacitorStatusBar extends Plugin {
376
374
  Log.d(TAG, "hideNavigationBar: animation=" + animation + ", API=" + Build.VERSION.SDK_INT);
377
375
  Window window = activity.getWindow();
378
376
  View decorView = window.getDecorView();
377
+ navBarHidden = true;
379
378
 
380
- String animationType = animation != null ? animation.toLowerCase() : "slide";
381
-
382
- if ("fade".equals(animationType)) {
383
- Log.d(TAG, "hideNavigationBar: fade mode - hiding navigation bar with transparent background");
379
+ if (!isAndroid15OrAbove()) {
380
+ WindowCompat.setDecorFitsSystemWindows(window, false);
381
+ }
384
382
 
385
- // Hide the navigation bar icons
386
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
387
- WindowInsetsController controller = window.getInsetsController();
388
- if (controller != null) {
389
- controller.hide(WindowInsets.Type.navigationBars());
390
- controller.setSystemBarsBehavior(
391
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
392
- }
393
- } else {
394
- decorView.setSystemUiVisibility(
395
- decorView.getSystemUiVisibility()
396
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
397
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
398
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
399
- | 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);
400
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
+ }
401
398
 
402
- // Make background transparent
403
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
404
- ViewGroup dv = (ViewGroup) decorView;
405
- View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
406
- if (navOverlay != null) {
407
- navOverlay.setBackgroundColor(Color.TRANSPARENT);
408
- }
409
- } else {
410
- window.setNavigationBarColor(Color.TRANSPARENT);
411
- }
412
- } else if ("slide".equals(animationType)) {
413
- Log.d(TAG, "hideNavigationBar: slide mode - hiding navigation bar completely");
414
-
415
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
416
- WindowInsetsController controller = window.getInsetsController();
417
- if (controller != null) {
418
- controller.hide(WindowInsets.Type.navigationBars());
419
- controller.setSystemBarsBehavior(
420
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
421
- } else {
422
- Log.w(TAG, "hideNavigationBar: WindowInsetsController is null");
423
- }
424
- } else {
425
- Log.d(TAG, "hideNavigationBar: hiding using system UI flags (API 29)");
426
- decorView.setSystemUiVisibility(
427
- decorView.getSystemUiVisibility()
428
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
429
- | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
430
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
431
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
432
- }
399
+ ViewGroup dv = (ViewGroup) decorView;
400
+ View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
401
+ if (navOverlay != null) {
402
+ navOverlay.setVisibility(View.GONE);
403
+ }
433
404
 
434
- // Make navigation bar overlay transparent
435
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
436
- ViewGroup dv = (ViewGroup) decorView;
437
- View navOverlay = dv.findViewWithTag(NAV_BAR_OVERLAY_TAG);
438
- if (navOverlay != null) {
439
- navOverlay.setBackgroundColor(Color.TRANSPARENT);
440
- }
441
- } else {
442
- window.setNavigationBarColor(Color.TRANSPARENT);
443
- }
444
- } else {
445
- Log.w(TAG, "hideNavigationBar: unknown animation type '" + animationType + "', defaulting to slide");
446
- hideNavigationBar(activity, "slide");
405
+ if (!isAndroid15OrAbove()) {
406
+ window.setNavigationBarColor(Color.TRANSPARENT);
447
407
  }
448
408
  }
449
409
 
450
- /**
451
- * Reapply the current style and colors after showing bars.
452
- * This ensures colors are preserved when hiding and then showing.
453
- */
454
410
  private void reapplyCurrentStyle(Activity activity) {
455
- Log.d(TAG, "reapplyCurrentStyle: style=" + currentStyle + ", colorHex=" + currentColorHex);
456
411
  Window window = activity.getWindow();
457
412
 
458
- // Reapply icon appearance
413
+ if (!isAndroid15OrAbove() && !statusBarHidden && !navBarHidden) {
414
+ ensureLegacyOpaqueSystemBars(window);
415
+ }
416
+
417
+ boolean light;
459
418
  if ("LIGHT".equalsIgnoreCase(currentStyle)) {
460
- setLightStatusBarIcons(window, true);
419
+ light = true;
461
420
  } else if ("DARK".equalsIgnoreCase(currentStyle)) {
462
- setLightStatusBarIcons(window, false);
421
+ light = false;
463
422
  } else if ("CUSTOM".equalsIgnoreCase(currentStyle)) {
464
- int parsed = parseColorOrDefault(currentColorHex, Color.BLACK);
465
- boolean isLight = isEffectiveLightColor(parsed);
466
- setLightStatusBarIcons(window, isLight);
423
+ light = isEffectiveLightColor(parseColorOrDefault(currentColorHex, Color.BLACK));
467
424
  } else {
468
- // Default: Auto-detect based on system theme
469
- boolean isSystemDarkMode = isSystemInDarkMode(activity);
470
- setLightStatusBarIcons(window, !isSystemDarkMode);
425
+ light = !isSystemInDarkMode(activity);
471
426
  }
472
427
 
473
- // Reapply colors
428
+ applyLegacyContrastPolicy(window);
429
+ setLightStatusBarIcons(window, light);
474
430
  applyStatusBarBackground(activity, currentStatusBarColor);
475
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();
476
467
  }
477
468
 
478
469
  private void setLightStatusBarIcons(Window window, boolean light) {
479
- Log.d(TAG, "setLightStatusBarIcons: light=" + light + ", API=" + Build.VERSION.SDK_INT);
480
470
  View decorView = window.getDecorView();
481
471
 
482
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
483
- // API 30+ - Use WindowInsetsController
472
+ if (supportsInsetsController()) {
484
473
  WindowInsetsController controller = window.getInsetsController();
485
474
  if (controller == null) {
486
- Log.w(TAG, "setLightStatusBarIcons: WindowInsetsController is null");
487
475
  return;
488
476
  }
489
477
  int mask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
490
478
  | WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
491
479
  controller.setSystemBarsAppearance(light ? mask : 0, mask);
492
- Log.d(TAG, "setLightStatusBarIcons: applied using WindowInsetsController (API 30+)");
493
480
  } else {
494
481
  int flags = decorView.getSystemUiVisibility();
495
482
  if (light) {
496
- // Light background -> dark icons
497
483
  flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
498
484
  flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
499
- Log.d(TAG, "setLightStatusBarIcons: set light icons (dark text) (API 29)");
500
485
  } else {
501
- // Dark background -> light icons
502
486
  flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
503
487
  flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
504
- Log.d(TAG, "setLightStatusBarIcons: set dark icons (light text) (API 29)");
505
488
  }
506
489
  decorView.setSystemUiVisibility(flags);
507
490
  }
508
491
  }
509
492
 
510
493
  private void applyStatusBarBackground(Activity activity, @ColorInt int color) {
511
- Log.d(TAG, "applyStatusBarBackground: color=#" + Integer.toHexString(color) + ", API=" + Build.VERSION.SDK_INT);
512
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
513
- // API 31+: edge-to-edge is enabled, setStatusBarColor() is unreliable.
514
- // Use an overlay View drawn on top of the status bar area.
515
- ensureStatusBarOverlay(activity, color);
516
- } else {
517
- // API 29–30: no edge-to-edge, native API works reliably.
518
- 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);
519
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);
520
504
  }
521
505
 
522
506
  private void applyNavigationBarBackground(Activity activity, @ColorInt int color) {
523
- Log.d(TAG, "applyNavigationBarBackground: color=#" + Integer.toHexString(color) + ", API="
524
- + Build.VERSION.SDK_INT);
525
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
526
- // API 31+: edge-to-edge is enabled, setNavigationBarColor() is unreliable
527
- // (especially gesture navigation which ignores it entirely).
528
- // Use an overlay View drawn at the bottom of the screen.
529
- ensureNavBarOverlay(activity, color);
530
- } else {
531
- // API 29–30: no edge-to-edge, native API works for button navigation.
532
- 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);
533
512
  }
513
+ window.setNavigationBarColor(Color.TRANSPARENT);
514
+ ensureNavBarOverlay(activity, color);
534
515
  }
535
516
 
536
517
  private void ensureStatusBarOverlay(Activity activity, @ColorInt int color) {
537
- Log.d(TAG, "ensureStatusBarOverlay: color=#" + Integer.toHexString(color));
538
518
  ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
539
519
  View existing = decorView.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
540
520
 
541
- // Get current status bar height synchronously for immediate sizing
542
521
  int initialHeight = 0;
543
522
  WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(decorView);
544
523
  if (rootInsets != null) {
@@ -546,7 +525,6 @@ public class CapacitorStatusBar extends Plugin {
546
525
  }
547
526
 
548
527
  if (existing == null) {
549
- Log.d(TAG, "ensureStatusBarOverlay: creating new overlay, initialHeight=" + initialHeight);
550
528
  View overlay = new View(activity);
551
529
  overlay.setTag(STATUS_BAR_OVERLAY_TAG);
552
530
  overlay.setBackgroundColor(color);
@@ -557,38 +535,21 @@ public class CapacitorStatusBar extends Plugin {
557
535
  overlay.setLayoutParams(lp);
558
536
 
559
537
  decorView.addView(overlay);
560
- // Sizing updates are handled by the unified listener in
561
- // ensureEdgeToEdgeConfigured
562
538
  decorView.requestApplyInsets();
563
539
  } else {
564
- Log.d(TAG, "ensureStatusBarOverlay: updating existing overlay");
565
540
  existing.setBackgroundColor(color);
566
- // Update height if it was 0 (listener hadn't fired yet)
567
541
  ViewGroup.LayoutParams params = existing.getLayoutParams();
568
542
  if (params.height == 0 && initialHeight > 0) {
569
543
  params.height = initialHeight;
570
544
  existing.setLayoutParams(params);
571
- Log.d(TAG, "ensureStatusBarOverlay: fixed height to " + initialHeight);
572
545
  }
573
546
  }
574
547
  }
575
548
 
576
- private void removeStatusBarOverlayIfPresent(Activity activity) {
577
- Log.d(TAG, "removeStatusBarOverlayIfPresent");
578
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
579
- View existing = decorView.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
580
- if (existing != null) {
581
- Log.d(TAG, "removeStatusBarOverlayIfPresent: removing overlay");
582
- decorView.removeView(existing);
583
- }
584
- }
585
-
586
549
  private void ensureNavBarOverlay(Activity activity, @ColorInt int color) {
587
- Log.d(TAG, "ensureNavBarOverlay: color=#" + Integer.toHexString(color));
588
550
  ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
589
551
  View existing = decorView.findViewWithTag(NAV_BAR_OVERLAY_TAG);
590
552
 
591
- // Get current nav bar height synchronously for immediate sizing
592
553
  int initialHeight = 0;
593
554
  WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(decorView);
594
555
  if (rootInsets != null) {
@@ -596,7 +557,6 @@ public class CapacitorStatusBar extends Plugin {
596
557
  }
597
558
 
598
559
  if (existing == null) {
599
- Log.d(TAG, "ensureNavBarOverlay: creating new overlay, initialHeight=" + initialHeight);
600
560
  View overlay = new View(activity);
601
561
  overlay.setTag(NAV_BAR_OVERLAY_TAG);
602
562
  overlay.setBackgroundColor(color);
@@ -608,94 +568,24 @@ public class CapacitorStatusBar extends Plugin {
608
568
  overlay.setLayoutParams(lp);
609
569
 
610
570
  decorView.addView(overlay);
611
- // Sizing updates are handled by the unified listener in
612
- // ensureEdgeToEdgeConfigured
613
571
  decorView.requestApplyInsets();
614
572
  } else {
615
- Log.d(TAG, "ensureNavBarOverlay: updating existing overlay");
616
573
  existing.setBackgroundColor(color);
617
- // Update height if it was 0 (listener hadn't fired yet)
618
574
  ViewGroup.LayoutParams params = existing.getLayoutParams();
619
575
  if (params.height == 0 && initialHeight > 0) {
620
576
  params.height = initialHeight;
621
577
  existing.setLayoutParams(params);
622
- Log.d(TAG, "ensureNavBarOverlay: fixed height to " + initialHeight);
623
578
  }
624
579
  }
625
580
  }
626
581
 
627
- private void removeNavBarOverlayIfPresent(Activity activity) {
628
- Log.d(TAG, "removeNavBarOverlayIfPresent");
629
- ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
630
- View existing = decorView.findViewWithTag(NAV_BAR_OVERLAY_TAG);
631
- if (existing != null) {
632
- Log.d(TAG, "removeNavBarOverlayIfPresent: removing overlay");
633
- decorView.removeView(existing);
634
- }
635
- }
636
-
637
- private void applyWindowBackground(Activity activity, @ColorInt int color) {
638
- Log.d(TAG, "applyWindowBackground: body is always transparent; status/nav bars use overlays/native APIs");
639
- View decorView = activity.getWindow().getDecorView();
640
- decorView.setBackgroundColor(Color.TRANSPARENT);
641
- }
642
-
643
- /**
644
- * Makes the status bar and navigation bar backgrounds transparent.
645
- * This allows content to show through when the bars are hidden.
646
- */
647
- private void makeStatusBarBackgroundTransparent(Activity activity) {
648
- Log.d(TAG, "makeStatusBarBackgroundTransparent: API=" + Build.VERSION.SDK_INT);
649
- Window window = activity.getWindow();
650
- ViewGroup decorView = (ViewGroup) window.getDecorView();
651
-
652
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
653
- // API 31+: edge-to-edge uses overlay views for both bars
654
- View statusBarOverlay = decorView.findViewWithTag(STATUS_BAR_OVERLAY_TAG);
655
- if (statusBarOverlay != null) {
656
- statusBarOverlay.setBackgroundColor(Color.TRANSPARENT);
657
- Log.d(TAG, "makeStatusBarBackgroundTransparent: status bar overlay made transparent");
658
- }
659
- View navBarOverlay = decorView.findViewWithTag(NAV_BAR_OVERLAY_TAG);
660
- if (navBarOverlay != null) {
661
- navBarOverlay.setBackgroundColor(Color.TRANSPARENT);
662
- Log.d(TAG, "makeStatusBarBackgroundTransparent: nav bar overlay made transparent");
663
- }
664
- } else {
665
- // API 29–30: no edge-to-edge, native window API
666
- window.setStatusBarColor(Color.TRANSPARENT);
667
- window.setNavigationBarColor(Color.TRANSPARENT);
668
- Log.d(TAG, "makeStatusBarBackgroundTransparent: set native bar colors to transparent");
669
- }
670
- }
671
-
672
- /**
673
- * Restores the status bar and navigation bar backgrounds to their stored
674
- * colors.
675
- * Called when showing the bars after they were hidden.
676
- */
677
- private void restoreStatusBarBackground(Activity activity) {
678
- Log.d(TAG, "restoreStatusBarBackground: API=" + Build.VERSION.SDK_INT
679
- + ", currentStatusBarColor=#" + Integer.toHexString(currentStatusBarColor)
680
- + ", currentNavBarColor=#" + Integer.toHexString(currentNavBarColor));
681
-
682
- // Restore all backgrounds to their stored colors
683
- applyStatusBarBackground(activity, currentStatusBarColor);
684
- applyNavigationBarBackground(activity, currentNavBarColor);
685
-
686
- Log.d(TAG, "restoreStatusBarBackground: backgrounds restored");
687
- }
688
-
689
582
  @ColorInt
690
583
  private int parseColorOrDefault(@Nullable String color, @ColorInt int def) {
691
584
  if (color == null) {
692
- Log.d(TAG, "parseColorOrDefault: color is null, using default");
693
585
  return def;
694
586
  }
695
587
  try {
696
- int parsed = parseHexColor(color);
697
- Log.d(TAG, "parseColorOrDefault: parsed color=" + color + " -> #" + Integer.toHexString(parsed));
698
- return parsed;
588
+ return parseHexColor(color);
699
589
  } catch (IllegalArgumentException ex) {
700
590
  Log.w(TAG, "parseColorOrDefault: invalid color=" + color + ", using default");
701
591
  return def;
@@ -703,12 +593,7 @@ public class CapacitorStatusBar extends Plugin {
703
593
  }
704
594
 
705
595
  /**
706
- * Parse hex color string similar to iOS implementation.
707
- * Handles both 6-digit (#RRGGBB) and 8-digit (#RRGGBBAA) formats.
708
- *
709
- * @param hex The hex color string (with or without # prefix)
710
- * @return The parsed color as an integer
711
- * @throws IllegalArgumentException if the color format is invalid
596
+ * Parse hex color string. Handles #RRGGBB and #RRGGBBAA formats.
712
597
  */
713
598
  private int parseHexColor(String hex) throws IllegalArgumentException {
714
599
  String hexSanitized = hex.trim().replaceFirst("^#", "");
@@ -721,15 +606,12 @@ public class CapacitorStatusBar extends Plugin {
721
606
  long rgb = Long.parseLong(hexSanitized, 16);
722
607
 
723
608
  if (hexSanitized.length() == 6) {
724
- // 6-digit format: #RRGGBB (opaque)
725
609
  return (int) (0xFF000000L | rgb);
726
610
  } else {
727
- // 8-digit format: #RRGGBBAA
728
611
  int r = (int) ((rgb & 0xFF000000L) >> 24);
729
612
  int g = (int) ((rgb & 0x00FF0000L) >> 16);
730
613
  int b = (int) ((rgb & 0x0000FF00L) >> 8);
731
614
  int a = (int) (rgb & 0x000000FFL);
732
-
733
615
  return Color.argb(a, r, g, b);
734
616
  }
735
617
  } catch (NumberFormatException e) {
@@ -737,64 +619,33 @@ public class CapacitorStatusBar extends Plugin {
737
619
  }
738
620
  }
739
621
 
740
- /**
741
- * Calculate effective brightness considering alpha channel.
742
- * For transparent colors, we assume they will be blended over a white
743
- * background.
744
- *
745
- * @param color The color to analyze
746
- * @return true if the effective color appears light, false if dark
747
- */
748
622
  private boolean isEffectiveLightColor(@ColorInt int color) {
749
623
  int alpha = Color.alpha(color);
750
624
 
751
625
  if (alpha == 255) {
752
- // Fully opaque - use standard luminance calculation
753
626
  return ColorUtils.calculateLuminance(color) > 0.5;
754
627
  }
755
628
 
756
- // For transparent colors, calculate effective color when blended over white
757
- // background
758
629
  float alphaRatio = alpha / 255.0f;
759
630
  int r = Color.red(color);
760
631
  int g = Color.green(color);
761
632
  int b = Color.blue(color);
762
633
 
763
- // Blend with white background (255, 255, 255)
764
634
  int effectiveR = (int) (r * alphaRatio + 255 * (1 - alphaRatio));
765
635
  int effectiveG = (int) (g * alphaRatio + 255 * (1 - alphaRatio));
766
636
  int effectiveB = (int) (b * alphaRatio + 255 * (1 - alphaRatio));
767
637
 
768
- int effectiveColor = Color.rgb(effectiveR, effectiveG, effectiveB);
769
- return ColorUtils.calculateLuminance(effectiveColor) > 0.5;
638
+ return ColorUtils.calculateLuminance(Color.rgb(effectiveR, effectiveG, effectiveB)) > 0.5;
770
639
  }
771
640
 
772
- /**
773
- * Apply default status bar style based on system theme.
774
- * Automatically detects if the device is in light or dark mode and applies the
775
- * appropriate style.
776
- *
777
- * @param activity The activity to apply the style to
778
- */
779
641
  public void applyDefaultStyle(Activity activity) {
780
642
  boolean isDarkMode = isSystemInDarkMode(activity);
781
- String style = isDarkMode ? "DARK" : "LIGHT";
782
- Log.d(TAG, "applyDefaultStyle: detected system theme=" + (isDarkMode ? "dark" : "light") + ", applying style="
783
- + style);
784
- setStyle(activity, style, null);
643
+ setStyle(activity, isDarkMode ? "DARK" : "LIGHT", null);
785
644
  }
786
645
 
787
- /**
788
- * Check if the system is currently in dark mode.
789
- *
790
- * @param activity The activity to check the configuration from
791
- * @return true if system is in dark mode, false otherwise
792
- */
793
646
  private boolean isSystemInDarkMode(Activity activity) {
794
647
  int nightModeFlags = activity.getResources().getConfiguration().uiMode
795
648
  & android.content.res.Configuration.UI_MODE_NIGHT_MASK;
796
- boolean isDarkMode = nightModeFlags == android.content.res.Configuration.UI_MODE_NIGHT_YES;
797
- Log.d(TAG, "isSystemInDarkMode: " + isDarkMode);
798
- return isDarkMode;
649
+ return nightModeFlags == android.content.res.Configuration.UI_MODE_NIGHT_YES;
799
650
  }
800
651
  }