xref: /MusicFree/src/components/base/toast.tsx (revision dcb7566c5aedded569cd0636af0ae8b7d6906559)
1b85f12f7S猫头猫import {timingConfig} from '@/constants/commonConst';
22a3194f5S猫头猫import {fontSizeConst} from '@/constants/uiConst';
3277c5280S猫头猫import useColors from '@/hooks/useColors';
4b85f12f7S猫头猫import rpx from '@/utils/rpx';
5b85f12f7S猫头猫import {GlobalState} from '@/utils/stateMapper';
6b85f12f7S猫头猫import {nanoid} from 'nanoid';
7b85f12f7S猫头猫import React, {useCallback, useEffect} from 'react';
8b85f12f7S猫头猫import {Pressable, StyleSheet, Text, View} from 'react-native';
9b85f12f7S猫头猫import {
10b85f12f7S猫头猫    Directions,
11b85f12f7S猫头猫    Gesture,
12b85f12f7S猫头猫    GestureDetector,
13b85f12f7S猫头猫} from 'react-native-gesture-handler';
14b85f12f7S猫头猫import Animated, {
15b85f12f7S猫头猫    cancelAnimation,
16b85f12f7S猫头猫    runOnJS,
17b85f12f7S猫头猫    useAnimatedStyle,
18b85f12f7S猫头猫    useSharedValue,
19b85f12f7S猫头猫    withDelay,
20b85f12f7S猫头猫    withTiming,
21b85f12f7S猫头猫} from 'react-native-reanimated';
225589cdf3S猫头猫import Icon from '@/components/base/icon.tsx';
232a3194f5S猫头猫
24b85f12f7S猫头猫export interface IToastConfig {
25b85f12f7S猫头猫    /** 类型 */
26b85f12f7S猫头猫    type: 'success' | 'warn';
27b85f12f7S猫头猫    /** 消息内容 */
28b85f12f7S猫头猫    message?: string;
29b85f12f7S猫头猫    /** 行动点 */
30b85f12f7S猫头猫    actionText?: string;
31b85f12f7S猫头猫    /** 行动点按钮行为 */
32b85f12f7S猫头猫    onActionClick?: () => void;
33b85f12f7S猫头猫    /** 展示时间 */
34b85f12f7S猫头猫    duration?: number;
352a3194f5S猫头猫}
362a3194f5S猫头猫
37b85f12f7S猫头猫type IToastConfigInner = IToastConfig & {
38b85f12f7S猫头猫    id: string;
392a3194f5S猫头猫};
402a3194f5S猫头猫
41b85f12f7S猫头猫const toastQueue: IToastConfigInner[] = [];
42b85f12f7S猫头猫
43b85f12f7S猫头猫const fixedTop = rpx(250);
44b85f12f7S猫头猫
45b85f12f7S猫头猫const activeToastStore = new GlobalState<IToastConfigInner | null>(null);
46b85f12f7S猫头猫
47b85f12f7S猫头猫const typeConfig = {
48b85f12f7S猫头猫    success: {
49b85f12f7S猫头猫        name: 'check-circle',
50b85f12f7S猫头猫        color: '#457236',
51b85f12f7S猫头猫    },
52b85f12f7S猫头猫    warn: {
535589cdf3S猫头猫        name: 'exclamation-circle',
54b85f12f7S猫头猫        color: '#de7622',
55b85f12f7S猫头猫    },
565589cdf3S猫头猫} as const;
57b85f12f7S猫头猫
58b85f12f7S猫头猫export function ToastBaseComponent() {
59b85f12f7S猫头猫    const activeToast = activeToastStore.useValue();
60b85f12f7S猫头猫    const colors = useColors();
61b85f12f7S猫头猫
62b85f12f7S猫头猫    const toastAnim = useSharedValue(0);
63b85f12f7S猫头猫
64b85f12f7S猫头猫    const setNextToast = useCallback(() => {
65b85f12f7S猫头猫        activeToastStore.setValue(toastQueue.shift() || null);
66b85f12f7S猫头猫    }, []);
67b85f12f7S猫头猫
68b85f12f7S猫头猫    useEffect(() => {
69b85f12f7S猫头猫        if (activeToast) {
70b85f12f7S猫头猫            toastAnim.value = withTiming(1, timingConfig.animationSlow, () => {
71b85f12f7S猫头猫                toastAnim.value = withDelay(
72b85f12f7S猫头猫                    activeToast.duration || 1200,
73b85f12f7S猫头猫                    withTiming(0, timingConfig.animationSlow, finished => {
74b85f12f7S猫头猫                        if (finished) {
75b85f12f7S猫头猫                            runOnJS(setNextToast)();
76b85f12f7S猫头猫                        }
77b85f12f7S猫头猫                    }),
78b85f12f7S猫头猫                );
79b85f12f7S猫头猫            });
80b85f12f7S猫头猫        }
81b85f12f7S猫头猫    }, [activeToast]);
82b85f12f7S猫头猫
83b85f12f7S猫头猫    function removeCurrentToast() {
84b85f12f7S猫头猫        if (toastAnim.value === 1) {
85b85f12f7S猫头猫            cancelAnimation(toastAnim);
86b85f12f7S猫头猫            toastAnim.value = withTiming(
87b85f12f7S猫头猫                0,
88b85f12f7S猫头猫                timingConfig.animationSlow,
89b85f12f7S猫头猫                finished => {
90b85f12f7S猫头猫                    if (finished) {
91b85f12f7S猫头猫                        runOnJS(setNextToast)();
92b85f12f7S猫头猫                    }
93b85f12f7S猫头猫                },
94b85f12f7S猫头猫            );
95b85f12f7S猫头猫        }
96b85f12f7S猫头猫    }
97b85f12f7S猫头猫
98b85f12f7S猫头猫    const flingGesture = Gesture.Fling()
99b85f12f7S猫头猫        .direction(Directions.UP)
100b85f12f7S猫头猫        .onEnd(() => {
101b85f12f7S猫头猫            removeCurrentToast();
102b85f12f7S猫头猫        })
103b85f12f7S猫头猫        .runOnJS(true);
104b85f12f7S猫头猫
105b85f12f7S猫头猫    const toastAnimStyle = useAnimatedStyle(() => {
106b85f12f7S猫头猫        return {
107b85f12f7S猫头猫            transform: [
108b85f12f7S猫头猫                {
109b85f12f7S猫头猫                    translateY: (toastAnim.value - 1) * fixedTop,
110b85f12f7S猫头猫                },
111b85f12f7S猫头猫            ],
112b85f12f7S猫头猫            opacity: toastAnim.value,
113b85f12f7S猫头猫        };
114b85f12f7S猫头猫    });
115b85f12f7S猫头猫
116b85f12f7S猫头猫    return activeToast ? (
117b85f12f7S猫头猫        <GestureDetector gesture={flingGesture}>
118b85f12f7S猫头猫            <View style={styles.container}>
119b85f12f7S猫头猫                <Animated.View
120b85f12f7S猫头猫                    style={[
121b85f12f7S猫头猫                        styles.contentContainer,
122b85f12f7S猫头猫                        {
123b85f12f7S猫头猫                            backgroundColor: colors.backdrop,
124b85f12f7S猫头猫                            shadowColor: colors.shadow,
125b85f12f7S猫头猫                        },
126b85f12f7S猫头猫                        toastAnimStyle,
127b85f12f7S猫头猫                    ]}>
128b85f12f7S猫头猫                    <Icon
1295589cdf3S猫头猫                        size={fontSizeConst.appbar}
130b85f12f7S猫头猫                        name={typeConfig[activeToast.type].name}
131b85f12f7S猫头猫                        color={typeConfig[activeToast.type].color}
132b85f12f7S猫头猫                    />
133b85f12f7S猫头猫                    <Text
134*dcb7566cS猫头猫                        numberOfLines={2}
135b85f12f7S猫头猫                        style={[styles.text, {color: colors.text}]}>
136b85f12f7S猫头猫                        {activeToast.message}
137b85f12f7S猫头猫                    </Text>
138b85f12f7S猫头猫                    {activeToast.actionText && activeToast.onActionClick ? (
139b85f12f7S猫头猫                        <Pressable
140b85f12f7S猫头猫                            style={[
141b85f12f7S猫头猫                                styles.actionTextContainer,
142b85f12f7S猫头猫                                {backgroundColor: colors.primary},
143b85f12f7S猫头猫                            ]}
144b85f12f7S猫头猫                            onPress={activeToast.onActionClick}>
145b85f12f7S猫头猫                            <Text style={styles.actionText} numberOfLines={1}>
146b85f12f7S猫头猫                                {activeToast.actionText}
147b85f12f7S猫头猫                            </Text>
148b85f12f7S猫头猫                        </Pressable>
149b85f12f7S猫头猫                    ) : null}
150b85f12f7S猫头猫                </Animated.View>
151b85f12f7S猫头猫            </View>
152b85f12f7S猫头猫        </GestureDetector>
153b85f12f7S猫头猫    ) : null;
154b85f12f7S猫头猫}
1552a3194f5S猫头猫
1562a3194f5S猫头猫const styles = StyleSheet.create({
157b85f12f7S猫头猫    container: {
158b85f12f7S猫头猫        position: 'absolute',
159b85f12f7S猫头猫        top: rpx(128),
160b85f12f7S猫头猫        width: '100%',
1612a3194f5S猫头猫        alignItems: 'center',
162b85f12f7S猫头猫        height: rpx(100),
163d1fd80edS猫头猫        zIndex: 20000,
1642a3194f5S猫头猫    },
165b85f12f7S猫头猫    contentContainer: {
166b85f12f7S猫头猫        width: rpx(688),
167b85f12f7S猫头猫        height: '100%',
168b85f12f7S猫头猫        borderRadius: rpx(12),
169b85f12f7S猫头猫        backgroundColor: 'blue',
170b85f12f7S猫头猫        flexDirection: 'row',
171b85f12f7S猫头猫        alignItems: 'center',
172b85f12f7S猫头猫        paddingHorizontal: rpx(24),
173b85f12f7S猫头猫        shadowOffset: {
174b85f12f7S猫头猫            width: 0,
175b85f12f7S猫头猫            height: 2,
176b85f12f7S猫头猫        },
177b85f12f7S猫头猫        shadowOpacity: 0.2,
178b85f12f7S猫头猫        shadowRadius: 1.41,
179b85f12f7S猫头猫
180b85f12f7S猫头猫        elevation: 2,
1812a3194f5S猫头猫    },
182b85f12f7S猫头猫    text: {
183b85f12f7S猫头猫        fontSize: fontSizeConst.content,
184b85f12f7S猫头猫        includeFontPadding: false,
185b85f12f7S猫头猫        flex: 1,
186b85f12f7S猫头猫        marginLeft: rpx(24),
187b85f12f7S猫头猫    },
188b85f12f7S猫头猫    actionText: {
189b85f12f7S猫头猫        fontSize: fontSizeConst.content,
190b85f12f7S猫头猫        includeFontPadding: false,
191b85f12f7S猫头猫        color: 'white',
192b85f12f7S猫头猫    },
193b85f12f7S猫头猫    actionTextContainer: {
194b85f12f7S猫头猫        marginLeft: rpx(24),
195b85f12f7S猫头猫        width: rpx(120),
1965589cdf3S猫头猫        paddingHorizontal: rpx(12),
197b85f12f7S猫头猫        flexDirection: 'row',
198b85f12f7S猫头猫        alignItems: 'center',
199b85f12f7S猫头猫        justifyContent: 'center',
200b85f12f7S猫头猫        borderRadius: rpx(30),
201b85f12f7S猫头猫        height: rpx(58),
2022a3194f5S猫头猫    },
2032a3194f5S猫头猫});
204b85f12f7S猫头猫
205b85f12f7S猫头猫export function showToast(config: IToastConfig) {
206b85f12f7S猫头猫    const id = nanoid();
207b85f12f7S猫头猫    const _config = {
208b85f12f7S猫头猫        ...config,
209b85f12f7S猫头猫        id,
210b85f12f7S猫头猫    };
211b85f12f7S猫头猫    const activeToast = activeToastStore.getValue();
212b85f12f7S猫头猫    if (!activeToast) {
213b85f12f7S猫头猫        activeToastStore.setValue(_config);
214b85f12f7S猫头猫    } else {
215b85f12f7S猫头猫        toastQueue.push(_config);
216b85f12f7S猫头猫    }
217b85f12f7S猫头猫
218b85f12f7S猫头猫    return id;
219b85f12f7S猫头猫}
220