Files

140 lines
4.0 KiB
TypeScript

import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
import { View, Text, StyleSheet, Animated, Platform, Dimensions } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { useAppTheme } from './ThemeContext';
interface ToastContextType {
showToast: (message: string, type?: 'success' | 'error' | 'info') => void;
}
const { width } = Dimensions.get('window');
const LIME = '#C6F135';
const ToastContext = createContext<ToastContextType | undefined>(undefined);
export function ToastProvider({ children }: { children: React.ReactNode }) {
const { colors, isDark } = useAppTheme();
const [toast, setToast] = useState<{ message: string, type: string } | null>(null);
const translateY = useRef(new Animated.Value(-120)).current;
const opacity = useRef(new Animated.Value(0)).current;
const showToast = useCallback((message: string, type: 'success' | 'error' | 'info' = 'info') => {
setToast({ message, type });
translateY.setValue(-120);
opacity.setValue(0);
Animated.sequence([
Animated.parallel([
Animated.spring(translateY, { toValue: Platform.OS === 'ios' ? 60 : 40, useNativeDriver: true, friction: 9, tension: 50 }),
Animated.timing(opacity, { toValue: 1, duration: 400, useNativeDriver: true })
]),
Animated.delay(2800),
Animated.parallel([
Animated.timing(translateY, { toValue: -120, duration: 300, useNativeDriver: true }),
Animated.timing(opacity, { toValue: 0, duration: 300, useNativeDriver: true })
])
]).start(() => setToast(null));
}, []);
// Theme configuration for the toast
const getMeta = () => {
if (!toast) return { icon: 'information', color: LIME };
switch(toast.type) {
case 'success': return { icon: 'check-circle', color: LIME };
case 'error': return { icon: 'alert-circle', color: '#EF4444' };
default: return { icon: 'information', color: '#3B82F6' };
}
};
const meta = getMeta();
const toastBg = isDark ? '#1A1A1A' : '#1A1A1A'; // Solid dark toast for both modes is more premium
const border = isDark ? '#2A2A2A' : '#333';
return (
<ToastContext.Provider value={{ showToast }}>
{children}
{toast && (
<Animated.View
style={[
styles.container,
{
opacity,
transform: [{ translateY }],
zIndex: 10000,
}
]}
pointerEvents="none"
>
<View style={[
styles.toast,
{
backgroundColor: toastBg,
borderColor: border,
}
]}>
<View style={[styles.iconBox, { backgroundColor: `${meta.color}20` }]}>
<MaterialCommunityIcons
name={meta.icon as any}
size={22}
color={meta.color}
/>
</View>
<Text style={styles.text}>{toast.message}</Text>
</View>
</Animated.View>
)}
</ToastContext.Provider>
);
}
export function useToast() {
const context = useContext(ToastContext);
if (context === undefined) {
return {
showToast: (msg: string) => console.log('Toast (Fallback):', msg)
};
}
return context;
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
left: 20,
right: 20,
alignItems: 'center',
},
toast: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 14,
paddingHorizontal: 16,
borderRadius: 20,
borderWidth: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 15 },
shadowOpacity: 0.25,
shadowRadius: 20,
elevation: 12,
width: '100%',
maxWidth: 450,
},
iconBox: {
width: 42,
height: 42,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
text: {
marginLeft: 14,
fontSize: 14,
fontFamily: 'Outfit_700Bold',
color: '#FFFFFF',
flexShrink: 1,
},
});