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