@lodev09/react-native-true-sheet 4.0.0-beta.1 → 4.0.0-beta.11
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 +24 -7
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +211 -182
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +14 -19
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -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 +82 -39
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +275 -88
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +26 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +75 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +56 -34
- 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 +1 -1
- package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
- 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 +0 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +52 -29
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +6 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +6 -4
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +7 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -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 +36 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
- package/ios/TrueSheetContainerView.h +14 -7
- package/ios/TrueSheetContainerView.mm +10 -7
- package/ios/TrueSheetContentView.h +31 -15
- package/ios/TrueSheetContentView.mm +148 -188
- package/ios/TrueSheetFooterView.h +6 -0
- package/ios/TrueSheetFooterView.mm +48 -2
- 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 +171 -29
- package/ios/TrueSheetViewController.h +10 -0
- package/ios/TrueSheetViewController.mm +256 -74
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- 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 +149 -34
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +122 -53
- package/lib/module/TrueSheet.web.js.map +1 -1
- 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 +3 -3
- package/lib/module/TrueSheetPeek.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +10 -0
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigation.js +31 -21
- package/lib/module/mocks/navigation.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js +21 -0
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
- package/lib/module/mocks/reanimated.js +1 -1
- package/lib/module/mocks/reanimated.js.map +1 -1
- package/lib/module/navigation/{TrueSheetView.js → TrueSheetNavigatorContent.js} +14 -12
- package/lib/module/navigation/TrueSheetNavigatorContent.js.map +1 -0
- package/lib/module/navigation/actions.js +50 -0
- package/lib/module/navigation/actions.js.map +1 -0
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/{TrueSheetRouter.js → createTrueSheetRouter.js} +11 -24
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -0
- 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 +50 -0
- package/lib/module/navigation/expo-router/index.js.map +1 -0
- 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 +43 -2
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/navigator.js +10 -0
- package/lib/module/navigation/navigator.js.map +1 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +2 -2
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/TrueSheetScreen.js +2 -2
- package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +5 -4
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/navigation/useTrueSheetNavigation.js +1 -1
- package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -1
- package/lib/module/web/dom.js +10 -0
- package/lib/module/web/dom.js.map +1 -0
- package/lib/module/web/vaul/constants.js +4 -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 +10 -4
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +35 -2
- package/lib/module/web/vaul/index.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 +72 -1
- 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/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.map +1 -1
- 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 +4 -0
- 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/navigation.d.ts +5 -1
- package/lib/typescript/src/mocks/navigation.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +22 -0
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -0
- package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -1
- package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts +5 -0
- package/lib/typescript/src/navigation/TrueSheetNavigatorContent.d.ts.map +1 -0
- package/lib/typescript/src/navigation/actions.d.ts +71 -0
- package/lib/typescript/src/navigation/actions.d.ts.map +1 -0
- 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 +3 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -0
- 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 +37 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -0
- 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 +42 -3
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/navigator.d.ts +7 -0
- package/lib/typescript/src/navigation/navigator.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts +1 -1
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +1 -1
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/types.d.ts +3 -4
- package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +5 -6
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +65 -5
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +1 -1
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -1
- package/lib/typescript/src/web/dom.d.ts +7 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +1 -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.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +7 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/package.json +32 -9
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +184 -46
- package/src/TrueSheet.types.ts +77 -1
- package/src/TrueSheet.web.tsx +135 -64
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +4 -3
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +7 -0
- package/src/mocks/navigation.ts +32 -25
- package/src/mocks/navigationExpoRouter.ts +41 -0
- package/src/mocks/reanimated.ts +8 -9
- package/src/navigation/{TrueSheetView.tsx → TrueSheetNavigatorContent.tsx} +24 -17
- package/src/navigation/actions.ts +78 -0
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +194 -0
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/index.ts +90 -0
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +68 -2
- package/src/navigation/navigator.ts +18 -0
- package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +2 -2
- package/src/navigation/screen/TrueSheetScreen.tsx +2 -2
- package/src/navigation/screen/types.ts +4 -6
- package/src/navigation/screen/useSheetScreenState.ts +10 -15
- package/src/navigation/types.ts +99 -16
- package/src/navigation/useTrueSheetNavigation.ts +1 -1
- package/src/web/dom.ts +9 -0
- package/src/web/vaul/constants.ts +4 -0
- package/src/web/vaul/context.ts +1 -0
- package/src/web/vaul/helpers.ts +13 -6
- package/src/web/vaul/index.tsx +46 -1
- package/lib/module/navigation/TrueSheetRouter.js.map +0 -1
- package/lib/module/navigation/TrueSheetView.js.map +0 -1
- package/lib/module/navigation/createTrueSheetNavigator.js +0 -63
- package/lib/module/navigation/createTrueSheetNavigator.js.map +0 -1
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts +0 -57
- package/lib/typescript/src/navigation/TrueSheetRouter.d.ts.map +0 -1
- package/lib/typescript/src/navigation/TrueSheetView.d.ts +0 -10
- package/lib/typescript/src/navigation/TrueSheetView.d.ts.map +0 -1
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts +0 -35
- package/lib/typescript/src/navigation/createTrueSheetNavigator.d.ts.map +0 -1
- package/src/navigation/TrueSheetRouter.ts +0 -234
- package/src/navigation/createTrueSheetNavigator.tsx +0 -93
|
@@ -37,8 +37,10 @@ import com.lodev09.truesheet.core.TrueSheetDimViewDelegate
|
|
|
37
37
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
38
38
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
39
39
|
import com.lodev09.truesheet.core.TrueSheetStackManager
|
|
40
|
+
import com.lodev09.truesheet.utils.Insets
|
|
40
41
|
import com.lodev09.truesheet.utils.KeyboardUtils
|
|
41
42
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
43
|
+
import com.lodev09.truesheet.utils.TouchEventDeduper
|
|
42
44
|
|
|
43
45
|
// =============================================================================
|
|
44
46
|
// MARK: - Data Types & Delegate Protocol
|
|
@@ -53,6 +55,7 @@ interface TrueSheetViewControllerDelegate {
|
|
|
53
55
|
fun viewControllerDidPresent(index: Int, position: Float, detent: Float)
|
|
54
56
|
fun viewControllerWillDismiss()
|
|
55
57
|
fun viewControllerDidDismiss(parent: TrueSheetView?)
|
|
58
|
+
fun viewControllerDidAttemptDismiss()
|
|
56
59
|
fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float)
|
|
57
60
|
fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float)
|
|
58
61
|
fun viewControllerDidDragChange(index: Int, position: Float, detent: Float)
|
|
@@ -121,6 +124,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
121
124
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
122
125
|
private const val DISMISS_DURATION = 200L
|
|
123
126
|
private const val SCREEN_FADE_DURATION = 150L
|
|
127
|
+
|
|
128
|
+
// Mirrors BottomSheetBehavior.HIDE_THRESHOLD — how far past the lowest stop,
|
|
129
|
+
// as a fraction of its height, a release hides a dismissible sheet
|
|
130
|
+
private const val HIDE_THRESHOLD = 0.5f
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
// =============================================================================
|
|
@@ -161,6 +168,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
161
168
|
private var pendingDetentIndex: Int = -1
|
|
162
169
|
|
|
163
170
|
private var interactionState: InteractionState = InteractionState.Idle
|
|
171
|
+
|
|
172
|
+
// A content/header/footer size change arrived mid-drag — reconfiguring then
|
|
173
|
+
// would reset behavior state and kill the gesture, so it's applied on settle.
|
|
174
|
+
private var hasPendingSizeChange = false
|
|
164
175
|
internal var isBeingDismissed = false
|
|
165
176
|
private set
|
|
166
177
|
var wasHiddenByScreen = false
|
|
@@ -174,6 +185,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
174
185
|
// Keyboard State
|
|
175
186
|
private var detentIndexBeforeKeyboard: Int = -1
|
|
176
187
|
private var isKeyboardDismissProgrammatic = false
|
|
188
|
+
|
|
189
|
+
// Sheet top when a keyboard-floor drag was released; -1 once the settle target is known
|
|
190
|
+
private var keyboardSettleStartTop = -1
|
|
177
191
|
private var focusedViewBeforeBlur: View? = null
|
|
178
192
|
|
|
179
193
|
// Promises
|
|
@@ -199,6 +213,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
199
213
|
// the finger drifts outside the footer's rect mid-gesture.
|
|
200
214
|
private var footerOwnsTouchStream = false
|
|
201
215
|
|
|
216
|
+
private val touchDeduper = TouchEventDeduper()
|
|
217
|
+
|
|
202
218
|
private val eventDispatcher
|
|
203
219
|
get() = delegate?.eventDispatcher
|
|
204
220
|
|
|
@@ -224,6 +240,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
224
240
|
behavior?.scrollingExpandsSheet = value?.scrollingExpandsSheet ?: true
|
|
225
241
|
}
|
|
226
242
|
|
|
243
|
+
// React tag of the user-provided scrollable within the content (see the scrollableRef prop)
|
|
244
|
+
var scrollableHandle: Int = -1
|
|
245
|
+
|
|
227
246
|
override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
228
247
|
set(value) {
|
|
229
248
|
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
@@ -267,9 +286,29 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
267
286
|
val screenWidth: Int
|
|
268
287
|
get() = ScreenUtils.getScreenWidth(reactContext)
|
|
269
288
|
|
|
289
|
+
// Cached per layout/configuration pass — the WindowManager and
|
|
290
|
+
// rootWindowInsets queries behind these allocate, and they're read many
|
|
291
|
+
// times per animation frame (position emission, detent interpolation,
|
|
292
|
+
// stacked sheet sync)
|
|
293
|
+
private var cachedRealScreenHeight: Int = 0
|
|
294
|
+
private var cachedInsets: Insets? = null
|
|
295
|
+
|
|
296
|
+
private fun invalidateScreenMetrics() {
|
|
297
|
+
cachedRealScreenHeight = 0
|
|
298
|
+
cachedInsets = null
|
|
299
|
+
}
|
|
300
|
+
|
|
270
301
|
// Includes system bars for accurate positioning
|
|
271
302
|
override val realScreenHeight: Int
|
|
272
|
-
get()
|
|
303
|
+
get() {
|
|
304
|
+
if (cachedRealScreenHeight == 0) {
|
|
305
|
+
cachedRealScreenHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
306
|
+
}
|
|
307
|
+
return cachedRealScreenHeight
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private val insets: Insets
|
|
311
|
+
get() = cachedInsets ?: ScreenUtils.getInsets(reactContext).also { cachedInsets = it }
|
|
273
312
|
|
|
274
313
|
// Content Measurements
|
|
275
314
|
// Cached values used during dismiss when container is unmounted
|
|
@@ -306,16 +345,30 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
306
345
|
private val isKeyboardTransitioning: Boolean
|
|
307
346
|
get() = keyboardObserver?.isTransitioning ?: false
|
|
308
347
|
|
|
348
|
+
// Keyboard is up or animating — detents are (or are about to be) keyboard-shifted
|
|
349
|
+
val isKeyboardActive: Boolean
|
|
350
|
+
get() = keyboardInset > 0 || isKeyboardTransitioning
|
|
351
|
+
|
|
352
|
+
// While the keyboard holds the sheet up, the keyboard-free first detent is the
|
|
353
|
+
// collapsed stop — landing there dismisses the keyboard, and (when dismissible)
|
|
354
|
+
// dragging more than half its height below it hides the sheet too, like iOS.
|
|
355
|
+
override val hasKeyboardFloor: Boolean
|
|
356
|
+
get() = keyboardInset > 0
|
|
357
|
+
|
|
358
|
+
// The detent JS last heard about — the keyboard expansion is never reported
|
|
359
|
+
private val knownDetentIndex: Int
|
|
360
|
+
get() = if (detentIndexBeforeKeyboard >= 0) detentIndexBeforeKeyboard else currentDetentIndex
|
|
361
|
+
|
|
309
362
|
fun isFocusedViewWithinSheet(): Boolean {
|
|
310
363
|
val sheet = sheetView ?: return false
|
|
311
364
|
return keyboardObserver?.isFocusedViewWithinSheet(sheet) ?: false
|
|
312
365
|
}
|
|
313
366
|
|
|
314
367
|
val bottomInset: Int
|
|
315
|
-
get() = if (edgeToEdgeEnabled)
|
|
368
|
+
get() = if (edgeToEdgeEnabled) insets.bottom else 0
|
|
316
369
|
|
|
317
370
|
override val topInset: Int
|
|
318
|
-
get() = if (edgeToEdgeEnabled)
|
|
371
|
+
get() = if (edgeToEdgeEnabled) insets.top else 0
|
|
319
372
|
|
|
320
373
|
override val contentBottomInset: Int
|
|
321
374
|
get() = if (insetAdjustment == TrueSheetInsetAdjustment.AUTOMATIC) bottomInset else 0
|
|
@@ -328,6 +381,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
328
381
|
}
|
|
329
382
|
|
|
330
383
|
// Sheet State
|
|
384
|
+
val isSettling: Boolean
|
|
385
|
+
get() = behavior?.state == BottomSheetBehavior.STATE_SETTLING
|
|
386
|
+
|
|
331
387
|
val isExpanded: Boolean
|
|
332
388
|
get() {
|
|
333
389
|
val sheetTop = sheetView?.top ?: return false
|
|
@@ -389,6 +445,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
389
445
|
sheetView = null
|
|
390
446
|
|
|
391
447
|
interactionState = InteractionState.Idle
|
|
448
|
+
hasPendingSizeChange = false
|
|
392
449
|
isBeingDismissed = false
|
|
393
450
|
isPresented = false
|
|
394
451
|
isSheetVisible = false
|
|
@@ -437,6 +494,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
437
494
|
// =============================================================================
|
|
438
495
|
|
|
439
496
|
override fun coordinatorLayoutDidLayout(changed: Boolean) {
|
|
497
|
+
if (changed) invalidateScreenMetrics()
|
|
498
|
+
|
|
440
499
|
// Reposition footer when layout changes
|
|
441
500
|
if (isPresented && changed) {
|
|
442
501
|
positionFooter()
|
|
@@ -444,6 +503,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
444
503
|
}
|
|
445
504
|
|
|
446
505
|
override fun coordinatorLayoutDidChangeConfiguration() {
|
|
506
|
+
invalidateScreenMetrics()
|
|
447
507
|
if (!isPresented) return
|
|
448
508
|
|
|
449
509
|
sheetView?.updateGravity()
|
|
@@ -455,6 +515,28 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
455
515
|
override fun findScrollView(): ViewGroup? = containerView?.contentView?.findScrollView()
|
|
456
516
|
override fun findSheetView(): TrueSheetBottomSheetView? = sheetView
|
|
457
517
|
|
|
518
|
+
override fun coordinatorLayoutDidPullDown(overdragPx: Int, keyboardWasVisible: Boolean) {
|
|
519
|
+
if (!isPresented || dismissible || !draggable) return
|
|
520
|
+
val sheet = sheetView ?: return
|
|
521
|
+
|
|
522
|
+
// Only a pull clamped at the lowest stop is a blocked drag-to-dismiss — a
|
|
523
|
+
// child that swallowed the gesture higher up isn't
|
|
524
|
+
val lowestTop = detentCalculator.getLowestSheetTop()
|
|
525
|
+
if (sheet.top < lowestTop) return
|
|
526
|
+
|
|
527
|
+
if (keyboardWasVisible) {
|
|
528
|
+
// A sheet that couldn't move at all (first detent already full height) never
|
|
529
|
+
// began a drag, so the keyboard is still up — dismiss it here
|
|
530
|
+
if (hasKeyboardFloor && !isKeyboardDismissProgrammatic) commitDetentAndDismissKeyboard(0, sheet)
|
|
531
|
+
|
|
532
|
+
// Past the floor by the distance that would hide a dismissible sheet, like iOS
|
|
533
|
+
val floorHeight = realScreenHeight - lowestTop
|
|
534
|
+
if (overdragPx < floorHeight * HIDE_THRESHOLD) return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
538
|
+
}
|
|
539
|
+
|
|
458
540
|
// =============================================================================
|
|
459
541
|
// MARK: - TrueSheetDimViewDelegate
|
|
460
542
|
// =============================================================================
|
|
@@ -492,8 +574,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
492
574
|
|
|
493
575
|
override fun bottomSheetViewDidTapGrabber() {
|
|
494
576
|
val nextIndex = (currentDetentIndex + 1) % detents.size
|
|
495
|
-
if (nextIndex == 0 && detents.size == 1
|
|
496
|
-
|
|
577
|
+
if (nextIndex == 0 && detents.size == 1) {
|
|
578
|
+
if (dismissible) {
|
|
579
|
+
dismiss(animated = true)
|
|
580
|
+
} else {
|
|
581
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
582
|
+
}
|
|
497
583
|
} else {
|
|
498
584
|
setStateForDetentIndex(nextIndex)
|
|
499
585
|
}
|
|
@@ -510,6 +596,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
510
596
|
setStateForDetentIndex(currentDetentIndex - 1)
|
|
511
597
|
} else if (dismissible) {
|
|
512
598
|
dismiss(animated = true)
|
|
599
|
+
} else {
|
|
600
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
513
601
|
}
|
|
514
602
|
}
|
|
515
603
|
|
|
@@ -523,7 +611,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
523
611
|
}
|
|
524
612
|
|
|
525
613
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
526
|
-
handleSlide(sheetView
|
|
614
|
+
handleSlide(sheetView)
|
|
527
615
|
}
|
|
528
616
|
}
|
|
529
617
|
|
|
@@ -552,27 +640,40 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
552
640
|
}
|
|
553
641
|
}
|
|
554
642
|
|
|
555
|
-
private fun handleSlide(sheetView: View
|
|
643
|
+
private fun handleSlide(sheetView: View) {
|
|
556
644
|
// Skip during dismiss animation
|
|
557
645
|
if (isBeingDismissed) return
|
|
558
646
|
|
|
559
647
|
val behavior = behavior ?: return
|
|
560
648
|
|
|
561
649
|
when (behavior.state) {
|
|
562
|
-
BottomSheetBehavior.STATE_DRAGGING
|
|
563
|
-
|
|
650
|
+
BottomSheetBehavior.STATE_DRAGGING -> handleDragChange(sheetView)
|
|
651
|
+
|
|
652
|
+
BottomSheetBehavior.STATE_SETTLING -> {
|
|
653
|
+
handleDragChange(sheetView)
|
|
654
|
+
dismissKeyboardForSettleTarget(sheetView)
|
|
655
|
+
}
|
|
564
656
|
|
|
565
657
|
else -> { }
|
|
566
658
|
}
|
|
567
659
|
|
|
568
660
|
emitChangePositionDelegate(sheetView.top)
|
|
569
661
|
|
|
662
|
+
// Drive the parent's stack translation from our actual frame position —
|
|
663
|
+
// an independently-timed animation lags behind (e.g. auto detent shrink)
|
|
664
|
+
// and reveals the parent above this sheet's top edge. Skipped while
|
|
665
|
+
// dragging so the parent stays put during drag-to-dismiss, and while the
|
|
666
|
+
// keyboard is active so growing for it doesn't move the parent (like iOS).
|
|
667
|
+
if (!isPresentAnimating && interactionState !is InteractionState.Dragging && !isKeyboardActive) {
|
|
668
|
+
syncParentTranslation(sheetView.top)
|
|
669
|
+
}
|
|
670
|
+
|
|
570
671
|
// Position on every slide frame — during keyboard transitions the IME
|
|
571
672
|
// animation callbacks are sparser than the sheet's settle frames, and the
|
|
572
673
|
// footer rides the sheet between them, jittering against the keyboard.
|
|
573
674
|
// positionFooter derives from current state, so double-driving it with
|
|
574
675
|
// keyboardDidChangeHeight is harmless.
|
|
575
|
-
positionFooter(
|
|
676
|
+
positionFooter()
|
|
576
677
|
|
|
577
678
|
if (!isKeyboardTransitioning) {
|
|
578
679
|
updateDimAmount(sheetView.top)
|
|
@@ -582,11 +683,22 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
582
683
|
private fun handleStateSettled(sheetView: View, newState: Int) {
|
|
583
684
|
if (interactionState is InteractionState.Reconfiguring) return
|
|
584
685
|
|
|
686
|
+
// A release already at a stop settles without a slide frame
|
|
687
|
+
keyboardSettleStartTop = -1
|
|
688
|
+
|
|
585
689
|
// Sheet has reached a stable state — any in-flight resize target is now stale,
|
|
586
690
|
// whether settled at the target, interrupted by drag, or superseded.
|
|
587
691
|
pendingDetentIndex = -1
|
|
588
692
|
|
|
589
|
-
val index = detentCalculator.getDetentIndexForState(newState) ?:
|
|
693
|
+
val index = detentCalculator.getDetentIndexForState(newState) ?: run {
|
|
694
|
+
// Unmapped settle — end any drag anyway so deferred size changes aren't
|
|
695
|
+
// dropped forever (a leaked Dragging state skips every reconfigure)
|
|
696
|
+
if (interactionState is InteractionState.Dragging) {
|
|
697
|
+
interactionState = InteractionState.Idle
|
|
698
|
+
}
|
|
699
|
+
flushPendingSizeChange()
|
|
700
|
+
return
|
|
701
|
+
}
|
|
590
702
|
val position = getPositionDpForView(sheetView)
|
|
591
703
|
val detentInfo = DetentInfo(index, position)
|
|
592
704
|
|
|
@@ -597,18 +709,26 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
597
709
|
return
|
|
598
710
|
}
|
|
599
711
|
|
|
712
|
+
val wasDragging = interactionState is InteractionState.Dragging
|
|
713
|
+
|
|
600
714
|
when (interactionState) {
|
|
601
715
|
is InteractionState.Dragging -> {
|
|
602
716
|
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
603
717
|
delegate?.viewControllerDidDragEnd(detentInfo.index, detentInfo.position, detent)
|
|
604
718
|
|
|
605
|
-
// Skip detent change if keyboard inset is still active —
|
|
606
|
-
//
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
719
|
+
// Skip detent change if keyboard inset is still active — the keyboard floor
|
|
720
|
+
// landing below commits the detent instead.
|
|
721
|
+
if (keyboardInset == 0) {
|
|
722
|
+
val changed = detentInfo.index != knownDetentIndex
|
|
723
|
+
detentIndexBeforeKeyboard = -1
|
|
724
|
+
if (detentInfo.index != currentDetentIndex) {
|
|
725
|
+
currentDetentIndex = detentInfo.index
|
|
726
|
+
setupDimmedBackground()
|
|
727
|
+
}
|
|
728
|
+
if (changed) {
|
|
729
|
+
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
730
|
+
this@TrueSheetViewController.sheetView?.updateGrabberAccessibilityValue(detentInfo.index, detents.size)
|
|
731
|
+
}
|
|
612
732
|
}
|
|
613
733
|
|
|
614
734
|
interactionState = InteractionState.Idle
|
|
@@ -626,14 +746,29 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
626
746
|
}
|
|
627
747
|
}
|
|
628
748
|
|
|
629
|
-
|
|
630
|
-
|
|
749
|
+
if (hasKeyboardFloor && wasDragging && newState != BottomSheetBehavior.STATE_EXPANDED) {
|
|
750
|
+
// Dragged down onto a keyboard-free stop (the floor or the keyboard detent) —
|
|
751
|
+
// dismiss the keyboard and commit that detent. keyboardWillHide reconfigures
|
|
752
|
+
// the detents (and the container) to it.
|
|
753
|
+
commitDetentAndDismissKeyboard(index, sheetView)
|
|
754
|
+
} else {
|
|
755
|
+
// Settled — resize the container to the settled detent (applies deferred shrinks)
|
|
756
|
+
updateStateDimensions()
|
|
757
|
+
}
|
|
631
758
|
|
|
632
759
|
if (!isKeyboardTransitioning) {
|
|
633
760
|
updateDimAmount(animated = true)
|
|
634
761
|
}
|
|
635
762
|
|
|
636
763
|
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
764
|
+
|
|
765
|
+
flushPendingSizeChange()
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
private fun flushPendingSizeChange() {
|
|
769
|
+
if (!hasPendingSizeChange) return
|
|
770
|
+
hasPendingSizeChange = false
|
|
771
|
+
setupSheetDetentsForSizeChange()
|
|
637
772
|
}
|
|
638
773
|
|
|
639
774
|
// =============================================================================
|
|
@@ -695,6 +830,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
695
830
|
val sheet = this.sheetView ?: return
|
|
696
831
|
if (isPresented) return
|
|
697
832
|
|
|
833
|
+
invalidateScreenMetrics()
|
|
698
834
|
shouldAnimatePresent = animated
|
|
699
835
|
currentDetentIndex = detentIndex
|
|
700
836
|
interactionState = InteractionState.Idle
|
|
@@ -732,9 +868,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
732
868
|
fun resize(detentIndex: Int) {
|
|
733
869
|
if (!isPresented) return
|
|
734
870
|
|
|
735
|
-
// Commit the new index so keyboardWillHide restores to it instead of the stale
|
|
871
|
+
// Commit the new index so keyboardWillHide restores to it instead of the stale
|
|
872
|
+
// one; the sheet keeps avoiding the keyboard meanwhile
|
|
736
873
|
if (detentIndexBeforeKeyboard >= 0) {
|
|
737
874
|
detentIndexBeforeKeyboard = detentIndex
|
|
875
|
+
if (hasKeyboardFloor) {
|
|
876
|
+
resizePromise?.invoke()
|
|
877
|
+
resizePromise = null
|
|
878
|
+
return
|
|
879
|
+
}
|
|
738
880
|
}
|
|
739
881
|
|
|
740
882
|
pendingDetentIndex = detentIndex
|
|
@@ -863,11 +1005,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
863
1005
|
|
|
864
1006
|
val maxAvailableHeight = realScreenHeight - topInset
|
|
865
1007
|
|
|
866
|
-
|
|
1008
|
+
// With a keyboard floor only the last detent stays keyboard-shifted; the
|
|
1009
|
+
// collapsed and half stops are the keyboard-free first and second detents,
|
|
1010
|
+
// so a short drag down from the keyboard-expanded sheet rests at the second
|
|
1011
|
+
// and a long one at the first, like iOS
|
|
1012
|
+
val peekHeight = minOf(detentCalculator.getDetentHeight(detents[0], includeKeyboard = !hasKeyboardFloor), maxAvailableHeight)
|
|
867
1013
|
|
|
868
1014
|
val halfExpandedDetentHeight = when (detents.size) {
|
|
869
1015
|
1 -> peekHeight
|
|
870
|
-
else -> detentCalculator.getDetentHeight(detents[1])
|
|
1016
|
+
else -> detentCalculator.getDetentHeight(detents[1], includeKeyboard = !hasKeyboardFloor)
|
|
871
1017
|
}
|
|
872
1018
|
|
|
873
1019
|
val maxDetentHeight = minOf(detentCalculator.getDetentHeight(detents.last()), maxAvailableHeight)
|
|
@@ -877,8 +1023,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
877
1023
|
|
|
878
1024
|
val expandedOffset = realScreenHeight - maxDetentHeight
|
|
879
1025
|
|
|
880
|
-
// fitToContents works better with <= 2 detents when no expanded offset
|
|
881
|
-
|
|
1026
|
+
// fitToContents works better with <= 2 detents when no expanded offset, but
|
|
1027
|
+
// it drops the half stop the keyboard floor relies on
|
|
1028
|
+
val fitToContents = !hasKeyboardFloor && detents.size < 3 && expandedOffset == 0
|
|
882
1029
|
|
|
883
1030
|
configureDetents(
|
|
884
1031
|
behavior = behavior,
|
|
@@ -919,10 +1066,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
919
1066
|
}
|
|
920
1067
|
|
|
921
1068
|
fun setupSheetDetentsForSizeChange() {
|
|
922
|
-
//
|
|
1069
|
+
// Defer while dragging — the size change is driven by the drag itself (the
|
|
923
1070
|
// container tracks the sheet's visible height), and reconfiguring resets
|
|
924
|
-
// behavior state, killing the gesture.
|
|
925
|
-
|
|
1071
|
+
// behavior state, killing the gesture. Applied on settle so a real content
|
|
1072
|
+
// change mid-drag (e.g. items removed from a list) isn't lost.
|
|
1073
|
+
if (interactionState is InteractionState.Dragging) {
|
|
1074
|
+
hasPendingSizeChange = true
|
|
1075
|
+
return
|
|
1076
|
+
}
|
|
1077
|
+
hasPendingSizeChange = false
|
|
926
1078
|
|
|
927
1079
|
setupSheetDetents()
|
|
928
1080
|
positionFooter()
|
|
@@ -978,7 +1130,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
978
1130
|
if (contentHeight == 0) return
|
|
979
1131
|
|
|
980
1132
|
// While keyboard is active or transitioning, use the target detent position for dim
|
|
981
|
-
val top = if (
|
|
1133
|
+
val top = if (isKeyboardActive) {
|
|
982
1134
|
detentCalculator.getSheetTopForDetentIndex(currentDetentIndex)
|
|
983
1135
|
} else {
|
|
984
1136
|
val keyboardOffset = if (isBeingDismissed) 0 else currentKeyboardInset
|
|
@@ -1003,13 +1155,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1003
1155
|
|
|
1004
1156
|
var footerKeyboardOffset: Int = 0
|
|
1005
1157
|
|
|
1006
|
-
fun positionFooter(
|
|
1158
|
+
fun positionFooter() {
|
|
1007
1159
|
if (!isPresented) return
|
|
1008
1160
|
val footerView = containerView?.footerView ?: return
|
|
1009
1161
|
val sheet = sheetView ?: return
|
|
1010
1162
|
|
|
1011
|
-
val keyboardShift = if (currentKeyboardInset > 0) maxOf(0, currentKeyboardInset + footerKeyboardOffset) else 0
|
|
1012
|
-
|
|
1013
1163
|
// A relative footer is laid out by Yoga (pinned to the container's bottom
|
|
1014
1164
|
// edge) and stays in the layout flow behind the keyboard — only an
|
|
1015
1165
|
// absolute footer floats above it
|
|
@@ -1018,20 +1168,23 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1018
1168
|
return
|
|
1019
1169
|
}
|
|
1020
1170
|
|
|
1171
|
+
// The footer's absorbed safe-area inset rides below the keyboard's top
|
|
1172
|
+
// edge — its content stays flush with the keyboard without dropping the
|
|
1173
|
+
// inset padding from the layout, which would jump at the transition edges
|
|
1174
|
+
val keyboardShift = if (currentKeyboardInset > 0) {
|
|
1175
|
+
maxOf(0, currentKeyboardInset + footerKeyboardOffset - footerView.bottomInset)
|
|
1176
|
+
} else {
|
|
1177
|
+
0
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1021
1180
|
val footerHeight = footerView.height
|
|
1022
1181
|
val sheetHeight = sheet.height
|
|
1023
|
-
val sheetTop = sheet.top
|
|
1024
1182
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
// adjustment would sink the footer behind the keyboard, snapping back up
|
|
1031
|
-
// when the keyboard detents reset on release.
|
|
1032
|
-
if (slideOffset != null && slideOffset < 0 && keyboardShift == 0) {
|
|
1033
|
-
footerY -= (footerHeight * slideOffset)
|
|
1034
|
-
}
|
|
1183
|
+
// Pinned to the screen bottom (above the keyboard) while the sheet sits at
|
|
1184
|
+
// or above its lowest stop; past it the footer rides down with the sheet
|
|
1185
|
+
// instead, like iOS
|
|
1186
|
+
val anchorTop = minOf(sheet.top, detentCalculator.getLowestSheetTop())
|
|
1187
|
+
val footerY = (sheetHeight - anchorTop - footerHeight - keyboardShift).toFloat()
|
|
1035
1188
|
|
|
1036
1189
|
// Clamp to prevent footer going above safe area
|
|
1037
1190
|
val maxAllowedY = (sheetHeight - topInset - footerHeight).toFloat()
|
|
@@ -1076,9 +1229,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1076
1229
|
delegate = object : TrueSheetKeyboardObserverDelegate {
|
|
1077
1230
|
override fun keyboardWillShow(height: Int) {
|
|
1078
1231
|
if (!shouldHandleKeyboard()) return
|
|
1079
|
-
// An absolute footer rises above the keyboard, so it drops the bottom
|
|
1080
|
-
// inset padding — before detents are configured against its height
|
|
1081
|
-
if (absoluteFooter) containerView?.footerView?.keyboardVisible = true
|
|
1082
1232
|
// If a resize is in flight, restore to its target — not the stale current
|
|
1083
1233
|
detentIndexBeforeKeyboard = if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex
|
|
1084
1234
|
pendingDetentIndex = -1
|
|
@@ -1091,7 +1241,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1091
1241
|
|
|
1092
1242
|
override fun keyboardWillHide() {
|
|
1093
1243
|
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
1094
|
-
|
|
1244
|
+
// Mid-drag (the drag itself dismissed the keyboard) the release decides
|
|
1245
|
+
// the detent and a reconfigure would kill the gesture — the floor stops
|
|
1246
|
+
// stay put until the settle flushes the pending reconfigure
|
|
1247
|
+
if (interactionState is InteractionState.Dragging) {
|
|
1248
|
+
hasPendingSizeChange = true
|
|
1249
|
+
return
|
|
1250
|
+
}
|
|
1095
1251
|
val restoring = !isBeingDismissed && detentIndexBeforeKeyboard >= 0
|
|
1096
1252
|
|
|
1097
1253
|
// Skip reconfigure during interactive keyboard dismiss (e.g. keyboardDismissMode="on-drag")
|
|
@@ -1116,9 +1272,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1116
1272
|
|
|
1117
1273
|
override fun keyboardDidHide() {
|
|
1118
1274
|
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
1119
|
-
containerView?.footerView?.keyboardVisible = false
|
|
1120
|
-
detentIndexBeforeKeyboard = -1
|
|
1121
1275
|
isKeyboardDismissProgrammatic = false
|
|
1276
|
+
// Keep detentIndexBeforeKeyboard for the settle's detent-change check
|
|
1277
|
+
if (interactionState is InteractionState.Dragging) {
|
|
1278
|
+
hasPendingSizeChange = true
|
|
1279
|
+
return
|
|
1280
|
+
}
|
|
1281
|
+
detentIndexBeforeKeyboard = -1
|
|
1122
1282
|
setupSheetDetents(applyState = false)
|
|
1123
1283
|
positionFooter()
|
|
1124
1284
|
updateDimAmount(
|
|
@@ -1138,7 +1298,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1138
1298
|
// Handle case where keyboard is already visible and focus moves into the sheet
|
|
1139
1299
|
if (!shouldHandleKeyboard()) return
|
|
1140
1300
|
if (detentIndexBeforeKeyboard < 0 && (keyboardObserver?.currentHeight ?: 0) > 0) {
|
|
1141
|
-
if (absoluteFooter) containerView?.footerView?.keyboardVisible = true
|
|
1142
1301
|
detentIndexBeforeKeyboard = currentDetentIndex
|
|
1143
1302
|
currentDetentIndex = detents.size - 1
|
|
1144
1303
|
setupSheetDetents()
|
|
@@ -1166,8 +1325,28 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1166
1325
|
detentIndexBeforeKeyboard = -1
|
|
1167
1326
|
}
|
|
1168
1327
|
|
|
1328
|
+
private fun handleSettling(sheetView: View) {
|
|
1329
|
+
// A keyboard-floor drag was released — the first settle frame reveals the target
|
|
1330
|
+
keyboardSettleStartTop = if (hasKeyboardFloor && interactionState is InteractionState.Dragging) sheetView.top else -1
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Dismisses the keyboard as soon as a released drag is known to land on a
|
|
1335
|
+
* keyboard-free stop, rather than waiting for the landing. BottomSheetBehavior
|
|
1336
|
+
* keeps its target private, so read it off the first settle frame: heading
|
|
1337
|
+
* down means the half stop or the floor; heading up from below the half stop
|
|
1338
|
+
* means the half stop; only heading up from above it means the expanded stop.
|
|
1339
|
+
*/
|
|
1340
|
+
private fun dismissKeyboardForSettleTarget(sheetView: View) {
|
|
1341
|
+
if (keyboardSettleStartTop < 0) return
|
|
1342
|
+
val startTop = keyboardSettleStartTop
|
|
1343
|
+
keyboardSettleStartTop = -1
|
|
1344
|
+
|
|
1345
|
+
val headingToKeyboardFreeStop = sheetView.top > startTop || startTop > detentCalculator.getKeyboardHalfStopTop()
|
|
1346
|
+
if (headingToKeyboardFreeStop && shouldHandleKeyboard()) dismissKeyboard()
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1169
1349
|
private fun handleDragBegin(sheetView: View) {
|
|
1170
|
-
detentIndexBeforeKeyboard = -1
|
|
1171
1350
|
pendingDetentIndex = -1
|
|
1172
1351
|
|
|
1173
1352
|
val position = getPositionDpForView(sheetView)
|
|
@@ -1176,36 +1355,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1176
1355
|
interactionState = InteractionState.Dragging(startTop = sheetView.top)
|
|
1177
1356
|
}
|
|
1178
1357
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1358
|
+
// Commits a non-keyboard detent and hides the keyboard; keyboardWillHide then
|
|
1359
|
+
// reconfigures the detents so the sheet settles at the committed index.
|
|
1360
|
+
private fun commitDetentAndDismissKeyboard(index: Int, sheetView: View) {
|
|
1361
|
+
val changed = index != knownDetentIndex
|
|
1362
|
+
detentIndexBeforeKeyboard = -1
|
|
1363
|
+
currentDetentIndex = index
|
|
1364
|
+
dismissKeyboard()
|
|
1365
|
+
setupDimmedBackground()
|
|
1182
1366
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
for (i in detents.indices) {
|
|
1190
|
-
val nonKeyboardHeight = minOf(detentCalculator.getDetentHeight(detents[i], includeKeyboard = false), maxAvailableHeight)
|
|
1191
|
-
val nonKeyboardTop = realScreenHeight - nonKeyboardHeight
|
|
1192
|
-
val midpoint = keyboardTop + (nonKeyboardTop - keyboardTop) / 2
|
|
1193
|
-
|
|
1194
|
-
if (sheetView.top >= midpoint) {
|
|
1195
|
-
// Target position matches the keyboard-expanded position — keep keyboard open
|
|
1196
|
-
if (nonKeyboardTop == keyboardTop) break
|
|
1197
|
-
|
|
1198
|
-
detentIndexBeforeKeyboard = -1
|
|
1199
|
-
currentDetentIndex = i
|
|
1200
|
-
dismissKeyboard()
|
|
1201
|
-
setupDimmedBackground()
|
|
1202
|
-
|
|
1203
|
-
val position = getPositionDpForView(sheetView)
|
|
1204
|
-
val detent = detentCalculator.getDetentValueForIndex(i)
|
|
1205
|
-
delegate?.viewControllerDidChangeDetent(i, position, detent)
|
|
1206
|
-
break
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1367
|
+
if (!changed) return
|
|
1368
|
+
val position = getPositionDpForView(sheetView)
|
|
1369
|
+
val detent = detentCalculator.getDetentValueForIndex(index)
|
|
1370
|
+
delegate?.viewControllerDidChangeDetent(index, position, detent)
|
|
1371
|
+
this.sheetView?.updateGrabberAccessibilityValue(index, detents.size)
|
|
1209
1372
|
}
|
|
1210
1373
|
|
|
1211
1374
|
private fun handleDragChange(sheetView: View) {
|
|
@@ -1253,8 +1416,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1253
1416
|
lastEmittedPositionPx = currentTop
|
|
1254
1417
|
val visibleHeight = realScreenHeight - currentTop
|
|
1255
1418
|
val position = detentCalculator.getPositionDp(visibleHeight)
|
|
1256
|
-
val interpolatedIndex = detentCalculator.
|
|
1257
|
-
val detent = detentCalculator.getInterpolatedDetentForPosition(currentTop)
|
|
1419
|
+
val (interpolatedIndex, detent) = detentCalculator.getInterpolatedPositionInfo(currentTop)
|
|
1258
1420
|
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
1259
1421
|
}
|
|
1260
1422
|
|
|
@@ -1262,9 +1424,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1262
1424
|
* Updates position emission, footer, and dim amount together.
|
|
1263
1425
|
* This pattern is commonly used during animations and state changes.
|
|
1264
1426
|
*/
|
|
1265
|
-
private fun updateSheetVisuals(effectiveTop: Int
|
|
1427
|
+
private fun updateSheetVisuals(effectiveTop: Int) {
|
|
1266
1428
|
emitChangePositionDelegate(effectiveTop)
|
|
1267
|
-
positionFooter(
|
|
1429
|
+
positionFooter()
|
|
1268
1430
|
updateDimAmount(effectiveTop)
|
|
1269
1431
|
}
|
|
1270
1432
|
|
|
@@ -1313,7 +1475,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1313
1475
|
if (detents.isEmpty()) return
|
|
1314
1476
|
|
|
1315
1477
|
val maxAvailableHeight = realScreenHeight - topInset
|
|
1316
|
-
val minHeight = minOf(
|
|
1478
|
+
val minHeight = minOf(
|
|
1479
|
+
detentCalculator.getDetentHeight(detents.first(), includeKeyboard = !hasKeyboardFloor),
|
|
1480
|
+
maxAvailableHeight
|
|
1481
|
+
)
|
|
1317
1482
|
val newHeight = detentCalculator.getVisibleSheetHeight(sheetTop).coerceIn(minHeight, maxAvailableHeight)
|
|
1318
1483
|
|
|
1319
1484
|
setStateDimensions(getStateWidth(), newHeight)
|
|
@@ -1333,9 +1498,27 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1333
1498
|
delegate?.viewControllerDidChangeSize(width, height)
|
|
1334
1499
|
}
|
|
1335
1500
|
|
|
1336
|
-
|
|
1501
|
+
/**
|
|
1502
|
+
* Realtime (non-animated) counterpart of TrueSheetStackManager.updateParentTranslation,
|
|
1503
|
+
* driven by onSlide so the parent tracks this sheet's actual position.
|
|
1504
|
+
*/
|
|
1505
|
+
private fun syncParentTranslation(sheetTop: Int) {
|
|
1506
|
+
val parent = parentSheetView ?: return
|
|
1507
|
+
val minSheetTop = detentCalculator.getSheetTopForDetentIndex(0)
|
|
1508
|
+
parent.syncTranslationForChild(sheetTop, minSheetTop)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
fun translateSheet(translationY: Int, animated: Boolean = true, onEnd: (() -> Unit)? = null) {
|
|
1337
1512
|
val sheet = sheetView ?: return
|
|
1338
1513
|
|
|
1514
|
+
if (!animated) {
|
|
1515
|
+
sheet.animate().cancel()
|
|
1516
|
+
sheet.translationY = translationY.toFloat()
|
|
1517
|
+
emitChangePositionDelegate(sheet.top + translationY)
|
|
1518
|
+
onEnd?.invoke()
|
|
1519
|
+
return
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1339
1522
|
sheet.animate()
|
|
1340
1523
|
.translationY(translationY.toFloat())
|
|
1341
1524
|
.setDuration(TRANSLATE_ANIMATION_DURATION)
|
|
@@ -1419,7 +1602,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1419
1602
|
|
|
1420
1603
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
1421
1604
|
eventDispatcher?.let {
|
|
1422
|
-
|
|
1605
|
+
if (touchDeduper.shouldDispatch(event)) {
|
|
1606
|
+
jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
|
|
1607
|
+
}
|
|
1423
1608
|
jsPointerDispatcher.handleMotionEvent(event, it, true)
|
|
1424
1609
|
}
|
|
1425
1610
|
return super.onInterceptTouchEvent(event)
|
|
@@ -1433,7 +1618,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1433
1618
|
|
|
1434
1619
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
1435
1620
|
eventDispatcher?.let {
|
|
1436
|
-
|
|
1621
|
+
if (touchDeduper.shouldDispatch(event)) {
|
|
1622
|
+
jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
|
|
1623
|
+
}
|
|
1437
1624
|
jsPointerDispatcher.handleMotionEvent(event, it, false)
|
|
1438
1625
|
}
|
|
1439
1626
|
super.onTouchEvent(event)
|