From ce44bfcd7852d353befe923ae9e4d1cd891eba79 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 24 Feb 2026 13:31:14 +0100 Subject: [PATCH 1/7] Create a WindowEventListener interface --- .../facebook/react/bridge/ReactContext.java | 35 +++++++++++++++++++ .../react/bridge/WindowEventListener.kt | 29 +++++++++++++++ .../react/uimanager/ThemedReactContext.kt | 18 ++++++++++ 3 files changed, 82 insertions(+) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowEventListener.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index dc9acd738dd7..f4d6102981cb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -15,6 +15,7 @@ import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; @@ -48,6 +49,8 @@ public interface RCTDeviceEventEmitter extends JavaScriptModule { new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet mActivityEventListeners = new CopyOnWriteArraySet<>(); + private final CopyOnWriteArraySet mWindowEventListeners = + new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet mWindowFocusEventListeners = new CopyOnWriteArraySet<>(); private final ScrollEndedListeners mScrollEndedListeners = new ScrollEndedListeners(); @@ -246,6 +249,14 @@ public void removeActivityEventListener(ActivityEventListener listener) { mActivityEventListeners.remove(listener); } + public void addWindowEventListener(WindowEventListener listener) { + mWindowEventListeners.add(listener); + } + + public void removeWindowEventListener(WindowEventListener listener) { + mWindowEventListeners.remove(listener); + } + public void addWindowFocusChangeListener(WindowFocusChangeListener listener) { mWindowFocusEventListeners.add(listener); } @@ -356,6 +367,30 @@ public void onActivityResult( } } + @ThreadConfined(UI) + public void onWindowCreated(Window window) { + UiThreadUtil.assertOnUiThread(); + for (WindowEventListener listener : mWindowEventListeners) { + try { + listener.onWindowCreated(window); + } catch (RuntimeException e) { + handleException(e); + } + } + } + + @ThreadConfined(UI) + public void onWindowDestroyed(Window window) { + UiThreadUtil.assertOnUiThread(); + for (WindowEventListener listener : mWindowEventListeners) { + try { + listener.onWindowDestroyed(window); + } catch (RuntimeException e) { + handleException(e); + } + } + } + @ThreadConfined(UI) public void onWindowFocusChange(boolean hasFocus) { UiThreadUtil.assertOnUiThread(); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowEventListener.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowEventListener.kt new file mode 100644 index 000000000000..3b8b1e59b847 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowEventListener.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import android.view.Window + +/** + * Listener for receiving window creation and destruction events. + * + * This allows modules to react to new windows being added or removed, such as Dialog windows + * created by Modal components. Modules like StatusBarModule can implement this interface to apply + * their configuration to all active windows. + * + * Third-party libraries can both implement this listener and emit window events through + * [ReactContext.onWindowCreated] and [ReactContext.onWindowDestroyed]. + */ +public interface WindowEventListener { + + /** Called when a new [Window] is created (e.g. a Dialog window for a Modal). */ + public fun onWindowCreated(window: Window) + + /** Called when a [Window] is about to be destroyed (e.g. a Dialog window being dismissed). */ + public fun onWindowDestroyed(window: Window) +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt index 58fc5c8527b5..e581b0b3407f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt @@ -11,6 +11,7 @@ package com.facebook.react.uimanager import android.app.Activity import android.content.Context +import android.view.Window import com.facebook.react.bridge.Callback import com.facebook.react.bridge.CatalystInstance import com.facebook.react.bridge.JavaScriptContextHolder @@ -20,6 +21,7 @@ import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ScrollEndedListeners +import com.facebook.react.bridge.WindowEventListener import com.facebook.react.bridge.UIManager import com.facebook.react.common.annotations.internal.LegacyArchitecture import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder @@ -67,6 +69,22 @@ public class ThemedReactContext( reactApplicationContext.removeLifecycleEventListener(listener) } + override fun addWindowEventListener(listener: WindowEventListener) { + reactApplicationContext.addWindowEventListener(listener) + } + + override fun removeWindowEventListener(listener: WindowEventListener) { + reactApplicationContext.removeWindowEventListener(listener) + } + + override fun onWindowCreated(window: Window) { + reactApplicationContext.onWindowCreated(window) + } + + override fun onWindowDestroyed(window: Window) { + reactApplicationContext.onWindowDestroyed(window) + } + override fun hasCurrentActivity(): Boolean = reactApplicationContext.hasCurrentActivity() override fun getCurrentActivity(): Activity? = reactApplicationContext.getCurrentActivity() From 05c6732ac4e51a443d47467d0c651d2d5813a17d Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 24 Feb 2026 13:32:42 +0100 Subject: [PATCH 2/7] Emit onWindowCreated / onWindowDestroyed on Modal creation / dismiss --- .../java/com/facebook/react/views/modal/ReactModalHostView.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index d634b601db6b..f6cadc94336b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -196,6 +196,9 @@ public class ReactModalHostView(context: ThemedReactContext) : UiThreadUtil.assertOnUiThread() dialog?.let { nonNullDialog -> + nonNullDialog.window?.let { window -> + (context as ThemedReactContext).onWindowDestroyed(window) + } if (nonNullDialog.isShowing) { val dialogContext = ContextUtils.findContextOfType(nonNullDialog.context, Activity::class.java) @@ -341,6 +344,7 @@ public class ReactModalHostView(context: ThemedReactContext) : newDialog.show() updateSystemAppearance() window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) + (context as ThemedReactContext).onWindowCreated(window) } } From e984171fdd71877b124cb9e654d7bdbabc88c2ca Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 24 Feb 2026 15:13:15 +0100 Subject: [PATCH 3/7] Allow StatusBar to update registered Window instances statusBar --- .../modules/statusbar/StatusBarModule.kt | 143 ++++++++++-------- .../facebook/react/views/view/WindowUtil.kt | 47 ++++++ 2 files changed, 123 insertions(+), 67 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 1e773ba83d61..4e411b437e5a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -7,30 +7,79 @@ package com.facebook.react.modules.statusbar -import android.animation.ArgbEvaluator -import android.animation.ValueAnimator -import android.os.Build -import android.view.View -import android.view.WindowInsetsController +import android.graphics.Color +import android.view.Window import android.view.WindowManager +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat import com.facebook.common.logging.FLog import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec -import com.facebook.react.bridge.GuardedRunnable import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.bridge.WindowEventListener import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx import com.facebook.react.uimanager.PixelUtil import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn +import com.facebook.react.views.view.setStatusBarColor +import com.facebook.react.views.view.setStatusBarStyle import com.facebook.react.views.view.setStatusBarTranslucency import com.facebook.react.views.view.setStatusBarVisibility /** [NativeModule] that allows changing the appearance of the status bar. */ @ReactModule(name = NativeStatusBarManagerAndroidSpec.NAME) internal class StatusBarModule(reactContext: ReactApplicationContext?) : - NativeStatusBarManagerAndroidSpec(reactContext) { + NativeStatusBarManagerAndroidSpec(reactContext), WindowEventListener { + + private val extrasWindows = mutableSetOf() + + private object CurrentState { + var color = Color.BLACK + var hidden = false + var style = "default" + var translucent = false + } + + init { + reactApplicationContext.addWindowEventListener(this) + reactApplicationContext.currentActivity?.window?.let { readInitialState(it) } + } + + override fun invalidate() { + super.invalidate() + reactApplicationContext.removeWindowEventListener(this) + } + + override fun onWindowCreated(window: Window) { + extrasWindows.add(window) + + window.setStatusBarColor(CurrentState.color, false) + window.setStatusBarVisibility(CurrentState.hidden) + window.setStatusBarStyle(CurrentState.style) + window.setStatusBarTranslucency(CurrentState.translucent) + } + + override fun onWindowDestroyed(window: Window) { + extrasWindows.remove(window) + } + + @Suppress("DEPRECATION") + fun readInitialState(window: Window) { + val controller = WindowCompat.getInsetsController(window, window.decorView) + val insets = ViewCompat.getRootWindowInsets(window.decorView) + val visible = insets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true + + CurrentState.apply { + color = window.statusBarColor + hidden = !visible + style = if (controller.isAppearanceLightStatusBars) "dark-content" else "light-content" + translucent = + (window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0 + } + } @Suppress("DEPRECATION") override fun getTypedExportedConstants(): Map { @@ -45,9 +94,8 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) } - @Suppress("DEPRECATION") override fun setColor(colorDouble: Double, animated: Boolean) { - val color = colorDouble.toInt() + CurrentState.color = colorDouble.toInt() val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -63,28 +111,14 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - UiThreadUtil.runOnUiThread( - object : GuardedRunnable(reactApplicationContext) { - override fun runGuarded() { - val window = activity.window ?: return - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) - if (animated) { - val curColor = window.statusBarColor - val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), curColor, color) - colorAnimation.addUpdateListener { animator -> - activity.window?.statusBarColor = (animator.animatedValue as Int) - } - colorAnimation.setDuration(300).startDelay = 0 - colorAnimation.start() - } else { - window.statusBarColor = color - } - } - } - ) + UiThreadUtil.runOnUiThread { + activity.window?.setStatusBarColor(CurrentState.color, animated) + extrasWindows.forEach { it.setStatusBarColor(CurrentState.color, animated) } + } } override fun setTranslucent(translucent: Boolean) { + CurrentState.translucent = translucent val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -100,16 +134,14 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - UiThreadUtil.runOnUiThread( - object : GuardedRunnable(reactApplicationContext) { - override fun runGuarded() { - activity.window?.setStatusBarTranslucency(translucent) - } - } - ) + UiThreadUtil.runOnUiThread { + activity.window?.setStatusBarTranslucency(CurrentState.translucent) + extrasWindows.forEach { it.setStatusBarTranslucency(CurrentState.translucent) } + } } override fun setHidden(hidden: Boolean) { + CurrentState.hidden = hidden val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -118,11 +150,14 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - UiThreadUtil.runOnUiThread { activity.window?.setStatusBarVisibility(hidden) } + UiThreadUtil.runOnUiThread { + activity.window?.setStatusBarVisibility(CurrentState.hidden) + extrasWindows.forEach { it.setStatusBarVisibility(CurrentState.hidden) } + } } - @Suppress("DEPRECATION") override fun setStyle(style: String?) { + CurrentState.style = style ?: "default" val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -131,36 +166,10 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - UiThreadUtil.runOnUiThread( - Runnable { - val window = activity.window ?: return@Runnable - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) { - val insetsController = window.insetsController ?: return@Runnable - if ("dark-content" == style) { - // dark-content means dark icons on a light status bar - insetsController.setSystemBarsAppearance( - WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, - WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, - ) - } else { - insetsController.setSystemBarsAppearance( - 0, - WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, - ) - } - } else { - val decorView = window.decorView - var systemUiVisibilityFlags = decorView.systemUiVisibility - systemUiVisibilityFlags = - if ("dark-content" == style) { - systemUiVisibilityFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - } else { - systemUiVisibilityFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() - } - decorView.systemUiVisibility = systemUiVisibilityFlags - } - } - ) + UiThreadUtil.runOnUiThread { + activity.window?.setStatusBarStyle(CurrentState.style) + extrasWindows.forEach { it.setStatusBarStyle(CurrentState.style) } + } } companion object { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 0cff3bc458d8..43429d9fd0ac 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -7,9 +7,13 @@ package com.facebook.react.views.view +import android.animation.ArgbEvaluator +import android.animation.ValueAnimator import android.graphics.Color import android.os.Build +import android.view.View import android.view.Window +import android.view.WindowInsetsController import android.view.WindowManager import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat @@ -37,6 +41,49 @@ public fun setEdgeToEdgeFeatureFlagOn() { isEdgeToEdgeFeatureFlagOn = true } +@Suppress("DEPRECATION") +internal fun Window.setStatusBarColor(color: Int, animated: Boolean) { + addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + if (animated) { + val curColor = statusBarColor + val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), curColor, color) + colorAnimation.addUpdateListener { animator -> + statusBarColor = (animator.animatedValue as Int) + } + colorAnimation.setDuration(300).startDelay = 0 + colorAnimation.start() + } else { + statusBarColor = color + } +} + +@Suppress("DEPRECATION") +internal fun Window.setStatusBarStyle(style: String?) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) { + if ("dark-content" == style) { + // dark-content means dark icons on a light status bar + insetsController?.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, + ) + } else { + insetsController?.setSystemBarsAppearance( + 0, + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, + ) + } + } else { + var systemUiVisibilityFlags = decorView.systemUiVisibility + systemUiVisibilityFlags = + if ("dark-content" == style) { + systemUiVisibilityFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } else { + systemUiVisibilityFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + } + decorView.systemUiVisibility = systemUiVisibilityFlags + } +} + @Suppress("DEPRECATION") internal fun Window.setStatusBarTranslucency(isTranslucent: Boolean) { // If the status bar is translucent hook into the window insets calculations From 5cd64152814b4b6d1371818db1f25fb78f683f02 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 24 Feb 2026 18:05:16 +0100 Subject: [PATCH 4/7] Rename readInitialState --- .../modules/statusbar/StatusBarModule.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 4e411b437e5a..4d0e0fbd8222 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -45,7 +45,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : init { reactApplicationContext.addWindowEventListener(this) - reactApplicationContext.currentActivity?.window?.let { readInitialState(it) } + reactApplicationContext.currentActivity?.window?.let { initializeState(it) } } override fun invalidate() { @@ -53,21 +53,8 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : reactApplicationContext.removeWindowEventListener(this) } - override fun onWindowCreated(window: Window) { - extrasWindows.add(window) - - window.setStatusBarColor(CurrentState.color, false) - window.setStatusBarVisibility(CurrentState.hidden) - window.setStatusBarStyle(CurrentState.style) - window.setStatusBarTranslucency(CurrentState.translucent) - } - - override fun onWindowDestroyed(window: Window) { - extrasWindows.remove(window) - } - @Suppress("DEPRECATION") - fun readInitialState(window: Window) { + fun initializeState(window: Window) { val controller = WindowCompat.getInsetsController(window, window.decorView) val insets = ViewCompat.getRootWindowInsets(window.decorView) val visible = insets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true @@ -81,6 +68,19 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : } } + override fun onWindowCreated(window: Window) { + extrasWindows.add(window) + + window.setStatusBarColor(CurrentState.color, false) + window.setStatusBarVisibility(CurrentState.hidden) + window.setStatusBarStyle(CurrentState.style) + window.setStatusBarTranslucency(CurrentState.translucent) + } + + override fun onWindowDestroyed(window: Window) { + extrasWindows.remove(window) + } + @Suppress("DEPRECATION") override fun getTypedExportedConstants(): Map { val currentActivity = reactApplicationContext.currentActivity From 73c2c9f48c6105e75de952317e60ca85edbb8c32 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 24 Feb 2026 21:38:25 +0100 Subject: [PATCH 5/7] Remove CurrentState --- .../modules/statusbar/StatusBarModule.kt | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 4d0e0fbd8222..5da92e21822b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -7,7 +7,6 @@ package com.facebook.react.modules.statusbar -import android.graphics.Color import android.view.Window import android.view.WindowManager import androidx.core.view.ViewCompat @@ -36,16 +35,8 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : private val extrasWindows = mutableSetOf() - private object CurrentState { - var color = Color.BLACK - var hidden = false - var style = "default" - var translucent = false - } - init { reactApplicationContext.addWindowEventListener(this) - reactApplicationContext.currentActivity?.window?.let { initializeState(it) } } override fun invalidate() { @@ -54,27 +45,25 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : } @Suppress("DEPRECATION") - fun initializeState(window: Window) { - val controller = WindowCompat.getInsetsController(window, window.decorView) - val insets = ViewCompat.getRootWindowInsets(window.decorView) - val visible = insets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true - - CurrentState.apply { - color = window.statusBarColor - hidden = !visible - style = if (controller.isAppearanceLightStatusBars) "dark-content" else "light-content" - translucent = - (window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0 - } - } - override fun onWindowCreated(window: Window) { extrasWindows.add(window) - window.setStatusBarColor(CurrentState.color, false) - window.setStatusBarVisibility(CurrentState.hidden) - window.setStatusBarStyle(CurrentState.style) - window.setStatusBarTranslucency(CurrentState.translucent) + UiThreadUtil.runOnUiThread { + val controller = WindowCompat.getInsetsController(window, window.decorView) + val insets = ViewCompat.getRootWindowInsets(window.decorView) + val style = if (controller.isAppearanceLightStatusBars) "dark-content" else "light-content" + val visible = insets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true + + window.setStatusBarStyle(style) + window.setStatusBarVisibility(!visible) + + if (!isEdgeToEdgeFeatureFlagOn) { + window.setStatusBarColor(window.statusBarColor, false) + window.setStatusBarTranslucency( + (window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0 + ) + } + } } override fun onWindowDestroyed(window: Window) { @@ -95,7 +84,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : } override fun setColor(colorDouble: Double, animated: Boolean) { - CurrentState.color = colorDouble.toInt() + val color = colorDouble.toInt() val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -112,13 +101,12 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : return } UiThreadUtil.runOnUiThread { - activity.window?.setStatusBarColor(CurrentState.color, animated) - extrasWindows.forEach { it.setStatusBarColor(CurrentState.color, animated) } + activity.window?.setStatusBarColor(color, animated) + extrasWindows.forEach { it.setStatusBarColor(color, animated) } } } override fun setTranslucent(translucent: Boolean) { - CurrentState.translucent = translucent val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -135,13 +123,12 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : return } UiThreadUtil.runOnUiThread { - activity.window?.setStatusBarTranslucency(CurrentState.translucent) - extrasWindows.forEach { it.setStatusBarTranslucency(CurrentState.translucent) } + activity.window?.setStatusBarTranslucency(translucent) + extrasWindows.forEach { it.setStatusBarTranslucency(translucent) } } } override fun setHidden(hidden: Boolean) { - CurrentState.hidden = hidden val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -151,13 +138,12 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : return } UiThreadUtil.runOnUiThread { - activity.window?.setStatusBarVisibility(CurrentState.hidden) - extrasWindows.forEach { it.setStatusBarVisibility(CurrentState.hidden) } + activity.window?.setStatusBarVisibility(hidden) + extrasWindows.forEach { it.setStatusBarVisibility(hidden) } } } override fun setStyle(style: String?) { - CurrentState.style = style ?: "default" val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( @@ -167,8 +153,8 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : return } UiThreadUtil.runOnUiThread { - activity.window?.setStatusBarStyle(CurrentState.style) - extrasWindows.forEach { it.setStatusBarStyle(CurrentState.style) } + activity.window?.setStatusBarStyle(style) + extrasWindows.forEach { it.setStatusBarStyle(style) } } } From fb974acb1c15d08c8e78da168548dd8ff0bcdc6e Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 25 Feb 2026 09:19:09 +0100 Subject: [PATCH 6/7] Don't update status bar color / translucent --- .../react/modules/statusbar/StatusBarModule.kt | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 5da92e21822b..383b22b91cb8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -8,7 +8,6 @@ package com.facebook.react.modules.statusbar import android.view.Window -import android.view.WindowManager import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -56,13 +55,6 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : window.setStatusBarStyle(style) window.setStatusBarVisibility(!visible) - - if (!isEdgeToEdgeFeatureFlagOn) { - window.setStatusBarColor(window.statusBarColor, false) - window.setStatusBarTranslucency( - (window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0 - ) - } } } @@ -100,10 +92,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - UiThreadUtil.runOnUiThread { - activity.window?.setStatusBarColor(color, animated) - extrasWindows.forEach { it.setStatusBarColor(color, animated) } - } + UiThreadUtil.runOnUiThread { activity.window?.setStatusBarColor(color, animated) } } override fun setTranslucent(translucent: Boolean) { @@ -122,10 +111,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - UiThreadUtil.runOnUiThread { - activity.window?.setStatusBarTranslucency(translucent) - extrasWindows.forEach { it.setStatusBarTranslucency(translucent) } - } + UiThreadUtil.runOnUiThread { activity.window?.setStatusBarTranslucency(translucent) } } override fun setHidden(hidden: Boolean) { From 4fd48e88bbf572a3c87fbca3a6c5f3ecf3b54634 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 25 Feb 2026 09:26:50 +0100 Subject: [PATCH 7/7] Remove ress("DEPRECATION") --- .../java/com/facebook/react/modules/statusbar/StatusBarModule.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 383b22b91cb8..d864142ad8cf 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -43,7 +43,6 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : reactApplicationContext.removeWindowEventListener(this) } - @Suppress("DEPRECATION") override fun onWindowCreated(window: Window) { extrasWindows.add(window)