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, 21ec4205c4S猫头猫} from 'react-native-reanimated'; 22ec4205c4S猫头猫import useColors from '@/hooks/useColors'; 23ec4205c4S猫头猫import {useSafeAreaInsets} from 'react-native-safe-area-context'; 24ec4205c4S猫头猫import useOrientation from '@/hooks/useOrientation'; 25ec4205c4S猫头猫import {panelInfoStore} from '../usePanel'; 26ec4205c4S猫头猫 27ec4205c4S猫头猫const ANIMATION_EASING: Animated.EasingFunction = Easing.out(Easing.exp); 28ec4205c4S猫头猫const ANIMATION_DURATION = 250; 29ec4205c4S猫头猫 30ec4205c4S猫头猫const timingConfig = { 31ec4205c4S猫头猫 duration: ANIMATION_DURATION, 32ec4205c4S猫头猫 easing: ANIMATION_EASING, 33ec4205c4S猫头猫}; 34ec4205c4S猫头猫 35ec4205c4S猫头猫interface IPanelBaseProps { 36428a0723S猫头猫 keyboardAvoidBehavior?: 'height' | 'padding' | 'position' | 'none'; 37ec4205c4S猫头猫 height?: number; 38ec4205c4S猫头猫 renderBody: (loading: boolean) => JSX.Element; 39209bed7bS猫头猫 awareKeyboard?: boolean; 40ec4205c4S猫头猫} 41ec4205c4S猫头猫 42ec4205c4S猫头猫export default function (props: IPanelBaseProps) { 43209bed7bS猫头猫 const { 44209bed7bS猫头猫 height = vh(60), 45209bed7bS猫头猫 renderBody, 46209bed7bS猫头猫 keyboardAvoidBehavior, 47209bed7bS猫头猫 awareKeyboard, 48209bed7bS猫头猫 } = props; 49ec4205c4S猫头猫 const snapPoint = useSharedValue(0); 50ec4205c4S猫头猫 51ec4205c4S猫头猫 const colors = useColors(); 52ec4205c4S猫头猫 const [loading, setLoading] = useState(true); // 是否处于弹出状态 53ec4205c4S猫头猫 const timerRef = useRef<any>(); 54ec4205c4S猫头猫 const safeAreaInsets = useSafeAreaInsets(); 55ec4205c4S猫头猫 const orientation = useOrientation(); 56ec4205c4S猫头猫 const useAnimatedBase = useMemo( 57ec4205c4S猫头猫 () => (orientation === 'horizonal' ? rpx(750) : height), 58ec4205c4S猫头猫 [orientation], 59ec4205c4S猫头猫 ); 60ec4205c4S猫头猫 const backHandlerRef = useRef<NativeEventSubscription>(); 61ec4205c4S猫头猫 62ec4205c4S猫头猫 const hideCallbackRef = useRef<Function[]>([]); 63ec4205c4S猫头猫 64209bed7bS猫头猫 const [keyboardHeight, setKeyboardHeight] = useState(0); 65ec4205c4S猫头猫 useEffect(() => { 66ec4205c4S猫头猫 snapPoint.value = withTiming(1, timingConfig); 67ec4205c4S猫头猫 timerRef.current = setTimeout(() => { 68ec4205c4S猫头猫 if (loading) { 69ec4205c4S猫头猫 // 兜底 70ec4205c4S猫头猫 setLoading(false); 71ec4205c4S猫头猫 } 72ec4205c4S猫头猫 }, 400); 73ec4205c4S猫头猫 if (backHandlerRef.current) { 74ec4205c4S猫头猫 backHandlerRef.current?.remove(); 75ec4205c4S猫头猫 backHandlerRef.current = undefined; 76ec4205c4S猫头猫 } 77ec4205c4S猫头猫 backHandlerRef.current = BackHandler.addEventListener( 78ec4205c4S猫头猫 'hardwareBackPress', 79ec4205c4S猫头猫 () => { 80ec4205c4S猫头猫 snapPoint.value = withTiming(0, timingConfig); 81ec4205c4S猫头猫 return true; 82ec4205c4S猫头猫 }, 83ec4205c4S猫头猫 ); 84ec4205c4S猫头猫 85ec4205c4S猫头猫 const listenerSubscription = DeviceEventEmitter.addListener( 86ec4205c4S猫头猫 'hidePanel', 87ec4205c4S猫头猫 (callback?: () => void) => { 88ec4205c4S猫头猫 if (callback) { 89ec4205c4S猫头猫 hideCallbackRef.current.push(callback); 90ec4205c4S猫头猫 } 91ec4205c4S猫头猫 snapPoint.value = withTiming(0, timingConfig); 92ec4205c4S猫头猫 }, 93ec4205c4S猫头猫 ); 94ec4205c4S猫头猫 95209bed7bS猫头猫 let keyboardDidShowListener: EmitterSubscription; 96209bed7bS猫头猫 let keyboardDidHideListener: EmitterSubscription; 97209bed7bS猫头猫 if (awareKeyboard) { 98209bed7bS猫头猫 keyboardDidShowListener = Keyboard.addListener( 99209bed7bS猫头猫 'keyboardDidShow', 100209bed7bS猫头猫 event => { 101209bed7bS猫头猫 setKeyboardHeight(event.endCoordinates.height); 102209bed7bS猫头猫 }, 103209bed7bS猫头猫 ); 104209bed7bS猫头猫 105209bed7bS猫头猫 keyboardDidHideListener = Keyboard.addListener( 106209bed7bS猫头猫 'keyboardDidHide', 107209bed7bS猫头猫 () => { 108209bed7bS猫头猫 setKeyboardHeight(0); 109209bed7bS猫头猫 }, 110209bed7bS猫头猫 ); 111209bed7bS猫头猫 } 112209bed7bS猫头猫 113ec4205c4S猫头猫 return () => { 114ec4205c4S猫头猫 if (timerRef.current) { 115ec4205c4S猫头猫 clearTimeout(timerRef.current); 116ec4205c4S猫头猫 timerRef.current = null; 117ec4205c4S猫头猫 } 118ec4205c4S猫头猫 if (backHandlerRef.current) { 119ec4205c4S猫头猫 backHandlerRef.current?.remove(); 120ec4205c4S猫头猫 backHandlerRef.current = undefined; 121ec4205c4S猫头猫 } 122ec4205c4S猫头猫 listenerSubscription.remove(); 123209bed7bS猫头猫 keyboardDidShowListener?.remove(); 124209bed7bS猫头猫 keyboardDidHideListener?.remove(); 125ec4205c4S猫头猫 }; 126ec4205c4S猫头猫 }, []); 127ec4205c4S猫头猫 128ec4205c4S猫头猫 const maskAnimated = useAnimatedStyle(() => { 129ec4205c4S猫头猫 return { 130756bc302S猫头猫 opacity: snapPoint.value * 0.5, 131ec4205c4S猫头猫 }; 132ec4205c4S猫头猫 }); 133ec4205c4S猫头猫 134ec4205c4S猫头猫 const panelAnimated = useAnimatedStyle(() => { 135ec4205c4S猫头猫 return { 136ec4205c4S猫头猫 transform: [ 137ec4205c4S猫头猫 orientation === 'vertical' 138ec4205c4S猫头猫 ? { 139756bc302S猫头猫 translateY: (1 - snapPoint.value) * useAnimatedBase, 140ec4205c4S猫头猫 } 141ec4205c4S猫头猫 : { 142756bc302S猫头猫 translateX: (1 - snapPoint.value) * useAnimatedBase, 143ec4205c4S猫头猫 }, 144ec4205c4S猫头猫 ], 145ec4205c4S猫头猫 }; 146ec4205c4S猫头猫 }, [orientation]); 147ec4205c4S猫头猫 148ec4205c4S猫头猫 const mountPanel = useCallback(() => { 149ec4205c4S猫头猫 setLoading(false); 150ec4205c4S猫头猫 }, []); 151ec4205c4S猫头猫 152ec4205c4S猫头猫 const unmountPanel = useCallback(() => { 153ec4205c4S猫头猫 panelInfoStore.setValue({ 154ec4205c4S猫头猫 name: null, 155ec4205c4S猫头猫 payload: null, 156ec4205c4S猫头猫 }); 157ec4205c4S猫头猫 hideCallbackRef.current.forEach(cb => cb?.()); 158ec4205c4S猫头猫 }, []); 159ec4205c4S猫头猫 160ec4205c4S猫头猫 useAnimatedReaction( 161ec4205c4S猫头猫 () => snapPoint.value, 162ec4205c4S猫头猫 (result, prevResult) => { 163ec4205c4S猫头猫 if (prevResult && result > prevResult && result > 0.8) { 164ec4205c4S猫头猫 runOnJS(mountPanel)(); 165ec4205c4S猫头猫 } 166ec4205c4S猫头猫 if (prevResult && result < prevResult && result === 0) { 167ec4205c4S猫头猫 runOnJS(unmountPanel)(); 168ec4205c4S猫头猫 } 169ec4205c4S猫头猫 }, 170ec4205c4S猫头猫 [], 171ec4205c4S猫头猫 ); 172ec4205c4S猫头猫 173428a0723S猫头猫 const panelBody = ( 174ec4205c4S猫头猫 <Animated.View 175ec4205c4S猫头猫 style={[ 176ec4205c4S猫头猫 style.wrapper, 177ec4205c4S猫头猫 { 178277c5280S猫头猫 backgroundColor: colors.backdrop, 179ec4205c4S猫头猫 height: 180ec4205c4S猫头猫 orientation === 'horizonal' 181ec4205c4S猫头猫 ? vh(100) - safeAreaInsets.top 182*dcb7566cS猫头猫 : height - 183*dcb7566cS猫头猫 (isFinite(keyboardHeight) ? keyboardHeight : 0), 184ec4205c4S猫头猫 }, 185ec4205c4S猫头猫 panelAnimated, 186ec4205c4S猫头猫 ]}> 187ec4205c4S猫头猫 {renderBody(loading)} 188ec4205c4S猫头猫 </Animated.View> 189428a0723S猫头猫 ); 190428a0723S猫头猫 191428a0723S猫头猫 return ( 192428a0723S猫头猫 <> 193428a0723S猫头猫 <Pressable 194428a0723S猫头猫 style={style.maskWrapper} 195428a0723S猫头猫 onPress={() => { 196428a0723S猫头猫 snapPoint.value = withTiming(0, timingConfig); 197428a0723S猫头猫 }}> 198428a0723S猫头猫 <Animated.View 199428a0723S猫头猫 style={[style.maskWrapper, style.mask, maskAnimated]} 200428a0723S猫头猫 /> 201428a0723S猫头猫 </Pressable> 202428a0723S猫头猫 {keyboardAvoidBehavior === 'none' ? ( 203428a0723S猫头猫 panelBody 204428a0723S猫头猫 ) : ( 205428a0723S猫头猫 <KeyboardAvoidingView 206fe32deaaS猫头猫 style={style.kbContainer} 207428a0723S猫头猫 behavior={keyboardAvoidBehavior || 'position'}> 208428a0723S猫头猫 {panelBody} 209cf3527d9S猫头猫 </KeyboardAvoidingView> 210428a0723S猫头猫 )} 211ec4205c4S猫头猫 </> 212ec4205c4S猫头猫 ); 213ec4205c4S猫头猫} 214ec4205c4S猫头猫 215ec4205c4S猫头猫const style = StyleSheet.create({ 216ec4205c4S猫头猫 maskWrapper: { 217ec4205c4S猫头猫 position: 'absolute', 218ec4205c4S猫头猫 width: '100%', 219ec4205c4S猫头猫 height: '100%', 220ec4205c4S猫头猫 top: 0, 221ec4205c4S猫头猫 left: 0, 222ec4205c4S猫头猫 right: 0, 223ec4205c4S猫头猫 bottom: 0, 224fe32deaaS猫头猫 zIndex: 15000, 225ec4205c4S猫头猫 }, 226ec4205c4S猫头猫 mask: { 227277c5280S猫头猫 backgroundColor: '#000', 228ec4205c4S猫头猫 opacity: 0.5, 229ec4205c4S猫头猫 }, 230ec4205c4S猫头猫 wrapper: { 231ec4205c4S猫头猫 position: 'absolute', 232ec4205c4S猫头猫 width: rpx(750), 233ec4205c4S猫头猫 bottom: 0, 234ec4205c4S猫头猫 right: 0, 235ec4205c4S猫头猫 borderTopLeftRadius: rpx(28), 236ec4205c4S猫头猫 borderTopRightRadius: rpx(28), 237fe32deaaS猫头猫 zIndex: 15010, 238fe32deaaS猫头猫 }, 239fe32deaaS猫头猫 kbContainer: { 240fe32deaaS猫头猫 zIndex: 15010, 241ec4205c4S猫头猫 }, 242ec4205c4S猫头猫}); 243