import { useState, useEffect, useRef, useCallback } from "react"; import { Animated, Text, View, type ViewStyle } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { useTheme } from "../theme"; type ToastType = "success" | "error" | "info"; interface ToastData { message: string; type: ToastType; id: number; } let toastId = 0; let showToastFn: ((message: string, type?: ToastType) => void) | null = null; export function showToast(message: string, type: ToastType = "info") { showToastFn?.(message, type); } export function ToastProvider({ children }: { children: React.ReactNode }) { const theme = useTheme(); const [toast, setToast] = useState(null); const opacity = useRef(new Animated.Value(0)).current; const translateY = useRef(new Animated.Value(-24)).current; const timerRef = useRef | null>(null); const show = useCallback((message: string, type: ToastType = "info") => { if (timerRef.current) clearTimeout(timerRef.current); setToast({ message, type, id: ++toastId }); Animated.parallel([ Animated.timing(opacity, { toValue: 1, duration: 220, useNativeDriver: true }), Animated.timing(translateY, { toValue: 0, duration: 220, useNativeDriver: true }), ]).start(); timerRef.current = setTimeout(() => { Animated.parallel([ Animated.timing(opacity, { toValue: 0, duration: 220, useNativeDriver: true }), Animated.timing(translateY, { toValue: -24, duration: 220, useNativeDriver: true }), ]).start(() => setToast(null)); }, 3200); }, [opacity, translateY]); useEffect(() => { showToastFn = show; return () => { showToastFn = null; }; }, [show]); const styles: Record = { success: { bg: theme.colors.success, icon: "checkmark-circle", text: "#FFFFFF" }, error: { bg: theme.colors.error, icon: "alert-circle", text: "#FFFFFF" }, info: { bg: theme.colors.secondary, icon: "information-circle", text: "#FFFFFF" }, }; return ( {children} {toast && ( {toast.message} )} ); }