import { useCallback, useMemo, useRef, useEffect } from 'react'; import { Animated, Easing } from 'react-native'; import type { VideoAnimations } from '../types'; export const useJSAnimations = ( controlAnimationTiming: number = 300, // shorter = snappier ): VideoAnimations => { // Use TranslateY instead of Margins to allow NativeDriver support const bottomControlTranslateY = useRef(new Animated.Value(0)).current; const controlsOpacity = useRef(new Animated.Value(1)).current; const topControlTranslateY = useRef(new Animated.Value(0)).current; // Store running animations to stop them on new calls const runningAnimations = useRef(null); const stopCurrentAnimations = useCallback(() => { if (runningAnimations.current) { runningAnimations.current.stop(); runningAnimations.current = null; } }, []); // Clean up on unmount useEffect(() => { return () => { stopCurrentAnimations(); }; }, [stopCurrentAnimations]); const hideControlAnimation = useCallback(() => { stopCurrentAnimations(); const anim = Animated.parallel([ Animated.timing(controlsOpacity, { toValue: 0, duration: controlAnimationTiming, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), Animated.timing(topControlTranslateY, { toValue: -100, duration: controlAnimationTiming, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), Animated.timing(bottomControlTranslateY, { toValue: 100, duration: controlAnimationTiming, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), ]); runningAnimations.current = anim; anim.start(() => { runningAnimations.current = null; }); }, [ controlAnimationTiming, controlsOpacity, topControlTranslateY, bottomControlTranslateY, stopCurrentAnimations, ]); const showControlAnimation = useCallback(() => { stopCurrentAnimations(); const anim = Animated.parallel([ Animated.timing(controlsOpacity, { toValue: 1, duration: controlAnimationTiming, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), Animated.timing(topControlTranslateY, { toValue: 0, duration: controlAnimationTiming, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), Animated.timing(bottomControlTranslateY, { toValue: 0, duration: controlAnimationTiming, easing: Easing.inOut(Easing.ease), useNativeDriver: true, }), ]); runningAnimations.current = anim; anim.start(() => { runningAnimations.current = null; }); }, [ controlAnimationTiming, controlsOpacity, topControlTranslateY, bottomControlTranslateY, stopCurrentAnimations, ]); // Memoize the animations object to prevent re‑renders of child components const animations = useMemo( () => ({ bottomControl: { transform: [{ translateY: bottomControlTranslateY }], }, topControl: { transform: [{ translateY: topControlTranslateY }], }, controlsOpacity: { opacity: controlsOpacity, }, showControlAnimation, hideControlAnimation, AnimatedView: Animated.View, } as unknown as VideoAnimations), [ bottomControlTranslateY, topControlTranslateY, controlsOpacity, showControlAnimation, hideControlAnimation, ], ); return animations; };