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