react-native-screens 4.25.0-beta.1 → 4.25.0-beta.2

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.
Files changed (96) hide show
  1. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderAppBarLayout.kt +6 -14
  2. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderAppBarLayoutBehavior.kt +29 -0
  3. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderCoordinator.kt +56 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfig.kt +11 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfigProviding.kt +5 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfigViewManager.kt +35 -0
  7. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/subview/StackHeaderSubview.kt +3 -7
  8. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsActionOrigin.kt +26 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt +227 -151
  10. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsNavigationState.kt +60 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/{TabsContainerDelegate.kt → TabsNavigationStateObserver.kt} +19 -14
  12. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsNavigationStateObserverRegistry.kt +88 -0
  13. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt +40 -24
  14. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostEventEmitter.kt +11 -9
  15. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt +19 -7
  16. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectedEvent.kt +4 -3
  17. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectionPreventedEvent.kt +3 -3
  18. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectionRejectedEvent.kt +11 -10
  19. package/ios/conversion/RNSConversions-Tabs.mm +19 -0
  20. package/ios/conversion/RNSConversions.h +3 -0
  21. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryHelper.mm +34 -5
  22. package/ios/tabs/host/RNSTabBarController.h +152 -99
  23. package/ios/tabs/host/RNSTabBarController.mm +137 -113
  24. package/ios/tabs/host/RNSTabsHostComponentView.h +7 -8
  25. package/ios/tabs/host/RNSTabsHostComponentView.mm +37 -33
  26. package/ios/tabs/host/RNSTabsHostEventEmitter.h +4 -4
  27. package/ios/tabs/host/RNSTabsHostEventEmitter.mm +5 -3
  28. package/ios/tabs/host/RNSTabsNavigationState.h +142 -27
  29. package/ios/tabs/host/RNSTabsNavigationState.mm +35 -2
  30. package/ios/tabs/host/RNSTabsNavigationStateObserverRegistry.h +62 -0
  31. package/ios/tabs/host/RNSTabsNavigationStateObserverRegistry.mm +104 -0
  32. package/lib/commonjs/components/gamma/stack/header/StackHeaderConfig.android.js +46 -1
  33. package/lib/commonjs/components/gamma/stack/header/StackHeaderConfig.android.js.map +1 -1
  34. package/lib/commonjs/components/safe-area/SafeAreaView.web.js +2 -3
  35. package/lib/commonjs/components/safe-area/SafeAreaView.web.js.map +1 -1
  36. package/lib/commonjs/components/tabs/host/TabsHost.android.js +2 -2
  37. package/lib/commonjs/components/tabs/host/TabsHost.android.js.map +1 -1
  38. package/lib/commonjs/components/tabs/host/TabsHost.ios.js +2 -2
  39. package/lib/commonjs/components/tabs/host/TabsHost.ios.js.map +1 -1
  40. package/lib/commonjs/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
  41. package/lib/commonjs/flags.js +1 -0
  42. package/lib/commonjs/flags.js.map +1 -1
  43. package/lib/module/components/gamma/stack/header/StackHeaderConfig.android.js +46 -1
  44. package/lib/module/components/gamma/stack/header/StackHeaderConfig.android.js.map +1 -1
  45. package/lib/module/components/safe-area/SafeAreaView.web.js +1 -1
  46. package/lib/module/components/safe-area/SafeAreaView.web.js.map +1 -1
  47. package/lib/module/components/tabs/host/TabsHost.android.js +2 -2
  48. package/lib/module/components/tabs/host/TabsHost.android.js.map +1 -1
  49. package/lib/module/components/tabs/host/TabsHost.ios.js +2 -2
  50. package/lib/module/components/tabs/host/TabsHost.ios.js.map +1 -1
  51. package/lib/module/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
  52. package/lib/module/flags.js +1 -0
  53. package/lib/module/flags.js.map +1 -1
  54. package/lib/typescript/components/gamma/split/SplitHost.types.d.ts +1 -1
  55. package/lib/typescript/components/gamma/split/SplitHost.types.d.ts.map +1 -1
  56. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.android.d.ts.map +1 -1
  57. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.android.types.d.ts +183 -8
  58. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.android.types.d.ts.map +1 -1
  59. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.types.d.ts +37 -0
  60. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.types.d.ts.map +1 -1
  61. package/lib/typescript/components/gamma/stack/header/android/StackHeaderSubview.android.types.d.ts +1 -1
  62. package/lib/typescript/components/gamma/stack/header/android/StackHeaderSubview.android.types.d.ts.map +1 -1
  63. package/lib/typescript/components/gamma/stack/host/StackHost.types.d.ts +1 -1
  64. package/lib/typescript/components/gamma/stack/host/StackHost.types.d.ts.map +1 -1
  65. package/lib/typescript/components/safe-area/SafeAreaView.web.d.ts +1 -1
  66. package/lib/typescript/components/safe-area/SafeAreaView.web.d.ts.map +1 -1
  67. package/lib/typescript/components/tabs/host/TabsHost.types.d.ts +27 -17
  68. package/lib/typescript/components/tabs/host/TabsHost.types.d.ts.map +1 -1
  69. package/lib/typescript/components/tabs/index.d.ts +1 -1
  70. package/lib/typescript/components/tabs/index.d.ts.map +1 -1
  71. package/lib/typescript/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.d.ts +5 -0
  72. package/lib/typescript/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.d.ts.map +1 -1
  73. package/lib/typescript/fabric/tabs/TabsHostAndroidNativeComponent.d.ts +4 -4
  74. package/lib/typescript/fabric/tabs/TabsHostAndroidNativeComponent.d.ts.map +1 -1
  75. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts +4 -4
  76. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts.map +1 -1
  77. package/lib/typescript/flags.d.ts +1 -0
  78. package/lib/typescript/flags.d.ts.map +1 -1
  79. package/package.json +1 -1
  80. package/src/components/gamma/split/SplitHost.types.ts +1 -1
  81. package/src/components/gamma/stack/header/StackHeaderConfig.android.tsx +72 -2
  82. package/src/components/gamma/stack/header/StackHeaderConfig.android.types.ts +183 -8
  83. package/src/components/gamma/stack/header/StackHeaderConfig.types.ts +37 -0
  84. package/src/components/gamma/stack/header/android/StackHeaderSubview.android.types.ts +1 -1
  85. package/src/components/gamma/stack/host/StackHost.types.ts +1 -1
  86. package/src/components/safe-area/SafeAreaView.web.tsx +1 -1
  87. package/src/components/tabs/host/TabsHost.android.tsx +2 -2
  88. package/src/components/tabs/host/TabsHost.ios.tsx +2 -2
  89. package/src/components/tabs/host/TabsHost.types.ts +27 -17
  90. package/src/components/tabs/index.ts +1 -1
  91. package/src/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.ts +6 -0
  92. package/src/fabric/tabs/TabsHostAndroidNativeComponent.ts +4 -4
  93. package/src/fabric/tabs/TabsHostIOSNativeComponent.ts +4 -4
  94. package/src/flags.ts +1 -0
  95. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainerOps.kt +0 -7
  96. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsNavState.kt +0 -43
@@ -0,0 +1,88 @@
1
+ package com.swmansion.rnscreens.gamma.tabs.container
2
+
3
+ /**
4
+ * Holds the set of [TabsNavigationStateObserver]s registered against a [TabsContainer]
5
+ * and fans out container events to all of them.
6
+ *
7
+ * Observers are held with strong references; callers must explicitly call
8
+ * [TabsContainer.removeNavigationStateObserver] before observer dealloc, or rely on the
9
+ * host invoking [TabsContainer.tearDown] (which calls [clear]) on container teardown.
10
+ *
11
+ * # Reentrance
12
+ *
13
+ * Recursive emission is forbidden. While an `emit*` call is in flight:
14
+ * - additional `emit*` calls fail-fast with [IllegalStateException] (recursion is a
15
+ * programmer error — it would deliver out-of-order events to later observers),
16
+ * - [add] / [remove] are rejected gracefully and return `false`.
17
+ *
18
+ * The registry is single-threaded — use it from a single thread (typically the UI
19
+ * thread on Android). It does not synchronize.
20
+ */
21
+ internal class TabsNavigationStateObserverRegistry {
22
+ private val observers: MutableList<TabsNavigationStateObserver> = mutableListOf()
23
+ private var isEmitting: Boolean = false
24
+
25
+ /**
26
+ * Register an observer. Returns `false` if the observer is already registered or
27
+ * if called during an in-flight `emit*` (modifications during emission are rejected).
28
+ */
29
+ fun add(observer: TabsNavigationStateObserver): Boolean {
30
+ if (isEmitting) return false
31
+ if (observers.contains(observer)) return false
32
+ observers.add(observer)
33
+ return true
34
+ }
35
+
36
+ /**
37
+ * Unregister an observer. Returns `false` if the observer was not registered or
38
+ * if called during an in-flight `emit*` (modifications during emission are rejected).
39
+ */
40
+ fun remove(observer: TabsNavigationStateObserver): Boolean {
41
+ if (isEmitting) return false
42
+ return observers.remove(observer)
43
+ }
44
+
45
+ /**
46
+ * Drop all registered observers. Must not be called during an in-flight `emit*`.
47
+ */
48
+ fun clear() {
49
+ check(!isEmitting) { "[RNScreens] TabsNavigationStateObserverRegistry.clear during emission" }
50
+ observers.clear()
51
+ }
52
+
53
+ fun emitOnNavigationStateUpdate(
54
+ navState: TabsNavigationState,
55
+ isRepeated: Boolean,
56
+ hasTriggeredSpecialEffect: Boolean,
57
+ actionOrigin: TabsActionOrigin,
58
+ ) {
59
+ emitSignal { observer -> observer.onNavigationStateUpdate(navState, isRepeated, hasTriggeredSpecialEffect, actionOrigin) }
60
+ }
61
+
62
+ fun emitOnNavigationStateUpdateRejected(
63
+ currentNavState: TabsNavigationState,
64
+ rejectedRequest: TabsNavigationStateUpdateRequest,
65
+ reason: TabsNavigationStateRejectionReason,
66
+ ) {
67
+ emitSignal { observer -> observer.onNavigationStateUpdateRejected(currentNavState, rejectedRequest, reason) }
68
+ }
69
+
70
+ fun emitOnNavigationStateUpdatePrevented(
71
+ currentNavState: TabsNavigationState,
72
+ preventedScreenKey: String,
73
+ ) {
74
+ emitSignal { observer -> observer.onNavigationStateUpdatePrevented(currentNavState, preventedScreenKey) }
75
+ }
76
+
77
+ private fun emitSignal(emitBlock: (TabsNavigationStateObserver) -> Unit) {
78
+ check(!isEmitting) { "[RNScreens] Recursive emission on TabsNavigationStateObserverRegistry" }
79
+ isEmitting = true
80
+ try {
81
+ observers.forEach {
82
+ emitBlock(it)
83
+ }
84
+ } finally {
85
+ isEmitting = false
86
+ }
87
+ }
88
+ }
@@ -12,11 +12,12 @@ import com.facebook.react.uimanager.ThemedReactContext
12
12
  import com.facebook.react.uimanager.UIManagerHelper
13
13
  import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme
14
14
  import com.swmansion.rnscreens.gamma.helpers.getFabricUIManagerNotNull
15
- import com.swmansion.rnscreens.gamma.tabs.container.TabSelectOp
15
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin
16
16
  import com.swmansion.rnscreens.gamma.tabs.container.TabsContainer
17
- import com.swmansion.rnscreens.gamma.tabs.container.TabsContainerDelegate
18
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
19
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavStateUpdateRejectionReason
17
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationState
18
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateObserver
19
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateRejectionReason
20
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateUpdateRequest
20
21
  import com.swmansion.rnscreens.gamma.tabs.screen.TabsScreen
21
22
  import com.swmansion.rnscreens.utils.RNSLog
22
23
  import kotlin.properties.Delegates
@@ -26,13 +27,13 @@ import kotlin.properties.Delegates
26
27
  class TabsHost(
27
28
  val reactContext: ThemedReactContext,
28
29
  ) : FrameLayout(reactContext),
29
- TabsContainerDelegate,
30
+ TabsNavigationStateObserver,
30
31
  UIManagerListener {
31
32
  private val renderedScreens: ArrayList<TabsScreen> = arrayListOf()
32
- private var jsNavState: TabsNavState = TabsNavState.EMPTY
33
+ private var jsNavStateRequest: TabsNavigationStateUpdateRequest? = null
33
34
 
34
35
  private val container: TabsContainer =
35
- TabsContainer(reactContext, this).apply {
36
+ TabsContainer(reactContext).apply {
36
37
  layoutParams =
37
38
  LayoutParams(
38
39
  LayoutParams.MATCH_PARENT,
@@ -40,7 +41,7 @@ class TabsHost(
40
41
  )
41
42
  }
42
43
 
43
- internal var rejectStaleNavStateUpdates: Boolean by container::rejectOpsWithStaleNavState
44
+ internal var rejectStaleNavigationStateUpdates: Boolean by container::rejectStaleNavigationStateUpdates
44
45
 
45
46
  internal lateinit var eventEmitter: TabsHostEventEmitter
46
47
 
@@ -59,6 +60,9 @@ class TabsHost(
59
60
 
60
61
  init {
61
62
  addView(container)
63
+ check(container.addNavigationStateObserver(this)) {
64
+ "[RNScreens] Failed to register TabsHost as navigation state observer"
65
+ }
62
66
  UIManagerHelper
63
67
  .getFabricUIManagerNotNull(reactContext)
64
68
  .addUIManagerEventListener(this)
@@ -109,9 +113,9 @@ class TabsHost(
109
113
  container.removeAllTabsScreens()
110
114
  }
111
115
 
112
- internal fun updateJSNavState(navState: TabsNavState) {
113
- jsNavState = navState
114
- container.setContainerOperation(TabSelectOp(jsNavState.copy()))
116
+ internal fun updateJSNavigationStateUpdateRequest(navStateRequest: TabsNavigationStateUpdateRequest) {
117
+ jsNavStateRequest = navStateRequest
118
+ container.setPendingNavigationStateUpdate(navStateRequest.copy())
115
119
  }
116
120
 
117
121
  private val layoutCallback =
@@ -155,42 +159,54 @@ class TabsHost(
155
159
  eventEmitter = TabsHostEventEmitter(reactContext, id)
156
160
  }
157
161
 
158
- override fun onNavStateUpdate(
159
- navState: TabsNavState,
162
+ override fun onNavigationStateUpdate(
163
+ navState: TabsNavigationState,
160
164
  isRepeated: Boolean,
161
165
  hasTriggeredSpecialEffect: Boolean,
162
- isNativeAction: Boolean,
166
+ actionOrigin: TabsActionOrigin,
163
167
  ) {
164
168
  eventEmitter.emitOnTabSelectedEvent(
165
- navState.selectedKey,
169
+ navState.selectedScreenKey,
166
170
  navState.provenance,
167
171
  isRepeated,
168
172
  hasTriggeredSpecialEffect,
169
- isNativeAction,
173
+ actionOrigin,
170
174
  )
171
175
  }
172
176
 
173
- override fun onNavStateUpdateRejected(
174
- currentNavState: TabsNavState,
175
- rejectedNavState: TabsNavState,
176
- reason: TabsNavStateUpdateRejectionReason,
177
+ override fun onNavigationStateUpdateRejected(
178
+ currentNavState: TabsNavigationState,
179
+ rejectedRequest: TabsNavigationStateUpdateRequest,
180
+ reason: TabsNavigationStateRejectionReason,
177
181
  ) {
178
182
  eventEmitter.emitOnTabSelectionRejectedEvent(
179
183
  currentNavState,
180
- rejectedNavState,
184
+ rejectedRequest,
181
185
  reason,
182
186
  )
183
187
  }
184
188
 
185
- override fun onNavStateUpdatePrevented(
186
- currentNavState: TabsNavState,
189
+ override fun onNavigationStateUpdatePrevented(
190
+ currentNavState: TabsNavigationState,
187
191
  preventedScreenKey: String,
188
192
  ) {
189
193
  eventEmitter.emitOnTabSelectionPreventedEvent(currentNavState, preventedScreenKey)
190
194
  }
191
195
 
192
196
  override fun didMountItems(uiManager: UIManager) {
193
- container.performContainerUpdateIfNeeded()
197
+ container.flushPendingUpdates()
198
+ }
199
+
200
+ /**
201
+ * Called by [TabsHostViewManager.onDropViewInstance] when this view is recycled.
202
+ * Idempotent: safe to call multiple times.
203
+ */
204
+ internal fun tearDown() {
205
+ container.removeNavigationStateObserver(this)
206
+ container.tearDown()
207
+ UIManagerHelper
208
+ .getFabricUIManagerNotNull(reactContext)
209
+ .removeUIManagerEventListener(this)
194
210
  }
195
211
 
196
212
  override fun willDispatchViewUpdates(uiManager: UIManager) = Unit
@@ -2,8 +2,10 @@ package com.swmansion.rnscreens.gamma.tabs.host
2
2
 
3
3
  import com.facebook.react.bridge.ReactContext
4
4
  import com.swmansion.rnscreens.gamma.common.event.BaseEventEmitter
5
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
6
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavStateUpdateRejectionReason
5
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin
6
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationState
7
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateRejectionReason
8
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateUpdateRequest
7
9
  import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectedEvent
8
10
  import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectionPreventedEvent
9
11
  import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectionRejectedEvent
@@ -20,7 +22,7 @@ internal class TabsHostEventEmitter(
20
22
  provenance: Int,
21
23
  isRepeated: Boolean,
22
24
  hasTriggeredSpecialEffect: Boolean,
23
- isNativeAction: Boolean,
25
+ actionOrigin: TabsActionOrigin,
24
26
  ) {
25
27
  reactEventDispatcher.dispatchEvent(
26
28
  TabsHostTabSelectedEvent(
@@ -30,7 +32,7 @@ internal class TabsHostEventEmitter(
30
32
  provenance,
31
33
  isRepeated,
32
34
  hasTriggeredSpecialEffect,
33
- isNativeAction,
35
+ actionOrigin,
34
36
  ),
35
37
  )
36
38
  }
@@ -40,16 +42,16 @@ internal class TabsHostEventEmitter(
40
42
  * Carries both the active state and the rejected update so that JS can reconcile.
41
43
  */
42
44
  fun emitOnTabSelectionRejectedEvent(
43
- currentNavState: TabsNavState,
44
- rejectedNavState: TabsNavState,
45
- rejectionReason: TabsNavStateUpdateRejectionReason,
45
+ currentNavState: TabsNavigationState,
46
+ rejectedRequest: TabsNavigationStateUpdateRequest,
47
+ rejectionReason: TabsNavigationStateRejectionReason,
46
48
  ) {
47
49
  reactEventDispatcher.dispatchEvent(
48
50
  TabsHostTabSelectionRejectedEvent(
49
51
  surfaceId,
50
52
  viewTag,
51
53
  currentNavState,
52
- rejectedNavState,
54
+ rejectedRequest,
53
55
  rejectionReason,
54
56
  ),
55
57
  )
@@ -60,7 +62,7 @@ internal class TabsHostEventEmitter(
60
62
  * because the target screen has `preventNativeSelection` enabled.
61
63
  */
62
64
  fun emitOnTabSelectionPreventedEvent(
63
- currentNavState: TabsNavState,
65
+ currentNavState: TabsNavigationState,
64
66
  preventedScreenKey: String,
65
67
  ) {
66
68
  reactEventDispatcher.dispatchEvent(
@@ -11,7 +11,8 @@ import com.facebook.react.viewmanagers.RNSTabsHostAndroidManagerDelegate
11
11
  import com.facebook.react.viewmanagers.RNSTabsHostAndroidManagerInterface
12
12
  import com.swmansion.rnscreens.gamma.common.colorscheme.ColorScheme
13
13
  import com.swmansion.rnscreens.gamma.helpers.makeEventRegistrationInfo
14
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
14
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin
15
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateUpdateRequest
15
16
  import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectedEvent
16
17
  import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectionPreventedEvent
17
18
  import com.swmansion.rnscreens.gamma.tabs.host.event.TabsHostTabSelectionRejectedEvent
@@ -72,21 +73,32 @@ class TabsHostViewManager :
72
73
  view.onViewManagerAddEventEmitters()
73
74
  }
74
75
 
75
- override fun setNavState(
76
+ override fun onDropViewInstance(view: TabsHost) {
77
+ view.tearDown()
78
+ super.onDropViewInstance(view)
79
+ }
80
+
81
+ override fun setNavStateRequest(
76
82
  view: TabsHost,
77
83
  value: ReadableMap?,
78
84
  ) {
79
- val navStateMap = requireNotNull(value) { "[RNScreens] NavState must not be nullish" }
80
- val selectedScreenKey = requireNotNull(navStateMap.getString("selectedScreenKey"))
81
- val provenance = requireNotNull(navStateMap.getInt("provenance"))
82
- view.updateJSNavState(TabsNavState(selectedScreenKey, provenance))
85
+ val navStateRequestMap = requireNotNull(value) { "[RNScreens] navStateRequest must not be nullish" }
86
+ val selectedScreenKey = requireNotNull(navStateRequestMap.getString("selectedScreenKey"))
87
+ val baseProvenance = requireNotNull(navStateRequestMap.getInt("baseProvenance"))
88
+ view.updateJSNavigationStateUpdateRequest(
89
+ TabsNavigationStateUpdateRequest(
90
+ selectedScreenKey = selectedScreenKey,
91
+ baseProvenance = baseProvenance,
92
+ actionOrigin = TabsActionOrigin.PROGRAMMATIC_JS,
93
+ ),
94
+ )
83
95
  }
84
96
 
85
97
  override fun setRejectStaleNavStateUpdates(
86
98
  view: TabsHost,
87
99
  value: Boolean,
88
100
  ) {
89
- view.rejectStaleNavStateUpdates = value
101
+ view.rejectStaleNavigationStateUpdates = value
90
102
  }
91
103
 
92
104
  override fun setTabBarHidden(
@@ -4,6 +4,7 @@ import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.WritableMap
5
5
  import com.facebook.react.uimanager.events.Event
6
6
  import com.swmansion.rnscreens.gamma.common.event.NamingAwareEventType
7
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsActionOrigin
7
8
 
8
9
  class TabsHostTabSelectedEvent(
9
10
  surfaceId: Int,
@@ -12,7 +13,7 @@ class TabsHostTabSelectedEvent(
12
13
  val provenance: Int,
13
14
  val isRepeated: Boolean,
14
15
  val hasTriggeredSpecialEffect: Boolean,
15
- val isNativeAction: Boolean,
16
+ val actionOrigin: TabsActionOrigin,
16
17
  ) : Event<TabsHostTabSelectedEvent>(surfaceId, viewId),
17
18
  NamingAwareEventType {
18
19
  override fun getEventName() = EVENT_NAME
@@ -28,7 +29,7 @@ class TabsHostTabSelectedEvent(
28
29
  putInt(EK_PROVENANCE, provenance)
29
30
  putBoolean(EK_IS_REPEATED, isRepeated)
30
31
  putBoolean(EK_HAS_TRIGGERED_SPECIAL_EFFECT, hasTriggeredSpecialEffect)
31
- putBoolean(EK_IS_NATIVE_ACTION, isNativeAction)
32
+ putString(EK_ACTION_ORIGIN, actionOrigin.toString())
32
33
  }
33
34
 
34
35
  companion object : NamingAwareEventType {
@@ -39,7 +40,7 @@ class TabsHostTabSelectedEvent(
39
40
  private const val EK_PROVENANCE = "provenance"
40
41
  private const val EK_IS_REPEATED = "isRepeated"
41
42
  private const val EK_HAS_TRIGGERED_SPECIAL_EFFECT = "hasTriggeredSpecialEffect"
42
- private const val EK_IS_NATIVE_ACTION = "isNativeAction"
43
+ private const val EK_ACTION_ORIGIN = "actionOrigin"
43
44
 
44
45
  override fun getEventName() = EVENT_NAME
45
46
 
@@ -4,7 +4,7 @@ import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.WritableMap
5
5
  import com.facebook.react.uimanager.events.Event
6
6
  import com.swmansion.rnscreens.gamma.common.event.NamingAwareEventType
7
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
7
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationState
8
8
 
9
9
  /**
10
10
  * React Native event dispatched to JS when a tab selection is prevented because the target
@@ -15,7 +15,7 @@ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
15
15
  class TabsHostTabSelectionPreventedEvent(
16
16
  surfaceId: Int,
17
17
  viewId: Int,
18
- val currentNavState: TabsNavState,
18
+ val currentNavState: TabsNavigationState,
19
19
  val preventedScreenKey: String,
20
20
  ) : Event<TabsHostTabSelectionPreventedEvent>(surfaceId, viewId),
21
21
  NamingAwareEventType {
@@ -27,7 +27,7 @@ class TabsHostTabSelectionPreventedEvent(
27
27
 
28
28
  override fun getEventData(): WritableMap? =
29
29
  Arguments.createMap().apply {
30
- putString(EK_SELECTED_KEY, currentNavState.selectedKey)
30
+ putString(EK_SELECTED_KEY, currentNavState.selectedScreenKey)
31
31
  putInt(EK_PROVENANCE, currentNavState.provenance)
32
32
  putString(EK_PREVENTED_KEY, preventedScreenKey)
33
33
  }
@@ -4,22 +4,23 @@ import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.WritableMap
5
5
  import com.facebook.react.uimanager.events.Event
6
6
  import com.swmansion.rnscreens.gamma.common.event.NamingAwareEventType
7
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavState
8
- import com.swmansion.rnscreens.gamma.tabs.container.TabsNavStateUpdateRejectionReason
7
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationState
8
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateRejectionReason
9
+ import com.swmansion.rnscreens.gamma.tabs.container.TabsNavigationStateUpdateRequest
9
10
 
10
11
  /**
11
12
  * React Native event dispatched to JS when a tab selection request is rejected by the container.
12
13
  *
13
- * Carries the currently active navigation state ([currentNavState]), the rejected update
14
- * ([rejectedNavState]), and the [rejectionReason]. This event is never coalesced — every
14
+ * Carries the currently active navigation state ([currentNavState]), the rejected request
15
+ * ([rejectedRequest]), and the [rejectionReason]. This event is never coalesced — every
15
16
  * rejection is delivered individually so the JS side has a complete picture of state transitions.
16
17
  */
17
18
  class TabsHostTabSelectionRejectedEvent(
18
19
  surfaceId: Int,
19
20
  viewId: Int,
20
- val currentNavState: TabsNavState,
21
- val rejectedNavState: TabsNavState,
22
- val rejectionReason: TabsNavStateUpdateRejectionReason,
21
+ val currentNavState: TabsNavigationState,
22
+ val rejectedRequest: TabsNavigationStateUpdateRequest,
23
+ val rejectionReason: TabsNavigationStateRejectionReason,
23
24
  ) : Event<TabsHostTabSelectionRejectedEvent>(surfaceId, viewId),
24
25
  NamingAwareEventType {
25
26
  override fun getEventName() = EVENT_NAME
@@ -31,10 +32,10 @@ class TabsHostTabSelectionRejectedEvent(
31
32
 
32
33
  override fun getEventData(): WritableMap? =
33
34
  Arguments.createMap().apply {
34
- putString(EK_SELECTED_KEY, currentNavState.selectedKey)
35
+ putString(EK_SELECTED_KEY, currentNavState.selectedScreenKey)
35
36
  putInt(EK_PROVENANCE, currentNavState.provenance)
36
- putString(EK_REJECTED_KEY, rejectedNavState.selectedKey)
37
- putInt(EK_REJECTED_PROVENANCE, rejectedNavState.provenance)
37
+ putString(EK_REJECTED_KEY, rejectedRequest.selectedScreenKey)
38
+ putInt(EK_REJECTED_PROVENANCE, rejectedRequest.baseProvenance)
38
39
  putString(EK_REJECTION_REASON, rejectionReason.toString())
39
40
  }
40
41
 
@@ -233,6 +233,25 @@ RNSOnTabSelectionRejectedRejectionReasonFromRNSTabsNavigationStateRejectionReaso
233
233
  }
234
234
  }
235
235
 
236
+ react::RNSTabsHostIOSEventEmitter::OnTabSelectedActionOrigin RNSOnTabSelectedActionOriginFromRNSTabsActionOrigin(
237
+ RNSTabsActionOrigin actionOrigin)
238
+ {
239
+ using enum facebook::react::RNSTabsHostIOSEventEmitter::OnTabSelectedActionOrigin;
240
+ switch (actionOrigin) {
241
+ case RNSTabsActionOriginUser:
242
+ return User;
243
+ case RNSTabsActionOriginProgrammaticJs:
244
+ return ProgrammaticJs;
245
+ case RNSTabsActionOriginProgrammaticNative:
246
+ return ProgrammaticNative;
247
+ case RNSTabsActionOriginImplicit:
248
+ return Implicit;
249
+ default:
250
+ RCTLogError(@"[RNScreens] Unexpected actionOrigin: %ld", actionOrigin);
251
+ }
252
+ return User;
253
+ }
254
+
236
255
  RNSTabsIconType RNSTabsIconTypeFromIcon(react::RNSTabsScreenIOSIconType iconType)
237
256
  {
238
257
  using enum facebook::react::RNSTabsScreenIOSIconType;
@@ -63,6 +63,9 @@ react::RNSTabsHostIOSEventEmitter::OnTabSelectionRejectedRejectionReason
63
63
  RNSOnTabSelectionRejectedRejectionReasonFromRNSTabsNavigationStateRejectionReason(
64
64
  RNSTabsNavigationStateRejectionReason reason);
65
65
 
66
+ react::RNSTabsHostIOSEventEmitter::OnTabSelectedActionOrigin RNSOnTabSelectedActionOriginFromRNSTabsActionOrigin(
67
+ RNSTabsActionOrigin actionOrigin);
68
+
66
69
  RNSTabsIconType RNSTabsIconTypeFromIcon(react::RNSTabsScreenIOSIconType iconType);
67
70
 
68
71
  RNSTabsScreenSystemItem RNSTabsScreenSystemItemFromReactRNSTabsScreenSystemItem(
@@ -8,8 +8,11 @@
8
8
 
9
9
  namespace react = facebook::react;
10
10
 
11
+ static void *RNSTabsBottomAccessoryNativeWrapperViewContext = &RNSTabsBottomAccessoryNativeWrapperViewContext;
12
+
11
13
  @implementation RNSTabsBottomAccessoryHelper {
12
14
  RNSTabsBottomAccessoryComponentView *__weak _bottomAccessoryView;
15
+ UIView *__weak _observedNativeWrapperView;
13
16
 
14
17
  #if REACT_NATIVE_VERSION_MINOR < 82
15
18
  BOOL _initialStateUpdateSent;
@@ -35,6 +38,7 @@ namespace react = facebook::react;
35
38
 
36
39
  - (void)initState
37
40
  {
41
+ _observedNativeWrapperView = nil;
38
42
  #if REACT_NATIVE_VERSION_MINOR < 82
39
43
  _initialStateUpdateSent = NO;
40
44
  _displayLink = nil;
@@ -110,9 +114,32 @@ namespace react = facebook::react;
110
114
 
111
115
  #pragma mark - Observing frame changes
112
116
 
117
+ - (void)unregisterForAccessoryFrameChanges
118
+ {
119
+ UIView *observedNativeWrapperView = _observedNativeWrapperView;
120
+ if (observedNativeWrapperView == nil) {
121
+ return;
122
+ }
123
+
124
+ [observedNativeWrapperView removeObserver:self
125
+ forKeyPath:@"center"
126
+ context:RNSTabsBottomAccessoryNativeWrapperViewContext];
127
+ _observedNativeWrapperView = nil;
128
+ }
129
+
113
130
  - (void)registerForAccessoryFrameChanges
114
131
  {
115
- [self.nativeWrapperView addObserver:self forKeyPath:@"center" options:NSKeyValueObservingOptionInitial context:nil];
132
+ UIView *nativeWrapperView = self.nativeWrapperView;
133
+ if (_observedNativeWrapperView == nativeWrapperView) {
134
+ return;
135
+ }
136
+
137
+ [self unregisterForAccessoryFrameChanges];
138
+ [nativeWrapperView addObserver:self
139
+ forKeyPath:@"center"
140
+ options:NSKeyValueObservingOptionInitial
141
+ context:RNSTabsBottomAccessoryNativeWrapperViewContext];
142
+ _observedNativeWrapperView = nativeWrapperView;
116
143
  }
117
144
 
118
145
  - (void)observeValueForKeyPath:(NSString *)keyPath
@@ -120,7 +147,11 @@ namespace react = facebook::react;
120
147
  change:(NSDictionary *)change
121
148
  context:(void *)context
122
149
  {
123
- [self notifyWrapperViewFrameHasChanged];
150
+ if (context == RNSTabsBottomAccessoryNativeWrapperViewContext) {
151
+ [self notifyWrapperViewFrameHasChanged];
152
+ } else {
153
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
154
+ }
124
155
  }
125
156
 
126
157
  - (UIView *)nativeWrapperView
@@ -192,9 +223,7 @@ namespace react = facebook::react;
192
223
  {
193
224
  [_bottomAccessoryView unregisterForTraitChanges:_traitChangeRegistration];
194
225
  _traitChangeRegistration = nil;
195
- // Using nativeWrapperView directly here to avoid failing RCTAssert in self.nativeWrapperView.
196
- // If we're called from didMoveToWindow, it's not a problem, but I'm not sure if this will always be the case.
197
- [_bottomAccessoryView.superview.superview removeObserver:self forKeyPath:@"center"];
226
+ [self unregisterForAccessoryFrameChanges];
198
227
  _bottomAccessoryView = nil;
199
228
  #if REACT_NATIVE_VERSION_MINOR < 82
200
229
  [self invalidateDisplayLink];