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, 21*4da0658bSmaotoumao 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猫头猫 28*4da0658bSmaotoumaoconst 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( 58ec4205c4S猫头猫 () => (orientation === 'horizonal' ? rpx(750) : height), 59ec4205c4S猫头猫 [orientation], 60ec4205c4S猫头猫 ); 61*4da0658bSmaotoumao 62ec4205c4S猫头猫 const backHandlerRef = useRef<NativeEventSubscription>(); 63ec4205c4S猫头猫 64ec4205c4S猫头猫 const hideCallbackRef = useRef<Function[]>([]); 65ec4205c4S猫头猫 66209bed7bS猫头猫 const [keyboardHeight, setKeyboardHeight] = useState(0); 67ec4205c4S猫头猫 useEffect(() => { 68ec4205c4S猫头猫 snapPoint.value = withTiming(1, timingConfig); 69*4da0658bSmaotoumao 70ec4205c4S猫头猫 timerRef.current = setTimeout(() => { 71ec4205c4S猫头猫 if (loading) { 72ec4205c4S猫头猫 // 兜底 73ec4205c4S猫头猫 setLoading(false); 74ec4205c4S猫头猫 } 75ec4205c4S猫头猫 }, 400); 76ec4205c4S猫头猫 if (backHandlerRef.current) { 77*4da0658bSmaotoumao backHandlerRef.current.remove(); 78ec4205c4S猫头猫 backHandlerRef.current = undefined; 79ec4205c4S猫头猫 } 80ec4205c4S猫头猫 backHandlerRef.current = BackHandler.addEventListener( 81ec4205c4S猫头猫 'hardwareBackPress', 82ec4205c4S猫头猫 () => { 83ec4205c4S猫头猫 snapPoint.value = withTiming(0, timingConfig); 84ec4205c4S猫头猫 return true; 85ec4205c4S猫头猫 }, 86ec4205c4S猫头猫 ); 87ec4205c4S猫头猫 88ec4205c4S猫头猫 const listenerSubscription = DeviceEventEmitter.addListener( 89ec4205c4S猫头猫 'hidePanel', 90ec4205c4S猫头猫 (callback?: () => void) => { 91ec4205c4S猫头猫 if (callback) { 92ec4205c4S猫头猫 hideCallbackRef.current.push(callback); 93ec4205c4S猫头猫 } 94ec4205c4S猫头猫 snapPoint.value = withTiming(0, timingConfig); 95ec4205c4S猫头猫 }, 96ec4205c4S猫头猫 ); 97ec4205c4S猫头猫 98209bed7bS猫头猫 let keyboardDidShowListener: EmitterSubscription; 99209bed7bS猫头猫 let keyboardDidHideListener: EmitterSubscription; 100209bed7bS猫头猫 if (awareKeyboard) { 101209bed7bS猫头猫 keyboardDidShowListener = Keyboard.addListener( 102209bed7bS猫头猫 'keyboardDidShow', 103209bed7bS猫头猫 event => { 104209bed7bS猫头猫 setKeyboardHeight(event.endCoordinates.height); 105209bed7bS猫头猫 }, 106209bed7bS猫头猫 ); 107209bed7bS猫头猫 108209bed7bS猫头猫 keyboardDidHideListener = Keyboard.addListener( 109209bed7bS猫头猫 'keyboardDidHide', 110209bed7bS猫头猫 () => { 111209bed7bS猫头猫 setKeyboardHeight(0); 112209bed7bS猫头猫 }, 113209bed7bS猫头猫 ); 114209bed7bS猫头猫 } 115209bed7bS猫头猫 116ec4205c4S猫头猫 return () => { 117ec4205c4S猫头猫 if (timerRef.current) { 118ec4205c4S猫头猫 clearTimeout(timerRef.current); 119ec4205c4S猫头猫 timerRef.current = null; 120ec4205c4S猫头猫 } 121ec4205c4S猫头猫 if (backHandlerRef.current) { 122ec4205c4S猫头猫 backHandlerRef.current?.remove(); 123ec4205c4S猫头猫 backHandlerRef.current = undefined; 124ec4205c4S猫头猫 } 125ec4205c4S猫头猫 listenerSubscription.remove(); 126209bed7bS猫头猫 keyboardDidShowListener?.remove(); 127209bed7bS猫头猫 keyboardDidHideListener?.remove(); 128ec4205c4S猫头猫 }; 129ec4205c4S猫头猫 }, []); 130ec4205c4S猫头猫 131ec4205c4S猫头猫 const maskAnimated = useAnimatedStyle(() => { 132ec4205c4S猫头猫 return { 133756bc302S猫头猫 opacity: snapPoint.value * 0.5, 134ec4205c4S猫头猫 }; 135ec4205c4S猫头猫 }); 136ec4205c4S猫头猫 137ec4205c4S猫头猫 const panelAnimated = useAnimatedStyle(() => { 138ec4205c4S猫头猫 return { 139ec4205c4S猫头猫 transform: [ 140ec4205c4S猫头猫 orientation === 'vertical' 141ec4205c4S猫头猫 ? { 142756bc302S猫头猫 translateY: (1 - snapPoint.value) * useAnimatedBase, 143ec4205c4S猫头猫 } 144ec4205c4S猫头猫 : { 145756bc302S猫头猫 translateX: (1 - snapPoint.value) * useAnimatedBase, 146ec4205c4S猫头猫 }, 147ec4205c4S猫头猫 ], 148ec4205c4S猫头猫 }; 149ec4205c4S猫头猫 }, [orientation]); 150ec4205c4S猫头猫 151ec4205c4S猫头猫 const mountPanel = useCallback(() => { 152ec4205c4S猫头猫 setLoading(false); 153ec4205c4S猫头猫 }, []); 154ec4205c4S猫头猫 155ec4205c4S猫头猫 const unmountPanel = useCallback(() => { 156ec4205c4S猫头猫 panelInfoStore.setValue({ 157ec4205c4S猫头猫 name: null, 158ec4205c4S猫头猫 payload: null, 159ec4205c4S猫头猫 }); 160ec4205c4S猫头猫 hideCallbackRef.current.forEach(cb => cb?.()); 161ec4205c4S猫头猫 }, []); 162ec4205c4S猫头猫 163ec4205c4S猫头猫 useAnimatedReaction( 164ec4205c4S猫头猫 () => snapPoint.value, 165ec4205c4S猫头猫 (result, prevResult) => { 166*4da0658bSmaotoumao if ( 167*4da0658bSmaotoumao ((prevResult !== null && result > prevResult) || 168*4da0658bSmaotoumao prevResult === null) && 169*4da0658bSmaotoumao result > 0.8 170*4da0658bSmaotoumao ) { 171ec4205c4S猫头猫 runOnJS(mountPanel)(); 172ec4205c4S猫头猫 } 173*4da0658bSmaotoumao 174ec4205c4S猫头猫 if (prevResult && result < prevResult && result === 0) { 175ec4205c4S猫头猫 runOnJS(unmountPanel)(); 176ec4205c4S猫头猫 } 177ec4205c4S猫头猫 }, 178ec4205c4S猫头猫 [], 179ec4205c4S猫头猫 ); 180ec4205c4S猫头猫 181428a0723S猫头猫 const panelBody = ( 182ec4205c4S猫头猫 <Animated.View 183ec4205c4S猫头猫 style={[ 184ec4205c4S猫头猫 style.wrapper, 185ec4205c4S猫头猫 { 186277c5280S猫头猫 backgroundColor: colors.backdrop, 187ec4205c4S猫头猫 height: 188ec4205c4S猫头猫 orientation === 'horizonal' 189ec4205c4S猫头猫 ? vh(100) - safeAreaInsets.top 190dcb7566cS猫头猫 : height - 191dcb7566cS猫头猫 (isFinite(keyboardHeight) ? keyboardHeight : 0), 192ec4205c4S猫头猫 }, 193ec4205c4S猫头猫 panelAnimated, 194ec4205c4S猫头猫 ]}> 195ec4205c4S猫头猫 {renderBody(loading)} 196ec4205c4S猫头猫 </Animated.View> 197428a0723S猫头猫 ); 198428a0723S猫头猫 199428a0723S猫头猫 return ( 200428a0723S猫头猫 <> 201428a0723S猫头猫 <Pressable 202428a0723S猫头猫 style={style.maskWrapper} 203428a0723S猫头猫 onPress={() => { 204428a0723S猫头猫 snapPoint.value = withTiming(0, timingConfig); 205428a0723S猫头猫 }}> 206428a0723S猫头猫 <Animated.View 207428a0723S猫头猫 style={[style.maskWrapper, style.mask, maskAnimated]} 208428a0723S猫头猫 /> 209428a0723S猫头猫 </Pressable> 210428a0723S猫头猫 {keyboardAvoidBehavior === 'none' ? ( 211428a0723S猫头猫 panelBody 212428a0723S猫头猫 ) : ( 213428a0723S猫头猫 <KeyboardAvoidingView 214fe32deaaS猫头猫 style={style.kbContainer} 215428a0723S猫头猫 behavior={keyboardAvoidBehavior || 'position'}> 216428a0723S猫头猫 {panelBody} 217cf3527d9S猫头猫 </KeyboardAvoidingView> 218428a0723S猫头猫 )} 219ec4205c4S猫头猫 </> 220ec4205c4S猫头猫 ); 221ec4205c4S猫头猫} 222ec4205c4S猫头猫 223ec4205c4S猫头猫const style = StyleSheet.create({ 224ec4205c4S猫头猫 maskWrapper: { 225ec4205c4S猫头猫 position: 'absolute', 226ec4205c4S猫头猫 width: '100%', 227ec4205c4S猫头猫 height: '100%', 228ec4205c4S猫头猫 top: 0, 229ec4205c4S猫头猫 left: 0, 230ec4205c4S猫头猫 right: 0, 231ec4205c4S猫头猫 bottom: 0, 232fe32deaaS猫头猫 zIndex: 15000, 233ec4205c4S猫头猫 }, 234ec4205c4S猫头猫 mask: { 235277c5280S猫头猫 backgroundColor: '#000', 236ec4205c4S猫头猫 opacity: 0.5, 237ec4205c4S猫头猫 }, 238ec4205c4S猫头猫 wrapper: { 239ec4205c4S猫头猫 position: 'absolute', 240ec4205c4S猫头猫 width: rpx(750), 241ec4205c4S猫头猫 bottom: 0, 242ec4205c4S猫头猫 right: 0, 243ec4205c4S猫头猫 borderTopLeftRadius: rpx(28), 244ec4205c4S猫头猫 borderTopRightRadius: rpx(28), 245fe32deaaS猫头猫 zIndex: 15010, 246fe32deaaS猫头猫 }, 247fe32deaaS猫头猫 kbContainer: { 248fe32deaaS猫头猫 zIndex: 15010, 249ec4205c4S猫头猫 }, 250ec4205c4S猫头猫}); 251