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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
*
|
|
52
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
105
|
+
Log.d(TAG, "ensureEdgeToEdgeConfigured: edge-to-edge (API " + Build.VERSION.SDK_INT + ")");
|
|
129
106
|
} else {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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 + ",
|
|
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 (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
164
|
+
// Edge-to-edge so the WebView extends into the freed bar regions.
|
|
165
|
+
WindowCompat.setDecorFitsSystemWindows(window, false);
|
|
179
166
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
405
|
-
|
|
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
|
-
|
|
413
|
+
if (!isAndroid15OrAbove() && !statusBarHidden && !navBarHidden) {
|
|
414
|
+
ensureLegacyOpaqueSystemBars(window);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
boolean light;
|
|
429
418
|
if ("LIGHT".equalsIgnoreCase(currentStyle)) {
|
|
430
|
-
|
|
419
|
+
light = true;
|
|
431
420
|
} else if ("DARK".equalsIgnoreCase(currentStyle)) {
|
|
432
|
-
|
|
421
|
+
light = false;
|
|
433
422
|
} else if ("CUSTOM".equalsIgnoreCase(currentStyle)) {
|
|
434
|
-
|
|
435
|
-
boolean isLight = isEffectiveLightColor(parsed);
|
|
436
|
-
setLightStatusBarIcons(window, isLight);
|
|
423
|
+
light = isEffectiveLightColor(parseColorOrDefault(currentColorHex, Color.BLACK));
|
|
437
424
|
} else {
|
|
438
|
-
|
|
439
|
-
boolean isSystemDarkMode = isSystemInDarkMode(activity);
|
|
440
|
-
setLightStatusBarIcons(window, !isSystemDarkMode);
|
|
425
|
+
light = !isSystemInDarkMode(activity);
|
|
441
426
|
}
|
|
442
427
|
|
|
443
|
-
|
|
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 (
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|