Skip to content

Commit a8c0c89

Browse files
committed
Handle edge-to-edge when it's not enabled by the edgeToEdgeEnabled gradle property
# Conflicts: # packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt
1 parent c2280e3 commit a8c0c89

7 files changed

Lines changed: 51 additions & 75 deletions

File tree

packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@ abstract class GenerateEntryPointTask : DefaultTask() {
9797
DefaultNewArchitectureEntryPoint.load();
9898
}
9999
100-
if ({{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
101-
WindowUtilKt.setEdgeToEdgeFeatureFlagOn();
102-
}
100+
WindowUtilKt.initEdgeToEdge(context, {{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED);
103101
}
104102
}
105103
"""

packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,7 @@ class GenerateEntryPointTaskTest {
8080
DefaultNewArchitectureEntryPoint.load();
8181
}
8282
83-
if (com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) {
84-
WindowUtilKt.setEdgeToEdgeFeatureFlagOn();
85-
}
83+
WindowUtilKt.initEdgeToEdge(context, com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED);
8684
}
8785
}
8886
"""

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6545,7 +6545,8 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion {
65456545

65466546
public final class com/facebook/react/views/view/WindowUtilKt {
65476547
public static final fun isEdgeToEdgeFeatureFlagOn ()Z
6548-
public static final fun setEdgeToEdgeFeatureFlagOn ()V
6548+
public static final fun isEdgeToEdge ()Z
6549+
public static final fun initEdgeToEdge (Landroid/content/Context;Z)V
65496550
}
65506551

65516552
public final class com/facebook/react/views/virtual/VirtualViewMode : java/lang/Enum {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import com.facebook.react.bridge.ReadableMap
1616
import com.facebook.react.module.annotations.ReactModule
1717
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
1818
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
19-
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
19+
import com.facebook.react.views.view.isEdgeToEdge
2020

2121
/** Module that exposes Android Constants to JS. */
2222
@ReactModule(name = NativeDeviceInfoSpec.NAME)
@@ -38,7 +38,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
3838

3939
return mapOf(
4040
"Dimensions" to displayMetrics.toHashMap(),
41-
"isEdgeToEdge" to isEdgeToEdgeFeatureFlagOn,
41+
"isEdgeToEdge" to isEdgeToEdge,
4242
)
4343
}
4444

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.facebook.react.common.ReactConstants
2323
import com.facebook.react.module.annotations.ReactModule
2424
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
2525
import com.facebook.react.uimanager.PixelUtil
26-
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
26+
import com.facebook.react.views.view.isEdgeToEdge
2727
import com.facebook.react.views.view.setStatusBarTranslucency
2828
import com.facebook.react.views.view.setStatusBarVisibility
2929

@@ -56,7 +56,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
5656
)
5757
return
5858
}
59-
if (isEdgeToEdgeFeatureFlagOn) {
59+
if (isEdgeToEdge) {
6060
FLog.w(
6161
ReactConstants.TAG,
6262
"StatusBarModule: Ignored status bar change, current activity is edge-to-edge.",
@@ -93,7 +93,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
9393
)
9494
return
9595
}
96-
if (isEdgeToEdgeFeatureFlagOn) {
96+
if (isEdgeToEdge) {
9797
FLog.w(
9898
ReactConstants.TAG,
9999
"StatusBarModule: Ignored status bar change, current activity is edge-to-edge.",

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import com.facebook.react.views.common.ContextUtils
5353
import com.facebook.react.views.view.ReactViewGroup
5454
import com.facebook.react.views.view.disableEdgeToEdge
5555
import com.facebook.react.views.view.enableEdgeToEdge
56-
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
56+
import com.facebook.react.views.view.isEdgeToEdge
5757
import com.facebook.react.views.view.setStatusBarTranslucency
5858

5959
/**
@@ -81,17 +81,17 @@ public class ReactModalHostView(context: ThemedReactContext) :
8181
public var onRequestCloseListener: OnRequestCloseListener? = null
8282

8383
public var statusBarTranslucent: Boolean = false
84-
get() = field || isEdgeToEdgeFeatureFlagOn
84+
get() = field || isEdgeToEdge
8585
set(value) {
8686
field = value
87-
createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn
87+
createNewDialog = createNewDialog || !isEdgeToEdge
8888
}
8989

9090
public var navigationBarTranslucent: Boolean = false
91-
get() = field || isEdgeToEdgeFeatureFlagOn
91+
get() = field || isEdgeToEdge
9292
set(value) {
9393
field = value
94-
createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn
94+
createNewDialog = createNewDialog || !isEdgeToEdge
9595
}
9696

9797
public var animationType: String? = null
@@ -428,7 +428,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
428428
val dialogWindowInsetsController =
429429
WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView)
430430

431-
if (isEdgeToEdgeFeatureFlagOn) {
431+
if (isEdgeToEdge) {
432432
activityWindowInsetsController.systemBarsBehavior =
433433
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
434434
dialogWindowInsetsController.systemBarsBehavior =

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

Lines changed: 36 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
package com.facebook.react.views.view
99

10-
import android.app.Activity
10+
import android.content.Context
1111
import android.graphics.Color
1212
import android.os.Build
1313
import android.view.Window
@@ -16,7 +16,6 @@ import androidx.core.view.ViewCompat
1616
import androidx.core.view.WindowCompat
1717
import androidx.core.view.WindowInsetsCompat
1818
import androidx.core.view.WindowInsetsControllerCompat
19-
import com.facebook.react.util.AndroidVersion
2019
import com.facebook.react.views.common.UiModeUtils
2120

2221
// The light scrim color used in the platform API 29+
@@ -35,34 +34,28 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b)
3534
public var isEdgeToEdgeFeatureFlagOn: Boolean = false
3635
private set
3736

38-
public fun setEdgeToEdgeFeatureFlagOn() {
39-
isEdgeToEdgeFeatureFlagOn = true
40-
}
41-
42-
internal fun updateEdgeToEdgeFeatureFlag(activity: Activity) {
43-
// When the app targets SDK 35+, edge-to-edge may be enforced by the OS even if the
44-
// feature flag wasn't explicitly set. In that case, turn the flag on to match.
45-
if (AndroidVersion.isAtLeastTargetSdk35(activity)) {
46-
if (Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA) {
47-
// The device is running Android 16+ (where edge-to-edge is always enforced)
48-
isEdgeToEdgeFeatureFlagOn = true
49-
} else {
50-
val attributes = intArrayOf(AndroidVersion.ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT)
51-
val typedArray = activity.theme.obtainStyledAttributes(attributes)
52-
53-
// The device is running Android 15 with / without opting out
54-
isEdgeToEdgeFeatureFlagOn =
55-
try {
56-
!typedArray.getBoolean(0, false)
57-
} finally {
58-
typedArray.recycle()
59-
}
60-
}
61-
}
37+
/**
38+
* Whether edge-to-edge is enforced. Computed once in [initEdgeToEdge], based on:
39+
* - The device is running Android 16+ (where edge-to-edge is always enforced)
40+
* - The edge-to-edge feature flag has been explicitly enabled
41+
* - The device is running Android 15 (API 35) without opting out
42+
*/
43+
public var isEdgeToEdge: Boolean = false
44+
private set
6245

63-
if (isEdgeToEdgeFeatureFlagOn) {
64-
activity.window.enableEdgeToEdge()
65-
}
46+
public fun initEdgeToEdge(context: Context, flag: Boolean) {
47+
isEdgeToEdgeFeatureFlagOn = flag
48+
isEdgeToEdge =
49+
when {
50+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA || flag -> true
51+
Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM -> false
52+
else ->
53+
context.theme
54+
.obtainStyledAttributes(
55+
intArrayOf(android.R.attr.windowOptOutEdgeToEdgeEnforcement)
56+
)
57+
.use { !it.getBoolean(0, false) }
58+
}
6659
}
6760

6861
@Suppress("DEPRECATION")
@@ -95,7 +88,7 @@ internal fun Window.setStatusBarVisibility(isHidden: Boolean) {
9588

9689
@Suppress("DEPRECATION")
9790
private fun Window.statusBarHide() {
98-
if (isEdgeToEdgeFeatureFlagOn) {
91+
if (isEdgeToEdge) {
9992
WindowInsetsControllerCompat(this, decorView).run {
10093
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
10194
hide(WindowInsetsCompat.Type.statusBars())
@@ -114,7 +107,7 @@ private fun Window.statusBarHide() {
114107

115108
@Suppress("DEPRECATION")
116109
private fun Window.statusBarShow() {
117-
if (isEdgeToEdgeFeatureFlagOn) {
110+
if (isEdgeToEdge) {
118111
WindowInsetsControllerCompat(this, decorView).run {
119112
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
120113
show(WindowInsetsCompat.Type.statusBars())
@@ -134,37 +127,23 @@ private fun Window.statusBarShow() {
134127
internal fun Window.enableEdgeToEdge() {
135128
WindowCompat.setDecorFitsSystemWindows(this, false)
136129

137-
val insetsController = WindowInsetsControllerCompat(this, decorView)
138130
val isDarkMode = UiModeUtils.isDarkMode(context)
139131

140-
statusBarColor = Color.TRANSPARENT
141-
142132
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
143-
navigationBarColor = Color.TRANSPARENT
144-
145-
val attributes = intArrayOf(android.R.attr.enforceNavigationBarContrast)
146-
val typedArray = context.theme.obtainStyledAttributes(attributes)
147-
148-
val enforceNavigationBarContrast =
149-
try {
150-
typedArray.getBoolean(0, true)
151-
} finally {
152-
typedArray.recycle()
153-
}
154-
155133
isStatusBarContrastEnforced = false
156-
isNavigationBarContrastEnforced = enforceNavigationBarContrast
157-
158-
if (enforceNavigationBarContrast) {
159-
insetsController.isAppearanceLightNavigationBars = !isDarkMode
160-
}
161-
} else {
162-
val isAppearanceLightNavigationBars =
163-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isDarkMode
134+
isNavigationBarContrastEnforced = true
135+
}
164136

165-
navigationBarColor =
166-
if (isAppearanceLightNavigationBars) LightNavigationBarColor else DarkNavigationBarColor
167-
insetsController.isAppearanceLightNavigationBars = isAppearanceLightNavigationBars
137+
statusBarColor = Color.TRANSPARENT
138+
navigationBarColor =
139+
when {
140+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Color.TRANSPARENT
141+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isDarkMode -> LightNavigationBarColor
142+
else -> DarkNavigationBarColor
143+
}
144+
145+
WindowInsetsControllerCompat(this, decorView).run {
146+
isAppearanceLightNavigationBars = !isDarkMode
168147
}
169148

170149
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {

0 commit comments

Comments
 (0)