import MaskedView from '@react-native-masked-view/masked-view'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import type { LayoutChangeEvent } from 'react-native'; import { Animated, Easing, Platform, View } from 'react-native'; import { useTheme } from '../../theme'; import Typography from '../Typography'; interface AnimatedGradientTextProps { children: string; fontSize: number; lineHeight: number; } const ANIMATION_DURATION_MS = 2000; const AnimatedGradientText = ({ children, fontSize, lineHeight, }: AnimatedGradientTextProps) => { const theme = useTheme(); const gradient = theme.colors.gradients.aiHorizontal; const [size, setSize] = useState<{ width: number; height: number } | null>( null ); const animatedValue = useRef(new Animated.Value(0)); const onLayout = useCallback((event: LayoutChangeEvent) => { const { width, height } = event.nativeEvent.layout; setSize((prev) => { if (prev?.width === width && prev?.height === height) return prev; return { width, height }; }); }, []); useEffect(() => { if (!size) return; animatedValue.current.setValue(0); const animation = Animated.loop( Animated.timing(animatedValue.current, { toValue: 1, duration: ANIMATION_DURATION_MS, easing: Easing.linear, useNativeDriver: Platform.OS !== 'web', }) ); animation.start(); return () => animation.stop(); }, [size]); // Slide left by one full text-width per loop cycle. // Starting at 0 keeps the gradient visible from the first frame. const translateX = size ? animatedValue.current.interpolate({ inputRange: [0, 1], outputRange: [0, -size.width], }) : animatedValue.current; return ( {children} } > {size ? ( {/* * Double the colour pattern so the gradient tiles seamlessly: * [A, B, A, B, A] at equal spacing means the slice at * translateX=0 looks identical to the slice at translateX=-width, * eliminating the jump when the loop restarts. */} ) : ( {children} )} {/* Visually hidden but accessible text for screen readers */} {children} ); }; export default AnimatedGradientText;