Files
streamyfin/hooks/useCastDismissGesture.ts

70 lines
1.9 KiB
TypeScript

import type { Router } from "expo-router";
import { useCallback } from "react";
import { Gesture } from "react-native-gesture-handler";
import {
runOnJS,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated";
interface UseCastDismissGestureParams {
router: Router;
}
/**
* Swipe-down-to-dismiss gesture cluster for the casting player modal.
* Owns the `translateY`/`context` shared values, the pan gesture, the animated
* style, and the `dismissModal` callback (also invoked by the header button).
*/
export function useCastDismissGesture({ router }: UseCastDismissGestureParams) {
// Swipe down to dismiss gesture
const translateY = useSharedValue(0);
const context = useSharedValue({ y: 0 });
const dismissModal = useCallback(() => {
// Navigate immediately without animation to prevent crashes
if (router.canGoBack()) {
router.back();
} else {
router.replace("/(auth)/(tabs)/(home)/");
}
}, [router]);
const panGesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value };
})
.onUpdate((event) => {
// Only allow downward swipes from top of screen
if (event.translationY > 0) {
translateY.value = context.value.y + event.translationY;
}
})
.onEnd((event) => {
// Dismiss if swiped down more than 150px or fast swipe
if (event.translationY > 150 || event.velocityY > 600) {
// Animate down and dismiss
translateY.value = withSpring(
1000,
{
damping: 20,
stiffness: 90,
},
() => {
runOnJS(dismissModal)();
},
);
} else {
// Spring back to position
translateY.value = withSpring(0);
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
}));
return { panGesture, animatedStyle, dismissModal };
}