import React, { useCallback, useEffect, useRef, useState } from 'react' import { Animated, Image, LayoutAnimation, StyleSheet, Text, TouchableOpacity, View } from 'react-native' import Emitter from '../utilities/Emitter' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { colors, fontSizes, hitSlop, Images, metrics } from '@/themes' export const TOAST_TYPE = { SUCCESS: 'SUCCESS', ERROR: 'ERROR', INFO: 'INFO', WARNING: 'WARNING', } as const const TOAST_EVENTS = { showToastMessage: 'SHOW_TOAST_MESSAGE', } const DEFAULT_DELAY = 5000 const DEFAULT_DURATION = 300 interface IToastState { message: string type: keyof typeof TOAST_TYPE subMessage?: string option?: { delay?: number duration?: number } } const messageContent = { [TOAST_TYPE.SUCCESS]: { color: colors.success, }, [TOAST_TYPE.ERROR]: { color: colors.error, }, [TOAST_TYPE.INFO]: { color: colors.info, }, [TOAST_TYPE.WARNING]: { color: colors.warning, }, } as const const initState = { message: '', type: TOAST_TYPE.INFO, subMessage: '', } export const Toast: React.FC = () => { const insets = useSafeAreaInsets() const [state, setState] = useState(initState) const HEIGHT = insets.bottom + metrics.toast const animationRef = useRef(null) const animation = useRef(new Animated.Value(0)).current const offset = animation.interpolate({ inputRange: [0, 1], outputRange: [HEIGHT, 0 - metrics.xxs], // padding bottom metrics.xxs }) const opacity = animation.interpolate({ inputRange: [0, 1], outputRange: [0, 1], }) const displayMessage = useCallback( (param?: unknown): void => { const args = param as IToastState animation.setValue(0) setState(args) animationRef.current?.stop() setTimeout(() => { animationRef.current = Animated.sequence([ // Fade In Animated.timing(animation, { toValue: 1, duration: args.option?.duration || DEFAULT_DURATION, useNativeDriver: true, }), Animated.delay(args.option?.delay || DEFAULT_DELAY), // Fade Out Animated.timing(animation, { toValue: 0, duration: args.option?.duration || DEFAULT_DURATION, useNativeDriver: true, }), ]) animationRef.current.start(() => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) }) }, 100) }, [animation], ) const dismiss = () => { animationRef.current?.stop() setState(initState) Animated.timing(animation, { toValue: 0, duration: DEFAULT_DURATION, useNativeDriver: true, }).start() } useEffect(() => { Emitter.on(TOAST_EVENTS.showToastMessage, displayMessage) return () => { Emitter.rm(TOAST_EVENTS.showToastMessage) } }, [displayMessage]) return ( {state.message} {!!state.subMessage && {state.subMessage}} ) } export const showToast = (args: IToastState) => { Emitter.emit(TOAST_EVENTS.showToastMessage, args) } const styles = StyleSheet.create({ container: { zIndex: 9999, position: 'absolute', bottom: 0, left: 0, right: 0, paddingHorizontal: metrics.paddingHorizontal, }, messageContainer: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-start', borderRadius: metrics.borderRadius, paddingHorizontal: metrics.xs, paddingVertical: metrics.xxs, }, textContent: { flex: 1, paddingHorizontal: metrics.xxs, flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'center', }, titleStyle: { fontSize: fontSizes.title, color: colors.white, }, textStyle: { fontSize: fontSizes.body, color: colors.white, }, icon: { width: metrics.icon, height: metrics.icon, tintColor: colors.white, }, })