react-native-unistyles 2.8.0-beta.2 → 2.8.0-beta.4

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.
@@ -25,11 +25,11 @@ void makeShared(JNIEnv *env, jobject unistylesModule, std::shared_ptr<UnistylesR
25
25
  return getContentSizeCategory(env, unistylesModule);
26
26
  });
27
27
 
28
- unistylesRuntime->setNavigationBarColorCallback([&](const std::string &color) {
28
+ unistylesRuntime->setNavigationBarColorCallback([=](const std::string &color) {
29
29
  setNavigationBarColor(env, unistylesModule, color);
30
30
  });
31
31
 
32
- unistylesRuntime->setStatusBarColorCallback([&](const std::string &color) {
32
+ unistylesRuntime->setStatusBarColorCallback([=](const std::string &color) {
33
33
  setStatusBarColor(env, unistylesModule, color);
34
34
  });
35
35
 
@@ -36,50 +36,29 @@ class Insets(var top: Int, var bottom: Int, var left: Int, var right: Int) {
36
36
  }
37
37
  }
38
38
 
39
- class LayoutConfig(
40
- val screen: Dimensions,
41
- val insets: Insets,
42
- val statusBar: Dimensions,
43
- val navigationBar: Dimensions
39
+ class InsetsCompat(
40
+ val statusBar: Insets,
41
+ val navigationBar: Insets,
42
+ val cutout: Insets
44
43
  ) {
45
- fun isEqual(config: LayoutConfig): Boolean {
46
- if (!this.screen.isEqual(config.screen)) {
47
- return false
48
- }
49
-
50
- if (!this.insets.isEqual(config.insets)) {
51
- return false
52
- }
53
-
54
- if (!this.statusBar.isEqual(config.statusBar)) {
55
- return false
56
- }
57
-
58
- return this.navigationBar.isEqual(config.navigationBar)
59
- }
60
-
61
44
  override fun toString(): String {
62
45
  return buildString {
63
- append("screen=")
64
- append(screen)
65
- append(" insets=")
66
- append(insets)
67
46
  append(" statusBar=")
68
47
  append(statusBar)
69
48
  append(" navigationBar=")
70
49
  append(navigationBar)
50
+ append(" cutout=")
51
+ append(cutout)
71
52
  }
72
53
  }
73
- }
74
54
 
75
- class Config(
76
- val colorScheme: String,
77
- val contentSizeCategory: String,
78
- ) {
79
- var hasNewColorScheme: Boolean = false
80
- var hasNewContentSizeCategory: Boolean = false
81
-
82
- override fun toString(): String {
83
- return "colorScheme=${colorScheme} contentSizeCategory:${contentSizeCategory}"
55
+ companion object {
56
+ fun getDefaults(): InsetsCompat {
57
+ return InsetsCompat(
58
+ Insets(0, 0, 0, 0),
59
+ Insets(0, 0, 0, 0),
60
+ Insets(0, 0, 0, 0)
61
+ )
62
+ }
84
63
  }
85
64
  }
@@ -1,50 +1,114 @@
1
1
  package com.unistyles
2
2
 
3
+ import android.content.res.Configuration
4
+ import android.graphics.Rect
5
+ import android.util.DisplayMetrics
6
+ import android.view.View
7
+ import androidx.core.view.WindowInsetsCompat
3
8
  import com.facebook.react.bridge.ReactApplicationContext
9
+ import kotlin.math.max
4
10
 
5
- class Platform(reactApplicationContext: ReactApplicationContext) {
6
- private val config: UnistylesConfig = UnistylesConfig(reactApplicationContext)
11
+ class Platform(private val reactApplicationContext: ReactApplicationContext) {
12
+ private val displayMetrics: DisplayMetrics = reactApplicationContext.resources.displayMetrics
13
+ private var insetsCompat: InsetsCompat = InsetsCompat.getDefaults()
7
14
 
8
15
  var defaultNavigationBarColor: Int? = null
9
16
  var defaultStatusBarColor: Int? = null
10
17
 
11
- fun hasNewLayoutConfig(): Boolean {
12
- return this.config.hasNewLayoutConfig()
13
- }
18
+ fun getScreenDimensions(): Dimensions {
19
+ val density = reactApplicationContext.resources.displayMetrics.density
20
+ val screenWidth = (displayMetrics.widthPixels / density).toInt()
21
+ val screenHeight = (displayMetrics.heightPixels / density).toInt()
14
22
 
15
- fun hasNewConfig(): Boolean {
16
- return this.config.hasNewConfig()
23
+ return Dimensions(screenWidth, screenHeight)
17
24
  }
18
25
 
19
- fun getConfig(): Config {
20
- return this.config.getConfig()
21
- }
26
+ fun getColorScheme(): String {
27
+ val uiMode = this.reactApplicationContext.resources.configuration.uiMode
22
28
 
23
- fun getLayoutConfig(): LayoutConfig {
24
- return this.config.getLayoutConfig()
25
- }
29
+ val colorScheme = when (uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
30
+ Configuration.UI_MODE_NIGHT_YES -> "dark"
31
+ Configuration.UI_MODE_NIGHT_NO -> "light"
32
+ else -> "unspecified"
33
+ }
26
34
 
27
- fun getScreenDimensions(): Dimensions {
28
- return this.config.getScreenDimensions()
29
- }
30
-
31
- fun getColorScheme(): String {
32
- return this.config.getColorScheme()
35
+ return colorScheme
33
36
  }
34
37
 
35
38
  fun getStatusBarDimensions(): Dimensions {
36
- return this.config.getStatusBarDimensions()
39
+ val density = reactApplicationContext.resources.displayMetrics.density
40
+ val screenWidth = (displayMetrics.widthPixels / density).toInt()
41
+
42
+ return Dimensions(screenWidth, getStatusBarHeight())
37
43
  }
38
44
 
39
45
  fun getNavigationBarDimensions(): Dimensions {
40
- return this.config.getNavigationBarDimensions()
46
+ val density = reactApplicationContext.resources.displayMetrics.density
47
+ val screenWidth = (displayMetrics.widthPixels / density).toInt()
48
+
49
+ return Dimensions(screenWidth, getNavigationBarHeight())
41
50
  }
42
51
 
43
52
  fun getContentSizeCategory(): String {
44
- return this.config.getContentSizeCategory()
53
+ val fontScale = reactApplicationContext.resources.configuration.fontScale
54
+
55
+ val contentSizeCategory = when {
56
+ fontScale <= 0.85f -> "Small"
57
+ fontScale <= 1.0f -> "Default"
58
+ fontScale <= 1.15f -> "Large"
59
+ fontScale <= 1.3f -> "ExtraLarge"
60
+ fontScale <= 1.5f -> "Huge"
61
+ fontScale <= 1.8 -> "ExtraHuge"
62
+ else -> "ExtraExtraHuge"
63
+ }
64
+
65
+ return contentSizeCategory
66
+ }
67
+
68
+ fun setInsetsCompat(insetsCompat: WindowInsetsCompat, decorView: View) {
69
+ val statusBar = insetsCompat.getInsets(WindowInsetsCompat.Type.statusBars())
70
+ val navigationBar = insetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars())
71
+ val cutout = insetsCompat.getInsets(WindowInsetsCompat.Type.displayCutout())
72
+
73
+ // get the visible frame of the window to detect translucent status bar
74
+ // react native (and expo) are setting top inset to 0
75
+ // so there is no other way to detect if status bar is hidden or translucent
76
+ val visibleFrame = Rect()
77
+ decorView.getWindowVisibleDisplayFrame(visibleFrame)
78
+
79
+ val visibleTopFrame = max(visibleFrame.top, statusBar.top)
80
+
81
+ this.insetsCompat = InsetsCompat(
82
+ Insets(visibleTopFrame, statusBar.bottom, statusBar.left, statusBar.right),
83
+ Insets(navigationBar.top, navigationBar.bottom, navigationBar.left, navigationBar.right),
84
+ Insets(cutout.top, cutout.bottom, cutout.left, cutout.right)
85
+ )
45
86
  }
46
87
 
47
88
  fun getInsets(): Insets {
48
- return this.config.getInsets()
89
+ val density = reactApplicationContext.resources.displayMetrics.density
90
+ val top = max(this.insetsCompat.cutout.top, this.insetsCompat.statusBar.top)
91
+ val bottom = this.insetsCompat.navigationBar.bottom
92
+ val left = this.insetsCompat.statusBar.left
93
+ val right = this.insetsCompat.statusBar.right
94
+
95
+ return Insets(
96
+ (top / density).toInt(),
97
+ (bottom / density).toInt(),
98
+ (left / density).toInt(),
99
+ (right / density).toInt()
100
+ )
101
+ }
102
+
103
+ private fun getStatusBarHeight(): Int {
104
+ val density = reactApplicationContext.resources.displayMetrics.density
105
+
106
+ return (this.insetsCompat.statusBar.top / density).toInt()
107
+ }
108
+
109
+ private fun getNavigationBarHeight(): Int {
110
+ val density = reactApplicationContext.resources.displayMetrics.density
111
+
112
+ return (this.insetsCompat.navigationBar.bottom / density).toInt()
49
113
  }
50
114
  }
@@ -4,11 +4,14 @@ import android.content.BroadcastReceiver
4
4
  import android.content.Context
5
5
  import android.content.Intent
6
6
  import android.content.IntentFilter
7
+ import android.content.res.Configuration
7
8
  import android.graphics.Color
8
9
  import android.os.Handler
9
10
  import android.os.Looper
10
11
  import android.util.Log
11
- import android.view.ViewTreeObserver
12
+ import android.view.View
13
+ import androidx.core.view.ViewCompat
14
+ import androidx.core.view.WindowCompat
12
15
  import com.facebook.react.bridge.LifecycleEventListener
13
16
  import com.facebook.react.bridge.ReactApplicationContext
14
17
  import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -16,31 +19,30 @@ import com.facebook.react.bridge.ReactMethod
16
19
  import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder
17
20
 
18
21
  class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
19
- private val drawHandler = Handler(Looper.getMainLooper())
20
- private val debounceDuration = 250L
21
- private var runnable: Runnable? = null
22
-
23
22
  private var isCxxReady: Boolean = false
24
23
  private lateinit var platform: Platform
25
- private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
26
- if (this.isCxxReady) {
27
- runnable?.let { drawHandler.removeCallbacks(it) }
28
-
29
- runnable = Runnable {
30
- this@UnistylesModule.onLayoutConfigChange()
31
- }.also {
32
- drawHandler.postDelayed(it, debounceDuration)
33
- }
34
- }
35
- }
36
24
 
37
25
  private val configurationChangeReceiver = object : BroadcastReceiver() {
38
26
  override fun onReceive(context: Context, intent: Intent) {
39
- if (intent.action == Intent.ACTION_CONFIGURATION_CHANGED && this@UnistylesModule.isCxxReady) {
27
+ if (!this@UnistylesModule.isCxxReady) {
28
+ return
29
+ }
30
+
31
+ if (intent.action == Intent.ACTION_CONFIGURATION_CHANGED) {
40
32
  Handler(Looper.getMainLooper()).postDelayed({
41
33
  this@UnistylesModule.onConfigChange()
42
34
  }, 10)
43
35
  }
36
+
37
+ val newConfig = context.resources.configuration
38
+
39
+ if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
40
+ this@UnistylesModule.onLayoutConfigChange()
41
+ }
42
+
43
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
44
+ this@UnistylesModule.onLayoutConfigChange()
45
+ }
44
46
  }
45
47
  }
46
48
 
@@ -55,20 +57,8 @@ class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
55
57
  reactApplicationContext.addLifecycleEventListener(this)
56
58
  }
57
59
 
58
- private fun setupLayoutListener() {
59
- val activity = currentActivity ?: return
60
- activity.window.decorView.rootView.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
61
- }
62
-
63
- private fun stopLayoutListener() {
64
- val activity = currentActivity ?: return
65
- activity.window.decorView.rootView.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
66
- }
67
-
68
60
  override fun invalidate() {
69
- this.stopLayoutListener()
70
61
  reactApplicationContext.unregisterReceiver(configurationChangeReceiver)
71
- runnable?.let { drawHandler.removeCallbacks(it) }
72
62
  reactApplicationContext.removeLifecycleEventListener(this)
73
63
 
74
64
  if (this.isCxxReady) {
@@ -79,36 +69,27 @@ class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
79
69
  //endregion
80
70
  //region Event handlers
81
71
  private fun onConfigChange() {
82
- if (!platform.hasNewConfig()) {
83
- return
84
- }
85
-
86
- val config = platform.getConfig()
72
+ val colorScheme = this.platform.getColorScheme()
73
+ val contentSizeCategory = this.platform.getContentSizeCategory()
87
74
 
88
75
  reactApplicationContext.runOnJSQueueThread {
89
- if (config.hasNewColorScheme) {
90
- this.nativeOnAppearanceChange(config.colorScheme)
91
- }
92
-
93
- if (config.hasNewContentSizeCategory) {
94
- this.nativeOnContentSizeCategoryChange(config.contentSizeCategory)
95
- }
76
+ this.nativeOnAppearanceChange(colorScheme)
77
+ this.nativeOnContentSizeCategoryChange(contentSizeCategory)
96
78
  }
97
79
  }
98
80
 
99
81
  private fun onLayoutConfigChange() {
100
- if (!platform.hasNewLayoutConfig()) {
101
- return
102
- }
103
-
104
- val config = platform.getLayoutConfig()
82
+ val screen = this.getScreenDimensions()
83
+ val insets = this.getInsets()
84
+ val statusBar = this.getStatusBarDimensions()
85
+ val navigationBar = this.getNavigationBarDimensions()
105
86
 
106
87
  reactApplicationContext.runOnJSQueueThread {
107
88
  this.nativeOnOrientationChange(
108
- config.screen,
109
- config.insets,
110
- config.statusBar,
111
- config.navigationBar
89
+ screen,
90
+ insets,
91
+ statusBar,
92
+ navigationBar
112
93
  )
113
94
  }
114
95
  }
@@ -121,6 +102,7 @@ class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
121
102
  System.loadLibrary("unistyles")
122
103
 
123
104
  this.platform = Platform(reactApplicationContext)
105
+ this.enableEdgeToEdge()
124
106
 
125
107
  this.reactApplicationContext.javaScriptContextHolder?.let { contextHolder ->
126
108
  this.reactApplicationContext.catalystInstance.jsCallInvokerHolder?.let { callInvokerHolder: CallInvokerHolder ->
@@ -205,6 +187,14 @@ class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
205
187
  }
206
188
  }
207
189
 
190
+ private fun enableEdgeToEdge() {
191
+ this.reactApplicationContext.currentActivity?.let { activity ->
192
+ activity.runOnUiThread {
193
+ WindowCompat.setDecorFitsSystemWindows(activity.window, false)
194
+ }
195
+ }
196
+ }
197
+
208
198
  @ReactMethod
209
199
  fun addListener(eventName: String?) = Unit
210
200
 
@@ -215,11 +205,29 @@ class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
215
205
  this.onConfigChange()
216
206
  }
217
207
 
218
- this.setupLayoutListener()
208
+ this.reactApplicationContext.currentActivity?.let { activity ->
209
+ activity.findViewById<View>(android.R.id.content)?.let { mainView ->
210
+ activity.window?.decorView?.let { decorView ->
211
+ ViewCompat.setOnApplyWindowInsetsListener(mainView) { _, insets ->
212
+ this.platform.setInsetsCompat(insets, decorView)
213
+
214
+ if (this.isCxxReady) {
215
+ this.onLayoutConfigChange()
216
+ }
217
+
218
+ insets
219
+ }
220
+ }
221
+ }
222
+ }
219
223
  }
220
224
 
221
225
  override fun onHostPause() {
222
- this.stopLayoutListener()
226
+ this.reactApplicationContext.currentActivity?.let { activity ->
227
+ activity.window?.decorView?.let { view ->
228
+ ViewCompat.setOnApplyWindowInsetsListener(view, null)
229
+ }
230
+ }
223
231
  }
224
232
 
225
233
  override fun onHostDestroy() {}
@@ -26,15 +26,15 @@ void UnistylesModel::handleScreenSizeChange(Dimensions& screen, std::optional<In
26
26
 
27
27
  this->breakpoint = breakpoint;
28
28
  this->screen = {screen.width, screen.height};
29
-
29
+
30
30
  if (insets.has_value()) {
31
31
  this->insets = {insets->top, insets->bottom, insets->left, insets->right};
32
32
  }
33
-
33
+
34
34
  if (statusBar.has_value()) {
35
35
  this->statusBar = {statusBar->width, statusBar->height};
36
36
  }
37
-
37
+
38
38
  if (navigationBar.has_value()) {
39
39
  this->navigationBar = {navigationBar->width, navigationBar->height};
40
40
  }
@@ -58,6 +58,10 @@ void UnistylesModel::handleAppearanceChange(std::string colorScheme) {
58
58
  }
59
59
 
60
60
  void UnistylesModel::handleContentSizeCategoryChange(std::string contentSizeCategory) {
61
+ if (this->contentSizeCategory == contentSizeCategory) {
62
+ return;
63
+ }
64
+
61
65
  this->contentSizeCategory = contentSizeCategory;
62
66
  this->onContentSizeCategoryChange(contentSizeCategory);
63
67
  }
@@ -158,7 +162,7 @@ void UnistylesModel::onLayoutChange() {
158
162
 
159
163
  EventNestedValue screenPayload;
160
164
  auto screen = this->screen;
161
-
165
+
162
166
  screenPayload["width"] = screen.width;
163
167
  screenPayload["height"] = screen.height;
164
168
 
@@ -166,7 +170,7 @@ void UnistylesModel::onLayoutChange() {
166
170
 
167
171
  EventNestedValue statusBarPayload;
168
172
  auto statusBar = this->statusBar;
169
-
173
+
170
174
  statusBarPayload["width"] = statusBar.width;
171
175
  statusBarPayload["height"] = statusBar.height;
172
176
 
@@ -174,7 +178,7 @@ void UnistylesModel::onLayoutChange() {
174
178
 
175
179
  EventNestedValue navigationBarPayload;
176
180
  auto navigationBar = this->navigationBar;
177
-
181
+
178
182
  navigationBarPayload["width"] = navigationBar.width;
179
183
  navigationBarPayload["height"] = navigationBar.height;
180
184
 
@@ -182,7 +186,7 @@ void UnistylesModel::onLayoutChange() {
182
186
 
183
187
  EventNestedValue insetsPayload;
184
188
  auto insets = this->insets;
185
-
189
+
186
190
  insetsPayload["top"] = insets.top;
187
191
  insetsPayload["bottom"] = insets.bottom;
188
192
  insetsPayload["left"] = insets.left;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-unistyles",
3
- "version": "2.8.0-beta.2",
3
+ "version": "2.8.0-beta.4",
4
4
  "description": "Level up your React Native StyleSheet",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -1,142 +0,0 @@
1
- package com.unistyles
2
-
3
- import android.annotation.SuppressLint
4
- import android.content.res.Configuration
5
- import com.facebook.react.bridge.ReactApplicationContext
6
-
7
- class UnistylesConfig(private val reactApplicationContext: ReactApplicationContext) {
8
- private val insets: UnistylesInsets = UnistylesInsets(reactApplicationContext)
9
- private val density: Float = reactApplicationContext.resources.displayMetrics.density
10
- private var lastConfig: Config = this.getAppConfig()
11
- private var lastLayoutConfig: LayoutConfig = this.getAppLayoutConfig()
12
-
13
- fun hasNewConfig(): Boolean {
14
- val newConfig = this.getAppConfig()
15
- val newContentSizeCategory = newConfig.contentSizeCategory != lastConfig.contentSizeCategory
16
- val newColorScheme = newConfig.colorScheme != lastConfig.colorScheme
17
-
18
- if (!newContentSizeCategory && !newColorScheme) {
19
- return false
20
- }
21
-
22
- lastConfig = newConfig
23
- lastConfig.hasNewContentSizeCategory = newContentSizeCategory
24
- lastConfig.hasNewColorScheme = newColorScheme
25
-
26
- return true
27
- }
28
-
29
- fun hasNewLayoutConfig(): Boolean {
30
- val newConfig = this.getAppLayoutConfig()
31
-
32
- if (newConfig.isEqual(lastLayoutConfig)) {
33
- return false
34
- }
35
-
36
- lastLayoutConfig = newConfig
37
-
38
- return true
39
- }
40
-
41
- fun getConfig(): Config {
42
- return this.lastConfig
43
- }
44
-
45
- fun getLayoutConfig(): LayoutConfig {
46
- return this.lastLayoutConfig
47
- }
48
-
49
- private fun getAppConfig(): Config {
50
- return Config(
51
- this.getColorScheme(),
52
- this.getContentSizeCategory(),
53
- )
54
- }
55
-
56
- private fun getAppLayoutConfig(): LayoutConfig {
57
- val displayMetrics = reactApplicationContext.resources.displayMetrics
58
- val screenWidth = (displayMetrics.widthPixels / density).toInt()
59
- val screenHeight = (displayMetrics.heightPixels / density).toInt()
60
-
61
- return LayoutConfig(
62
- Dimensions(screenWidth, screenHeight),
63
- this.insets.get(),
64
- Dimensions(screenWidth, getStatusBarHeight()),
65
- Dimensions(screenWidth, getNavigationBarHeight())
66
- )
67
- }
68
-
69
- fun getScreenDimensions(): Dimensions {
70
- val displayMetrics = reactApplicationContext.resources.displayMetrics
71
- val screenWidth = (displayMetrics.widthPixels / density).toInt()
72
- val screenHeight = (displayMetrics.heightPixels / density).toInt()
73
-
74
- return Dimensions(screenWidth, screenHeight)
75
- }
76
-
77
- fun getContentSizeCategory(): String {
78
- val fontScale = reactApplicationContext.resources.configuration.fontScale
79
-
80
- val contentSizeCategory = when {
81
- fontScale <= 0.85f -> "Small"
82
- fontScale <= 1.0f -> "Default"
83
- fontScale <= 1.15f -> "Large"
84
- fontScale <= 1.3f -> "ExtraLarge"
85
- fontScale <= 1.5f -> "Huge"
86
- fontScale <= 1.8 -> "ExtraHuge"
87
- else -> "ExtraExtraHuge"
88
- }
89
-
90
- return contentSizeCategory
91
- }
92
-
93
- fun getColorScheme(): String {
94
- val colorScheme = when (reactApplicationContext.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
95
- Configuration.UI_MODE_NIGHT_YES -> "dark"
96
- Configuration.UI_MODE_NIGHT_NO -> "light"
97
- else -> "unspecified"
98
- }
99
-
100
- return colorScheme
101
- }
102
-
103
- fun getStatusBarDimensions(): Dimensions {
104
- val displayMetrics = reactApplicationContext.resources.displayMetrics
105
- val screenWidth = (displayMetrics.widthPixels / density).toInt()
106
-
107
- return Dimensions(screenWidth, getStatusBarHeight())
108
- }
109
-
110
- fun getNavigationBarDimensions(): Dimensions {
111
- val displayMetrics = reactApplicationContext.resources.displayMetrics
112
- val screenWidth = (displayMetrics.widthPixels / density).toInt()
113
-
114
- return Dimensions(screenWidth, getNavigationBarHeight())
115
- }
116
-
117
- fun getInsets(): Insets {
118
- return this.insets.get()
119
- }
120
-
121
- @SuppressLint("InternalInsetResource", "DiscouragedApi")
122
- private fun getStatusBarHeight(): Int {
123
- val heightResId = reactApplicationContext.resources.getIdentifier("status_bar_height", "dimen", "android")
124
-
125
- if (heightResId > 0) {
126
- return (reactApplicationContext.resources.getDimensionPixelSize(heightResId) / density).toInt()
127
- }
128
-
129
- return 0
130
- }
131
-
132
- @SuppressLint("InternalInsetResource", "DiscouragedApi")
133
- private fun getNavigationBarHeight(): Int {
134
- val heightResId = reactApplicationContext.resources.getIdentifier("navigation_bar_height", "dimen", "android")
135
-
136
- if (heightResId > 0) {
137
- return (reactApplicationContext.resources.getDimensionPixelSize(heightResId) / density).toInt()
138
- }
139
-
140
- return 0
141
- }
142
- }
@@ -1,141 +0,0 @@
1
- package com.unistyles
2
-
3
- import android.content.Context
4
- import android.graphics.Rect
5
- import android.os.Build
6
- import android.view.View
7
- import android.view.Window
8
- import android.view.WindowInsets
9
- import android.view.WindowManager
10
- import com.facebook.react.bridge.ReactApplicationContext
11
- import kotlin.math.roundToInt
12
-
13
- class UnistylesInsets(private val reactApplicationContext: ReactApplicationContext) {
14
- private val density: Float = reactApplicationContext.resources.displayMetrics.density
15
-
16
- fun get(): Insets {
17
- return this.getCurrentInsets()
18
- }
19
-
20
- private fun getCurrentInsets(): Insets {
21
- val baseInsets = this.getBaseInsets()
22
- val statusBarTranslucent = this.hasTranslucentStatusBar() ?: return baseInsets
23
- val window = reactApplicationContext.currentActivity?.window ?: return baseInsets
24
- val shouldModifyLandscapeInsets = this.forceLandscapeInsets(baseInsets, window)
25
-
26
- val topInset = this.getTopInset(baseInsets, statusBarTranslucent, window)
27
- val bottomInset = this.getBottomInset(baseInsets, window)
28
- val leftInset = if (shouldModifyLandscapeInsets) 0 else baseInsets.left
29
- val rightInset = if (shouldModifyLandscapeInsets) 0 else baseInsets.right
30
-
31
- return Insets(topInset, bottomInset, leftInset, rightInset)
32
- }
33
-
34
- @Suppress("DEPRECATION")
35
- private fun getBaseInsets(): Insets {
36
- // this is the best API, but it's available from Android 11
37
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
38
- val windowManager = reactApplicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
39
- val systemBarsInsets = windowManager.currentWindowMetrics.windowInsets.getInsets(WindowInsets.Type.systemBars())
40
-
41
- val top = (systemBarsInsets.top / density).roundToInt()
42
- val bottom = (systemBarsInsets.bottom / density).roundToInt()
43
- val left = (systemBarsInsets.left / density).roundToInt()
44
- val right = (systemBarsInsets.right / density).roundToInt()
45
-
46
- return Insets(top, bottom, left, right)
47
- }
48
-
49
- // available from Android 6.0
50
- val window = reactApplicationContext.currentActivity?.window ?: return Insets(0, 0, 0, 0)
51
- val systemInsets = window.decorView.rootWindowInsets ?: return Insets(0, 0, 0, 0)
52
-
53
- val top = (systemInsets.systemWindowInsetTop / density).roundToInt()
54
- val bottom = (systemInsets.systemWindowInsetBottom / density).roundToInt()
55
- val left = (systemInsets.systemWindowInsetLeft / density).roundToInt()
56
- val right = (systemInsets.systemWindowInsetRight / density).roundToInt()
57
-
58
- return Insets(top, bottom, left, right)
59
- }
60
-
61
- // this function helps to detect statusBar translucent, as React Native is using weird API instead of windows flags
62
- private fun hasTranslucentStatusBar(): Boolean? {
63
- val window = reactApplicationContext.currentActivity?.window ?: return null
64
- val contentView = window.decorView.findViewById<View>(android.R.id.content) ?: return null
65
- val decorViewLocation = IntArray(2)
66
- val contentViewLocation = IntArray(2)
67
-
68
- // decor view is a top level view with navigationBar and statusBar
69
- window.decorView.getLocationOnScreen(decorViewLocation)
70
- // content view is view without navigationBar and statusBar
71
- contentView.getLocationOnScreen(contentViewLocation)
72
-
73
- val statusBarHeight = contentViewLocation[1] - decorViewLocation[1]
74
-
75
- // if positions are the same, then the status bar is translucent
76
- return statusBarHeight == 0
77
- }
78
-
79
- private fun getTopInset(baseInsets: Insets, statusBarTranslucent: Boolean, window: Window): Int {
80
- if (!statusBarTranslucent) {
81
- return 0
82
- }
83
-
84
- return baseInsets.top
85
- }
86
-
87
- @Suppress("DEPRECATION")
88
- private fun getBottomInset(baseInsets: Insets, window: Window): Int {
89
- val translucentNavigation = hasTranslucentNavigation(window)
90
- val fullScreenMode = hasFullScreenMode(window)
91
-
92
- // Android 11 has inconsistent FLAG_LAYOUT_NO_LIMITS, which alone does nothing
93
- // https://stackoverflow.com/questions/64153785/android-11-window-setdecorfitssystemwindow-doesnt-show-screen-behind-status-a
94
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
95
- if ((fullScreenMode && translucentNavigation) || translucentNavigation) {
96
- return baseInsets.bottom
97
- }
98
-
99
- return 0
100
- }
101
-
102
- return if (translucentNavigation || fullScreenMode) baseInsets.bottom else 0
103
- }
104
-
105
- private fun forceLandscapeInsets(baseInsets: Insets, window: Window): Boolean {
106
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
107
- return false
108
- }
109
-
110
- val displayMetrics = reactApplicationContext.resources.displayMetrics
111
- val isLandscape = displayMetrics.widthPixels > displayMetrics.heightPixels
112
-
113
- if (!isLandscape) {
114
- return false
115
- }
116
-
117
- val contentView = window.decorView.findViewById<View>(android.R.id.content) ?: return false
118
- val visibleRect = Rect()
119
- val drawingRect = Rect()
120
-
121
- window.decorView.getGlobalVisibleRect(visibleRect)
122
- contentView.getDrawingRect(drawingRect)
123
-
124
- // width is equal to navigationBarHeight + statusBarHeight (in landscape)
125
- val width = ((visibleRect.width() - contentView.width) / density).roundToInt()
126
-
127
- // we should correct landscape if insets are equal to width
128
- return (baseInsets.left + baseInsets.right) == width
129
- }
130
-
131
- @Suppress("DEPRECATION")
132
- private fun hasTranslucentNavigation(window: Window): Boolean {
133
- return (window.attributes.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
134
- }
135
-
136
- private fun hasFullScreenMode(window: Window): Boolean {
137
- val decorFitsSystemWindows = window.decorView.fitsSystemWindows
138
-
139
- return (window.attributes.flags and WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) == WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS || !decorFitsSystemWindows
140
- }
141
- }