react-native-screens 3.35.0-rc.0 → 4.0.0-beta.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 (115) hide show
  1. package/android/build.gradle +2 -2
  2. package/android/src/main/java/com/swmansion/rnscreens/InsetsObserverProxy.kt +67 -0
  3. package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +101 -4
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapper.kt +38 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapperManager.kt +25 -0
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenFooter.kt +287 -0
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenFooterManager.kt +25 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +11 -19
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +4 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenModalFragment.kt +281 -0
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +62 -19
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +403 -41
  14. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt +4 -1
  15. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +2 -2
  16. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +95 -11
  17. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +39 -28
  18. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetDialogRootView.kt +104 -0
  19. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetDialogScreen.kt +26 -0
  20. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingFragment.kt +488 -0
  21. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingView.kt +66 -0
  22. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/GestureTransparentViewGroup.kt +24 -0
  23. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +127 -0
  24. package/android/src/main/java/com/swmansion/rnscreens/events/SheetDetentChangedEvent.kt +27 -0
  25. package/android/src/main/java/com/swmansion/rnscreens/ext/NumericExt.kt +12 -0
  26. package/android/src/main/java/com/swmansion/rnscreens/ext/ViewExt.kt +32 -0
  27. package/android/src/main/java/com/swmansion/rnscreens/utils/ScreenDummyLayoutHelper.kt +45 -8
  28. package/android/src/main/res/base/drawable/rns_rounder_top_corners_shape.xml +8 -0
  29. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenContentWrapperManagerDelegate.java +25 -0
  30. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenContentWrapperManagerInterface.java +16 -0
  31. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenFooterManagerDelegate.java +25 -0
  32. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenFooterManagerInterface.java +16 -0
  33. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +9 -2
  34. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +5 -2
  35. package/ios/RNSConvert.h +5 -3
  36. package/ios/RNSConvert.mm +14 -20
  37. package/ios/RNSScreen.h +3 -2
  38. package/ios/RNSScreen.mm +433 -49
  39. package/ios/RNSScreenContentWrapper.h +44 -0
  40. package/ios/RNSScreenContentWrapper.mm +61 -0
  41. package/ios/RNSScreenFooter.h +30 -0
  42. package/ios/RNSScreenFooter.mm +137 -0
  43. package/lib/commonjs/components/Screen.js +6 -2
  44. package/lib/commonjs/components/Screen.js.map +1 -1
  45. package/lib/commonjs/components/ScreenContentWrapper.js +19 -0
  46. package/lib/commonjs/components/ScreenContentWrapper.js.map +1 -0
  47. package/lib/commonjs/components/ScreenFooter.js +23 -0
  48. package/lib/commonjs/components/ScreenFooter.js.map +1 -0
  49. package/lib/commonjs/fabric/ModalScreenNativeComponent.js.map +1 -1
  50. package/lib/commonjs/fabric/ScreenContentWrapperNativeComponent.js +10 -0
  51. package/lib/commonjs/fabric/ScreenContentWrapperNativeComponent.js.map +1 -0
  52. package/lib/commonjs/fabric/ScreenFooterNativeComponent.js +10 -0
  53. package/lib/commonjs/fabric/ScreenFooterNativeComponent.js.map +1 -0
  54. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  55. package/lib/commonjs/index.js +30 -0
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/commonjs/native-stack/views/FooterComponent.js +18 -0
  58. package/lib/commonjs/native-stack/views/FooterComponent.js.map +1 -0
  59. package/lib/commonjs/native-stack/views/NativeStackView.js +59 -14
  60. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  61. package/lib/module/components/Screen.js +6 -2
  62. package/lib/module/components/Screen.js.map +1 -1
  63. package/lib/module/components/ScreenContentWrapper.js +12 -0
  64. package/lib/module/components/ScreenContentWrapper.js.map +1 -0
  65. package/lib/module/components/ScreenFooter.js +17 -0
  66. package/lib/module/components/ScreenFooter.js.map +1 -0
  67. package/lib/module/fabric/ModalScreenNativeComponent.js.map +1 -1
  68. package/lib/module/fabric/ScreenContentWrapperNativeComponent.js +3 -0
  69. package/lib/module/fabric/ScreenContentWrapperNativeComponent.js.map +1 -0
  70. package/lib/module/fabric/ScreenFooterNativeComponent.js +3 -0
  71. package/lib/module/fabric/ScreenFooterNativeComponent.js.map +1 -0
  72. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  73. package/lib/module/index.js +2 -0
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/native-stack/views/FooterComponent.js +11 -0
  76. package/lib/module/native-stack/views/FooterComponent.js.map +1 -0
  77. package/lib/module/native-stack/views/NativeStackView.js +61 -15
  78. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  79. package/lib/typescript/components/Screen.d.ts.map +1 -1
  80. package/lib/typescript/components/ScreenContentWrapper.d.ts +6 -0
  81. package/lib/typescript/components/ScreenContentWrapper.d.ts.map +1 -0
  82. package/lib/typescript/components/ScreenFooter.d.ts +12 -0
  83. package/lib/typescript/components/ScreenFooter.d.ts.map +1 -0
  84. package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts +2 -3
  85. package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts.map +1 -1
  86. package/lib/typescript/fabric/ScreenContentWrapperNativeComponent.d.ts +7 -0
  87. package/lib/typescript/fabric/ScreenContentWrapperNativeComponent.d.ts.map +1 -0
  88. package/lib/typescript/fabric/ScreenFooterNativeComponent.d.ts +7 -0
  89. package/lib/typescript/fabric/ScreenFooterNativeComponent.d.ts.map +1 -0
  90. package/lib/typescript/fabric/ScreenNativeComponent.d.ts +9 -3
  91. package/lib/typescript/fabric/ScreenNativeComponent.d.ts.map +1 -1
  92. package/lib/typescript/index.d.ts +2 -0
  93. package/lib/typescript/index.d.ts.map +1 -1
  94. package/lib/typescript/native-stack/types.d.ts +63 -23
  95. package/lib/typescript/native-stack/types.d.ts.map +1 -1
  96. package/lib/typescript/native-stack/views/FooterComponent.d.ts +7 -0
  97. package/lib/typescript/native-stack/views/FooterComponent.d.ts.map +1 -0
  98. package/lib/typescript/native-stack/views/NativeStackView.d.ts.map +1 -1
  99. package/lib/typescript/types.d.ts +42 -17
  100. package/lib/typescript/types.d.ts.map +1 -1
  101. package/native-stack/README.md +16 -14
  102. package/package.json +1 -1
  103. package/react-native.config.js +18 -16
  104. package/src/components/Screen.tsx +6 -2
  105. package/src/components/ScreenContentWrapper.tsx +12 -0
  106. package/src/components/ScreenFooter.tsx +18 -0
  107. package/src/fabric/ModalScreenNativeComponent.ts +2 -4
  108. package/src/fabric/ScreenContentWrapperNativeComponent.ts +9 -0
  109. package/src/fabric/ScreenFooterNativeComponent.ts +6 -0
  110. package/src/fabric/ScreenNativeComponent.ts +10 -4
  111. package/src/index.tsx +10 -0
  112. package/src/native-stack/types.tsx +57 -23
  113. package/src/native-stack/views/FooterComponent.tsx +10 -0
  114. package/src/native-stack/views/NativeStackView.tsx +74 -11
  115. package/src/types.tsx +41 -16
@@ -3,6 +3,8 @@ package com.swmansion.rnscreens
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
5
  import android.graphics.Color
6
+ import android.graphics.drawable.ColorDrawable
7
+ import android.os.Build
6
8
  import android.os.Bundle
7
9
  import android.view.LayoutInflater
8
10
  import android.view.Menu
@@ -10,17 +12,44 @@ import android.view.MenuInflater
10
12
  import android.view.MenuItem
11
13
  import android.view.View
12
14
  import android.view.ViewGroup
15
+ import android.view.WindowInsets
16
+ import android.view.WindowManager
13
17
  import android.view.animation.Animation
14
18
  import android.view.animation.AnimationSet
19
+ import android.view.animation.AnimationUtils
15
20
  import android.view.animation.Transformation
21
+ import android.view.inputmethod.InputMethodManager
16
22
  import android.widget.LinearLayout
23
+ import androidx.annotation.RequiresApi
17
24
  import androidx.appcompat.widget.Toolbar
18
25
  import androidx.coordinatorlayout.widget.CoordinatorLayout
26
+ import androidx.core.view.WindowInsetsCompat
27
+ import androidx.fragment.app.commit
19
28
  import com.facebook.react.uimanager.PixelUtil
29
+ import com.facebook.react.uimanager.PointerEvents
30
+ import com.facebook.react.uimanager.ReactPointerEventsView
20
31
  import com.google.android.material.appbar.AppBarLayout
21
32
  import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
33
+ import com.google.android.material.bottomsheet.BottomSheetBehavior
34
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
35
+ import com.google.android.material.shape.CornerFamily
36
+ import com.google.android.material.shape.MaterialShapeDrawable
37
+ import com.google.android.material.shape.ShapeAppearanceModel
38
+ import com.swmansion.rnscreens.bottomsheet.DimmingFragment
39
+ import com.swmansion.rnscreens.bottomsheet.SheetUtils
40
+ import com.swmansion.rnscreens.ext.recycle
22
41
  import com.swmansion.rnscreens.utils.DeviceUtils
23
42
 
43
+ sealed class KeyboardState
44
+
45
+ object KeyboardNotVisible : KeyboardState()
46
+
47
+ object KeyboardDidHide : KeyboardState()
48
+
49
+ class KeyboardVisible(
50
+ val height: Int,
51
+ ) : KeyboardState()
52
+
24
53
  class ScreenStackFragment :
25
54
  ScreenFragment,
26
55
  ScreenStackFragmentWrapper {
@@ -34,6 +63,15 @@ class ScreenStackFragment :
34
63
  var searchView: CustomSearchView? = null
35
64
  var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null
36
65
 
66
+ private lateinit var coordinatorLayout: ScreensCoordinatorLayout
67
+
68
+ private val screenStack: ScreenStack
69
+ get() {
70
+ val container = screen.container
71
+ check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
72
+ return container
73
+ }
74
+
37
75
  @SuppressLint("ValidFragment")
38
76
  constructor(screenView: Screen) : super(screenView)
39
77
 
@@ -99,50 +137,350 @@ class ScreenStackFragment :
99
137
  }
100
138
  }
101
139
 
102
- override fun onStart() {
103
- lastFocusedChild?.requestFocus()
104
- super.onStart()
140
+ // If the Screen has `formSheet` presentation this callback is attached to its behavior.
141
+ // It is responsible for firing detent changed events & removing the sheet from the container
142
+ // once it is hidden by user gesture.
143
+ private val bottomSheetStateCallback =
144
+ object : BottomSheetCallback() {
145
+ private var lastStableState: Int = SheetUtils.sheetStateFromDetentIndex(screen.sheetInitialDetentIndex, screen.sheetDetents.count())
146
+
147
+ override fun onStateChanged(
148
+ bottomSheet: View,
149
+ newState: Int,
150
+ ) {
151
+ if (SheetUtils.isStateStable(newState)) {
152
+ lastStableState = newState
153
+ screen.notifySheetDetentChange(SheetUtils.detentIndexFromSheetState(lastStableState, screen.sheetDetents.count()), true)
154
+ } else if (newState == BottomSheetBehavior.STATE_DRAGGING) {
155
+ screen.notifySheetDetentChange(SheetUtils.detentIndexFromSheetState(lastStableState, screen.sheetDetents.count()), false)
156
+ }
157
+
158
+ if (newState == BottomSheetBehavior.STATE_HIDDEN) {
159
+ // If we are wrapped in DimmingFragment we want it to be removed alongside
160
+ // => we use its fragment manager. Otherwise we just remove this fragment.
161
+ if (this@ScreenStackFragment.parentFragment is DimmingFragment) {
162
+ parentFragmentManager.commit {
163
+ setReorderingAllowed(true)
164
+ remove(this@ScreenStackFragment)
165
+ }
166
+ } else {
167
+ this@ScreenStackFragment.dismissFromContainer()
168
+ }
169
+ }
170
+ }
171
+
172
+ override fun onSlide(
173
+ bottomSheet: View,
174
+ slideOffset: Float,
175
+ ) = Unit
176
+ }
177
+
178
+ override fun onCreateAnimation(
179
+ transit: Int,
180
+ enter: Boolean,
181
+ nextAnim: Int,
182
+ ): Animation? {
183
+ if (screen.stackPresentation != Screen.StackPresentation.FORM_SHEET) {
184
+ return null
185
+ }
186
+ return if (enter) {
187
+ AnimationUtils.loadAnimation(context, R.anim.rns_slide_in_from_bottom)
188
+ } else {
189
+ AnimationUtils.loadAnimation(context, R.anim.rns_slide_out_to_bottom)
190
+ }
191
+ }
192
+
193
+ internal fun onSheetCornerRadiusChange() {
194
+ (screen.background as MaterialShapeDrawable).shapeAppearanceModel =
195
+ ShapeAppearanceModel
196
+ .Builder()
197
+ .apply {
198
+ setTopLeftCorner(CornerFamily.ROUNDED, screen.sheetCornerRadius)
199
+ setTopRightCorner(CornerFamily.ROUNDED, screen.sheetCornerRadius)
200
+ }.build()
105
201
  }
106
202
 
107
203
  override fun onCreateView(
108
204
  inflater: LayoutInflater,
109
205
  container: ViewGroup?,
110
206
  savedInstanceState: Bundle?,
111
- ): View? {
112
- val view: ScreensCoordinatorLayout? =
113
- context?.let { ScreensCoordinatorLayout(it, this) }
207
+ ): View {
208
+ coordinatorLayout = ScreensCoordinatorLayout(requireContext(), this)
114
209
 
115
210
  screen.layoutParams =
116
211
  CoordinatorLayout
117
212
  .LayoutParams(
118
213
  LinearLayout.LayoutParams.MATCH_PARENT,
119
214
  LinearLayout.LayoutParams.MATCH_PARENT,
120
- ).apply { behavior = if (isToolbarTranslucent) null else ScrollingViewBehavior() }
121
-
122
- view?.addView(recycleView(screen))
123
-
124
- appBarLayout =
125
- context?.let { AppBarLayout(it) }?.apply {
126
- // By default AppBarLayout will have a background color set but since we cover the whole layout
127
- // with toolbar (that can be semi-transparent) the bar layout background color does not pay a
128
- // role. On top of that it breaks screens animations when alfa offscreen compositing is off
129
- // (which is the default)
130
- setBackgroundColor(Color.TRANSPARENT)
131
- layoutParams =
132
- AppBarLayout.LayoutParams(
133
- AppBarLayout.LayoutParams.MATCH_PARENT,
134
- AppBarLayout.LayoutParams.WRAP_CONTENT,
215
+ ).apply {
216
+ behavior =
217
+ if (screen.stackPresentation == Screen.StackPresentation.FORM_SHEET) {
218
+ createAndConfigureBottomSheetBehaviour()
219
+ } else if (isToolbarTranslucent) {
220
+ null
221
+ } else {
222
+ ScrollingViewBehavior()
223
+ }
224
+ }
225
+
226
+ if (screen.stackPresentation == Screen.StackPresentation.FORM_SHEET) {
227
+ screen.clipToOutline = true
228
+ // TODO(@kkafar): without this line there is no drawable / outline & nothing shows...? Determine what's going on here
229
+ attachShapeToScreen(screen)
230
+ screen.elevation = screen.sheetElevation
231
+ }
232
+
233
+ coordinatorLayout.addView(screen.recycle())
234
+
235
+ if (screen.stackPresentation != Screen.StackPresentation.MODAL &&
236
+ screen.stackPresentation != Screen.StackPresentation.FORM_SHEET
237
+ ) {
238
+ appBarLayout =
239
+ context?.let { AppBarLayout(it) }?.apply {
240
+ // By default AppBarLayout will have a background color set but since we cover the whole layout
241
+ // with toolbar (that can be semi-transparent) the bar layout background color does not pay a
242
+ // role. On top of that it breaks screens animations when alfa offscreen compositing is off
243
+ // (which is the default)
244
+ setBackgroundColor(Color.TRANSPARENT)
245
+ layoutParams =
246
+ AppBarLayout.LayoutParams(
247
+ AppBarLayout.LayoutParams.MATCH_PARENT,
248
+ AppBarLayout.LayoutParams.WRAP_CONTENT,
249
+ )
250
+ }
251
+
252
+ coordinatorLayout.addView(appBarLayout)
253
+ if (isToolbarShadowHidden) {
254
+ appBarLayout?.targetElevation = 0f
255
+ }
256
+ toolbar?.let { appBarLayout?.addView(it.recycle()) }
257
+ setHasOptionsMenu(true)
258
+ }
259
+ return coordinatorLayout
260
+ }
261
+
262
+ /**
263
+ * This method might return slightly different values depending on code path,
264
+ * but during testing I've found this effect negligible. For practical purposes
265
+ * this is acceptable.
266
+ */
267
+ private fun tryResolveContainerHeight(): Int? {
268
+ if (screen.container != null) {
269
+ return screenStack.height
270
+ }
271
+
272
+ context
273
+ ?.resources
274
+ ?.displayMetrics
275
+ ?.heightPixels
276
+ ?.let { return it }
277
+
278
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
279
+ (context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)
280
+ ?.currentWindowMetrics
281
+ ?.bounds
282
+ ?.height()
283
+ ?.let { return it }
284
+ }
285
+ return null
286
+ }
287
+
288
+ private val keyboardSheetCallback =
289
+ object : BottomSheetCallback() {
290
+ @RequiresApi(Build.VERSION_CODES.M)
291
+ override fun onStateChanged(
292
+ bottomSheet: View,
293
+ newState: Int,
294
+ ) {
295
+ if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
296
+ val isImeVisible =
297
+ WindowInsetsCompat
298
+ .toWindowInsetsCompat(bottomSheet.rootWindowInsets)
299
+ .isVisible(WindowInsetsCompat.Type.ime())
300
+ if (isImeVisible) {
301
+ // Does it not interfere with React Native focus mechanism? In any case I'm not aware
302
+ // of different way of hiding the keyboard.
303
+ // https://stackoverflow.com/questions/1109022/how-can-i-close-hide-the-android-soft-keyboard-programmatically
304
+ // https://developer.android.com/develop/ui/views/touch-and-input/keyboard-input/visibility
305
+
306
+ // I want to be polite here and request focus before dismissing the keyboard,
307
+ // however even if it fails I want to try to hide the keyboard. This sometimes works...
308
+ bottomSheet.requestFocus()
309
+ val imm = requireContext().getSystemService(InputMethodManager::class.java)
310
+ imm.hideSoftInputFromWindow(bottomSheet.windowToken, 0)
311
+ }
312
+ }
313
+ }
314
+
315
+ override fun onSlide(
316
+ bottomSheet: View,
317
+ slideOffset: Float,
318
+ ) = Unit
319
+ }
320
+
321
+ internal fun configureBottomSheetBehaviour(
322
+ behavior: BottomSheetBehavior<Screen>,
323
+ keyboardState: KeyboardState = KeyboardNotVisible,
324
+ ): BottomSheetBehavior<Screen> {
325
+ val containerHeight = tryResolveContainerHeight()
326
+ check(containerHeight != null) {
327
+ "[RNScreens] Failed to find window height during bottom sheet behaviour configuration"
328
+ }
329
+
330
+ behavior.apply {
331
+ isHideable = true
332
+ isDraggable = true
333
+
334
+ // It seems that there is a guard in material implementation that will prevent
335
+ // this callback from being registered multiple times.
336
+ addBottomSheetCallback(bottomSheetStateCallback)
337
+ }
338
+
339
+ screen.footer?.registerWithSheetBehavior(behavior)
340
+
341
+ return when (keyboardState) {
342
+ is KeyboardNotVisible -> {
343
+ when (screen.sheetDetents.count()) {
344
+ 1 ->
345
+ behavior.apply {
346
+ state = BottomSheetBehavior.STATE_EXPANDED
347
+ skipCollapsed = true
348
+ isFitToContents = true
349
+ maxHeight = (screen.sheetDetents.first() * containerHeight).toInt()
350
+ }
351
+
352
+ 2 ->
353
+ behavior.apply {
354
+ state =
355
+ SheetUtils.sheetStateFromDetentIndex(
356
+ screen.sheetInitialDetentIndex,
357
+ screen.sheetDetents.count(),
358
+ )
359
+ skipCollapsed = false
360
+ isFitToContents = true
361
+ peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
362
+ maxHeight = (screen.sheetDetents[1] * containerHeight).toInt()
363
+ }
364
+
365
+ 3 ->
366
+ behavior.apply {
367
+ state =
368
+ SheetUtils.sheetStateFromDetentIndex(
369
+ screen.sheetInitialDetentIndex,
370
+ screen.sheetDetents.count(),
371
+ )
372
+ skipCollapsed = false
373
+ isFitToContents = false
374
+ peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
375
+ expandedOffset = ((1 - screen.sheetDetents[2]) * containerHeight).toInt()
376
+ halfExpandedRatio =
377
+ (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat()
378
+ }
379
+
380
+ else -> throw IllegalStateException(
381
+ "[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
135
382
  )
383
+ }
136
384
  }
137
385
 
138
- view?.addView(appBarLayout)
139
- if (isToolbarShadowHidden) {
140
- appBarLayout?.elevation = 0f
141
- appBarLayout?.stateListAnimator = null
386
+ is KeyboardVisible -> {
387
+ val newMaxHeight =
388
+ if (behavior.maxHeight - keyboardState.height > 1) {
389
+ behavior.maxHeight - keyboardState.height
390
+ } else {
391
+ behavior.maxHeight
392
+ }
393
+ when (screen.sheetDetents.count()) {
394
+ 1 ->
395
+ behavior.apply {
396
+ state = BottomSheetBehavior.STATE_EXPANDED
397
+ skipCollapsed = true
398
+ isFitToContents = true
399
+ maxHeight = newMaxHeight
400
+ addBottomSheetCallback(keyboardSheetCallback)
401
+ }
402
+
403
+ 2 ->
404
+ behavior.apply {
405
+ state = BottomSheetBehavior.STATE_EXPANDED
406
+ skipCollapsed = false
407
+ isFitToContents = true
408
+ maxHeight = newMaxHeight
409
+ addBottomSheetCallback(keyboardSheetCallback)
410
+ }
411
+
412
+ 3 ->
413
+ behavior.apply {
414
+ state = BottomSheetBehavior.STATE_EXPANDED
415
+ skipCollapsed = false
416
+ isFitToContents = false
417
+ maxHeight = newMaxHeight
418
+ addBottomSheetCallback(keyboardSheetCallback)
419
+ }
420
+
421
+ else -> throw IllegalStateException(
422
+ "[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
423
+ )
424
+ }
425
+ }
426
+
427
+ is KeyboardDidHide -> {
428
+ // Here we assume that the keyboard was either closed explicitly by user,
429
+ // or the user dragged the sheet down. In any case the state should
430
+ // stay unchanged.
431
+
432
+ behavior.removeBottomSheetCallback(keyboardSheetCallback)
433
+ when (screen.sheetDetents.count()) {
434
+ 1 ->
435
+ behavior.apply {
436
+ skipCollapsed = true
437
+ isFitToContents = true
438
+ maxHeight = (screen.sheetDetents.first() * containerHeight).toInt()
439
+ }
440
+
441
+ 2 ->
442
+ behavior.apply {
443
+ skipCollapsed = false
444
+ isFitToContents = true
445
+ peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
446
+ maxHeight = (screen.sheetDetents[1] * containerHeight).toInt()
447
+ }
448
+
449
+ 3 ->
450
+ behavior.apply {
451
+ skipCollapsed = false
452
+ isFitToContents = false
453
+ peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
454
+ expandedOffset = ((1 - screen.sheetDetents[2]) * containerHeight).toInt()
455
+ halfExpandedRatio =
456
+ (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat()
457
+ }
458
+
459
+ else -> throw IllegalStateException(
460
+ "[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
461
+ )
462
+ }
463
+ }
142
464
  }
143
- toolbar?.let { appBarLayout?.addView(recycleView(it)) }
144
- setHasOptionsMenu(true)
145
- return view
465
+ }
466
+
467
+ // In general it would be great to create BottomSheetBehaviour only via this method as it runs some
468
+ // side effects.
469
+ internal fun createAndConfigureBottomSheetBehaviour(): BottomSheetBehavior<Screen> =
470
+ configureBottomSheetBehaviour(BottomSheetBehavior<Screen>())
471
+
472
+ private fun attachShapeToScreen(screen: Screen) {
473
+ val cornerSize = PixelUtil.toPixelFromDIP(screen.sheetCornerRadius)
474
+ val shapeAppearanceModel =
475
+ ShapeAppearanceModel
476
+ .Builder()
477
+ .apply {
478
+ setTopLeftCorner(CornerFamily.ROUNDED, cornerSize)
479
+ setTopRightCorner(CornerFamily.ROUNDED, cornerSize)
480
+ }.build()
481
+ val shape = MaterialShapeDrawable(shapeAppearanceModel)
482
+ shape.setTint((screen.background as? ColorDrawable?)?.color ?: Color.TRANSPARENT)
483
+ screen.background = shape
146
484
  }
147
485
 
148
486
  override fun onStop() {
@@ -228,24 +566,26 @@ class ScreenStackFragment :
228
566
  }
229
567
  }
230
568
 
231
- override fun dismiss() {
232
- val container: ScreenContainer? = screen.container
233
- check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
234
- container.dismiss(this)
569
+ override fun dismissFromContainer() {
570
+ screenStack.dismiss(this)
235
571
  }
236
572
 
237
573
  private class ScreensCoordinatorLayout(
238
574
  context: Context,
239
- private val mFragment: ScreenFragment,
240
- ) : CoordinatorLayout(context) {
241
- private val mAnimationListener: Animation.AnimationListener =
575
+ private val fragment: ScreenStackFragment,
576
+ // ) : CoordinatorLayout(context), ReactCompoundViewGroup, ReactHitSlopView {
577
+ ) : CoordinatorLayout(context),
578
+ ReactPointerEventsView {
579
+ override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets)
580
+
581
+ private val animationListener: Animation.AnimationListener =
242
582
  object : Animation.AnimationListener {
243
583
  override fun onAnimationStart(animation: Animation) {
244
- mFragment.onViewAnimationStart()
584
+ fragment.onViewAnimationStart()
245
585
  }
246
586
 
247
587
  override fun onAnimationEnd(animation: Animation) {
248
- mFragment.onViewAnimationEnd()
588
+ fragment.onViewAnimationEnd()
249
589
  }
250
590
 
251
591
  override fun onAnimationRepeat(animation: Animation) {}
@@ -261,13 +601,13 @@ class ScreenStackFragment :
261
601
  // and also this is not necessary when going back since the lifecycle methods
262
602
  // are correctly dispatched then.
263
603
  // We also add fakeAnimation to the set of animations, which sends the progress of animation
264
- val fakeAnimation = ScreensAnimation(mFragment).apply { duration = animation.duration }
604
+ val fakeAnimation = ScreensAnimation(fragment).apply { duration = animation.duration }
265
605
 
266
- if (animation is AnimationSet && !mFragment.isRemoving) {
606
+ if (animation is AnimationSet && !fragment.isRemoving) {
267
607
  animation
268
608
  .apply {
269
609
  addAnimation(fakeAnimation)
270
- setAnimationListener(mAnimationListener)
610
+ setAnimationListener(animationListener)
271
611
  }.also {
272
612
  super.startAnimation(it)
273
613
  }
@@ -276,7 +616,7 @@ class ScreenStackFragment :
276
616
  .apply {
277
617
  addAnimation(animation)
278
618
  addAnimation(fakeAnimation)
279
- setAnimationListener(mAnimationListener)
619
+ setAnimationListener(animationListener)
280
620
  }.also {
281
621
  super.startAnimation(it)
282
622
  }
@@ -295,6 +635,28 @@ class ScreenStackFragment :
295
635
  super.clearFocus()
296
636
  }
297
637
  }
638
+
639
+ // override fun reactTagForTouch(touchX: Float, touchY: Float): Int {
640
+ // throw IllegalStateException("Screen wrapper should never be asked for the view tag")
641
+ // }
642
+ //
643
+ // override fun interceptsTouchEvent(touchX: Float, touchY: Float): Boolean {
644
+ // return false
645
+ // }
646
+ //
647
+ // override fun getHitSlopRect(): Rect? {
648
+ // val screen: Screen = fragment.screen
649
+ // // left – The X coordinate of the left side of the rectangle
650
+ // // top – The Y coordinate of the top of the rectangle i
651
+ // // right – The X coordinate of the right side of the rectangle
652
+ // // bottom – The Y coordinate of the bottom of the rectangle
653
+ // return Rect(screen.x.toInt(), -screen.y.toInt(), screen.x.toInt() + screen.width, screen.y.toInt() + screen.height)
654
+ // }
655
+
656
+ // We set pointer events to BOX_NONE, because we don't want the ScreensCoordinatorLayout
657
+ // to be target of react gestures and effectively prevent interaction with screens
658
+ // underneath the current screen (useful in `modal` & `formSheet` presentation).
659
+ override fun getPointerEvents(): PointerEvents = PointerEvents.BOX_NONE
298
660
  }
299
661
 
300
662
  private class ScreensAnimation(
@@ -15,5 +15,8 @@ interface ScreenStackFragmentWrapper : ScreenFragmentWrapper {
15
15
  // Navigation
16
16
  fun canNavigateBack(): Boolean
17
17
 
18
- fun dismiss()
18
+ /**
19
+ * Removes this fragment from the container it/it's screen belongs to.
20
+ */
21
+ fun dismissFromContainer()
19
22
  }
@@ -55,14 +55,14 @@ class ScreenStackHeaderConfig(
55
55
  val parentFragment = it.parentFragment
56
56
  if (parentFragment is ScreenStackFragment) {
57
57
  if (parentFragment.screen.nativeBackButtonDismissalEnabled) {
58
- parentFragment.dismiss()
58
+ parentFragment.dismissFromContainer()
59
59
  } else {
60
60
  parentFragment.dispatchHeaderBackButtonClickedEvent()
61
61
  }
62
62
  }
63
63
  } else {
64
64
  if (it.screen.nativeBackButtonDismissalEnabled) {
65
- it.dismiss()
65
+ it.dismissFromContainer()
66
66
  } else {
67
67
  it.dispatchHeaderBackButtonClickedEvent()
68
68
  }