react-native-screens 4.6.0 → 4.7.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +70 -3
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapper.kt +2 -2
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +1 -7
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +8 -4
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +130 -17
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/BottomSheetBehaviorExt.kt +3 -1
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingView.kt +3 -1
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingViewManager.kt +165 -0
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +119 -0
- package/android/src/main/java/com/swmansion/rnscreens/events/ScreenEventDelegate.kt +48 -0
- package/android/src/main/java/com/swmansion/rnscreens/transition/ExternalBoundaryValuesEvaluator.kt +38 -0
- package/package.json +1 -1
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/DimmingFragment.kt +0 -493
|
@@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo
|
|
|
5
5
|
import android.graphics.Paint
|
|
6
6
|
import android.os.Parcelable
|
|
7
7
|
import android.util.SparseArray
|
|
8
|
+
import android.view.MotionEvent
|
|
8
9
|
import android.view.View
|
|
9
10
|
import android.view.ViewGroup
|
|
10
11
|
import android.view.WindowManager
|
|
@@ -17,6 +18,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
|
17
18
|
import com.facebook.react.bridge.GuardedRunnable
|
|
18
19
|
import com.facebook.react.bridge.ReactContext
|
|
19
20
|
import com.facebook.react.uimanager.PixelUtil
|
|
21
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
20
22
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
21
23
|
import com.facebook.react.uimanager.UIManagerModule
|
|
22
24
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
@@ -24,13 +26,16 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
|
24
26
|
import com.google.android.material.shape.CornerFamily
|
|
25
27
|
import com.google.android.material.shape.MaterialShapeDrawable
|
|
26
28
|
import com.google.android.material.shape.ShapeAppearanceModel
|
|
29
|
+
import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
|
|
30
|
+
import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
|
|
27
31
|
import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
|
|
28
32
|
import com.swmansion.rnscreens.events.SheetDetentChangedEvent
|
|
33
|
+
import com.swmansion.rnscreens.ext.parentAsViewGroup
|
|
29
34
|
import java.lang.ref.WeakReference
|
|
30
35
|
|
|
31
36
|
@SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated.
|
|
32
37
|
class Screen(
|
|
33
|
-
val reactContext:
|
|
38
|
+
val reactContext: ThemedReactContext,
|
|
34
39
|
) : FabricEnabledViewGroup(reactContext),
|
|
35
40
|
ScreenContentWrapper.OnLayoutCallback {
|
|
36
41
|
val fragment: Fragment?
|
|
@@ -81,6 +86,13 @@ class Screen(
|
|
|
81
86
|
var sheetClosesOnTouchOutside = true
|
|
82
87
|
var sheetElevation: Float = 24F
|
|
83
88
|
|
|
89
|
+
/**
|
|
90
|
+
* When using form sheet presentation we want to delay enter transition **on Paper** in order
|
|
91
|
+
* to wait for initial layout from React, otherwise the animator-based animation will look
|
|
92
|
+
* glitchy. *This is not needed on Fabric*.
|
|
93
|
+
*/
|
|
94
|
+
var shouldTriggerPostponedTransitionAfterLayout = false
|
|
95
|
+
|
|
84
96
|
var footer: ScreenFooter? = null
|
|
85
97
|
set(value) {
|
|
86
98
|
if (value == null && field != null) {
|
|
@@ -110,7 +122,7 @@ class Screen(
|
|
|
110
122
|
* `fitToContents` for formSheets, as this is first entry point where we can acquire
|
|
111
123
|
* height of our content.
|
|
112
124
|
*/
|
|
113
|
-
override fun
|
|
125
|
+
override fun onContentWrapperLayout(
|
|
114
126
|
changed: Boolean,
|
|
115
127
|
left: Int,
|
|
116
128
|
top: Int,
|
|
@@ -119,12 +131,23 @@ class Screen(
|
|
|
119
131
|
) {
|
|
120
132
|
val height = bottom - top
|
|
121
133
|
|
|
122
|
-
if (
|
|
134
|
+
if (isSheetFitToContents()) {
|
|
123
135
|
sheetBehavior?.let {
|
|
124
136
|
if (it.maxHeight != height) {
|
|
125
137
|
it.maxHeight = height
|
|
126
138
|
}
|
|
127
139
|
}
|
|
140
|
+
|
|
141
|
+
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
142
|
+
// On old architecture we delay enter transition in order to wait for initial frame.
|
|
143
|
+
shouldTriggerPostponedTransitionAfterLayout = true
|
|
144
|
+
val parent = parentAsViewGroup()
|
|
145
|
+
if (parent != null && !parent.isInLayout) {
|
|
146
|
+
// There are reported cases (irreproducible) when Screen is not laid out after
|
|
147
|
+
// maxHeight is set on behaviour.
|
|
148
|
+
parent.requestLayout()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
128
151
|
}
|
|
129
152
|
}
|
|
130
153
|
|
|
@@ -162,6 +185,17 @@ class Screen(
|
|
|
162
185
|
|
|
163
186
|
footer?.onParentLayout(changed, l, t, r, b, container!!.height)
|
|
164
187
|
notifyHeaderHeightChange(t)
|
|
188
|
+
|
|
189
|
+
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
190
|
+
maybeTriggerPostponedTransition()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private fun maybeTriggerPostponedTransition() {
|
|
196
|
+
if (shouldTriggerPostponedTransitionAfterLayout) {
|
|
197
|
+
shouldTriggerPostponedTransitionAfterLayout = false
|
|
198
|
+
fragment?.startPostponedEnterTransition()
|
|
165
199
|
}
|
|
166
200
|
}
|
|
167
201
|
|
|
@@ -377,6 +411,28 @@ class Screen(
|
|
|
377
411
|
}
|
|
378
412
|
}
|
|
379
413
|
|
|
414
|
+
fun endRemovalTransition() {
|
|
415
|
+
if (!isBeingRemoved) {
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
isBeingRemoved = false
|
|
419
|
+
endTransitionRecursive(this)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private fun endTransitionRecursive(parent: ViewGroup) {
|
|
423
|
+
parent.children.forEach { childView ->
|
|
424
|
+
parent.endViewTransition(childView)
|
|
425
|
+
|
|
426
|
+
if (childView is ScreenStackHeaderConfig) {
|
|
427
|
+
endTransitionRecursive(childView.toolbar)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (childView is ViewGroup) {
|
|
431
|
+
endTransitionRecursive(childView)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
380
436
|
private fun startTransitionRecursive(parent: ViewGroup?) {
|
|
381
437
|
parent?.let {
|
|
382
438
|
for (i in 0 until it.childCount) {
|
|
@@ -407,6 +463,17 @@ class Screen(
|
|
|
407
463
|
}
|
|
408
464
|
}
|
|
409
465
|
|
|
466
|
+
// We do not want to perform any action, therefore do not need to override the associated method.
|
|
467
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
468
|
+
override fun onTouchEvent(event: MotionEvent?): Boolean =
|
|
469
|
+
if (usesFormSheetPresentation()) {
|
|
470
|
+
// If we're a form sheet we want to consume the gestures to prevent
|
|
471
|
+
// DimmingView's callback from triggering when clicking on the sheet itself.
|
|
472
|
+
true
|
|
473
|
+
} else {
|
|
474
|
+
super.onTouchEvent(event)
|
|
475
|
+
}
|
|
476
|
+
|
|
410
477
|
private fun notifyHeaderHeightChange(headerHeight: Int) {
|
|
411
478
|
val screenContext = context as ReactContext
|
|
412
479
|
val surfaceId = UIManagerHelper.getSurfaceId(screenContext)
|
|
@@ -17,7 +17,7 @@ class ScreenContentWrapper(
|
|
|
17
17
|
internal var delegate: OnLayoutCallback? = null
|
|
18
18
|
|
|
19
19
|
interface OnLayoutCallback {
|
|
20
|
-
fun
|
|
20
|
+
fun onContentWrapperLayout(
|
|
21
21
|
changed: Boolean,
|
|
22
22
|
left: Int,
|
|
23
23
|
top: Int,
|
|
@@ -33,6 +33,6 @@ class ScreenContentWrapper(
|
|
|
33
33
|
right: Int,
|
|
34
34
|
bottom: Int,
|
|
35
35
|
) {
|
|
36
|
-
delegate?.
|
|
36
|
+
delegate?.onContentWrapperLayout(changed, left, top, right, bottom)
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -15,7 +15,6 @@ import com.facebook.react.bridge.UiThreadUtil
|
|
|
15
15
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
16
16
|
import com.facebook.react.uimanager.events.Event
|
|
17
17
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
18
|
-
import com.swmansion.rnscreens.bottomsheet.DimmingFragment
|
|
19
18
|
import com.swmansion.rnscreens.events.HeaderBackButtonClickedEvent
|
|
20
19
|
import com.swmansion.rnscreens.events.ScreenAppearEvent
|
|
21
20
|
import com.swmansion.rnscreens.events.ScreenDisappearEvent
|
|
@@ -290,12 +289,7 @@ open class ScreenFragment :
|
|
|
290
289
|
// since we subscribe to parent's animation start/end and dispatch events in child from there
|
|
291
290
|
// check for `isTransitioning` should be enough since the child's animation should take only
|
|
292
291
|
// 20ms due to always being `StackAnimation.NONE` when nested stack being pushed
|
|
293
|
-
val parent =
|
|
294
|
-
if (parentFragment is DimmingFragment) {
|
|
295
|
-
parentFragment?.parentFragment
|
|
296
|
-
} else {
|
|
297
|
-
parentFragment
|
|
298
|
-
}
|
|
292
|
+
val parent = parentFragment
|
|
299
293
|
if (parent == null || (parent is ScreenFragment && !parent.isTransitioning)) {
|
|
300
294
|
// onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root
|
|
301
295
|
// view. We override an appropriate method of the StackFragment's
|
|
@@ -7,11 +7,10 @@ import android.view.View
|
|
|
7
7
|
import com.facebook.react.bridge.ReactContext
|
|
8
8
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
9
9
|
import com.swmansion.rnscreens.Screen.StackAnimation
|
|
10
|
-
import com.swmansion.rnscreens.bottomsheet.
|
|
10
|
+
import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
|
|
11
11
|
import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
|
|
12
12
|
import java.util.Collections
|
|
13
13
|
import kotlin.collections.ArrayList
|
|
14
|
-
import kotlin.collections.HashSet
|
|
15
14
|
|
|
16
15
|
class ScreenStack(
|
|
17
16
|
context: Context?,
|
|
@@ -50,7 +49,7 @@ class ScreenStack(
|
|
|
50
49
|
|
|
51
50
|
override fun adapt(screen: Screen): ScreenStackFragmentWrapper =
|
|
52
51
|
when (screen.stackPresentation) {
|
|
53
|
-
Screen.StackPresentation.FORM_SHEET ->
|
|
52
|
+
Screen.StackPresentation.FORM_SHEET -> ScreenStackFragment(screen)
|
|
54
53
|
else -> ScreenStackFragment(screen)
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -242,7 +241,6 @@ class ScreenStack(
|
|
|
242
241
|
}
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
|
-
|
|
246
244
|
// animation logic end
|
|
247
245
|
goingForward = shouldUseOpenAnimation
|
|
248
246
|
|
|
@@ -302,6 +300,12 @@ class ScreenStack(
|
|
|
302
300
|
}
|
|
303
301
|
}
|
|
304
302
|
} else if (newTop != null && !newTop.fragment.isAdded) {
|
|
303
|
+
if (!BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && newTop.screen.isSheetFitToContents()) {
|
|
304
|
+
// On old architecture the content wrapper might not have received its frame yet,
|
|
305
|
+
// which is required to determine height of the sheet after animation. Therefore
|
|
306
|
+
// we delay the transition and trigger it after views receive the layout.
|
|
307
|
+
newTop.fragment.postponeEnterTransition()
|
|
308
|
+
}
|
|
305
309
|
it.add(id, newTop.fragment)
|
|
306
310
|
}
|
|
307
311
|
topScreenWrapper = newTop as? ScreenStackFragmentWrapper
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
package com.swmansion.rnscreens
|
|
2
2
|
|
|
3
|
+
import android.animation.Animator
|
|
4
|
+
import android.animation.AnimatorSet
|
|
5
|
+
import android.animation.ValueAnimator
|
|
3
6
|
import android.annotation.SuppressLint
|
|
4
7
|
import android.content.Context
|
|
5
8
|
import android.graphics.Color
|
|
@@ -16,17 +19,18 @@ import android.view.WindowInsets
|
|
|
16
19
|
import android.view.WindowManager
|
|
17
20
|
import android.view.animation.Animation
|
|
18
21
|
import android.view.animation.AnimationSet
|
|
19
|
-
import android.view.animation.AnimationUtils
|
|
20
22
|
import android.view.animation.Transformation
|
|
21
23
|
import android.view.inputmethod.InputMethodManager
|
|
22
24
|
import android.widget.LinearLayout
|
|
23
25
|
import androidx.annotation.RequiresApi
|
|
24
26
|
import androidx.appcompat.widget.Toolbar
|
|
25
27
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
28
|
+
import androidx.core.animation.addListener
|
|
26
29
|
import androidx.core.view.WindowInsetsCompat
|
|
27
30
|
import com.facebook.react.uimanager.PixelUtil
|
|
28
31
|
import com.facebook.react.uimanager.PointerEvents
|
|
29
32
|
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
33
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
30
34
|
import com.google.android.material.appbar.AppBarLayout
|
|
31
35
|
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
|
|
32
36
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
@@ -34,13 +38,18 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCa
|
|
|
34
38
|
import com.google.android.material.shape.CornerFamily
|
|
35
39
|
import com.google.android.material.shape.MaterialShapeDrawable
|
|
36
40
|
import com.google.android.material.shape.ShapeAppearanceModel
|
|
41
|
+
import com.swmansion.rnscreens.bottomsheet.DimmingViewManager
|
|
42
|
+
import com.swmansion.rnscreens.bottomsheet.SheetDelegate
|
|
37
43
|
import com.swmansion.rnscreens.bottomsheet.SheetUtils
|
|
38
44
|
import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
|
|
39
45
|
import com.swmansion.rnscreens.bottomsheet.useSingleDetent
|
|
40
46
|
import com.swmansion.rnscreens.bottomsheet.useThreeDetents
|
|
41
47
|
import com.swmansion.rnscreens.bottomsheet.useTwoDetents
|
|
42
48
|
import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
|
|
49
|
+
import com.swmansion.rnscreens.events.ScreenDismissedEvent
|
|
50
|
+
import com.swmansion.rnscreens.events.ScreenEventDelegate
|
|
43
51
|
import com.swmansion.rnscreens.ext.recycle
|
|
52
|
+
import com.swmansion.rnscreens.transition.ExternalBoundaryValuesEvaluator
|
|
44
53
|
import com.swmansion.rnscreens.utils.DeviceUtils
|
|
45
54
|
|
|
46
55
|
sealed class KeyboardState
|
|
@@ -76,6 +85,13 @@ class ScreenStackFragment :
|
|
|
76
85
|
return container
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
private val dimmingDelegate =
|
|
89
|
+
lazy(LazyThreadSafetyMode.NONE) {
|
|
90
|
+
DimmingViewManager(screen.reactContext, screen)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private var sheetDelegate: SheetDelegate? = null
|
|
94
|
+
|
|
79
95
|
@SuppressLint("ValidFragment")
|
|
80
96
|
constructor(screenView: Screen) : super(screenView)
|
|
81
97
|
|
|
@@ -131,7 +147,12 @@ class ScreenStackFragment :
|
|
|
131
147
|
|
|
132
148
|
override fun onViewAnimationEnd() {
|
|
133
149
|
super.onViewAnimationEnd()
|
|
150
|
+
|
|
151
|
+
// Rely on guards inside the callee to detect whether this was indeed appear transition.
|
|
134
152
|
notifyViewAppearTransitionEnd()
|
|
153
|
+
|
|
154
|
+
// Rely on guards inside the callee to detect whether this was indeed removal transition.
|
|
155
|
+
screen.endRemovalTransition()
|
|
135
156
|
}
|
|
136
157
|
|
|
137
158
|
private fun notifyViewAppearTransitionEnd() {
|
|
@@ -176,7 +197,7 @@ class ScreenStackFragment :
|
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
179
|
-
|
|
200
|
+
dismissSelf()
|
|
180
201
|
}
|
|
181
202
|
}
|
|
182
203
|
|
|
@@ -186,18 +207,17 @@ class ScreenStackFragment :
|
|
|
186
207
|
) = Unit
|
|
187
208
|
}
|
|
188
209
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
AnimationUtils.loadAnimation(context, R.anim.rns_slide_out_to_bottom)
|
|
210
|
+
/**
|
|
211
|
+
* Currently this method dispatches event to JS where state is recomputed and fragment
|
|
212
|
+
* gets removed in the result of incoming state update.
|
|
213
|
+
*/
|
|
214
|
+
internal fun dismissSelf() {
|
|
215
|
+
if (!this.isRemoving || !this.isDetached) {
|
|
216
|
+
val reactContext = screen.reactContext
|
|
217
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
218
|
+
UIManagerHelper
|
|
219
|
+
.getEventDispatcherForReactTag(reactContext, screen.id)
|
|
220
|
+
?.dispatchEvent(ScreenDismissedEvent(surfaceId, screen.id))
|
|
201
221
|
}
|
|
202
222
|
}
|
|
203
223
|
|
|
@@ -205,6 +225,10 @@ class ScreenStackFragment :
|
|
|
205
225
|
screen.onSheetCornerRadiusChange()
|
|
206
226
|
}
|
|
207
227
|
|
|
228
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
229
|
+
super.onCreate(savedInstanceState)
|
|
230
|
+
}
|
|
231
|
+
|
|
208
232
|
override fun onCreateView(
|
|
209
233
|
inflater: LayoutInflater,
|
|
210
234
|
container: ViewGroup?,
|
|
@@ -262,6 +286,90 @@ class ScreenStackFragment :
|
|
|
262
286
|
return coordinatorLayout
|
|
263
287
|
}
|
|
264
288
|
|
|
289
|
+
override fun onViewCreated(
|
|
290
|
+
view: View,
|
|
291
|
+
savedInstanceState: Bundle?,
|
|
292
|
+
) {
|
|
293
|
+
super.onViewCreated(view, savedInstanceState)
|
|
294
|
+
|
|
295
|
+
if (!screen.usesFormSheetPresentation()) {
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
sheetDelegate = SheetDelegate(screen)
|
|
300
|
+
|
|
301
|
+
assert(view == coordinatorLayout)
|
|
302
|
+
dimmingDelegate.value.onViewHierarchyCreated(screen, coordinatorLayout)
|
|
303
|
+
dimmingDelegate.value.onBehaviourAttached(screen, screen.sheetBehavior!!)
|
|
304
|
+
|
|
305
|
+
val container = screen.container!!
|
|
306
|
+
coordinatorLayout.measure(
|
|
307
|
+
View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
|
|
308
|
+
View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY),
|
|
309
|
+
)
|
|
310
|
+
coordinatorLayout.layout(0, 0, container.width, container.height)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
override fun onCreateAnimation(
|
|
314
|
+
transit: Int,
|
|
315
|
+
enter: Boolean,
|
|
316
|
+
nextAnim: Int,
|
|
317
|
+
): Animation? {
|
|
318
|
+
// Ensure onCreateAnimator is called
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
override fun onCreateAnimator(
|
|
323
|
+
transit: Int,
|
|
324
|
+
enter: Boolean,
|
|
325
|
+
nextAnim: Int,
|
|
326
|
+
): Animator? {
|
|
327
|
+
if (!screen.usesFormSheetPresentation()) {
|
|
328
|
+
// Use animation defined while defining transaction in screen stack
|
|
329
|
+
return null
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
val animatorSet = AnimatorSet()
|
|
333
|
+
|
|
334
|
+
if (enter) {
|
|
335
|
+
val alphaAnimator =
|
|
336
|
+
ValueAnimator.ofFloat(0f, dimmingDelegate.value.maxAlpha).apply {
|
|
337
|
+
addUpdateListener { anim ->
|
|
338
|
+
val animatedValue = anim.animatedValue as? Float
|
|
339
|
+
animatedValue?.let { dimmingDelegate.value.dimmingView.alpha = it }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
val startValueCallback = { initialStartValue: Number? -> screen.height.toFloat() }
|
|
343
|
+
val evaluator = ExternalBoundaryValuesEvaluator(startValueCallback, { 0f })
|
|
344
|
+
val slideAnimator =
|
|
345
|
+
ValueAnimator.ofObject(evaluator, screen.height.toFloat(), 0f).apply {
|
|
346
|
+
addUpdateListener { anim ->
|
|
347
|
+
val animatedValue = anim.animatedValue as? Float
|
|
348
|
+
animatedValue?.let { screen.translationY = it }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
animatorSet.play(alphaAnimator).with(slideAnimator)
|
|
352
|
+
} else {
|
|
353
|
+
val alphaAnimator =
|
|
354
|
+
ValueAnimator.ofFloat(dimmingDelegate.value.dimmingView.alpha, 0f).apply {
|
|
355
|
+
addUpdateListener { anim ->
|
|
356
|
+
val animatedValue = anim.animatedValue as? Float
|
|
357
|
+
animatedValue?.let { dimmingDelegate.value.dimmingView.alpha = it }
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
val slideAnimator =
|
|
361
|
+
ValueAnimator.ofFloat(0f, (coordinatorLayout.bottom - screen.top).toFloat()).apply {
|
|
362
|
+
addUpdateListener { anim ->
|
|
363
|
+
val animatedValue = anim.animatedValue as? Float
|
|
364
|
+
animatedValue?.let { screen.translationY = it }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
animatorSet.play(alphaAnimator).with(slideAnimator)
|
|
368
|
+
}
|
|
369
|
+
animatorSet.addListener(ScreenEventDelegate(this))
|
|
370
|
+
return animatorSet
|
|
371
|
+
}
|
|
372
|
+
|
|
265
373
|
/**
|
|
266
374
|
* This method might return slightly different values depending on code path,
|
|
267
375
|
* but during testing I've found this effect negligible. For practical purposes
|
|
@@ -348,7 +456,10 @@ class ScreenStackFragment :
|
|
|
348
456
|
behavior.apply {
|
|
349
457
|
val height =
|
|
350
458
|
if (screen.isSheetFitToContents()) {
|
|
351
|
-
screen.contentWrapper
|
|
459
|
+
screen.contentWrapper
|
|
460
|
+
.get()
|
|
461
|
+
?.height
|
|
462
|
+
.takeIf { screen.contentWrapper.get()?.isLaidOut == true }
|
|
352
463
|
} else {
|
|
353
464
|
(screen.sheetDetents.first() * containerHeight).toInt()
|
|
354
465
|
}
|
|
@@ -456,10 +567,12 @@ class ScreenStackFragment :
|
|
|
456
567
|
}
|
|
457
568
|
}
|
|
458
569
|
|
|
570
|
+
private fun createBottomSheetBehaviour(): BottomSheetBehavior<Screen> = BottomSheetBehavior<Screen>()
|
|
571
|
+
|
|
459
572
|
// In general it would be great to create BottomSheetBehaviour only via this method as it runs some
|
|
460
573
|
// side effects.
|
|
461
|
-
|
|
462
|
-
configureBottomSheetBehaviour(
|
|
574
|
+
private fun createAndConfigureBottomSheetBehaviour(): BottomSheetBehavior<Screen> =
|
|
575
|
+
configureBottomSheetBehaviour(createBottomSheetBehaviour())
|
|
463
576
|
|
|
464
577
|
private fun attachShapeToScreen(screen: Screen) {
|
|
465
578
|
val cornerSize = PixelUtil.toPixelFromDIP(screen.sheetCornerRadius)
|
|
@@ -11,7 +11,7 @@ import com.facebook.react.uimanager.ReactPointerEventsView
|
|
|
11
11
|
import com.swmansion.rnscreens.ext.equalWithRespectToEps
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Serves as dimming view that can be used as background for some view that not fully
|
|
14
|
+
* Serves as dimming view that can be used as background for some view that does not fully fill
|
|
15
15
|
* the viewport.
|
|
16
16
|
*
|
|
17
17
|
* This dimming view has one more additional feature: it blocks gestures if its alpha > 0.
|
|
@@ -40,6 +40,8 @@ class DimmingView(
|
|
|
40
40
|
b: Int,
|
|
41
41
|
) = Unit
|
|
42
42
|
|
|
43
|
+
// We do not want to have any action defined here. We just want listeners notified that the click happened.
|
|
44
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
43
45
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
|
44
46
|
if (blockGestures) {
|
|
45
47
|
callOnClick()
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.bottomsheet
|
|
2
|
+
|
|
3
|
+
import android.animation.ValueAnimator
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
8
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
|
9
|
+
import com.swmansion.rnscreens.Screen
|
|
10
|
+
import com.swmansion.rnscreens.ScreenStackFragment
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Provides bulk of necessary logic for the dimming view accompanying the formSheet.
|
|
14
|
+
*/
|
|
15
|
+
class DimmingViewManager(
|
|
16
|
+
val reactContext: ThemedReactContext,
|
|
17
|
+
screen: Screen,
|
|
18
|
+
) {
|
|
19
|
+
internal val dimmingView: DimmingView = createDimmingView(screen)
|
|
20
|
+
internal val maxAlpha: Float = 0.3f
|
|
21
|
+
private var dimmingViewCallback: BottomSheetCallback? = null
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Should be called when hosting fragment has its view hierarchy created.
|
|
25
|
+
*/
|
|
26
|
+
fun onViewHierarchyCreated(
|
|
27
|
+
screen: Screen,
|
|
28
|
+
root: ViewGroup,
|
|
29
|
+
) {
|
|
30
|
+
root.addView(dimmingView, 0)
|
|
31
|
+
if (screen.sheetInitialDetentIndex <= screen.sheetLargestUndimmedDetentIndex) {
|
|
32
|
+
dimmingView.alpha = 0.0f
|
|
33
|
+
} else {
|
|
34
|
+
dimmingView.alpha = maxAlpha
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Should be called after screen of hosting fragment has its behaviour attached.
|
|
40
|
+
*/
|
|
41
|
+
fun onBehaviourAttached(
|
|
42
|
+
screen: Screen,
|
|
43
|
+
behavior: BottomSheetBehavior<Screen>,
|
|
44
|
+
) {
|
|
45
|
+
behavior.addBottomSheetCallback(requireBottomSheetCallback(screen))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* This bottom sheet callback is responsible for animating alpha of the dimming view.
|
|
50
|
+
*/
|
|
51
|
+
private class AnimateDimmingViewCallback(
|
|
52
|
+
val screen: Screen,
|
|
53
|
+
val viewToAnimate: View,
|
|
54
|
+
val maxAlpha: Float,
|
|
55
|
+
) : BottomSheetCallback() {
|
|
56
|
+
// largest *slide offset* that is yet undimmed
|
|
57
|
+
private var largestUndimmedOffset: Float =
|
|
58
|
+
computeOffsetFromDetentIndex(screen.sheetLargestUndimmedDetentIndex)
|
|
59
|
+
|
|
60
|
+
// first *slide offset* that should be fully dimmed
|
|
61
|
+
private var firstDimmedOffset: Float =
|
|
62
|
+
computeOffsetFromDetentIndex(
|
|
63
|
+
(screen.sheetLargestUndimmedDetentIndex + 1).coerceIn(
|
|
64
|
+
0,
|
|
65
|
+
screen.sheetDetents.count() - 1,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// interval that we interpolate the alpha value over
|
|
70
|
+
private var intervalLength = firstDimmedOffset - largestUndimmedOffset
|
|
71
|
+
private val animator =
|
|
72
|
+
ValueAnimator.ofFloat(0F, maxAlpha).apply {
|
|
73
|
+
duration = 1 // Driven manually
|
|
74
|
+
addUpdateListener {
|
|
75
|
+
viewToAnimate.alpha = it.animatedValue as Float
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun onStateChanged(
|
|
80
|
+
bottomSheet: View,
|
|
81
|
+
newState: Int,
|
|
82
|
+
) {
|
|
83
|
+
if (newState == BottomSheetBehavior.STATE_DRAGGING || newState == BottomSheetBehavior.STATE_SETTLING) {
|
|
84
|
+
largestUndimmedOffset =
|
|
85
|
+
computeOffsetFromDetentIndex(screen.sheetLargestUndimmedDetentIndex)
|
|
86
|
+
firstDimmedOffset =
|
|
87
|
+
computeOffsetFromDetentIndex(
|
|
88
|
+
(screen.sheetLargestUndimmedDetentIndex + 1).coerceIn(
|
|
89
|
+
0,
|
|
90
|
+
screen.sheetDetents.count() - 1,
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
assert(firstDimmedOffset >= largestUndimmedOffset) {
|
|
94
|
+
"[RNScreens] Invariant violation: firstDimmedOffset ($firstDimmedOffset) < largestDimmedOffset ($largestUndimmedOffset)"
|
|
95
|
+
}
|
|
96
|
+
intervalLength = firstDimmedOffset - largestUndimmedOffset
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override fun onSlide(
|
|
101
|
+
bottomSheet: View,
|
|
102
|
+
slideOffset: Float,
|
|
103
|
+
) {
|
|
104
|
+
if (largestUndimmedOffset < slideOffset && slideOffset < firstDimmedOffset) {
|
|
105
|
+
val fraction = (slideOffset - largestUndimmedOffset) / intervalLength
|
|
106
|
+
animator.setCurrentFraction(fraction)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* This method does compute slide offset (see [BottomSheetCallback.onSlide] docs) for detent
|
|
112
|
+
* at given index in the detents array.
|
|
113
|
+
*/
|
|
114
|
+
private fun computeOffsetFromDetentIndex(index: Int): Float =
|
|
115
|
+
when (screen.sheetDetents.size) {
|
|
116
|
+
1 -> // Only 1 detent present in detents array
|
|
117
|
+
when (index) {
|
|
118
|
+
-1 -> -1F // hidden
|
|
119
|
+
0 -> 1F // fully expanded
|
|
120
|
+
else -> -1F // unexpected, default
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
2 ->
|
|
124
|
+
when (index) {
|
|
125
|
+
-1 -> -1F // hidden
|
|
126
|
+
0 -> 0F // collapsed
|
|
127
|
+
1 -> 1F // expanded
|
|
128
|
+
else -> -1F
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
3 ->
|
|
132
|
+
when (index) {
|
|
133
|
+
-1 -> -1F // hidden
|
|
134
|
+
0 -> 0F // collapsed
|
|
135
|
+
1 -> screen.sheetBehavior!!.halfExpandedRatio // half
|
|
136
|
+
2 -> 1F // expanded
|
|
137
|
+
else -> -1F
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
else -> -1F
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private fun createDimmingView(screen: Screen): DimmingView =
|
|
145
|
+
DimmingView(reactContext, maxAlpha).apply {
|
|
146
|
+
// These do not guarantee fullscreen width & height, TODO: find a way to guarantee that
|
|
147
|
+
layoutParams =
|
|
148
|
+
ViewGroup.LayoutParams(
|
|
149
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
150
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
151
|
+
)
|
|
152
|
+
setOnClickListener {
|
|
153
|
+
if (screen.sheetClosesOnTouchOutside) {
|
|
154
|
+
(screen.fragment as ScreenStackFragment).dismissSelf()
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private fun requireBottomSheetCallback(screen: Screen): BottomSheetCallback {
|
|
160
|
+
if (dimmingViewCallback == null) {
|
|
161
|
+
dimmingViewCallback = AnimateDimmingViewCallback(screen, dimmingView, maxAlpha)
|
|
162
|
+
}
|
|
163
|
+
return dimmingViewCallback!!
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.bottomsheet
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import androidx.core.graphics.Insets
|
|
5
|
+
import androidx.core.view.OnApplyWindowInsetsListener
|
|
6
|
+
import androidx.core.view.WindowInsetsCompat
|
|
7
|
+
import androidx.lifecycle.Lifecycle
|
|
8
|
+
import androidx.lifecycle.LifecycleEventObserver
|
|
9
|
+
import androidx.lifecycle.LifecycleOwner
|
|
10
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
11
|
+
import com.swmansion.rnscreens.InsetsObserverProxy
|
|
12
|
+
import com.swmansion.rnscreens.KeyboardDidHide
|
|
13
|
+
import com.swmansion.rnscreens.KeyboardNotVisible
|
|
14
|
+
import com.swmansion.rnscreens.KeyboardState
|
|
15
|
+
import com.swmansion.rnscreens.KeyboardVisible
|
|
16
|
+
import com.swmansion.rnscreens.Screen
|
|
17
|
+
import com.swmansion.rnscreens.ScreenStackFragment
|
|
18
|
+
|
|
19
|
+
class SheetDelegate(
|
|
20
|
+
val screen: Screen,
|
|
21
|
+
) : LifecycleEventObserver,
|
|
22
|
+
OnApplyWindowInsetsListener {
|
|
23
|
+
private var isKeyboardVisible: Boolean = false
|
|
24
|
+
private var keyboardState: KeyboardState = KeyboardNotVisible
|
|
25
|
+
|
|
26
|
+
private val sheetBehavior: BottomSheetBehavior<Screen>?
|
|
27
|
+
get() = screen.sheetBehavior
|
|
28
|
+
|
|
29
|
+
private val stackFragment: ScreenStackFragment
|
|
30
|
+
get() = screen.fragment as ScreenStackFragment
|
|
31
|
+
|
|
32
|
+
private fun requireDecorView(): View =
|
|
33
|
+
checkNotNull(screen.reactContext.currentActivity) { "[RNScreens] Attempt to access activity on detached context" }
|
|
34
|
+
.window.decorView
|
|
35
|
+
|
|
36
|
+
init {
|
|
37
|
+
assert(screen.fragment is ScreenStackFragment) { "[RNScreens] Sheets are supported only in native stack" }
|
|
38
|
+
screen.fragment!!.lifecycle.addObserver(this)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// LifecycleEventObserver
|
|
42
|
+
override fun onStateChanged(
|
|
43
|
+
source: LifecycleOwner,
|
|
44
|
+
event: Lifecycle.Event,
|
|
45
|
+
) {
|
|
46
|
+
when (event) {
|
|
47
|
+
Lifecycle.Event.ON_START -> handleHostFragmentOnStart()
|
|
48
|
+
Lifecycle.Event.ON_RESUME -> handleHostFragmentOnResume()
|
|
49
|
+
Lifecycle.Event.ON_PAUSE -> handleHostFragmentOnPause()
|
|
50
|
+
else -> Unit
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private fun handleHostFragmentOnStart() {
|
|
55
|
+
InsetsObserverProxy.registerOnView(requireDecorView())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private fun handleHostFragmentOnResume() {
|
|
59
|
+
InsetsObserverProxy.addOnApplyWindowInsetsListener(this)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private fun handleHostFragmentOnPause() {
|
|
63
|
+
InsetsObserverProxy.removeOnApplyWindowInsetsListener(this)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// This is listener function, not the view's.
|
|
67
|
+
override fun onApplyWindowInsets(
|
|
68
|
+
v: View,
|
|
69
|
+
insets: WindowInsetsCompat,
|
|
70
|
+
): WindowInsetsCompat {
|
|
71
|
+
val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
|
72
|
+
val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime())
|
|
73
|
+
|
|
74
|
+
if (isImeVisible) {
|
|
75
|
+
isKeyboardVisible = true
|
|
76
|
+
keyboardState = KeyboardVisible(imeInset.bottom)
|
|
77
|
+
sheetBehavior?.let {
|
|
78
|
+
stackFragment.configureBottomSheetBehaviour(it, keyboardState)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
|
82
|
+
return WindowInsetsCompat
|
|
83
|
+
.Builder(insets)
|
|
84
|
+
.setInsets(
|
|
85
|
+
WindowInsetsCompat.Type.navigationBars(),
|
|
86
|
+
Insets.of(
|
|
87
|
+
prevInsets.left,
|
|
88
|
+
prevInsets.top,
|
|
89
|
+
prevInsets.right,
|
|
90
|
+
0,
|
|
91
|
+
),
|
|
92
|
+
).build()
|
|
93
|
+
} else {
|
|
94
|
+
sheetBehavior?.let {
|
|
95
|
+
if (isKeyboardVisible) {
|
|
96
|
+
stackFragment.configureBottomSheetBehaviour(it, KeyboardDidHide)
|
|
97
|
+
} else if (keyboardState != KeyboardNotVisible) {
|
|
98
|
+
stackFragment.configureBottomSheetBehaviour(it, KeyboardNotVisible)
|
|
99
|
+
} else {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
keyboardState = KeyboardNotVisible
|
|
104
|
+
isKeyboardVisible = false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
|
108
|
+
return WindowInsetsCompat
|
|
109
|
+
.Builder(insets)
|
|
110
|
+
.setInsets(
|
|
111
|
+
WindowInsetsCompat.Type.navigationBars(),
|
|
112
|
+
Insets.of(prevInsets.left, prevInsets.top, prevInsets.right, 0),
|
|
113
|
+
).build()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
companion object {
|
|
117
|
+
const val TAG = "SheetDelegate"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.events
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator
|
|
4
|
+
import com.swmansion.rnscreens.ScreenFragmentWrapper
|
|
5
|
+
|
|
6
|
+
class ScreenEventDelegate(
|
|
7
|
+
private val wrapper: ScreenFragmentWrapper,
|
|
8
|
+
) : Animator.AnimatorListener {
|
|
9
|
+
private var currentState: LifecycleState = LifecycleState.INITIALIZED
|
|
10
|
+
|
|
11
|
+
private fun progressState() {
|
|
12
|
+
currentState =
|
|
13
|
+
when (currentState) {
|
|
14
|
+
LifecycleState.INITIALIZED -> LifecycleState.START_DISPATCHED
|
|
15
|
+
LifecycleState.START_DISPATCHED -> LifecycleState.END_DISPATCHED
|
|
16
|
+
LifecycleState.END_DISPATCHED -> LifecycleState.END_DISPATCHED
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun onAnimationStart(animation: Animator) {
|
|
21
|
+
if (currentState === LifecycleState.INITIALIZED) {
|
|
22
|
+
progressState()
|
|
23
|
+
wrapper.onViewAnimationStart()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
28
|
+
if (currentState === LifecycleState.START_DISPATCHED) {
|
|
29
|
+
progressState()
|
|
30
|
+
animation.removeListener(this)
|
|
31
|
+
wrapper.onViewAnimationEnd()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun onAnimationCancel(animation: Animator) = Unit
|
|
36
|
+
|
|
37
|
+
override fun onAnimationRepeat(animation: Animator) = Unit
|
|
38
|
+
|
|
39
|
+
private enum class LifecycleState {
|
|
40
|
+
INITIALIZED,
|
|
41
|
+
START_DISPATCHED,
|
|
42
|
+
END_DISPATCHED,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
companion object {
|
|
46
|
+
const val TAG = "ScreenEventDelegate"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/android/src/main/java/com/swmansion/rnscreens/transition/ExternalBoundaryValuesEvaluator.kt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.transition
|
|
2
|
+
|
|
3
|
+
import android.animation.FloatEvaluator
|
|
4
|
+
|
|
5
|
+
typealias BoundaryValueProviderFn = (Number?) -> Float?
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Float type evaluator that uses boundary values provided by callbacks passed as arguments and does
|
|
9
|
+
* not use boundary values used during value animator construction. This allows to defer computation
|
|
10
|
+
* of animator boundary values to the moment when animation starts.
|
|
11
|
+
*/
|
|
12
|
+
class ExternalBoundaryValuesEvaluator(val startValueProvider: BoundaryValueProviderFn, val endValueProvider: BoundaryValueProviderFn) : FloatEvaluator() {
|
|
13
|
+
var startValueCache: Number? = null
|
|
14
|
+
var endValueCache: Number? = null
|
|
15
|
+
|
|
16
|
+
private fun getStartValue(startValue: Number?): Number? {
|
|
17
|
+
if (startValueCache == null) {
|
|
18
|
+
startValueCache = startValueProvider(startValue)
|
|
19
|
+
}
|
|
20
|
+
return startValueCache
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private fun getEndValue(endValue: Number?): Number? {
|
|
24
|
+
if (endValueCache == null) {
|
|
25
|
+
endValueCache = endValueProvider(endValue)
|
|
26
|
+
}
|
|
27
|
+
return endValueCache
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun evaluate(fraction: Float, startValue: Number?, endValue: Number?): Float? {
|
|
31
|
+
val realStartValue = getStartValue(startValue)
|
|
32
|
+
val realEndValue = getEndValue(endValue)
|
|
33
|
+
if (realStartValue == null || realEndValue == null) {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
return super.evaluate(fraction, realStartValue, realEndValue)
|
|
37
|
+
}
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-screens",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0-beta.1",
|
|
4
4
|
"description": "Native navigation primitives for your React Native app.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"submodules": "git submodule update --init --recursive && (cd react-navigation && yarn && yarn build && cd ../)",
|
|
@@ -1,493 +0,0 @@
|
|
|
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.NativeDismissalObserver
|
|
31
|
-
import com.swmansion.rnscreens.R
|
|
32
|
-
import com.swmansion.rnscreens.Screen
|
|
33
|
-
import com.swmansion.rnscreens.ScreenContainer
|
|
34
|
-
import com.swmansion.rnscreens.ScreenFragment
|
|
35
|
-
import com.swmansion.rnscreens.ScreenFragmentWrapper
|
|
36
|
-
import com.swmansion.rnscreens.ScreenStack
|
|
37
|
-
import com.swmansion.rnscreens.ScreenStackFragment
|
|
38
|
-
import com.swmansion.rnscreens.ScreenStackFragmentWrapper
|
|
39
|
-
import com.swmansion.rnscreens.events.ScreenDismissedEvent
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* This fragment aims to provide dimming view functionality behind the nested fragment.
|
|
43
|
-
* Useful when nested fragment is transparent / uses some kind of non-fullscreen presentation,
|
|
44
|
-
* such as `formSheet`.
|
|
45
|
-
*/
|
|
46
|
-
class DimmingFragment(
|
|
47
|
-
val nestedFragment: ScreenFragmentWrapper,
|
|
48
|
-
) : Fragment(),
|
|
49
|
-
LifecycleEventObserver,
|
|
50
|
-
ScreenStackFragmentWrapper,
|
|
51
|
-
Animation.AnimationListener,
|
|
52
|
-
OnApplyWindowInsetsListener,
|
|
53
|
-
NativeDismissalObserver {
|
|
54
|
-
private lateinit var dimmingView: DimmingView
|
|
55
|
-
private lateinit var containerView: GestureTransparentViewGroup
|
|
56
|
-
|
|
57
|
-
private val maxAlpha: Float = 0.15F
|
|
58
|
-
|
|
59
|
-
private var isKeyboardVisible: Boolean = false
|
|
60
|
-
private var keyboardState: KeyboardState = KeyboardNotVisible
|
|
61
|
-
|
|
62
|
-
private var dimmingViewCallback: BottomSheetCallback? = null
|
|
63
|
-
|
|
64
|
-
private val container: ScreenStack?
|
|
65
|
-
get() = screen.container as? ScreenStack
|
|
66
|
-
|
|
67
|
-
private val insetsProxy = InsetsObserverProxy
|
|
68
|
-
|
|
69
|
-
init {
|
|
70
|
-
assert(
|
|
71
|
-
nestedFragment.fragment is ScreenStackFragment,
|
|
72
|
-
) { "[RNScreens] Dimming fragment is intended for use only with ScreenStackFragment" }
|
|
73
|
-
val fragment = nestedFragment.fragment as ScreenStackFragment
|
|
74
|
-
|
|
75
|
-
// We register for our child lifecycle as we want to know when it starts, because bottom sheet
|
|
76
|
-
// behavior is attached only then & we want to attach our own callbacks to it.
|
|
77
|
-
fragment.lifecycle.addObserver(this)
|
|
78
|
-
fragment.nativeDismissalObserver = this
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* This bottom sheet callback is responsible for animating alpha of the dimming view.
|
|
83
|
-
*/
|
|
84
|
-
private class AnimateDimmingViewCallback(
|
|
85
|
-
val screen: Screen,
|
|
86
|
-
val viewToAnimate: View,
|
|
87
|
-
val maxAlpha: Float,
|
|
88
|
-
) : BottomSheetCallback() {
|
|
89
|
-
// largest *slide offset* that is yet undimmed
|
|
90
|
-
private var largestUndimmedOffset: Float =
|
|
91
|
-
computeOffsetFromDetentIndex(screen.sheetLargestUndimmedDetentIndex)
|
|
92
|
-
|
|
93
|
-
// first *slide offset* that should be fully dimmed
|
|
94
|
-
private var firstDimmedOffset: Float =
|
|
95
|
-
computeOffsetFromDetentIndex(
|
|
96
|
-
(screen.sheetLargestUndimmedDetentIndex + 1).coerceIn(
|
|
97
|
-
0,
|
|
98
|
-
screen.sheetDetents.count() - 1,
|
|
99
|
-
),
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
// interval that we interpolate the alpha value over
|
|
103
|
-
private var intervalLength = firstDimmedOffset - largestUndimmedOffset
|
|
104
|
-
private val animator =
|
|
105
|
-
ValueAnimator.ofFloat(0F, maxAlpha).apply {
|
|
106
|
-
duration = 1 // Driven manually
|
|
107
|
-
addUpdateListener {
|
|
108
|
-
viewToAnimate.alpha = it.animatedValue as Float
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
override fun onStateChanged(
|
|
113
|
-
bottomSheet: View,
|
|
114
|
-
newState: Int,
|
|
115
|
-
) {
|
|
116
|
-
if (newState == BottomSheetBehavior.STATE_DRAGGING || newState == BottomSheetBehavior.STATE_SETTLING) {
|
|
117
|
-
largestUndimmedOffset =
|
|
118
|
-
computeOffsetFromDetentIndex(screen.sheetLargestUndimmedDetentIndex)
|
|
119
|
-
firstDimmedOffset =
|
|
120
|
-
computeOffsetFromDetentIndex(
|
|
121
|
-
(screen.sheetLargestUndimmedDetentIndex + 1).coerceIn(
|
|
122
|
-
0,
|
|
123
|
-
screen.sheetDetents.count() - 1,
|
|
124
|
-
),
|
|
125
|
-
)
|
|
126
|
-
assert(firstDimmedOffset >= largestUndimmedOffset) {
|
|
127
|
-
"[RNScreens] Invariant violation: firstDimmedOffset ($firstDimmedOffset) < largestDimmedOffset ($largestUndimmedOffset)"
|
|
128
|
-
}
|
|
129
|
-
intervalLength = firstDimmedOffset - largestUndimmedOffset
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
override fun onSlide(
|
|
134
|
-
bottomSheet: View,
|
|
135
|
-
slideOffset: Float,
|
|
136
|
-
) {
|
|
137
|
-
if (largestUndimmedOffset < slideOffset && slideOffset < firstDimmedOffset) {
|
|
138
|
-
val fraction = (slideOffset - largestUndimmedOffset) / intervalLength
|
|
139
|
-
animator.setCurrentFraction(fraction)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* This method does compute slide offset (see [BottomSheetCallback.onSlide] docs) for detent
|
|
145
|
-
* at given index in the detents array.
|
|
146
|
-
*/
|
|
147
|
-
private fun computeOffsetFromDetentIndex(index: Int): Float =
|
|
148
|
-
when (screen.sheetDetents.size) {
|
|
149
|
-
1 -> // Only 1 detent present in detents array
|
|
150
|
-
when (index) {
|
|
151
|
-
-1 -> -1F // hidden
|
|
152
|
-
0 -> 1F // fully expanded
|
|
153
|
-
else -> -1F // unexpected, default
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
2 ->
|
|
157
|
-
when (index) {
|
|
158
|
-
-1 -> -1F // hidden
|
|
159
|
-
0 -> 0F // collapsed
|
|
160
|
-
1 -> 1F // expanded
|
|
161
|
-
else -> -1F
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
3 ->
|
|
165
|
-
when (index) {
|
|
166
|
-
-1 -> -1F // hidden
|
|
167
|
-
0 -> 0F // collapsed
|
|
168
|
-
1 -> screen.sheetBehavior!!.halfExpandedRatio // half
|
|
169
|
-
2 -> 1F // expanded
|
|
170
|
-
else -> -1F
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
else -> -1F
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
override fun onCreateAnimation(
|
|
178
|
-
transit: Int,
|
|
179
|
-
enter: Boolean,
|
|
180
|
-
nextAnim: Int,
|
|
181
|
-
): Animation? =
|
|
182
|
-
// We want dimming view to have always fade animation in current usages.
|
|
183
|
-
AnimationUtils.loadAnimation(
|
|
184
|
-
context,
|
|
185
|
-
if (enter) R.anim.rns_fade_in else R.anim.rns_fade_out,
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
override fun onCreateView(
|
|
189
|
-
inflater: LayoutInflater,
|
|
190
|
-
container: ViewGroup?,
|
|
191
|
-
savedInstanceState: Bundle?,
|
|
192
|
-
): View {
|
|
193
|
-
initViewHierarchy()
|
|
194
|
-
return containerView
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
override fun onViewCreated(
|
|
198
|
-
view: View,
|
|
199
|
-
savedInstanceState: Bundle?,
|
|
200
|
-
) {
|
|
201
|
-
if (screen.sheetInitialDetentIndex <= screen.sheetLargestUndimmedDetentIndex) {
|
|
202
|
-
dimmingView.alpha = 0.0F
|
|
203
|
-
} else {
|
|
204
|
-
dimmingView.alpha = maxAlpha
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
override fun onStart() {
|
|
209
|
-
// This is the earliest we can access child fragment manager & present another fragment
|
|
210
|
-
super.onStart()
|
|
211
|
-
insetsProxy.registerOnView(requireDecorView())
|
|
212
|
-
presentNestedFragment()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
override fun onResume() {
|
|
216
|
-
insetsProxy.addOnApplyWindowInsetsListener(this)
|
|
217
|
-
super.onResume()
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
override fun onPause() {
|
|
221
|
-
super.onPause()
|
|
222
|
-
insetsProxy.removeOnApplyWindowInsetsListener(this)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
override fun onStateChanged(
|
|
226
|
-
source: LifecycleOwner,
|
|
227
|
-
event: Lifecycle.Event,
|
|
228
|
-
) {
|
|
229
|
-
when (event) {
|
|
230
|
-
Lifecycle.Event.ON_START -> {
|
|
231
|
-
nestedFragment.screen.sheetBehavior?.let {
|
|
232
|
-
dimmingViewCallback =
|
|
233
|
-
AnimateDimmingViewCallback(nestedFragment.screen, dimmingView, maxAlpha)
|
|
234
|
-
it.addBottomSheetCallback(dimmingViewCallback!!)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
else -> {}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private fun presentNestedFragment() {
|
|
243
|
-
childFragmentManager.commit(allowStateLoss = true) {
|
|
244
|
-
setReorderingAllowed(true)
|
|
245
|
-
add(requireView().id, nestedFragment.fragment, null)
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private fun cleanRegisteredCallbacks() {
|
|
250
|
-
dimmingViewCallback?.let {
|
|
251
|
-
nestedFragment.screen.sheetBehavior?.removeBottomSheetCallback(it)
|
|
252
|
-
}
|
|
253
|
-
dimmingView.setOnClickListener(null)
|
|
254
|
-
nestedFragment.fragment.lifecycle.removeObserver(this)
|
|
255
|
-
insetsProxy.removeOnApplyWindowInsetsListener(this)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private fun dismissSelf(emitDismissedEvent: Boolean = false) {
|
|
259
|
-
if (!this.isRemoving) {
|
|
260
|
-
if (emitDismissedEvent) {
|
|
261
|
-
val reactContext = nestedFragment.screen.reactContext
|
|
262
|
-
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
263
|
-
UIManagerHelper
|
|
264
|
-
.getEventDispatcherForReactTag(reactContext, screen.id)
|
|
265
|
-
?.dispatchEvent(ScreenDismissedEvent(surfaceId, screen.id))
|
|
266
|
-
}
|
|
267
|
-
cleanRegisteredCallbacks()
|
|
268
|
-
dismissFromContainer()
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private fun initViewHierarchy() {
|
|
273
|
-
initContainerView()
|
|
274
|
-
initDimmingView()
|
|
275
|
-
containerView.addView(dimmingView)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private fun initContainerView() {
|
|
279
|
-
containerView =
|
|
280
|
-
GestureTransparentViewGroup(requireContext()).apply {
|
|
281
|
-
// These do not guarantee fullscreen width & height, TODO: find a way to guarantee that
|
|
282
|
-
layoutParams =
|
|
283
|
-
ViewGroup.LayoutParams(
|
|
284
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
285
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
286
|
-
)
|
|
287
|
-
setBackgroundColor(Color.TRANSPARENT)
|
|
288
|
-
// This is purely native view, React does not know of it, thus there should be no conflict with ids.
|
|
289
|
-
id = View.generateViewId()
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private fun initDimmingView() {
|
|
294
|
-
dimmingView =
|
|
295
|
-
DimmingView(requireContext(), maxAlpha).apply {
|
|
296
|
-
// These do not guarantee fullscreen width & height, TODO: find a way to guarantee that
|
|
297
|
-
layoutParams =
|
|
298
|
-
ViewGroup.LayoutParams(
|
|
299
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
300
|
-
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
301
|
-
)
|
|
302
|
-
setOnClickListener {
|
|
303
|
-
if (screen.sheetClosesOnTouchOutside) {
|
|
304
|
-
dismissSelf(true)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private fun requireDecorView(): View =
|
|
311
|
-
checkNotNull(screen.reactContext.currentActivity) { "[RNScreens] Attempt to access activity on detached context" }
|
|
312
|
-
.window.decorView
|
|
313
|
-
|
|
314
|
-
// TODO: Move these methods related to toolbar to separate interface
|
|
315
|
-
override fun removeToolbar() = Unit
|
|
316
|
-
|
|
317
|
-
override fun setToolbar(toolbar: Toolbar) = Unit
|
|
318
|
-
|
|
319
|
-
override fun setToolbarShadowHidden(hidden: Boolean) = Unit
|
|
320
|
-
|
|
321
|
-
override fun setToolbarTranslucent(translucent: Boolean) = Unit
|
|
322
|
-
|
|
323
|
-
// Dimming view should never be bottom-most fragment
|
|
324
|
-
override fun canNavigateBack(): Boolean = true
|
|
325
|
-
|
|
326
|
-
override fun dismissFromContainer() {
|
|
327
|
-
container?.dismiss(this)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
override var screen: Screen
|
|
331
|
-
get() = nestedFragment.screen
|
|
332
|
-
set(value) {
|
|
333
|
-
nestedFragment.screen = value
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
override val childScreenContainers: List<ScreenContainer> = nestedFragment.childScreenContainers
|
|
337
|
-
|
|
338
|
-
override fun addChildScreenContainer(container: ScreenContainer) {
|
|
339
|
-
nestedFragment.addChildScreenContainer(container)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
override fun removeChildScreenContainer(container: ScreenContainer) {
|
|
343
|
-
nestedFragment.removeChildScreenContainer(container)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
override fun onContainerUpdate() {
|
|
347
|
-
nestedFragment.onContainerUpdate()
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
override fun onViewAnimationStart() {
|
|
351
|
-
nestedFragment.onViewAnimationStart()
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
override fun onViewAnimationEnd() {
|
|
355
|
-
nestedFragment.onViewAnimationEnd()
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
override fun tryGetActivity(): Activity? = activity
|
|
359
|
-
|
|
360
|
-
override fun tryGetContext(): ReactContext? = context as? ReactContext?
|
|
361
|
-
|
|
362
|
-
override val fragment: Fragment
|
|
363
|
-
get() = this
|
|
364
|
-
|
|
365
|
-
override fun canDispatchLifecycleEvent(event: ScreenFragment.ScreenLifecycleEvent): Boolean {
|
|
366
|
-
TODO("Not yet implemented")
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
override fun updateLastEventDispatched(event: ScreenFragment.ScreenLifecycleEvent) {
|
|
370
|
-
TODO("Not yet implemented")
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
override fun dispatchLifecycleEvent(
|
|
374
|
-
event: ScreenFragment.ScreenLifecycleEvent,
|
|
375
|
-
fragmentWrapper: ScreenFragmentWrapper,
|
|
376
|
-
) {
|
|
377
|
-
TODO("Not yet implemented")
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
override fun dispatchLifecycleEventInChildContainers(event: ScreenFragment.ScreenLifecycleEvent) {
|
|
381
|
-
TODO("Not yet implemented")
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
override fun dispatchHeaderBackButtonClickedEvent() {
|
|
385
|
-
TODO("Not yet implemented")
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
override fun dispatchTransitionProgressEvent(
|
|
389
|
-
alpha: Float,
|
|
390
|
-
closing: Boolean,
|
|
391
|
-
) {
|
|
392
|
-
TODO("Not yet implemented")
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
override fun onAnimationStart(animation: Animation?) = Unit
|
|
396
|
-
|
|
397
|
-
override fun onAnimationEnd(animation: Animation?) {
|
|
398
|
-
dismissFromContainer()
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
override fun onAnimationRepeat(animation: Animation?) = Unit
|
|
402
|
-
|
|
403
|
-
companion object {
|
|
404
|
-
const val TAG = "DimmingFragment"
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// This is View.OnApplyWindowInsetsListener method, not view's own!
|
|
408
|
-
override fun onApplyWindowInsets(
|
|
409
|
-
v: View,
|
|
410
|
-
insets: WindowInsetsCompat,
|
|
411
|
-
): WindowInsetsCompat {
|
|
412
|
-
val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
|
413
|
-
val imeInset = insets.getInsets(WindowInsetsCompat.Type.ime())
|
|
414
|
-
|
|
415
|
-
if (isImeVisible) {
|
|
416
|
-
isKeyboardVisible = true
|
|
417
|
-
keyboardState = KeyboardVisible(imeInset.bottom)
|
|
418
|
-
screen.sheetBehavior?.let {
|
|
419
|
-
(nestedFragment as ScreenStackFragment).configureBottomSheetBehaviour(
|
|
420
|
-
it,
|
|
421
|
-
KeyboardVisible(imeInset.bottom),
|
|
422
|
-
)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (this.isRemoving) {
|
|
426
|
-
return insets
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
|
430
|
-
return WindowInsetsCompat
|
|
431
|
-
.Builder(insets)
|
|
432
|
-
.setInsets(
|
|
433
|
-
WindowInsetsCompat.Type.navigationBars(),
|
|
434
|
-
Insets.of(
|
|
435
|
-
prevInsets.left,
|
|
436
|
-
prevInsets.top,
|
|
437
|
-
prevInsets.right,
|
|
438
|
-
0,
|
|
439
|
-
),
|
|
440
|
-
).build()
|
|
441
|
-
} else {
|
|
442
|
-
if (this.isRemoving) {
|
|
443
|
-
val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
|
444
|
-
return WindowInsetsCompat
|
|
445
|
-
.Builder(insets)
|
|
446
|
-
.setInsets(
|
|
447
|
-
WindowInsetsCompat.Type.navigationBars(),
|
|
448
|
-
Insets.of(
|
|
449
|
-
prevInsets.left,
|
|
450
|
-
prevInsets.top,
|
|
451
|
-
prevInsets.right,
|
|
452
|
-
0,
|
|
453
|
-
),
|
|
454
|
-
).build()
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
screen.sheetBehavior?.let {
|
|
458
|
-
if (isKeyboardVisible) {
|
|
459
|
-
(nestedFragment as ScreenStackFragment).configureBottomSheetBehaviour(
|
|
460
|
-
it,
|
|
461
|
-
KeyboardDidHide,
|
|
462
|
-
)
|
|
463
|
-
} else if (keyboardState != KeyboardNotVisible) {
|
|
464
|
-
(nestedFragment as ScreenStackFragment).configureBottomSheetBehaviour(
|
|
465
|
-
it,
|
|
466
|
-
KeyboardNotVisible,
|
|
467
|
-
)
|
|
468
|
-
} else {
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
keyboardState = KeyboardNotVisible
|
|
473
|
-
isKeyboardVisible = false
|
|
474
|
-
|
|
475
|
-
val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
|
476
|
-
return WindowInsetsCompat
|
|
477
|
-
.Builder(insets)
|
|
478
|
-
.setInsets(
|
|
479
|
-
WindowInsetsCompat.Type.navigationBars(),
|
|
480
|
-
Insets.of(
|
|
481
|
-
prevInsets.left,
|
|
482
|
-
prevInsets.top,
|
|
483
|
-
prevInsets.right,
|
|
484
|
-
0,
|
|
485
|
-
),
|
|
486
|
-
).build()
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
override fun onNativeDismiss(dismissed: ScreenStackFragmentWrapper) {
|
|
491
|
-
dismissSelf(emitDismissedEvent = true)
|
|
492
|
-
}
|
|
493
|
-
}
|