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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- }