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