xref: /MusicFree/src/components/panels/base/panelBase.tsx (revision ec4205c41122a549937356610b34e18616168da1)
1*ec4205c4S猫头猫import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2*ec4205c4S猫头猫import {
3*ec4205c4S猫头猫    BackHandler,
4*ec4205c4S猫头猫    DeviceEventEmitter,
5*ec4205c4S猫头猫    NativeEventSubscription,
6*ec4205c4S猫头猫    Pressable,
7*ec4205c4S猫头猫    StyleSheet,
8*ec4205c4S猫头猫} from 'react-native';
9*ec4205c4S猫头猫import rpx, {vh} from '@/utils/rpx';
10*ec4205c4S猫头猫
11*ec4205c4S猫头猫import Animated, {
12*ec4205c4S猫头猫    Easing,
13*ec4205c4S猫头猫    runOnJS,
14*ec4205c4S猫头猫    useAnimatedReaction,
15*ec4205c4S猫头猫    useAnimatedStyle,
16*ec4205c4S猫头猫    useSharedValue,
17*ec4205c4S猫头猫    withTiming,
18*ec4205c4S猫头猫} from 'react-native-reanimated';
19*ec4205c4S猫头猫import useColors from '@/hooks/useColors';
20*ec4205c4S猫头猫import {useSafeAreaInsets} from 'react-native-safe-area-context';
21*ec4205c4S猫头猫import useOrientation from '@/hooks/useOrientation';
22*ec4205c4S猫头猫import {panelInfoStore} from '../usePanel';
23*ec4205c4S猫头猫
24*ec4205c4S猫头猫const ANIMATION_EASING: Animated.EasingFunction = Easing.out(Easing.exp);
25*ec4205c4S猫头猫const ANIMATION_DURATION = 250;
26*ec4205c4S猫头猫
27*ec4205c4S猫头猫const timingConfig = {
28*ec4205c4S猫头猫    duration: ANIMATION_DURATION,
29*ec4205c4S猫头猫    easing: ANIMATION_EASING,
30*ec4205c4S猫头猫};
31*ec4205c4S猫头猫
32*ec4205c4S猫头猫interface IPanelBaseProps {
33*ec4205c4S猫头猫    height?: number;
34*ec4205c4S猫头猫    renderBody: (loading: boolean) => JSX.Element;
35*ec4205c4S猫头猫}
36*ec4205c4S猫头猫
37*ec4205c4S猫头猫export default function (props: IPanelBaseProps) {
38*ec4205c4S猫头猫    const {height = vh(60), renderBody} = props;
39*ec4205c4S猫头猫    const snapPoint = useSharedValue(0);
40*ec4205c4S猫头猫
41*ec4205c4S猫头猫    const colors = useColors();
42*ec4205c4S猫头猫    const [loading, setLoading] = useState(true); // 是否处于弹出状态
43*ec4205c4S猫头猫    const timerRef = useRef<any>();
44*ec4205c4S猫头猫    const safeAreaInsets = useSafeAreaInsets();
45*ec4205c4S猫头猫    const orientation = useOrientation();
46*ec4205c4S猫头猫    const useAnimatedBase = useMemo(
47*ec4205c4S猫头猫        () => (orientation === 'horizonal' ? rpx(750) : height),
48*ec4205c4S猫头猫        [orientation],
49*ec4205c4S猫头猫    );
50*ec4205c4S猫头猫    const backHandlerRef = useRef<NativeEventSubscription>();
51*ec4205c4S猫头猫
52*ec4205c4S猫头猫    const hideCallbackRef = useRef<Function[]>([]);
53*ec4205c4S猫头猫
54*ec4205c4S猫头猫    useEffect(() => {
55*ec4205c4S猫头猫        snapPoint.value = withTiming(1, timingConfig);
56*ec4205c4S猫头猫        timerRef.current = setTimeout(() => {
57*ec4205c4S猫头猫            if (loading) {
58*ec4205c4S猫头猫                // 兜底
59*ec4205c4S猫头猫                setLoading(false);
60*ec4205c4S猫头猫            }
61*ec4205c4S猫头猫        }, 400);
62*ec4205c4S猫头猫        if (backHandlerRef.current) {
63*ec4205c4S猫头猫            backHandlerRef.current?.remove();
64*ec4205c4S猫头猫            backHandlerRef.current = undefined;
65*ec4205c4S猫头猫        }
66*ec4205c4S猫头猫        backHandlerRef.current = BackHandler.addEventListener(
67*ec4205c4S猫头猫            'hardwareBackPress',
68*ec4205c4S猫头猫            () => {
69*ec4205c4S猫头猫                snapPoint.value = withTiming(0, timingConfig);
70*ec4205c4S猫头猫                return true;
71*ec4205c4S猫头猫            },
72*ec4205c4S猫头猫        );
73*ec4205c4S猫头猫
74*ec4205c4S猫头猫        const listenerSubscription = DeviceEventEmitter.addListener(
75*ec4205c4S猫头猫            'hidePanel',
76*ec4205c4S猫头猫            (callback?: () => void) => {
77*ec4205c4S猫头猫                if (callback) {
78*ec4205c4S猫头猫                    hideCallbackRef.current.push(callback);
79*ec4205c4S猫头猫                }
80*ec4205c4S猫头猫                snapPoint.value = withTiming(0, timingConfig);
81*ec4205c4S猫头猫            },
82*ec4205c4S猫头猫        );
83*ec4205c4S猫头猫
84*ec4205c4S猫头猫        return () => {
85*ec4205c4S猫头猫            if (timerRef.current) {
86*ec4205c4S猫头猫                clearTimeout(timerRef.current);
87*ec4205c4S猫头猫                timerRef.current = null;
88*ec4205c4S猫头猫            }
89*ec4205c4S猫头猫            if (backHandlerRef.current) {
90*ec4205c4S猫头猫                backHandlerRef.current?.remove();
91*ec4205c4S猫头猫                backHandlerRef.current = undefined;
92*ec4205c4S猫头猫            }
93*ec4205c4S猫头猫            listenerSubscription.remove();
94*ec4205c4S猫头猫        };
95*ec4205c4S猫头猫    }, []);
96*ec4205c4S猫头猫
97*ec4205c4S猫头猫    const maskAnimated = useAnimatedStyle(() => {
98*ec4205c4S猫头猫        return {
99*ec4205c4S猫头猫            opacity: withTiming(snapPoint.value * 0.5, timingConfig),
100*ec4205c4S猫头猫        };
101*ec4205c4S猫头猫    });
102*ec4205c4S猫头猫
103*ec4205c4S猫头猫    const panelAnimated = useAnimatedStyle(() => {
104*ec4205c4S猫头猫        return {
105*ec4205c4S猫头猫            transform: [
106*ec4205c4S猫头猫                orientation === 'vertical'
107*ec4205c4S猫头猫                    ? {
108*ec4205c4S猫头猫                          translateY: withTiming(
109*ec4205c4S猫头猫                              (1 - snapPoint.value) * useAnimatedBase,
110*ec4205c4S猫头猫                              timingConfig,
111*ec4205c4S猫头猫                          ),
112*ec4205c4S猫头猫                      }
113*ec4205c4S猫头猫                    : {
114*ec4205c4S猫头猫                          translateX: withTiming(
115*ec4205c4S猫头猫                              (1 - snapPoint.value) * useAnimatedBase,
116*ec4205c4S猫头猫                              timingConfig,
117*ec4205c4S猫头猫                          ),
118*ec4205c4S猫头猫                      },
119*ec4205c4S猫头猫            ],
120*ec4205c4S猫头猫        };
121*ec4205c4S猫头猫    }, [orientation]);
122*ec4205c4S猫头猫
123*ec4205c4S猫头猫    const mountPanel = useCallback(() => {
124*ec4205c4S猫头猫        setLoading(false);
125*ec4205c4S猫头猫    }, []);
126*ec4205c4S猫头猫
127*ec4205c4S猫头猫    const unmountPanel = useCallback(() => {
128*ec4205c4S猫头猫        panelInfoStore.setValue({
129*ec4205c4S猫头猫            name: null,
130*ec4205c4S猫头猫            payload: null,
131*ec4205c4S猫头猫        });
132*ec4205c4S猫头猫        hideCallbackRef.current.forEach(cb => cb?.());
133*ec4205c4S猫头猫    }, []);
134*ec4205c4S猫头猫
135*ec4205c4S猫头猫    useAnimatedReaction(
136*ec4205c4S猫头猫        () => snapPoint.value,
137*ec4205c4S猫头猫        (result, prevResult) => {
138*ec4205c4S猫头猫            if (prevResult && result > prevResult && result > 0.8) {
139*ec4205c4S猫头猫                runOnJS(mountPanel)();
140*ec4205c4S猫头猫            }
141*ec4205c4S猫头猫            if (prevResult && result < prevResult && result === 0) {
142*ec4205c4S猫头猫                runOnJS(unmountPanel)();
143*ec4205c4S猫头猫            }
144*ec4205c4S猫头猫        },
145*ec4205c4S猫头猫        [],
146*ec4205c4S猫头猫    );
147*ec4205c4S猫头猫
148*ec4205c4S猫头猫    return (
149*ec4205c4S猫头猫        <>
150*ec4205c4S猫头猫            <Pressable
151*ec4205c4S猫头猫                style={style.maskWrapper}
152*ec4205c4S猫头猫                onPress={() => {
153*ec4205c4S猫头猫                    snapPoint.value = withTiming(0, timingConfig);
154*ec4205c4S猫头猫                }}>
155*ec4205c4S猫头猫                <Animated.View
156*ec4205c4S猫头猫                    style={[style.maskWrapper, style.mask, maskAnimated]}
157*ec4205c4S猫头猫                />
158*ec4205c4S猫头猫            </Pressable>
159*ec4205c4S猫头猫            <Animated.View
160*ec4205c4S猫头猫                style={[
161*ec4205c4S猫头猫                    style.wrapper,
162*ec4205c4S猫头猫                    {
163*ec4205c4S猫头猫                        backgroundColor: colors.primary,
164*ec4205c4S猫头猫                        height:
165*ec4205c4S猫头猫                            orientation === 'horizonal'
166*ec4205c4S猫头猫                                ? vh(100) - safeAreaInsets.top
167*ec4205c4S猫头猫                                : height,
168*ec4205c4S猫头猫                    },
169*ec4205c4S猫头猫                    panelAnimated,
170*ec4205c4S猫头猫                ]}>
171*ec4205c4S猫头猫                {renderBody(loading)}
172*ec4205c4S猫头猫            </Animated.View>
173*ec4205c4S猫头猫        </>
174*ec4205c4S猫头猫    );
175*ec4205c4S猫头猫}
176*ec4205c4S猫头猫
177*ec4205c4S猫头猫const style = StyleSheet.create({
178*ec4205c4S猫头猫    maskWrapper: {
179*ec4205c4S猫头猫        position: 'absolute',
180*ec4205c4S猫头猫        width: '100%',
181*ec4205c4S猫头猫        height: '100%',
182*ec4205c4S猫头猫        top: 0,
183*ec4205c4S猫头猫        left: 0,
184*ec4205c4S猫头猫        right: 0,
185*ec4205c4S猫头猫        bottom: 0,
186*ec4205c4S猫头猫    },
187*ec4205c4S猫头猫    mask: {
188*ec4205c4S猫头猫        backgroundColor: '#333333',
189*ec4205c4S猫头猫        opacity: 0.5,
190*ec4205c4S猫头猫    },
191*ec4205c4S猫头猫    wrapper: {
192*ec4205c4S猫头猫        position: 'absolute',
193*ec4205c4S猫头猫        width: rpx(750),
194*ec4205c4S猫头猫        bottom: 0,
195*ec4205c4S猫头猫        right: 0,
196*ec4205c4S猫头猫        borderTopLeftRadius: rpx(28),
197*ec4205c4S猫头猫        borderTopRightRadius: rpx(28),
198*ec4205c4S猫头猫    },
199*ec4205c4S猫头猫});
200