Skip to content

Commit 3a40e80

Browse files
authored
Observe light / dark theme switch (#35)
1 parent bc69331 commit 3a40e80

File tree

13 files changed

+81
-65
lines changed

13 files changed

+81
-65
lines changed

android/src/main/java/com/zoontek/rnedgetoedge/RNEdgeToEdgeModuleImpl.kt

+23-38
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.zoontek.rnedgetoedge
22

3-
import android.app.Activity
43
import android.content.res.Configuration
54
import android.graphics.Color
65
import android.os.Build
7-
import android.util.TypedValue
6+
import android.os.Handler
7+
import android.os.Looper
88
import android.view.WindowManager
99

1010
import androidx.core.content.ContextCompat
@@ -13,75 +13,60 @@ import androidx.core.view.WindowInsetsCompat
1313
import androidx.core.view.WindowInsetsControllerCompat
1414

1515
import com.facebook.common.logging.FLog
16+
import com.facebook.react.bridge.ReactApplicationContext
1617
import com.facebook.react.bridge.ReadableMap
1718
import com.facebook.react.common.ReactConstants
1819

1920
object RNEdgeToEdgeModuleImpl {
2021
const val NAME = "RNEdgeToEdge"
21-
private var isInitialHostResume = true
2222

23-
fun onHostResume(activity: Activity?) {
24-
if (activity == null) {
25-
return FLog.w(ReactConstants.TAG, "$NAME: Ignored, current activity is null.")
26-
}
23+
private fun applyEdgeToEdge(reactContext: ReactApplicationContext) {
24+
val activity = reactContext.currentActivity
25+
?: return FLog.w(ReactConstants.TAG, "$NAME: Ignored, current activity is null.")
2726

2827
activity.runOnUiThread {
2928
val window = activity.window
3029
val view = window.decorView
3130
val context = view.context
3231

33-
val isDarkMode =
34-
view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
35-
Configuration.UI_MODE_NIGHT_YES
36-
3732
WindowCompat.setDecorFitsSystemWindows(window, false)
3833

3934
window.statusBarColor = Color.TRANSPARENT
35+
window.navigationBarColor = ContextCompat.getColor(context, R.color.navigationBarColor)
4036

41-
window.navigationBarColor = when {
42-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Color.TRANSPARENT
43-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isDarkMode ->
44-
ContextCompat.getColor(context, R.color.systemBarLightScrim)
45-
else -> ContextCompat.getColor(context, R.color.systemBarDarkScrim)
37+
val isDarkMode =
38+
view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
39+
Configuration.UI_MODE_NIGHT_YES
40+
41+
WindowInsetsControllerCompat(window, view).run {
42+
isAppearanceLightNavigationBars = !isDarkMode
4643
}
4744

4845
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
4946
window.isStatusBarContrastEnforced = false
5047
window.isNavigationBarContrastEnforced = true
5148
}
5249

53-
WindowInsetsControllerCompat(window, view).run {
54-
if (isInitialHostResume) {
55-
val typedValue = TypedValue()
56-
57-
isAppearanceLightStatusBars = activity
58-
.theme
59-
.resolveAttribute(android.R.attr.windowLightStatusBar, typedValue, true) &&
60-
typedValue.data != 0
61-
}
62-
63-
isInitialHostResume = false
64-
isAppearanceLightNavigationBars = !isDarkMode
65-
}
66-
6750
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
6851
window.attributes.layoutInDisplayCutoutMode = when {
69-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
70-
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
52+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
7153
else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
7254
}
7355
}
7456
}
7557
}
7658

77-
fun onHostDestroy() {
78-
isInitialHostResume = true
59+
fun onHostResume(reactContext: ReactApplicationContext) {
60+
applyEdgeToEdge(reactContext)
7961
}
8062

81-
fun setSystemBarsConfig(activity: Activity?, config: ReadableMap) {
82-
if (activity == null) {
83-
return FLog.w(ReactConstants.TAG, "$NAME: Ignored, current activity is null.")
84-
}
63+
fun onConfigChange(reactContext: ReactApplicationContext) {
64+
Handler(Looper.getMainLooper()).postDelayed({ applyEdgeToEdge(reactContext) }, 100)
65+
}
66+
67+
fun setSystemBarsConfig(reactContext: ReactApplicationContext, config: ReadableMap) {
68+
val activity = reactContext.currentActivity
69+
?: return FLog.w(ReactConstants.TAG, "$NAME: Ignored, current activity is null.")
8570

8671
val statusBarHidden =
8772
config.takeIf { it.hasKey("statusBarHidden") }?.getBoolean("statusBarHidden")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<style name="Theme.EdgeToEdge.Base" parent="Theme.AppCompat.DayNight.NoActionBar">
4+
<item name="android:windowLightStatusBar">false</item>
5+
<item name="android:windowLightNavigationBar">false</item>
6+
</style>
7+
</resources>

android/src/main/res/values-v26/styles.xml

-6
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/com/android/internal/policy/DecorView.java;l=142 -->
4+
<color name="navigationBarColor">#e6ffffff</color>
5+
</resources>
+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3+
<style name="Theme.EdgeToEdge.Base" parent="Theme.AppCompat.DayNight.NoActionBar">
4+
<item name="android:windowLightStatusBar">true</item>
5+
<item name="android:windowLightNavigationBar">true</item>
6+
</style>
7+
38
<style name="Theme.EdgeToEdge" parent="Theme.EdgeToEdge.Common">
49
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
5-
<item name="android:navigationBarColor">@color/systemBarLightScrim</item>
6-
<item name="android:windowLightNavigationBar">true</item>
710
</style>
811
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<color name="navigationBarColor">@android:color/transparent</color>
4+
</resources>

android/src/main/res/values-v29/styles.xml

-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@
44
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
55
<item name="android:enforceStatusBarContrast">false</item>
66
<item name="android:enforceNavigationBarContrast">true</item>
7-
<item name="android:navigationBarColor">@android:color/transparent</item>
87
</style>
98
</resources>

android/src/main/res/values-v30/styles.xml

-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@
44
<item name="android:windowLayoutInDisplayCutoutMode">always</item>
55
<item name="android:enforceStatusBarContrast">false</item>
66
<item name="android:enforceNavigationBarContrast">true</item>
7-
<item name="android:navigationBarColor">@android:color/transparent</item>
87
</style>
98
</resources>

android/src/main/res/values/colors.xml

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,5 @@
22
<resources>
33
<!-- https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/remote_color_resources_res/values/colors.xml;l=67 -->
44
<!-- https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/res/color/system_bar_background_semi_transparent.xml -->
5-
<color name="systemBarDarkScrim">#801b1b1b</color>
6-
<!-- https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/com/android/internal/policy/DecorView.java;l=142 -->
7-
<color name="systemBarLightScrim">#e6ffffff</color>
5+
<color name="navigationBarColor">#801b1b1b</color>
86
</resources>

android/src/main/res/values/styles.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
<item name="android:fitsSystemWindows">false</item>
99
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
1010
<item name="android:statusBarColor">@android:color/transparent</item>
11+
<item name="android:navigationBarColor">@color/navigationBarColor</item>
1112
</style>
1213

1314
<style name="Theme.EdgeToEdge" parent="Theme.EdgeToEdge.Common">
14-
<item name="android:navigationBarColor">@color/systemBarDarkScrim</item>
1515
</style>
1616
</resources>
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
package com.zoontek.rnedgetoedge
22

3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
38
import com.facebook.react.bridge.LifecycleEventListener
49
import com.facebook.react.bridge.ReactApplicationContext
510
import com.facebook.react.bridge.ReadableMap
611
import com.facebook.react.module.annotations.ReactModule
712

813
@ReactModule(name = RNEdgeToEdgeModuleImpl.NAME)
9-
class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
14+
class RNEdgeToEdgeModule(reactContext: ReactApplicationContext) :
1015
NativeRNEdgeToEdgeSpec(reactContext), LifecycleEventListener {
1116

17+
private val configChangeReceiver = object : BroadcastReceiver() {
18+
override fun onReceive(context: Context, intent: Intent) {
19+
RNEdgeToEdgeModuleImpl.onConfigChange(reactContext)
20+
}
21+
}
22+
1223
init {
24+
reactApplicationContext.registerReceiver(configChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED))
1325
reactApplicationContext.addLifecycleEventListener(this)
1426
}
1527

1628
override fun invalidate() {
29+
reactApplicationContext.unregisterReceiver(configChangeReceiver)
1730
reactApplicationContext.removeLifecycleEventListener(this)
1831
}
1932

@@ -22,16 +35,14 @@ class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
2235
}
2336

2437
override fun onHostResume() {
25-
RNEdgeToEdgeModuleImpl.onHostResume(currentActivity)
38+
RNEdgeToEdgeModuleImpl.onHostResume(reactApplicationContext)
2639
}
2740

2841
override fun onHostPause() {}
2942

30-
override fun onHostDestroy() {
31-
RNEdgeToEdgeModuleImpl.onHostDestroy()
32-
}
43+
override fun onHostDestroy() {}
3344

3445
override fun setSystemBarsConfig(config: ReadableMap) {
35-
RNEdgeToEdgeModuleImpl.setSystemBarsConfig(currentActivity, config)
46+
RNEdgeToEdgeModuleImpl.setSystemBarsConfig(reactApplicationContext, config)
3647
}
3748
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package com.zoontek.rnedgetoedge
22

3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
38
import com.facebook.react.bridge.LifecycleEventListener
49
import com.facebook.react.bridge.ReactApplicationContext
510
import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -8,14 +13,22 @@ import com.facebook.react.bridge.ReadableMap
813
import com.facebook.react.module.annotations.ReactModule
914

1015
@ReactModule(name = RNEdgeToEdgeModuleImpl.NAME)
11-
class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
16+
class RNEdgeToEdgeModule(reactContext: ReactApplicationContext) :
1217
ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
1318

19+
private val configChangeReceiver = object : BroadcastReceiver() {
20+
override fun onReceive(context: Context, intent: Intent) {
21+
RNEdgeToEdgeModuleImpl.onConfigChange(reactContext)
22+
}
23+
}
24+
1425
init {
26+
reactApplicationContext.registerReceiver(configChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED))
1527
reactApplicationContext.addLifecycleEventListener(this)
1628
}
1729

1830
override fun invalidate() {
31+
reactApplicationContext.unregisterReceiver(configChangeReceiver)
1932
reactApplicationContext.removeLifecycleEventListener(this)
2033
}
2134

@@ -24,17 +37,15 @@ class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
2437
}
2538

2639
override fun onHostResume() {
27-
RNEdgeToEdgeModuleImpl.onHostResume(currentActivity)
40+
RNEdgeToEdgeModuleImpl.onHostResume(reactApplicationContext)
2841
}
2942

3043
override fun onHostPause() {}
3144

32-
override fun onHostDestroy() {
33-
RNEdgeToEdgeModuleImpl.onHostDestroy()
34-
}
45+
override fun onHostDestroy() {}
3546

3647
@ReactMethod
3748
fun setSystemBarsConfig(config: ReadableMap) {
38-
RNEdgeToEdgeModuleImpl.setSystemBarsConfig(currentActivity, config)
49+
RNEdgeToEdgeModuleImpl.setSystemBarsConfig(reactApplicationContext, config)
3950
}
4051
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-edge-to-edge",
3-
"version": "0.1.2",
3+
"version": "0.2.0",
44
"license": "MIT",
55
"description": "Effortlessly enable edge-to-edge display in React Native",
66
"author": "Mathieu Acthernoene <[email protected]>",

0 commit comments

Comments
 (0)