@lodev09/react-native-true-sheet 4.0.0-beta.2 → 4.0.0-beta.21
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/README.md +25 -4
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +21 -110
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +116 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +124 -50
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +369 -163
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +59 -12
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +126 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +22 -9
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +84 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +110 -30
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +9 -3
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +3 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +4 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
- package/ios/TrueSheetContainerView.h +27 -7
- package/ios/TrueSheetContainerView.mm +31 -11
- package/ios/TrueSheetContentView.h +43 -15
- package/ios/TrueSheetContentView.mm +211 -189
- package/ios/TrueSheetFooterView.h +6 -0
- package/ios/TrueSheetFooterView.mm +93 -17
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +7 -16
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetView.mm +275 -56
- package/ios/TrueSheetViewController.h +31 -3
- package/ios/TrueSheetViewController.mm +393 -94
- package/ios/core/TrueSheetBlurView.h +0 -2
- package/ios/core/TrueSheetBlurView.mm +3 -36
- package/ios/core/TrueSheetDetentCalculator.h +7 -1
- package/ios/core/TrueSheetDetentCalculator.mm +27 -4
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetLifecycleEvents.h +2 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +175 -39
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +220 -243
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetBackground.js +15 -0
- package/lib/module/TrueSheetBackground.js.map +1 -0
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +20 -15
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/detentBackgrounds.js +21 -0
- package/lib/module/detentBackgrounds.js.map +1 -0
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -10
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +18 -2
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/createTrueSheetRouter.js +0 -9
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/base-types.js +2 -0
- package/lib/module/navigation/expo-router/base-types.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +10 -7
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/module/navigation/expo-router/types.js +4 -0
- package/lib/module/navigation/expo-router/types.js.map +1 -0
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/web/constants.js +1 -1
- package/lib/module/web/constants.js.map +1 -1
- package/lib/module/web/layout.js +168 -0
- package/lib/module/web/layout.js.map +1 -0
- package/lib/module/web/vaul/constants.js +9 -0
- package/lib/module/web/vaul/constants.js.map +1 -1
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/helpers.js +1 -0
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +47 -7
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/module/web/vaul/use-snap-points.js +21 -10
- package/lib/module/web/vaul/use-snap-points.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +6 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +157 -34
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
- package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
- package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
- package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +12 -7
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/base-types.d.ts +9 -0
- package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +2 -13
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +8 -6
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +3 -4
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +41 -5
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/web/constants.d.ts +1 -1
- package/lib/typescript/src/web/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/layout.d.ts +19 -0
- package/lib/typescript/src/web/layout.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +13 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts +2 -1
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
- package/package.json +17 -8
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +208 -48
- package/src/TrueSheet.types.ts +166 -35
- package/src/TrueSheet.web.tsx +272 -290
- package/src/TrueSheetBackground.tsx +17 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +25 -23
- package/src/detentBackgrounds.ts +24 -0
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +15 -10
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +17 -2
- package/src/mocks/navigationExpoRouter.ts +6 -5
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +5 -33
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/index.ts +28 -30
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +6 -7
- package/src/navigation/screen/useSheetScreenState.ts +2 -0
- package/src/navigation/types.ts +75 -22
- package/src/web/constants.ts +1 -1
- package/src/web/layout.ts +196 -0
- package/src/web/vaul/constants.ts +9 -0
- package/src/web/vaul/context.ts +1 -0
- package/src/web/vaul/helpers.ts +5 -0
- package/src/web/vaul/index.tsx +75 -5
- package/src/web/vaul/use-snap-points.ts +32 -15
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
|
+
import android.animation.ValueAnimator
|
|
3
4
|
import android.annotation.SuppressLint
|
|
4
5
|
import android.graphics.Canvas
|
|
5
6
|
import android.os.Build
|
|
@@ -34,9 +35,11 @@ import com.lodev09.truesheet.core.TrueSheetDetentCalculator
|
|
|
34
35
|
import com.lodev09.truesheet.core.TrueSheetDetentCalculatorDelegate
|
|
35
36
|
import com.lodev09.truesheet.core.TrueSheetDimView
|
|
36
37
|
import com.lodev09.truesheet.core.TrueSheetDimViewDelegate
|
|
38
|
+
import com.lodev09.truesheet.core.TrueSheetDismissThreshold
|
|
37
39
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
38
40
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
39
41
|
import com.lodev09.truesheet.core.TrueSheetStackManager
|
|
42
|
+
import com.lodev09.truesheet.utils.Insets
|
|
40
43
|
import com.lodev09.truesheet.utils.KeyboardUtils
|
|
41
44
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
42
45
|
import com.lodev09.truesheet.utils.TouchEventDeduper
|
|
@@ -54,6 +57,7 @@ interface TrueSheetViewControllerDelegate {
|
|
|
54
57
|
fun viewControllerDidPresent(index: Int, position: Float, detent: Float)
|
|
55
58
|
fun viewControllerWillDismiss()
|
|
56
59
|
fun viewControllerDidDismiss(parent: TrueSheetView?)
|
|
60
|
+
fun viewControllerDidAttemptDismiss()
|
|
57
61
|
fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float)
|
|
58
62
|
fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float)
|
|
59
63
|
fun viewControllerDidDragChange(index: Int, position: Float, detent: Float)
|
|
@@ -78,17 +82,19 @@ interface TrueSheetViewControllerDelegate {
|
|
|
78
82
|
* enabling touch pass-through to underlying views. Handles detent configuration, drag interactions,
|
|
79
83
|
* keyboard avoidance, dimmed backgrounds, back button, and lifecycle events for stacked sheets.
|
|
80
84
|
*/
|
|
81
|
-
enum class
|
|
82
|
-
|
|
85
|
+
enum class TrueSheetPlacement {
|
|
86
|
+
AUTOMATIC,
|
|
87
|
+
LEADING,
|
|
83
88
|
CENTER,
|
|
84
|
-
|
|
89
|
+
TRAILING;
|
|
85
90
|
|
|
86
91
|
companion object {
|
|
87
|
-
fun fromString(value: String?):
|
|
92
|
+
fun fromString(value: String?): TrueSheetPlacement =
|
|
88
93
|
when (value) {
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
|
|
94
|
+
"leading" -> LEADING
|
|
95
|
+
"center" -> CENTER
|
|
96
|
+
"trailing" -> TRAILING
|
|
97
|
+
else -> AUTOMATIC
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
100
|
}
|
|
@@ -118,10 +124,14 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
118
124
|
companion object {
|
|
119
125
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
120
126
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
121
|
-
private const val
|
|
127
|
+
private const val DEFAULT_PLACEMENT_OFFSET = 16 // dp
|
|
122
128
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
123
129
|
private const val DISMISS_DURATION = 200L
|
|
124
130
|
private const val SCREEN_FADE_DURATION = 150L
|
|
131
|
+
|
|
132
|
+
// Mirrors BottomSheetBehavior.HIDE_THRESHOLD — how far past the lowest stop,
|
|
133
|
+
// as a fraction of its height, a release hides a dismissible sheet
|
|
134
|
+
private const val HIDE_THRESHOLD = 0.5f
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
// =============================================================================
|
|
@@ -162,6 +172,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
162
172
|
private var pendingDetentIndex: Int = -1
|
|
163
173
|
|
|
164
174
|
private var interactionState: InteractionState = InteractionState.Idle
|
|
175
|
+
|
|
176
|
+
// A content/header/footer size change arrived mid-drag — reconfiguring then
|
|
177
|
+
// would reset behavior state and kill the gesture, so it's applied on settle.
|
|
178
|
+
private var hasPendingSizeChange = false
|
|
165
179
|
internal var isBeingDismissed = false
|
|
166
180
|
private set
|
|
167
181
|
var wasHiddenByScreen = false
|
|
@@ -175,6 +189,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
175
189
|
// Keyboard State
|
|
176
190
|
private var detentIndexBeforeKeyboard: Int = -1
|
|
177
191
|
private var isKeyboardDismissProgrammatic = false
|
|
192
|
+
|
|
193
|
+
// Sheet top when a keyboard-floor drag was released; -1 once the settle target is known
|
|
194
|
+
private var keyboardSettleStartTop = -1
|
|
178
195
|
private var focusedViewBeforeBlur: View? = null
|
|
179
196
|
|
|
180
197
|
// Promises
|
|
@@ -194,12 +211,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
194
211
|
// Touch Dispatchers
|
|
195
212
|
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
196
213
|
private val jsPointerDispatcher = JSPointerDispatcher(this)
|
|
197
|
-
|
|
198
|
-
// True when the active touch stream began inside the footer. Latched on
|
|
199
|
-
// ACTION_DOWN so subsequent MOVE events keep routing to the footer even if
|
|
200
|
-
// the finger drifts outside the footer's rect mid-gesture.
|
|
201
|
-
private var footerOwnsTouchStream = false
|
|
202
|
-
|
|
203
214
|
private val touchDeduper = TouchEventDeduper()
|
|
204
215
|
|
|
205
216
|
private val eventDispatcher
|
|
@@ -208,8 +219,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
208
219
|
// Detent Configuration
|
|
209
220
|
override var maxContentHeight: Int? = null
|
|
210
221
|
override var maxContentWidth: Int? = null
|
|
211
|
-
override var
|
|
212
|
-
override var
|
|
222
|
+
override var placement: TrueSheetPlacement = TrueSheetPlacement.AUTOMATIC
|
|
223
|
+
override var placementOffset: Int = DEFAULT_PLACEMENT_OFFSET.dpToPx().toInt()
|
|
213
224
|
override var detents: MutableList<Double> = mutableListOf(0.5, 1.0)
|
|
214
225
|
|
|
215
226
|
// Appearance Configuration
|
|
@@ -218,7 +229,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
218
229
|
override var grabber: Boolean = true
|
|
219
230
|
override var grabberOptions: GrabberOptions? = null
|
|
220
231
|
override var accessibilityOptions: AccessibilityOptions? = null
|
|
232
|
+
var backgroundColor: Int? = null
|
|
233
|
+
var detentBackgrounds: List<Int?> = emptyList()
|
|
234
|
+
private var displayedBackgroundIndex = 0
|
|
235
|
+
private var backgroundAnimator: ValueAnimator? = null
|
|
236
|
+
private var targetBackgroundColor: Int? = null
|
|
221
237
|
override var sheetBackgroundColor: Int? = null
|
|
238
|
+
private set
|
|
222
239
|
var insetAdjustment: TrueSheetInsetAdjustment = TrueSheetInsetAdjustment.AUTOMATIC
|
|
223
240
|
|
|
224
241
|
var scrollableOptions: ScrollableOptions? = null
|
|
@@ -227,6 +244,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
227
244
|
behavior?.scrollingExpandsSheet = value?.scrollingExpandsSheet ?: true
|
|
228
245
|
}
|
|
229
246
|
|
|
247
|
+
// React tag of the user-provided scrollable within the content (see the scrollableRef prop)
|
|
248
|
+
var scrollableHandle: Int = -1
|
|
249
|
+
|
|
230
250
|
override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
231
251
|
set(value) {
|
|
232
252
|
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
@@ -252,6 +272,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
252
272
|
if (isPresented) sheetView?.setupGrabber()
|
|
253
273
|
}
|
|
254
274
|
|
|
275
|
+
var dismissThreshold: TrueSheetDismissThreshold = TrueSheetDismissThreshold.HALF
|
|
276
|
+
set(value) {
|
|
277
|
+
field = value
|
|
278
|
+
behavior?.dismissThreshold = value
|
|
279
|
+
}
|
|
280
|
+
|
|
255
281
|
// =============================================================================
|
|
256
282
|
// MARK: - Computed Properties
|
|
257
283
|
// =============================================================================
|
|
@@ -270,9 +296,29 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
270
296
|
val screenWidth: Int
|
|
271
297
|
get() = ScreenUtils.getScreenWidth(reactContext)
|
|
272
298
|
|
|
299
|
+
// Cached per layout/configuration pass — the WindowManager and
|
|
300
|
+
// rootWindowInsets queries behind these allocate, and they're read many
|
|
301
|
+
// times per animation frame (position emission, detent interpolation,
|
|
302
|
+
// stacked sheet sync)
|
|
303
|
+
private var cachedRealScreenHeight: Int = 0
|
|
304
|
+
private var cachedInsets: Insets? = null
|
|
305
|
+
|
|
306
|
+
private fun invalidateScreenMetrics() {
|
|
307
|
+
cachedRealScreenHeight = 0
|
|
308
|
+
cachedInsets = null
|
|
309
|
+
}
|
|
310
|
+
|
|
273
311
|
// Includes system bars for accurate positioning
|
|
274
312
|
override val realScreenHeight: Int
|
|
275
|
-
get()
|
|
313
|
+
get() {
|
|
314
|
+
if (cachedRealScreenHeight == 0) {
|
|
315
|
+
cachedRealScreenHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
316
|
+
}
|
|
317
|
+
return cachedRealScreenHeight
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private val insets: Insets
|
|
321
|
+
get() = cachedInsets ?: ScreenUtils.getInsets(reactContext).also { cachedInsets = it }
|
|
276
322
|
|
|
277
323
|
// Content Measurements
|
|
278
324
|
// Cached values used during dismiss when container is unmounted
|
|
@@ -294,6 +340,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
294
340
|
|
|
295
341
|
override var absoluteFooter: Boolean = false
|
|
296
342
|
|
|
343
|
+
override val footerInsetAdjustment: Boolean
|
|
344
|
+
get() = absoluteFooter && scrollableHandle > 0 && scrollableOptions?.contentInsetAdjustment?.footer != false
|
|
345
|
+
|
|
297
346
|
override val peekContentHeight: Int
|
|
298
347
|
get() = containerView?.peekContentHeight ?: cachedPeekContentHeight
|
|
299
348
|
|
|
@@ -309,16 +358,30 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
309
358
|
private val isKeyboardTransitioning: Boolean
|
|
310
359
|
get() = keyboardObserver?.isTransitioning ?: false
|
|
311
360
|
|
|
361
|
+
// Keyboard is up or animating — detents are (or are about to be) keyboard-shifted
|
|
362
|
+
val isKeyboardActive: Boolean
|
|
363
|
+
get() = keyboardInset > 0 || isKeyboardTransitioning
|
|
364
|
+
|
|
365
|
+
// While the keyboard holds the sheet up, the keyboard-free first detent is the
|
|
366
|
+
// collapsed stop — landing there dismisses the keyboard, and (when dismissible)
|
|
367
|
+
// dragging more than half its height below it hides the sheet too, like iOS.
|
|
368
|
+
override val hasKeyboardFloor: Boolean
|
|
369
|
+
get() = keyboardInset > 0
|
|
370
|
+
|
|
371
|
+
// The detent JS last heard about — the keyboard expansion is never reported
|
|
372
|
+
private val knownDetentIndex: Int
|
|
373
|
+
get() = if (detentIndexBeforeKeyboard >= 0) detentIndexBeforeKeyboard else currentDetentIndex
|
|
374
|
+
|
|
312
375
|
fun isFocusedViewWithinSheet(): Boolean {
|
|
313
376
|
val sheet = sheetView ?: return false
|
|
314
377
|
return keyboardObserver?.isFocusedViewWithinSheet(sheet) ?: false
|
|
315
378
|
}
|
|
316
379
|
|
|
317
380
|
val bottomInset: Int
|
|
318
|
-
get() = if (edgeToEdgeEnabled)
|
|
381
|
+
get() = if (edgeToEdgeEnabled) insets.bottom else 0
|
|
319
382
|
|
|
320
383
|
override val topInset: Int
|
|
321
|
-
get() = if (edgeToEdgeEnabled)
|
|
384
|
+
get() = if (edgeToEdgeEnabled) insets.top else 0
|
|
322
385
|
|
|
323
386
|
override val contentBottomInset: Int
|
|
324
387
|
get() = if (insetAdjustment == TrueSheetInsetAdjustment.AUTOMATIC) bottomInset else 0
|
|
@@ -331,6 +394,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
331
394
|
}
|
|
332
395
|
|
|
333
396
|
// Sheet State
|
|
397
|
+
val isSettling: Boolean
|
|
398
|
+
get() = behavior?.state == BottomSheetBehavior.STATE_SETTLING
|
|
399
|
+
|
|
334
400
|
val isExpanded: Boolean
|
|
335
401
|
get() {
|
|
336
402
|
val sheetTop = sheetView?.top ?: return false
|
|
@@ -373,6 +439,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
373
439
|
|
|
374
440
|
private fun cleanupSheet() {
|
|
375
441
|
cleanupKeyboardObserver()
|
|
442
|
+
backgroundAnimator?.cancel()
|
|
443
|
+
backgroundAnimator = null
|
|
444
|
+
targetBackgroundColor = null
|
|
376
445
|
sheetView?.animate()?.cancel()
|
|
377
446
|
|
|
378
447
|
// Cleanup dim views
|
|
@@ -392,6 +461,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
392
461
|
sheetView = null
|
|
393
462
|
|
|
394
463
|
interactionState = InteractionState.Idle
|
|
464
|
+
hasPendingSizeChange = false
|
|
395
465
|
isBeingDismissed = false
|
|
396
466
|
isPresented = false
|
|
397
467
|
isSheetVisible = false
|
|
@@ -440,13 +510,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
440
510
|
// =============================================================================
|
|
441
511
|
|
|
442
512
|
override fun coordinatorLayoutDidLayout(changed: Boolean) {
|
|
443
|
-
|
|
513
|
+
if (changed) invalidateScreenMetrics()
|
|
514
|
+
|
|
444
515
|
if (isPresented && changed) {
|
|
445
|
-
|
|
516
|
+
setupSheetDetentsForSizeChange()
|
|
446
517
|
}
|
|
447
518
|
}
|
|
448
519
|
|
|
449
520
|
override fun coordinatorLayoutDidChangeConfiguration() {
|
|
521
|
+
invalidateScreenMetrics()
|
|
450
522
|
if (!isPresented) return
|
|
451
523
|
|
|
452
524
|
sheetView?.updateGravity()
|
|
@@ -458,6 +530,28 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
458
530
|
override fun findScrollView(): ViewGroup? = containerView?.contentView?.findScrollView()
|
|
459
531
|
override fun findSheetView(): TrueSheetBottomSheetView? = sheetView
|
|
460
532
|
|
|
533
|
+
override fun coordinatorLayoutDidPullDown(overdragPx: Int, keyboardWasVisible: Boolean) {
|
|
534
|
+
if (!isPresented || dismissible || !draggable) return
|
|
535
|
+
val sheet = sheetView ?: return
|
|
536
|
+
|
|
537
|
+
// Only a pull clamped at the lowest stop is a blocked drag-to-dismiss — a
|
|
538
|
+
// child that swallowed the gesture higher up isn't
|
|
539
|
+
val lowestTop = detentCalculator.getLowestSheetTop()
|
|
540
|
+
if (sheet.top < lowestTop) return
|
|
541
|
+
|
|
542
|
+
if (keyboardWasVisible) {
|
|
543
|
+
// A sheet that couldn't move at all (first detent already full height) never
|
|
544
|
+
// began a drag, so the keyboard is still up — dismiss it here
|
|
545
|
+
if (hasKeyboardFloor && !isKeyboardDismissProgrammatic) commitDetentAndDismissKeyboard(0, sheet)
|
|
546
|
+
|
|
547
|
+
// Past the floor by the distance that would hide a dismissible sheet, like iOS
|
|
548
|
+
val floorHeight = realScreenHeight - lowestTop
|
|
549
|
+
if (overdragPx < floorHeight * HIDE_THRESHOLD) return
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
553
|
+
}
|
|
554
|
+
|
|
461
555
|
// =============================================================================
|
|
462
556
|
// MARK: - TrueSheetDimViewDelegate
|
|
463
557
|
// =============================================================================
|
|
@@ -495,24 +589,30 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
495
589
|
|
|
496
590
|
override fun bottomSheetViewDidTapGrabber() {
|
|
497
591
|
val nextIndex = (currentDetentIndex + 1) % detents.size
|
|
498
|
-
if (nextIndex == 0 && detents.size == 1
|
|
499
|
-
|
|
592
|
+
if (nextIndex == 0 && detents.size == 1) {
|
|
593
|
+
if (dismissible) {
|
|
594
|
+
dismiss(animated = true)
|
|
595
|
+
} else {
|
|
596
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
597
|
+
}
|
|
500
598
|
} else {
|
|
501
|
-
|
|
599
|
+
resize(nextIndex)
|
|
502
600
|
}
|
|
503
601
|
}
|
|
504
602
|
|
|
505
603
|
override fun bottomSheetViewDidAccessibilityIncrement() {
|
|
506
604
|
if (currentDetentIndex < detents.size - 1) {
|
|
507
|
-
|
|
605
|
+
resize(currentDetentIndex + 1)
|
|
508
606
|
}
|
|
509
607
|
}
|
|
510
608
|
|
|
511
609
|
override fun bottomSheetViewDidAccessibilityDecrement() {
|
|
512
610
|
if (currentDetentIndex > 0) {
|
|
513
|
-
|
|
611
|
+
resize(currentDetentIndex - 1)
|
|
514
612
|
} else if (dismissible) {
|
|
515
613
|
dismiss(animated = true)
|
|
614
|
+
} else {
|
|
615
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
516
616
|
}
|
|
517
617
|
}
|
|
518
618
|
|
|
@@ -526,7 +626,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
526
626
|
}
|
|
527
627
|
|
|
528
628
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
529
|
-
handleSlide(sheetView
|
|
629
|
+
handleSlide(sheetView)
|
|
530
630
|
}
|
|
531
631
|
}
|
|
532
632
|
|
|
@@ -555,27 +655,44 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
555
655
|
}
|
|
556
656
|
}
|
|
557
657
|
|
|
558
|
-
private fun handleSlide(sheetView: View
|
|
658
|
+
private fun handleSlide(sheetView: View) {
|
|
559
659
|
// Skip during dismiss animation
|
|
560
660
|
if (isBeingDismissed) return
|
|
561
661
|
|
|
562
662
|
val behavior = behavior ?: return
|
|
563
663
|
|
|
664
|
+
if (interactionState is InteractionState.Dragging || pendingDetentIndex >= 0) {
|
|
665
|
+
updateStateDimensionsForPosition(sheetView.top)
|
|
666
|
+
}
|
|
667
|
+
|
|
564
668
|
when (behavior.state) {
|
|
565
|
-
BottomSheetBehavior.STATE_DRAGGING
|
|
566
|
-
|
|
669
|
+
BottomSheetBehavior.STATE_DRAGGING -> handleDragChange(sheetView)
|
|
670
|
+
|
|
671
|
+
BottomSheetBehavior.STATE_SETTLING -> {
|
|
672
|
+
handleDragChange(sheetView)
|
|
673
|
+
dismissKeyboardForSettleTarget(sheetView)
|
|
674
|
+
}
|
|
567
675
|
|
|
568
676
|
else -> { }
|
|
569
677
|
}
|
|
570
678
|
|
|
571
679
|
emitChangePositionDelegate(sheetView.top)
|
|
572
680
|
|
|
681
|
+
// Drive the parent's stack translation from our actual frame position —
|
|
682
|
+
// an independently-timed animation lags behind (e.g. auto detent shrink)
|
|
683
|
+
// and reveals the parent above this sheet's top edge. Skipped while
|
|
684
|
+
// dragging so the parent stays put during drag-to-dismiss, and while the
|
|
685
|
+
// keyboard is active so growing for it doesn't move the parent (like iOS).
|
|
686
|
+
if (!isPresentAnimating && interactionState !is InteractionState.Dragging && !isKeyboardActive) {
|
|
687
|
+
syncParentTranslation(sheetView.top)
|
|
688
|
+
}
|
|
689
|
+
|
|
573
690
|
// Position on every slide frame — during keyboard transitions the IME
|
|
574
691
|
// animation callbacks are sparser than the sheet's settle frames, and the
|
|
575
692
|
// footer rides the sheet between them, jittering against the keyboard.
|
|
576
693
|
// positionFooter derives from current state, so double-driving it with
|
|
577
694
|
// keyboardDidChangeHeight is harmless.
|
|
578
|
-
positionFooter(
|
|
695
|
+
positionFooter()
|
|
579
696
|
|
|
580
697
|
if (!isKeyboardTransitioning) {
|
|
581
698
|
updateDimAmount(sheetView.top)
|
|
@@ -585,11 +702,22 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
585
702
|
private fun handleStateSettled(sheetView: View, newState: Int) {
|
|
586
703
|
if (interactionState is InteractionState.Reconfiguring) return
|
|
587
704
|
|
|
705
|
+
// A release already at a stop settles without a slide frame
|
|
706
|
+
keyboardSettleStartTop = -1
|
|
707
|
+
|
|
588
708
|
// Sheet has reached a stable state — any in-flight resize target is now stale,
|
|
589
709
|
// whether settled at the target, interrupted by drag, or superseded.
|
|
590
710
|
pendingDetentIndex = -1
|
|
591
711
|
|
|
592
|
-
val index = detentCalculator.getDetentIndexForState(newState) ?:
|
|
712
|
+
val index = detentCalculator.getDetentIndexForState(newState) ?: run {
|
|
713
|
+
// Unmapped settle — end any drag anyway so deferred size changes aren't
|
|
714
|
+
// dropped forever (a leaked Dragging state skips every reconfigure)
|
|
715
|
+
if (interactionState is InteractionState.Dragging) {
|
|
716
|
+
interactionState = InteractionState.Idle
|
|
717
|
+
}
|
|
718
|
+
flushPendingSizeChange()
|
|
719
|
+
return
|
|
720
|
+
}
|
|
593
721
|
val position = getPositionDpForView(sheetView)
|
|
594
722
|
val detentInfo = DetentInfo(index, position)
|
|
595
723
|
|
|
@@ -597,21 +725,30 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
597
725
|
if (isPresentAnimating) {
|
|
598
726
|
isPresentAnimating = false
|
|
599
727
|
finishPresent()
|
|
728
|
+
flushPendingSizeChange()
|
|
600
729
|
return
|
|
601
730
|
}
|
|
602
731
|
|
|
732
|
+
val wasDragging = interactionState is InteractionState.Dragging
|
|
733
|
+
|
|
603
734
|
when (interactionState) {
|
|
604
735
|
is InteractionState.Dragging -> {
|
|
605
736
|
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
606
737
|
delegate?.viewControllerDidDragEnd(detentInfo.index, detentInfo.position, detent)
|
|
607
738
|
|
|
608
|
-
// Skip detent change if keyboard inset is still active —
|
|
609
|
-
//
|
|
610
|
-
if (
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
739
|
+
// Skip detent change if keyboard inset is still active — the keyboard floor
|
|
740
|
+
// landing below commits the detent instead.
|
|
741
|
+
if (keyboardInset == 0) {
|
|
742
|
+
val changed = detentInfo.index != knownDetentIndex
|
|
743
|
+
detentIndexBeforeKeyboard = -1
|
|
744
|
+
if (detentInfo.index != currentDetentIndex) {
|
|
745
|
+
currentDetentIndex = detentInfo.index
|
|
746
|
+
setupDimmedBackground()
|
|
747
|
+
}
|
|
748
|
+
if (changed) {
|
|
749
|
+
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
750
|
+
this@TrueSheetViewController.sheetView?.updateGrabberAccessibilityValue(detentInfo.index, detents.size)
|
|
751
|
+
}
|
|
615
752
|
}
|
|
616
753
|
|
|
617
754
|
interactionState = InteractionState.Idle
|
|
@@ -629,14 +766,28 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
629
766
|
}
|
|
630
767
|
}
|
|
631
768
|
|
|
632
|
-
|
|
633
|
-
|
|
769
|
+
if (hasKeyboardFloor && wasDragging && newState != BottomSheetBehavior.STATE_EXPANDED) {
|
|
770
|
+
// Dragged down onto a keyboard-free stop (the floor or the keyboard detent) —
|
|
771
|
+
// dismiss the keyboard and commit that detent. keyboardWillHide reconfigures
|
|
772
|
+
// the detents (and the container) to it.
|
|
773
|
+
commitDetentAndDismissKeyboard(index, sheetView)
|
|
774
|
+
} else {
|
|
775
|
+
updateStateDimensions()
|
|
776
|
+
}
|
|
634
777
|
|
|
635
778
|
if (!isKeyboardTransitioning) {
|
|
636
779
|
updateDimAmount(animated = true)
|
|
637
780
|
}
|
|
638
781
|
|
|
639
782
|
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
783
|
+
|
|
784
|
+
flushPendingSizeChange()
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private fun flushPendingSizeChange() {
|
|
788
|
+
if (!hasPendingSizeChange) return
|
|
789
|
+
hasPendingSizeChange = false
|
|
790
|
+
setupSheetDetentsForSizeChange()
|
|
640
791
|
}
|
|
641
792
|
|
|
642
793
|
// =============================================================================
|
|
@@ -698,6 +849,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
698
849
|
val sheet = this.sheetView ?: return
|
|
699
850
|
if (isPresented) return
|
|
700
851
|
|
|
852
|
+
invalidateScreenMetrics()
|
|
701
853
|
shouldAnimatePresent = animated
|
|
702
854
|
currentDetentIndex = detentIndex
|
|
703
855
|
interactionState = InteractionState.Idle
|
|
@@ -711,6 +863,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
711
863
|
setupDimmedBackground()
|
|
712
864
|
setupKeyboardObserver()
|
|
713
865
|
|
|
866
|
+
displayedBackgroundIndex = detentIndex
|
|
867
|
+
setupBackground(animated = false)
|
|
714
868
|
sheet.setupBackground()
|
|
715
869
|
sheet.setupElevation()
|
|
716
870
|
sheet.setupGrabber()
|
|
@@ -735,13 +889,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
735
889
|
fun resize(detentIndex: Int) {
|
|
736
890
|
if (!isPresented) return
|
|
737
891
|
|
|
738
|
-
// Commit the new index so keyboardWillHide restores to it instead of the stale
|
|
892
|
+
// Commit the new index so keyboardWillHide restores to it instead of the stale
|
|
893
|
+
// one; the sheet keeps avoiding the keyboard meanwhile
|
|
739
894
|
if (detentIndexBeforeKeyboard >= 0) {
|
|
740
895
|
detentIndexBeforeKeyboard = detentIndex
|
|
896
|
+
if (hasKeyboardFloor) {
|
|
897
|
+
resizePromise?.invoke()
|
|
898
|
+
resizePromise = null
|
|
899
|
+
return
|
|
900
|
+
}
|
|
741
901
|
}
|
|
742
902
|
|
|
743
903
|
pendingDetentIndex = detentIndex
|
|
744
|
-
updateStateDimensions(deferShrink = true)
|
|
745
904
|
setupDimmedBackground()
|
|
746
905
|
setStateForDetentIndex(detentIndex)
|
|
747
906
|
resizePromise?.invoke()
|
|
@@ -763,6 +922,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
763
922
|
behavior.isHideable = true
|
|
764
923
|
behavior.isDraggable = draggable
|
|
765
924
|
behavior.scrollingExpandsSheet = scrollableOptions?.scrollingExpandsSheet ?: true
|
|
925
|
+
behavior.dismissThreshold = dismissThreshold
|
|
926
|
+
behavior.lowestSheetTop = { detentCalculator.getLowestSheetTop() }
|
|
766
927
|
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
|
767
928
|
behavior.addBottomSheetCallback(sheetCallback)
|
|
768
929
|
|
|
@@ -866,11 +1027,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
866
1027
|
|
|
867
1028
|
val maxAvailableHeight = realScreenHeight - topInset
|
|
868
1029
|
|
|
869
|
-
|
|
1030
|
+
// With a keyboard floor only the last detent stays keyboard-shifted; the
|
|
1031
|
+
// collapsed and half stops are the keyboard-free first and second detents,
|
|
1032
|
+
// so a short drag down from the keyboard-expanded sheet rests at the second
|
|
1033
|
+
// and a long one at the first, like iOS
|
|
1034
|
+
val peekHeight = minOf(detentCalculator.getDetentHeight(detents[0], includeKeyboard = !hasKeyboardFloor), maxAvailableHeight)
|
|
870
1035
|
|
|
871
1036
|
val halfExpandedDetentHeight = when (detents.size) {
|
|
872
1037
|
1 -> peekHeight
|
|
873
|
-
else -> detentCalculator.getDetentHeight(detents[1])
|
|
1038
|
+
else -> detentCalculator.getDetentHeight(detents[1], includeKeyboard = !hasKeyboardFloor)
|
|
874
1039
|
}
|
|
875
1040
|
|
|
876
1041
|
val maxDetentHeight = minOf(detentCalculator.getDetentHeight(detents.last()), maxAvailableHeight)
|
|
@@ -880,8 +1045,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
880
1045
|
|
|
881
1046
|
val expandedOffset = realScreenHeight - maxDetentHeight
|
|
882
1047
|
|
|
883
|
-
// fitToContents works better with <= 2 detents when no expanded offset
|
|
884
|
-
|
|
1048
|
+
// fitToContents works better with <= 2 detents when no expanded offset, but
|
|
1049
|
+
// it drops the half stop the keyboard floor relies on
|
|
1050
|
+
val fitToContents = !hasKeyboardFloor && detents.size < 3 && expandedOffset == 0
|
|
885
1051
|
|
|
886
1052
|
configureDetents(
|
|
887
1053
|
behavior = behavior,
|
|
@@ -892,13 +1058,25 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
892
1058
|
animate = isPresented
|
|
893
1059
|
)
|
|
894
1060
|
|
|
895
|
-
updateStateDimensions()
|
|
896
|
-
|
|
897
1061
|
if (isPresented && applyState) {
|
|
898
1062
|
// Prefer the pending target while a resize animation is in flight so a
|
|
899
1063
|
// layout-driven reconfigure doesn't revert to the stale currentDetentIndex.
|
|
900
1064
|
val targetIndex = if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex
|
|
1065
|
+
|
|
1066
|
+
// Track the settle like resize() — the container (and the footer pinned
|
|
1067
|
+
// to its bottom) follows the sheet's visible height instead of jumping
|
|
1068
|
+
// to the target and riding the animation
|
|
1069
|
+
pendingDetentIndex = targetIndex
|
|
1070
|
+
sheetView?.let { updateStateDimensionsForPosition(it.top) }
|
|
901
1071
|
setStateForDetentIndex(targetIndex)
|
|
1072
|
+
|
|
1073
|
+
// Already at the target — no slide frames will follow
|
|
1074
|
+
if (!isSettling) {
|
|
1075
|
+
pendingDetentIndex = -1
|
|
1076
|
+
updateStateDimensions()
|
|
1077
|
+
}
|
|
1078
|
+
} else {
|
|
1079
|
+
updateStateDimensions()
|
|
902
1080
|
}
|
|
903
1081
|
|
|
904
1082
|
interactionState = InteractionState.Idle
|
|
@@ -922,10 +1100,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
922
1100
|
}
|
|
923
1101
|
|
|
924
1102
|
fun setupSheetDetentsForSizeChange() {
|
|
925
|
-
//
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
|
|
1103
|
+
// Reconfiguring while the container tracks the sheet can interrupt its
|
|
1104
|
+
// motion. Apply content size changes once the drag or resize settles.
|
|
1105
|
+
if (interactionState is InteractionState.Dragging || isSettling) {
|
|
1106
|
+
hasPendingSizeChange = true
|
|
1107
|
+
return
|
|
1108
|
+
}
|
|
1109
|
+
hasPendingSizeChange = false
|
|
929
1110
|
|
|
930
1111
|
setupSheetDetents()
|
|
931
1112
|
positionFooter()
|
|
@@ -939,6 +1120,30 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
939
1120
|
// MARK: - Dimmed Background
|
|
940
1121
|
// =============================================================================
|
|
941
1122
|
|
|
1123
|
+
fun setupBackground(animated: Boolean = true) {
|
|
1124
|
+
val sheet = sheetView ?: return
|
|
1125
|
+
val color = detentBackgrounds.getOrNull(displayedBackgroundIndex)
|
|
1126
|
+
?: backgroundColor ?: sheet.getDefaultBackgroundColor()
|
|
1127
|
+
if (animated && color == targetBackgroundColor) return
|
|
1128
|
+
|
|
1129
|
+
backgroundAnimator?.cancel()
|
|
1130
|
+
targetBackgroundColor = color
|
|
1131
|
+
val startColor = sheetBackgroundColor ?: color
|
|
1132
|
+
if (!animated || !isPresented || startColor == color) {
|
|
1133
|
+
sheetBackgroundColor = color
|
|
1134
|
+
sheet.updateBackgroundColor(color)
|
|
1135
|
+
return
|
|
1136
|
+
}
|
|
1137
|
+
backgroundAnimator = ValueAnimator.ofArgb(startColor, color).apply {
|
|
1138
|
+
duration = 200L
|
|
1139
|
+
addUpdateListener {
|
|
1140
|
+
sheetBackgroundColor = it.animatedValue as Int
|
|
1141
|
+
sheet.updateBackgroundColor(it.animatedValue as Int)
|
|
1142
|
+
}
|
|
1143
|
+
start()
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
942
1147
|
fun setupDimmedBackground() {
|
|
943
1148
|
val coordinator = this.coordinatorLayout ?: run {
|
|
944
1149
|
RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupDimmedBackground")
|
|
@@ -978,10 +1183,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
978
1183
|
|
|
979
1184
|
fun updateDimAmount(sheetTop: Int? = null, animated: Boolean = false) {
|
|
980
1185
|
if (!dimmed) return
|
|
981
|
-
if (contentHeight == 0) return
|
|
982
1186
|
|
|
983
1187
|
// While keyboard is active or transitioning, use the target detent position for dim
|
|
984
|
-
val top = if (
|
|
1188
|
+
val top = if (isKeyboardActive) {
|
|
985
1189
|
detentCalculator.getSheetTopForDetentIndex(currentDetentIndex)
|
|
986
1190
|
} else {
|
|
987
1191
|
val keyboardOffset = if (isBeingDismissed) 0 else currentKeyboardInset
|
|
@@ -1006,13 +1210,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1006
1210
|
|
|
1007
1211
|
var footerKeyboardOffset: Int = 0
|
|
1008
1212
|
|
|
1009
|
-
|
|
1213
|
+
/**
|
|
1214
|
+
* false keeps an absolute footer pinned to the sheet's bottom edge behind
|
|
1215
|
+
* the keyboard instead of rising above it.
|
|
1216
|
+
*/
|
|
1217
|
+
override var footerAvoidsKeyboard: Boolean = true
|
|
1218
|
+
|
|
1219
|
+
fun positionFooter() {
|
|
1010
1220
|
if (!isPresented) return
|
|
1011
1221
|
val footerView = containerView?.footerView ?: return
|
|
1012
1222
|
val sheet = sheetView ?: return
|
|
1013
1223
|
|
|
1014
|
-
val keyboardShift = if (currentKeyboardInset > 0) maxOf(0, currentKeyboardInset + footerKeyboardOffset) else 0
|
|
1015
|
-
|
|
1016
1224
|
// A relative footer is laid out by Yoga (pinned to the container's bottom
|
|
1017
1225
|
// edge) and stays in the layout flow behind the keyboard — only an
|
|
1018
1226
|
// absolute footer floats above it
|
|
@@ -1021,24 +1229,31 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1021
1229
|
return
|
|
1022
1230
|
}
|
|
1023
1231
|
|
|
1232
|
+
// The footer's absorbed safe-area inset rides below the keyboard's top
|
|
1233
|
+
// edge — its content stays flush with the keyboard without dropping the
|
|
1234
|
+
// inset padding from the layout, which would jump at the transition edges
|
|
1235
|
+
val keyboardShift = if (currentKeyboardInset > 0 && footerAvoidsKeyboard) {
|
|
1236
|
+
maxOf(0, currentKeyboardInset + footerKeyboardOffset - footerView.bottomInset)
|
|
1237
|
+
} else {
|
|
1238
|
+
0
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1024
1241
|
val footerHeight = footerView.height
|
|
1025
1242
|
val sheetHeight = sheet.height
|
|
1026
|
-
val sheetTop = sheet.top
|
|
1027
|
-
|
|
1028
|
-
var footerY = (sheetHeight - sheetTop - footerHeight - keyboardShift).toFloat()
|
|
1029
1243
|
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
//
|
|
1033
|
-
//
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
footerY -= (footerHeight * slideOffset)
|
|
1037
|
-
}
|
|
1244
|
+
// Pinned to the screen bottom (above the keyboard) while the sheet sits at
|
|
1245
|
+
// or above its lowest stop; dragged past it the footer rides down with the
|
|
1246
|
+
// sheet instead, like iOS. A resize settle always pins — the sheet may be
|
|
1247
|
+
// animating up from below a stop that just grew
|
|
1248
|
+
val anchorTop = if (pendingDetentIndex >= 0) sheet.top else minOf(sheet.top, detentCalculator.getLowestSheetTop())
|
|
1249
|
+
val footerY = (sheetHeight - anchorTop - footerHeight - keyboardShift).toFloat()
|
|
1038
1250
|
|
|
1039
1251
|
// Clamp to prevent footer going above safe area
|
|
1040
1252
|
val maxAllowedY = (sheetHeight - topInset - footerHeight).toFloat()
|
|
1041
1253
|
footerView.y = minOf(footerY, maxAllowedY)
|
|
1254
|
+
if (!footerAvoidsKeyboard) {
|
|
1255
|
+
containerView?.contentView?.updateContentInset()
|
|
1256
|
+
}
|
|
1042
1257
|
}
|
|
1043
1258
|
|
|
1044
1259
|
// =============================================================================
|
|
@@ -1064,7 +1279,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1064
1279
|
if (!viewToFocus.isAttachedToWindow) return
|
|
1065
1280
|
if (viewToFocus.requestFocus()) {
|
|
1066
1281
|
viewToFocus.postDelayed({
|
|
1067
|
-
KeyboardUtils.show(viewToFocus)
|
|
1282
|
+
if (viewToFocus.isFocused) KeyboardUtils.show(viewToFocus)
|
|
1068
1283
|
}, 100)
|
|
1069
1284
|
}
|
|
1070
1285
|
}
|
|
@@ -1079,9 +1294,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1079
1294
|
delegate = object : TrueSheetKeyboardObserverDelegate {
|
|
1080
1295
|
override fun keyboardWillShow(height: Int) {
|
|
1081
1296
|
if (!shouldHandleKeyboard()) return
|
|
1082
|
-
// An absolute footer rises above the keyboard, so it drops the bottom
|
|
1083
|
-
// inset padding — before detents are configured against its height
|
|
1084
|
-
if (absoluteFooter) containerView?.footerView?.keyboardVisible = true
|
|
1085
1297
|
// If a resize is in flight, restore to its target — not the stale current
|
|
1086
1298
|
detentIndexBeforeKeyboard = if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex
|
|
1087
1299
|
pendingDetentIndex = -1
|
|
@@ -1094,17 +1306,22 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1094
1306
|
|
|
1095
1307
|
override fun keyboardWillHide() {
|
|
1096
1308
|
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
1097
|
-
|
|
1309
|
+
// Mid-drag (the drag itself dismissed the keyboard) the release decides
|
|
1310
|
+
// the detent and a reconfigure would kill the gesture — the floor stops
|
|
1311
|
+
// stay put until the settle flushes the pending reconfigure
|
|
1312
|
+
if (interactionState is InteractionState.Dragging) {
|
|
1313
|
+
hasPendingSizeChange = true
|
|
1314
|
+
return
|
|
1315
|
+
}
|
|
1098
1316
|
val restoring = !isBeingDismissed && detentIndexBeforeKeyboard >= 0
|
|
1099
1317
|
|
|
1100
1318
|
// Skip reconfigure during interactive keyboard dismiss (e.g. keyboardDismissMode="on-drag")
|
|
1101
1319
|
// to prevent the sheet from jumping. keyboardDidHide will reconfigure after.
|
|
1102
1320
|
if (restoring || isKeyboardDismissProgrammatic) {
|
|
1103
|
-
setupSheetDetents()
|
|
1104
1321
|
if (restoring) {
|
|
1105
1322
|
currentDetentIndex = detentIndexBeforeKeyboard
|
|
1106
|
-
setStateForDetentIndex(currentDetentIndex)
|
|
1107
1323
|
}
|
|
1324
|
+
setupSheetDetents()
|
|
1108
1325
|
}
|
|
1109
1326
|
|
|
1110
1327
|
// Same-frame reposition — the reconfigure above shrinks the container
|
|
@@ -1119,9 +1336,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1119
1336
|
|
|
1120
1337
|
override fun keyboardDidHide() {
|
|
1121
1338
|
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
1122
|
-
containerView?.footerView?.keyboardVisible = false
|
|
1123
|
-
detentIndexBeforeKeyboard = -1
|
|
1124
1339
|
isKeyboardDismissProgrammatic = false
|
|
1340
|
+
// Keep detentIndexBeforeKeyboard for the settle's detent-change check
|
|
1341
|
+
if (interactionState is InteractionState.Dragging) {
|
|
1342
|
+
hasPendingSizeChange = true
|
|
1343
|
+
return
|
|
1344
|
+
}
|
|
1345
|
+
detentIndexBeforeKeyboard = -1
|
|
1125
1346
|
setupSheetDetents(applyState = false)
|
|
1126
1347
|
positionFooter()
|
|
1127
1348
|
updateDimAmount(
|
|
@@ -1141,7 +1362,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1141
1362
|
// Handle case where keyboard is already visible and focus moves into the sheet
|
|
1142
1363
|
if (!shouldHandleKeyboard()) return
|
|
1143
1364
|
if (detentIndexBeforeKeyboard < 0 && (keyboardObserver?.currentHeight ?: 0) > 0) {
|
|
1144
|
-
if (absoluteFooter) containerView?.footerView?.keyboardVisible = true
|
|
1145
1365
|
detentIndexBeforeKeyboard = currentDetentIndex
|
|
1146
1366
|
currentDetentIndex = detents.size - 1
|
|
1147
1367
|
setupSheetDetents()
|
|
@@ -1169,8 +1389,28 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1169
1389
|
detentIndexBeforeKeyboard = -1
|
|
1170
1390
|
}
|
|
1171
1391
|
|
|
1392
|
+
private fun handleSettling(sheetView: View) {
|
|
1393
|
+
// A keyboard-floor drag was released — the first settle frame reveals the target
|
|
1394
|
+
keyboardSettleStartTop = if (hasKeyboardFloor && interactionState is InteractionState.Dragging) sheetView.top else -1
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Dismisses the keyboard as soon as a released drag is known to land on a
|
|
1399
|
+
* keyboard-free stop, rather than waiting for the landing. BottomSheetBehavior
|
|
1400
|
+
* keeps its target private, so read it off the first settle frame: heading
|
|
1401
|
+
* down means the half stop or the floor; heading up from below the half stop
|
|
1402
|
+
* means the half stop; only heading up from above it means the expanded stop.
|
|
1403
|
+
*/
|
|
1404
|
+
private fun dismissKeyboardForSettleTarget(sheetView: View) {
|
|
1405
|
+
if (keyboardSettleStartTop < 0) return
|
|
1406
|
+
val startTop = keyboardSettleStartTop
|
|
1407
|
+
keyboardSettleStartTop = -1
|
|
1408
|
+
|
|
1409
|
+
val headingToKeyboardFreeStop = sheetView.top > startTop || startTop > detentCalculator.getKeyboardHalfStopTop()
|
|
1410
|
+
if (headingToKeyboardFreeStop && shouldHandleKeyboard()) dismissKeyboard()
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1172
1413
|
private fun handleDragBegin(sheetView: View) {
|
|
1173
|
-
detentIndexBeforeKeyboard = -1
|
|
1174
1414
|
pendingDetentIndex = -1
|
|
1175
1415
|
|
|
1176
1416
|
val position = getPositionDpForView(sheetView)
|
|
@@ -1179,43 +1419,25 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1179
1419
|
interactionState = InteractionState.Dragging(startTop = sheetView.top)
|
|
1180
1420
|
}
|
|
1181
1421
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1422
|
+
// Commits a non-keyboard detent and hides the keyboard; keyboardWillHide then
|
|
1423
|
+
// reconfigures the detents so the sheet settles at the committed index.
|
|
1424
|
+
private fun commitDetentAndDismissKeyboard(index: Int, sheetView: View) {
|
|
1425
|
+
val changed = index != knownDetentIndex
|
|
1426
|
+
detentIndexBeforeKeyboard = -1
|
|
1427
|
+
currentDetentIndex = index
|
|
1428
|
+
dismissKeyboard()
|
|
1429
|
+
setupDimmedBackground()
|
|
1185
1430
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
for (i in detents.indices) {
|
|
1193
|
-
val nonKeyboardHeight = minOf(detentCalculator.getDetentHeight(detents[i], includeKeyboard = false), maxAvailableHeight)
|
|
1194
|
-
val nonKeyboardTop = realScreenHeight - nonKeyboardHeight
|
|
1195
|
-
val midpoint = keyboardTop + (nonKeyboardTop - keyboardTop) / 2
|
|
1196
|
-
|
|
1197
|
-
if (sheetView.top >= midpoint) {
|
|
1198
|
-
// Target position matches the keyboard-expanded position — keep keyboard open
|
|
1199
|
-
if (nonKeyboardTop == keyboardTop) break
|
|
1200
|
-
|
|
1201
|
-
detentIndexBeforeKeyboard = -1
|
|
1202
|
-
currentDetentIndex = i
|
|
1203
|
-
dismissKeyboard()
|
|
1204
|
-
setupDimmedBackground()
|
|
1205
|
-
|
|
1206
|
-
val position = getPositionDpForView(sheetView)
|
|
1207
|
-
val detent = detentCalculator.getDetentValueForIndex(i)
|
|
1208
|
-
delegate?.viewControllerDidChangeDetent(i, position, detent)
|
|
1209
|
-
break
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1431
|
+
if (!changed) return
|
|
1432
|
+
val position = getPositionDpForView(sheetView)
|
|
1433
|
+
val detent = detentCalculator.getDetentValueForIndex(index)
|
|
1434
|
+
delegate?.viewControllerDidChangeDetent(index, position, detent)
|
|
1435
|
+
this.sheetView?.updateGrabberAccessibilityValue(index, detents.size)
|
|
1212
1436
|
}
|
|
1213
1437
|
|
|
1214
1438
|
private fun handleDragChange(sheetView: View) {
|
|
1215
1439
|
if (interactionState !is InteractionState.Dragging) return
|
|
1216
1440
|
|
|
1217
|
-
updateStateDimensionsForDrag(sheetView.top)
|
|
1218
|
-
|
|
1219
1441
|
val position = getPositionDpForView(sheetView)
|
|
1220
1442
|
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
1221
1443
|
delegate?.viewControllerDidDragChange(currentDetentIndex, position, detent)
|
|
@@ -1250,14 +1472,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1250
1472
|
}
|
|
1251
1473
|
|
|
1252
1474
|
private fun emitChangePositionDelegate(currentTop: Int, realtime: Boolean = true) {
|
|
1475
|
+
if (isPresented && !isPresentAnimating && !isBeingDismissed) {
|
|
1476
|
+
val index = detentCalculator.getBackgroundIndex(currentTop, displayedBackgroundIndex)
|
|
1477
|
+
if (index != displayedBackgroundIndex) {
|
|
1478
|
+
displayedBackgroundIndex = index
|
|
1479
|
+
setupBackground()
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1253
1482
|
// Dedupe emissions for same position
|
|
1254
1483
|
if (currentTop == lastEmittedPositionPx) return
|
|
1255
1484
|
|
|
1256
1485
|
lastEmittedPositionPx = currentTop
|
|
1257
1486
|
val visibleHeight = realScreenHeight - currentTop
|
|
1258
1487
|
val position = detentCalculator.getPositionDp(visibleHeight)
|
|
1259
|
-
val interpolatedIndex = detentCalculator.
|
|
1260
|
-
val detent = detentCalculator.getInterpolatedDetentForPosition(currentTop)
|
|
1488
|
+
val (interpolatedIndex, detent) = detentCalculator.getInterpolatedPositionInfo(currentTop)
|
|
1261
1489
|
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
1262
1490
|
}
|
|
1263
1491
|
|
|
@@ -1265,9 +1493,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1265
1493
|
* Updates position emission, footer, and dim amount together.
|
|
1266
1494
|
* This pattern is commonly used during animations and state changes.
|
|
1267
1495
|
*/
|
|
1268
|
-
private fun updateSheetVisuals(effectiveTop: Int
|
|
1496
|
+
private fun updateSheetVisuals(effectiveTop: Int) {
|
|
1269
1497
|
emitChangePositionDelegate(effectiveTop)
|
|
1270
|
-
positionFooter(
|
|
1498
|
+
positionFooter()
|
|
1271
1499
|
updateDimAmount(effectiveTop)
|
|
1272
1500
|
}
|
|
1273
1501
|
|
|
@@ -1287,13 +1515,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1287
1515
|
/**
|
|
1288
1516
|
* Updates the Fabric state with the current/target detent size so Yoga sizes
|
|
1289
1517
|
* the container to the sheet's visible height.
|
|
1290
|
-
*
|
|
1291
|
-
* Unlike iOS, resize animations run over multiple frames (ViewDragHelper), so
|
|
1292
|
-
* shrinking is deferred to settle ([deferShrink]) when a resize animation is
|
|
1293
|
-
* about to run — growing applies immediately so content is laid out before
|
|
1294
|
-
* the sheet reveals it.
|
|
1295
1518
|
*/
|
|
1296
|
-
private fun updateStateDimensions(
|
|
1519
|
+
private fun updateStateDimensions() {
|
|
1297
1520
|
if (detents.isEmpty()) return
|
|
1298
1521
|
|
|
1299
1522
|
val targetIndex = (if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex)
|
|
@@ -1302,21 +1525,21 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1302
1525
|
val newHeight = minOf(detentCalculator.getDetentHeight(detents[targetIndex]), maxAvailableHeight)
|
|
1303
1526
|
val newWidth = getStateWidth()
|
|
1304
1527
|
|
|
1305
|
-
if (deferShrink && newWidth == lastStateWidth && newHeight < lastStateHeight) return
|
|
1306
|
-
|
|
1307
1528
|
setStateDimensions(newWidth, newHeight)
|
|
1308
1529
|
}
|
|
1309
1530
|
|
|
1310
1531
|
/**
|
|
1311
|
-
* Tracks the sheet's visible height during
|
|
1312
|
-
* animation) so the container resizes with the finger, like iOS. Clamped to the
|
|
1532
|
+
* Tracks the sheet's visible height during drags and resize animations. Clamped to the
|
|
1313
1533
|
* smallest detent — dragging below it slides the sheet out without resizing.
|
|
1314
1534
|
*/
|
|
1315
|
-
private fun
|
|
1535
|
+
private fun updateStateDimensionsForPosition(sheetTop: Int) {
|
|
1316
1536
|
if (detents.isEmpty()) return
|
|
1317
1537
|
|
|
1318
1538
|
val maxAvailableHeight = realScreenHeight - topInset
|
|
1319
|
-
val minHeight = minOf(
|
|
1539
|
+
val minHeight = minOf(
|
|
1540
|
+
detentCalculator.getDetentHeight(detents.first(), includeKeyboard = !hasKeyboardFloor),
|
|
1541
|
+
maxAvailableHeight
|
|
1542
|
+
)
|
|
1320
1543
|
val newHeight = detentCalculator.getVisibleSheetHeight(sheetTop).coerceIn(minHeight, maxAvailableHeight)
|
|
1321
1544
|
|
|
1322
1545
|
setStateDimensions(getStateWidth(), newHeight)
|
|
@@ -1336,9 +1559,27 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1336
1559
|
delegate?.viewControllerDidChangeSize(width, height)
|
|
1337
1560
|
}
|
|
1338
1561
|
|
|
1339
|
-
|
|
1562
|
+
/**
|
|
1563
|
+
* Realtime (non-animated) counterpart of TrueSheetStackManager.updateParentTranslation,
|
|
1564
|
+
* driven by onSlide so the parent tracks this sheet's actual position.
|
|
1565
|
+
*/
|
|
1566
|
+
private fun syncParentTranslation(sheetTop: Int) {
|
|
1567
|
+
val parent = parentSheetView ?: return
|
|
1568
|
+
val minSheetTop = detentCalculator.getSheetTopForDetentIndex(0)
|
|
1569
|
+
parent.syncTranslationForChild(sheetTop, minSheetTop)
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
fun translateSheet(translationY: Int, animated: Boolean = true, onEnd: (() -> Unit)? = null) {
|
|
1340
1573
|
val sheet = sheetView ?: return
|
|
1341
1574
|
|
|
1575
|
+
if (!animated) {
|
|
1576
|
+
sheet.animate().cancel()
|
|
1577
|
+
sheet.translationY = translationY.toFloat()
|
|
1578
|
+
emitChangePositionDelegate(sheet.top + translationY)
|
|
1579
|
+
onEnd?.invoke()
|
|
1580
|
+
return
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1342
1583
|
sheet.animate()
|
|
1343
1584
|
.translationY(translationY.toFloat())
|
|
1344
1585
|
.setDuration(TRANSLATE_ANIMATION_DURATION)
|
|
@@ -1385,41 +1626,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1385
1626
|
// MARK: - RootView Touch Handling
|
|
1386
1627
|
// =============================================================================
|
|
1387
1628
|
|
|
1388
|
-
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
1389
|
-
val footer = containerView?.footerView
|
|
1390
|
-
val action = event.actionMasked
|
|
1391
|
-
|
|
1392
|
-
if (footer != null && footer.isShown) {
|
|
1393
|
-
if (action == MotionEvent.ACTION_DOWN) {
|
|
1394
|
-
val loc = ScreenUtils.getScreenLocation(footer)
|
|
1395
|
-
val x = event.rawX.toInt()
|
|
1396
|
-
val y = event.rawY.toInt()
|
|
1397
|
-
footerOwnsTouchStream = x >= loc[0] &&
|
|
1398
|
-
x <= loc[0] + footer.width &&
|
|
1399
|
-
y >= loc[1] &&
|
|
1400
|
-
y <= loc[1] + footer.height
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
if (footerOwnsTouchStream) {
|
|
1404
|
-
val loc = ScreenUtils.getScreenLocation(footer)
|
|
1405
|
-
val localEvent = MotionEvent.obtain(event)
|
|
1406
|
-
localEvent.setLocation(event.rawX - loc[0], event.rawY - loc[1])
|
|
1407
|
-
val handled = footer.dispatchTouchEvent(localEvent)
|
|
1408
|
-
localEvent.recycle()
|
|
1409
|
-
|
|
1410
|
-
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
1411
|
-
footerOwnsTouchStream = false
|
|
1412
|
-
}
|
|
1413
|
-
if (handled) return true
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
1418
|
-
footerOwnsTouchStream = false
|
|
1419
|
-
}
|
|
1420
|
-
return super.dispatchTouchEvent(event)
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
1629
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
1424
1630
|
eventDispatcher?.let {
|
|
1425
1631
|
if (touchDeduper.shouldDispatch(event)) {
|