import { useCallback, useEffect, useRef } from "react"; import { BackHandler, ViewStyle } from "react-native"; import { Extrapolate, interpolate, runOnJS, useAnimatedGestureHandler, useAnimatedScrollHandler, useAnimatedStyle, withTiming, } from "react-native-reanimated"; import { useDimensions } from "../../commons"; import { DISTANCE, RATIO } from "./configs"; import { AnimatedValue, AnimationType, GestureCtxType, LayoutType, ModalType, } from "./types"; export const useStyle = ( layout: LayoutType, modalType: ModalType, animationType: AnimationType, visibleValue: AnimatedValue, gestureX: AnimatedValue, gestureY: AnimatedValue ) => { const { width, height } = useDimensions(); const overlayStyle = useAnimatedStyle( () => ({ opacity: interpolate(visibleValue.value, [0, 1], [0, 1], Extrapolate.CLAMP) - interpolate( Math.abs(gestureX.value), [0, layout.width], [0, 1], Extrapolate.CLAMP ) - interpolate( Math.abs(gestureY.value), [0, layout.height], [0, 1], Extrapolate.CLAMP ), }), [layout] ); const containerStyle = useAnimatedStyle(() => { const slide = animationType === "slide"; const style: ViewStyle = {}; switch (modalType) { case "top": if (slide) style.transform = [ { translateY: interpolate( visibleValue.value, [0, 1], [-height, 0], Extrapolate.CLAMP ), }, ]; break; case "left": style.flexDirection = "row"; if (slide) style.transform = [ { translateX: interpolate( visibleValue.value, [0, 1], [-width, 0], Extrapolate.CLAMP ), }, ]; break; case "right": style.flexDirection = "row"; if (slide) style.transform = [ { translateX: interpolate( visibleValue.value, [0, 1], [width, 0], Extrapolate.CLAMP ), }, ]; break; case "bottom": if (slide) style.transform = [ { translateY: interpolate( visibleValue.value, [0, 1], [height, 0], Extrapolate.CLAMP ), }, ]; break; default: style.alignItems = "center"; style.justifyContent = "center"; break; } if (animationType === "scale") style.transform = [ { scale: interpolate( visibleValue.value, [0, 1], [0, 1], Extrapolate.CLAMP ), }, ]; if (animationType === "fade") style.opacity = interpolate( visibleValue.value, [0, 1], [0, 1], Extrapolate.CLAMP ); return style; }, [modalType, animationType]); const contentStyle = useAnimatedStyle(() => { return { transform: [ { translateX: gestureX.value, }, { translateY: gestureY.value, }, ], }; }, []); return { overlayStyle, contentStyle, containerStyle }; }; export const useGesture = ( layout: LayoutType, modalType: ModalType, gestureX: AnimatedValue, gestureY: AnimatedValue, visibleHandler?: (v: boolean) => void ) => { return useAnimatedGestureHandler( { onStart: (_, ctx: GestureCtxType) => { ctx.startX = gestureX.value; ctx.startY = gestureY.value; }, onActive: (ev, ctx) => { if (modalType === "top") { gestureY.value = Math.max( -layout.height, Math.min(ctx.startY + ev.translationY, 0) ); return; } if (modalType === "left") { gestureX.value = Math.max( -layout.width, Math.min(ctx.startX + ev.translationX, 0) ); return; } if (modalType === "right") { gestureX.value = Math.max( 0, Math.min(ctx.startX + ev.translationX, layout.width) ); return; } if (modalType === "bottom") { gestureY.value = Math.max( 0, Math.min(ctx.startY + ev.translationY, layout.height) ); return; } }, onEnd: () => { if (visibleHandler) { if ( modalType === "top" && gestureY.value < -Math.min(DISTANCE, layout.height * RATIO) ) { runOnJS(visibleHandler)(false); return; } if ( modalType === "left" && gestureX.value < -Math.min(DISTANCE, layout.width * RATIO) ) { runOnJS(visibleHandler)(false); return; } if ( modalType === "right" && gestureX.value > Math.min(DISTANCE, layout.width * RATIO) ) { runOnJS(visibleHandler)(false); return; } if ( modalType === "bottom" && gestureY.value > Math.min(DISTANCE, layout.height * RATIO) ) { runOnJS(visibleHandler)(false); return; } } gestureX.value = withTiming(0); gestureY.value = withTiming(0); }, }, [layout, modalType] ); }; export const useScroll = ( layout: LayoutType, modalType: ModalType, gestureX: AnimatedValue, gestureY: AnimatedValue, visibleHandler?: (v: boolean) => void ) => { return useAnimatedScrollHandler( (ev) => { if (visibleHandler) { const offset = ev.contentOffset; if (offset.x > 0 || offset.y > 0) return; if (modalType === "top") { return; } if (modalType === "left") { return; } if (modalType === "right") { gestureX.value = Math.max(0, Math.min(-offset.x, layout.width)); return; } if (modalType === "bottom") { gestureY.value = Math.max(0, Math.min(-offset.y, layout.height)); return; } } }, [layout, modalType] ); }; export const useTimeout = ( duration: number ): [(cb: () => void) => void, () => void] => { const timeout = useRef(); const stop = useCallback(() => { timeout.current && clearTimeout(timeout.current); }, []); const start = useCallback((cb: () => void) => { stop(); timeout.current = setTimeout(cb, duration); }, []); useEffect(() => { return () => { stop(); }; }, []); return [start, stop]; }; export const useBack = (cb: () => void) => { const onBack = () => { cb(); return true; }; useEffect(() => { const backHandler = BackHandler.addEventListener( "hardwareBackPress", onBack ); return () => { backHandler.remove(); }; }, []); };