react-native-edge-to-edge 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,9 @@ It is supporting the **latest version**, and the **two previous minor series**.
25
25
 
26
26
  ### Android 15
27
27
 
28
- With the upcoming release of Android 15, Google is introducing a significant change: apps targeting SDK 35 [will have edge-to-edge enforcement](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge). Currently, the latest version of React Native targets SDK 34, so this isn't an issue **yet**, but it will be in a future release.
28
+ With the upcoming release of Android 15, Google is introducing a significant change: apps targeting SDK 35 [will have edge-to-edge enforcement](https://developer.android.com/about/versions/15/behavior-changes-15#edge-to-edge). Google is _likely_ to mandate that app updates on the Play Store target SDK 35 starting on August 31, 2025. This assumption is based on the [previous years' requirements following a similar timeline](https://support.google.com/googleplay/android-developer/answer/11926878?sjid=11853000253346477363-EU#zippy=%2Care-there-any-exceptions-for-existing-apps-targeting-api-or-below:~:text=App%20update%20requirements).
29
+
30
+ Currently, new React Native projects target SDK 34.
29
31
 
30
32
  ### Immersive mode
31
33
 
@@ -1,10 +1,10 @@
1
1
  package com.zoontek.rnedgetoedge
2
2
 
3
- import android.app.Activity
4
3
  import android.content.res.Configuration
5
4
  import android.graphics.Color
6
5
  import android.os.Build
7
- import android.util.TypedValue
6
+ import android.os.Handler
7
+ import android.os.Looper
8
8
  import android.view.WindowManager
9
9
 
10
10
  import androidx.core.content.ContextCompat
@@ -13,36 +13,33 @@ import androidx.core.view.WindowInsetsCompat
13
13
  import androidx.core.view.WindowInsetsControllerCompat
14
14
 
15
15
  import com.facebook.common.logging.FLog
16
+ import com.facebook.react.bridge.ReactApplicationContext
16
17
  import com.facebook.react.bridge.ReadableMap
17
18
  import com.facebook.react.common.ReactConstants
18
19
 
19
20
  object RNEdgeToEdgeModuleImpl {
20
21
  const val NAME = "RNEdgeToEdge"
21
- private var isInitialHostResume = true
22
22
 
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.")
27
26
 
28
27
  activity.runOnUiThread {
29
28
  val window = activity.window
30
29
  val view = window.decorView
31
30
  val context = view.context
32
31
 
33
- val isDarkMode =
34
- view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
35
- Configuration.UI_MODE_NIGHT_YES
36
-
37
32
  WindowCompat.setDecorFitsSystemWindows(window, false)
38
33
 
39
34
  window.statusBarColor = Color.TRANSPARENT
35
+ window.navigationBarColor = ContextCompat.getColor(context, R.color.navigationBarColor)
40
36
 
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
46
43
  }
47
44
 
48
45
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -50,38 +47,26 @@ object RNEdgeToEdgeModuleImpl {
50
47
  window.isNavigationBarContrastEnforced = true
51
48
  }
52
49
 
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
-
67
50
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
68
51
  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
71
53
  else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
72
54
  }
73
55
  }
74
56
  }
75
57
  }
76
58
 
77
- fun onHostDestroy() {
78
- isInitialHostResume = true
59
+ fun onHostResume(reactContext: ReactApplicationContext) {
60
+ applyEdgeToEdge(reactContext)
79
61
  }
80
62
 
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.")
85
70
 
86
71
  val statusBarHidden =
87
72
  config.takeIf { it.hasKey("statusBarHidden") }?.getBoolean("statusBarHidden")
@@ -2,7 +2,5 @@
2
2
  <resources>
3
3
  <!-- https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/remote_color_resources_res/values/colors.xml;l=67 -->
4
4
  <!-- 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>
8
6
  </resources>
@@ -8,9 +8,9 @@
8
8
  <item name="android:fitsSystemWindows">false</item>
9
9
  <item name="android:windowDrawsSystemBarBackgrounds">true</item>
10
10
  <item name="android:statusBarColor">@android:color/transparent</item>
11
+ <item name="android:navigationBarColor">@color/navigationBarColor</item>
11
12
  </style>
12
13
 
13
14
  <style name="Theme.EdgeToEdge" parent="Theme.EdgeToEdge.Common">
14
- <item name="android:navigationBarColor">@color/systemBarDarkScrim</item>
15
15
  </style>
16
16
  </resources>
@@ -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>
@@ -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>
@@ -1,8 +1,11 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <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
+
3
8
  <style name="Theme.EdgeToEdge" parent="Theme.EdgeToEdge.Common">
4
9
  <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
5
- <item name="android:navigationBarColor">@color/systemBarLightScrim</item>
6
- <item name="android:windowLightNavigationBar">true</item>
7
10
  </style>
8
11
  </resources>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <color name="navigationBarColor">@android:color/transparent</color>
4
+ </resources>
@@ -4,6 +4,5 @@
4
4
  <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
5
5
  <item name="android:enforceStatusBarContrast">false</item>
6
6
  <item name="android:enforceNavigationBarContrast">true</item>
7
- <item name="android:navigationBarColor">@android:color/transparent</item>
8
7
  </style>
9
8
  </resources>
@@ -4,6 +4,5 @@
4
4
  <item name="android:windowLayoutInDisplayCutoutMode">always</item>
5
5
  <item name="android:enforceStatusBarContrast">false</item>
6
6
  <item name="android:enforceNavigationBarContrast">true</item>
7
- <item name="android:navigationBarColor">@android:color/transparent</item>
8
7
  </style>
9
8
  </resources>
@@ -1,19 +1,32 @@
1
1
  package com.zoontek.rnedgetoedge
2
2
 
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.content.IntentFilter
7
+
3
8
  import com.facebook.react.bridge.LifecycleEventListener
4
9
  import com.facebook.react.bridge.ReactApplicationContext
5
10
  import com.facebook.react.bridge.ReadableMap
6
11
  import com.facebook.react.module.annotations.ReactModule
7
12
 
8
13
  @ReactModule(name = RNEdgeToEdgeModuleImpl.NAME)
9
- class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
14
+ class RNEdgeToEdgeModule(reactContext: ReactApplicationContext) :
10
15
  NativeRNEdgeToEdgeSpec(reactContext), LifecycleEventListener {
11
16
 
17
+ private val configChangeReceiver = object : BroadcastReceiver() {
18
+ override fun onReceive(context: Context, intent: Intent) {
19
+ RNEdgeToEdgeModuleImpl.onConfigChange(reactContext)
20
+ }
21
+ }
22
+
12
23
  init {
24
+ reactApplicationContext.registerReceiver(configChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED))
13
25
  reactApplicationContext.addLifecycleEventListener(this)
14
26
  }
15
27
 
16
28
  override fun invalidate() {
29
+ reactApplicationContext.unregisterReceiver(configChangeReceiver)
17
30
  reactApplicationContext.removeLifecycleEventListener(this)
18
31
  }
19
32
 
@@ -22,16 +35,14 @@ class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
22
35
  }
23
36
 
24
37
  override fun onHostResume() {
25
- RNEdgeToEdgeModuleImpl.onHostResume(currentActivity)
38
+ RNEdgeToEdgeModuleImpl.onHostResume(reactApplicationContext)
26
39
  }
27
40
 
28
41
  override fun onHostPause() {}
29
42
 
30
- override fun onHostDestroy() {
31
- RNEdgeToEdgeModuleImpl.onHostDestroy()
32
- }
43
+ override fun onHostDestroy() {}
33
44
 
34
45
  override fun setSystemBarsConfig(config: ReadableMap) {
35
- RNEdgeToEdgeModuleImpl.setSystemBarsConfig(currentActivity, config)
46
+ RNEdgeToEdgeModuleImpl.setSystemBarsConfig(reactApplicationContext, config)
36
47
  }
37
48
  }
@@ -1,5 +1,10 @@
1
1
  package com.zoontek.rnedgetoedge
2
2
 
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.content.IntentFilter
7
+
3
8
  import com.facebook.react.bridge.LifecycleEventListener
4
9
  import com.facebook.react.bridge.ReactApplicationContext
5
10
  import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -8,14 +13,22 @@ import com.facebook.react.bridge.ReadableMap
8
13
  import com.facebook.react.module.annotations.ReactModule
9
14
 
10
15
  @ReactModule(name = RNEdgeToEdgeModuleImpl.NAME)
11
- class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
16
+ class RNEdgeToEdgeModule(reactContext: ReactApplicationContext) :
12
17
  ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
13
18
 
19
+ private val configChangeReceiver = object : BroadcastReceiver() {
20
+ override fun onReceive(context: Context, intent: Intent) {
21
+ RNEdgeToEdgeModuleImpl.onConfigChange(reactContext)
22
+ }
23
+ }
24
+
14
25
  init {
26
+ reactApplicationContext.registerReceiver(configChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED))
15
27
  reactApplicationContext.addLifecycleEventListener(this)
16
28
  }
17
29
 
18
30
  override fun invalidate() {
31
+ reactApplicationContext.unregisterReceiver(configChangeReceiver)
19
32
  reactApplicationContext.removeLifecycleEventListener(this)
20
33
  }
21
34
 
@@ -24,17 +37,15 @@ class RNEdgeToEdgeModule(reactContext: ReactApplicationContext?) :
24
37
  }
25
38
 
26
39
  override fun onHostResume() {
27
- RNEdgeToEdgeModuleImpl.onHostResume(currentActivity)
40
+ RNEdgeToEdgeModuleImpl.onHostResume(reactApplicationContext)
28
41
  }
29
42
 
30
43
  override fun onHostPause() {}
31
44
 
32
- override fun onHostDestroy() {
33
- RNEdgeToEdgeModuleImpl.onHostDestroy()
34
- }
45
+ override fun onHostDestroy() {}
35
46
 
36
47
  @ReactMethod
37
48
  fun setSystemBarsConfig(config: ReadableMap) {
38
- RNEdgeToEdgeModuleImpl.setSystemBarsConfig(currentActivity, config)
49
+ RNEdgeToEdgeModuleImpl.setSystemBarsConfig(reactApplicationContext, config)
39
50
  }
40
51
  }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _default = exports.default = {
8
+ setSystemBarsConfig() {}
9
+ };
10
+ //# sourceMappingURL=NativeRNEdgeToEdge.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["setSystemBarsConfig"],"sourceRoot":"../../src","sources":["NativeRNEdgeToEdge.web.ts"],"mappings":";;;;;;iCAEe;EACbA,mBAAmBA,CAAA,EAAG,CAAC;AACzB,CAAC","ignoreList":[]}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ export default {
4
+ setSystemBarsConfig() {}
5
+ };
6
+ //# sourceMappingURL=NativeRNEdgeToEdge.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["setSystemBarsConfig"],"sourceRoot":"../../src","sources":["NativeRNEdgeToEdge.web.ts"],"mappings":";;AAEA,eAAe;EACbA,mBAAmBA,CAAA,EAAG,CAAC;AACzB,CAAC","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ declare const _default: {
2
+ setSystemBarsConfig(): void;
3
+ };
4
+ export default _default;
5
+ //# sourceMappingURL=NativeRNEdgeToEdge.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeRNEdgeToEdge.web.d.ts","sourceRoot":"","sources":["../../src/NativeRNEdgeToEdge.web.ts"],"names":[],"mappings":";;;AAEA,wBAEiB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-edge-to-edge",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Effortlessly enable edge-to-edge display in React Native",
6
6
  "author": "Mathieu Acthernoene <zoontek@gmail.com>",
@@ -0,0 +1,5 @@
1
+ import type { Spec } from "./NativeRNEdgeToEdge";
2
+
3
+ export default {
4
+ setSystemBarsConfig() {},
5
+ } satisfies Spec;
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <resources>
3
- <style name="Theme.EdgeToEdge" parent="Theme.EdgeToEdge.Common">
4
- <item name="android:navigationBarColor">@color/systemBarLightScrim</item>
5
- </style>
6
- </resources>