diff --git a/android/src/main/java/com/reactnativenavigation/NavigationActivity.java b/android/src/main/java/com/reactnativenavigation/NavigationActivity.java index 0e5f70a8f9..4d6bdf4419 100644 --- a/android/src/main/java/com/reactnativenavigation/NavigationActivity.java +++ b/android/src/main/java/com/reactnativenavigation/NavigationActivity.java @@ -70,6 +70,16 @@ public void onPostCreate(@Nullable Bundle savedInstanceState) { ViewGroup contentLayout = findViewById(android.R.id.content); navigator.setContentLayout(contentLayout); SystemUiUtils.setupSystemBarBackgrounds(this, contentLayout); + applyThemeStatusBarColor(); + } + + private void applyThemeStatusBarColor() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM + && SystemUiUtils.needsManualStatusBarBackground()) { + //noinspection deprecation + getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT); + SystemUiUtils.setStatusBarColor(getWindow(), android.graphics.Color.TRANSPARENT); + } } @Override diff --git a/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt b/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt index 35c74cb8bd..7df9a31d3e 100644 --- a/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt +++ b/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt @@ -25,6 +25,7 @@ object SystemUiUtils { private const val THREE_BUTTON_NAV_BAR_OPACITY = 0.8f private var statusBarBackgroundView: View? = null + private var statusBarBackgroundActivity: java.lang.ref.WeakReference? = null private var navBarBackgroundView: View? = null @JvmStatic var isEdgeToEdgeActive = false @@ -69,11 +70,11 @@ object SystemUiUtils { * Initializes view-based system bar backgrounds for edge-to-edge. * Call from Activity.onPostCreate after the navigator content layout is set. * - * Status bar: reuses the system's android:id/statusBarBackground DecorView child. + * Status bar: reuses the system's android:id/statusBarBackground DecorView child + * when available. On API 35+ with EdgeToEdge, this view may not exist, so a + * manual view is created in the content layout, sized by status bar insets. * Navigation bar: creates a view in [contentLayout] sized by WindowInsets, * since the system's navigationBarBackground is not available with EdgeToEdge. - * - * Both fall back to deprecated window APIs when the views are unavailable. */ @JvmStatic fun setupSystemBarBackgrounds(activity: Activity, contentLayout: ViewGroup) { @@ -85,9 +86,35 @@ object SystemUiUtils { if (statusBarBackgroundView != null) return val sbView = activity.window.decorView.findViewById(android.R.id.statusBarBackground) if (sbView != null) { - sbView.setBackgroundColor(Color.BLACK) statusBarBackgroundView = sbView + } else { + statusBarBackgroundActivity = java.lang.ref.WeakReference(activity) + } + } + + private fun ensureStatusBarBackgroundView(): View? { + statusBarBackgroundView?.let { return it } + val activity = statusBarBackgroundActivity?.get() ?: return null + val contentLayout = activity.findViewById(android.R.id.content) ?: return null + val view = View(activity) + val params = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, 0, Gravity.TOP + ) + contentLayout.addView(view, params) + statusBarBackgroundView = view + statusBarBackgroundActivity = null + + ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> + val sbHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top + val lp = v.layoutParams + if (lp.height != sbHeight) { + lp.height = sbHeight + v.layoutParams = lp + } + insets } + view.requestApplyInsets() + return view } private fun setupNavigationBarBackground(contentLayout: ViewGroup) { @@ -131,6 +158,14 @@ object SystemUiUtils { return Color.argb(alpha, Color.red(DEFAULT_NAV_BAR_COLOR), Color.green(DEFAULT_NAV_BAR_COLOR), Color.blue(DEFAULT_NAV_BAR_COLOR)) } + /** + * Returns true when the system statusBarBackground view was not found during setup, + * meaning a manual view will be lazily created on the first setStatusBarColor call. + * Use this to decide whether to apply a theme-based initial status bar color. + */ + @JvmStatic + fun needsManualStatusBarBackground(): Boolean = statusBarBackgroundActivity != null + /** * Marks edge-to-edge as active. Call after EdgeToEdge.enable() in the activity. * This flag controls whether navigation bar insets are forwarded to SafeAreaView @@ -148,6 +183,7 @@ object SystemUiUtils { @JvmStatic fun tearDown() { statusBarBackgroundView = null + statusBarBackgroundActivity = null navBarBackgroundView = null isEdgeToEdgeActive = false isThreeButtonNav = false @@ -196,15 +232,26 @@ object SystemUiUtils { Color.green(color), Color.blue(color) ) - setStatusBarColor(window, opaqueColor) + applyStatusBarColor(window, opaqueColor) } /** - * Sets the status bar background color. - * Uses the view-based background when available (edge-to-edge), - * falls back to the deprecated window API on older configurations. + * Sets the status bar background color, lazily creating a manual view on API 35+ + * if the system view wasn't available at setup time. Use this for explicit app-level + * color requests (e.g. from MainActivity or NavigationActivity). */ + @JvmStatic fun setStatusBarColor(window: Window?, color: Int) { + val view = ensureStatusBarBackgroundView() + if (view != null) { + view.setBackgroundColor(color) + } else { + @Suppress("DEPRECATION") + window?.statusBarColor = color + } + } + + private fun applyStatusBarColor(window: Window?, color: Int) { statusBarBackgroundView?.setBackgroundColor(color) ?: run { @Suppress("DEPRECATION") window?.statusBarColor = color