import * as React from "react"; import { Animated, EasingFunction, StyleProp, ViewStyle } from "react-native"; import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils"; import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils"; import { noop } from "@applicaster/zapp-react-native-utils/functionUtils"; type AnimatedInterpolatedStyle = any; type AnimationConfig = { duration: number; easing: EasingFunction; styles: (animatedValue: Animated.Value) => { [Key: string]: AnimatedInterpolatedStyle | StyleProp; }; delay?: number; onAnimationEnd?: () => void; }; export type AnimatedInConfig = | AnimationConfig | { in: AnimationConfig; out: AnimationConfig; }; type Props = { visible: boolean; animationConfig: AnimatedInConfig; children: React.ReactElement; staticStyles?: ViewStyle; }; function getAnimation(config: AnimatedInConfig, phase: "in" | "out") { return config[phase] || config; } export function AnimatedInOut({ visible, animationConfig, staticStyles = {}, children, }: Props) { const [animatedValue] = React.useState(new Animated.Value(visible ? 1 : 0)); const animationRef = React.useRef(null); const delayTimerRef = React.useRef(null); const previousVisible = usePrevious(toBooleanWithDefaultFalse(visible)); const startAnimation = React.useCallback( (toValue: number, config: AnimationConfig) => { if (delayTimerRef.current) { clearTimeout(delayTimerRef.current); delayTimerRef.current = null; } if (animationRef.current) { animationRef.current.stop(); animationRef.current = null; } const { duration, easing, delay = 0, onAnimationEnd = noop } = config; const runAnimation = () => { animationRef.current = Animated.timing(animatedValue, { duration, toValue, easing, useNativeDriver: true, }); animationRef.current.start(({ finished }) => { if (finished) { animationRef.current = null; onAnimationEnd(); } }); }; if (delay > 0) { delayTimerRef.current = setTimeout(runAnimation, delay); } else { runAnimation(); } }, [animatedValue] ); React.useEffect(() => { if (previousVisible === undefined) { animatedValue.setValue(visible ? 1 : 0); return; } if (!previousVisible && visible) { startAnimation(1.0, getAnimation(animationConfig, "in")); } if (previousVisible && !visible) { startAnimation(0.0, getAnimation(animationConfig, "out")); } }, [ visible, previousVisible, animatedValue, startAnimation, animationConfig, ]); React.useEffect(() => { return () => { if (delayTimerRef.current) { clearTimeout(delayTimerRef.current); delayTimerRef.current = null; } if (animationRef.current) { animationRef.current.stop(); animationRef.current = null; } animatedValue.stopAnimation(); }; }, [animatedValue]); const styles = visible ? getAnimation(animationConfig, "in").styles : getAnimation(animationConfig, "out").styles; return ( {children} ); }