react-native-unistyles 3.0.0-alpha.36 → 3.0.0-alpha.37

Sign up to get free protection for your applications and to get access to all the features.
package/Unistyles.podspec CHANGED
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
  ]
20
20
  s.pod_target_xcconfig = {
21
21
  "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
22
- "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
22
+ "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES FOLLY_MOBILE"
23
23
  }
24
24
 
25
25
  s.public_header_files = [
@@ -23,7 +23,7 @@ include_directories(
23
23
  ../cxx/shadowTree
24
24
  )
25
25
 
26
- string(APPEND CMAKE_CXX_FLAGS "-DFOLLY_NO_CONFIG")
26
+ string(APPEND CMAKE_CXX_FLAGS "-DFOLLY_NO_CONFIG=1 -DFOLLY_CFG_NO_COROUTINES=1 -DFOLLY_MOBILE=1")
27
27
 
28
28
  set_target_properties(unistyles PROPERTIES
29
29
  CXX_STANDARD 20
@@ -32,17 +32,5 @@ set_target_properties(unistyles PROPERTIES
32
32
  POSITION_INDEPENDENT_CODE ON
33
33
  )
34
34
 
35
- # todo included by nitrogen
36
- # find_package(ReactAndroid REQUIRED CONFIG)
37
- # find_package(fbjni REQUIRED CONFIG)
38
-
39
- #if (ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
40
- # target_link_libraries(unistyles ReactAndroid::reactnative)
41
- #else()
42
- # target_link_libraries(unistyles
43
- # ReactAndroid::turbomodulejsijni
44
- # ReactAndroid::react_nativemodule_core
45
- # android
46
- # fbjni::fbjni
47
- # )
48
- #endif()
35
+ # For React Native 0.76 and above, we don't need to link anything
36
+ # as NitroModules will automatically add ReactAndroid::reactnative prefab
@@ -49,7 +49,7 @@ jni::local_ref<BindingsInstallerHolder::javaobject> UnistylesModule::getBindings
49
49
  }
50
50
 
51
51
  auto runOnJSThread = [&runtimeExecutor](std::function<void(jsi::Runtime&)>&& callback) {
52
- runtimeExecutor([&](jsi::Runtime &rt) {
52
+ runtimeExecutor([callback = std::move(callback)](jsi::Runtime &rt) {
53
53
  callback(rt);
54
54
  });
55
55
  };
@@ -0,0 +1,61 @@
1
+ package com.unistyles
2
+
3
+ import com.margelo.nitro.unistyles.Dimensions
4
+ import com.margelo.nitro.unistyles.Insets
5
+ import com.margelo.nitro.unistyles.UnistyleDependency
6
+ import com.margelo.nitro.unistyles.UnistylesNativeMiniRuntime
7
+
8
+ fun Dimensions.isEqualTo(other: Dimensions): Boolean {
9
+ return this.width == other.width && this.height == other.height
10
+ }
11
+
12
+ fun Insets.isEqualTo(other: Insets): Boolean {
13
+ return this.top == other.top && this.bottom == other.bottom &&
14
+ this.left == other.left && this.right == other.right &&
15
+ this.ime == other.ime
16
+ }
17
+
18
+ fun NativePlatformAndroid.diffMiniRuntimes(lhs: UnistylesNativeMiniRuntime, rhs: UnistylesNativeMiniRuntime): Array<UnistyleDependency> {
19
+ val dependencies: MutableList<UnistyleDependency> = mutableListOf()
20
+
21
+ if (lhs.colorScheme != rhs.colorScheme) {
22
+ dependencies.add(UnistyleDependency.COLORSCHEME)
23
+ }
24
+
25
+ if (!lhs.screen.isEqualTo(rhs.screen)) {
26
+ dependencies.add(UnistyleDependency.DIMENSIONS)
27
+ }
28
+
29
+ if (lhs.screen.width != rhs.screen.width) {
30
+ dependencies.add(UnistyleDependency.BREAKPOINTS)
31
+ }
32
+
33
+ // no need to check isLandscape, as it's always opposite
34
+ if (lhs.isPortrait != rhs.isPortrait) {
35
+ dependencies.add(UnistyleDependency.ORIENTATION)
36
+ }
37
+
38
+ if (lhs.contentSizeCategory != rhs.contentSizeCategory) {
39
+ dependencies.add(UnistyleDependency.CONTENTSIZECATEGORY)
40
+ }
41
+
42
+ if (!lhs.insets.isEqualTo(rhs.insets)) {
43
+ dependencies.add(UnistyleDependency.INSETS)
44
+ }
45
+
46
+ if (lhs.fontScale != rhs.fontScale) {
47
+ dependencies.add(UnistyleDependency.FONTSCALE)
48
+ }
49
+
50
+ if (!lhs.statusBar.isEqualTo(rhs.statusBar)) {
51
+ dependencies.add(UnistyleDependency.STATUSBAR)
52
+ }
53
+
54
+ if (!lhs.navigationBar.isEqualTo(rhs.navigationBar)) {
55
+ dependencies.add(UnistyleDependency.NAVIGATIONBAR)
56
+ }
57
+
58
+ // rtl and pixel ratio are not dynamic
59
+
60
+ return dependencies.toTypedArray()
61
+ }
@@ -0,0 +1,302 @@
1
+ package com.unistyles
2
+
3
+ import android.content.Context
4
+ import android.content.res.Configuration
5
+ import android.os.Build
6
+ import android.util.DisplayMetrics
7
+ import android.view.View
8
+ import android.view.WindowManager
9
+ import androidx.core.text.TextUtilsCompat
10
+ import androidx.core.view.ViewCompat
11
+ import androidx.core.view.WindowCompat
12
+ import androidx.core.view.WindowInsetsCompat
13
+ import androidx.core.view.WindowInsetsControllerCompat
14
+ import com.facebook.react.bridge.LifecycleEventListener
15
+ import com.facebook.react.bridge.ReactApplicationContext
16
+ import com.margelo.nitro.unistyles.ColorScheme
17
+ import com.margelo.nitro.unistyles.Dimensions
18
+ import com.margelo.nitro.unistyles.HybridNativePlatformSpec
19
+ import com.margelo.nitro.unistyles.Insets
20
+ import com.margelo.nitro.unistyles.Orientation
21
+ import com.margelo.nitro.unistyles.UnistyleDependency
22
+ import com.margelo.nitro.unistyles.UnistylesNativeMiniRuntime
23
+ import java.util.Locale
24
+
25
+ class NativePlatformAndroid(private val reactContext: ReactApplicationContext): HybridNativePlatformSpec(), LifecycleEventListener {
26
+ private val _insets = NativePlatformInsets(reactContext) { this.diffMiniRuntime() }
27
+ private var _miniRuntime: UnistylesNativeMiniRuntime = buildMiniRuntime()
28
+ private val _listener = NativePlatformListener(reactContext) { this.diffMiniRuntime() }
29
+
30
+ init {
31
+ reactContext.addLifecycleEventListener(this)
32
+ }
33
+
34
+ fun invalidate() {
35
+ reactContext.removeLifecycleEventListener(this)
36
+ _listener.invalidate()
37
+ }
38
+
39
+ override fun onHostResume() {
40
+ enableEdgeToEdge()
41
+ _insets.startInsetsListener()
42
+ }
43
+
44
+ override fun onHostPause() {
45
+ _insets.stopInsetsListener()
46
+ }
47
+
48
+ override fun onHostDestroy() {}
49
+
50
+ override val memorySize: Long
51
+ get() = 0
52
+
53
+ override fun getInsets(): Insets {
54
+ return _insets.getInsets()
55
+ }
56
+
57
+ override fun getColorScheme(): ColorScheme {
58
+ val uiMode = reactContext.resources.configuration.uiMode
59
+
60
+ val colorScheme = when (uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
61
+ Configuration.UI_MODE_NIGHT_YES -> ColorScheme.DARK
62
+ Configuration.UI_MODE_NIGHT_NO -> ColorScheme.LIGHT
63
+ else -> ColorScheme.UNSPECIFIED
64
+ }
65
+
66
+ return colorScheme
67
+ }
68
+
69
+ override fun getFontScale(): Double {
70
+ return reactContext.resources.configuration.fontScale.toDouble()
71
+ }
72
+
73
+ override fun getPixelRatio(): Double {
74
+ return reactContext.resources.displayMetrics.density.toDouble()
75
+ }
76
+
77
+ override fun getOrientation(): Orientation {
78
+ val orientation = when (reactContext.resources.configuration.orientation) {
79
+ Configuration.ORIENTATION_PORTRAIT -> Orientation.PORTRAIT
80
+ Configuration.ORIENTATION_LANDSCAPE -> Orientation.LANDSCAPE
81
+ else -> Orientation.PORTRAIT
82
+ }
83
+
84
+ return orientation
85
+ }
86
+
87
+ override fun getContentSizeCategory(): String {
88
+ val fontScale = getFontScale()
89
+
90
+ val contentSizeCategory = when {
91
+ fontScale <= 0.85f -> "Small"
92
+ fontScale <= 1.0f -> "Default"
93
+ fontScale <= 1.15f -> "Large"
94
+ fontScale <= 1.3f -> "ExtraLarge"
95
+ fontScale <= 1.5f -> "Huge"
96
+ fontScale <= 1.8 -> "ExtraHuge"
97
+ else -> "ExtraExtraHuge"
98
+ }
99
+
100
+ return contentSizeCategory
101
+ }
102
+
103
+ override fun getScreenDimensions(): Dimensions {
104
+ // function takes in count edge-to-edge layout
105
+ when {
106
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> {
107
+ val windowManager = reactContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
108
+ val metrics = DisplayMetrics()
109
+
110
+ @Suppress("DEPRECATION")
111
+ windowManager.defaultDisplay.getRealMetrics(metrics)
112
+
113
+ val screenWidth = (metrics.widthPixels / metrics.density).toDouble()
114
+ val screenHeight = (metrics.heightPixels / metrics.density).toDouble()
115
+
116
+ return Dimensions(screenWidth, screenHeight)
117
+ }
118
+ else -> {
119
+ val displayMetrics = reactContext.resources.displayMetrics
120
+
121
+ reactContext.currentActivity?.windowManager?.currentWindowMetrics?.bounds?.let {
122
+ val boundsWidth = (it.width() / displayMetrics.density).toDouble()
123
+ val boundsHeight = (it.height() / displayMetrics.density).toDouble()
124
+
125
+ return Dimensions(boundsWidth, boundsHeight)
126
+ } ?: run {
127
+ val screenWidth = (displayMetrics.widthPixels / displayMetrics.density).toDouble()
128
+ val screenHeight = (displayMetrics.heightPixels / displayMetrics.density).toDouble()
129
+
130
+ return Dimensions(screenWidth, screenHeight)
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ override fun getStatusBarDimensions(): Dimensions {
137
+ val screenWidth = getScreenDimensions().width
138
+
139
+ return Dimensions(screenWidth, _insets.getInsets().top)
140
+ }
141
+
142
+ override fun getNavigationBarDimensions(): Dimensions {
143
+ val screenWidth = getScreenDimensions().width
144
+
145
+ return Dimensions(screenWidth, _insets.getInsets().bottom)
146
+ }
147
+
148
+ override fun getPrefersRtlDirection(): Boolean {
149
+ // forced by React Native
150
+ val sharedPrefs = reactContext.getSharedPreferences(
151
+ "com.facebook.react.modules.i18nmanager.I18nUtil",
152
+ Context.MODE_PRIVATE
153
+ )
154
+ val hasForcedRtl = sharedPrefs.getBoolean("RCTI18nUtil_forceRTL", false)
155
+ // user preferences
156
+ val isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
157
+
158
+ return hasForcedRtl || isRtl
159
+ }
160
+
161
+ override fun setRootViewBackgroundColor(color: Double) {
162
+ reactContext.currentActivity?.let { activity ->
163
+ activity.window?.decorView?.let { decorView ->
164
+ activity.runOnUiThread {
165
+ decorView.rootView.setBackgroundColor(color.toInt())
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ @Suppress("DEPRECATION")
172
+ override fun setNavigationBarBackgroundColor(color: Double) {
173
+ reactContext.currentActivity?.let { activity ->
174
+ activity.runOnUiThread {
175
+ activity.window.navigationBarColor = color.toInt()
176
+ }
177
+ }
178
+ }
179
+
180
+ override fun setNavigationBarHidden(isHidden: Boolean) {
181
+ reactContext.currentActivity?.let { activity ->
182
+ WindowInsetsControllerCompat(activity.window, activity.window.decorView).apply {
183
+ activity.window?.decorView?.let { decorView ->
184
+ @Suppress("DEPRECATION")
185
+ activity.runOnUiThread {
186
+ if (isHidden) {
187
+ // below Android 11, we need to use window flags to hide the navigation bar
188
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
189
+ decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
190
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
191
+ } else {
192
+ hide(WindowInsetsCompat.Type.navigationBars())
193
+ systemBarsBehavior =
194
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
195
+ }
196
+
197
+ // dispatch new insets to invoke the insets listener
198
+ val newInsets = WindowInsetsCompat.Builder()
199
+ .setInsets(WindowInsetsCompat.Type.navigationBars(), androidx.core.graphics.Insets.of(0, 0, 0, 0))
200
+ .build()
201
+
202
+ ViewCompat.dispatchApplyWindowInsets(activity.findViewById(android.R.id.content), newInsets)
203
+ } else {
204
+ show(WindowInsetsCompat.Type.navigationBars())
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ override fun setStatusBarHidden(isHidden: Boolean) {
213
+ reactContext.currentActivity?.let { activity ->
214
+ WindowInsetsControllerCompat(activity.window, activity.window.decorView).apply {
215
+ activity.window?.let { window ->
216
+ @Suppress("DEPRECATION")
217
+ activity.runOnUiThread {
218
+ if (isHidden) {
219
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
220
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
221
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)
222
+ } else {
223
+ hide(WindowInsetsCompat.Type.statusBars())
224
+ }
225
+ } else {
226
+ show(WindowInsetsCompat.Type.statusBars())
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ @Suppress("DEPRECATION")
235
+ override fun setStatusBarBackgroundColor(color: Double) {
236
+ reactContext.currentActivity?.let { activity ->
237
+ activity.runOnUiThread {
238
+ activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
239
+ activity.window.statusBarColor = color.toInt()
240
+ }
241
+ }
242
+ }
243
+
244
+ override fun setImmersiveMode(isEnabled: Boolean) {
245
+ this.setStatusBarHidden(isEnabled)
246
+ this.setNavigationBarHidden(isEnabled)
247
+ }
248
+
249
+ override fun getMiniRuntime(): UnistylesNativeMiniRuntime {
250
+ return _miniRuntime
251
+ }
252
+
253
+ private fun buildMiniRuntime(): UnistylesNativeMiniRuntime {
254
+ val orientation = this.getOrientation()
255
+
256
+ return UnistylesNativeMiniRuntime(
257
+ colorScheme = this.getColorScheme(),
258
+ screen = this.getScreenDimensions(),
259
+ contentSizeCategory = this.getContentSizeCategory(),
260
+ insets = this.getInsets(),
261
+ pixelRatio = this.getPixelRatio(),
262
+ fontScale = this.getFontScale(),
263
+ rtl = this.getPrefersRtlDirection(),
264
+ statusBar = this.getStatusBarDimensions(),
265
+ navigationBar = this.getNavigationBarDimensions(),
266
+ isPortrait = orientation == Orientation.PORTRAIT,
267
+ isLandscape = orientation == Orientation.LANDSCAPE
268
+ )
269
+ }
270
+
271
+ private fun diffMiniRuntime(): Array<UnistyleDependency> {
272
+ val newMiniRuntime = this.buildMiniRuntime()
273
+ val changedDependencies = diffMiniRuntimes(this._miniRuntime, newMiniRuntime)
274
+
275
+ if (changedDependencies.isNotEmpty()) {
276
+ this._miniRuntime = newMiniRuntime
277
+ }
278
+
279
+ return changedDependencies
280
+ }
281
+
282
+ override fun registerPlatformListener(callback: (dependencies: Array<UnistyleDependency>) -> Unit) {
283
+ this._listener.addPlatformListener(callback)
284
+ }
285
+
286
+ override fun registerImeListener(callback: () -> Unit) {
287
+ this._insets.addImeListener(callback)
288
+ }
289
+
290
+ override fun unregisterPlatformListeners() {
291
+ this._listener.removePlatformListeners()
292
+ this._insets.removeImeListeners()
293
+ }
294
+
295
+ private fun enableEdgeToEdge() {
296
+ reactContext.currentActivity?.let { activity ->
297
+ activity.runOnUiThread {
298
+ WindowCompat.setDecorFitsSystemWindows(activity.window, false)
299
+ }
300
+ }
301
+ }
302
+ }
@@ -0,0 +1,148 @@
1
+ package com.unistyles
2
+
3
+ import android.graphics.Rect
4
+ import android.os.Build
5
+ import android.view.View
6
+ import android.view.Window
7
+ import android.view.WindowManager
8
+ import androidx.core.view.ViewCompat
9
+ import androidx.core.view.WindowInsetsAnimationCompat
10
+ import androidx.core.view.WindowInsetsCompat
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import com.margelo.nitro.unistyles.Insets
13
+ import com.margelo.nitro.unistyles.UnistyleDependency
14
+
15
+ typealias CxxImeListener = () -> Unit
16
+
17
+ class NativePlatformInsets(private val reactContext: ReactApplicationContext, private val diffMiniRuntime: () -> Array<UnistyleDependency>) {
18
+ private val _imeListeners: MutableList<CxxImeListener> = mutableListOf()
19
+ private var _insets: Insets = Insets(0.0, 0.0, 0.0, 0.0, 0.0)
20
+
21
+ fun getInsets(): Insets {
22
+ val density = reactContext.resources.displayMetrics.density
23
+
24
+ return Insets(
25
+ this._insets.top / density,
26
+ this._insets.bottom / density,
27
+ this._insets.left / density,
28
+ this._insets.right / density,
29
+ this._insets.ime / density
30
+ )
31
+ }
32
+
33
+ fun setInsets(insetsCompat: WindowInsetsCompat, window: Window, animatedBottomInsets: Double?) {
34
+ // below Android 11, we need to use window flags to detect status bar visibility
35
+ val isStatusBarVisible = when(Build.VERSION.SDK_INT) {
36
+ in 30..Int.MAX_VALUE -> {
37
+ insetsCompat.isVisible(WindowInsetsCompat.Type.statusBars())
38
+ }
39
+ else -> {
40
+ @Suppress("DEPRECATION")
41
+ window.attributes.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN != WindowManager.LayoutParams.FLAG_FULLSCREEN
42
+ }
43
+ }
44
+ // React Native is forcing insets to make status bar translucent
45
+ // so we need to calculate top inset manually, as WindowInsetCompat will always return 0
46
+ val statusBarTopInset = when(isStatusBarVisible) {
47
+ true -> {
48
+ val visibleRect = Rect()
49
+
50
+ window.decorView.getWindowVisibleDisplayFrame(visibleRect)
51
+
52
+ visibleRect.top
53
+ }
54
+ false -> 0
55
+ }
56
+
57
+ val insets = insetsCompat.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
58
+
59
+ // Android 10 and below - set bottom insets to 0 while keyboard is visible and use default bottom insets otherwise
60
+ // Android 11 and above - animate bottom insets while keyboard is appearing and disappearing
61
+ val imeInsets = if (Build.VERSION.SDK_INT >= 30) {
62
+ animatedBottomInsets ?: this._insets.ime
63
+ } else {
64
+ val nextBottomInset = insetsCompat.getInsets(WindowInsetsCompat.Type.ime()).bottom - insets.bottom
65
+
66
+ // call new IME event here, as for SDK >= 30 it's called in AnimationCallback
67
+ this@NativePlatformInsets.emitImeEvent()
68
+
69
+ if (nextBottomInset < 0) {
70
+ 0
71
+ } else {
72
+ nextBottomInset
73
+ }
74
+ }
75
+
76
+ this._insets = Insets(
77
+ statusBarTopInset.toDouble(),
78
+ insets.bottom.toDouble(),
79
+ insets.left.toDouble(),
80
+ insets.right.toDouble(),
81
+ imeInsets.toDouble()
82
+ )
83
+
84
+ diffMiniRuntime()
85
+ }
86
+
87
+ fun startInsetsListener() {
88
+ reactContext.currentActivity?.let { activity ->
89
+ activity.findViewById<View>(android.R.id.content)?.let { mainView ->
90
+ ViewCompat.setOnApplyWindowInsetsListener(mainView) { _, insets ->
91
+ setInsets(insets, activity.window, null)
92
+
93
+ insets
94
+ }
95
+
96
+ // IME insets are available from Android 11
97
+ if (Build.VERSION.SDK_INT >= 30) {
98
+ ViewCompat.setWindowInsetsAnimationCallback(
99
+ mainView,
100
+ object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
101
+ override fun onProgress(
102
+ insets: WindowInsetsCompat,
103
+ runningAnimations: List<WindowInsetsAnimationCompat>
104
+ ): WindowInsetsCompat {
105
+ runningAnimations.firstOrNull()?.let {
106
+ val bottomInset = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom.toDouble() - this@NativePlatformInsets._insets.bottom
107
+ val nextBottomInset = if (bottomInset < 0) {
108
+ 0.0
109
+ } else {
110
+ bottomInset
111
+ }
112
+
113
+ this@NativePlatformInsets.setInsets(insets, activity.window, nextBottomInset)
114
+ this@NativePlatformInsets.emitImeEvent()
115
+ }
116
+
117
+ return insets
118
+ }
119
+ }
120
+ )
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ fun emitImeEvent() {
127
+ _imeListeners.forEach { listener ->
128
+ listener()
129
+ }
130
+ }
131
+
132
+ fun stopInsetsListener() {
133
+ reactContext.currentActivity?.let { activity ->
134
+ activity.window?.decorView?.let { view ->
135
+ ViewCompat.setOnApplyWindowInsetsListener(view, null)
136
+ ViewCompat.setWindowInsetsAnimationCallback(view, null)
137
+ }
138
+ }
139
+ }
140
+
141
+ fun addImeListener(listener: CxxImeListener) {
142
+ this._imeListeners.add(listener)
143
+ }
144
+
145
+ fun removeImeListeners() {
146
+ this._imeListeners.clear()
147
+ }
148
+ }
@@ -0,0 +1,54 @@
1
+ package com.unistyles
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.content.IntentFilter
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import com.facebook.react.bridge.ReactApplicationContext
10
+ import com.margelo.nitro.unistyles.UnistyleDependency
11
+
12
+ typealias CxxDependencyListener = (dependencies: Array<UnistyleDependency>) -> Unit
13
+
14
+ class NativePlatformListener(private val reactContext: ReactApplicationContext, private val diffMiniRuntime: () -> Array<UnistyleDependency>) {
15
+ private val _dependencyListeners: MutableList<CxxDependencyListener> = mutableListOf()
16
+
17
+ private val configurationChangeReceiver = object : BroadcastReceiver() {
18
+ override fun onReceive(context: Context, intent: Intent) {
19
+ Handler(Looper.getMainLooper()).postDelayed({
20
+ this@NativePlatformListener.onConfigChange()
21
+ }, 10)
22
+ }
23
+ }
24
+
25
+ init {
26
+ reactContext.registerReceiver(configurationChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED))
27
+ }
28
+
29
+ fun invalidate() {
30
+ reactContext.unregisterReceiver(configurationChangeReceiver)
31
+ }
32
+
33
+ fun addPlatformListener(listener: CxxDependencyListener) {
34
+ this._dependencyListeners.add(listener)
35
+ }
36
+
37
+ fun removePlatformListeners() {
38
+ this._dependencyListeners.clear()
39
+ }
40
+
41
+ private fun emitCxxEvent(dependencies: Array<UnistyleDependency>) {
42
+ this._dependencyListeners.forEach { listener ->
43
+ listener(dependencies)
44
+ }
45
+ }
46
+
47
+ fun onConfigChange() {
48
+ val changedDependencies = diffMiniRuntime()
49
+
50
+ if (changedDependencies.isNotEmpty()) {
51
+ emitCxxEvent(changedDependencies)
52
+ }
53
+ }
54
+ }
@@ -16,7 +16,7 @@ import com.margelo.nitro.unistyles.HybridNativePlatformSpec
16
16
  class UnistylesModule(reactContext: ReactApplicationContext): NativeTurboUnistylesSpec(reactContext), TurboModuleWithJSIBindings {
17
17
  @DoNotStrip
18
18
  private var mHybridData: HybridData?
19
- private val _nativePlatform = NativePlatform(reactContext)
19
+ private val _nativePlatform = NativePlatformAndroid(reactContext)
20
20
 
21
21
  companion object {
22
22
  const val NAME = NativeTurboUnistylesSpec.NAME
@@ -26,6 +26,10 @@ class UnistylesModule(reactContext: ReactApplicationContext): NativeTurboUnistyl
26
26
  mHybridData = initializeHybridData(reactContext)
27
27
  }
28
28
 
29
+ override fun invalidate() {
30
+ _nativePlatform.invalidate()
31
+ }
32
+
29
33
  private fun initializeHybridData(reactContext: ReactApplicationContext): HybridData {
30
34
  val runtimeExecutor = reactContext.catalystInstance?.runtimeExecutor
31
35
  ?: throw IllegalStateException("Unistyles: React Native runtime executor is not available. Please follow installation guides.")
@@ -12,7 +12,7 @@ struct UnistyleData {
12
12
  : unistyle{unistyle}, variants(std::move(variants)), dynamicFunctionMetadata{std::move(arguments)} {}
13
13
 
14
14
  UnistyleData(const UnistyleData&) = delete;
15
- UnistyleData(UnistyleData&& other): unistyle{other.unistyle}, variants(std::move(other.variants)) {}
15
+ UnistyleData(UnistyleData&& other) = delete;
16
16
 
17
17
  core::Unistyle::Shared unistyle;
18
18
  core::Variants variants;
@@ -32,7 +32,6 @@ inline static Unistyle::Shared unistyleFromStaticStyleSheet(jsi::Runtime& rt, js
32
32
  return exoticUnistyle;
33
33
  }
34
34
 
35
-
36
35
  inline static std::vector<Unistyle::Shared> unistylesFromNonExistentNativeState(jsi::Runtime& rt, jsi::Object& value) {
37
36
  auto hasUnistyleName = value.hasProperty(rt, helpers::NAME_STYLE_KEY.c_str());
38
37
 
@@ -71,7 +70,7 @@ inline static jsi::Object generateUnistylesPrototype(
71
70
  auto hostFn = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forUtf8(rt, "getStyle"), 0, [unistyle, unistylesRuntime](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count){
72
71
  auto variants = helpers::variantsToPairs(rt, thisValue.asObject(rt).getProperty(rt, "variants").asObject(rt));
73
72
  auto arguments = helpers::parseDynamicFunctionArguments(rt, thisValue.asObject(rt).getProperty(rt, "arguments").asObject(rt).asArray(rt));
74
-
73
+
75
74
  parser::Parser(unistylesRuntime).rebuildUnistyle(rt, unistyle->parent, unistyle, variants, std::make_optional<std::vector<folly::dynamic>>(arguments));
76
75
 
77
76
  return jsi::Value(rt, unistyle->parsedStyle.value()).asObject(rt);
@@ -31,7 +31,7 @@ RootShadowNode::Unshared core::UnistylesCommitHook::shadowTreeWillCommit(
31
31
  auto& registry = core::UnistylesRegistry::get();
32
32
  auto& shadowLeafUpdates = registry.trafficController.getUpdates();
33
33
 
34
- // oops, not updates from Unistyles yet, skip it!
34
+ // oops, no updates from Unistyles yet, skip it!
35
35
  if (shadowLeafUpdates.size() == 0) {
36
36
  return newRootShadowNode;
37
37
  }
@@ -23,6 +23,6 @@ void core::UnistylesMountHook::shadowTreeDidMount(RootShadowNode::Shared const &
23
23
  auto& registry = core::UnistylesRegistry::get();
24
24
 
25
25
  if (!registry.trafficController.shouldStop()) {
26
- shadow::ShadowTreeManager::updateShadowTree(this->_unistylesRuntime->getRuntime());
26
+ shadow::ShadowTreeManager::updateShadowTree(this->_uiManager->getShadowTreeRegistry());
27
27
  }
28
28
  }
@@ -78,10 +78,6 @@ void core::UnistylesRegistry::linkShadowNodeWithUnistyle(
78
78
  auto parser = parser::Parser(nullptr);
79
79
  shadow::ShadowLeafUpdates updates;
80
80
 
81
- if (!this->_shadowRegistry[&rt].contains(shadowNodeFamily)) {
82
- this->_shadowRegistry[&rt][shadowNodeFamily] = {};
83
- }
84
-
85
81
  std::for_each(unistyles.begin(), unistyles.end(), [&, this](Unistyle::Shared unistyle){
86
82
  this->_shadowRegistry[&rt][shadowNodeFamily].emplace_back(std::make_shared<UnistyleData>(unistyle, variants, arguments));
87
83
 
@@ -104,16 +100,11 @@ void core::UnistylesRegistry::linkShadowNodeWithUnistyle(
104
100
 
105
101
  void core::UnistylesRegistry::unlinkShadowNodeWithUnistyles(jsi::Runtime& rt, const ShadowNodeFamily* shadowNodeFamily) {
106
102
  this->_shadowRegistry[&rt].erase(shadowNodeFamily);
107
- }
103
+ this->trafficController.removeShadowNode(shadowNodeFamily);
108
104
 
109
- core::Unistyle::Shared core::UnistylesRegistry::findUnistyleFromKey(jsi::Runtime& rt, std::string styleKey, int tag) {
110
- auto targetStyleSheet = this->_styleSheetRegistry[&rt][tag];
111
-
112
- if (targetStyleSheet == nullptr) {
113
- return nullptr;
105
+ if (this->_shadowRegistry[&rt].empty()) {
106
+ this->_shadowRegistry.erase(&rt);
114
107
  }
115
-
116
- return targetStyleSheet.get()->unistyles[styleKey];
117
108
  }
118
109
 
119
110
  std::shared_ptr<core::StyleSheet> core::UnistylesRegistry::addStyleSheet(jsi::Runtime& rt, int unid, core::StyleSheetType type, jsi::Object&& rawValue) {
@@ -162,7 +153,7 @@ void core::UnistylesRegistry::shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Uni
162
153
  for (const auto& [family, unistyles] : this->_shadowRegistry[&rt]) {
163
154
  for (const auto& unistyleData : unistyles) {
164
155
  if (unistyleData->unistyle == unistyle) {
165
- updates[family] = parser.parseStylesToShadowTreeStyles(rt, {unistyleData});
156
+ updates[family] = parser.parseStylesToShadowTreeStyles(rt, { unistyleData });
166
157
  }
167
158
  }
168
159
  }
@@ -41,7 +41,6 @@ struct UnistylesRegistry: public StyleSheetRegistry {
41
41
  void unlinkShadowNodeWithUnistyles(jsi::Runtime& rt, const ShadowNodeFamily*);
42
42
  std::shared_ptr<core::StyleSheet> addStyleSheet(jsi::Runtime& rt, int tag, core::StyleSheetType type, jsi::Object&& rawValue);
43
43
  DependencyMap buildDependencyMap(jsi::Runtime& rt, std::vector<UnistyleDependency>& deps);
44
- Unistyle::Shared findUnistyleFromKey(jsi::Runtime& rt, std::string styleKey, int tag);
45
44
  void shadowLeafUpdateFromUnistyle(jsi::Runtime& rt, Unistyle::Shared unistyle);
46
45
  shadow::ShadowTrafficController trafficController{};
47
46
 
@@ -50,7 +49,7 @@ private:
50
49
 
51
50
  std::unordered_map<jsi::Runtime*, UnistylesState> _states{};
52
51
  std::unordered_map<jsi::Runtime*, std::unordered_map<int, std::shared_ptr<core::StyleSheet>>> _styleSheetRegistry{};
53
- std::unordered_map<jsi::Runtime*, std::unordered_map<const ShadowNodeFamily*, std::vector<std::shared_ptr<UnistyleData>>>> _shadowRegistry{};
52
+ std::unordered_map<jsi::Runtime*, std::unordered_map<const ShadowNodeFamily*, std::vector<const std::shared_ptr<UnistyleData>>>> _shadowRegistry{};
54
53
  };
55
54
 
56
55
  inline UnistylesRegistry& UnistylesRegistry::get() {
@@ -25,7 +25,7 @@ std::optional<std::string>& core::UnistylesState::getCurrentThemeName() {
25
25
 
26
26
  jsi::Object core::UnistylesState::getCurrentJSTheme() {
27
27
  auto hasSomeThemes = _registeredThemeNames.size() > 0;
28
-
28
+
29
29
  if (!hasSomeThemes && !this->hasUserConfig) {
30
30
  helpers::assertThat(*_rt, false, "Unistyles: One of your stylesheets is trying to get the theme, but no theme has been selected yet. Did you forget to call StyleSheet.configure? If you called it, make sure you did so before any StyleSheet.create.");
31
31
  }
@@ -99,15 +99,18 @@ int core::UnistylesState::parseColor(jsi::Value& maybeColor) {
99
99
  if (!maybeColor.isString()) {
100
100
  return 0;
101
101
  }
102
-
102
+
103
103
  auto colorString = maybeColor.asString(*_rt);
104
-
104
+
105
105
  if (!this->_colorCache.contains(colorString.utf8(*_rt).c_str())) {
106
- // we must convert it to uint32_t first, otherwise color will be broken
107
- uint32_t color = this->_processColorFn.get()->call(*_rt, colorString).asNumber();
108
-
106
+ #ifdef ANDROID
107
+ int color = this->_processColorFn.get()->call(*_rt, colorString).asNumber();
108
+ #else
109
+ uint32_t color = this->_processColorFn.get()->call(*_rt, colorString).asNumber();
110
+ #endif
111
+
109
112
  this->_colorCache[colorString.utf8(*_rt).c_str()] = color ? color : 0;
110
113
  }
111
-
114
+
112
115
  return this->_colorCache[colorString.utf8(*_rt).c_str()];
113
116
  }
@@ -15,7 +15,7 @@ double HybridStyleSheet::getUnid() {
15
15
 
16
16
  jsi::Value HybridStyleSheet::create(jsi::Runtime& rt, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) {
17
17
  if (count == 1) {
18
- helpers::assertThat(rt, count == 2, "Unistyles is not initialized correctly. Please add babel plugin to your babel config.");
18
+ helpers::assertThat(rt, false, "Unistyles is not initialized correctly. Please add babel plugin to your babel config.");
19
19
  }
20
20
 
21
21
  // second argument is hidden, so validation is perfectly fine
@@ -128,7 +128,7 @@ void HybridStyleSheet::parseSettings(jsi::Runtime &rt, jsi::Object settings) {
128
128
  void HybridStyleSheet::parseBreakpoints(jsi::Runtime &rt, jsi::Object breakpoints){
129
129
  helpers::Breakpoints sortedBreakpoints = helpers::jsiBreakpointsToVecPairs(rt, std::move(breakpoints));
130
130
 
131
- helpers::assertThat(rt, sortedBreakpoints.size() > 0, "StyleSheet.configure's breakpoints can't be empty.");
131
+ helpers::assertThat(rt, !sortedBreakpoints.empty(), "StyleSheet.configure's breakpoints can't be empty.");
132
132
  helpers::assertThat(rt, sortedBreakpoints.front().second == 0, "StyleSheet.configure's first breakpoint must start from 0.");
133
133
 
134
134
  auto& registry = core::UnistylesRegistry::get();
@@ -197,7 +197,7 @@ void HybridStyleSheet::verifyAndSelectTheme(jsi::Runtime &rt) {
197
197
 
198
198
  void HybridStyleSheet::setThemeFromColorScheme(jsi::Runtime& rt) {
199
199
  auto& state = core::UnistylesRegistry::get().getState(rt);
200
- ColorScheme colorScheme = static_cast<ColorScheme>(this->_unistylesRuntime->getColorScheme());
200
+ auto colorScheme = static_cast<ColorScheme>(this->_unistylesRuntime->getColorScheme());
201
201
 
202
202
  switch (colorScheme) {
203
203
  case ColorScheme::LIGHT:
@@ -232,7 +232,7 @@ void HybridStyleSheet::loadExternalMethods(const jsi::Value& thisValue, jsi::Run
232
232
  void HybridStyleSheet::registerHooks(jsi::Runtime& rt) {
233
233
  // cleanup Shadow updates
234
234
  core::UnistylesRegistry::get().trafficController.restore();
235
-
235
+
236
236
  this->_unistylesCommitHook = std::make_shared<core::UnistylesCommitHook>(this->_uiManager);
237
237
  this->_unistylesMountHook = std::make_shared<core::UnistylesMountHook>(this->_uiManager, this->_unistylesRuntime);
238
238
  }
@@ -248,20 +248,20 @@ void HybridStyleSheet::onPlatformDependenciesChange(std::vector<UnistyleDependen
248
248
  auto dependencies = std::move(unistylesDependencies);
249
249
 
250
250
  // re-compute new breakpoint
251
- auto dimensionsIt = std::find(dependencies.begin(), dependencies.end(), UnistyleDependency::DIMENSIONS);
251
+ auto dimensionsIt = std::find(unistylesDependencies.begin(), unistylesDependencies.end(), UnistyleDependency::DIMENSIONS);
252
252
 
253
- if (dimensionsIt != dependencies.end()) {
253
+ if (dimensionsIt != unistylesDependencies.end()) {
254
254
  registry.getState(rt).computeCurrentBreakpoint(this->_unistylesRuntime->getScreen().width);
255
255
  }
256
256
 
257
257
  // check if color scheme changed and then if Unistyles state depend on it (adaptive themes)
258
- auto colorSchemeIt = std::find(dependencies.begin(), dependencies.end(), UnistyleDependency::COLORSCHEME);
259
- auto hasNewColorScheme = colorSchemeIt != dependencies.end();
258
+ auto colorSchemeIt = std::find(unistylesDependencies.begin(), unistylesDependencies.end(), UnistyleDependency::COLORSCHEME);
259
+ auto hasNewColorScheme = colorSchemeIt != unistylesDependencies.end();
260
260
 
261
261
  // in a later step, we will rebuild only Unistyles with mounted StyleSheets
262
262
  // however, user may have StyleSheets with components that haven't mounted yet
263
263
  // we need to rebuild all dependent StyleSheets as well
264
- auto dependentStyleSheets = registry.getStyleSheetsToRefresh(rt, hasNewColorScheme, dependencies.size() > 1);
264
+ auto dependentStyleSheets = registry.getStyleSheetsToRefresh(rt, hasNewColorScheme, unistylesDependencies.size() > 1);
265
265
 
266
266
  if (hasNewColorScheme) {
267
267
  this->_unistylesRuntime->includeDependenciesForColorSchemeChange(dependencies);
@@ -269,17 +269,17 @@ void HybridStyleSheet::onPlatformDependenciesChange(std::vector<UnistyleDependen
269
269
 
270
270
  auto dependencyMap = registry.buildDependencyMap(rt, dependencies);
271
271
 
272
- if (dependencyMap.size() == 0) {
272
+ if (dependencyMap.empty()) {
273
273
  this->notifyJSListeners(dependencies);
274
274
 
275
275
  return;
276
276
  }
277
277
 
278
278
  parser.rebuildUnistylesInDependencyMap(rt, dependencyMap, dependentStyleSheets);
279
- parser.rebuildShadowLeafUpdates(dependencyMap);
280
-
279
+ parser.rebuildShadowLeafUpdates(rt, dependencyMap);
280
+
281
281
  this->notifyJSListeners(dependencies);
282
- shadow::ShadowTreeManager::updateShadowTree(rt);
282
+ shadow::ShadowTreeManager::updateShadowTree(UIManagerBinding::getBinding(rt)->getUIManager().getShadowTreeRegistry());
283
283
  });
284
284
  }
285
285
 
@@ -298,18 +298,21 @@ void HybridStyleSheet::onImeChange() {
298
298
 
299
299
  auto dependencyMap = registry.buildDependencyMap(rt, dependencies);
300
300
 
301
- if (dependencyMap.size() == 0) {
301
+ if (dependencyMap.empty()) {
302
302
  return;
303
303
  }
304
+
305
+ std::vector<std::shared_ptr<core::StyleSheet>> styleSheet;
306
+
307
+ parser.rebuildUnistylesInDependencyMap(rt, dependencyMap, styleSheet);
308
+ parser.rebuildShadowLeafUpdates(rt, dependencyMap);
304
309
 
305
- parser.rebuildUnistylesInDependencyMap(rt, dependencyMap, {});
306
- parser.rebuildShadowLeafUpdates(dependencyMap);
307
- shadow::ShadowTreeManager::updateShadowTree(rt);
310
+ shadow::ShadowTreeManager::updateShadowTree(UIManagerBinding::getBinding(rt)->getUIManager().getShadowTreeRegistry());
308
311
  });
309
312
  }
310
313
 
311
314
  void HybridStyleSheet::notifyJSListeners(std::vector<UnistyleDependency>& dependencies) {
312
- if (dependencies.size() > 0) {
315
+ if (!dependencies.empty()) {
313
316
  std::for_each(this->_changeListeners.begin(), this->_changeListeners.end(), [&](auto& listener){
314
317
  (*listener)(dependencies);
315
318
  });
@@ -228,10 +228,6 @@ jsi::Value HybridUnistylesRuntime::getMiniRuntimeAsValue(jsi::Runtime& rt) {
228
228
  return obj;
229
229
  }
230
230
 
231
- jsi::Runtime& HybridUnistylesRuntime::getRuntime() {
232
- return *this->_rt;
233
- }
234
-
235
231
  void HybridUnistylesRuntime::registerPlatformListener(const std::function<void(std::vector<UnistyleDependency>)>& listener) {
236
232
  this->_nativePlatform.registerPlatformListener(listener);
237
233
  this->_onDependenciesChange = listener;
@@ -67,7 +67,6 @@ struct HybridUnistylesRuntime: public HybridUnistylesRuntimeSpec {
67
67
  std::unordered_map<std::string, double> getBreakpoints() override;
68
68
 
69
69
  jsi::Value getMiniRuntimeAsValue(jsi::Runtime& rt);
70
- jsi::Runtime& getRuntime();
71
70
  void includeDependenciesForColorSchemeChange(std::vector<UnistyleDependency>& deps);
72
71
  void calculateNewThemeAndDependencies(std::vector<UnistyleDependency>& deps);
73
72
  std::function<void(std::function<void(jsi::Runtime&)>&&)> runOnJSThread;
@@ -93,13 +93,12 @@ void parser::Parser::rebuildUnistylesWithVariants(jsi::Runtime& rt, std::shared_
93
93
  continue;
94
94
  }
95
95
 
96
- // todo skip dynamic functions
97
96
  this->rebuildUnistyle(rt, styleSheet, unistyle, variants, std::nullopt);
98
97
  }
99
98
  }
100
99
 
101
100
  // rebuild all unistyles that are affected by platform event
102
- void parser::Parser::rebuildUnistylesInDependencyMap(jsi::Runtime& rt, DependencyMap& dependencyMap, std::vector<std::shared_ptr<core::StyleSheet>> styleSheets) {
101
+ void parser::Parser::rebuildUnistylesInDependencyMap(jsi::Runtime& rt, DependencyMap& dependencyMap, std::vector<std::shared_ptr<core::StyleSheet>>& styleSheets) {
103
102
  std::unordered_map<std::shared_ptr<StyleSheet>, jsi::Value> parsedStyleSheets{};
104
103
  std::unordered_map<std::shared_ptr<core::Unistyle>, bool> parsedUnistyles{};
105
104
 
@@ -121,7 +120,7 @@ void parser::Parser::rebuildUnistylesInDependencyMap(jsi::Runtime& rt, Dependenc
121
120
  auto& unistyle = unistyleData->unistyle;
122
121
 
123
122
  // for RN styles or inline styles, compute styles only once
124
- if (unistyle->styleKey == helpers::EXOTIC_STYLE_KEY.c_str()) {
123
+ if (unistyle->styleKey == helpers::EXOTIC_STYLE_KEY) {
125
124
  if (!unistyleData->parsedStyle.has_value()) {
126
125
  unistyleData->parsedStyle = jsi::Value(rt, unistyle->rawValue).asObject(rt);
127
126
 
@@ -206,10 +205,9 @@ void parser::Parser::rebuildUnistyle(jsi::Runtime& rt, std::shared_ptr<StyleShee
206
205
  }
207
206
 
208
207
  // convert dependency map to shadow tree updates
209
- void parser::Parser::rebuildShadowLeafUpdates(core::DependencyMap& dependencyMap) {
208
+ void parser::Parser::rebuildShadowLeafUpdates(jsi::Runtime& rt, core::DependencyMap& dependencyMap) {
210
209
  shadow::ShadowLeafUpdates updates;
211
210
  auto& registry = core::UnistylesRegistry::get();
212
- auto& rt = this->_unistylesRuntime->getRuntime();
213
211
 
214
212
  for (const auto& [shadowNode, unistyles] : dependencyMap) {
215
213
  auto rawProps = this->parseStylesToShadowTreeStyles(rt, unistyles);
@@ -218,8 +216,6 @@ void parser::Parser::rebuildShadowLeafUpdates(core::DependencyMap& dependencyMap
218
216
  }
219
217
 
220
218
  registry.trafficController.setUpdates(updates);
221
-
222
- // this is required, we need to indicate that there are new changes
223
219
  registry.trafficController.resumeUnistylesTraffic();
224
220
  }
225
221
 
@@ -764,26 +760,6 @@ folly::dynamic parser::Parser::parseStylesToShadowTreeStyles(jsi::Runtime& rt, c
764
760
  return jsi::dynamicFromValue(rt, std::move(convertedStyles));
765
761
  }
766
762
 
767
- folly::dynamic parser::Parser::parseUnistyleToShadowTreeStyles(jsi::Runtime& rt, const Unistyle::Shared unistyle) {
768
- jsi::Object convertedStyles = jsi::Object(rt);
769
- auto& state = core::UnistylesRegistry::get().getState(rt);
770
-
771
- // can happen for exotic styles
772
- if (!unistyle->parsedStyle.has_value()) {
773
- return nullptr;
774
- }
775
-
776
- helpers::enumerateJSIObject(rt, unistyle->parsedStyle.value(), [&](const std::string& propertyName, jsi::Value& propertyValue){
777
- if (this->isColor(propertyName)) {
778
- return convertedStyles.setProperty(rt, propertyName.c_str(), jsi::Value(state.parseColor(propertyValue)));
779
- }
780
-
781
- convertedStyles.setProperty(rt, propertyName.c_str(), propertyValue);
782
- });
783
-
784
- return jsi::dynamicFromValue(rt, std::move(convertedStyles));
785
- }
786
-
787
763
  // check is styleKey contains color
788
764
  bool parser::Parser::isColor(const std::string& propertyName) {
789
765
  std::string str = propertyName;
@@ -24,9 +24,8 @@ struct Parser {
24
24
  void buildUnistyles(jsi::Runtime& rt, std::shared_ptr<StyleSheet> styleSheet);
25
25
  void parseUnistyles(jsi::Runtime& rt, std::shared_ptr<StyleSheet> styleSheet);
26
26
  void rebuildUnistylesWithVariants(jsi::Runtime& rt, std::shared_ptr<StyleSheet> styleSheet, Variants& variants);
27
- void rebuildUnistylesInDependencyMap(jsi::Runtime& rt, core::DependencyMap& dependencyMap, std::vector<std::shared_ptr<core::StyleSheet>> styleSheets);
28
- void rebuildShadowLeafUpdates(core::DependencyMap& dependencyMap);
29
- folly::dynamic parseUnistyleToShadowTreeStyles(jsi::Runtime& rt, const Unistyle::Shared unistyle);
27
+ void rebuildUnistylesInDependencyMap(jsi::Runtime& rt, core::DependencyMap& dependencyMap, std::vector<std::shared_ptr<core::StyleSheet>>& styleSheets);
28
+ void rebuildShadowLeafUpdates(jsi::Runtime& rt, core::DependencyMap& dependencyMap);
30
29
  folly::dynamic parseStylesToShadowTreeStyles(jsi::Runtime& rt, const std::vector<std::shared_ptr<UnistyleData>>& unistyles);
31
30
  void rebuildUnistyle(jsi::Runtime& rt, std::shared_ptr<StyleSheet> styleSheet, Unistyle::Shared unistyle, const Variants& variants, std::optional<std::vector<folly::dynamic>>);
32
31
 
@@ -13,20 +13,16 @@ struct ShadowTrafficController {
13
13
  }
14
14
 
15
15
  inline void stopUnistylesTraffic() {
16
- std::lock_guard<std::mutex> lock(_mutex);
17
-
18
16
  this->_canCommit = false;
19
17
  }
20
18
 
21
19
  inline void resumeUnistylesTraffic() {
22
- std::lock_guard<std::mutex> lock(_mutex);
23
-
24
20
  this->_canCommit = true;
25
21
  }
26
22
 
27
23
  inline shadow::ShadowLeafUpdates& getUpdates() {
28
24
  std::lock_guard<std::mutex> lock(_mutex);
29
-
25
+
30
26
  return _unistylesUpdates;
31
27
  }
32
28
 
@@ -47,6 +43,14 @@ struct ShadowTrafficController {
47
43
  targetUpdates.emplace(pair.first, std::move(pair.second));
48
44
  });
49
45
  }
46
+
47
+ inline void removeShadowNode(const ShadowNodeFamily* shadowNodeFamily) {
48
+ std::lock_guard<std::mutex> lock(_mutex);
49
+
50
+ if (_unistylesUpdates.contains(shadowNodeFamily)) {
51
+ _unistylesUpdates.erase(shadowNodeFamily);
52
+ }
53
+ }
50
54
 
51
55
  inline void restore() {
52
56
  std::lock_guard<std::mutex> lock(_mutex);
@@ -6,13 +6,11 @@ using namespace facebook;
6
6
 
7
7
  using AffectedNodes = std::unordered_map<const ShadowNodeFamily*, std::unordered_set<int>>;
8
8
 
9
- void shadow::ShadowTreeManager::updateShadowTree(facebook::jsi::Runtime& rt) {
9
+ void shadow::ShadowTreeManager::updateShadowTree(const ShadowTreeRegistry& shadowTreeRegistry) {
10
10
  auto& registry = core::UnistylesRegistry::get();
11
- auto& uiManager = UIManagerBinding::getBinding(rt)->getUIManager();
12
- const auto &shadowTreeRegistry = uiManager.getShadowTreeRegistry();
13
11
  auto updates = registry.trafficController.getUpdates();
14
12
 
15
- if (updates.size() == 0) {
13
+ if (updates.empty()) {
16
14
  return;
17
15
  }
18
16
 
@@ -111,9 +109,16 @@ ShadowNode::Unshared shadow::ShadowTreeManager::cloneShadowTree(const ShadowNode
111
109
  *shadowNode.getContextContainer()
112
110
  };
113
111
 
112
+ #ifdef ANDROID
113
+ auto safeProps = rawPropsIt->second == nullptr ? folly::dynamic::object() : rawPropsIt->second;
114
+ auto newProps = folly::dynamic::merge(shadowNode.getProps()->rawProps, safeProps);
115
+ #else
116
+ auto newProps = rawPropsIt->second;
117
+ #endif
118
+
114
119
  updatedProps = shadowNode
115
120
  .getComponentDescriptor()
116
- .cloneProps(propsParserContext, shadowNode.getProps(), RawProps(rawPropsIt->second));
121
+ .cloneProps(propsParserContext, shadowNode.getProps(), RawProps(newProps));
117
122
  }
118
123
 
119
124
  return shadowNode.clone({
@@ -16,7 +16,7 @@ using namespace facebook;
16
16
  using AffectedNodes = std::unordered_map<const ShadowNodeFamily *, std::unordered_set<int>>;
17
17
 
18
18
  struct ShadowTreeManager {
19
- static void updateShadowTree(jsi::Runtime& rt);
19
+ static void updateShadowTree(const ShadowTreeRegistry& shadowTreeRegistry);
20
20
  static AffectedNodes findAffectedNodes(const RootShadowNode& rootNode, ShadowLeafUpdates& updates);
21
21
  static ShadowNode::Unshared cloneShadowTree(const ShadowNode &shadowNode, ShadowLeafUpdates& updates, AffectedNodes& affectedNodes);
22
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-unistyles",
3
- "version": "3.0.0-alpha.36",
3
+ "version": "3.0.0-alpha.37",
4
4
  "description": "Level up your React Native StyleSheet",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -74,11 +74,11 @@
74
74
  "husky": "9.1.6",
75
75
  "jest": "29.7.0",
76
76
  "metro-react-native-babel-preset": "0.77.0",
77
- "nitro-codegen": "0.15.0",
77
+ "nitro-codegen": "0.16.2",
78
78
  "react": "18.3.1",
79
79
  "react-native": "0.76.0",
80
80
  "react-native-builder-bob": "0.30.2",
81
- "react-native-nitro-modules": "0.15.0",
81
+ "react-native-nitro-modules": "0.16.2",
82
82
  "react-test-renderer": "18.3.1",
83
83
  "release-it": "17.10.0",
84
84
  "typescript": "5.6.3"
@@ -1,184 +0,0 @@
1
- package com.unistyles
2
-
3
- import UnistylesModuleInsets
4
- import android.content.Context
5
- import android.content.res.Configuration
6
- import android.os.Build
7
- import android.util.DisplayMetrics
8
- import android.view.WindowManager
9
- import androidx.core.text.TextUtilsCompat
10
- import androidx.core.view.ViewCompat
11
- import com.facebook.react.bridge.ReactApplicationContext
12
- import com.margelo.nitro.unistyles.ColorScheme
13
- import com.margelo.nitro.unistyles.Dimensions
14
- import com.margelo.nitro.unistyles.HybridNativePlatformSpec
15
- import com.margelo.nitro.unistyles.Insets
16
- import com.margelo.nitro.unistyles.Orientation
17
- import com.margelo.nitro.unistyles.UnistyleDependency
18
- import com.margelo.nitro.unistyles.UnistylesNativeMiniRuntime
19
- import java.util.Locale
20
-
21
- class NativePlatform(private val reactContext: ReactApplicationContext): HybridNativePlatformSpec() {
22
- private val _insets = UnistylesModuleInsets(reactContext)
23
-
24
- override fun getInsets(): Insets {
25
- return _insets.getInsets()
26
- }
27
-
28
- override fun getColorScheme(): ColorScheme {
29
- val uiMode = reactContext.resources.configuration.uiMode
30
-
31
- val colorScheme = when (uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
32
- Configuration.UI_MODE_NIGHT_YES -> ColorScheme.DARK
33
- Configuration.UI_MODE_NIGHT_NO -> ColorScheme.LIGHT
34
- else -> ColorScheme.UNSPECIFIED
35
- }
36
-
37
- return colorScheme
38
- }
39
-
40
- override fun getFontScale(): Double {
41
- return reactContext.resources.configuration.fontScale.toDouble()
42
- }
43
-
44
- override fun getPixelRatio(): Double {
45
- return reactContext.resources.displayMetrics.density.toDouble()
46
- }
47
-
48
- override fun getOrientation(): Orientation {
49
- val orientation = when (reactContext.resources.configuration.orientation) {
50
- Configuration.ORIENTATION_PORTRAIT -> Orientation.PORTRAIT
51
- Configuration.ORIENTATION_LANDSCAPE -> Orientation.LANDSCAPE
52
- else -> Orientation.PORTRAIT
53
- }
54
-
55
- return orientation
56
- }
57
-
58
- override fun getContentSizeCategory(): String {
59
- val fontScale = reactContext.resources.configuration.fontScale
60
-
61
- val contentSizeCategory = when {
62
- fontScale <= 0.85f -> "Small"
63
- fontScale <= 1.0f -> "Default"
64
- fontScale <= 1.15f -> "Large"
65
- fontScale <= 1.3f -> "ExtraLarge"
66
- fontScale <= 1.5f -> "Huge"
67
- fontScale <= 1.8 -> "ExtraHuge"
68
- else -> "ExtraExtraHuge"
69
- }
70
-
71
- return contentSizeCategory
72
- }
73
-
74
- override fun getScreenDimensions(): Dimensions {
75
- // function takes in count edge-to-edge layout
76
- when {
77
- Build.VERSION.SDK_INT < Build.VERSION_CODES.R -> {
78
- val windowManager = reactContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
79
- val metrics = DisplayMetrics()
80
-
81
- windowManager.defaultDisplay.getRealMetrics(metrics)
82
-
83
- val screenWidth = (metrics.widthPixels / metrics.density).toDouble()
84
- val screenHeight = (metrics.heightPixels / metrics.density).toDouble()
85
-
86
- return Dimensions(screenWidth, screenHeight)
87
- }
88
- else -> {
89
- val displayMetrics = reactContext.resources.displayMetrics
90
-
91
- reactContext.currentActivity?.windowManager?.currentWindowMetrics?.bounds?.let {
92
- val boundsWidth = (it.width() / displayMetrics.density).toDouble()
93
- val boundsHeight = (it.height() / displayMetrics.density).toDouble()
94
-
95
- return Dimensions(boundsWidth, boundsHeight)
96
- } ?: run {
97
- val screenWidth = (displayMetrics.widthPixels / displayMetrics.density).toDouble()
98
- val screenHeight = (displayMetrics.heightPixels / displayMetrics.density).toDouble()
99
-
100
- return Dimensions(screenWidth, screenHeight)
101
- }
102
- }
103
- }
104
- }
105
-
106
- override fun getStatusBarDimensions(): Dimensions {
107
- // todo
108
- return Dimensions(0.0, 0.0)
109
- }
110
-
111
- override fun getNavigationBarDimensions(): Dimensions {
112
- // todo
113
- return Dimensions(0.0, 0.0)
114
- }
115
-
116
- override fun getPrefersRtlDirection(): Boolean {
117
- // forced by React Native
118
- val sharedPrefs = reactContext.getSharedPreferences(
119
- "com.facebook.react.modules.i18nmanager.I18nUtil",
120
- Context.MODE_PRIVATE
121
- )
122
- val hasForcedRtl = sharedPrefs.getBoolean("RCTI18nUtil_forceRTL", false)
123
- // user preferences
124
- val isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
125
-
126
- return hasForcedRtl || isRtl
127
- }
128
-
129
- override fun setRootViewBackgroundColor(color: Double) {
130
- // todo
131
- }
132
-
133
- override fun setNavigationBarBackgroundColor(color: Double) {
134
- // todo
135
- }
136
-
137
- override fun setNavigationBarHidden(isHidden: Boolean) {
138
- // todo
139
- }
140
-
141
- override fun setStatusBarHidden(isHidden: Boolean) {
142
- // todo
143
- }
144
-
145
- override fun setStatusBarBackgroundColor(color: Double) {
146
- // todo
147
- }
148
-
149
- override fun setImmersiveMode(isEnabled: Boolean) {
150
- this.setStatusBarHidden(isEnabled)
151
- this.setNavigationBarHidden(isEnabled)
152
- }
153
-
154
- override fun getMiniRuntime(): UnistylesNativeMiniRuntime {
155
- return UnistylesNativeMiniRuntime(
156
- colorScheme = this.getColorScheme(),
157
- screen = this.getScreenDimensions(),
158
- contentSizeCategory = this.getContentSizeCategory(),
159
- insets = this.getInsets(),
160
- pixelRatio = this.getPixelRatio(),
161
- fontScale = this.getFontScale(),
162
- rtl = this.getPrefersRtlDirection(),
163
- statusBar = this.getStatusBarDimensions(),
164
- navigationBar = this.getNavigationBarDimensions(),
165
- isPortrait = this.getOrientation() == Orientation.PORTRAIT,
166
- isLandscape = this.getOrientation() == Orientation.LANDSCAPE
167
- )
168
- }
169
-
170
- override fun registerPlatformListener(callback: (dependencies: Array<UnistyleDependency>) -> Unit) {
171
- // todo
172
- }
173
-
174
- override fun registerImeListener(callback: () -> Unit) {
175
- // todo
176
- }
177
-
178
- override fun unregisterPlatformListeners() {
179
- // todo
180
- }
181
-
182
- override val memorySize: Long
183
- get() = 0
184
- }
@@ -1,8 +0,0 @@
1
- import com.facebook.react.bridge.ReactApplicationContext
2
- import com.margelo.nitro.unistyles.Insets
3
-
4
- class UnistylesModuleInsets(private val reactContext: ReactApplicationContext) {
5
- fun getInsets(): Insets {
6
- return Insets(top = 0.0, bottom = 0.0, left = 0.0, right = 0.0, ime = 0.0)
7
- }
8
- }