react-native-screens 3.25.0 → 3.27.0

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 (138) hide show
  1. package/README.md +1 -1
  2. package/RNScreens.podspec +53 -32
  3. package/android/build.gradle +13 -11
  4. package/android/src/main/java/com/swmansion/rnscreens/FragmentHolder.kt +7 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +41 -10
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +35 -36
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt +7 -7
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenEventDispatcher.kt +21 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +46 -38
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +22 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +40 -39
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +35 -10
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt +15 -0
  14. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +7 -5
  15. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +11 -18
  16. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +1 -1
  17. package/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt +1 -1
  18. package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +23 -7
  19. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderAttachedEvent.kt +3 -5
  20. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderBackButtonClickedEvent.kt +3 -5
  21. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderDetachedEvent.kt +3 -5
  22. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderHeightChangeEvent.kt +26 -0
  23. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenAppearEvent.kt +3 -5
  24. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenDisappearEvent.kt +3 -5
  25. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenDismissedEvent.kt +4 -7
  26. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenTransitionProgressEvent.kt +7 -8
  27. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillAppearEvent.kt +3 -5
  28. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillDisappearEvent.kt +3 -5
  29. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarBlurEvent.kt +3 -5
  30. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarChangeTextEvent.kt +5 -6
  31. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarCloseEvent.kt +3 -5
  32. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarFocusEvent.kt +3 -5
  33. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarOpenEvent.kt +3 -5
  34. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarSearchButtonPressEvent.kt +4 -6
  35. package/android/src/main/java/com/swmansion/rnscreens/events/StackFinishTransitioningEvent.kt +3 -5
  36. package/android/src/main/java/com/swmansion/rnscreens/utils/DeviceUtils.kt +12 -0
  37. package/ios/RNSScreen.h +5 -0
  38. package/ios/RNSScreen.mm +163 -7
  39. package/ios/RNSScreenStack.mm +18 -0
  40. package/ios/RNSScreenStackHeaderConfig.mm +17 -0
  41. package/ios/events/RNSHeaderHeightChangeEvent.h +8 -0
  42. package/ios/events/RNSHeaderHeightChangeEvent.mm +59 -0
  43. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  44. package/lib/commonjs/index.native.js +11 -11
  45. package/lib/commonjs/index.native.js.map +1 -1
  46. package/lib/commonjs/native-stack/index.js +14 -0
  47. package/lib/commonjs/native-stack/index.js.map +1 -1
  48. package/lib/commonjs/native-stack/navigators/createNativeStackNavigator.js +1 -1
  49. package/lib/commonjs/native-stack/navigators/createNativeStackNavigator.js.map +1 -1
  50. package/lib/commonjs/native-stack/types.js.map +1 -1
  51. package/lib/commonjs/native-stack/utils/AnimatedHeaderHeightContext.js +13 -0
  52. package/lib/commonjs/native-stack/utils/AnimatedHeaderHeightContext.js.map +1 -0
  53. package/lib/commonjs/native-stack/utils/SafeAreaProviderCompat.js.map +1 -1
  54. package/lib/commonjs/native-stack/utils/getStatusBarHeight.js +22 -0
  55. package/lib/commonjs/native-stack/utils/getStatusBarHeight.js.map +1 -0
  56. package/lib/commonjs/native-stack/utils/useAnimatedHeaderHeight.js +19 -0
  57. package/lib/commonjs/native-stack/utils/useAnimatedHeaderHeight.js.map +1 -0
  58. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +2 -2
  59. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -1
  60. package/lib/commonjs/native-stack/views/HeaderConfig.js +2 -2
  61. package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
  62. package/lib/commonjs/native-stack/views/NativeStackView.js +39 -21
  63. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  64. package/lib/commonjs/reanimated/ReanimatedHeaderHeightContext.js +13 -0
  65. package/lib/commonjs/reanimated/ReanimatedHeaderHeightContext.js.map +1 -0
  66. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +37 -6
  67. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  68. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +2 -2
  69. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -1
  70. package/lib/commonjs/reanimated/index.js +7 -0
  71. package/lib/commonjs/reanimated/index.js.map +1 -1
  72. package/lib/commonjs/reanimated/useReanimatedHeaderHeight.js +19 -0
  73. package/lib/commonjs/reanimated/useReanimatedHeaderHeight.js.map +1 -0
  74. package/lib/commonjs/types.js.map +1 -1
  75. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  76. package/lib/module/index.native.js +12 -12
  77. package/lib/module/index.native.js.map +1 -1
  78. package/lib/module/native-stack/index.js +2 -0
  79. package/lib/module/native-stack/index.js.map +1 -1
  80. package/lib/module/native-stack/navigators/createNativeStackNavigator.js +1 -1
  81. package/lib/module/native-stack/navigators/createNativeStackNavigator.js.map +1 -1
  82. package/lib/module/native-stack/types.js.map +1 -1
  83. package/lib/module/native-stack/utils/AnimatedHeaderHeightContext.js +4 -0
  84. package/lib/module/native-stack/utils/AnimatedHeaderHeightContext.js.map +1 -0
  85. package/lib/module/native-stack/utils/SafeAreaProviderCompat.js.map +1 -1
  86. package/lib/module/native-stack/utils/getStatusBarHeight.js +16 -0
  87. package/lib/module/native-stack/utils/getStatusBarHeight.js.map +1 -0
  88. package/lib/module/native-stack/utils/useAnimatedHeaderHeight.js +10 -0
  89. package/lib/module/native-stack/utils/useAnimatedHeaderHeight.js.map +1 -0
  90. package/lib/module/native-stack/utils/useBackPressSubscription.js +2 -2
  91. package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -1
  92. package/lib/module/native-stack/views/HeaderConfig.js +2 -2
  93. package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
  94. package/lib/module/native-stack/views/NativeStackView.js +40 -22
  95. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  96. package/lib/module/reanimated/ReanimatedHeaderHeightContext.js +5 -0
  97. package/lib/module/reanimated/ReanimatedHeaderHeightContext.js.map +1 -0
  98. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +37 -6
  99. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  100. package/lib/module/reanimated/ReanimatedScreenProvider.js +2 -2
  101. package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -1
  102. package/lib/module/reanimated/index.js +1 -0
  103. package/lib/module/reanimated/index.js.map +1 -1
  104. package/lib/module/reanimated/useReanimatedHeaderHeight.js +10 -0
  105. package/lib/module/reanimated/useReanimatedHeaderHeight.js.map +1 -0
  106. package/lib/module/types.js.map +1 -1
  107. package/lib/typescript/fabric/ScreenNativeComponent.d.ts +4 -0
  108. package/lib/typescript/native-stack/index.d.ts +2 -0
  109. package/lib/typescript/native-stack/types.d.ts +8 -0
  110. package/lib/typescript/native-stack/utils/AnimatedHeaderHeightContext.d.ts +4 -0
  111. package/lib/typescript/native-stack/utils/getStatusBarHeight.d.ts +2 -0
  112. package/lib/typescript/native-stack/utils/useAnimatedHeaderHeight.d.ts +1 -0
  113. package/lib/typescript/reanimated/ReanimatedHeaderHeightContext.d.ts +4 -0
  114. package/lib/typescript/reanimated/index.d.ts +1 -0
  115. package/lib/typescript/reanimated/useReanimatedHeaderHeight.d.ts +3 -0
  116. package/lib/typescript/types.d.ts +11 -0
  117. package/native-stack/README.md +16 -1
  118. package/package.json +36 -38
  119. package/src/fabric/ScreenNativeComponent.ts +5 -0
  120. package/src/index.native.tsx +17 -19
  121. package/src/native-stack/index.tsx +3 -0
  122. package/src/native-stack/types.tsx +10 -1
  123. package/src/native-stack/utils/AnimatedHeaderHeightContext.tsx +8 -0
  124. package/src/native-stack/utils/SafeAreaProviderCompat.tsx +1 -1
  125. package/src/native-stack/utils/getStatusBarHeight.tsx +22 -0
  126. package/src/native-stack/utils/useAnimatedHeaderHeight.tsx +15 -0
  127. package/src/native-stack/views/HeaderConfig.tsx +1 -2
  128. package/src/native-stack/views/NativeStackView.tsx +78 -44
  129. package/src/reanimated/ReanimatedHeaderHeightContext.tsx +7 -0
  130. package/src/reanimated/ReanimatedNativeStackScreen.tsx +58 -11
  131. package/src/reanimated/index.tsx +1 -0
  132. package/src/reanimated/useReanimatedHeaderHeight.tsx +14 -0
  133. package/src/types.tsx +14 -0
  134. package/windows/RNScreens/RNScreens.vcxproj +6 -3
  135. package/windows/RNScreens/ScreenStack.cpp +16 -0
  136. package/windows/RNScreens/ScreenStack.h +1 -0
  137. /package/ios/{RNSScreenViewEvent.h → events/RNSScreenViewEvent.h} +0 -0
  138. /package/ios/{RNSScreenViewEvent.mm → events/RNSScreenViewEvent.mm} +0 -0
@@ -25,15 +25,18 @@ import com.swmansion.rnscreens.events.ScreenWillDisappearEvent
25
25
  import kotlin.math.max
26
26
  import kotlin.math.min
27
27
 
28
- open class ScreenFragment : Fragment {
28
+ open class ScreenFragment : Fragment, ScreenFragmentWrapper {
29
29
  enum class ScreenLifecycleEvent {
30
30
  Appear, WillAppear, Disappear, WillDisappear
31
31
  }
32
32
 
33
+ override val fragment: Fragment
34
+ get() = this
35
+
33
36
  // if we call empty constructor, there is no screen to be assigned so not sure why it is suggested
34
37
  @Suppress("JoinDeclarationAndAssignment")
35
- lateinit var screen: Screen
36
- private val mChildScreenContainers: MutableList<ScreenContainer<*>> = ArrayList()
38
+ override lateinit var screen: Screen
39
+ private val mChildScreenContainers: MutableList<ScreenContainer> = ArrayList()
37
40
  private var shouldUpdateOnResume = false
38
41
  // if we don't set it, it will be 0.0f at the beginning so the progress will not be sent
39
42
  // due to progress value being already 0.0f
@@ -100,7 +103,7 @@ open class ScreenFragment : Fragment {
100
103
  }
101
104
  }
102
105
 
103
- open fun onContainerUpdate() {
106
+ override fun onContainerUpdate() {
104
107
  updateWindowTraits()
105
108
  }
106
109
 
@@ -113,7 +116,7 @@ open class ScreenFragment : Fragment {
113
116
  ScreenWindowTraits.trySetWindowTraits(screen, activity, tryGetContext())
114
117
  }
115
118
 
116
- fun tryGetActivity(): Activity? {
119
+ override fun tryGetActivity(): Activity? {
117
120
  activity?.let { return it }
118
121
  val context = screen.context
119
122
  if (context is ReactContext && context.currentActivity != null) {
@@ -130,7 +133,7 @@ open class ScreenFragment : Fragment {
130
133
  return null
131
134
  }
132
135
 
133
- fun tryGetContext(): ReactContext? {
136
+ override fun tryGetContext(): ReactContext? {
134
137
  if (context is ReactContext) {
135
138
  return context as ReactContext
136
139
  }
@@ -149,17 +152,17 @@ open class ScreenFragment : Fragment {
149
152
  return null
150
153
  }
151
154
 
152
- val childScreenContainers: List<ScreenContainer<*>>
155
+ override val childScreenContainers: List<ScreenContainer>
153
156
  get() = mChildScreenContainers
154
157
 
155
- private fun canDispatchEvent(event: ScreenLifecycleEvent): Boolean = when (event) {
158
+ override fun canDispatchLifecycleEvent(event: ScreenLifecycleEvent): Boolean = when (event) {
156
159
  ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear
157
160
  ScreenLifecycleEvent.Appear -> canDispatchAppear
158
161
  ScreenLifecycleEvent.WillDisappear -> !canDispatchWillAppear
159
162
  ScreenLifecycleEvent.Disappear -> !canDispatchAppear
160
163
  }
161
164
 
162
- private fun setLastEventDispatched(event: ScreenLifecycleEvent) {
165
+ override fun updateLastEventDispatched(event: ScreenLifecycleEvent) {
163
166
  when (event) {
164
167
  ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear = false
165
168
  ScreenLifecycleEvent.Appear -> canDispatchAppear = false
@@ -169,58 +172,61 @@ open class ScreenFragment : Fragment {
169
172
  }
170
173
 
171
174
  private fun dispatchOnWillAppear() {
172
- dispatchEvent(ScreenLifecycleEvent.WillAppear, this)
173
- dispatchTransitionProgress(0.0f, false)
175
+ dispatchLifecycleEvent(ScreenLifecycleEvent.WillAppear, this)
176
+ dispatchTransitionProgressEvent(0.0f, false)
174
177
  }
175
178
 
176
179
  private fun dispatchOnAppear() {
177
- dispatchEvent(ScreenLifecycleEvent.Appear, this)
178
- dispatchTransitionProgress(1.0f, false)
180
+ dispatchLifecycleEvent(ScreenLifecycleEvent.Appear, this)
181
+ dispatchTransitionProgressEvent(1.0f, false)
179
182
  }
180
183
 
181
184
  private fun dispatchOnWillDisappear() {
182
- dispatchEvent(ScreenLifecycleEvent.WillDisappear, this)
183
- dispatchTransitionProgress(0.0f, true)
185
+ dispatchLifecycleEvent(ScreenLifecycleEvent.WillDisappear, this)
186
+ dispatchTransitionProgressEvent(0.0f, true)
184
187
  }
185
188
 
186
189
  private fun dispatchOnDisappear() {
187
- dispatchEvent(ScreenLifecycleEvent.Disappear, this)
188
- dispatchTransitionProgress(1.0f, true)
190
+ dispatchLifecycleEvent(ScreenLifecycleEvent.Disappear, this)
191
+ dispatchTransitionProgressEvent(1.0f, true)
189
192
  }
190
193
 
191
- private fun dispatchEvent(event: ScreenLifecycleEvent, fragment: ScreenFragment) {
192
- if (fragment is ScreenStackFragment && fragment.canDispatchEvent(event)) {
194
+ override fun dispatchLifecycleEvent(event: ScreenLifecycleEvent, fragmentWrapper: ScreenFragmentWrapper) {
195
+ val fragment = fragmentWrapper.fragment
196
+ if (fragment is ScreenStackFragment && fragment.canDispatchLifecycleEvent(event)) {
193
197
  fragment.screen.let {
194
- fragment.setLastEventDispatched(event)
198
+ fragmentWrapper.updateLastEventDispatched(event)
199
+ val surfaceId = UIManagerHelper.getSurfaceId(it)
195
200
  val lifecycleEvent: Event<*> = when (event) {
196
- ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(it.id)
197
- ScreenLifecycleEvent.Appear -> ScreenAppearEvent(it.id)
198
- ScreenLifecycleEvent.WillDisappear -> ScreenWillDisappearEvent(it.id)
199
- ScreenLifecycleEvent.Disappear -> ScreenDisappearEvent(it.id)
201
+ ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(surfaceId, it.id)
202
+ ScreenLifecycleEvent.Appear -> ScreenAppearEvent(surfaceId, it.id)
203
+ ScreenLifecycleEvent.WillDisappear -> ScreenWillDisappearEvent(surfaceId, it.id)
204
+ ScreenLifecycleEvent.Disappear -> ScreenDisappearEvent(surfaceId, it.id)
200
205
  }
201
206
  val screenContext = screen.context as ReactContext
202
207
  val eventDispatcher: EventDispatcher? =
203
208
  UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id)
204
209
  eventDispatcher?.dispatchEvent(lifecycleEvent)
205
- fragment.dispatchEventInChildContainers(event)
210
+ fragmentWrapper.dispatchLifecycleEventInChildContainers(event)
206
211
  }
207
212
  }
208
213
  }
209
214
 
210
- private fun dispatchEventInChildContainers(event: ScreenLifecycleEvent) {
215
+ override fun dispatchLifecycleEventInChildContainers(event: ScreenLifecycleEvent) {
211
216
  mChildScreenContainers.filter { it.screenCount > 0 }.forEach {
212
- it.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) }
217
+ it.topScreen?.fragmentWrapper?.let { fragment -> dispatchLifecycleEvent(event, fragment) }
213
218
  }
214
219
  }
215
220
 
216
- fun dispatchHeaderBackButtonClickedEvent() {
221
+ override fun dispatchHeaderBackButtonClickedEvent() {
217
222
  val screenContext = screen.context as ReactContext
223
+ val surfaceId = UIManagerHelper.getSurfaceId(screenContext)
218
224
  UIManagerHelper
219
225
  .getEventDispatcherForReactTag(screenContext, screen.id)
220
- ?.dispatchEvent(HeaderBackButtonClickedEvent(screen.id))
226
+ ?.dispatchEvent(HeaderBackButtonClickedEvent(surfaceId, screen.id))
221
227
  }
222
228
 
223
- fun dispatchTransitionProgress(alpha: Float, closing: Boolean) {
229
+ override fun dispatchTransitionProgressEvent(alpha: Float, closing: Boolean) {
224
230
  if (this is ScreenStackFragment) {
225
231
  if (mProgress != alpha) {
226
232
  mProgress = max(0.0f, min(1.0f, alpha))
@@ -230,13 +236,14 @@ open class ScreenFragment : Fragment {
230
236
  - progress is between 0 and 1 -> key 3
231
237
  */
232
238
  val coalescingKey = (if (mProgress == 0.0f) 1 else if (mProgress == 1.0f) 2 else 3).toShort()
233
- val container: ScreenContainer<*>? = screen.container
239
+ val container: ScreenContainer? = screen.container
234
240
  val goingForward = if (container is ScreenStack) container.goingForward else false
235
241
  val screenContext = screen.context as ReactContext
236
242
  UIManagerHelper
237
243
  .getEventDispatcherForReactTag(screenContext, screen.id)
238
244
  ?.dispatchEvent(
239
245
  ScreenTransitionProgressEvent(
246
+ UIManagerHelper.getSurfaceId(screenContext),
240
247
  screen.id, mProgress, closing, goingForward, coalescingKey
241
248
  )
242
249
  )
@@ -244,19 +251,19 @@ open class ScreenFragment : Fragment {
244
251
  }
245
252
  }
246
253
 
247
- fun registerChildScreenContainer(screenContainer: ScreenContainer<*>) {
248
- mChildScreenContainers.add(screenContainer)
254
+ override fun addChildScreenContainer(container: ScreenContainer) {
255
+ mChildScreenContainers.add(container)
249
256
  }
250
257
 
251
- fun unregisterChildScreenContainer(screenContainer: ScreenContainer<*>) {
252
- mChildScreenContainers.remove(screenContainer)
258
+ override fun removeChildScreenContainer(container: ScreenContainer) {
259
+ mChildScreenContainers.remove(container)
253
260
  }
254
261
 
255
- fun onViewAnimationStart() {
262
+ override fun onViewAnimationStart() {
256
263
  dispatchViewAnimationEvent(false)
257
264
  }
258
265
 
259
- open fun onViewAnimationEnd() {
266
+ override fun onViewAnimationEnd() {
260
267
  dispatchViewAnimationEvent(true)
261
268
  }
262
269
 
@@ -294,9 +301,10 @@ open class ScreenFragment : Fragment {
294
301
  // we only send dismissed even when the screen has been removed from its container
295
302
  val screenContext = screen.context
296
303
  if (screenContext is ReactContext) {
304
+ val surfaceId = UIManagerHelper.getSurfaceId(screenContext)
297
305
  UIManagerHelper
298
306
  .getEventDispatcherForReactTag(screenContext, screen.id)
299
- ?.dispatchEvent(ScreenDismissedEvent(screen.id))
307
+ ?.dispatchEvent(ScreenDismissedEvent(surfaceId, screen.id))
300
308
  }
301
309
  }
302
310
  mChildScreenContainers.clear()
@@ -0,0 +1,22 @@
1
+ package com.swmansion.rnscreens
2
+
3
+ import android.app.Activity
4
+ import com.facebook.react.bridge.ReactContext
5
+
6
+ interface ScreenFragmentWrapper : FragmentHolder, ScreenEventDispatcher {
7
+ var screen: Screen
8
+
9
+ // Communication with container
10
+ val childScreenContainers: List<ScreenContainer>
11
+ fun addChildScreenContainer(container: ScreenContainer)
12
+ fun removeChildScreenContainer(container: ScreenContainer)
13
+ fun onContainerUpdate()
14
+
15
+ // Animation phase callbacks
16
+ fun onViewAnimationStart()
17
+ fun onViewAnimationEnd()
18
+
19
+ // Helpers
20
+ fun tryGetActivity(): Activity?
21
+ fun tryGetContext(): ReactContext?
22
+ }
@@ -11,19 +11,19 @@ import java.util.Collections
11
11
  import kotlin.collections.ArrayList
12
12
  import kotlin.collections.HashSet
13
13
 
14
- class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(context) {
15
- private val mStack = ArrayList<ScreenStackFragment>()
16
- private val mDismissed: MutableSet<ScreenStackFragment> = HashSet()
14
+ class ScreenStack(context: Context?) : ScreenContainer(context) {
15
+ private val mStack = ArrayList<ScreenStackFragmentWrapper>()
16
+ private val mDismissed: MutableSet<ScreenStackFragmentWrapper> = HashSet()
17
17
  private val drawingOpPool: MutableList<DrawingOp> = ArrayList()
18
18
  private var drawingOps: MutableList<DrawingOp> = ArrayList()
19
- private var mTopScreen: ScreenStackFragment? = null
19
+ private var mTopScreen: ScreenStackFragmentWrapper? = null
20
20
  private var mRemovalTransitionStarted = false
21
21
  private var isDetachingCurrentScreen = false
22
22
  private var reverseLastTwoChildren = false
23
23
  private var previousChildrenCount = 0
24
24
  var goingForward = false
25
25
 
26
- fun dismiss(screenFragment: ScreenStackFragment) {
26
+ fun dismiss(screenFragment: ScreenStackFragmentWrapper) {
27
27
  mDismissed.add(screenFragment)
28
28
  performUpdatesNow()
29
29
  }
@@ -34,9 +34,9 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
34
34
  val rootScreen: Screen
35
35
  get() {
36
36
  for (i in 0 until screenCount) {
37
- val screen = getScreenAt(i)
38
- if (!mDismissed.contains(screen.fragment)) {
39
- return screen
37
+ val screenWrapper = getScreenFragmentWrapperAt(i)
38
+ if (!mDismissed.contains(screenWrapper)) {
39
+ return screenWrapper.screen
40
40
  }
41
41
  }
42
42
  throw IllegalStateException("Stack has no root screen set")
@@ -64,13 +64,14 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
64
64
  }
65
65
 
66
66
  private fun dispatchOnFinishTransitioning() {
67
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
67
68
  UIManagerHelper
68
69
  .getEventDispatcherForReactTag((context as ReactContext), id)
69
- ?.dispatchEvent(StackFinishTransitioningEvent(id))
70
+ ?.dispatchEvent(StackFinishTransitioningEvent(surfaceId, id))
70
71
  }
71
72
 
72
73
  override fun removeScreenAt(index: Int) {
73
- mDismissed.remove(getScreenAt(index).fragment)
74
+ mDismissed.remove(getScreenFragmentWrapperAt(index))
74
75
  super.removeScreenAt(index)
75
76
  }
76
77
 
@@ -79,18 +80,18 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
79
80
  super.removeAllScreens()
80
81
  }
81
82
 
82
- override fun hasScreen(screenFragment: ScreenFragment?): Boolean =
83
- super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment)
83
+ override fun hasScreen(screenFragmentWrapper: ScreenFragmentWrapper?): Boolean =
84
+ super.hasScreen(screenFragmentWrapper) && !mDismissed.contains(screenFragmentWrapper)
84
85
 
85
86
  override fun onUpdate() {
86
87
  // When going back from a nested stack with a single screen on it, we may hit an edge case
87
88
  // when all screens are dismissed and no screen is to be displayed on top. We need to gracefully
88
89
  // handle the case of newTop being NULL, which happens in several places below
89
- var newTop: ScreenStackFragment? = null // newTop is nullable, see the above comment ^
90
- var visibleBottom: ScreenStackFragment? = null // this is only set if newTop has TRANSPARENT_MODAL presentation mode
90
+ var newTop: ScreenFragmentWrapper? = null // newTop is nullable, see the above comment ^
91
+ var visibleBottom: ScreenFragmentWrapper? = null // this is only set if newTop has TRANSPARENT_MODAL presentation mode
91
92
  isDetachingCurrentScreen = false // we reset it so the previous value is not used by mistake
92
93
  for (i in mScreenFragments.indices.reversed()) {
93
- val screen = mScreenFragments[i]
94
+ val screen = getScreenFragmentWrapperAt(i)
94
95
  if (!mDismissed.contains(screen)) {
95
96
  if (newTop == null) {
96
97
  newTop = screen
@@ -177,43 +178,43 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
177
178
  }
178
179
 
179
180
  // remove all screens previously on stack
180
- for (screen in mStack) {
181
- if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) {
182
- it.remove(screen)
181
+ for (fragmentWrapper in mStack) {
182
+ if (!mScreenFragments.contains(fragmentWrapper) || mDismissed.contains(fragmentWrapper)) {
183
+ it.remove(fragmentWrapper.fragment)
183
184
  }
184
185
  }
185
- for (screen in mScreenFragments) {
186
+ for (fragmentWrapper in mScreenFragments) {
186
187
  // Stop detaching screens when reaching visible bottom. All screens above bottom should be
187
188
  // visible.
188
- if (screen === visibleBottom) {
189
+ if (fragmentWrapper === visibleBottom) {
189
190
  break
190
191
  }
191
192
  // detach all screens that should not be visible
192
- if (screen !== newTop && !mDismissed.contains(screen)) {
193
- it.remove(screen)
193
+ if (fragmentWrapper !== newTop && !mDismissed.contains(fragmentWrapper)) {
194
+ it.remove(fragmentWrapper.fragment)
194
195
  }
195
196
  }
196
197
 
197
198
  // attach screens that just became visible
198
- if (visibleBottom != null && !visibleBottom.isAdded) {
199
+ if (visibleBottom != null && !visibleBottom.fragment.isAdded) {
199
200
  val top = newTop
200
201
  var beneathVisibleBottom = true
201
- for (screen in mScreenFragments) {
202
+ for (fragmentWrapper in mScreenFragments) {
202
203
  // ignore all screens beneath the visible bottom
203
204
  if (beneathVisibleBottom) {
204
- beneathVisibleBottom = if (screen === visibleBottom) {
205
+ beneathVisibleBottom = if (fragmentWrapper === visibleBottom) {
205
206
  false
206
207
  } else continue
207
208
  }
208
209
  // when first visible screen found, make all screens after that visible
209
- it.add(id, screen).runOnCommit { top?.screen?.bringToFront() }
210
+ it.add(id, fragmentWrapper.fragment).runOnCommit { top?.screen?.bringToFront() }
210
211
  }
211
- } else if (newTop != null && !newTop.isAdded) {
212
- it.add(id, newTop)
212
+ } else if (newTop != null && !newTop.fragment.isAdded) {
213
+ it.add(id, newTop.fragment)
213
214
  }
214
- mTopScreen = newTop
215
+ mTopScreen = newTop as? ScreenStackFragmentWrapper
215
216
  mStack.clear()
216
- mStack.addAll(mScreenFragments)
217
+ mStack.addAll(mScreenFragments.map { it as ScreenStackFragmentWrapper })
217
218
 
218
219
  turnOffA11yUnderTransparentScreen(visibleBottom)
219
220
 
@@ -222,17 +223,17 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
222
223
  }
223
224
 
224
225
  // only top visible screen should be accessible
225
- private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenStackFragment?) {
226
+ private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenFragmentWrapper?) {
226
227
  if (mScreenFragments.size > 1 && visibleBottom != null) {
227
228
  mTopScreen?.let {
228
229
  if (isTransparent(it)) {
229
230
  val screenFragmentsBeneathTop = mScreenFragments.slice(0 until mScreenFragments.size - 1).asReversed()
230
231
  // go from the top of the stack excluding the top screen
231
- for (screenFragment in screenFragmentsBeneathTop) {
232
- screenFragment.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
232
+ for (fragmentWrapper in screenFragmentsBeneathTop) {
233
+ fragmentWrapper.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
233
234
 
234
235
  // don't change a11y below non-transparent screens
235
- if (screenFragment == visibleBottom) {
236
+ if (fragmentWrapper == visibleBottom) {
236
237
  break
237
238
  }
238
239
  }
@@ -325,11 +326,11 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
325
326
  }
326
327
 
327
328
  companion object {
328
- private fun isTransparent(fragment: ScreenStackFragment): Boolean =
329
- fragment.screen.stackPresentation === Screen.StackPresentation.TRANSPARENT_MODAL
329
+ private fun isTransparent(fragmentWrapper: ScreenFragmentWrapper): Boolean =
330
+ fragmentWrapper.screen.stackPresentation === Screen.StackPresentation.TRANSPARENT_MODAL
330
331
 
331
- private fun needsDrawReordering(fragment: ScreenStackFragment): Boolean =
332
- fragment.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM ||
333
- fragment.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM
332
+ private fun needsDrawReordering(fragmentWrapper: ScreenFragmentWrapper): Boolean =
333
+ fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM ||
334
+ fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM
334
335
  }
335
336
  }
@@ -19,13 +19,16 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
19
19
  import com.facebook.react.uimanager.PixelUtil
20
20
  import com.google.android.material.appbar.AppBarLayout
21
21
  import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
22
+ import com.swmansion.rnscreens.utils.DeviceUtils
22
23
 
23
- class ScreenStackFragment : ScreenFragment {
24
+ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper {
24
25
  private var mAppBarLayout: AppBarLayout? = null
25
26
  private var mToolbar: Toolbar? = null
26
27
  private var mShadowHidden = false
27
28
  private var mIsTranslucent = false
28
29
 
30
+ private var mLastFocusedChild: View? = null
31
+
29
32
  var searchView: CustomSearchView? = null
30
33
  var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null
31
34
 
@@ -38,7 +41,7 @@ class ScreenStackFragment : ScreenFragment {
38
41
  )
39
42
  }
40
43
 
41
- fun removeToolbar() {
44
+ override fun removeToolbar() {
42
45
  mAppBarLayout?.let {
43
46
  mToolbar?.let { toolbar ->
44
47
  if (toolbar.parent === it) {
@@ -49,7 +52,7 @@ class ScreenStackFragment : ScreenFragment {
49
52
  mToolbar = null
50
53
  }
51
54
 
52
- fun setToolbar(toolbar: Toolbar) {
55
+ override fun setToolbar(toolbar: Toolbar) {
53
56
  mAppBarLayout?.addView(toolbar)
54
57
  toolbar.layoutParams = AppBarLayout.LayoutParams(
55
58
  AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT
@@ -57,14 +60,14 @@ class ScreenStackFragment : ScreenFragment {
57
60
  mToolbar = toolbar
58
61
  }
59
62
 
60
- fun setToolbarShadowHidden(hidden: Boolean) {
63
+ override fun setToolbarShadowHidden(hidden: Boolean) {
61
64
  if (mShadowHidden != hidden) {
62
65
  mAppBarLayout?.targetElevation = if (hidden) 0f else PixelUtil.toPixelFromDIP(4f)
63
66
  mShadowHidden = hidden
64
67
  }
65
68
  }
66
69
 
67
- fun setToolbarTranslucent(translucent: Boolean) {
70
+ override fun setToolbarTranslucent(translucent: Boolean) {
68
71
  if (mIsTranslucent != translucent) {
69
72
  val params = screen.layoutParams
70
73
  (params as CoordinatorLayout.LayoutParams).behavior =
@@ -89,6 +92,11 @@ class ScreenStackFragment : ScreenFragment {
89
92
  }
90
93
  }
91
94
 
95
+ override fun onStart() {
96
+ mLastFocusedChild?.requestFocus()
97
+ super.onStart()
98
+ }
99
+
92
100
  override fun onCreateView(
93
101
  inflater: LayoutInflater,
94
102
  container: ViewGroup?,
@@ -123,6 +131,13 @@ class ScreenStackFragment : ScreenFragment {
123
131
  return view
124
132
  }
125
133
 
134
+ override fun onStop() {
135
+ if (DeviceUtils.isPlatformAndroidTV(context))
136
+ mLastFocusedChild = findLastFocusedChild()
137
+
138
+ super.onStop()
139
+ }
140
+
126
141
  override fun onPrepareOptionsMenu(menu: Menu) {
127
142
  updateToolbarMenu(menu)
128
143
  return super.onPrepareOptionsMenu(menu)
@@ -163,8 +178,18 @@ class ScreenStackFragment : ScreenFragment {
163
178
  }
164
179
  }
165
180
 
166
- fun canNavigateBack(): Boolean {
167
- val container: ScreenContainer<*>? = screen.container
181
+ private fun findLastFocusedChild(): View? {
182
+ var view: View? = screen
183
+ while (view != null) {
184
+ if (view.isFocused) return view
185
+ view = if (view is ViewGroup) view.focusedChild else null
186
+ }
187
+
188
+ return null
189
+ }
190
+
191
+ override fun canNavigateBack(): Boolean {
192
+ val container: ScreenContainer? = screen.container
168
193
  check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
169
194
  return if (container.rootScreen == screen) {
170
195
  // this screen is the root of the container, if it is nested we can check parent container
@@ -180,8 +205,8 @@ class ScreenStackFragment : ScreenFragment {
180
205
  }
181
206
  }
182
207
 
183
- fun dismiss() {
184
- val container: ScreenContainer<*>? = screen.container
208
+ override fun dismiss() {
209
+ val container: ScreenContainer? = screen.container
185
210
  check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
186
211
  container.dismiss(this)
187
212
  }
@@ -251,7 +276,7 @@ class ScreenStackFragment : ScreenFragment {
251
276
  override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
252
277
  super.applyTransformation(interpolatedTime, t)
253
278
  // interpolated time should be the progress of the current transition
254
- mFragment.dispatchTransitionProgress(interpolatedTime, !mFragment.isResumed)
279
+ mFragment.dispatchTransitionProgressEvent(interpolatedTime, !mFragment.isResumed)
255
280
  }
256
281
  }
257
282
  }
@@ -0,0 +1,15 @@
1
+ package com.swmansion.rnscreens
2
+
3
+ import androidx.appcompat.widget.Toolbar
4
+
5
+ interface ScreenStackFragmentWrapper : ScreenFragmentWrapper {
6
+ // Toolbar management
7
+ fun removeToolbar()
8
+ fun setToolbar(toolbar: Toolbar)
9
+ fun setToolbarShadowHidden(hidden: Boolean)
10
+ fun setToolbarTranslucent(translucent: Boolean)
11
+
12
+ // Navigation
13
+ fun canNavigateBack(): Boolean
14
+ fun dismiss()
15
+ }
@@ -24,6 +24,7 @@ import com.swmansion.rnscreens.events.HeaderDetachedEvent
24
24
  class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
25
25
  private val mConfigSubviews = ArrayList<ScreenStackHeaderSubview>(3)
26
26
  val toolbar: CustomToolbar
27
+ var mIsHidden = false
27
28
  private var headerTopInset: Int? = null
28
29
  private var mTitle: String? = null
29
30
  private var mTitleColor = 0
@@ -32,7 +33,6 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
32
33
  private var mTitleFontSize = 0f
33
34
  private var mTitleFontWeight = 0
34
35
  private var mBackgroundColor: Int? = null
35
- private var mIsHidden = false
36
36
  private var mIsBackButtonHidden = false
37
37
  private var mIsShadowHidden = false
38
38
  private var mDestroyed = false
@@ -76,8 +76,9 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
76
76
  override fun onAttachedToWindow() {
77
77
  super.onAttachedToWindow()
78
78
  mIsAttachedToWindow = true
79
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
79
80
  UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id)
80
- ?.dispatchEvent(HeaderAttachedEvent(id))
81
+ ?.dispatchEvent(HeaderAttachedEvent(surfaceId, id))
81
82
  // we want to save the top inset before the status bar can be hidden, which would resolve in
82
83
  // inset being 0
83
84
  if (headerTopInset == null) {
@@ -93,8 +94,9 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
93
94
  override fun onDetachedFromWindow() {
94
95
  super.onDetachedFromWindow()
95
96
  mIsAttachedToWindow = false
97
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
96
98
  UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id)
97
- ?.dispatchEvent(HeaderDetachedEvent(id))
99
+ ?.dispatchEvent(HeaderDetachedEvent(surfaceId, id))
98
100
  }
99
101
 
100
102
  private val screen: Screen?
@@ -139,7 +141,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
139
141
  val reactContext = if (context is ReactContext) {
140
142
  context as ReactContext
141
143
  } else {
142
- it.fragment?.tryGetContext()
144
+ it.fragmentWrapper?.tryGetContext()
143
145
  }
144
146
  ScreenWindowTraits.trySetWindowTraits(it, activity, reactContext)
145
147
  }
@@ -388,7 +390,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
388
390
 
389
391
  // set primary color as background by default
390
392
  val tv = TypedValue()
391
- if (context.theme.resolveAttribute(R.attr.colorPrimary, tv, true)) {
393
+ if (context.theme.resolveAttribute(android.R.attr.colorPrimary, tv, true)) {
392
394
  toolbar.setBackgroundColor(tv.data)
393
395
  }
394
396
  toolbar.clipChildren = false
@@ -11,6 +11,7 @@ import com.facebook.react.uimanager.annotations.ReactProp
11
11
  import com.facebook.react.viewmanagers.RNSScreenManagerDelegate
12
12
  import com.facebook.react.viewmanagers.RNSScreenManagerInterface
13
13
  import com.swmansion.rnscreens.events.HeaderBackButtonClickedEvent
14
+ import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
14
15
  import com.swmansion.rnscreens.events.ScreenAppearEvent
15
16
  import com.swmansion.rnscreens.events.ScreenDisappearEvent
16
17
  import com.swmansion.rnscreens.events.ScreenDismissedEvent
@@ -166,24 +167,16 @@ class ScreenViewManager : ViewGroupManager<Screen>(), RNSScreenManagerInterface<
166
167
 
167
168
  override fun setSheetExpandsWhenScrolledToEdge(view: Screen?, value: Boolean) = Unit
168
169
 
169
- override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
170
- return MapBuilder.of(
171
- ScreenDismissedEvent.EVENT_NAME,
172
- MapBuilder.of("registrationName", "onDismissed"),
173
- ScreenWillAppearEvent.EVENT_NAME,
174
- MapBuilder.of("registrationName", "onWillAppear"),
175
- ScreenAppearEvent.EVENT_NAME,
176
- MapBuilder.of("registrationName", "onAppear"),
177
- ScreenWillDisappearEvent.EVENT_NAME,
178
- MapBuilder.of("registrationName", "onWillDisappear"),
179
- ScreenDisappearEvent.EVENT_NAME,
180
- MapBuilder.of("registrationName", "onDisappear"),
181
- HeaderBackButtonClickedEvent.EVENT_NAME,
182
- MapBuilder.of("registrationName", "onHeaderBackButtonClicked"),
183
- ScreenTransitionProgressEvent.EVENT_NAME,
184
- MapBuilder.of("registrationName", "onTransitionProgress")
185
- )
186
- }
170
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> = mutableMapOf(
171
+ ScreenDismissedEvent.EVENT_NAME to MapBuilder.of("registrationName", "onDismissed"),
172
+ ScreenWillAppearEvent.EVENT_NAME to MapBuilder.of("registrationName", "onWillAppear"),
173
+ ScreenAppearEvent.EVENT_NAME to MapBuilder.of("registrationName", "onAppear"),
174
+ ScreenWillDisappearEvent.EVENT_NAME to MapBuilder.of("registrationName", "onWillDisappear"),
175
+ ScreenDisappearEvent.EVENT_NAME to MapBuilder.of("registrationName", "onDisappear"),
176
+ HeaderHeightChangeEvent.EVENT_NAME to MapBuilder.of("registrationName", "onHeaderHeightChange"),
177
+ HeaderBackButtonClickedEvent.EVENT_NAME to MapBuilder.of("registrationName", "onHeaderBackButtonClicked"),
178
+ ScreenTransitionProgressEvent.EVENT_NAME to MapBuilder.of("registrationName", "onTransitionProgress")
179
+ )
187
180
 
188
181
  protected override fun getDelegate(): ViewManagerDelegate<Screen> = mDelegate
189
182
 
@@ -259,7 +259,7 @@ object ScreenWindowTraits {
259
259
  screen: Screen?,
260
260
  trait: WindowTraits
261
261
  ): Screen? {
262
- screen?.fragment?.let {
262
+ screen?.fragmentWrapper?.let {
263
263
  for (sc in it.childScreenContainers) {
264
264
  // we check only the top screen for the trait
265
265
  val topScreen = sc.topScreen
@@ -11,7 +11,7 @@ internal class ScreensShadowNode(private var mContext: ReactContext) : LayoutSha
11
11
  super.onBeforeLayout(nativeViewHierarchyOptimizer)
12
12
  (mContext.getNativeModule(UIManagerModule::class.java))?.addUIBlock { nativeViewHierarchyManager: NativeViewHierarchyManager ->
13
13
  val view = nativeViewHierarchyManager.resolveView(reactTag)
14
- if (view is ScreenContainer<*>) {
14
+ if (view is ScreenContainer) {
15
15
  view.performUpdates()
16
16
  }
17
17
  }