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
@@ -0,0 +1,488 @@
1
+ package com.swmansion.rnscreens.bottomsheet
2
+
3
+ import android.animation.ValueAnimator
4
+ import android.app.Activity
5
+ import android.graphics.Color
6
+ import android.os.Bundle
7
+ import android.view.LayoutInflater
8
+ import android.view.View
9
+ import android.view.ViewGroup
10
+ import android.view.animation.Animation
11
+ import android.view.animation.AnimationUtils
12
+ import androidx.appcompat.widget.Toolbar
13
+ import androidx.core.graphics.Insets
14
+ import androidx.core.view.OnApplyWindowInsetsListener
15
+ import androidx.core.view.WindowInsetsCompat
16
+ import androidx.fragment.app.Fragment
17
+ import androidx.fragment.app.commit
18
+ import androidx.lifecycle.Lifecycle
19
+ import androidx.lifecycle.LifecycleEventObserver
20
+ import androidx.lifecycle.LifecycleOwner
21
+ import com.facebook.react.bridge.ReactContext
22
+ import com.facebook.react.uimanager.UIManagerHelper
23
+ import com.google.android.material.bottomsheet.BottomSheetBehavior
24
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
25
+ import com.swmansion.rnscreens.InsetsObserverProxy
26
+ import com.swmansion.rnscreens.KeyboardDidHide
27
+ import com.swmansion.rnscreens.KeyboardNotVisible
28
+ import com.swmansion.rnscreens.KeyboardState
29
+ import com.swmansion.rnscreens.KeyboardVisible
30
+ import com.swmansion.rnscreens.R
31
+ import com.swmansion.rnscreens.Screen
32
+ import com.swmansion.rnscreens.ScreenContainer
33
+ import com.swmansion.rnscreens.ScreenFragment
34
+ import com.swmansion.rnscreens.ScreenFragmentWrapper
35
+ import com.swmansion.rnscreens.ScreenStack
36
+ import com.swmansion.rnscreens.ScreenStackFragment
37
+ import com.swmansion.rnscreens.ScreenStackFragmentWrapper
38
+ import com.swmansion.rnscreens.events.ScreenDismissedEvent
39
+
40
+ /**
41
+ * This fragment aims to provide dimming view functionality behind the nested fragment.
42
+ * Useful when nested fragment is transparent / uses some kind of non-fullscreen presentation,
43
+ * such as `formSheet`.
44
+ */
45
+ class DimmingFragment(
46
+ val nestedFragment: ScreenFragmentWrapper,
47
+ ) : Fragment(),
48
+ LifecycleEventObserver,
49
+ ScreenStackFragmentWrapper,
50
+ Animation.AnimationListener,
51
+ OnApplyWindowInsetsListener {
52
+ private lateinit var dimmingView: DimmingView
53
+ private lateinit var containerView: GestureTransparentViewGroup
54
+
55
+ private val maxAlpha: Float = 0.15F
56
+
57
+ private var isKeyboardVisible: Boolean = false
58
+ private var keyboardState: KeyboardState = KeyboardNotVisible
59
+
60
+ private var dimmingViewCallback: BottomSheetCallback? = null
61
+
62
+ private val container: ScreenStack?
63
+ get() = screen.container as? ScreenStack
64
+
65
+ private val insetsProxy = InsetsObserverProxy
66
+
67
+ init {
68
+ // We register for our child lifecycle as we want to know when it's dismissed via native gesture
69
+ nestedFragment.fragment.lifecycle.addObserver(this)
70
+ }
71
+
72
+ /**
73
+ * This bottom sheet callback is responsible for animating alpha of the dimming view.
74
+ */
75
+ private class AnimateDimmingViewCallback(
76
+ val screen: Screen,
77
+ val viewToAnimate: View,
78
+ val maxAlpha: Float,
79
+ ) : BottomSheetCallback() {
80
+ // largest *slide offset* that is yet undimmed
81
+ private var largestUndimmedOffset: Float =
82
+ computeOffsetFromDetentIndex(screen.sheetLargestUndimmedDetentIndex)
83
+
84
+ // first *slide offset* that should be fully dimmed
85
+ private var firstDimmedOffset: Float =
86
+ computeOffsetFromDetentIndex(
87
+ (screen.sheetLargestUndimmedDetentIndex + 1).coerceIn(
88
+ 0,
89
+ screen.sheetDetents.count() - 1,
90
+ ),
91
+ )
92
+
93
+ // interval that we interpolate the alpha value over
94
+ private var intervalLength = firstDimmedOffset - largestUndimmedOffset
95
+ private val animator =
96
+ ValueAnimator.ofFloat(0F, maxAlpha).apply {
97
+ duration = 1 // Driven manually
98
+ addUpdateListener {
99
+ viewToAnimate.alpha = it.animatedValue as Float
100
+ }
101
+ }
102
+
103
+ override fun onStateChanged(
104
+ bottomSheet: View,
105
+ newState: Int,
106
+ ) {
107
+ if (newState == BottomSheetBehavior.STATE_DRAGGING || newState == BottomSheetBehavior.STATE_SETTLING) {
108
+ largestUndimmedOffset =
109
+ computeOffsetFromDetentIndex(screen.sheetLargestUndimmedDetentIndex)
110
+ firstDimmedOffset =
111
+ computeOffsetFromDetentIndex(
112
+ (screen.sheetLargestUndimmedDetentIndex + 1).coerceIn(
113
+ 0,
114
+ screen.sheetDetents.count() - 1
115
+ )
116
+ )
117
+ assert(firstDimmedOffset >= largestUndimmedOffset) {
118
+ "[RNScreens] Invariant violation: firstDimmedOffset ($firstDimmedOffset) < largestDimmedOffset ($largestUndimmedOffset)"
119
+ }
120
+ intervalLength = firstDimmedOffset - largestUndimmedOffset
121
+ }
122
+ }
123
+
124
+ override fun onSlide(
125
+ bottomSheet: View,
126
+ slideOffset: Float,
127
+ ) {
128
+ if (largestUndimmedOffset < slideOffset && slideOffset < firstDimmedOffset) {
129
+ val fraction = (slideOffset - largestUndimmedOffset) / intervalLength
130
+ animator.setCurrentFraction(fraction)
131
+ }
132
+ }
133
+
134
+ /**
135
+ * This method does compute slide offset (see [BottomSheetCallback.onSlide] docs) for detent
136
+ * at given index in the detents array.
137
+ */
138
+ private fun computeOffsetFromDetentIndex(index: Int): Float =
139
+ when (screen.sheetDetents.size) {
140
+ 1 -> // Only 1 detent present in detents array
141
+ when (index) {
142
+ -1 -> -1F // hidden
143
+ 0 -> 1F // fully expanded
144
+ else -> -1F // unexpected, default
145
+ }
146
+
147
+ 2 ->
148
+ when (index) {
149
+ -1 -> -1F // hidden
150
+ 0 -> 0F // collapsed
151
+ 1 -> 1F // expanded
152
+ else -> -1F
153
+ }
154
+
155
+ 3 ->
156
+ when (index) {
157
+ -1 -> -1F // hidden
158
+ 0 -> 0F // collapsed
159
+ 1 -> screen.sheetBehavior!!.halfExpandedRatio // half
160
+ 2 -> 1F // expanded
161
+ else -> -1F
162
+ }
163
+
164
+ else -> -1F
165
+ }
166
+ }
167
+
168
+ override fun onCreateAnimation(
169
+ transit: Int,
170
+ enter: Boolean,
171
+ nextAnim: Int,
172
+ ): Animation? =
173
+ // We want dimming view to have always fade animation in current usages.
174
+ AnimationUtils.loadAnimation(
175
+ context,
176
+ if (enter) R.anim.rns_fade_in else R.anim.rns_fade_out
177
+ )
178
+
179
+ override fun onCreateView(
180
+ inflater: LayoutInflater,
181
+ container: ViewGroup?,
182
+ savedInstanceState: Bundle?,
183
+ ): View {
184
+ initViewHierarchy()
185
+ return containerView
186
+ }
187
+
188
+ override fun onViewCreated(
189
+ view: View,
190
+ savedInstanceState: Bundle?,
191
+ ) {
192
+ if (screen.sheetInitialDetentIndex <= screen.sheetLargestUndimmedDetentIndex) {
193
+ dimmingView.alpha = 0.0F
194
+ } else {
195
+ dimmingView.alpha = maxAlpha
196
+ }
197
+ }
198
+
199
+ override fun onStart() {
200
+ // This is the earliest we can access child fragment manager & present another fragment
201
+ super.onStart()
202
+ insetsProxy.registerOnView(requireRootView())
203
+ presentNestedFragment()
204
+ }
205
+
206
+ override fun onResume() {
207
+ insetsProxy.addOnApplyWindowInsetsListener(this)
208
+ super.onResume()
209
+ }
210
+
211
+ override fun onPause() {
212
+ super.onPause()
213
+ insetsProxy.removeOnApplyWindowInsetsListener(this)
214
+ }
215
+
216
+ override fun onStateChanged(
217
+ source: LifecycleOwner,
218
+ event: Lifecycle.Event,
219
+ ) {
220
+ when (event) {
221
+ Lifecycle.Event.ON_START -> {
222
+ nestedFragment.screen.sheetBehavior?.let {
223
+ dimmingViewCallback =
224
+ AnimateDimmingViewCallback(nestedFragment.screen, dimmingView, maxAlpha)
225
+ it.addBottomSheetCallback(dimmingViewCallback!!)
226
+ }
227
+ }
228
+
229
+ Lifecycle.Event.ON_STOP -> {
230
+ dismissSelf(emitDismissedEvent = true)
231
+ }
232
+
233
+ else -> {}
234
+ }
235
+ }
236
+
237
+ private fun presentNestedFragment() {
238
+ childFragmentManager.commit(allowStateLoss = true) {
239
+ setReorderingAllowed(true)
240
+ add(requireView().id, nestedFragment.fragment, null)
241
+ }
242
+ }
243
+
244
+ private fun cleanRegisteredCallbacks() {
245
+ dimmingViewCallback?.let {
246
+ nestedFragment.screen.sheetBehavior?.removeBottomSheetCallback(it)
247
+ }
248
+ dimmingView.setOnClickListener(null)
249
+ nestedFragment.fragment.lifecycle.removeObserver(this)
250
+ insetsProxy.removeOnApplyWindowInsetsListener(this)
251
+ }
252
+
253
+ private fun dismissSelf(emitDismissedEvent: Boolean = false) {
254
+ if (!this.isRemoving) {
255
+ if (emitDismissedEvent) {
256
+ val reactContext = nestedFragment.screen.reactContext
257
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
258
+ UIManagerHelper
259
+ .getEventDispatcherForReactTag(reactContext, screen.id)
260
+ ?.dispatchEvent(ScreenDismissedEvent(surfaceId, screen.id))
261
+ }
262
+ cleanRegisteredCallbacks()
263
+ dismissFromContainer()
264
+ }
265
+ }
266
+
267
+ private fun initViewHierarchy() {
268
+ initContainerView()
269
+ initDimmingView()
270
+ containerView.addView(dimmingView)
271
+ }
272
+
273
+ private fun initContainerView() {
274
+ containerView =
275
+ GestureTransparentViewGroup(requireContext()).apply {
276
+ // These do not guarantee fullscreen width & height, TODO: find a way to guarantee that
277
+ layoutParams =
278
+ ViewGroup.LayoutParams(
279
+ ViewGroup.LayoutParams.MATCH_PARENT,
280
+ ViewGroup.LayoutParams.MATCH_PARENT,
281
+ )
282
+ setBackgroundColor(Color.TRANSPARENT)
283
+ // This is purely native view, React does not know of it, thus there should be no conflict with ids.
284
+ id = View.generateViewId()
285
+ }
286
+ }
287
+
288
+ private fun initDimmingView() {
289
+ dimmingView =
290
+ DimmingView(requireContext(), maxAlpha).apply {
291
+ // These do not guarantee fullscreen width & height, TODO: find a way to guarantee that
292
+ layoutParams =
293
+ ViewGroup.LayoutParams(
294
+ ViewGroup.LayoutParams.MATCH_PARENT,
295
+ ViewGroup.LayoutParams.MATCH_PARENT,
296
+ )
297
+ setOnClickListener {
298
+ if (screen.sheetClosesOnTouchOutside) {
299
+ dismissSelf(true)
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+ private fun requireRootView(): View =
306
+ checkNotNull(screen.reactContext.currentActivity) { "[RNScreens] Attempt to access activity on detached context" }
307
+ .window.decorView
308
+
309
+ // TODO: Move these methods related to toolbar to separate interface
310
+ override fun removeToolbar() = Unit
311
+
312
+ override fun setToolbar(toolbar: Toolbar) = Unit
313
+
314
+ override fun setToolbarShadowHidden(hidden: Boolean) = Unit
315
+
316
+ override fun setToolbarTranslucent(translucent: Boolean) = Unit
317
+
318
+ // Dimming view should never be bottom-most fragment
319
+ override fun canNavigateBack(): Boolean = true
320
+
321
+ override fun dismissFromContainer() {
322
+ container?.dismiss(this)
323
+ }
324
+
325
+ override var screen: Screen
326
+ get() = nestedFragment.screen
327
+ set(value) {
328
+ nestedFragment.screen = value
329
+ }
330
+
331
+ override val childScreenContainers: List<ScreenContainer> = nestedFragment.childScreenContainers
332
+
333
+ override fun addChildScreenContainer(container: ScreenContainer) {
334
+ nestedFragment.addChildScreenContainer(container)
335
+ }
336
+
337
+ override fun removeChildScreenContainer(container: ScreenContainer) {
338
+ nestedFragment.removeChildScreenContainer(container)
339
+ }
340
+
341
+ override fun onContainerUpdate() {
342
+ nestedFragment.onContainerUpdate()
343
+ }
344
+
345
+ override fun onViewAnimationStart() {
346
+ nestedFragment.onViewAnimationStart()
347
+ }
348
+
349
+ override fun onViewAnimationEnd() {
350
+ nestedFragment.onViewAnimationEnd()
351
+ }
352
+
353
+ override fun tryGetActivity(): Activity? {
354
+ return activity
355
+ }
356
+
357
+ override fun tryGetContext(): ReactContext? {
358
+ return context as? ReactContext?
359
+ }
360
+
361
+ override val fragment: Fragment
362
+ get() = this
363
+
364
+ override fun canDispatchLifecycleEvent(event: ScreenFragment.ScreenLifecycleEvent): Boolean {
365
+ TODO("Not yet implemented")
366
+ }
367
+
368
+ override fun updateLastEventDispatched(event: ScreenFragment.ScreenLifecycleEvent) {
369
+ TODO("Not yet implemented")
370
+ }
371
+
372
+ override fun dispatchLifecycleEvent(
373
+ event: ScreenFragment.ScreenLifecycleEvent,
374
+ fragmentWrapper: ScreenFragmentWrapper,
375
+ ) {
376
+ TODO("Not yet implemented")
377
+ }
378
+
379
+ override fun dispatchLifecycleEventInChildContainers(event: ScreenFragment.ScreenLifecycleEvent) {
380
+ TODO("Not yet implemented")
381
+ }
382
+
383
+ override fun dispatchHeaderBackButtonClickedEvent() {
384
+ TODO("Not yet implemented")
385
+ }
386
+
387
+ override fun dispatchTransitionProgressEvent(
388
+ alpha: Float,
389
+ closing: Boolean,
390
+ ) {
391
+ TODO("Not yet implemented")
392
+ }
393
+
394
+ override fun onAnimationStart(animation: Animation?) = Unit
395
+
396
+ override fun onAnimationEnd(animation: Animation?) {
397
+ dismissFromContainer()
398
+ }
399
+
400
+ override fun onAnimationRepeat(animation: Animation?) = Unit
401
+
402
+ companion object {
403
+ const val TAG = "DimmingFragment"
404
+ }
405
+
406
+ // This is View.OnApplyWindowInsetsListener method, not view's own!
407
+ override fun onApplyWindowInsets(
408
+ v: View,
409
+ insets: WindowInsetsCompat,
410
+ ): WindowInsetsCompat {
411
+ val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
412
+ val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime())
413
+
414
+ if (isImeVisible) {
415
+ isKeyboardVisible = true
416
+ keyboardState = KeyboardVisible(imeInset.bottom)
417
+ screen.sheetBehavior?.let {
418
+ (nestedFragment as ScreenStackFragment).configureBottomSheetBehaviour(
419
+ it,
420
+ KeyboardVisible(imeInset.bottom)
421
+ )
422
+ }
423
+
424
+ if (this.isRemoving) {
425
+ return insets
426
+ }
427
+
428
+ val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
429
+ return WindowInsetsCompat
430
+ .Builder(insets)
431
+ .setInsets(
432
+ WindowInsetsCompat.Type.navigationBars(),
433
+ Insets.of(
434
+ prevInsets.left,
435
+ prevInsets.top,
436
+ prevInsets.right,
437
+ 0,
438
+ ),
439
+ ).build()
440
+ } else {
441
+ if (this.isRemoving) {
442
+ val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
443
+ return WindowInsetsCompat
444
+ .Builder(insets)
445
+ .setInsets(
446
+ WindowInsetsCompat.Type.navigationBars(),
447
+ Insets.of(
448
+ prevInsets.left,
449
+ prevInsets.top,
450
+ prevInsets.right,
451
+ 0,
452
+ ),
453
+ ).build()
454
+ }
455
+
456
+ screen.sheetBehavior?.let {
457
+ if (isKeyboardVisible) {
458
+ (nestedFragment as ScreenStackFragment).configureBottomSheetBehaviour(
459
+ it,
460
+ KeyboardDidHide
461
+ )
462
+ } else if (keyboardState != KeyboardNotVisible) {
463
+ (nestedFragment as ScreenStackFragment).configureBottomSheetBehaviour(
464
+ it,
465
+ KeyboardNotVisible
466
+ )
467
+ } else {
468
+ }
469
+ }
470
+
471
+ keyboardState = KeyboardNotVisible
472
+ isKeyboardVisible = false
473
+
474
+ val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
475
+ return WindowInsetsCompat
476
+ .Builder(insets)
477
+ .setInsets(
478
+ WindowInsetsCompat.Type.navigationBars(),
479
+ Insets.of(
480
+ prevInsets.left,
481
+ prevInsets.top,
482
+ prevInsets.right,
483
+ 0,
484
+ ),
485
+ ).build()
486
+ }
487
+ }
488
+ }
@@ -0,0 +1,66 @@
1
+ package com.swmansion.rnscreens.bottomsheet
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.graphics.Color
6
+ import android.view.MotionEvent
7
+ import android.view.ViewGroup
8
+ import com.facebook.react.uimanager.PointerEvents
9
+ import com.facebook.react.uimanager.ReactCompoundViewGroup
10
+ import com.facebook.react.uimanager.ReactPointerEventsView
11
+ import com.swmansion.rnscreens.ext.equalWithRespectToEps
12
+
13
+ /**
14
+ * Serves as dimming view that can be used as background for some view that not fully fills
15
+ * the viewport.
16
+ *
17
+ * This dimming view has one more additional feature: it blocks gestures if its alpha > 0.
18
+ */
19
+ @SuppressLint("ViewConstructor") // Only we instantiate this view
20
+ class DimmingView(
21
+ context: Context,
22
+ initialAlpha: Float = 0.6F,
23
+ ) : ViewGroup(context),
24
+ ReactCompoundViewGroup,
25
+ ReactPointerEventsView {
26
+ private val blockGestures
27
+ get() = !alpha.equalWithRespectToEps(0F)
28
+
29
+ init {
30
+ setBackgroundColor(Color.BLACK)
31
+ alpha = initialAlpha
32
+ }
33
+
34
+ // This view group is not supposed to have any children, however we need it to be a view group
35
+ override fun onLayout(
36
+ changed: Boolean,
37
+ l: Int,
38
+ t: Int,
39
+ r: Int,
40
+ b: Int,
41
+ ) = Unit
42
+
43
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
44
+ if (blockGestures) {
45
+ callOnClick()
46
+ }
47
+ return blockGestures
48
+ }
49
+
50
+ override fun reactTagForTouch(
51
+ x: Float,
52
+ y: Float,
53
+ ): Int = throw IllegalStateException("[RNScreens] $TAG should never be asked for the view tag!")
54
+
55
+ override fun interceptsTouchEvent(
56
+ x: Float,
57
+ y: Float,
58
+ ) = blockGestures
59
+
60
+ override fun getPointerEvents(): PointerEvents =
61
+ if (blockGestures) PointerEvents.AUTO else PointerEvents.NONE
62
+
63
+ companion object {
64
+ const val TAG = "DimmingView"
65
+ }
66
+ }
@@ -0,0 +1,24 @@
1
+ package com.swmansion.rnscreens.bottomsheet
2
+
3
+ import android.content.Context
4
+ import android.widget.FrameLayout
5
+ import com.facebook.react.uimanager.PointerEvents
6
+ import com.facebook.react.uimanager.ReactPointerEventsView
7
+
8
+ /**
9
+ * View group that will be ignored by RN event system, and won't be target of touches.
10
+ *
11
+ * Currently used as container for the form sheet, so that user can interact with the view
12
+ * under the sheet (otherwise RN captures the gestures).
13
+ */
14
+ class GestureTransparentViewGroup(
15
+ context: Context,
16
+ ) : FrameLayout(context),
17
+ ReactPointerEventsView {
18
+
19
+ override fun getPointerEvents(): PointerEvents = PointerEvents.BOX_NONE
20
+
21
+ companion object {
22
+ const val TAG = "GestureTransparentFrameLayout"
23
+ }
24
+ }
@@ -0,0 +1,127 @@
1
+ package com.swmansion.rnscreens.bottomsheet
2
+
3
+ import com.google.android.material.bottomsheet.BottomSheetBehavior
4
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
5
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
6
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
7
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
8
+
9
+ object SheetUtils {
10
+ /**
11
+ * Verifies whether BottomSheetBehavior.State is one of stable states. As unstable states
12
+ * we consider `STATE_DRAGGING` and `STATE_SETTLING`.
13
+ *
14
+ * @param state bottom sheet state to verify
15
+ */
16
+ fun isStateStable(state: Int): Boolean =
17
+ when (state) {
18
+ STATE_HIDDEN,
19
+ STATE_EXPANDED,
20
+ STATE_COLLAPSED,
21
+ STATE_HALF_EXPANDED,
22
+ -> true
23
+
24
+ else -> false
25
+ }
26
+
27
+ /**
28
+ * This method maps indices from legal detents array (prop) to appropriate values
29
+ * recognized by BottomSheetBehaviour. In particular used when setting up the initial behaviour
30
+ * of the form sheet.
31
+ *
32
+ * @param index index from array with detents fractions
33
+ * @param detentCount length of array with detents fractions
34
+ *
35
+ * @throws IllegalArgumentException for invalid index / detentCount combinations
36
+ */
37
+ fun sheetStateFromDetentIndex(
38
+ index: Int,
39
+ detentCount: Int,
40
+ ): Int =
41
+ when (detentCount) {
42
+ 1 ->
43
+ when (index) {
44
+ -1 -> STATE_HIDDEN
45
+ 0 -> STATE_EXPANDED
46
+ else -> throw IllegalArgumentException("[RNScreens] Invalid detentCount/index combination $detentCount / $index")
47
+ }
48
+
49
+ 2 ->
50
+ when (index) {
51
+ -1 -> STATE_HIDDEN
52
+ 0 -> STATE_COLLAPSED
53
+ 1 -> STATE_EXPANDED
54
+ else -> throw IllegalArgumentException("[RNScreens] Invalid detentCount/index combination $detentCount / $index")
55
+ }
56
+
57
+ 3 ->
58
+ when (index) {
59
+ -1 -> STATE_HIDDEN
60
+ 0 -> STATE_COLLAPSED
61
+ 1 -> STATE_HALF_EXPANDED
62
+ 2 -> STATE_EXPANDED
63
+ else -> throw IllegalArgumentException("[RNScreens] Invalid detentCount/index combination $detentCount / $index")
64
+ }
65
+
66
+ else -> throw IllegalArgumentException("[RNScreens] Invalid detentCount/index combination $detentCount / $index")
67
+ }
68
+
69
+ /**
70
+ * This method maps BottomSheetBehavior.State values to appropriate indices of detents array.
71
+ *
72
+ * @param state state of the bottom sheet
73
+ * @param detentCount length of array with detents fractions
74
+ *
75
+ * @throws IllegalArgumentException for invalid state / detentCount combinations
76
+ */
77
+ fun detentIndexFromSheetState(
78
+ @BottomSheetBehavior.State state: Int,
79
+ detentCount: Int,
80
+ ): Int =
81
+ when (detentCount) {
82
+ 1 ->
83
+ when (state) {
84
+ STATE_HIDDEN -> -1
85
+ STATE_EXPANDED -> 0
86
+ else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount")
87
+ }
88
+
89
+ 2 ->
90
+ when (state) {
91
+ STATE_HIDDEN -> -1
92
+ STATE_COLLAPSED -> 0
93
+ STATE_EXPANDED -> 1
94
+ else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount")
95
+ }
96
+
97
+ 3 ->
98
+ when (state) {
99
+ STATE_HIDDEN -> -1
100
+ STATE_COLLAPSED -> 0
101
+ STATE_HALF_EXPANDED -> 1
102
+ STATE_EXPANDED -> 2
103
+ else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount")
104
+ }
105
+
106
+ else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount")
107
+ }
108
+
109
+ fun isStateLessEqualThan(
110
+ state: Int,
111
+ otherState: Int,
112
+ ): Boolean {
113
+ if (state == otherState) {
114
+ return true
115
+ }
116
+ if (state != STATE_HALF_EXPANDED && otherState != STATE_HALF_EXPANDED) {
117
+ return state > otherState
118
+ }
119
+ if (state == STATE_HALF_EXPANDED) {
120
+ return otherState == BottomSheetBehavior.STATE_EXPANDED
121
+ }
122
+ if (state == STATE_COLLAPSED) {
123
+ return otherState != STATE_HIDDEN
124
+ }
125
+ return false
126
+ }
127
+ }