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.
- 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
|
-
}
|