react-native-screens 4.10.0 → 4.11.0-beta.1

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 (83) hide show
  1. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +22 -13
  2. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +3 -0
  3. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +9 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/ScreenModalFragment.kt +2 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +53 -102
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +7 -125
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +1 -1
  8. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +2 -2
  9. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +23 -0
  10. package/android/src/main/java/com/swmansion/rnscreens/stack/anim/ScreensAnimation.kt +18 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/stack/views/ChildDrawingOrderStrategyImpl.kt +48 -0
  12. package/android/src/main/java/com/swmansion/rnscreens/stack/views/ChildrenDrawingOrderStrategy.kt +24 -0
  13. package/android/src/main/java/com/swmansion/rnscreens/stack/views/ScreensCoordinatorLayout.kt +99 -0
  14. package/ios/RNSConvert.mm +2 -0
  15. package/ios/RNSEnums.h +2 -1
  16. package/ios/RNSScreen.mm +10 -0
  17. package/ios/RNSScreenStack.mm +3 -1
  18. package/ios/RNSScreenStackHeaderConfig.mm +5 -10
  19. package/lib/commonjs/components/ScreenContentWrapper.windows.js +10 -0
  20. package/lib/commonjs/components/ScreenContentWrapper.windows.js.map +1 -0
  21. package/lib/commonjs/components/ScreenFooter.windows.js +11 -0
  22. package/lib/commonjs/components/ScreenFooter.windows.js.map +1 -0
  23. package/lib/commonjs/fabric/ModalScreenNativeComponent.js.map +1 -1
  24. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  25. package/lib/commonjs/gesture-handler/ScreenGestureDetector.js +3 -1
  26. package/lib/commonjs/gesture-handler/ScreenGestureDetector.js.map +1 -1
  27. package/lib/commonjs/native-stack/utils/getDefaultHeaderHeight.js +2 -2
  28. package/lib/commonjs/native-stack/utils/getDefaultHeaderHeight.js.map +1 -1
  29. package/lib/module/components/ScreenContentWrapper.windows.js +4 -0
  30. package/lib/module/components/ScreenContentWrapper.windows.js.map +1 -0
  31. package/lib/module/components/ScreenFooter.windows.js +6 -0
  32. package/lib/module/components/ScreenFooter.windows.js.map +1 -0
  33. package/lib/module/fabric/ModalScreenNativeComponent.js.map +1 -1
  34. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  35. package/lib/module/gesture-handler/ScreenGestureDetector.js +3 -1
  36. package/lib/module/gesture-handler/ScreenGestureDetector.js.map +1 -1
  37. package/lib/module/native-stack/utils/getDefaultHeaderHeight.js +2 -2
  38. package/lib/module/native-stack/utils/getDefaultHeaderHeight.js.map +1 -1
  39. package/lib/typescript/components/ScreenContentWrapper.windows.d.ts +4 -0
  40. package/lib/typescript/components/ScreenContentWrapper.windows.d.ts.map +1 -0
  41. package/lib/typescript/components/ScreenFooter.windows.d.ts +6 -0
  42. package/lib/typescript/components/ScreenFooter.windows.d.ts.map +1 -0
  43. package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts +1 -1
  44. package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts.map +1 -1
  45. package/lib/typescript/fabric/ScreenNativeComponent.d.ts +1 -1
  46. package/lib/typescript/fabric/ScreenNativeComponent.d.ts.map +1 -1
  47. package/lib/typescript/gesture-handler/ScreenGestureDetector.d.ts.map +1 -1
  48. package/lib/typescript/native-stack/types.d.ts +12 -5
  49. package/lib/typescript/native-stack/types.d.ts.map +1 -1
  50. package/lib/typescript/native-stack/utils/getDefaultHeaderHeight.d.ts.map +1 -1
  51. package/lib/typescript/types.d.ts +13 -6
  52. package/lib/typescript/types.d.ts.map +1 -1
  53. package/native-stack/README.md +7 -2
  54. package/package.json +1 -1
  55. package/src/components/ScreenContentWrapper.windows.tsx +5 -0
  56. package/src/components/ScreenFooter.windows.tsx +7 -0
  57. package/src/fabric/ModalScreenNativeComponent.ts +1 -0
  58. package/src/fabric/ScreenNativeComponent.ts +1 -0
  59. package/src/gesture-handler/ScreenGestureDetector.tsx +3 -1
  60. package/src/native-stack/types.tsx +12 -5
  61. package/src/native-stack/utils/getDefaultHeaderHeight.tsx +4 -2
  62. package/src/types.tsx +14 -6
  63. package/windows/RNScreens/ModalScreenViewManager.cpp +22 -0
  64. package/windows/RNScreens/ModalScreenViewManager.h +13 -0
  65. package/windows/RNScreens/RNScreens.vcxproj +11 -1
  66. package/windows/RNScreens/RNScreens.vcxproj.filters +10 -0
  67. package/windows/RNScreens/ReactPackageProvider.cpp +18 -0
  68. package/windows/RNScreens/Screen.cpp +128 -122
  69. package/windows/RNScreens/Screen.h +6 -2
  70. package/windows/RNScreens/ScreenStackHeaderConfig.cpp +25 -1
  71. package/windows/RNScreens/ScreenStackHeaderConfig.h +10 -1
  72. package/windows/RNScreens/ScreenStackHeaderConfigViewManager.cpp +42 -1
  73. package/windows/RNScreens/ScreenStackHeaderConfigViewManager.h +15 -0
  74. package/windows/RNScreens/ScreenStackHeaderSubview.cpp +43 -0
  75. package/windows/RNScreens/ScreenStackHeaderSubview.h +24 -0
  76. package/windows/RNScreens/ScreenStackHeaderSubviewViewManager.cpp +129 -0
  77. package/windows/RNScreens/ScreenStackHeaderSubviewViewManager.h +77 -0
  78. package/windows/RNScreens/ScreenViewManager.cpp +45 -16
  79. package/windows/RNScreens/ScreenViewManager.h +1 -1
  80. package/windows/RNScreens/SearchBar.cpp +19 -0
  81. package/windows/RNScreens/SearchBar.h +14 -0
  82. package/windows/RNScreens/SearchBarViewManager.cpp +88 -0
  83. package/windows/RNScreens/SearchBarViewManager.h +62 -0
@@ -32,7 +32,6 @@ import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
32
32
  import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
33
33
  import com.swmansion.rnscreens.events.SheetDetentChangedEvent
34
34
  import com.swmansion.rnscreens.ext.parentAsViewGroup
35
- import java.lang.ref.WeakReference
36
35
 
37
36
  @SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated.
38
37
  class Screen(
@@ -42,8 +41,6 @@ class Screen(
42
41
  val fragment: Fragment?
43
42
  get() = fragmentWrapper?.fragment
44
43
 
45
- var contentWrapper = WeakReference<ScreenContentWrapper>(null)
46
-
47
44
  val sheetBehavior: BottomSheetBehavior<Screen>?
48
45
  get() = (layoutParams as? CoordinatorLayout.LayoutParams)?.behavior as? BottomSheetBehavior<Screen>
49
46
 
@@ -135,8 +132,10 @@ class Screen(
135
132
  ) {
136
133
  val height = bottom - top
137
134
 
138
- if (isSheetFitToContents()) {
139
- sheetBehavior?.useSingleDetent(height)
135
+ if (usesFormSheetPresentation()) {
136
+ if (isSheetFitToContents()) {
137
+ sheetBehavior?.useSingleDetent(height)
138
+ }
140
139
 
141
140
  if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
142
141
  // On old architecture we delay enter transition in order to wait for initial frame.
@@ -153,7 +152,6 @@ class Screen(
153
152
 
154
153
  fun registerLayoutCallbackForWrapper(wrapper: ScreenContentWrapper) {
155
154
  wrapper.delegate = this
156
- this.contentWrapper = WeakReference(wrapper)
157
155
  }
158
156
 
159
157
  override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
@@ -187,22 +185,27 @@ class Screen(
187
185
  }
188
186
 
189
187
  internal fun onBottomSheetBehaviorDidLayout(coordinatorLayoutDidChange: Boolean) {
190
- if (!usesFormSheetPresentation()) {
188
+ if (!usesFormSheetPresentation() || !isNativeStackScreen) {
191
189
  return
192
190
  }
193
- if (coordinatorLayoutDidChange && isNativeStackScreen) {
191
+ if (coordinatorLayoutDidChange) {
194
192
  dispatchShadowStateUpdate(width, height, top)
195
193
  }
196
- if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
197
- maybeTriggerPostponedTransition()
198
- }
199
194
 
200
195
  footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height)
196
+
197
+ if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
198
+ // When using form sheet presentation we want to delay enter transition **on Paper** in order
199
+ // to wait for initial layout from React, otherwise the animator-based animation will look
200
+ // glitchy. *This seems to not be needed on Fabric*.
201
+ triggerPostponedEnterTransitionIfNeeded()
202
+ }
201
203
  }
202
204
 
203
- private fun maybeTriggerPostponedTransition() {
205
+ private fun triggerPostponedEnterTransitionIfNeeded() {
204
206
  if (shouldTriggerPostponedTransitionAfterLayout) {
205
207
  shouldTriggerPostponedTransitionAfterLayout = false
208
+ // This will trigger enter transition only if one was requested by ScreenStack
206
209
  fragment?.startPostponedEnterTransition()
207
210
  }
208
211
  }
@@ -240,6 +243,9 @@ class Screen(
240
243
  val headerConfig: ScreenStackHeaderConfig?
241
244
  get() = children.find { it is ScreenStackHeaderConfig } as? ScreenStackHeaderConfig
242
245
 
246
+ val contentWrapper: ScreenContentWrapper?
247
+ get() = children.find { it is ScreenContentWrapper } as? ScreenContentWrapper
248
+
243
249
  /**
244
250
  * While transitioning this property allows to optimize rendering behavior on Android and provide
245
251
  * a correct blending options for the animated screen. It is turned on automatically by the
@@ -260,7 +266,10 @@ class Screen(
260
266
  )
261
267
  }
262
268
 
263
- fun isTransparent(): Boolean =
269
+ /**
270
+ * Whether this screen allows to see the content underneath it.
271
+ */
272
+ fun isTranslucent(): Boolean =
264
273
  when (stackPresentation) {
265
274
  StackPresentation.TRANSPARENT_MODAL,
266
275
  StackPresentation.FORM_SHEET,
@@ -128,6 +128,9 @@ open class ScreenFragment :
128
128
  ScreenWindowTraits.trySetWindowTraits(screen, activity, tryGetContext())
129
129
  }
130
130
 
131
+ // Plain ScreenFragments can not be translucent
132
+ override fun isTranslucent() = false
133
+
131
134
  override fun tryGetActivity(): Activity? {
132
135
  activity?.let { return it }
133
136
  val context = screen.context
@@ -26,6 +26,15 @@ interface ScreenFragmentWrapper :
26
26
 
27
27
  fun onViewAnimationEnd()
28
28
 
29
+ // Fragment information
30
+
31
+ /**
32
+ * Whether this screen fragment makes it possible to see content underneath it
33
+ * (not fully opaque or does not fill full screen).
34
+ */
35
+ fun isTranslucent(): Boolean
36
+
37
+
29
38
  // Helpers
30
39
  fun tryGetActivity(): Activity?
31
40
 
@@ -92,6 +92,8 @@ class ScreenModalFragment :
92
92
  savedInstanceState: Bundle?,
93
93
  ): View? = null
94
94
 
95
+ override fun isTranslucent(): Boolean = true
96
+
95
97
  override fun dismissFromContainer() {
96
98
  check(container is ScreenStack)
97
99
  val container = container as ScreenStack
@@ -7,78 +7,16 @@ import android.view.View
7
7
  import com.facebook.react.bridge.ReactContext
8
8
  import com.facebook.react.uimanager.UIManagerHelper
9
9
  import com.swmansion.rnscreens.Screen.StackAnimation
10
- import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
10
+ import com.swmansion.rnscreens.bottomsheet.requiresEnterTransitionPostponing
11
11
  import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
12
+ import com.swmansion.rnscreens.stack.views.ChildrenDrawingOrderStrategy
13
+ import com.swmansion.rnscreens.stack.views.ReverseFromIndex
14
+ import com.swmansion.rnscreens.stack.views.ReverseOrder
15
+ import com.swmansion.rnscreens.stack.views.ScreensCoordinatorLayout
12
16
  import com.swmansion.rnscreens.utils.setTweenAnimations
13
- import java.util.Collections
14
17
  import kotlin.collections.ArrayList
15
18
  import kotlin.math.max
16
19
 
17
- internal interface ChildDrawingOrderStrategy {
18
- /**
19
- * Mutates the list of draw operations **in-place**.
20
- */
21
- fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>)
22
-
23
- /**
24
- * Enables the given strategy. When enabled - the strategy **might** mutate the operations
25
- * list passed to `apply` method.
26
- */
27
- fun enable()
28
-
29
- /**
30
- * Disables the given strategy - even when `apply` is called it **must not** produce
31
- * any side effect (it must not manipulate the drawing operations list passed to `apply` method).
32
- */
33
- fun disable()
34
-
35
- fun isEnabled(): Boolean
36
- }
37
-
38
- internal abstract class ChildDrawingOrderStrategyBase(
39
- var enabled: Boolean = false,
40
- ) : ChildDrawingOrderStrategy {
41
- override fun enable() {
42
- enabled = true
43
- }
44
-
45
- override fun disable() {
46
- enabled = false
47
- }
48
-
49
- override fun isEnabled() = enabled
50
- }
51
-
52
- internal class SwapLastTwo : ChildDrawingOrderStrategyBase() {
53
- override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
54
- if (!isEnabled()) {
55
- return
56
- }
57
- if (drawingOperations.size >= 2) {
58
- Collections.swap(drawingOperations, drawingOperations.lastIndex, drawingOperations.lastIndex - 1)
59
- }
60
- }
61
- }
62
-
63
- internal class ReverseOrderInRange(
64
- val range: IntRange,
65
- ) : ChildDrawingOrderStrategyBase() {
66
- override fun apply(drawingOperations: MutableList<ScreenStack.DrawingOp>) {
67
- if (!isEnabled()) {
68
- return
69
- }
70
-
71
- var startRange = range.start
72
- var endRange = range.endInclusive
73
-
74
- while (startRange < endRange) {
75
- Collections.swap(drawingOperations, startRange, endRange)
76
- startRange += 1
77
- endRange -= 1
78
- }
79
- }
80
- }
81
-
82
20
  class ScreenStack(
83
21
  context: Context?,
84
22
  ) : ScreenContainer(context) {
@@ -88,9 +26,9 @@ class ScreenStack(
88
26
  private var drawingOps: MutableList<DrawingOp> = ArrayList()
89
27
  private var topScreenWrapper: ScreenStackFragmentWrapper? = null
90
28
  private var removalTransitionStarted = false
91
- private var previousChildrenCount = 0
92
29
 
93
- private var childDrawingOrderStrategy: ChildDrawingOrderStrategy? = null
30
+ private var childrenDrawingOrderStrategy: ChildrenDrawingOrderStrategy? = null
31
+ private var disappearingTransitioningChildren: MutableList<View> = ArrayList()
94
32
 
95
33
  var goingForward = false
96
34
 
@@ -122,14 +60,25 @@ class ScreenStack(
122
60
  }
123
61
 
124
62
  override fun startViewTransition(view: View) {
63
+ check(view is ScreensCoordinatorLayout) { "[RNScreens] Unexpected type of ScreenStack direct subview ${view.javaClass}" }
125
64
  super.startViewTransition(view)
126
- childDrawingOrderStrategy?.enable()
65
+ if (view.fragment.isRemoving) {
66
+ disappearingTransitioningChildren.add(view)
67
+ }
68
+ if (disappearingTransitioningChildren.isNotEmpty()) {
69
+ childrenDrawingOrderStrategy?.enable()
70
+ }
127
71
  removalTransitionStarted = true
128
72
  }
129
73
 
130
74
  override fun endViewTransition(view: View) {
131
75
  super.endViewTransition(view)
132
- childDrawingOrderStrategy?.disable()
76
+
77
+ disappearingTransitioningChildren.remove(view)
78
+
79
+ if (disappearingTransitioningChildren.isEmpty()) {
80
+ childrenDrawingOrderStrategy?.disable()
81
+ }
133
82
  if (removalTransitionStarted) {
134
83
  removalTransitionStarted = false
135
84
  dispatchOnFinishTransitioning()
@@ -172,19 +121,22 @@ class ScreenStack(
172
121
  var visibleBottom: ScreenFragmentWrapper? = null
173
122
 
174
123
  // reset, to not use previously set strategy by mistake
175
- childDrawingOrderStrategy = null
124
+ childrenDrawingOrderStrategy = null
176
125
 
177
126
  // Determine new first & last visible screens.
178
127
  val notDismissedWrappers =
179
128
  screenWrappers
180
129
  .asReversed()
181
130
  .asSequence()
182
- .filter { !dismissedWrappers.contains(it) && it.screen.activityState !== Screen.ActivityState.INACTIVE }
131
+ .filter {
132
+ !dismissedWrappers.contains(it) &&
133
+ it.screen.activityState !== Screen.ActivityState.INACTIVE
134
+ }
183
135
 
184
136
  newTop = notDismissedWrappers.firstOrNull()
185
137
  visibleBottom =
186
138
  notDismissedWrappers
187
- .dropWhile { it.screen.isTransparent() }
139
+ .dropWhile { it.isTranslucent() }
188
140
  .firstOrNull()
189
141
  ?.takeUnless { it === newTop }
190
142
 
@@ -202,11 +154,13 @@ class ScreenStack(
202
154
  // if the previous top screen does not exist anymore and the new top was not on the stack
203
155
  // before, probably replace or reset was called, so we play the "close animation".
204
156
  // Otherwise it's open animation
205
- val previousTopScreenRemainsInStack = topScreenWrapper?.let { screenWrappers.contains(it) } == true
157
+ val previousTopScreenRemainsInStack =
158
+ topScreenWrapper?.let { screenWrappers.contains(it) } == true
206
159
  val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH
207
160
  shouldUseOpenAnimation = previousTopScreenRemainsInStack || isPushReplace
208
161
  // if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one
209
- stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else topScreenWrapper?.screen?.stackAnimation
162
+ stackAnimation =
163
+ if (shouldUseOpenAnimation) newTop.screen.stackAnimation else topScreenWrapper?.screen?.stackAnimation
210
164
  } else {
211
165
  // mTopScreen was not present before so newTop is the first screen added to a stack
212
166
  // and we don't want the animation when it is entering
@@ -226,20 +180,20 @@ class ScreenStack(
226
180
  needsDrawReordering(newTop, stackAnimation) &&
227
181
  visibleBottom == null
228
182
  ) {
229
- // When using an open animation in which two screens overlap (eg. fade_from_bottom or
230
- // slide_from_bottom), we want to draw the previous screen under the new one,
231
- // which is apparently not the default option. Android always draws the disappearing view
183
+ // When using an open animation in which screens overlap (eg. fade_from_bottom or
184
+ // slide_from_bottom), we want to draw the previous screens under the new one,
185
+ // which is apparently not the default option. Android always draws the disappearing views
232
186
  // on top of the appearing one. We then reverse the order of the views so the new screen
233
- // appears on top of the previous one. You can read more about in the comment
187
+ // appears on top of the previous ones. You can read more about in the comment
234
188
  // for the code we use to change that behavior:
235
189
  // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18
236
190
  // Note: This should not be set in case there is only a single screen in stack or animation `none` is used.
237
191
  // Atm needsDrawReordering implementation guards that assuming that first screen on stack uses `NONE` animation.
238
- childDrawingOrderStrategy = SwapLastTwo()
192
+ childrenDrawingOrderStrategy = ReverseOrder()
239
193
  } else if (newTop != null &&
240
194
  newTopAlreadyInStack &&
241
- topScreenWrapper?.screen?.isTransparent() == true &&
242
- newTop.screen.isTransparent() == false
195
+ topScreenWrapper?.isTranslucent() == true &&
196
+ newTop.isTranslucent() == false
243
197
  ) {
244
198
  // In case where we dismiss multiple transparent views we want to ensure
245
199
  // that they are drawn in correct order - Android swaps them by default,
@@ -250,11 +204,11 @@ class ScreenStack(
250
204
  .asSequence()
251
205
  .takeWhile {
252
206
  it !== newTop &&
253
- it.screen.isTransparent()
207
+ it.isTranslucent()
254
208
  }.count()
255
209
  if (dismissedTransparentScreenApproxCount > 1) {
256
- childDrawingOrderStrategy =
257
- ReverseOrderInRange(max(stack.lastIndex - dismissedTransparentScreenApproxCount + 1, 0)..stack.lastIndex)
210
+ childrenDrawingOrderStrategy =
211
+ ReverseFromIndex(max(stack.lastIndex - dismissedTransparentScreenApproxCount + 1, 0))
258
212
  }
259
213
  }
260
214
 
@@ -267,8 +221,12 @@ class ScreenStack(
267
221
  // no longer rendered or were dismissed natively.
268
222
  stack
269
223
  .asSequence()
270
- .filter { wrapper -> !screenWrappers.contains(wrapper) || dismissedWrappers.contains(wrapper) }
271
- .forEach { wrapper -> transaction.remove(wrapper.fragment) }
224
+ .filter { wrapper ->
225
+ !screenWrappers.contains(wrapper) ||
226
+ dismissedWrappers.contains(
227
+ wrapper,
228
+ )
229
+ }.forEach { wrapper -> transaction.remove(wrapper.fragment) }
272
230
 
273
231
  // Remove all screens underneath visibleBottom && these marked for preload, but keep newTop.
274
232
  screenWrappers
@@ -290,14 +248,12 @@ class ScreenStack(
290
248
  }
291
249
  }
292
250
  } else if (newTop != null && !newTop.fragment.isAdded) {
293
- if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && newTop.screen.isSheetFitToContents()) {
294
- // On old architecture the content wrapper might not have received its frame yet,
295
- // which is required to determine height of the sheet after animation. Therefore
296
- // we delay the transition and trigger it after views receive the layout.
251
+ if (newTop.screen.requiresEnterTransitionPostponing()) {
297
252
  newTop.fragment.postponeEnterTransition()
298
253
  }
299
254
  transaction.add(id, newTop.fragment)
300
255
  }
256
+
301
257
  topScreenWrapper = newTop as? ScreenStackFragmentWrapper
302
258
  stack.clear()
303
259
  stack.addAll(screenWrappers.asSequence().map { it as ScreenStackFragmentWrapper })
@@ -311,8 +267,9 @@ class ScreenStack(
311
267
  private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenFragmentWrapper?) {
312
268
  if (screenWrappers.size > 1 && visibleBottom != null) {
313
269
  topScreenWrapper?.let {
314
- if (it.screen.isTransparent()) {
315
- val screenFragmentsBeneathTop = screenWrappers.slice(0 until screenWrappers.size - 1).asReversed()
270
+ if (it.isTranslucent()) {
271
+ val screenFragmentsBeneathTop =
272
+ screenWrappers.slice(0 until screenWrappers.size - 1).asReversed()
316
273
  // go from the top of the stack excluding the top screen
317
274
  for (fragmentWrapper in screenFragmentsBeneathTop) {
318
275
  fragmentWrapper.screen.changeAccessibilityMode(
@@ -351,13 +308,7 @@ class ScreenStack(
351
308
  override fun dispatchDraw(canvas: Canvas) {
352
309
  super.dispatchDraw(canvas)
353
310
 
354
- // check the view removal is completed (by comparing the previous children count)
355
- if (drawingOps.size < previousChildrenCount) {
356
- childDrawingOrderStrategy = null
357
- }
358
- previousChildrenCount = drawingOps.size
359
-
360
- childDrawingOrderStrategy?.apply(drawingOps)
311
+ childrenDrawingOrderStrategy?.apply(drawingOps)
361
312
 
362
313
  drawAndRelease()
363
314
  }
@@ -407,7 +358,7 @@ class ScreenStack(
407
358
  fragmentWrapper: ScreenFragmentWrapper,
408
359
  resolvedStackAnimation: StackAnimation?,
409
360
  ): Boolean {
410
- val stackAnimation = if (resolvedStackAnimation != null) resolvedStackAnimation else fragmentWrapper.screen.stackAnimation
361
+ val stackAnimation = resolvedStackAnimation ?: fragmentWrapper.screen.stackAnimation
411
362
  // On Android sdk 33 and above the animation is different and requires draw reordering.
412
363
  // For React Native 0.70 and lower versions, `Build.VERSION_CODES.TIRAMISU` is not defined yet.
413
364
  // Hence, we're comparing numerical version here.
@@ -4,7 +4,6 @@ import android.animation.Animator
4
4
  import android.animation.AnimatorSet
5
5
  import android.animation.ValueAnimator
6
6
  import android.annotation.SuppressLint
7
- import android.content.Context
8
7
  import android.graphics.Color
9
8
  import android.graphics.drawable.ColorDrawable
10
9
  import android.os.Bundle
@@ -14,15 +13,11 @@ import android.view.MenuInflater
14
13
  import android.view.MenuItem
15
14
  import android.view.View
16
15
  import android.view.ViewGroup
17
- import android.view.WindowInsets
18
16
  import android.view.animation.Animation
19
- import android.view.animation.AnimationSet
20
- import android.view.animation.Transformation
21
17
  import android.widget.LinearLayout
22
18
  import androidx.appcompat.widget.Toolbar
23
19
  import androidx.coordinatorlayout.widget.CoordinatorLayout
24
20
  import com.facebook.react.uimanager.PixelUtil
25
- import com.facebook.react.uimanager.ReactPointerEventsView
26
21
  import com.facebook.react.uimanager.UIManagerHelper
27
22
  import com.google.android.material.appbar.AppBarLayout
28
23
  import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
@@ -37,6 +32,7 @@ import com.swmansion.rnscreens.events.ScreenAnimationDelegate
37
32
  import com.swmansion.rnscreens.events.ScreenDismissedEvent
38
33
  import com.swmansion.rnscreens.events.ScreenEventEmitter
39
34
  import com.swmansion.rnscreens.ext.recycle
35
+ import com.swmansion.rnscreens.stack.views.ScreensCoordinatorLayout
40
36
  import com.swmansion.rnscreens.transition.ExternalBoundaryValuesEvaluator
41
37
  import com.swmansion.rnscreens.utils.DeviceUtils
42
38
  import com.swmansion.rnscreens.utils.resolveBackgroundColor
@@ -86,6 +82,8 @@ class ScreenStackFragment :
86
82
  )
87
83
  }
88
84
 
85
+ override fun isTranslucent(): Boolean = screen.isTranslucent()
86
+
89
87
  override fun removeToolbar() {
90
88
  appBarLayout?.let {
91
89
  toolbar?.let { toolbar ->
@@ -231,6 +229,8 @@ class ScreenStackFragment :
231
229
  dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout)
232
230
  dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!)
233
231
 
232
+ // Pre-layout the content for the sake of enter transition.
233
+
234
234
  val container = screen.container!!
235
235
  coordinatorLayout.measure(
236
236
  View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
@@ -339,7 +339,7 @@ class ScreenStackFragment :
339
339
  return screenColor
340
340
  }
341
341
 
342
- val contentWrapper = screen.contentWrapper.get()
342
+ val contentWrapper = screen.contentWrapper
343
343
  if (contentWrapper == null) {
344
344
  return null
345
345
  }
@@ -380,7 +380,7 @@ class ScreenStackFragment :
380
380
  // If the screen is a transparent modal with hidden header we don't want to update the toolbar
381
381
  // menu because it may erase the menu of the previous screen (which is still visible in these
382
382
  // circumstances). See here: https://github.com/software-mansion/react-native-screens/issues/2271
383
- if (!screen.isTransparent() || screen.headerConfig?.isHeaderHidden == false) {
383
+ if (!screen.isTranslucent() || screen.headerConfig?.isHeaderHidden == false) {
384
384
  updateToolbarMenu(menu)
385
385
  }
386
386
  return super.onPrepareOptionsMenu(menu)
@@ -469,122 +469,4 @@ class ScreenStackFragment :
469
469
  }
470
470
  return sheetDelegate!!
471
471
  }
472
-
473
- private class ScreensCoordinatorLayout(
474
- context: Context,
475
- private val fragment: ScreenStackFragment,
476
- private val pointerEventsImpl: ReactPointerEventsView,
477
- // ) : CoordinatorLayout(context), ReactCompoundViewGroup, ReactHitSlopView {
478
- ) : CoordinatorLayout(context),
479
- ReactPointerEventsView by pointerEventsImpl {
480
- constructor(context: Context, fragment: ScreenStackFragment) : this(
481
- context,
482
- fragment,
483
- PointerEventsBoxNoneImpl(),
484
- )
485
-
486
- override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets)
487
-
488
- private val animationListener: Animation.AnimationListener =
489
- object : Animation.AnimationListener {
490
- override fun onAnimationStart(animation: Animation) {
491
- fragment.onViewAnimationStart()
492
- }
493
-
494
- override fun onAnimationEnd(animation: Animation) {
495
- fragment.onViewAnimationEnd()
496
- }
497
-
498
- override fun onAnimationRepeat(animation: Animation) {}
499
- }
500
-
501
- override fun startAnimation(animation: Animation) {
502
- // For some reason View##onAnimationEnd doesn't get called for
503
- // exit transitions so we explicitly attach animation listener.
504
- // We also have some animations that are an AnimationSet, so we don't wrap them
505
- // in another set since it causes some visual glitches when going forward.
506
- // We also set the listener only when going forward, since when going back,
507
- // there is already a listener for dismiss action added, which would be overridden
508
- // and also this is not necessary when going back since the lifecycle methods
509
- // are correctly dispatched then.
510
- // We also add fakeAnimation to the set of animations, which sends the progress of animation
511
- val fakeAnimation = ScreensAnimation(fragment).apply { duration = animation.duration }
512
-
513
- if (animation is AnimationSet && !fragment.isRemoving) {
514
- animation
515
- .apply {
516
- addAnimation(fakeAnimation)
517
- setAnimationListener(animationListener)
518
- }.also {
519
- super.startAnimation(it)
520
- }
521
- } else {
522
- AnimationSet(true)
523
- .apply {
524
- addAnimation(animation)
525
- addAnimation(fakeAnimation)
526
- setAnimationListener(animationListener)
527
- }.also {
528
- super.startAnimation(it)
529
- }
530
- }
531
- }
532
-
533
- /**
534
- * This method implements a workaround for RN's autoFocus functionality. Because of the way
535
- * autoFocus is implemented it dismisses soft keyboard in fragment transition
536
- * due to change of visibility of the view at the start of the transition. Here we override the
537
- * call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the
538
- * hiding of the keyboard in `ReactEditText.java`.
539
- */
540
- override fun clearFocus() {
541
- if (visibility != INVISIBLE) {
542
- super.clearFocus()
543
- }
544
- }
545
-
546
- override fun onLayout(
547
- changed: Boolean,
548
- l: Int,
549
- t: Int,
550
- r: Int,
551
- b: Int,
552
- ) {
553
- super.onLayout(changed, l, t, r, b)
554
-
555
- if (fragment.screen.usesFormSheetPresentation()) {
556
- fragment.screen.onBottomSheetBehaviorDidLayout(changed)
557
- }
558
- }
559
-
560
- // override fun reactTagForTouch(touchX: Float, touchY: Float): Int {
561
- // throw IllegalStateException("Screen wrapper should never be asked for the view tag")
562
- // }
563
- //
564
- // override fun interceptsTouchEvent(touchX: Float, touchY: Float): Boolean {
565
- // return false
566
- // }
567
- //
568
- // override fun getHitSlopRect(): Rect? {
569
- // val screen: Screen = fragment.screen
570
- // // left – The X coordinate of the left side of the rectangle
571
- // // top – The Y coordinate of the top of the rectangle i
572
- // // right – The X coordinate of the right side of the rectangle
573
- // // bottom – The Y coordinate of the bottom of the rectangle
574
- // return Rect(screen.x.toInt(), -screen.y.toInt(), screen.x.toInt() + screen.width, screen.y.toInt() + screen.height)
575
- // }
576
- }
577
-
578
- private class ScreensAnimation(
579
- private val mFragment: ScreenFragment,
580
- ) : Animation() {
581
- override fun applyTransformation(
582
- interpolatedTime: Float,
583
- t: Transformation,
584
- ) {
585
- super.applyTransformation(interpolatedTime, t)
586
- // interpolated time should be the progress of the current transition
587
- mFragment.dispatchTransitionProgressEvent(interpolatedTime, !mFragment.isResumed)
588
- }
589
- }
590
472
  }
@@ -126,7 +126,7 @@ open class ScreenViewManager :
126
126
  when (presentation) {
127
127
  "push" -> Screen.StackPresentation.PUSH
128
128
  "formSheet" -> Screen.StackPresentation.FORM_SHEET
129
- "modal", "containedModal", "fullScreenModal" ->
129
+ "modal", "containedModal", "fullScreenModal", "pageSheet" ->
130
130
  Screen.StackPresentation.MODAL
131
131
  "transparentModal", "containedTransparentModal" ->
132
132
  Screen.StackPresentation.TRANSPARENT_MODAL
@@ -130,13 +130,13 @@ class SheetDelegate(
130
130
  behavior.apply {
131
131
  val height =
132
132
  if (screen.isSheetFitToContents()) {
133
- screen.contentWrapper.get()?.let { contentWrapper ->
133
+ screen.contentWrapper?.let { contentWrapper ->
134
134
  contentWrapper.height.takeIf {
135
135
  // subtree might not be laid out, e.g. after fragment reattachment
136
136
  // and view recreation, however since it is retained by
137
137
  // react-native it has its height cached. We want to use it.
138
138
  // Otherwise we would have to trigger RN layout manually.
139
- contentWrapper.isLaidOut || contentWrapper.height > 0
139
+ contentWrapper.isLaidOutOrHasCachedLayout()
140
140
  }
141
141
  }
142
142
  } else {
@@ -1,10 +1,12 @@
1
1
  package com.swmansion.rnscreens.bottomsheet
2
2
 
3
+ import android.view.View
3
4
  import com.google.android.material.bottomsheet.BottomSheetBehavior
4
5
  import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
5
6
  import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
6
7
  import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
7
8
  import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
9
+ import com.swmansion.rnscreens.BuildConfig
8
10
  import com.swmansion.rnscreens.Screen
9
11
 
10
12
  object SheetUtils {
@@ -133,3 +135,24 @@ fun Screen.isSheetFitToContents(): Boolean =
133
135
  sheetDetents.first() == Screen.SHEET_FIT_TO_CONTENTS
134
136
 
135
137
  fun Screen.usesFormSheetPresentation(): Boolean = stackPresentation === Screen.StackPresentation.FORM_SHEET
138
+
139
+ fun Screen.requiresEnterTransitionPostponing(): Boolean {
140
+ // On old architecture the content wrapper might not have received its frame yet,
141
+ // which is required to determine height of the sheet after animation. Therefore
142
+ // we delay the transition and trigger it after views receive the layout.
143
+ // This is used only for formSheet presentation, because we use value animators
144
+ // there. Tween animations have some magic way to make this work (maybe they
145
+ // postpone the transition internally, dunno).
146
+
147
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED || !this.usesFormSheetPresentation()) {
148
+ return false
149
+ }
150
+ // Assumes that formSheet uses content wrapper
151
+ return !this.isLaidOutOrHasCachedLayout() || this.contentWrapper?.isLaidOutOrHasCachedLayout() != true
152
+ }
153
+
154
+ /**
155
+ * The view might not be laid out, but have cached dimensions e.g. when host fragment
156
+ * is reattached to container.
157
+ */
158
+ fun View.isLaidOutOrHasCachedLayout() = this.isLaidOut || height > 0 || width > 0