Skip to content

Commit 6614fe7

Browse files
zoontekmeta-codesync[bot]
authored andcommitted
Fix StatusBar modifications not applying to Modal windows on Android (#56059)
Summary: When a `Modal` is displayed on Android, it creates a new `Dialog` with its own `Window`. Modules like `StatusBarModule` only apply their configuration to the main Activity's window, so status bar changes (style, visibility) are not reflected in Modal windows. This PR: - Implement `ExtraWindowEventListener` in `StatusBarModule` to track extra windows - Extract `Window` extension `setStatusBarStyle` function into `WindowUtil.kt` for reuse - Omit status bar color and translucency sync, as those functions have no effect when the app is edge-to-edge This also fixes a race condition that occurs when opening a Modal and changing the status bar state at the same time. ## Related issue - zoontek/react-native-edge-to-edge#98 ## Changelog: [ANDROID] [FIXED] - StatusBar configuration now applies to Modal windows, fixing visual inconsistencies Pull Request resolved: #56059 Test Plan: 1. Open the RNTester app on Android 2. Set status bar style or visibility using the StatusBar API 3. Open a Modal - verify the status bar appearance is preserved 4. Change the status bar style / visibility while the Modal is open - verify it updates on the Modal window too 5. Dismiss the Modal - verify the status bar appearance is still correct https://github.com/user-attachments/assets/6a1c9a7e-6100-422b-8a6f-184be8b6a007 Reviewed By: javache Differential Revision: D101312951 Pulled By: alanleedev fbshipit-source-id: 0dca9995a4eb977e2c4e4cef3753d6069f52535b
1 parent 3982315 commit 6614fe7

2 files changed

Lines changed: 74 additions & 36 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,60 @@ package com.facebook.react.modules.statusbar
99

1010
import android.animation.ArgbEvaluator
1111
import android.animation.ValueAnimator
12-
import android.os.Build
13-
import android.view.View
14-
import android.view.WindowInsetsController
12+
import android.view.Window
1513
import android.view.WindowManager
14+
import androidx.core.view.ViewCompat
15+
import androidx.core.view.WindowCompat
16+
import androidx.core.view.WindowInsetsCompat
1617
import com.facebook.common.logging.FLog
1718
import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec
1819
import com.facebook.react.bridge.GuardedRunnable
1920
import com.facebook.react.bridge.NativeModule
2021
import com.facebook.react.bridge.ReactApplicationContext
2122
import com.facebook.react.bridge.UiThreadUtil
2223
import com.facebook.react.common.ReactConstants
24+
import com.facebook.react.interfaces.ExtraWindowEventListener
2325
import com.facebook.react.module.annotations.ReactModule
2426
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
2527
import com.facebook.react.uimanager.PixelUtil
2628
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
29+
import com.facebook.react.views.view.setStatusBarStyle
2730
import com.facebook.react.views.view.setStatusBarTranslucency
2831
import com.facebook.react.views.view.setStatusBarVisibility
32+
import java.util.Collections
33+
import java.util.WeakHashMap
2934

3035
/** [NativeModule] that allows changing the appearance of the status bar. */
3136
@ReactModule(name = NativeStatusBarManagerAndroidSpec.NAME)
3237
internal class StatusBarModule(reactContext: ReactApplicationContext?) :
33-
NativeStatusBarManagerAndroidSpec(reactContext) {
38+
NativeStatusBarManagerAndroidSpec(reactContext), ExtraWindowEventListener {
39+
40+
init {
41+
reactApplicationContext.addExtraWindowEventListener(this)
42+
}
43+
44+
override fun invalidate() {
45+
super.invalidate()
46+
reactApplicationContext.removeExtraWindowEventListener(this)
47+
}
48+
49+
override fun onExtraWindowCreate(window: Window) {
50+
extraWindows.add(window)
51+
52+
reactApplicationContext.currentActivity?.window?.let { activityWindow ->
53+
val controller = WindowCompat.getInsetsController(activityWindow, activityWindow.decorView)
54+
val insets = ViewCompat.getRootWindowInsets(activityWindow.decorView)
55+
val style = if (controller.isAppearanceLightStatusBars) "dark-content" else "light-content"
56+
val visible = insets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true
57+
58+
window.setStatusBarStyle(style)
59+
window.setStatusBarVisibility(!visible)
60+
}
61+
}
62+
63+
override fun onExtraWindowDestroy(window: Window) {
64+
extraWindows.remove(window)
65+
}
3466

3567
@Suppress("DEPRECATION")
3668
override fun getTypedExportedConstants(): Map<String, Any> {
@@ -118,10 +150,12 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
118150
)
119151
return
120152
}
121-
UiThreadUtil.runOnUiThread { activity.window?.setStatusBarVisibility(hidden) }
153+
UiThreadUtil.runOnUiThread {
154+
activity.window?.setStatusBarVisibility(hidden)
155+
extraWindows.forEach { it.setStatusBarVisibility(hidden) }
156+
}
122157
}
123158

124-
@Suppress("DEPRECATION")
125159
override fun setStyle(style: String?) {
126160
val activity = reactApplicationContext.getCurrentActivity()
127161
if (activity == null) {
@@ -131,41 +165,16 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
131165
)
132166
return
133167
}
134-
UiThreadUtil.runOnUiThread(
135-
Runnable {
136-
val window = activity.window ?: return@Runnable
137-
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
138-
val insetsController = window.insetsController ?: return@Runnable
139-
if ("dark-content" == style) {
140-
// dark-content means dark icons on a light status bar
141-
insetsController.setSystemBarsAppearance(
142-
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
143-
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
144-
)
145-
} else {
146-
insetsController.setSystemBarsAppearance(
147-
0,
148-
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
149-
)
150-
}
151-
} else {
152-
val decorView = window.decorView
153-
var systemUiVisibilityFlags = decorView.systemUiVisibility
154-
systemUiVisibilityFlags =
155-
if ("dark-content" == style) {
156-
systemUiVisibilityFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
157-
} else {
158-
systemUiVisibilityFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
159-
}
160-
decorView.systemUiVisibility = systemUiVisibilityFlags
161-
}
162-
}
163-
)
168+
UiThreadUtil.runOnUiThread {
169+
activity.window?.setStatusBarStyle(style)
170+
extraWindows.forEach { it.setStatusBarStyle(style) }
171+
}
164172
}
165173

166174
companion object {
167175
private const val HEIGHT_KEY = "HEIGHT"
168176
private const val DEFAULT_BACKGROUND_COLOR_KEY = "DEFAULT_BACKGROUND_COLOR"
169177
const val NAME: String = NativeStatusBarManagerAndroidSpec.NAME
178+
private val extraWindows = Collections.newSetFromMap<Window>(WeakHashMap())
170179
}
171180
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ package com.facebook.react.views.view
1010
import android.app.Activity
1111
import android.graphics.Color
1212
import android.os.Build
13+
import android.view.View
1314
import android.view.Window
15+
import android.view.WindowInsetsController
1416
import android.view.WindowManager
1517
import androidx.core.view.ViewCompat
1618
import androidx.core.view.WindowCompat
@@ -93,6 +95,33 @@ internal fun Window.setStatusBarVisibility(isHidden: Boolean) {
9395
}
9496
}
9597

98+
@Suppress("DEPRECATION")
99+
internal fun Window.setStatusBarStyle(style: String?) {
100+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
101+
if ("dark-content" == style) {
102+
// dark-content means dark icons on a light status bar
103+
insetsController?.setSystemBarsAppearance(
104+
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
105+
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
106+
)
107+
} else {
108+
insetsController?.setSystemBarsAppearance(
109+
0,
110+
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
111+
)
112+
}
113+
} else {
114+
var systemUiVisibilityFlags = decorView.systemUiVisibility
115+
systemUiVisibilityFlags =
116+
if ("dark-content" == style) {
117+
systemUiVisibilityFlags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
118+
} else {
119+
systemUiVisibilityFlags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
120+
}
121+
decorView.systemUiVisibility = systemUiVisibilityFlags
122+
}
123+
}
124+
96125
@Suppress("DEPRECATION")
97126
private fun Window.statusBarHide() {
98127
if (isEdgeToEdgeFeatureFlagOn) {

0 commit comments

Comments
 (0)