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