import { useEffect, useRef } from 'react'; import { Modal, View, Text, Pressable, Animated, Easing } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; type Props = { visible: boolean; title: string; message?: string; onClose: () => void; }; /** * iOS-style success alert mit animiertem Check-Icon. * - Card scaled mit Spring-overshoot rein * - Check-Icon sequenced danach mit eigenem Spring + rotation-pop * - Tap auf Backdrop schließt * - OK-Button schließt */ export function SuccessAlert({ visible, title, message, onClose }: Props) { const { t } = useTranslation(); const cardScale = useRef(new Animated.Value(0.8)).current; const cardOpacity = useRef(new Animated.Value(0)).current; const checkScale = useRef(new Animated.Value(0)).current; const checkRotate = useRef(new Animated.Value(0)).current; useEffect(() => { if (visible) { cardScale.setValue(0.8); cardOpacity.setValue(0); checkScale.setValue(0); checkRotate.setValue(0); Animated.parallel([ Animated.spring(cardScale, { toValue: 1, useNativeDriver: true, friction: 7, tension: 80, }), Animated.timing(cardOpacity, { toValue: 1, duration: 220, useNativeDriver: true, easing: Easing.out(Easing.cubic), }), Animated.sequence([ Animated.delay(140), Animated.parallel([ Animated.spring(checkScale, { toValue: 1, useNativeDriver: true, friction: 5, tension: 180, }), Animated.timing(checkRotate, { toValue: 1, duration: 380, useNativeDriver: true, easing: Easing.out(Easing.back(1.7)), }), ]), ]), ]).start(); } }, [visible, cardScale, cardOpacity, checkScale, checkRotate]); const rotateInterpolate = checkRotate.interpolate({ inputRange: [0, 1], outputRange: ['-30deg', '0deg'], }); return ( {/* Backdrop — Pressable damit Tap-outside schließt */} {/* Card — Pressable mit onPress={()=>{}} damit Tap auf Card NICHT bubbelt * zum Backdrop und das Modal schließt. */} {}} style={{ width: '85%', maxWidth: 320 }}> {/* Animated Check-Circle */} {title} {message && ( {message} )} {t('common.ok')} ); }