import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState, } from 'react'; import type { ForwardedRef } from 'react'; import { Polyline as BarePolyline } from '../../bare'; import type { PolylineProps } from '../../bare'; import Reanimated, { interpolateColor, useAnimatedProps, useSharedValue, withTiming, Easing, interpolate, // @ts-ignore } from 'react-native-reanimated'; import { delay, withFastCompare } from '../../utils'; export const Polyline = withFastCompare(BarePolyline); const AnimatedBarePolyline = withFastCompare( Reanimated.createAnimatedComponent(BarePolyline) ); export type AnimatedPolylineRef = { animate: ( point: number, duration?: number, ignoreMiddlePoints?: boolean ) => Promise; reset: () => void; }; export type AnimationPrimitive = { // From 0 to 1 animationValue: number; inputRange: Array; outputRange: Array; }; export type AnimatedPolylineAnimationProp = { color?: AnimationPrimitive; width?: AnimationPrimitive; }; export const AnimatedPolyline = withFastCompare( forwardRef( ( props: PolylineProps & { sectionId: number; animation?: AnimatedPolylineAnimationProp; }, ref: ForwardedRef ) => { const { points: _points, strokeColor: _strokeColor, strokeWidth: _strokeWidth, ...p } = props; const [internalPoints, setInternalPoints] = useState(_points); const pointsRef = useRef(_points); const currentPoint = useRef(0); const lat = useSharedValue(_points[0]?.lat ?? 0); const lon = useSharedValue(_points[0]?.lon ?? 0); const colorAnimation = useSharedValue(0); const widthAnimation = useSharedValue(0); useEffect(() => { if (props.animation?.color) { colorAnimation.value = props.animation.color.animationValue; } else { colorAnimation.value = withTiming(0); } // eslint-disable-next-line }, [props.animation?.color]); useEffect(() => { if (props.animation?.width) { widthAnimation.value = props.animation.width.animationValue; } else { colorAnimation.value = withTiming(0); } }, [props.animation?.width]); const animatedProps = useAnimatedProps(() => { return { points: [ { lat: lat.value, lon: lon.value, }, ...internalPoints, ], strokeColor: interpolateColor( colorAnimation.value, props.animation?.color ? props.animation.color.inputRange : [0, 0], props.animation?.color ? props.animation.color.outputRange : [_strokeColor, _strokeColor] ), strokeWidth: interpolate( widthAnimation.value, props.animation?.width ? props.animation.width.inputRange : [0, 0], props.animation?.width ? props.animation.width.outputRange : [_strokeWidth, _strokeWidth] ), }; }, [internalPoints, colorAnimation, props, _strokeColor, _strokeWidth]); const reset = useCallback(() => { setInternalPoints(_points); lat.value = _points[0]?.lat ?? 0; lon.value = _points[0]?.lon ?? 0; }, [_points, lat, lon]); const animate = useCallback( async ( point: number, duration?: number, ignoreMiddlePoints?: boolean ) => { if (point === currentPoint.current) { return; } setInternalPoints( pointsRef.current.slice( ignoreMiddlePoints ? pointsRef.current.length - 1 : point, pointsRef.current.length ) ); await delay(16); // FRAME_TIME lat.value = withTiming(pointsRef.current[point]!.lat, { duration: duration ?? 0, easing: Easing.linear, }); lon.value = withTiming(pointsRef.current[point]!.lon, { duration: duration ?? 0, easing: Easing.linear, }); if (duration) { await delay(duration); } // eslint-disable-next-line }, []); useImperativeHandle(ref, () => ({ reset, animate, })); // @ts-ignore return ; } ) );