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.
- package/android/src/main/cxx/platform.cpp +2 -2
- package/android/src/main/java/com/unistyles/Models.kt +14 -35
- package/android/src/main/java/com/unistyles/Platform.kt +87 -23
- package/android/src/main/java/com/unistyles/UnistylesModule.kt +60 -52
- package/cxx/UnistylesModel.cpp +11 -7
- package/package.json +1 -1
- package/android/src/main/java/com/unistyles/Config.kt +0 -142
- package/android/src/main/java/com/unistyles/Insets.kt +0 -141
@@ -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([
|
28
|
+
unistylesRuntime->setNavigationBarColorCallback([=](const std::string &color) {
|
29
29
|
setNavigationBarColor(env, unistylesModule, color);
|
30
30
|
});
|
31
31
|
|
32
|
-
unistylesRuntime->setStatusBarColorCallback([
|
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
|
40
|
-
val
|
41
|
-
val
|
42
|
-
val
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
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
|
12
|
-
|
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
|
-
|
16
|
-
return this.config.hasNewConfig()
|
23
|
+
return Dimensions(screenWidth, screenHeight)
|
17
24
|
}
|
18
25
|
|
19
|
-
fun
|
20
|
-
|
21
|
-
}
|
26
|
+
fun getColorScheme(): String {
|
27
|
+
val uiMode = this.reactApplicationContext.resources.configuration.uiMode
|
22
28
|
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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 (
|
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
|
-
|
83
|
-
|
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
|
-
|
90
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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.
|
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.
|
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() {}
|
package/cxx/UnistylesModel.cpp
CHANGED
@@ -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,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
|
-
}
|