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