xref: /MusicFree/src/components/panels/base/panelBase.tsx (revision 209bed7bb7e97d20f1d52847712e6b9133f90125)
1ec4205c4S猫头猫import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2ec4205c4S猫头猫import {
3ec4205c4S猫头猫    BackHandler,
4ec4205c4S猫头猫    DeviceEventEmitter,
5*209bed7bS猫头猫    EmitterSubscription,
6*209bed7bS猫头猫    Keyboard,
7cf3527d9S猫头猫    KeyboardAvoidingView,
8ec4205c4S猫头猫    NativeEventSubscription,
9ec4205c4S猫头猫    Pressable,
10ec4205c4S猫头猫    StyleSheet,
11ec4205c4S猫头猫} from 'react-native';
12ec4205c4S猫头猫import rpx, {vh} from '@/utils/rpx';
13ec4205c4S猫头猫
14ec4205c4S猫头猫import Animated, {
15ec4205c4S猫头猫    Easing,
16ec4205c4S猫头猫    runOnJS,
17ec4205c4S猫头猫    useAnimatedReaction,
18ec4205c4S猫头猫    useAnimatedStyle,
19ec4205c4S猫头猫    useSharedValue,
20ec4205c4S猫头猫    withTiming,
21ec4205c4S猫头猫} from 'react-native-reanimated';
22ec4205c4S猫头猫import useColors from '@/hooks/useColors';
23ec4205c4S猫头猫import {useSafeAreaInsets} from 'react-native-safe-area-context';
24ec4205c4S猫头猫import useOrientation from '@/hooks/useOrientation';
25ec4205c4S猫头猫import {panelInfoStore} from '../usePanel';
26ec4205c4S猫头猫
27ec4205c4S猫头猫const ANIMATION_EASING: Animated.EasingFunction = Easing.out(Easing.exp);
28ec4205c4S猫头猫const ANIMATION_DURATION = 250;
29ec4205c4S猫头猫
30ec4205c4S猫头猫const timingConfig = {
31ec4205c4S猫头猫    duration: ANIMATION_DURATION,
32ec4205c4S猫头猫    easing: ANIMATION_EASING,
33ec4205c4S猫头猫};
34ec4205c4S猫头猫
35ec4205c4S猫头猫interface IPanelBaseProps {
36428a0723S猫头猫    keyboardAvoidBehavior?: 'height' | 'padding' | 'position' | 'none';
37ec4205c4S猫头猫    height?: number;
38ec4205c4S猫头猫    renderBody: (loading: boolean) => JSX.Element;
39*209bed7bS猫头猫    awareKeyboard?: boolean;
40ec4205c4S猫头猫}
41ec4205c4S猫头猫
42ec4205c4S猫头猫export default function (props: IPanelBaseProps) {
43*209bed7bS猫头猫    const {
44*209bed7bS猫头猫        height = vh(60),
45*209bed7bS猫头猫        renderBody,
46*209bed7bS猫头猫        keyboardAvoidBehavior,
47*209bed7bS猫头猫        awareKeyboard,
48*209bed7bS猫头猫    } = props;
49ec4205c4S猫头猫    const snapPoint = useSharedValue(0);
50ec4205c4S猫头猫
51ec4205c4S猫头猫    const colors = useColors();
52ec4205c4S猫头猫    const [loading, setLoading] = useState(true); // 是否处于弹出状态
53ec4205c4S猫头猫    const timerRef = useRef<any>();
54ec4205c4S猫头猫    const safeAreaInsets = useSafeAreaInsets();
55ec4205c4S猫头猫    const orientation = useOrientation();
56ec4205c4S猫头猫    const useAnimatedBase = useMemo(
57ec4205c4S猫头猫        () => (orientation === 'horizonal' ? rpx(750) : height),
58ec4205c4S猫头猫        [orientation],
59ec4205c4S猫头猫    );
60ec4205c4S猫头猫    const backHandlerRef = useRef<NativeEventSubscription>();
61ec4205c4S猫头猫
62ec4205c4S猫头猫    const hideCallbackRef = useRef<Function[]>([]);
63ec4205c4S猫头猫
64*209bed7bS猫头猫    const [keyboardHeight, setKeyboardHeight] = useState(0);
65ec4205c4S猫头猫    useEffect(() => {
66ec4205c4S猫头猫        snapPoint.value = withTiming(1, timingConfig);
67ec4205c4S猫头猫        timerRef.current = setTimeout(() => {
68ec4205c4S猫头猫            if (loading) {
69ec4205c4S猫头猫                // 兜底
70ec4205c4S猫头猫                setLoading(false);
71ec4205c4S猫头猫            }
72ec4205c4S猫头猫        }, 400);
73ec4205c4S猫头猫        if (backHandlerRef.current) {
74ec4205c4S猫头猫            backHandlerRef.current?.remove();
75ec4205c4S猫头猫            backHandlerRef.current = undefined;
76ec4205c4S猫头猫        }
77ec4205c4S猫头猫        backHandlerRef.current = BackHandler.addEventListener(
78ec4205c4S猫头猫            'hardwareBackPress',
79ec4205c4S猫头猫            () => {
80ec4205c4S猫头猫                snapPoint.value = withTiming(0, timingConfig);
81ec4205c4S猫头猫                return true;
82ec4205c4S猫头猫            },
83ec4205c4S猫头猫        );
84ec4205c4S猫头猫
85ec4205c4S猫头猫        const listenerSubscription = DeviceEventEmitter.addListener(
86ec4205c4S猫头猫            'hidePanel',
87ec4205c4S猫头猫            (callback?: () => void) => {
88ec4205c4S猫头猫                if (callback) {
89ec4205c4S猫头猫                    hideCallbackRef.current.push(callback);
90ec4205c4S猫头猫                }
91ec4205c4S猫头猫                snapPoint.value = withTiming(0, timingConfig);
92ec4205c4S猫头猫            },
93ec4205c4S猫头猫        );
94ec4205c4S猫头猫
95*209bed7bS猫头猫        let keyboardDidShowListener: EmitterSubscription;
96*209bed7bS猫头猫        let keyboardDidHideListener: EmitterSubscription;
97*209bed7bS猫头猫        if (awareKeyboard) {
98*209bed7bS猫头猫            Keyboard.addListener('keyboardDidChangeFrame', event => {
99*209bed7bS猫头猫                console.log(event, 'KKss');
100*209bed7bS猫头猫            });
101*209bed7bS猫头猫            Keyboard.addListener('keyboardWillShow', event => {
102*209bed7bS猫头猫                console.log(event, 'KKsss');
103*209bed7bS猫头猫            });
104*209bed7bS猫头猫            keyboardDidShowListener = Keyboard.addListener(
105*209bed7bS猫头猫                'keyboardDidShow',
106*209bed7bS猫头猫                event => {
107*209bed7bS猫头猫                    setKeyboardHeight(event.endCoordinates.height);
108*209bed7bS猫头猫                },
109*209bed7bS猫头猫            );
110*209bed7bS猫头猫
111*209bed7bS猫头猫            keyboardDidHideListener = Keyboard.addListener(
112*209bed7bS猫头猫                'keyboardDidHide',
113*209bed7bS猫头猫                () => {
114*209bed7bS猫头猫                    setKeyboardHeight(0);
115*209bed7bS猫头猫                },
116*209bed7bS猫头猫            );
117*209bed7bS猫头猫        }
118*209bed7bS猫头猫
119ec4205c4S猫头猫        return () => {
120ec4205c4S猫头猫            if (timerRef.current) {
121ec4205c4S猫头猫                clearTimeout(timerRef.current);
122ec4205c4S猫头猫                timerRef.current = null;
123ec4205c4S猫头猫            }
124ec4205c4S猫头猫            if (backHandlerRef.current) {
125ec4205c4S猫头猫                backHandlerRef.current?.remove();
126ec4205c4S猫头猫                backHandlerRef.current = undefined;
127ec4205c4S猫头猫            }
128ec4205c4S猫头猫            listenerSubscription.remove();
129*209bed7bS猫头猫            keyboardDidShowListener?.remove();
130*209bed7bS猫头猫            keyboardDidHideListener?.remove();
131ec4205c4S猫头猫        };
132ec4205c4S猫头猫    }, []);
133ec4205c4S猫头猫
134ec4205c4S猫头猫    const maskAnimated = useAnimatedStyle(() => {
135ec4205c4S猫头猫        return {
136756bc302S猫头猫            opacity: snapPoint.value * 0.5,
137ec4205c4S猫头猫        };
138ec4205c4S猫头猫    });
139ec4205c4S猫头猫
140ec4205c4S猫头猫    const panelAnimated = useAnimatedStyle(() => {
141ec4205c4S猫头猫        return {
142ec4205c4S猫头猫            transform: [
143ec4205c4S猫头猫                orientation === 'vertical'
144ec4205c4S猫头猫                    ? {
145756bc302S猫头猫                          translateY: (1 - snapPoint.value) * useAnimatedBase,
146ec4205c4S猫头猫                      }
147ec4205c4S猫头猫                    : {
148756bc302S猫头猫                          translateX: (1 - snapPoint.value) * useAnimatedBase,
149ec4205c4S猫头猫                      },
150ec4205c4S猫头猫            ],
151ec4205c4S猫头猫        };
152ec4205c4S猫头猫    }, [orientation]);
153ec4205c4S猫头猫
154ec4205c4S猫头猫    const mountPanel = useCallback(() => {
155ec4205c4S猫头猫        setLoading(false);
156ec4205c4S猫头猫    }, []);
157ec4205c4S猫头猫
158ec4205c4S猫头猫    const unmountPanel = useCallback(() => {
159ec4205c4S猫头猫        panelInfoStore.setValue({
160ec4205c4S猫头猫            name: null,
161ec4205c4S猫头猫            payload: null,
162ec4205c4S猫头猫        });
163ec4205c4S猫头猫        hideCallbackRef.current.forEach(cb => cb?.());
164ec4205c4S猫头猫    }, []);
165ec4205c4S猫头猫
166ec4205c4S猫头猫    useAnimatedReaction(
167ec4205c4S猫头猫        () => snapPoint.value,
168ec4205c4S猫头猫        (result, prevResult) => {
169ec4205c4S猫头猫            if (prevResult && result > prevResult && result > 0.8) {
170ec4205c4S猫头猫                runOnJS(mountPanel)();
171ec4205c4S猫头猫            }
172ec4205c4S猫头猫            if (prevResult && result < prevResult && result === 0) {
173ec4205c4S猫头猫                runOnJS(unmountPanel)();
174ec4205c4S猫头猫            }
175ec4205c4S猫头猫        },
176ec4205c4S猫头猫        [],
177ec4205c4S猫头猫    );
178ec4205c4S猫头猫
179428a0723S猫头猫    const panelBody = (
180ec4205c4S猫头猫        <Animated.View
181ec4205c4S猫头猫            style={[
182ec4205c4S猫头猫                style.wrapper,
183ec4205c4S猫头猫                {
184277c5280S猫头猫                    backgroundColor: colors.backdrop,
185ec4205c4S猫头猫                    height:
186ec4205c4S猫头猫                        orientation === 'horizonal'
187ec4205c4S猫头猫                            ? vh(100) - safeAreaInsets.top
188*209bed7bS猫头猫                            : height - keyboardHeight,
189ec4205c4S猫头猫                },
190ec4205c4S猫头猫                panelAnimated,
191ec4205c4S猫头猫            ]}>
192ec4205c4S猫头猫            {renderBody(loading)}
193ec4205c4S猫头猫        </Animated.View>
194428a0723S猫头猫    );
195428a0723S猫头猫
196428a0723S猫头猫    return (
197428a0723S猫头猫        <>
198428a0723S猫头猫            <Pressable
199428a0723S猫头猫                style={style.maskWrapper}
200428a0723S猫头猫                onPress={() => {
201428a0723S猫头猫                    snapPoint.value = withTiming(0, timingConfig);
202428a0723S猫头猫                }}>
203428a0723S猫头猫                <Animated.View
204428a0723S猫头猫                    style={[style.maskWrapper, style.mask, maskAnimated]}
205428a0723S猫头猫                />
206428a0723S猫头猫            </Pressable>
207428a0723S猫头猫            {keyboardAvoidBehavior === 'none' ? (
208428a0723S猫头猫                panelBody
209428a0723S猫头猫            ) : (
210428a0723S猫头猫                <KeyboardAvoidingView
211fe32deaaS猫头猫                    style={style.kbContainer}
212428a0723S猫头猫                    behavior={keyboardAvoidBehavior || 'position'}>
213428a0723S猫头猫                    {panelBody}
214cf3527d9S猫头猫                </KeyboardAvoidingView>
215428a0723S猫头猫            )}
216ec4205c4S猫头猫        </>
217ec4205c4S猫头猫    );
218ec4205c4S猫头猫}
219ec4205c4S猫头猫
220ec4205c4S猫头猫const style = StyleSheet.create({
221ec4205c4S猫头猫    maskWrapper: {
222ec4205c4S猫头猫        position: 'absolute',
223ec4205c4S猫头猫        width: '100%',
224ec4205c4S猫头猫        height: '100%',
225ec4205c4S猫头猫        top: 0,
226ec4205c4S猫头猫        left: 0,
227ec4205c4S猫头猫        right: 0,
228ec4205c4S猫头猫        bottom: 0,
229fe32deaaS猫头猫        zIndex: 15000,
230ec4205c4S猫头猫    },
231ec4205c4S猫头猫    mask: {
232277c5280S猫头猫        backgroundColor: '#000',
233ec4205c4S猫头猫        opacity: 0.5,
234ec4205c4S猫头猫    },
235ec4205c4S猫头猫    wrapper: {
236ec4205c4S猫头猫        position: 'absolute',
237ec4205c4S猫头猫        width: rpx(750),
238ec4205c4S猫头猫        bottom: 0,
239ec4205c4S猫头猫        right: 0,
240ec4205c4S猫头猫        borderTopLeftRadius: rpx(28),
241ec4205c4S猫头猫        borderTopRightRadius: rpx(28),
242fe32deaaS猫头猫        zIndex: 15010,
243fe32deaaS猫头猫    },
244fe32deaaS猫头猫    kbContainer: {
245fe32deaaS猫头猫        zIndex: 15010,
246ec4205c4S猫头猫    },
247ec4205c4S猫头猫});
248