File filter import React, { useRef, useState } from 'react' import { StyleSheet, Animated, Dimensions } from 'react-native' import { Ionicons } from '@expo/vector-icons' export interface HeartAnimationProps { onComplete?: () => void } export interface HeartAnimationRef { trigger: (x: number, y: number) => void } const getSeed = () => Math.floor(Math.random()) export const HeartAnimation = React.forwardRef( ({ onComplete }, ref) => { const [showHeart, setShowHeart] = useState(false) const [heartPosition, setHeartPosition] = useState({ x: 0, y: 0 }) const heartScale = useRef(new Animated.Value(0)).current const heartOpacity = useRef(new Animated.Value(0)).current const heartRotation = useRef(new Animated.Value(getSeed())).current const triggerHeartAnimation = (x: number, y: number) => { const { width, height } = Dimensions.get('window') // Constrain heart position to screen bounds (heart is 80px, so 40px radius) const constrainedX = Math.max(40, Math.min(width - 40, x)) const constrainedY = Math.max(40, Math.min(height - 40, y)) setHeartPosition({ x: constrainedX, y: constrainedY }) setShowHeart(true) // Generate random rotation between -15 and 15 degrees const randomRotation = (Math.random() - 0.5) * 30 heartRotation.setValue(randomRotation) Animated.parallel([ Animated.sequence([ Animated.timing(heartScale, { toValue: 1.2, duration: 150, useNativeDriver: true, }), Animated.timing(heartScale, { toValue: 1, duration: 100, useNativeDriver: true, }), ]), Animated.sequence([ Animated.timing(heartOpacity, { toValue: 1, duration: 150, useNativeDriver: true, }), Animated.delay(500), Animated.timing(heartOpacity, { toValue: 0, duration: 300, useNativeDriver: true, }), ]), ]).start(() => { setShowHeart(false) heartScale.setValue(0) heartOpacity.setValue(0) heartRotation.setValue(0) onComplete?.() }) } React.useImperativeHandle(ref, () => ({ trigger: triggerHeartAnimation, })) if (!showHeart) { return null } return ( ) } ) HeartAnimation.displayName = 'HeartAnimation' const heartAnimation = StyleSheet.create({ style: { position: 'absolute', justifyContent: 'center', alignItems: 'center', zIndex: 1000, }, })