1ec4205c4S猫头猫import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; 2ec4205c4S猫头猫import { 3ec4205c4S猫头猫 BackHandler, 4ec4205c4S猫头猫 DeviceEventEmitter, 5209bed7bS猫头猫 EmitterSubscription, 6209bed7bS猫头猫 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, 214da0658bSmaotoumao EasingFunction, 22ec4205c4S猫头猫} from 'react-native-reanimated'; 23ec4205c4S猫头猫import useColors from '@/hooks/useColors'; 24ec4205c4S猫头猫import {useSafeAreaInsets} from 'react-native-safe-area-context'; 25ec4205c4S猫头猫import useOrientation from '@/hooks/useOrientation'; 26ec4205c4S猫头猫import {panelInfoStore} from '../usePanel'; 27ec4205c4S猫头猫 284da0658bSmaotoumaoconst ANIMATION_EASING: EasingFunction = Easing.out(Easing.exp); 29ec4205c4S猫头猫const ANIMATION_DURATION = 250; 30ec4205c4S猫头猫 31ec4205c4S猫头猫const timingConfig = { 32ec4205c4S猫头猫 duration: ANIMATION_DURATION, 33ec4205c4S猫头猫 easing: ANIMATION_EASING, 34ec4205c4S猫头猫}; 35ec4205c4S猫头猫 36ec4205c4S猫头猫interface IPanelBaseProps { 37428a0723S猫头猫 keyboardAvoidBehavior?: 'height' | 'padding' | 'position' | 'none'; 38ec4205c4S猫头猫 height?: number; 39ec4205c4S猫头猫 renderBody: (loading: boolean) => JSX.Element; 40209bed7bS猫头猫 awareKeyboard?: boolean; 41ec4205c4S猫头猫} 42ec4205c4S猫头猫 43ec4205c4S猫头猫export default function (props: IPanelBaseProps) { 44209bed7bS猫头猫 const { 45209bed7bS猫头猫 height = vh(60), 46209bed7bS猫头猫 renderBody, 47209bed7bS猫头猫 keyboardAvoidBehavior, 48209bed7bS猫头猫 awareKeyboard, 49209bed7bS猫头猫 } = props; 50ec4205c4S猫头猫 const snapPoint = useSharedValue(0); 51ec4205c4S猫头猫 52ec4205c4S猫头猫 const colors = useColors(); 53ec4205c4S猫头猫 const [loading, setLoading] = useState(true); // 是否处于弹出状态 54ec4205c4S猫头猫 const timerRef = useRef<any>(); 55ec4205c4S猫头猫 const safeAreaInsets = useSafeAreaInsets(); 56ec4205c4S猫头猫 const orientation = useOrientation(); 57ec4205c4S猫头猫 const useAnimatedBase = useMemo( 58*ab5f994aSmaotoumao () => (orientation === 'horizontal' ? rpx(750) : height), 59ec4205c4S猫头猫 [orientation], 60ec4205c4S猫头猫 ); 614da0658bSmaotoumao 62ec4205c4S猫头猫 const backHandlerRef = useRef<NativeEventSubscription>(); 63ec4205c4S猫头猫 64ec4205c4S猫头猫 const hideCallbackRef = useRef<Function[]>([]); 65ec4205c4S猫头猫 66209bed7bS猫头猫 const [keyboardHeight, setKeyboardHeight] = useState(0); 67*ab5f994aSmaotoumao 68ec4205c4S猫头猫 useEffect(() => { 69ec4205c4S猫头猫 snapPoint.value = withTiming(1, timingConfig); 704da0658bSmaotoumao 71ec4205c4S猫头猫 timerRef.current = setTimeout(() => { 72ec4205c4S猫头猫 if (loading) { 73ec4205c4S猫头猫 // 兜底 74ec4205c4S猫头猫 setLoading(false); 75ec4205c4S猫头猫 } 76ec4205c4S猫头猫 }, 400); 77ec4205c4S猫头猫 if (backHandlerRef.current) { 784da0658bSmaotoumao backHandlerRef.current.remove(); 79ec4205c4S猫头猫 backHandlerRef.current = undefined; 80ec4205c4S猫头猫 } 81ec4205c4S猫头猫 backHandlerRef.current = BackHandler.addEventListener( 82ec4205c4S猫头猫 'hardwareBackPress', 83ec4205c4S猫头猫 () => { 84ec4205c4S猫头猫 snapPoint.value = withTiming(0, timingConfig); 85ec4205c4S猫头猫 return true; 86ec4205c4S猫头猫 }, 87ec4205c4S猫头猫 ); 88ec4205c4S猫头猫 89ec4205c4S猫头猫 const listenerSubscription = DeviceEventEmitter.addListener( 90ec4205c4S猫头猫 'hidePanel', 91ec4205c4S猫头猫 (callback?: () => void) => { 92ec4205c4S猫头猫 if (callback) { 93ec4205c4S猫头猫 hideCallbackRef.current.push(callback); 94ec4205c4S猫头猫 } 95ec4205c4S猫头猫 snapPoint.value = withTiming(0, timingConfig); 96ec4205c4S猫头猫 }, 97ec4205c4S猫头猫 ); 98ec4205c4S猫头猫 99209bed7bS猫头猫 let keyboardDidShowListener: EmitterSubscription; 100209bed7bS猫头猫 let keyboardDidHideListener: EmitterSubscription; 101209bed7bS猫头猫 if (awareKeyboard) { 102209bed7bS猫头猫 keyboardDidShowListener = Keyboard.addListener( 103209bed7bS猫头猫 'keyboardDidShow', 104209bed7bS猫头猫 event => { 105209bed7bS猫头猫 setKeyboardHeight(event.endCoordinates.height); 106209bed7bS猫头猫 }, 107209bed7bS猫头猫 ); 108209bed7bS猫头猫 109209bed7bS猫头猫 keyboardDidHideListener = Keyboard.addListener( 110209bed7bS猫头猫 'keyboardDidHide', 111209bed7bS猫头猫 () => { 112209bed7bS猫头猫 setKeyboardHeight(0); 113209bed7bS猫头猫 }, 114209bed7bS猫头猫 ); 115209bed7bS猫头猫 } 116209bed7bS猫头猫 117ec4205c4S猫头猫 return () => { 118ec4205c4S猫头猫 if (timerRef.current) { 119ec4205c4S猫头猫 clearTimeout(timerRef.current); 120ec4205c4S猫头猫 timerRef.current = null; 121ec4205c4S猫头猫 } 122ec4205c4S猫头猫 if (backHandlerRef.current) { 123ec4205c4S猫头猫 backHandlerRef.current?.remove(); 124ec4205c4S猫头猫 backHandlerRef.current = undefined; 125ec4205c4S猫头猫 } 126ec4205c4S猫头猫 listenerSubscription.remove(); 127209bed7bS猫头猫 keyboardDidShowListener?.remove(); 128209bed7bS猫头猫 keyboardDidHideListener?.remove(); 129ec4205c4S猫头猫 }; 130ec4205c4S猫头猫 }, []); 131ec4205c4S猫头猫 132ec4205c4S猫头猫 const maskAnimated = useAnimatedStyle(() => { 133ec4205c4S猫头猫 return { 134756bc302S猫头猫 opacity: snapPoint.value * 0.5, 135ec4205c4S猫头猫 }; 136ec4205c4S猫头猫 }); 137ec4205c4S猫头猫 138ec4205c4S猫头猫 const panelAnimated = useAnimatedStyle(() => { 139ec4205c4S猫头猫 return { 140ec4205c4S猫头猫 transform: [ 141ec4205c4S猫头猫 orientation === 'vertical' 142ec4205c4S猫头猫 ? { 143756bc302S猫头猫 translateY: (1 - snapPoint.value) * useAnimatedBase, 144ec4205c4S猫头猫 } 145ec4205c4S猫头猫 : { 146756bc302S猫头猫 translateX: (1 - snapPoint.value) * useAnimatedBase, 147ec4205c4S猫头猫 }, 148ec4205c4S猫头猫 ], 149ec4205c4S猫头猫 }; 150ec4205c4S猫头猫 }, [orientation]); 151ec4205c4S猫头猫 152ec4205c4S猫头猫 const mountPanel = useCallback(() => { 153ec4205c4S猫头猫 setLoading(false); 154ec4205c4S猫头猫 }, []); 155ec4205c4S猫头猫 156ec4205c4S猫头猫 const unmountPanel = useCallback(() => { 157ec4205c4S猫头猫 panelInfoStore.setValue({ 158ec4205c4S猫头猫 name: null, 159ec4205c4S猫头猫 payload: null, 160ec4205c4S猫头猫 }); 161ec4205c4S猫头猫 hideCallbackRef.current.forEach(cb => cb?.()); 162ec4205c4S猫头猫 }, []); 163ec4205c4S猫头猫 164ec4205c4S猫头猫 useAnimatedReaction( 165ec4205c4S猫头猫 () => snapPoint.value, 166ec4205c4S猫头猫 (result, prevResult) => { 1674da0658bSmaotoumao if ( 1684da0658bSmaotoumao ((prevResult !== null && result > prevResult) || 1694da0658bSmaotoumao prevResult === null) && 1704da0658bSmaotoumao result > 0.8 1714da0658bSmaotoumao ) { 172ec4205c4S猫头猫 runOnJS(mountPanel)(); 173ec4205c4S猫头猫 } 1744da0658bSmaotoumao 175ec4205c4S猫头猫 if (prevResult && result < prevResult && result === 0) { 176ec4205c4S猫头猫 runOnJS(unmountPanel)(); 177ec4205c4S猫头猫 } 178ec4205c4S猫头猫 }, 179ec4205c4S猫头猫 [], 180ec4205c4S猫头猫 ); 181ec4205c4S猫头猫 182428a0723S猫头猫 const panelBody = ( 183ec4205c4S猫头猫 <Animated.View 184ec4205c4S猫头猫 style={[ 185ec4205c4S猫头猫 style.wrapper, 186ec4205c4S猫头猫 { 187277c5280S猫头猫 backgroundColor: colors.backdrop, 188ec4205c4S猫头猫 height: 189*ab5f994aSmaotoumao orientation === 'horizontal' 190ec4205c4S猫头猫 ? vh(100) - safeAreaInsets.top 191dcb7566cS猫头猫 : height - 192dcb7566cS猫头猫 (isFinite(keyboardHeight) ? keyboardHeight : 0), 193ec4205c4S猫头猫 }, 194ec4205c4S猫头猫 panelAnimated, 195ec4205c4S猫头猫 ]}> 196ec4205c4S猫头猫 {renderBody(loading)} 197ec4205c4S猫头猫 </Animated.View> 198428a0723S猫头猫 ); 199428a0723S猫头猫 200428a0723S猫头猫 return ( 201428a0723S猫头猫 <> 202428a0723S猫头猫 <Pressable 203428a0723S猫头猫 style={style.maskWrapper} 204428a0723S猫头猫 onPress={() => { 205428a0723S猫头猫 snapPoint.value = withTiming(0, timingConfig); 206428a0723S猫头猫 }}> 207428a0723S猫头猫 <Animated.View 208428a0723S猫头猫 style={[style.maskWrapper, style.mask, maskAnimated]} 209428a0723S猫头猫 /> 210428a0723S猫头猫 </Pressable> 211428a0723S猫头猫 {keyboardAvoidBehavior === 'none' ? ( 212428a0723S猫头猫 panelBody 213428a0723S猫头猫 ) : ( 214428a0723S猫头猫 <KeyboardAvoidingView 215fe32deaaS猫头猫 style={style.kbContainer} 216428a0723S猫头猫 behavior={keyboardAvoidBehavior || 'position'}> 217428a0723S猫头猫 {panelBody} 218cf3527d9S猫头猫 </KeyboardAvoidingView> 219428a0723S猫头猫 )} 220ec4205c4S猫头猫 </> 221ec4205c4S猫头猫 ); 222ec4205c4S猫头猫} 223ec4205c4S猫头猫 224ec4205c4S猫头猫const style = StyleSheet.create({ 225ec4205c4S猫头猫 maskWrapper: { 226ec4205c4S猫头猫 position: 'absolute', 227ec4205c4S猫头猫 width: '100%', 228ec4205c4S猫头猫 height: '100%', 229ec4205c4S猫头猫 top: 0, 230ec4205c4S猫头猫 left: 0, 231ec4205c4S猫头猫 right: 0, 232ec4205c4S猫头猫 bottom: 0, 233fe32deaaS猫头猫 zIndex: 15000, 234ec4205c4S猫头猫 }, 235ec4205c4S猫头猫 mask: { 236277c5280S猫头猫 backgroundColor: '#000', 237ec4205c4S猫头猫 opacity: 0.5, 238ec4205c4S猫头猫 }, 239ec4205c4S猫头猫 wrapper: { 240ec4205c4S猫头猫 position: 'absolute', 241ec4205c4S猫头猫 width: rpx(750), 242ec4205c4S猫头猫 bottom: 0, 243ec4205c4S猫头猫 right: 0, 244ec4205c4S猫头猫 borderTopLeftRadius: rpx(28), 245ec4205c4S猫头猫 borderTopRightRadius: rpx(28), 246fe32deaaS猫头猫 zIndex: 15010, 247fe32deaaS猫头猫 }, 248fe32deaaS猫头猫 kbContainer: { 249fe32deaaS猫头猫 zIndex: 15010, 250ec4205c4S猫头猫 }, 251ec4205c4S猫头猫}); 252