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.
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/swmansion/rnscreens/InsetsObserverProxy.kt +67 -0
- package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -0
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +101 -4
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapper.kt +38 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapperManager.kt +25 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFooter.kt +287 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFooterManager.kt +25 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +11 -19
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +4 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenModalFragment.kt +281 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +62 -19
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +403 -41
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt +4 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +2 -2
- package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +95 -11
- package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +39 -28
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetDialogRootView.kt +104 -0
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetDialogScreen.kt +26 -0
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingFragment.kt +488 -0
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingView.kt +66 -0
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/GestureTransparentViewGroup.kt +24 -0
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetUtils.kt +127 -0
- package/android/src/main/java/com/swmansion/rnscreens/events/SheetDetentChangedEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/rnscreens/ext/NumericExt.kt +12 -0
- package/android/src/main/java/com/swmansion/rnscreens/ext/ViewExt.kt +32 -0
- package/android/src/main/java/com/swmansion/rnscreens/utils/ScreenDummyLayoutHelper.kt +45 -8
- package/android/src/main/res/base/drawable/rns_rounder_top_corners_shape.xml +8 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenContentWrapperManagerDelegate.java +25 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenContentWrapperManagerInterface.java +16 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenFooterManagerDelegate.java +25 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenFooterManagerInterface.java +16 -0
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +9 -2
- package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +5 -2
- package/ios/RNSConvert.h +5 -3
- package/ios/RNSConvert.mm +14 -20
- package/ios/RNSScreen.h +3 -2
- package/ios/RNSScreen.mm +433 -49
- package/ios/RNSScreenContentWrapper.h +44 -0
- package/ios/RNSScreenContentWrapper.mm +61 -0
- package/ios/RNSScreenFooter.h +30 -0
- package/ios/RNSScreenFooter.mm +137 -0
- package/lib/commonjs/components/Screen.js +6 -2
- package/lib/commonjs/components/Screen.js.map +1 -1
- package/lib/commonjs/components/ScreenContentWrapper.js +19 -0
- package/lib/commonjs/components/ScreenContentWrapper.js.map +1 -0
- package/lib/commonjs/components/ScreenFooter.js +23 -0
- package/lib/commonjs/components/ScreenFooter.js.map +1 -0
- package/lib/commonjs/fabric/ModalScreenNativeComponent.js.map +1 -1
- package/lib/commonjs/fabric/ScreenContentWrapperNativeComponent.js +10 -0
- package/lib/commonjs/fabric/ScreenContentWrapperNativeComponent.js.map +1 -0
- package/lib/commonjs/fabric/ScreenFooterNativeComponent.js +10 -0
- package/lib/commonjs/fabric/ScreenFooterNativeComponent.js.map +1 -0
- package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
- package/lib/commonjs/index.js +30 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native-stack/views/FooterComponent.js +18 -0
- package/lib/commonjs/native-stack/views/FooterComponent.js.map +1 -0
- package/lib/commonjs/native-stack/views/NativeStackView.js +59 -14
- package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
- package/lib/module/components/Screen.js +6 -2
- package/lib/module/components/Screen.js.map +1 -1
- package/lib/module/components/ScreenContentWrapper.js +12 -0
- package/lib/module/components/ScreenContentWrapper.js.map +1 -0
- package/lib/module/components/ScreenFooter.js +17 -0
- package/lib/module/components/ScreenFooter.js.map +1 -0
- package/lib/module/fabric/ModalScreenNativeComponent.js.map +1 -1
- package/lib/module/fabric/ScreenContentWrapperNativeComponent.js +3 -0
- package/lib/module/fabric/ScreenContentWrapperNativeComponent.js.map +1 -0
- package/lib/module/fabric/ScreenFooterNativeComponent.js +3 -0
- package/lib/module/fabric/ScreenFooterNativeComponent.js.map +1 -0
- package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native-stack/views/FooterComponent.js +11 -0
- package/lib/module/native-stack/views/FooterComponent.js.map +1 -0
- package/lib/module/native-stack/views/NativeStackView.js +61 -15
- package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
- package/lib/typescript/components/Screen.d.ts.map +1 -1
- package/lib/typescript/components/ScreenContentWrapper.d.ts +6 -0
- package/lib/typescript/components/ScreenContentWrapper.d.ts.map +1 -0
- package/lib/typescript/components/ScreenFooter.d.ts +12 -0
- package/lib/typescript/components/ScreenFooter.d.ts.map +1 -0
- package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts +2 -3
- package/lib/typescript/fabric/ModalScreenNativeComponent.d.ts.map +1 -1
- package/lib/typescript/fabric/ScreenContentWrapperNativeComponent.d.ts +7 -0
- package/lib/typescript/fabric/ScreenContentWrapperNativeComponent.d.ts.map +1 -0
- package/lib/typescript/fabric/ScreenFooterNativeComponent.d.ts +7 -0
- package/lib/typescript/fabric/ScreenFooterNativeComponent.d.ts.map +1 -0
- package/lib/typescript/fabric/ScreenNativeComponent.d.ts +9 -3
- package/lib/typescript/fabric/ScreenNativeComponent.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/native-stack/types.d.ts +63 -23
- package/lib/typescript/native-stack/types.d.ts.map +1 -1
- package/lib/typescript/native-stack/views/FooterComponent.d.ts +7 -0
- package/lib/typescript/native-stack/views/FooterComponent.d.ts.map +1 -0
- package/lib/typescript/native-stack/views/NativeStackView.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +42 -17
- package/lib/typescript/types.d.ts.map +1 -1
- package/native-stack/README.md +16 -14
- package/package.json +1 -1
- package/react-native.config.js +18 -16
- package/src/components/Screen.tsx +6 -2
- package/src/components/ScreenContentWrapper.tsx +12 -0
- package/src/components/ScreenFooter.tsx +18 -0
- package/src/fabric/ModalScreenNativeComponent.ts +2 -4
- package/src/fabric/ScreenContentWrapperNativeComponent.ts +9 -0
- package/src/fabric/ScreenFooterNativeComponent.ts +6 -0
- package/src/fabric/ScreenNativeComponent.ts +10 -4
- package/src/index.tsx +10 -0
- package/src/native-stack/types.tsx +57 -23
- package/src/native-stack/views/FooterComponent.tsx +10 -0
- package/src/native-stack/views/NativeStackView.tsx +74 -11
- 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
|
+
}
|
package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/GestureTransparentViewGroup.kt
ADDED
|
@@ -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
|
+
}
|