@lodev09/react-native-true-sheet 4.0.0-beta.0 → 4.0.0-beta.10
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/LICENSE +1 -1
- package/README.md +62 -30
- package/RNTrueSheet.podspec +10 -2
- package/android/build.gradle +24 -2
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +189 -47
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +401 -19
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +18 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +145 -21
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterViewManager.kt +14 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderView.kt +38 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderViewManager.kt +28 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +84 -24
- 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 +6 -4
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekView.kt +72 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPeekViewManager.kt +33 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +63 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +564 -226
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +1399 -598
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +161 -29
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +63 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +36 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +255 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +375 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +299 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +162 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +180 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetKeyboardObserver.kt +207 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +220 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +127 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +84 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +93 -67
- package/android/src/main/java/com/lodev09/truesheet/utils/TouchEventDeduper.kt +31 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +42 -0
- package/android/src/main/jni/CMakeLists.txt +63 -0
- package/android/src/main/jni/TrueSheetSpec.h +20 -0
- package/android/src/main/jni/TrueSheetStateUpdater.cpp +96 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +16 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +68 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +33 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +11 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +39 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +24 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +63 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +28 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +11 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +41 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +18 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +16 -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/TrueSheetViewComponentDescriptor.h +28 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +30 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +45 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +23 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +52 -0
- package/ios/TrueSheetContainerView.h +88 -6
- package/ios/TrueSheetContainerView.mm +278 -21
- package/ios/TrueSheetContentView.h +54 -1
- package/ios/TrueSheetContentView.mm +371 -47
- package/ios/TrueSheetFooterView.h +33 -2
- package/ios/TrueSheetFooterView.mm +187 -35
- package/ios/{events/OnWillDismissEvent.h → TrueSheetHeaderView.h} +10 -7
- package/ios/TrueSheetHeaderView.mm +64 -0
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +98 -20
- package/ios/TrueSheetOverlayView.h +32 -0
- package/ios/TrueSheetOverlayView.mm +161 -0
- package/ios/TrueSheetPeekView.h +32 -0
- package/ios/TrueSheetPeekView.mm +99 -0
- package/ios/TrueSheetView.h +7 -15
- package/ios/TrueSheetView.mm +775 -215
- package/ios/TrueSheetViewController.h +94 -16
- package/ios/TrueSheetViewController.mm +1316 -331
- package/ios/core/RNScreensEventObserver.h +46 -0
- package/ios/core/RNScreensEventObserver.mm +280 -0
- package/ios/core/TrueSheetBlurView.h +27 -0
- package/ios/core/TrueSheetBlurView.mm +82 -0
- package/ios/core/TrueSheetDetentCalculator.h +107 -0
- package/ios/core/TrueSheetDetentCalculator.mm +197 -0
- package/ios/core/TrueSheetGrabberView.h +72 -0
- package/ios/core/TrueSheetGrabberView.mm +188 -0
- package/ios/core/TrueSheetKeyboardObserver.h +39 -0
- package/ios/core/TrueSheetKeyboardObserver.mm +112 -0
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetDragEvents.h +39 -0
- package/ios/events/TrueSheetDragEvents.mm +62 -0
- package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
- package/ios/events/TrueSheetFocusEvents.mm +49 -0
- package/ios/events/TrueSheetLifecycleEvents.h +42 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +79 -0
- package/ios/events/TrueSheetStateEvents.h +35 -0
- package/ios/events/TrueSheetStateEvents.mm +49 -0
- package/ios/tvos/TrueSheetStubs.mm +166 -0
- package/ios/{events/OnMountEvent.h → utils/BlurUtil.h} +5 -7
- package/ios/utils/BlurUtil.mm +69 -0
- package/ios/utils/GestureUtil.h +7 -0
- package/ios/utils/GestureUtil.mm +12 -0
- package/ios/utils/PlatformUtil.h +15 -0
- package/ios/utils/UIView+FirstResponder.h +15 -0
- package/ios/utils/UIView+FirstResponder.mm +26 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +26 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +84 -0
- package/ios/utils/WindowUtil.mm +17 -1
- package/lib/module/TrueSheet.js +349 -74
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +1270 -0
- package/lib/module/TrueSheet.web.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.js +15 -0
- package/lib/module/TrueSheetPeek.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +55 -0
- package/lib/module/TrueSheetPeek.web.js.map +1 -0
- package/lib/module/TrueSheetProvider.js +28 -0
- package/lib/module/TrueSheetProvider.js.map +1 -0
- package/lib/module/TrueSheetProvider.web.js +157 -0
- package/lib/module/TrueSheetProvider.web.js.map +1 -0
- package/lib/module/fabric/TrueSheetContainerViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
- package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
- package/lib/module/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +110 -18
- package/lib/module/index.js +4 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +103 -0
- package/lib/module/mocks/index.js.map +1 -0
- package/lib/module/mocks/navigation.js +95 -0
- package/lib/module/mocks/navigation.js.map +1 -0
- package/lib/module/mocks/navigationExpoRouter.js +21 -0
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -0
- package/lib/module/mocks/reanimated.js +91 -0
- package/lib/module/mocks/reanimated.js.map +1 -0
- package/lib/module/navigation/TrueSheetNavigatorContent.js +73 -0
- 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/createTrueSheetRouter.js +157 -0
- 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 +47 -0
- package/lib/module/navigation/index.js.map +1 -0
- package/lib/module/navigation/navigator.js +10 -0
- package/lib/module/navigation/navigator.js.map +1 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +56 -0
- package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -0
- package/lib/module/navigation/screen/TrueSheetScreen.js +49 -0
- package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -0
- package/lib/module/navigation/screen/index.js +5 -0
- package/lib/module/navigation/screen/index.js.map +1 -0
- package/lib/module/navigation/screen/types.js +4 -0
- package/lib/module/navigation/screen/types.js.map +1 -0
- package/lib/module/navigation/screen/useSheetScreenState.js +69 -0
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -0
- package/lib/module/navigation/types.js +4 -0
- package/lib/module/navigation/types.js.map +1 -0
- package/lib/module/navigation/useTrueSheetNavigation.js +24 -0
- package/lib/module/navigation/useTrueSheetNavigation.js.map +1 -0
- package/lib/module/reanimated/ReanimatedTrueSheet.js +15 -9
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.web.js +81 -0
- package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -0
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/module/reanimated/index.js +2 -2
- package/lib/module/reanimated/index.js.map +1 -1
- package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +20 -0
- package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -0
- package/lib/module/specs/NativeTrueSheetModule.js.map +1 -1
- package/lib/module/web/constants.js +16 -0
- package/lib/module/web/constants.js.map +1 -0
- package/lib/module/web/dom.js +10 -0
- package/lib/module/web/dom.js.map +1 -0
- package/lib/module/web/vaul/README.md +9 -0
- package/lib/module/web/vaul/browser.js +30 -0
- package/lib/module/web/vaul/browser.js.map +1 -0
- package/lib/module/web/vaul/constants.js +19 -0
- package/lib/module/web/vaul/constants.js.map +1 -0
- package/lib/module/web/vaul/context.js +59 -0
- package/lib/module/web/vaul/context.js.map +1 -0
- package/lib/module/web/vaul/helpers.js +99 -0
- package/lib/module/web/vaul/helpers.js.map +1 -0
- package/lib/module/web/vaul/index.js +1342 -0
- package/lib/module/web/vaul/index.js.map +1 -0
- package/lib/module/web/vaul/style.css +287 -0
- package/lib/module/web/vaul/style.css.d.js +2 -0
- package/lib/module/web/vaul/style.css.d.js.map +1 -0
- package/lib/module/web/vaul/types.js +2 -0
- package/lib/module/web/vaul/types.js.map +1 -0
- package/lib/module/web/vaul/use-composed-refs.js +34 -0
- package/lib/module/web/vaul/use-composed-refs.js.map +1 -0
- package/lib/module/web/vaul/use-controllable-state.js +54 -0
- package/lib/module/web/vaul/use-controllable-state.js.map +1 -0
- package/lib/module/web/vaul/use-position-fixed.js +123 -0
- package/lib/module/web/vaul/use-position-fixed.js.map +1 -0
- package/lib/module/web/vaul/use-prevent-scroll.js +258 -0
- package/lib/module/web/vaul/use-prevent-scroll.js.map +1 -0
- package/lib/module/web/vaul/use-scale-background.js +57 -0
- package/lib/module/web/vaul/use-scale-background.js.map +1 -0
- package/lib/module/web/vaul/use-snap-points.js +305 -0
- package/lib/module/web/vaul/use-snap-points.js.map +1 -0
- package/lib/typescript/src/TrueSheet.d.ts +57 -14
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +535 -55
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts +5 -0
- package/lib/typescript/src/TrueSheet.web.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.d.ts +9 -0
- package/lib/typescript/src/TrueSheetPeek.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +28 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetProvider.d.ts +18 -0
- package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetProvider.web.d.ts +27 -0
- package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetContainerViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetContentViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -0
- package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetHeaderViewNativeComponent.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/TrueSheetPeekViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetPeekViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +71 -16
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +47 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -0
- package/lib/typescript/src/mocks/navigation.d.ts +70 -0
- package/lib/typescript/src/mocks/navigation.d.ts.map +1 -0
- 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 +48 -0
- package/lib/typescript/src/mocks/reanimated.d.ts.map +1 -0
- 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 +45 -0
- package/lib/typescript/src/navigation/index.d.ts.map +1 -0
- 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 +3 -0
- package/lib/typescript/src/navigation/screen/ReanimatedTrueSheetScreen.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts +3 -0
- package/lib/typescript/src/navigation/screen/TrueSheetScreen.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/index.d.ts +4 -0
- package/lib/typescript/src/navigation/screen/index.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/types.d.ts +14 -0
- package/lib/typescript/src/navigation/screen/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +34 -0
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -0
- package/lib/typescript/src/navigation/types.d.ts +213 -0
- package/lib/typescript/src/navigation/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts +23 -0
- package/lib/typescript/src/navigation/useTrueSheetNavigation.d.ts.map +1 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +41 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -0
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +15 -0
- package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts +25 -3
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts.map +1 -1
- package/lib/typescript/src/web/constants.d.ts +14 -0
- package/lib/typescript/src/web/constants.d.ts.map +1 -0
- 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/browser.d.ts +8 -0
- package/lib/typescript/src/web/vaul/browser.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +14 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/context.d.ts +54 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts +17 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/index.d.ts +234 -0
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/types.d.ts +7 -0
- package/lib/typescript/src/web/vaul/types.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-composed-refs.d.ts +14 -0
- package/lib/typescript/src/web/vaul/use-composed-refs.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-controllable-state.d.ts +9 -0
- package/lib/typescript/src/web/vaul/use-controllable-state.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-position-fixed.d.ts +19 -0
- package/lib/typescript/src/web/vaul/use-position-fixed.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts +18 -0
- package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-scale-background.d.ts +2 -0
- package/lib/typescript/src/web/vaul/use-scale-background.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts +38 -0
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -0
- package/package.json +104 -37
- package/react-native.config.js +10 -2
- package/src/TrueSheet.tsx +424 -70
- package/src/TrueSheet.types.ts +602 -72
- package/src/TrueSheet.web.tsx +1437 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.tsx +11 -0
- package/src/TrueSheetPeek.web.tsx +64 -0
- package/src/TrueSheetProvider.tsx +33 -0
- package/src/TrueSheetProvider.web.tsx +220 -0
- package/src/fabric/TrueSheetContainerViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
- package/src/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
- package/src/fabric/TrueSheetHeaderViewNativeComponent.ts +8 -0
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetPeekViewNativeComponent.ts +8 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +110 -18
- package/src/index.ts +3 -2
- package/src/mocks/index.ts +96 -0
- package/src/mocks/navigation.ts +79 -0
- package/src/mocks/navigationExpoRouter.ts +41 -0
- package/src/mocks/reanimated.ts +100 -0
- package/src/navigation/TrueSheetNavigatorContent.tsx +106 -0
- 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 +80 -0
- package/src/navigation/navigator.ts +18 -0
- package/src/navigation/screen/ReanimatedTrueSheetScreen.tsx +61 -0
- package/src/navigation/screen/TrueSheetScreen.tsx +54 -0
- package/src/navigation/screen/index.ts +3 -0
- package/src/navigation/screen/types.ts +19 -0
- package/src/navigation/screen/useSheetScreenState.ts +102 -0
- package/src/navigation/types.ts +302 -0
- package/src/navigation/useTrueSheetNavigation.ts +26 -0
- package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
- package/src/reanimated/ReanimatedTrueSheet.web.tsx +75 -0
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +12 -8
- package/src/reanimated/useReanimatedPositionChangeHandler.web.ts +31 -0
- package/src/specs/NativeTrueSheetModule.ts +28 -3
- package/src/web/constants.ts +13 -0
- package/src/web/dom.ts +9 -0
- package/src/web/vaul/README.md +9 -0
- package/src/web/vaul/browser.ts +38 -0
- package/src/web/vaul/constants.ts +24 -0
- package/src/web/vaul/context.ts +102 -0
- package/src/web/vaul/helpers.ts +131 -0
- package/src/web/vaul/index.tsx +1785 -0
- package/src/web/vaul/style.css +287 -0
- package/src/web/vaul/style.css.d.ts +1 -0
- package/src/web/vaul/types.ts +7 -0
- package/src/web/vaul/use-composed-refs.ts +35 -0
- package/src/web/vaul/use-controllable-state.ts +66 -0
- package/src/web/vaul/use-position-fixed.ts +147 -0
- package/src/web/vaul/use-prevent-scroll.ts +309 -0
- package/src/web/vaul/use-scale-background.ts +64 -0
- package/src/web/vaul/use-snap-points.ts +455 -0
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
- package/android/src/main/java/com/lodev09/truesheet/events/SizeChangeEvent.kt +0 -27
- package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
- package/android/src/main/res/values/styles.xml +0 -8
- package/ios/events/OnDetentChangeEvent.h +0 -28
- package/ios/events/OnDetentChangeEvent.mm +0 -30
- package/ios/events/OnDidDismissEvent.h +0 -26
- package/ios/events/OnDidDismissEvent.mm +0 -25
- package/ios/events/OnDidPresentEvent.h +0 -28
- package/ios/events/OnDidPresentEvent.mm +0 -30
- package/ios/events/OnDragBeginEvent.h +0 -28
- package/ios/events/OnDragBeginEvent.mm +0 -30
- package/ios/events/OnDragChangeEvent.h +0 -28
- package/ios/events/OnDragChangeEvent.mm +0 -30
- package/ios/events/OnDragEndEvent.h +0 -28
- package/ios/events/OnDragEndEvent.mm +0 -30
- package/ios/events/OnMountEvent.mm +0 -25
- package/ios/events/OnPositionChangeEvent.mm +0 -32
- package/ios/events/OnSizeChangeEvent.h +0 -28
- package/ios/events/OnSizeChangeEvent.mm +0 -30
- package/ios/events/OnWillDismissEvent.mm +0 -25
- package/ios/events/OnWillPresentEvent.h +0 -28
- package/ios/events/OnWillPresentEvent.mm +0 -30
- package/ios/utils/LayoutUtil.h +0 -44
- package/ios/utils/LayoutUtil.mm +0 -50
- package/lib/module/TrueSheetGrabber.js +0 -51
- package/lib/module/TrueSheetGrabber.js.map +0 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
- package/src/TrueSheetGrabber.tsx +0 -82
- package/src/__mocks__/index.js +0 -67
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.graphics.
|
|
5
|
-
import android.
|
|
6
|
-
import android.graphics.drawable.shapes.RoundRectShape
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.os.Build
|
|
7
6
|
import android.view.MotionEvent
|
|
8
7
|
import android.view.View
|
|
9
|
-
import android.view.
|
|
8
|
+
import android.view.ViewGroup
|
|
9
|
+
import android.view.accessibility.AccessibilityEvent
|
|
10
10
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
11
|
-
import android.widget.
|
|
11
|
+
import android.widget.ImageView
|
|
12
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
13
|
+
import androidx.core.graphics.createBitmap
|
|
12
14
|
import androidx.core.view.isNotEmpty
|
|
13
15
|
import com.facebook.react.R
|
|
14
|
-
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
15
16
|
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
16
17
|
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
17
18
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
@@ -19,837 +20,1637 @@ import com.facebook.react.uimanager.PixelUtil.pxToDp
|
|
|
19
20
|
import com.facebook.react.uimanager.RootView
|
|
20
21
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
21
22
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
23
|
+
import com.facebook.react.util.RNLog
|
|
22
24
|
import com.facebook.react.views.view.ReactViewGroup
|
|
23
25
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
24
|
-
import com.
|
|
26
|
+
import com.lodev09.truesheet.core.AccessibilityOptions
|
|
27
|
+
import com.lodev09.truesheet.core.GrabberOptions
|
|
28
|
+
import com.lodev09.truesheet.core.TrueSheetBottomSheetBehavior
|
|
29
|
+
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
|
|
30
|
+
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
|
|
31
|
+
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
|
|
32
|
+
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayoutDelegate
|
|
33
|
+
import com.lodev09.truesheet.core.TrueSheetDetentCalculator
|
|
34
|
+
import com.lodev09.truesheet.core.TrueSheetDetentCalculatorDelegate
|
|
35
|
+
import com.lodev09.truesheet.core.TrueSheetDimView
|
|
36
|
+
import com.lodev09.truesheet.core.TrueSheetDimViewDelegate
|
|
37
|
+
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
38
|
+
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
39
|
+
import com.lodev09.truesheet.core.TrueSheetStackManager
|
|
40
|
+
import com.lodev09.truesheet.utils.Insets
|
|
41
|
+
import com.lodev09.truesheet.utils.KeyboardUtils
|
|
25
42
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
43
|
+
import com.lodev09.truesheet.utils.TouchEventDeduper
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// MARK: - Data Types & Delegate Protocol
|
|
47
|
+
// =============================================================================
|
|
26
48
|
|
|
27
49
|
data class DetentInfo(val index: Int, val position: Float)
|
|
28
50
|
|
|
29
|
-
/**
|
|
30
|
-
* Delegate protocol for TrueSheetViewController lifecycle and interaction events.
|
|
31
|
-
* Similar to iOS TrueSheetViewControllerDelegate pattern.
|
|
32
|
-
*/
|
|
33
51
|
interface TrueSheetViewControllerDelegate {
|
|
34
|
-
|
|
35
|
-
fun
|
|
52
|
+
val eventDispatcher: EventDispatcher?
|
|
53
|
+
fun findRootContainerView(): ViewGroup?
|
|
54
|
+
fun viewControllerWillPresent(index: Int, position: Float, detent: Float)
|
|
55
|
+
fun viewControllerDidPresent(index: Int, position: Float, detent: Float)
|
|
36
56
|
fun viewControllerWillDismiss()
|
|
37
|
-
fun viewControllerDidDismiss()
|
|
38
|
-
fun
|
|
39
|
-
fun
|
|
40
|
-
fun
|
|
41
|
-
fun
|
|
42
|
-
fun
|
|
57
|
+
fun viewControllerDidDismiss(parent: TrueSheetView?)
|
|
58
|
+
fun viewControllerDidAttemptDismiss()
|
|
59
|
+
fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float)
|
|
60
|
+
fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float)
|
|
61
|
+
fun viewControllerDidDragChange(index: Int, position: Float, detent: Float)
|
|
62
|
+
fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float)
|
|
63
|
+
fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean)
|
|
43
64
|
fun viewControllerDidChangeSize(width: Int, height: Int)
|
|
65
|
+
fun viewControllerWillFocus()
|
|
66
|
+
fun viewControllerDidFocus()
|
|
67
|
+
fun viewControllerWillBlur()
|
|
68
|
+
fun viewControllerDidBlur()
|
|
69
|
+
fun viewControllerDidChangeVisibility(visible: Boolean)
|
|
44
70
|
}
|
|
45
71
|
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// MARK: - TrueSheetViewController
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
46
76
|
/**
|
|
47
|
-
*
|
|
48
|
-
* Similar to iOS TrueSheetViewController pattern.
|
|
77
|
+
* Controls the presentation and behavior of a bottom sheet.
|
|
49
78
|
*
|
|
50
|
-
*
|
|
79
|
+
* Uses CoordinatorLayout with BottomSheetBehavior to manage the sheet within the activity window,
|
|
80
|
+
* enabling touch pass-through to underlying views. Handles detent configuration, drag interactions,
|
|
81
|
+
* keyboard avoidance, dimmed backgrounds, back button, and lifecycle events for stacked sheets.
|
|
51
82
|
*/
|
|
83
|
+
enum class TrueSheetAnchor {
|
|
84
|
+
LEFT,
|
|
85
|
+
CENTER,
|
|
86
|
+
RIGHT;
|
|
87
|
+
|
|
88
|
+
companion object {
|
|
89
|
+
fun fromString(value: String?): TrueSheetAnchor =
|
|
90
|
+
when (value) {
|
|
91
|
+
"left" -> LEFT
|
|
92
|
+
"right" -> RIGHT
|
|
93
|
+
else -> CENTER
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
enum class TrueSheetInsetAdjustment {
|
|
99
|
+
AUTOMATIC,
|
|
100
|
+
NEVER;
|
|
101
|
+
|
|
102
|
+
companion object {
|
|
103
|
+
fun fromString(value: String?): TrueSheetInsetAdjustment =
|
|
104
|
+
when (value) {
|
|
105
|
+
"never" -> NEVER
|
|
106
|
+
else -> AUTOMATIC
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
52
111
|
@SuppressLint("ClickableViewAccessibility", "ViewConstructor")
|
|
53
112
|
class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
54
113
|
ReactViewGroup(reactContext),
|
|
55
|
-
RootView
|
|
114
|
+
RootView,
|
|
115
|
+
TrueSheetDetentCalculatorDelegate,
|
|
116
|
+
TrueSheetDimViewDelegate,
|
|
117
|
+
TrueSheetCoordinatorLayoutDelegate,
|
|
118
|
+
TrueSheetBottomSheetViewDelegate {
|
|
56
119
|
|
|
57
120
|
companion object {
|
|
58
|
-
private const val
|
|
121
|
+
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
122
|
+
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
123
|
+
private const val DEFAULT_ANCHOR_OFFSET = 16 // dp
|
|
124
|
+
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
125
|
+
private const val DISMISS_DURATION = 200L
|
|
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
|
|
59
131
|
}
|
|
60
132
|
|
|
61
|
-
//
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// MARK: - Types
|
|
135
|
+
// =============================================================================
|
|
62
136
|
|
|
63
|
-
|
|
137
|
+
private sealed class InteractionState {
|
|
138
|
+
data object Idle : InteractionState()
|
|
139
|
+
data class Dragging(val startTop: Int) : InteractionState()
|
|
140
|
+
data object Reconfiguring : InteractionState()
|
|
141
|
+
}
|
|
64
142
|
|
|
65
|
-
|
|
66
|
-
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// MARK: - Properties
|
|
145
|
+
// =============================================================================
|
|
67
146
|
|
|
68
|
-
/**
|
|
69
|
-
* Delegate for handling view controller events
|
|
70
|
-
*/
|
|
71
147
|
var delegate: TrueSheetViewControllerDelegate? = null
|
|
72
148
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
private var
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The sheet behavior from the dialog
|
|
80
|
-
*/
|
|
81
|
-
private val behavior: BottomSheetBehavior<FrameLayout>?
|
|
82
|
-
get() = dialog?.behavior
|
|
149
|
+
// CoordinatorLayout components (replaces DialogFragment)
|
|
150
|
+
internal var sheetView: TrueSheetBottomSheetView? = null
|
|
151
|
+
internal var coordinatorLayout: TrueSheetCoordinatorLayout? = null
|
|
152
|
+
private var dimView: TrueSheetDimView? = null
|
|
153
|
+
private var parentDimView: TrueSheetDimView? = null
|
|
83
154
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
private val sheetContainer: FrameLayout?
|
|
88
|
-
get() = this.parent as? FrameLayout
|
|
155
|
+
// Presentation State
|
|
156
|
+
var isPresented = false
|
|
157
|
+
private set
|
|
89
158
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* This is the view whose position changes during drag
|
|
93
|
-
*/
|
|
94
|
-
private val bottomSheetView: FrameLayout?
|
|
95
|
-
get() = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet)
|
|
159
|
+
var isSheetVisible = false
|
|
160
|
+
private set
|
|
96
161
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*/
|
|
100
|
-
private val containerView: TrueSheetContainerView?
|
|
101
|
-
get() = if (this.isNotEmpty()) {
|
|
102
|
-
this.getChildAt(0) as? TrueSheetContainerView
|
|
103
|
-
} else {
|
|
104
|
-
null
|
|
105
|
-
}
|
|
162
|
+
var currentDetentIndex: Int = -1
|
|
163
|
+
private set
|
|
106
164
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
private
|
|
111
|
-
get() = containerView?.footerView
|
|
165
|
+
// Target detent index during an in-flight resize animation. -1 when idle.
|
|
166
|
+
// Used by setupSheetDetents so a layout-driven reconfigure during the
|
|
167
|
+
// animation doesn't snap the sheet back to the stale currentDetentIndex.
|
|
168
|
+
private var pendingDetentIndex: Int = -1
|
|
112
169
|
|
|
113
|
-
|
|
114
|
-
* Track if the dialog is currently being dragged
|
|
115
|
-
*/
|
|
116
|
-
private var isDragging = false
|
|
170
|
+
private var interactionState: InteractionState = InteractionState.Idle
|
|
117
171
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
var
|
|
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
|
|
175
|
+
internal var isBeingDismissed = false
|
|
122
176
|
private set
|
|
177
|
+
var wasHiddenByScreen = false
|
|
178
|
+
private var shouldAnimatePresent = false
|
|
179
|
+
private var isPresentAnimating = false
|
|
123
180
|
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
val defaultEnabled = android.os.Build.VERSION.SDK_INT >= 36
|
|
128
|
-
return BuildConfig.EDGE_TO_EDGE_ENABLED || dialog?.edgeToEdgeEnabled == true || defaultEnabled
|
|
129
|
-
}
|
|
181
|
+
private var lastStateWidth: Int = 0
|
|
182
|
+
private var lastStateHeight: Int = 0
|
|
183
|
+
private var lastEmittedPositionPx: Int = -1
|
|
130
184
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
var edgeToEdgeFullScreen: Boolean = false
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Top inset to apply to sheet max height calculation (only when not edgeToEdgeFullScreen)
|
|
138
|
-
*/
|
|
139
|
-
private val sheetTopInset: Int
|
|
140
|
-
get() = if (edgeToEdgeEnabled && !edgeToEdgeFullScreen) ScreenUtils.getStatusBarHeight(reactContext) else 0
|
|
185
|
+
// Keyboard State
|
|
186
|
+
private var detentIndexBeforeKeyboard: Int = -1
|
|
187
|
+
private var isKeyboardDismissProgrammatic = false
|
|
141
188
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
var currentDetentIndex: Int = -1
|
|
146
|
-
private set
|
|
189
|
+
// Sheet top when a keyboard-floor drag was released; -1 once the settle target is known
|
|
190
|
+
private var keyboardSettleStartTop = -1
|
|
191
|
+
private var focusedViewBeforeBlur: View? = null
|
|
147
192
|
|
|
148
|
-
|
|
149
|
-
* Promise callback to be invoked after present is called
|
|
150
|
-
*/
|
|
193
|
+
// Promises
|
|
151
194
|
var presentPromise: (() -> Unit)? = null
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Promise callback to be invoked after dismiss is called
|
|
155
|
-
*/
|
|
195
|
+
var resizePromise: (() -> Unit)? = null
|
|
156
196
|
var dismissPromise: (() -> Unit)? = null
|
|
157
197
|
|
|
158
|
-
//
|
|
198
|
+
// For stacked sheets
|
|
199
|
+
var parentSheetView: TrueSheetView? = null
|
|
159
200
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
201
|
+
// Helper Objects
|
|
202
|
+
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
203
|
+
internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
|
|
204
|
+
delegate = this@TrueSheetViewController
|
|
205
|
+
}
|
|
165
206
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
*/
|
|
170
|
-
var dimmedIndex = 0
|
|
207
|
+
// Touch Dispatchers
|
|
208
|
+
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
209
|
+
private val jsPointerDispatcher = JSPointerDispatcher(this)
|
|
171
210
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
var
|
|
211
|
+
// True when the active touch stream began inside the footer. Latched on
|
|
212
|
+
// ACTION_DOWN so subsequent MOVE events keep routing to the footer even if
|
|
213
|
+
// the finger drifts outside the footer's rect mid-gesture.
|
|
214
|
+
private var footerOwnsTouchStream = false
|
|
176
215
|
|
|
177
|
-
|
|
216
|
+
private val touchDeduper = TouchEventDeduper()
|
|
178
217
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
var
|
|
218
|
+
private val eventDispatcher
|
|
219
|
+
get() = delegate?.eventDispatcher
|
|
220
|
+
|
|
221
|
+
// Detent Configuration
|
|
222
|
+
override var maxContentHeight: Int? = null
|
|
223
|
+
override var maxContentWidth: Int? = null
|
|
224
|
+
override var anchor: TrueSheetAnchor = TrueSheetAnchor.CENTER
|
|
225
|
+
override var anchorOffset: Int = DEFAULT_ANCHOR_OFFSET.dpToPx().toInt()
|
|
226
|
+
override var detents: MutableList<Double> = mutableListOf(0.5, 1.0)
|
|
227
|
+
|
|
228
|
+
// Appearance Configuration
|
|
229
|
+
var dimmed = true
|
|
230
|
+
var dimmedDetentIndex = 0
|
|
231
|
+
override var grabber: Boolean = true
|
|
232
|
+
override var grabberOptions: GrabberOptions? = null
|
|
233
|
+
override var accessibilityOptions: AccessibilityOptions? = null
|
|
234
|
+
override var sheetBackgroundColor: Int? = null
|
|
235
|
+
var insetAdjustment: TrueSheetInsetAdjustment = TrueSheetInsetAdjustment.AUTOMATIC
|
|
236
|
+
|
|
237
|
+
var scrollableOptions: ScrollableOptions? = null
|
|
238
|
+
set(value) {
|
|
239
|
+
field = value
|
|
240
|
+
behavior?.scrollingExpandsSheet = value?.scrollingExpandsSheet ?: true
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// React tag of the user-provided scrollable within the content (see the scrollableRef prop)
|
|
244
|
+
var scrollableHandle: Int = -1
|
|
245
|
+
|
|
246
|
+
override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
247
|
+
set(value) {
|
|
248
|
+
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
249
|
+
if (isPresented) sheetView?.setupBackground()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
override var sheetElevation: Float = -1f
|
|
253
|
+
set(value) {
|
|
254
|
+
field = value
|
|
255
|
+
if (isPresented) sheetView?.setupElevation()
|
|
256
|
+
}
|
|
184
257
|
|
|
185
258
|
var dismissible: Boolean = true
|
|
186
259
|
set(value) {
|
|
187
260
|
field = value
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
261
|
+
behavior?.isHideable = value
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
override var draggable: Boolean = true
|
|
265
|
+
set(value) {
|
|
266
|
+
field = value
|
|
267
|
+
behavior?.isDraggable = value
|
|
268
|
+
if (isPresented) sheetView?.setupGrabber()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// =============================================================================
|
|
272
|
+
// MARK: - Computed Properties
|
|
273
|
+
// =============================================================================
|
|
274
|
+
|
|
275
|
+
// Behavior
|
|
276
|
+
private val behavior: TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>?
|
|
277
|
+
get() = sheetView?.behavior
|
|
278
|
+
|
|
279
|
+
internal val containerView: TrueSheetContainerView?
|
|
280
|
+
get() = if (this.isNotEmpty()) getChildAt(0) as? TrueSheetContainerView else null
|
|
281
|
+
|
|
282
|
+
// Screen Measurements
|
|
283
|
+
override val screenHeight: Int
|
|
284
|
+
get() = ScreenUtils.getScreenHeight(reactContext)
|
|
285
|
+
|
|
286
|
+
val screenWidth: Int
|
|
287
|
+
get() = ScreenUtils.getScreenWidth(reactContext)
|
|
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
|
+
|
|
301
|
+
// Includes system bars for accurate positioning
|
|
302
|
+
override val realScreenHeight: Int
|
|
303
|
+
get() {
|
|
304
|
+
if (cachedRealScreenHeight == 0) {
|
|
305
|
+
cachedRealScreenHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
192
306
|
}
|
|
307
|
+
return cachedRealScreenHeight
|
|
193
308
|
}
|
|
194
309
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
310
|
+
private val insets: Insets
|
|
311
|
+
get() = cachedInsets ?: ScreenUtils.getInsets(reactContext).also { cachedInsets = it }
|
|
312
|
+
|
|
313
|
+
// Content Measurements
|
|
314
|
+
// Cached values used during dismiss when container is unmounted
|
|
315
|
+
private var cachedContentHeight: Int = 0
|
|
316
|
+
private var cachedHeaderHeight: Int = 0
|
|
317
|
+
private var cachedFooterHeight: Int = 0
|
|
318
|
+
private var cachedPeekContentHeight: Int = 0
|
|
198
319
|
|
|
199
|
-
|
|
320
|
+
override val contentHeight: Int
|
|
321
|
+
get() = containerView?.contentHeight ?: cachedContentHeight
|
|
200
322
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
323
|
+
override val headerHeight: Int
|
|
324
|
+
get() = containerView?.headerHeight ?: cachedHeaderHeight
|
|
325
|
+
|
|
326
|
+
override var absoluteHeader: Boolean = false
|
|
327
|
+
|
|
328
|
+
override val footerHeight: Int
|
|
329
|
+
get() = containerView?.footerHeight ?: cachedFooterHeight
|
|
330
|
+
|
|
331
|
+
override var absoluteFooter: Boolean = false
|
|
332
|
+
|
|
333
|
+
override val peekContentHeight: Int
|
|
334
|
+
get() = containerView?.peekContentHeight ?: cachedPeekContentHeight
|
|
335
|
+
|
|
336
|
+
// Insets
|
|
337
|
+
// Target keyboard height used for detent calculations
|
|
338
|
+
override val keyboardInset: Int
|
|
339
|
+
get() = keyboardObserver?.targetHeight ?: 0
|
|
340
|
+
|
|
341
|
+
// Current animated keyboard height for positioning
|
|
342
|
+
private val currentKeyboardInset: Int
|
|
343
|
+
get() = keyboardObserver?.currentHeight ?: 0
|
|
344
|
+
|
|
345
|
+
private val isKeyboardTransitioning: Boolean
|
|
346
|
+
get() = keyboardObserver?.isTransitioning ?: false
|
|
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
|
+
|
|
362
|
+
fun isFocusedViewWithinSheet(): Boolean {
|
|
363
|
+
val sheet = sheetView ?: return false
|
|
364
|
+
return keyboardObserver?.isFocusedViewWithinSheet(sheet) ?: false
|
|
204
365
|
}
|
|
205
366
|
|
|
206
|
-
|
|
367
|
+
val bottomInset: Int
|
|
368
|
+
get() = if (edgeToEdgeEnabled) insets.bottom else 0
|
|
207
369
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*/
|
|
211
|
-
fun createDialog() {
|
|
212
|
-
if (dialog != null) return
|
|
370
|
+
override val topInset: Int
|
|
371
|
+
get() = if (edgeToEdgeEnabled) insets.top else 0
|
|
213
372
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
373
|
+
override val contentBottomInset: Int
|
|
374
|
+
get() = if (insetAdjustment == TrueSheetInsetAdjustment.AUTOMATIC) bottomInset else 0
|
|
375
|
+
|
|
376
|
+
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
|
377
|
+
private val edgeToEdgeEnabled: Boolean
|
|
378
|
+
get() {
|
|
379
|
+
val defaultEnabled = Build.VERSION.SDK_INT >= 36
|
|
380
|
+
return BuildConfig.EDGE_TO_EDGE_ENABLED || defaultEnabled
|
|
218
381
|
}
|
|
219
382
|
|
|
220
|
-
|
|
221
|
-
|
|
383
|
+
// Sheet State
|
|
384
|
+
val isSettling: Boolean
|
|
385
|
+
get() = behavior?.state == BottomSheetBehavior.STATE_SETTLING
|
|
222
386
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
387
|
+
val isExpanded: Boolean
|
|
388
|
+
get() {
|
|
389
|
+
val sheetTop = sheetView?.top ?: return false
|
|
390
|
+
return sheetTop <= topInset
|
|
391
|
+
}
|
|
228
392
|
|
|
229
|
-
|
|
230
|
-
|
|
393
|
+
val currentTranslationY: Int
|
|
394
|
+
get() = sheetView?.translationY?.toInt() ?: 0
|
|
231
395
|
|
|
232
|
-
|
|
233
|
-
|
|
396
|
+
override val isTopmostSheet: Boolean
|
|
397
|
+
get() {
|
|
398
|
+
val hostView = delegate as? TrueSheetView ?: return true
|
|
399
|
+
return TrueSheetStackManager.isTopmostSheet(hostView)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private val dimViews: List<TrueSheetDimView>
|
|
403
|
+
get() = listOfNotNull(dimView, parentDimView)
|
|
404
|
+
|
|
405
|
+
val isDimmedAtCurrentDetent: Boolean
|
|
406
|
+
get() = isDimmedAtDetentIndex(currentDetentIndex)
|
|
407
|
+
|
|
408
|
+
fun isDimmedAtDetentIndex(index: Int): Boolean = dimmed && index >= dimmedDetentIndex
|
|
234
409
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
behavior.isHideable = dismissible
|
|
410
|
+
// =============================================================================
|
|
411
|
+
// MARK: - Sheet Creation & Cleanup
|
|
412
|
+
// =============================================================================
|
|
239
413
|
|
|
240
|
-
|
|
241
|
-
|
|
414
|
+
fun createSheet() {
|
|
415
|
+
if (coordinatorLayout != null) return
|
|
416
|
+
|
|
417
|
+
// Create coordinator layout
|
|
418
|
+
coordinatorLayout = TrueSheetCoordinatorLayout(reactContext).apply {
|
|
419
|
+
delegate = this@TrueSheetViewController
|
|
242
420
|
}
|
|
243
|
-
}
|
|
244
421
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
*/
|
|
248
|
-
private fun cleanupDialog() {
|
|
249
|
-
dialog?.apply {
|
|
250
|
-
setOnShowListener(null)
|
|
251
|
-
setOnCancelListener(null)
|
|
252
|
-
setOnDismissListener(null)
|
|
422
|
+
sheetView = TrueSheetBottomSheetView(reactContext).apply {
|
|
423
|
+
delegate = this@TrueSheetViewController
|
|
253
424
|
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private fun cleanupSheet() {
|
|
428
|
+
cleanupKeyboardObserver()
|
|
429
|
+
sheetView?.animate()?.cancel()
|
|
430
|
+
|
|
431
|
+
// Cleanup dim views
|
|
432
|
+
dimView?.detach()
|
|
433
|
+
dimView = null
|
|
434
|
+
parentDimView?.detach()
|
|
435
|
+
parentDimView = null
|
|
254
436
|
|
|
255
|
-
//
|
|
256
|
-
|
|
437
|
+
// Cleanup snapshot
|
|
438
|
+
removeSheetSnapshot()
|
|
257
439
|
|
|
258
|
-
|
|
259
|
-
|
|
440
|
+
// Detach content from sheet
|
|
441
|
+
sheetView?.removeView(this)
|
|
442
|
+
|
|
443
|
+
containerView?.cleanupKeyboardHandler()
|
|
444
|
+
coordinatorLayout = null
|
|
445
|
+
sheetView = null
|
|
446
|
+
|
|
447
|
+
interactionState = InteractionState.Idle
|
|
448
|
+
hasPendingSizeChange = false
|
|
449
|
+
isBeingDismissed = false
|
|
260
450
|
isPresented = false
|
|
451
|
+
isSheetVisible = false
|
|
452
|
+
wasHiddenByScreen = false
|
|
453
|
+
cachedContentHeight = 0
|
|
454
|
+
cachedHeaderHeight = 0
|
|
455
|
+
isPresentAnimating = false
|
|
456
|
+
lastEmittedPositionPx = -1
|
|
457
|
+
detentIndexBeforeKeyboard = -1
|
|
458
|
+
pendingDetentIndex = -1
|
|
459
|
+
isKeyboardDismissProgrammatic = false
|
|
460
|
+
focusedViewBeforeBlur = null
|
|
461
|
+
shouldAnimatePresent = true
|
|
261
462
|
}
|
|
262
463
|
|
|
263
|
-
|
|
264
|
-
* Setup dialog lifecycle listeners
|
|
265
|
-
*/
|
|
266
|
-
private fun setupDialogListeners(dialog: BottomSheetDialog) {
|
|
267
|
-
// Setup listener when the dialog has been presented
|
|
268
|
-
dialog.setOnShowListener {
|
|
269
|
-
isPresented = true
|
|
270
|
-
|
|
271
|
-
// Re-enable animation
|
|
272
|
-
resetAnimation()
|
|
273
|
-
|
|
274
|
-
// Wait for the sheet to settle before notifying didPresent
|
|
275
|
-
// The sheet animates to its final position after onShow fires
|
|
276
|
-
sheetContainer?.post {
|
|
277
|
-
// Notify delegate with the settled position
|
|
278
|
-
val detentInfo = getDetentInfoForIndex(currentDetentIndex)
|
|
279
|
-
delegate?.viewControllerDidPresent(detentInfo.index, detentInfo.position)
|
|
280
|
-
|
|
281
|
-
// Emit position change with transitioning=true so Reanimated can animate it
|
|
282
|
-
delegate?.viewControllerDidChangePosition(detentInfo.index, detentInfo.position, transitioning = true)
|
|
283
|
-
|
|
284
|
-
// Resolve the present promise
|
|
285
|
-
presentPromise?.let { promise ->
|
|
286
|
-
promise()
|
|
287
|
-
presentPromise = null
|
|
288
|
-
}
|
|
464
|
+
private var snapshotView: View? = null
|
|
289
465
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
466
|
+
fun createSheetSnapshot() {
|
|
467
|
+
if (!isPresented) return
|
|
468
|
+
val sheet = sheetView ?: return
|
|
469
|
+
if (sheet.width <= 0 || sheet.height <= 0) return
|
|
470
|
+
|
|
471
|
+
val bitmap = createBitmap(sheet.width, sheet.height)
|
|
472
|
+
val canvas = Canvas(bitmap)
|
|
473
|
+
sheet.draw(canvas)
|
|
474
|
+
|
|
475
|
+
val snapshot = ImageView(reactContext).apply {
|
|
476
|
+
setImageBitmap(bitmap)
|
|
477
|
+
layoutParams = LayoutParams(sheet.width, sheet.height)
|
|
293
478
|
}
|
|
294
479
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
480
|
+
sheet.addView(snapshot, 0)
|
|
481
|
+
snapshotView = snapshot
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
fun removeSheetSnapshot() {
|
|
485
|
+
snapshotView?.let {
|
|
486
|
+
(it as? ImageView)?.setImageDrawable(null)
|
|
487
|
+
sheetView?.removeView(it)
|
|
299
488
|
}
|
|
489
|
+
snapshotView = null
|
|
490
|
+
}
|
|
300
491
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
dismissPromise?.let { promise ->
|
|
305
|
-
promise()
|
|
306
|
-
dismissPromise = null
|
|
307
|
-
}
|
|
492
|
+
// =============================================================================
|
|
493
|
+
// MARK: - TrueSheetCoordinatorLayout.Delegate
|
|
494
|
+
// =============================================================================
|
|
308
495
|
|
|
309
|
-
|
|
310
|
-
|
|
496
|
+
override fun coordinatorLayoutDidLayout(changed: Boolean) {
|
|
497
|
+
if (changed) invalidateScreenMetrics()
|
|
311
498
|
|
|
312
|
-
|
|
313
|
-
|
|
499
|
+
// Reposition footer when layout changes
|
|
500
|
+
if (isPresented && changed) {
|
|
501
|
+
positionFooter()
|
|
314
502
|
}
|
|
315
503
|
}
|
|
316
504
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
private fun setupBottomSheetBehavior(dialog: BottomSheetDialog) {
|
|
321
|
-
dialog.behavior.addBottomSheetCallback(
|
|
322
|
-
object : BottomSheetBehavior.BottomSheetCallback() {
|
|
323
|
-
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
324
|
-
val behavior = behavior ?: return
|
|
325
|
-
|
|
326
|
-
// Emit position change event continuously during slide
|
|
327
|
-
// Set transitioning=false during drag to get real-time position updates
|
|
328
|
-
val detentInfo = getCurrentDetentInfo(sheetView)
|
|
329
|
-
delegate?.viewControllerDidChangePosition(detentInfo.index, detentInfo.position, transitioning = false)
|
|
330
|
-
|
|
331
|
-
when (behavior.state) {
|
|
332
|
-
// For consistency with iOS, we consider SETTLING as dragging change
|
|
333
|
-
BottomSheetBehavior.STATE_DRAGGING,
|
|
334
|
-
BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
|
|
335
|
-
|
|
336
|
-
else -> { }
|
|
337
|
-
}
|
|
505
|
+
override fun coordinatorLayoutDidChangeConfiguration() {
|
|
506
|
+
invalidateScreenMetrics()
|
|
507
|
+
if (!isPresented) return
|
|
338
508
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
509
|
+
sheetView?.updateGravity()
|
|
510
|
+
updateBehaviorMaxWidth()
|
|
511
|
+
updateStateDimensions()
|
|
512
|
+
sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
513
|
+
}
|
|
342
514
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// This ensures we can dismiss even if dialog state gets out of sync
|
|
346
|
-
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
347
|
-
dismiss()
|
|
348
|
-
return
|
|
349
|
-
}
|
|
515
|
+
override fun findScrollView(): ViewGroup? = containerView?.contentView?.findScrollView()
|
|
516
|
+
override fun findSheetView(): TrueSheetBottomSheetView? = sheetView
|
|
350
517
|
|
|
351
|
-
|
|
518
|
+
override fun coordinatorLayoutDidPullDown(overdragPx: Int, keyboardWasVisible: Boolean) {
|
|
519
|
+
if (!isPresented || dismissible || !draggable) return
|
|
520
|
+
val sheet = sheetView ?: return
|
|
352
521
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
356
526
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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)
|
|
361
531
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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()
|
|
367
538
|
}
|
|
368
539
|
|
|
369
|
-
//
|
|
540
|
+
// =============================================================================
|
|
541
|
+
// MARK: - TrueSheetDimViewDelegate
|
|
542
|
+
// =============================================================================
|
|
370
543
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
val
|
|
376
|
-
|
|
544
|
+
override fun dimViewDidTap() {
|
|
545
|
+
val hostView = delegate as? TrueSheetView ?: return
|
|
546
|
+
|
|
547
|
+
val children = TrueSheetStackManager.getSheetsAbove(hostView)
|
|
548
|
+
val topmostChild = children.firstOrNull()?.viewController
|
|
549
|
+
|
|
550
|
+
// If topmost child is dimmed, only handle that child
|
|
551
|
+
if (topmostChild?.isDimmedAtCurrentDetent == true) {
|
|
552
|
+
if (topmostChild.dismissible) {
|
|
553
|
+
topmostChild.dismiss(animated = true)
|
|
554
|
+
}
|
|
377
555
|
return
|
|
378
556
|
}
|
|
379
557
|
|
|
380
|
-
|
|
381
|
-
|
|
558
|
+
// Pass through to parent - dismiss all if possible
|
|
559
|
+
val allDismissible = dismissible && children.all { it.viewController.dismissible }
|
|
560
|
+
if (allDismissible) {
|
|
561
|
+
children.forEach { it.viewController.dismiss(animated = true) }
|
|
562
|
+
}
|
|
382
563
|
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
564
|
+
if (dismissible) {
|
|
565
|
+
dismiss(animated = true)
|
|
566
|
+
} else if (parentSheetView == null && isDimmedAtCurrentDetent && dimmedDetentIndex > 0) {
|
|
567
|
+
setStateForDetentIndex(dimmedDetentIndex - 1)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
388
570
|
|
|
389
|
-
|
|
390
|
-
|
|
571
|
+
// =============================================================================
|
|
572
|
+
// MARK: - TrueSheetBottomSheetViewDelegate - Grabber Tap
|
|
573
|
+
// =============================================================================
|
|
574
|
+
|
|
575
|
+
override fun bottomSheetViewDidTapGrabber() {
|
|
576
|
+
val nextIndex = (currentDetentIndex + 1) % detents.size
|
|
577
|
+
if (nextIndex == 0 && detents.size == 1) {
|
|
578
|
+
if (dismissible) {
|
|
579
|
+
dismiss(animated = true)
|
|
580
|
+
} else {
|
|
581
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
582
|
+
}
|
|
391
583
|
} else {
|
|
392
|
-
|
|
393
|
-
|
|
584
|
+
setStateForDetentIndex(nextIndex)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
394
587
|
|
|
395
|
-
|
|
396
|
-
|
|
588
|
+
override fun bottomSheetViewDidAccessibilityIncrement() {
|
|
589
|
+
if (currentDetentIndex < detents.size - 1) {
|
|
590
|
+
setStateForDetentIndex(currentDetentIndex + 1)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
397
593
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
594
|
+
override fun bottomSheetViewDidAccessibilityDecrement() {
|
|
595
|
+
if (currentDetentIndex > 0) {
|
|
596
|
+
setStateForDetentIndex(currentDetentIndex - 1)
|
|
597
|
+
} else if (dismissible) {
|
|
598
|
+
dismiss(animated = true)
|
|
599
|
+
} else {
|
|
600
|
+
delegate?.viewControllerDidAttemptDismiss()
|
|
601
|
+
}
|
|
602
|
+
}
|
|
401
603
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
604
|
+
// =============================================================================
|
|
605
|
+
// MARK: - BottomSheetCallback
|
|
606
|
+
// =============================================================================
|
|
406
607
|
|
|
407
|
-
|
|
608
|
+
private val sheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
|
|
609
|
+
override fun onStateChanged(sheetView: View, newState: Int) {
|
|
610
|
+
handleStateChanged(sheetView, newState)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
614
|
+
handleSlide(sheetView)
|
|
408
615
|
}
|
|
409
616
|
}
|
|
410
617
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
delegate?.viewControllerDidChangePosition(currentDetentIndex, offScreenPosition, transitioning = true)
|
|
618
|
+
private fun handleStateChanged(sheetView: View, newState: Int) {
|
|
619
|
+
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
620
|
+
if (isBeingDismissed) return
|
|
621
|
+
isBeingDismissed = true
|
|
622
|
+
dismissKeyboard()
|
|
623
|
+
emitWillDismissEvents()
|
|
624
|
+
finishDismiss()
|
|
625
|
+
return
|
|
420
626
|
}
|
|
421
627
|
|
|
422
|
-
|
|
628
|
+
if (!isPresented) return
|
|
629
|
+
|
|
630
|
+
when (newState) {
|
|
631
|
+
BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)
|
|
632
|
+
|
|
633
|
+
BottomSheetBehavior.STATE_SETTLING -> handleSettling(sheetView)
|
|
634
|
+
|
|
635
|
+
BottomSheetBehavior.STATE_EXPANDED,
|
|
636
|
+
BottomSheetBehavior.STATE_COLLAPSED,
|
|
637
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED -> handleStateSettled(sheetView, newState)
|
|
638
|
+
|
|
639
|
+
else -> {}
|
|
640
|
+
}
|
|
423
641
|
}
|
|
424
642
|
|
|
425
|
-
|
|
643
|
+
private fun handleSlide(sheetView: View) {
|
|
644
|
+
// Skip during dismiss animation
|
|
645
|
+
if (isBeingDismissed) return
|
|
426
646
|
|
|
427
|
-
|
|
428
|
-
* Setup sheet detents based on the detent preference.
|
|
429
|
-
*/
|
|
430
|
-
fun setupSheetDetents() {
|
|
431
|
-
val behavior = this.behavior ?: return
|
|
647
|
+
val behavior = behavior ?: return
|
|
432
648
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
skipCollapsed = false
|
|
436
|
-
isFitToContents = true
|
|
649
|
+
when (behavior.state) {
|
|
650
|
+
BottomSheetBehavior.STATE_DRAGGING -> handleDragChange(sheetView)
|
|
437
651
|
|
|
438
|
-
|
|
439
|
-
|
|
652
|
+
BottomSheetBehavior.STATE_SETTLING -> {
|
|
653
|
+
handleDragChange(sheetView)
|
|
654
|
+
dismissKeyboardForSettleTarget(sheetView)
|
|
655
|
+
}
|
|
440
656
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
val detentHeight = getDetentHeight(detents[0])
|
|
444
|
-
maxHeight = detentHeight
|
|
445
|
-
skipCollapsed = true
|
|
446
|
-
}
|
|
657
|
+
else -> { }
|
|
658
|
+
}
|
|
447
659
|
|
|
448
|
-
|
|
449
|
-
val peekHeight = getDetentHeight(detents[0])
|
|
450
|
-
val maxHeightValue = getDetentHeight(detents[1])
|
|
451
|
-
maxHeight = maxHeightValue
|
|
452
|
-
setPeekHeight(peekHeight, isPresented)
|
|
453
|
-
}
|
|
660
|
+
emitChangePositionDelegate(sheetView.top)
|
|
454
661
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
+
}
|
|
458
670
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
671
|
+
// Position on every slide frame — during keyboard transitions the IME
|
|
672
|
+
// animation callbacks are sparser than the sheet's settle frames, and the
|
|
673
|
+
// footer rides the sheet between them, jittering against the keyboard.
|
|
674
|
+
// positionFooter derives from current state, so double-driving it with
|
|
675
|
+
// keyboardDidChangeHeight is harmless.
|
|
676
|
+
positionFooter()
|
|
462
677
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
678
|
+
if (!isKeyboardTransitioning) {
|
|
679
|
+
updateDimAmount(sheetView.top)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private fun handleStateSettled(sheetView: View, newState: Int) {
|
|
684
|
+
if (interactionState is InteractionState.Reconfiguring) return
|
|
685
|
+
|
|
686
|
+
// A release already at a stop settles without a slide frame
|
|
687
|
+
keyboardSettleStartTop = -1
|
|
688
|
+
|
|
689
|
+
// Sheet has reached a stable state — any in-flight resize target is now stale,
|
|
690
|
+
// whether settled at the target, interrupted by drag, or superseded.
|
|
691
|
+
pendingDetentIndex = -1
|
|
692
|
+
|
|
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
|
+
}
|
|
702
|
+
val position = getPositionDpForView(sheetView)
|
|
703
|
+
val detentInfo = DetentInfo(index, position)
|
|
704
|
+
|
|
705
|
+
// Handle present animation completion
|
|
706
|
+
if (isPresentAnimating) {
|
|
707
|
+
isPresentAnimating = false
|
|
708
|
+
finishPresent()
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
val wasDragging = interactionState is InteractionState.Dragging
|
|
713
|
+
|
|
714
|
+
when (interactionState) {
|
|
715
|
+
is InteractionState.Dragging -> {
|
|
716
|
+
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
717
|
+
delegate?.viewControllerDidDragEnd(detentInfo.index, detentInfo.position, detent)
|
|
718
|
+
|
|
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
|
+
}
|
|
467
732
|
}
|
|
733
|
+
|
|
734
|
+
interactionState = InteractionState.Idle
|
|
468
735
|
}
|
|
469
736
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
737
|
+
else -> {
|
|
738
|
+
if (detentInfo.index != currentDetentIndex) {
|
|
739
|
+
currentDetentIndex = detentInfo.index
|
|
740
|
+
if (!isKeyboardTransitioning) {
|
|
741
|
+
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
742
|
+
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
743
|
+
}
|
|
744
|
+
this@TrueSheetViewController.sheetView?.updateGrabberAccessibilityValue(detentInfo.index, detents.size)
|
|
477
745
|
}
|
|
478
746
|
}
|
|
479
747
|
}
|
|
748
|
+
|
|
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
|
+
}
|
|
758
|
+
|
|
759
|
+
if (!isKeyboardTransitioning) {
|
|
760
|
+
updateDimAmount(animated = true)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
764
|
+
|
|
765
|
+
flushPendingSizeChange()
|
|
480
766
|
}
|
|
481
767
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
cornerRadius,
|
|
492
|
-
0f,
|
|
493
|
-
0f,
|
|
494
|
-
0f,
|
|
495
|
-
0f
|
|
496
|
-
)
|
|
768
|
+
private fun flushPendingSizeChange() {
|
|
769
|
+
if (!hasPendingSizeChange) return
|
|
770
|
+
hasPendingSizeChange = false
|
|
771
|
+
setupSheetDetentsForSizeChange()
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// =============================================================================
|
|
775
|
+
// MARK: - Screen Visibility
|
|
776
|
+
// =============================================================================
|
|
497
777
|
|
|
498
|
-
|
|
499
|
-
|
|
778
|
+
private fun setSheetVisibility(visible: Boolean) {
|
|
779
|
+
coordinatorLayout?.visibility = if (visible) VISIBLE else GONE
|
|
780
|
+
dimViews.forEach { it.visibility = if (visible) VISIBLE else INVISIBLE }
|
|
781
|
+
}
|
|
500
782
|
|
|
501
|
-
|
|
502
|
-
|
|
783
|
+
internal fun hideForScreen() {
|
|
784
|
+
val sheet = sheetView ?: run {
|
|
785
|
+
RNLog.e(reactContext, "TrueSheet: sheetView is null in hideForScreen")
|
|
786
|
+
return
|
|
503
787
|
}
|
|
788
|
+
|
|
789
|
+
isSheetVisible = false
|
|
790
|
+
wasHiddenByScreen = true
|
|
791
|
+
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
792
|
+
delegate?.viewControllerDidChangeVisibility(false)
|
|
793
|
+
dimViews.forEach { it.animate().alpha(0f).setDuration(SCREEN_FADE_DURATION).start() }
|
|
794
|
+
sheet.animate()
|
|
795
|
+
.alpha(0f)
|
|
796
|
+
.setDuration(SCREEN_FADE_DURATION)
|
|
797
|
+
.withEndAction {
|
|
798
|
+
setSheetVisibility(false)
|
|
799
|
+
}
|
|
800
|
+
.start()
|
|
801
|
+
|
|
802
|
+
// This will hide parent sheets first
|
|
803
|
+
parentSheetView?.viewController?.hideForScreen()
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
internal fun showAfterScreen() {
|
|
807
|
+
isSheetVisible = true
|
|
808
|
+
delegate?.viewControllerDidChangeVisibility(true)
|
|
809
|
+
setSheetVisibility(true)
|
|
810
|
+
sheetView?.alpha = 1f
|
|
811
|
+
updateDimAmount(animated = true)
|
|
812
|
+
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
504
813
|
}
|
|
505
814
|
|
|
506
815
|
/**
|
|
507
|
-
*
|
|
508
|
-
*
|
|
816
|
+
* Re-applies hidden state after returning from background.
|
|
817
|
+
* Android may restore visibility on activity resume, so we need to hide it again.
|
|
509
818
|
*/
|
|
510
|
-
fun
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
false
|
|
532
|
-
}
|
|
819
|
+
fun reapplyHiddenState() {
|
|
820
|
+
if (!wasHiddenByScreen) return
|
|
821
|
+
setSheetVisibility(false)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// =============================================================================
|
|
825
|
+
// MARK: - Presentation
|
|
826
|
+
// =============================================================================
|
|
827
|
+
|
|
828
|
+
fun present(detentIndex: Int, animated: Boolean = true) {
|
|
829
|
+
val coordinator = this.coordinatorLayout ?: return
|
|
830
|
+
val sheet = this.sheetView ?: return
|
|
831
|
+
if (isPresented) return
|
|
832
|
+
|
|
833
|
+
invalidateScreenMetrics()
|
|
834
|
+
shouldAnimatePresent = animated
|
|
835
|
+
currentDetentIndex = detentIndex
|
|
836
|
+
interactionState = InteractionState.Idle
|
|
837
|
+
|
|
838
|
+
// Setup sheet in coordinator layout
|
|
839
|
+
setupSheetInCoordinator(coordinator, sheet)
|
|
533
840
|
|
|
534
|
-
|
|
535
|
-
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
841
|
+
emitWillPresentEvents()
|
|
536
842
|
|
|
537
|
-
|
|
843
|
+
setupSheetDetents()
|
|
844
|
+
setupDimmedBackground()
|
|
845
|
+
setupKeyboardObserver()
|
|
846
|
+
|
|
847
|
+
sheet.setupBackground()
|
|
848
|
+
sheet.setupElevation()
|
|
849
|
+
sheet.setupGrabber()
|
|
850
|
+
sheet.setupAccessibility()
|
|
851
|
+
|
|
852
|
+
if (shouldAnimatePresent) {
|
|
853
|
+
isPresentAnimating = true
|
|
854
|
+
post { setStateForDetentIndex(currentDetentIndex) }
|
|
855
|
+
} else {
|
|
856
|
+
setStateForDetentIndex(currentDetentIndex)
|
|
857
|
+
post {
|
|
858
|
+
emitChangePositionDelegate(detentCalculator.getSheetTopForDetentIndex(currentDetentIndex))
|
|
859
|
+
updateDimAmount()
|
|
860
|
+
finishPresent()
|
|
538
861
|
}
|
|
539
862
|
}
|
|
863
|
+
|
|
864
|
+
isPresented = true
|
|
865
|
+
isSheetVisible = true
|
|
540
866
|
}
|
|
541
867
|
|
|
542
|
-
fun
|
|
543
|
-
|
|
544
|
-
|
|
868
|
+
fun resize(detentIndex: Int) {
|
|
869
|
+
if (!isPresented) return
|
|
870
|
+
|
|
871
|
+
// Commit the new index so keyboardWillHide restores to it instead of the stale
|
|
872
|
+
// one; the sheet keeps avoiding the keyboard meanwhile
|
|
873
|
+
if (detentIndexBeforeKeyboard >= 0) {
|
|
874
|
+
detentIndexBeforeKeyboard = detentIndex
|
|
875
|
+
if (hasKeyboardFloor) {
|
|
876
|
+
resizePromise?.invoke()
|
|
877
|
+
resizePromise = null
|
|
878
|
+
return
|
|
879
|
+
}
|
|
545
880
|
}
|
|
881
|
+
|
|
882
|
+
pendingDetentIndex = detentIndex
|
|
883
|
+
updateStateDimensions(deferShrink = true)
|
|
884
|
+
setupDimmedBackground()
|
|
885
|
+
setStateForDetentIndex(detentIndex)
|
|
886
|
+
resizePromise?.invoke()
|
|
887
|
+
resizePromise = null
|
|
546
888
|
}
|
|
547
889
|
|
|
548
|
-
fun
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
890
|
+
private fun setupSheetInCoordinator(coordinator: TrueSheetCoordinatorLayout, sheet: TrueSheetBottomSheetView) {
|
|
891
|
+
// Add this controller as content to the sheet
|
|
892
|
+
(parent as? ViewGroup)?.removeView(this)
|
|
893
|
+
sheet.addView(this)
|
|
894
|
+
|
|
895
|
+
// Create layout params with behavior
|
|
896
|
+
val params = sheet.createLayoutParams()
|
|
552
897
|
|
|
553
|
-
|
|
898
|
+
@Suppress("UNCHECKED_CAST")
|
|
899
|
+
val behavior = params.behavior as TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>
|
|
554
900
|
|
|
555
|
-
//
|
|
556
|
-
|
|
901
|
+
// Configure behavior
|
|
902
|
+
behavior.isHideable = true
|
|
903
|
+
behavior.isDraggable = draggable
|
|
904
|
+
behavior.scrollingExpandsSheet = scrollableOptions?.scrollingExpandsSheet ?: true
|
|
905
|
+
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
|
906
|
+
behavior.addBottomSheetCallback(sheetCallback)
|
|
557
907
|
|
|
558
|
-
//
|
|
559
|
-
|
|
560
|
-
|
|
908
|
+
// Add sheet to coordinator
|
|
909
|
+
coordinator.addView(sheet, params)
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
fun dismiss(animated: Boolean = true) {
|
|
913
|
+
if (isBeingDismissed) return
|
|
914
|
+
|
|
915
|
+
isBeingDismissed = true
|
|
916
|
+
dismissKeyboard()
|
|
917
|
+
emitWillDismissEvents()
|
|
918
|
+
|
|
919
|
+
if (animated) {
|
|
920
|
+
animateDismiss()
|
|
921
|
+
} else {
|
|
922
|
+
emitChangePositionDelegate(realScreenHeight)
|
|
923
|
+
finishDismiss()
|
|
561
924
|
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
private fun dismissKeyboard() {
|
|
928
|
+
isKeyboardDismissProgrammatic = true
|
|
929
|
+
KeyboardUtils.dismiss(reactContext)
|
|
562
930
|
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
footer.y = minOf(footerY, maxAllowedY)
|
|
931
|
+
// Clear focus from any focused view within the sheet to prevent
|
|
932
|
+
// Android from autofocusing the next focusable view on the main
|
|
933
|
+
// screen when the sheet is removed from the hierarchy.
|
|
934
|
+
sheetView?.findFocus()?.clearFocus()
|
|
568
935
|
}
|
|
569
936
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
behavior?.state = getStateForDetentIndex(index)
|
|
937
|
+
fun handleBackPress() {
|
|
938
|
+
if (dismissible) {
|
|
939
|
+
dismiss(animated = true)
|
|
940
|
+
}
|
|
575
941
|
}
|
|
576
942
|
|
|
577
|
-
fun
|
|
578
|
-
|
|
943
|
+
private fun animateDismiss() {
|
|
944
|
+
val sheet = sheetView ?: run {
|
|
945
|
+
finishDismiss()
|
|
946
|
+
return
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
sheet.animate()
|
|
950
|
+
.y(realScreenHeight.toFloat())
|
|
951
|
+
.setDuration(DISMISS_DURATION)
|
|
952
|
+
.setInterpolator(android.view.animation.AccelerateInterpolator())
|
|
953
|
+
.setUpdateListener { updateSheetVisuals(sheet.y.toInt()) }
|
|
954
|
+
.withEndAction { finishDismiss() }
|
|
955
|
+
.start()
|
|
579
956
|
}
|
|
580
957
|
|
|
581
|
-
|
|
958
|
+
private fun finishPresent() {
|
|
959
|
+
// Restore isHideable to actual value after present animation
|
|
960
|
+
behavior?.isHideable = dismissible
|
|
582
961
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
962
|
+
containerView?.setupKeyboardHandler()
|
|
963
|
+
|
|
964
|
+
val (index, position, detent) = getDetentInfoWithValue(currentDetentIndex)
|
|
965
|
+
delegate?.viewControllerDidPresent(index, position, detent)
|
|
966
|
+
parentSheetView?.viewControllerDidBlur()
|
|
967
|
+
delegate?.viewControllerDidFocus()
|
|
968
|
+
sheetView?.updateGrabberAccessibilityValue(index, detents.size)
|
|
969
|
+
|
|
970
|
+
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
971
|
+
// Move accessibility focus into the sheet
|
|
972
|
+
sheetView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
|
|
973
|
+
|
|
974
|
+
presentPromise?.invoke()
|
|
975
|
+
presentPromise = null
|
|
591
976
|
}
|
|
592
977
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
delegate?.viewControllerDidDragBegin(detentInfo.index, detentInfo.position)
|
|
599
|
-
isDragging = true
|
|
978
|
+
private fun finishDismiss() {
|
|
979
|
+
TrueSheetStackManager.updateBackgroundAccessibility()
|
|
980
|
+
restoreFocusedView()
|
|
981
|
+
emitDidDismissEvents()
|
|
982
|
+
cleanupSheet()
|
|
600
983
|
}
|
|
601
984
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
private fun handleDragChange(sheetView: View) {
|
|
606
|
-
if (!isDragging) return
|
|
985
|
+
// =============================================================================
|
|
986
|
+
// MARK: - Sheet Configuration
|
|
987
|
+
// =============================================================================
|
|
607
988
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
989
|
+
fun setupSheetDetents(applyState: Boolean = true) {
|
|
990
|
+
val behavior = this.behavior ?: run {
|
|
991
|
+
RNLog.e(reactContext, "TrueSheet: behavior is null in setupSheetDetents")
|
|
992
|
+
return
|
|
993
|
+
}
|
|
611
994
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// For consistency with iOS,
|
|
619
|
-
// we only handle state changes after dragging.
|
|
620
|
-
//
|
|
621
|
-
// Changing detent programmatically is handled via the present method.
|
|
622
|
-
val detentInfo = getDetentInfoForState(state)
|
|
623
|
-
detentInfo?.let {
|
|
624
|
-
// Notify delegate of drag end
|
|
625
|
-
delegate?.viewControllerDidDragEnd(it.index, it.position)
|
|
626
|
-
|
|
627
|
-
if (it.index != currentDetentIndex) {
|
|
628
|
-
presentPromise?.let { promise ->
|
|
629
|
-
promise()
|
|
630
|
-
presentPromise = null
|
|
631
|
-
}
|
|
995
|
+
containerView?.let {
|
|
996
|
+
cachedContentHeight = it.contentHeight
|
|
997
|
+
cachedHeaderHeight = it.headerHeight
|
|
998
|
+
cachedFooterHeight = it.footerHeight
|
|
999
|
+
cachedPeekContentHeight = it.peekContentHeight
|
|
1000
|
+
}
|
|
632
1001
|
|
|
633
|
-
|
|
634
|
-
setupDimmedBackground(it.index)
|
|
1002
|
+
interactionState = InteractionState.Reconfiguring
|
|
635
1003
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1004
|
+
behavior.isFitToContents = false
|
|
1005
|
+
|
|
1006
|
+
val maxAvailableHeight = realScreenHeight - topInset
|
|
1007
|
+
|
|
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)
|
|
1013
|
+
|
|
1014
|
+
val halfExpandedDetentHeight = when (detents.size) {
|
|
1015
|
+
1 -> peekHeight
|
|
1016
|
+
else -> detentCalculator.getDetentHeight(detents[1], includeKeyboard = !hasKeyboardFloor)
|
|
639
1017
|
}
|
|
640
1018
|
|
|
641
|
-
|
|
1019
|
+
val maxDetentHeight = minOf(detentCalculator.getDetentHeight(detents.last()), maxAvailableHeight)
|
|
1020
|
+
|
|
1021
|
+
val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
|
|
1022
|
+
val halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
|
|
1023
|
+
|
|
1024
|
+
val expandedOffset = realScreenHeight - maxDetentHeight
|
|
1025
|
+
|
|
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
|
|
1029
|
+
|
|
1030
|
+
configureDetents(
|
|
1031
|
+
behavior = behavior,
|
|
1032
|
+
peekHeight = peekHeight,
|
|
1033
|
+
halfExpandedRatio = halfExpandedRatio,
|
|
1034
|
+
expandedOffset = expandedOffset,
|
|
1035
|
+
fitToContents = fitToContents,
|
|
1036
|
+
animate = isPresented
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
updateStateDimensions()
|
|
1040
|
+
|
|
1041
|
+
if (isPresented && applyState) {
|
|
1042
|
+
// Prefer the pending target while a resize animation is in flight so a
|
|
1043
|
+
// layout-driven reconfigure doesn't revert to the stale currentDetentIndex.
|
|
1044
|
+
val targetIndex = if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex
|
|
1045
|
+
setStateForDetentIndex(targetIndex)
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
interactionState = InteractionState.Idle
|
|
642
1049
|
}
|
|
643
1050
|
|
|
644
|
-
|
|
1051
|
+
private fun configureDetents(
|
|
1052
|
+
behavior: BottomSheetBehavior<TrueSheetBottomSheetView>,
|
|
1053
|
+
peekHeight: Int,
|
|
1054
|
+
halfExpandedRatio: Float,
|
|
1055
|
+
expandedOffset: Int,
|
|
1056
|
+
fitToContents: Boolean,
|
|
1057
|
+
animate: Boolean
|
|
1058
|
+
) {
|
|
1059
|
+
behavior.apply {
|
|
1060
|
+
isFitToContents = fitToContents
|
|
1061
|
+
skipCollapsed = false
|
|
1062
|
+
setPeekHeight(peekHeight, animate)
|
|
1063
|
+
this.halfExpandedRatio = halfExpandedRatio.coerceIn(0.01f, 0.999f)
|
|
1064
|
+
this.expandedOffset = expandedOffset
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
645
1067
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (detent <= 0.0 || detent > 1.0) {
|
|
655
|
-
throw IllegalArgumentException("TrueSheet: detent fraction ($detent) must be between 0 and 1")
|
|
656
|
-
}
|
|
657
|
-
(detent * maxScreenHeight).toInt()
|
|
1068
|
+
fun setupSheetDetentsForSizeChange() {
|
|
1069
|
+
// Defer while dragging — the size change is driven by the drag itself (the
|
|
1070
|
+
// container tracks the sheet's visible height), and reconfiguring resets
|
|
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
|
|
658
1076
|
}
|
|
1077
|
+
hasPendingSizeChange = false
|
|
659
1078
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
val finalHeight = maxSheetHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
|
|
663
|
-
return finalHeight
|
|
1079
|
+
setupSheetDetents()
|
|
1080
|
+
positionFooter()
|
|
664
1081
|
}
|
|
665
1082
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
1083
|
+
fun setStateForDetentIndex(index: Int) {
|
|
1084
|
+
behavior?.state = detentCalculator.getStateForDetentIndex(index)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// =============================================================================
|
|
1088
|
+
// MARK: - Dimmed Background
|
|
1089
|
+
// =============================================================================
|
|
674
1090
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1091
|
+
fun setupDimmedBackground() {
|
|
1092
|
+
val coordinator = this.coordinatorLayout ?: run {
|
|
1093
|
+
RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupDimmedBackground")
|
|
1094
|
+
return
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (dimmed) {
|
|
1098
|
+
val parentDimVisible = (parentSheetView?.viewController?.dimView?.alpha ?: 0f) > 0f
|
|
1099
|
+
|
|
1100
|
+
if (dimView == null) {
|
|
1101
|
+
dimView = TrueSheetDimView(reactContext).apply {
|
|
1102
|
+
delegate = this@TrueSheetViewController
|
|
680
1103
|
}
|
|
681
1104
|
}
|
|
1105
|
+
if (!parentDimVisible) {
|
|
1106
|
+
dimView?.attachToCoordinator(coordinator)
|
|
1107
|
+
}
|
|
682
1108
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1109
|
+
// Attach dim view to parent sheet if stacked
|
|
1110
|
+
val parentController = parentSheetView?.viewController
|
|
1111
|
+
val parentBottomSheet = parentController?.sheetView
|
|
1112
|
+
if (parentBottomSheet != null) {
|
|
1113
|
+
if (parentDimView == null) {
|
|
1114
|
+
parentDimView = TrueSheetDimView(reactContext).apply {
|
|
1115
|
+
delegate = this@TrueSheetViewController
|
|
1116
|
+
}
|
|
689
1117
|
}
|
|
1118
|
+
parentDimView?.attach(parentBottomSheet, parentController.sheetCornerRadius)
|
|
690
1119
|
}
|
|
1120
|
+
} else {
|
|
1121
|
+
dimView?.detach()
|
|
1122
|
+
dimView = null
|
|
1123
|
+
parentDimView?.detach()
|
|
1124
|
+
parentDimView = null
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
691
1127
|
|
|
692
|
-
|
|
1128
|
+
fun updateDimAmount(sheetTop: Int? = null, animated: Boolean = false) {
|
|
1129
|
+
if (!dimmed) return
|
|
1130
|
+
if (contentHeight == 0) return
|
|
1131
|
+
|
|
1132
|
+
// While keyboard is active or transitioning, use the target detent position for dim
|
|
1133
|
+
val top = if (isKeyboardActive) {
|
|
1134
|
+
detentCalculator.getSheetTopForDetentIndex(currentDetentIndex)
|
|
1135
|
+
} else {
|
|
1136
|
+
val keyboardOffset = if (isBeingDismissed) 0 else currentKeyboardInset
|
|
1137
|
+
(sheetTop ?: sheetView?.top ?: return) + keyboardOffset
|
|
693
1138
|
}
|
|
694
1139
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1140
|
+
if (animated) {
|
|
1141
|
+
val targetAlpha = dimView?.calculateAlpha(
|
|
1142
|
+
top,
|
|
1143
|
+
dimmedDetentIndex,
|
|
1144
|
+
detentCalculator::getSheetTopForDetentIndex
|
|
1145
|
+
) ?: 0f
|
|
1146
|
+
dimViews.forEach { it.animate().alpha(targetAlpha).setDuration(200).start() }
|
|
1147
|
+
} else {
|
|
1148
|
+
dimViews.forEach { it.interpolateAlpha(top, dimmedDetentIndex, detentCalculator::getSheetTopForDetentIndex) }
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// =============================================================================
|
|
1153
|
+
// MARK: - Footer Positioning
|
|
1154
|
+
// =============================================================================
|
|
1155
|
+
|
|
1156
|
+
var footerKeyboardOffset: Int = 0
|
|
1157
|
+
|
|
1158
|
+
fun positionFooter() {
|
|
1159
|
+
if (!isPresented) return
|
|
1160
|
+
val footerView = containerView?.footerView ?: return
|
|
1161
|
+
val sheet = sheetView ?: return
|
|
1162
|
+
|
|
1163
|
+
// A relative footer is laid out by Yoga (pinned to the container's bottom
|
|
1164
|
+
// edge) and stays in the layout flow behind the keyboard — only an
|
|
1165
|
+
// absolute footer floats above it
|
|
1166
|
+
if (!absoluteFooter) {
|
|
1167
|
+
footerView.translationY = 0f
|
|
1168
|
+
return
|
|
1169
|
+
}
|
|
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
|
+
|
|
1180
|
+
val footerHeight = footerView.height
|
|
1181
|
+
val sheetHeight = sheet.height
|
|
1182
|
+
|
|
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()
|
|
1188
|
+
|
|
1189
|
+
// Clamp to prevent footer going above safe area
|
|
1190
|
+
val maxAllowedY = (sheetHeight - topInset - footerHeight).toFloat()
|
|
1191
|
+
footerView.y = minOf(footerY, maxAllowedY)
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// =============================================================================
|
|
1195
|
+
// MARK: - Keyboard Handling
|
|
1196
|
+
// =============================================================================
|
|
1197
|
+
|
|
1198
|
+
private fun shouldHandleKeyboard(checkFocus: Boolean = true): Boolean {
|
|
1199
|
+
if (wasHiddenByScreen) return false
|
|
1200
|
+
if (!isTopmostSheet) return false
|
|
1201
|
+
if (checkFocus && !isFocusedViewWithinSheet()) return false
|
|
1202
|
+
return true
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
fun saveFocusedView() {
|
|
1206
|
+
focusedViewBeforeBlur = delegate?.findRootContainerView()?.findFocus()
|
|
1207
|
+
?: reactContext.currentActivity?.currentFocus
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
fun restoreFocusedView() {
|
|
1211
|
+
val viewToFocus = focusedViewBeforeBlur ?: return
|
|
1212
|
+
focusedViewBeforeBlur = null
|
|
1213
|
+
|
|
1214
|
+
if (!viewToFocus.isAttachedToWindow) return
|
|
1215
|
+
if (viewToFocus.requestFocus()) {
|
|
1216
|
+
viewToFocus.postDelayed({
|
|
1217
|
+
KeyboardUtils.show(viewToFocus)
|
|
1218
|
+
}, 100)
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
fun setupKeyboardObserver() {
|
|
1223
|
+
val coordinator = coordinatorLayout ?: run {
|
|
1224
|
+
RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupKeyboardObserver")
|
|
1225
|
+
return
|
|
1226
|
+
}
|
|
1227
|
+
cleanupKeyboardObserver()
|
|
1228
|
+
keyboardObserver = TrueSheetKeyboardObserver(coordinator, reactContext).apply {
|
|
1229
|
+
delegate = object : TrueSheetKeyboardObserverDelegate {
|
|
1230
|
+
override fun keyboardWillShow(height: Int) {
|
|
1231
|
+
if (!shouldHandleKeyboard()) return
|
|
1232
|
+
// If a resize is in flight, restore to its target — not the stale current
|
|
1233
|
+
detentIndexBeforeKeyboard = if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex
|
|
1234
|
+
pendingDetentIndex = -1
|
|
1235
|
+
// Commit the target index first so the container grows before the sheet expands
|
|
1236
|
+
currentDetentIndex = detents.size - 1
|
|
1237
|
+
setupSheetDetents()
|
|
1238
|
+
positionFooter()
|
|
1239
|
+
updateDimAmount(animated = true)
|
|
705
1240
|
}
|
|
706
|
-
}
|
|
707
1241
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1242
|
+
override fun keyboardWillHide() {
|
|
1243
|
+
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
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
|
+
}
|
|
1251
|
+
val restoring = !isBeingDismissed && detentIndexBeforeKeyboard >= 0
|
|
1252
|
+
|
|
1253
|
+
// Skip reconfigure during interactive keyboard dismiss (e.g. keyboardDismissMode="on-drag")
|
|
1254
|
+
// to prevent the sheet from jumping. keyboardDidHide will reconfigure after.
|
|
1255
|
+
if (restoring || isKeyboardDismissProgrammatic) {
|
|
1256
|
+
setupSheetDetents()
|
|
1257
|
+
if (restoring) {
|
|
1258
|
+
currentDetentIndex = detentIndexBeforeKeyboard
|
|
1259
|
+
setStateForDetentIndex(currentDetentIndex)
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Same-frame reposition — the reconfigure above shrinks the container
|
|
1264
|
+
// to the target detent, which yanks the bottom-pinned footer up until
|
|
1265
|
+
// the next keyboard animation frame repositions it.
|
|
1266
|
+
positionFooter()
|
|
1267
|
+
updateDimAmount(
|
|
1268
|
+
sheetTop = detentCalculator.getSheetTopForDetentIndex(currentDetentIndex),
|
|
1269
|
+
animated = true
|
|
1270
|
+
)
|
|
713
1271
|
}
|
|
714
|
-
}
|
|
715
1272
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1273
|
+
override fun keyboardDidHide() {
|
|
1274
|
+
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
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
|
|
1282
|
+
setupSheetDetents(applyState = false)
|
|
1283
|
+
positionFooter()
|
|
1284
|
+
updateDimAmount(
|
|
1285
|
+
sheetTop = detentCalculator.getSheetTopForDetentIndex(currentDetentIndex),
|
|
1286
|
+
animated = true
|
|
1287
|
+
)
|
|
722
1288
|
}
|
|
723
|
-
}
|
|
724
1289
|
|
|
725
|
-
|
|
1290
|
+
override fun keyboardDidChangeHeight(height: Int) {
|
|
1291
|
+
// Skip focus check during active keyboard transitions (focus may be lost during hide)
|
|
1292
|
+
val skipFocusCheck = detentIndexBeforeKeyboard >= 0 || isKeyboardTransitioning
|
|
1293
|
+
if (!shouldHandleKeyboard(checkFocus = !skipFocusCheck)) return
|
|
1294
|
+
positionFooter()
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
override fun focusDidChange(newFocus: View) {
|
|
1298
|
+
// Handle case where keyboard is already visible and focus moves into the sheet
|
|
1299
|
+
if (!shouldHandleKeyboard()) return
|
|
1300
|
+
if (detentIndexBeforeKeyboard < 0 && (keyboardObserver?.currentHeight ?: 0) > 0) {
|
|
1301
|
+
detentIndexBeforeKeyboard = currentDetentIndex
|
|
1302
|
+
currentDetentIndex = detents.size - 1
|
|
1303
|
+
setupSheetDetents()
|
|
1304
|
+
positionFooter()
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
start()
|
|
726
1309
|
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
fun cleanupKeyboardObserver() {
|
|
1313
|
+
keyboardObserver?.stop()
|
|
1314
|
+
keyboardObserver = null
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// =============================================================================
|
|
1318
|
+
// MARK: - Drag Handling
|
|
1319
|
+
// =============================================================================
|
|
1320
|
+
|
|
1321
|
+
private fun getPositionDpForView(sheetView: View): Float =
|
|
1322
|
+
detentCalculator.getPositionDp(detentCalculator.getVisibleSheetHeight(sheetView.top))
|
|
1323
|
+
|
|
1324
|
+
fun commitKeyboardDetent() {
|
|
1325
|
+
detentIndexBeforeKeyboard = -1
|
|
1326
|
+
}
|
|
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
|
+
}
|
|
727
1332
|
|
|
728
1333
|
/**
|
|
729
|
-
*
|
|
730
|
-
*
|
|
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.
|
|
731
1339
|
*/
|
|
732
|
-
private fun
|
|
733
|
-
if (
|
|
734
|
-
|
|
735
|
-
|
|
1340
|
+
private fun dismissKeyboardForSettleTarget(sheetView: View) {
|
|
1341
|
+
if (keyboardSettleStartTop < 0) return
|
|
1342
|
+
val startTop = keyboardSettleStartTop
|
|
1343
|
+
keyboardSettleStartTop = -1
|
|
736
1344
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1345
|
+
val headingToKeyboardFreeStop = sheetView.top > startTop || startTop > detentCalculator.getKeyboardHalfStopTop()
|
|
1346
|
+
if (headingToKeyboardFreeStop && shouldHandleKeyboard()) dismissKeyboard()
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
private fun handleDragBegin(sheetView: View) {
|
|
1350
|
+
pendingDetentIndex = -1
|
|
1351
|
+
|
|
1352
|
+
val position = getPositionDpForView(sheetView)
|
|
1353
|
+
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
1354
|
+
delegate?.viewControllerDidDragBegin(currentDetentIndex, position, detent)
|
|
1355
|
+
interactionState = InteractionState.Dragging(startTop = sheetView.top)
|
|
1356
|
+
}
|
|
1357
|
+
|
|
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()
|
|
1366
|
+
|
|
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)
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
private fun handleDragChange(sheetView: View) {
|
|
1375
|
+
if (interactionState !is InteractionState.Dragging) return
|
|
1376
|
+
|
|
1377
|
+
updateStateDimensionsForDrag(sheetView.top)
|
|
1378
|
+
|
|
1379
|
+
val position = getPositionDpForView(sheetView)
|
|
1380
|
+
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
1381
|
+
delegate?.viewControllerDidDragChange(currentDetentIndex, position, detent)
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// =============================================================================
|
|
1385
|
+
// MARK: - Event Emission
|
|
1386
|
+
// =============================================================================
|
|
1387
|
+
|
|
1388
|
+
private fun emitWillPresentEvents() {
|
|
1389
|
+
val (index, position, detent) = getDetentInfoWithValue(currentDetentIndex)
|
|
1390
|
+
parentSheetView?.viewControllerWillBlur()
|
|
1391
|
+
delegate?.viewControllerWillPresent(index, position, detent)
|
|
1392
|
+
delegate?.viewControllerWillFocus()
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
private fun emitWillDismissEvents() {
|
|
1396
|
+
delegate?.viewControllerWillBlur()
|
|
1397
|
+
delegate?.viewControllerWillDismiss()
|
|
1398
|
+
parentSheetView?.viewControllerWillFocus()
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
private fun emitDidDismissEvents() {
|
|
1402
|
+
val parent = parentSheetView
|
|
1403
|
+
parentSheetView = null
|
|
1404
|
+
|
|
1405
|
+
delegate?.viewControllerDidBlur()
|
|
1406
|
+
delegate?.viewControllerDidDismiss(parent)
|
|
1407
|
+
|
|
1408
|
+
dismissPromise?.invoke()
|
|
1409
|
+
dismissPromise = null
|
|
1410
|
+
}
|
|
746
1411
|
|
|
747
|
-
|
|
748
|
-
|
|
1412
|
+
private fun emitChangePositionDelegate(currentTop: Int, realtime: Boolean = true) {
|
|
1413
|
+
// Dedupe emissions for same position
|
|
1414
|
+
if (currentTop == lastEmittedPositionPx) return
|
|
749
1415
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1416
|
+
lastEmittedPositionPx = currentTop
|
|
1417
|
+
val visibleHeight = realScreenHeight - currentTop
|
|
1418
|
+
val position = detentCalculator.getPositionDp(visibleHeight)
|
|
1419
|
+
val (interpolatedIndex, detent) = detentCalculator.getInterpolatedPositionInfo(currentTop)
|
|
1420
|
+
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
1421
|
+
}
|
|
755
1422
|
|
|
756
|
-
|
|
1423
|
+
/**
|
|
1424
|
+
* Updates position emission, footer, and dim amount together.
|
|
1425
|
+
* This pattern is commonly used during animations and state changes.
|
|
1426
|
+
*/
|
|
1427
|
+
private fun updateSheetVisuals(effectiveTop: Int) {
|
|
1428
|
+
emitChangePositionDelegate(effectiveTop)
|
|
1429
|
+
positionFooter()
|
|
1430
|
+
updateDimAmount(effectiveTop)
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// =============================================================================
|
|
1434
|
+
// MARK: - Detent Helpers
|
|
1435
|
+
// =============================================================================
|
|
1436
|
+
|
|
1437
|
+
fun updateBehaviorMaxWidth() {
|
|
1438
|
+
val behavior = this.behavior ?: return
|
|
1439
|
+
val applyMaxWidth = maxContentWidth != null && !ScreenUtils.isPortraitPhone(reactContext)
|
|
1440
|
+
val newMaxWidth = if (applyMaxWidth) maxContentWidth!! else DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
1441
|
+
if (behavior.maxWidth == newMaxWidth) return
|
|
1442
|
+
|
|
1443
|
+
behavior.maxWidth = newMaxWidth
|
|
757
1444
|
}
|
|
758
1445
|
|
|
759
1446
|
/**
|
|
760
|
-
*
|
|
1447
|
+
* Updates the Fabric state with the current/target detent size so Yoga sizes
|
|
1448
|
+
* the container to the sheet's visible height.
|
|
1449
|
+
*
|
|
1450
|
+
* Unlike iOS, resize animations run over multiple frames (ViewDragHelper), so
|
|
1451
|
+
* shrinking is deferred to settle ([deferShrink]) when a resize animation is
|
|
1452
|
+
* about to run — growing applies immediately so content is laid out before
|
|
1453
|
+
* the sheet reveals it.
|
|
761
1454
|
*/
|
|
762
|
-
fun
|
|
1455
|
+
private fun updateStateDimensions(deferShrink: Boolean = false) {
|
|
1456
|
+
if (detents.isEmpty()) return
|
|
763
1457
|
|
|
764
|
-
|
|
1458
|
+
val targetIndex = (if (pendingDetentIndex >= 0) pendingDetentIndex else currentDetentIndex)
|
|
1459
|
+
.coerceIn(0, detents.size - 1)
|
|
1460
|
+
val maxAvailableHeight = realScreenHeight - topInset
|
|
1461
|
+
val newHeight = minOf(detentCalculator.getDetentHeight(detents[targetIndex]), maxAvailableHeight)
|
|
1462
|
+
val newWidth = getStateWidth()
|
|
765
1463
|
|
|
766
|
-
|
|
767
|
-
super.onInitializeAccessibilityNodeInfo(info)
|
|
1464
|
+
if (deferShrink && newWidth == lastStateWidth && newHeight < lastStateHeight) return
|
|
768
1465
|
|
|
769
|
-
|
|
770
|
-
if (testId != null) {
|
|
771
|
-
info.viewIdResourceName = testId
|
|
772
|
-
}
|
|
1466
|
+
setStateDimensions(newWidth, newHeight)
|
|
773
1467
|
}
|
|
774
1468
|
|
|
775
|
-
|
|
776
|
-
|
|
1469
|
+
/**
|
|
1470
|
+
* Tracks the sheet's visible height during drag (and the post-release settle
|
|
1471
|
+
* animation) so the container resizes with the finger, like iOS. Clamped to the
|
|
1472
|
+
* smallest detent — dragging below it slides the sheet out without resizing.
|
|
1473
|
+
*/
|
|
1474
|
+
private fun updateStateDimensionsForDrag(sheetTop: Int) {
|
|
1475
|
+
if (detents.isEmpty()) return
|
|
777
1476
|
|
|
778
|
-
|
|
779
|
-
|
|
1477
|
+
val maxAvailableHeight = realScreenHeight - topInset
|
|
1478
|
+
val minHeight = minOf(
|
|
1479
|
+
detentCalculator.getDetentHeight(detents.first(), includeKeyboard = !hasKeyboardFloor),
|
|
1480
|
+
maxAvailableHeight
|
|
1481
|
+
)
|
|
1482
|
+
val newHeight = detentCalculator.getVisibleSheetHeight(sheetTop).coerceIn(minHeight, maxAvailableHeight)
|
|
780
1483
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1484
|
+
setStateDimensions(getStateWidth(), newHeight)
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
private fun getStateWidth(): Int {
|
|
1488
|
+
val applyMaxWidth = maxContentWidth != null && !ScreenUtils.isPortraitPhone(reactContext)
|
|
1489
|
+
val effectiveMaxWidth = if (applyMaxWidth) maxContentWidth!! else DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
1490
|
+
return minOf(screenWidth, effectiveMaxWidth)
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
private fun setStateDimensions(width: Int, height: Int) {
|
|
1494
|
+
if (lastStateWidth == width && lastStateHeight == height) return
|
|
1495
|
+
|
|
1496
|
+
lastStateWidth = width
|
|
1497
|
+
lastStateHeight = height
|
|
1498
|
+
delegate?.viewControllerDidChangeSize(width, height)
|
|
1499
|
+
}
|
|
1500
|
+
|
|
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
|
+
}
|
|
784
1510
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
// Recalculate sheet detents with new screen dimensions
|
|
788
|
-
setupSheetDetents()
|
|
1511
|
+
fun translateSheet(translationY: Int, animated: Boolean = true, onEnd: (() -> Unit)? = null) {
|
|
1512
|
+
val sheet = sheetView ?: return
|
|
789
1513
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1514
|
+
if (!animated) {
|
|
1515
|
+
sheet.animate().cancel()
|
|
1516
|
+
sheet.translationY = translationY.toFloat()
|
|
1517
|
+
emitChangePositionDelegate(sheet.top + translationY)
|
|
1518
|
+
onEnd?.invoke()
|
|
1519
|
+
return
|
|
1520
|
+
}
|
|
793
1521
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1522
|
+
sheet.animate()
|
|
1523
|
+
.translationY(translationY.toFloat())
|
|
1524
|
+
.setDuration(TRANSLATE_ANIMATION_DURATION)
|
|
1525
|
+
.setUpdateListener {
|
|
1526
|
+
val effectiveTop = sheet.top + sheet.translationY.toInt()
|
|
1527
|
+
emitChangePositionDelegate(effectiveTop)
|
|
1528
|
+
}
|
|
1529
|
+
.withEndAction { onEnd?.invoke() }
|
|
1530
|
+
.start()
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
private fun getDetentInfoWithValue(index: Int): Triple<Int, Float, Float> {
|
|
1534
|
+
val state = detentCalculator.getStateForDetentIndex(index)
|
|
1535
|
+
val detentIndex = detentCalculator.getDetentIndexForState(state) ?: 0
|
|
1536
|
+
val position = getPositionForDetentIndex(detentIndex)
|
|
1537
|
+
val detent = detentCalculator.getDetentValueForIndex(detentIndex)
|
|
1538
|
+
return Triple(detentIndex, position, detent)
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
private fun getPositionForDetentIndex(index: Int): Float {
|
|
1542
|
+
if (index < 0 || index >= detents.size) return screenHeight.pxToDp()
|
|
1543
|
+
|
|
1544
|
+
sheetView?.let {
|
|
1545
|
+
val visibleSheetHeight = detentCalculator.getVisibleSheetHeight(it.top)
|
|
1546
|
+
if (visibleSheetHeight in 1..<realScreenHeight) {
|
|
1547
|
+
return detentCalculator.getPositionDp(visibleSheetHeight)
|
|
797
1548
|
}
|
|
798
1549
|
}
|
|
799
1550
|
|
|
800
|
-
|
|
801
|
-
|
|
1551
|
+
val detentHeight = detentCalculator.getDetentHeight(detents[index])
|
|
1552
|
+
return detentCalculator.getPositionDp(detentHeight)
|
|
802
1553
|
}
|
|
803
1554
|
|
|
804
|
-
|
|
805
|
-
|
|
1555
|
+
// =============================================================================
|
|
1556
|
+
// MARK: - RootView Implementation
|
|
1557
|
+
// =============================================================================
|
|
1558
|
+
|
|
1559
|
+
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
|
|
1560
|
+
super.onInitializeAccessibilityNodeInfo(info)
|
|
1561
|
+
(getTag(R.id.react_test_id) as? String)?.let { info.viewIdResourceName = it }
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// =============================================================================
|
|
1565
|
+
// MARK: - RootView Touch Handling
|
|
1566
|
+
// =============================================================================
|
|
1567
|
+
|
|
1568
|
+
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
1569
|
+
val footer = containerView?.footerView
|
|
1570
|
+
val action = event.actionMasked
|
|
1571
|
+
|
|
1572
|
+
if (footer != null && footer.isShown) {
|
|
1573
|
+
if (action == MotionEvent.ACTION_DOWN) {
|
|
1574
|
+
val loc = ScreenUtils.getScreenLocation(footer)
|
|
1575
|
+
val x = event.rawX.toInt()
|
|
1576
|
+
val y = event.rawY.toInt()
|
|
1577
|
+
footerOwnsTouchStream = x >= loc[0] &&
|
|
1578
|
+
x <= loc[0] + footer.width &&
|
|
1579
|
+
y >= loc[1] &&
|
|
1580
|
+
y <= loc[1] + footer.height
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (footerOwnsTouchStream) {
|
|
1584
|
+
val loc = ScreenUtils.getScreenLocation(footer)
|
|
1585
|
+
val localEvent = MotionEvent.obtain(event)
|
|
1586
|
+
localEvent.setLocation(event.rawX - loc[0], event.rawY - loc[1])
|
|
1587
|
+
val handled = footer.dispatchTouchEvent(localEvent)
|
|
1588
|
+
localEvent.recycle()
|
|
1589
|
+
|
|
1590
|
+
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
1591
|
+
footerOwnsTouchStream = false
|
|
1592
|
+
}
|
|
1593
|
+
if (handled) return true
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
|
1598
|
+
footerOwnsTouchStream = false
|
|
1599
|
+
}
|
|
1600
|
+
return super.dispatchTouchEvent(event)
|
|
806
1601
|
}
|
|
807
1602
|
|
|
808
1603
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
809
|
-
eventDispatcher?.let {
|
|
810
|
-
|
|
811
|
-
|
|
1604
|
+
eventDispatcher?.let {
|
|
1605
|
+
if (touchDeduper.shouldDispatch(event)) {
|
|
1606
|
+
jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
|
|
1607
|
+
}
|
|
1608
|
+
jsPointerDispatcher.handleMotionEvent(event, it, true)
|
|
812
1609
|
}
|
|
813
1610
|
return super.onInterceptTouchEvent(event)
|
|
814
1611
|
}
|
|
815
1612
|
|
|
1613
|
+
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|
|
1614
|
+
// Mirrors ReactSurfaceView: keep receiving onInterceptTouchEvent so
|
|
1615
|
+
// jsTouchDispatcher sees the gesture end, but forward the request up the tree.
|
|
1616
|
+
parent?.requestDisallowInterceptTouchEvent(disallowIntercept)
|
|
1617
|
+
}
|
|
1618
|
+
|
|
816
1619
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
817
|
-
eventDispatcher?.let {
|
|
818
|
-
|
|
819
|
-
|
|
1620
|
+
eventDispatcher?.let {
|
|
1621
|
+
if (touchDeduper.shouldDispatch(event)) {
|
|
1622
|
+
jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
|
|
1623
|
+
}
|
|
1624
|
+
jsPointerDispatcher.handleMotionEvent(event, it, false)
|
|
820
1625
|
}
|
|
821
1626
|
super.onTouchEvent(event)
|
|
822
|
-
// In case when there is no children interested in handling touch event, we return true from
|
|
823
|
-
// the root view in order to receive subsequent events related to that gesture
|
|
824
1627
|
return true
|
|
825
1628
|
}
|
|
826
1629
|
|
|
827
1630
|
override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
|
|
828
|
-
eventDispatcher?.let {
|
|
1631
|
+
eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, true) }
|
|
829
1632
|
return super.onHoverEvent(event)
|
|
830
1633
|
}
|
|
831
1634
|
|
|
832
1635
|
override fun onHoverEvent(event: MotionEvent): Boolean {
|
|
833
|
-
eventDispatcher?.let {
|
|
1636
|
+
eventDispatcher?.let { jsPointerDispatcher.handleMotionEvent(event, it, false) }
|
|
834
1637
|
return super.onHoverEvent(event)
|
|
835
1638
|
}
|
|
836
1639
|
|
|
837
|
-
@OptIn(UnstableReactNativeAPI::class)
|
|
838
|
-
@Suppress("DEPRECATION")
|
|
839
1640
|
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1641
|
+
coordinatorLayout?.childDidClaimNativeGesture()
|
|
1642
|
+
eventDispatcher?.let {
|
|
1643
|
+
jsTouchDispatcher.onChildStartedNativeGesture(ev, it)
|
|
1644
|
+
jsPointerDispatcher.onChildStartedNativeGesture(childView, ev, it)
|
|
843
1645
|
}
|
|
844
1646
|
}
|
|
845
1647
|
|
|
846
1648
|
override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
|
|
847
|
-
eventDispatcher?.let {
|
|
848
|
-
|
|
1649
|
+
eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
|
|
1650
|
+
jsPointerDispatcher.onChildEndedNativeGesture()
|
|
849
1651
|
}
|
|
850
1652
|
|
|
851
|
-
override fun
|
|
852
|
-
|
|
853
|
-
super.requestDisallowInterceptTouchEvent(disallowIntercept)
|
|
1653
|
+
override fun handleException(t: Throwable) {
|
|
1654
|
+
reactContext.reactApplicationContext.handleException(RuntimeException(t))
|
|
854
1655
|
}
|
|
855
1656
|
}
|