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
|
@@ -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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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 {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
232
|
-
|
|
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
|
|
240
|
-
) : CoordinatorLayout(context) {
|
|
241
|
-
|
|
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
|
-
|
|
584
|
+
fragment.onViewAnimationStart()
|
|
245
585
|
}
|
|
246
586
|
|
|
247
587
|
override fun onAnimationEnd(animation: Animation) {
|
|
248
|
-
|
|
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(
|
|
604
|
+
val fakeAnimation = ScreensAnimation(fragment).apply { duration = animation.duration }
|
|
265
605
|
|
|
266
|
-
if (animation is AnimationSet && !
|
|
606
|
+
if (animation is AnimationSet && !fragment.isRemoving) {
|
|
267
607
|
animation
|
|
268
608
|
.apply {
|
|
269
609
|
addAnimation(fakeAnimation)
|
|
270
|
-
setAnimationListener(
|
|
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(
|
|
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(
|
|
@@ -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.
|
|
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.
|
|
65
|
+
it.dismissFromContainer()
|
|
66
66
|
} else {
|
|
67
67
|
it.dispatchHeaderBackButtonClickedEvent()
|
|
68
68
|
}
|