import React , { FC , useState , useRef , useMemo , useCallback } from 'react' ; import { View , FlatList , TouchableOpacity , Image , Animated , PanResponderInstance , PanResponder , LayoutChangeEvent , } from 'react-native' ; import { TYText , Utils , IconFont , TYSdk , TopBar , TabBar , Carousel } from 'tuya-panel-kit' ; import styles from './style' ; import { DeviceListPanelProps } from './interface' ; import { getOnlineState } from '../../utils' ; import Shadow from '../shadowBox' ; const { convertX : cx , winHeight } = Utils . RatioUtils ; const deviceItemHeight = Math . floor ( cx ( 120 ) ) ; const pullDownHeight = TopBar . height ; const minSpeed = 0.5 ; const borderRadius = cx ( 10 ) ; const DeviceListPanel = ( { dataSource , tabs , panelStyle , containerStyle , tabBarStyle , highestPosition , initialPosition , autoShrinkDistance , isShowIconMore , initialTab , children , panelChildren , panelChildrenHeight , ListEmptyComponent , swipeable , isShowPullDown , flatListProps , tabBarProps , onIconMorePress , onTabChange , customRenderItem , customRenderTabBar , customRenderList , customRenderPullDown , } ) => { const [ chosenTab , setChosenTab ] = useState ( initialTab || '0' ) ; const [ radius , setRadius ] = useState ( borderRadius ) ; const [ releaseTop , setReleaseTop ] = useState ( initialPosition ) ; const animatedTop = useRef ( new Animated . Value ( initialPosition ) ) . current ; const pullDownTop = useRef ( new Animated . Value ( - pullDownHeight ) ) . current ; const releaseTopRef = useRef ( initialPosition ) ; const contentOffset = useRef ( 0 ) ; const [ listHeight , setListHeight ] = useState ( 300 ) ; const panelHeight = useRef ( 0 ) ; const tabsHeight = useRef ( 0 ) ; const selectedIndex = useMemo ( ( ) => { return tabs . findIndex ( d => d . key === chosenTab ) ; } , [ chosenTab , tabs ] ) ; const listPaddingHorizontal = cx ( 24 ) ; const deviceItemWidth = cx ( 375 - listPaddingHorizontal * 2 - 12 ) / 2 ; const boundaryTop = highestPosition + autoShrinkDistance ; const boundaryBottom = initialPosition - autoShrinkDistance ; let movingTop = initialPosition ; let vy = 0 ; let direction = 'down' ; let panResponder = { } ; const setTop = ( top ) => { return Animated . timing ( animatedTop , { toValue : top , duration : movingTop > boundaryBottom && movingTop < initialPosition ? 500 : 200 , } ) ; } ; const showPullDown = Animated . timing ( pullDownTop , { toValue : 0 , duration : 200 , } ) ; const hidePullDown = Animated . timing ( pullDownTop , { toValue : - pullDownHeight , duration : 150 , } ) ; const enterItem = ( item ) => { TYSdk . native . pushToNextPageWithDeviceId ( item . devId ) ; } ; const onScroll = ( event ) => { const { y } = event . nativeEvent . contentOffset ; contentOffset . current = y ; } ; const pullDown = ( ) => { animatedToTop ( initialPosition ) ; setTimeout ( ( ) => hidePullDown . start ( ) , 200 ) ; } ; /**
     * 吸顶和吸底动画
     */ const animatedToTop = ( top ) => { setTop ( top ) . start ( ( ) => { setRadius ( top === highestPosition ? 0 : borderRadius ) ; setReleaseTop ( top ) ; releaseTopRef . current = top ; if ( top === highestPosition ) { showPullDown . start ( ) ; } else if ( top === initialPosition ) { hidePullDown . start ( ) ; } direction = 'down' ; } ) ; } ; const renderItem = ( { item } ) => { const { iconUrl , icon , name , isOnline : online , pcc = '' } = item ; const isOnline = getOnlineState ( pcc ) || online ; const itemOpacity = isOnline ? 1 : 0.3 ; return ( < View style = { { height : deviceItemHeight + 12 } } > < Shadow shadowStyle = { { borderRadius : 5 , shadowRadius : 5 , shadowColor : '#2B2B2B' , shadowOpacity : 0.1 , height : deviceItemHeight , width : deviceItemWidth , backgroundColor : '#ffffff' , shadowOffset : { width : 0 , height : 0 , } , } } > < TouchableOpacity style = { styles . itemContent } onPress = { ( ) => enterItem ( item ) } > < View style = { styles . iconContainer } > { isShowIconMore && ( < TouchableOpacity style = { [ styles . iconMore , { opacity : itemOpacity } ] } onPress = { ( ) => onIconMorePress ( item ) } > < IconFont size = { cx ( 20 ) } name = "moreH" / > < /TouchableOpacity>)} < /View> < Image source = { { uri : iconUrl || icon } } style = { [ styles . itemImage , { opacity : itemOpacity } ] } / > < TYText text = { name } numberOfLines = { 1 } style = { [ styles . itemText , { opacity : itemOpacity } ] } / > < /TouchableOpacity> < /Shadow> < /View>); } ; const onPanResponderRelease = ( ) => { let finalTop = initialPosition ; if ( vy >= minSpeed ) { if ( direction === 'up' ) { finalTop = highestPosition ; } else { finalTop = initialPosition ; } } else if ( ( releaseTopRef . current < boundaryTop && movingTop < boundaryTop ) || ( releaseTopRef . current > boundaryBottom && movingTop < boundaryBottom ) ) { finalTop = highestPosition ; } else { finalTop = initialPosition ; } animatedToTop ( finalTop ) ; setListHeight ( panelHeight . current - tabsHeight . current - panelChildrenHeight ) ; } ; panResponder = useRef ( PanResponder . create ( { onStartShouldSetPanResponderCapture : ( ) => false , onStartShouldSetPanResponder : ( ) => false , onMoveShouldSetPanResponderCapture : ( __ , gestureState ) => { if ( releaseTopRef . current > highestPosition ) { if ( Math . abs ( gestureState . dy ) < 5 ) { return false ; } return true ; } if ( contentOffset . current <= 0 && releaseTopRef . current === highestPosition ) { if ( gestureState . dy > 0 ) { return true ; } return false ; } return false ; } , onMoveShouldSetPanResponder : ( ) => false , onPanResponderMove : ( __ , gestureState ) => { const { vy : gsVy , dy } = gestureState ; vy = Math . abs ( gsVy ) ; direction = dy >= 0 ? 'down' : 'up' ; const finalValue = releaseTopRef . current + dy ; if ( finalValue <= initialPosition && finalValue >= highestPosition ) { animatedTop . setValue ( finalValue ) ; movingTop = finalValue ; releaseTopRef . current = finalValue ; } } , onPanResponderTerminationRequest : ( ) => true , onPanResponderRelease , onPanResponderTerminate : onPanResponderRelease , } ) ) . current ; const renderPullDown = useCallback ( ( ) => { return ( < Animated . View style = { [ styles . pullDown , { top : pullDownTop , height : pullDownHeight } ] } > < TouchableOpacity onPress = { pullDown } style = { styles . pullDownButton } > < IconFont name = "arrow" size = { 16 } style = { styles . iconPullDown } / > < /TouchableOpacity> < /Animated.View>); } , [ pullDownTop , pullDownHeight ] ) ; const renderPanResponder = ( ) => { return ( < Animated . View style = { [ styles . scrollMain , panelStyle , { top : animatedTop , borderRadius : radius } ] } { ... ( panResponder && panResponder . panHandlers ? panResponder . panHandlers : { } ) } onLayout = { panResponderOnLayout } > < View style = { [ styles . tabMain , { paddingHorizontal : listPaddingHorizontal } ] } onLayout = { tabsOnLayout } > < TouchableOpacity style = { [ styles . tabMainIcon , { backgroundColor : releaseTop !== highestPosition ? 'rgb(235,235,235)' : 'rgba(0,0,0,0)' , } , ] } /> { typeof customRenderTabBar === 'function' ? ( customRenderTabBar ( ) ) : ( < TabBar tabs = { tabs } activeKey = { chosenTab } onChange = { handleTabChange } type = "radio" style = { [ styles . tabBar , tabBarStyle ] } { ... tabBarProps } />)} < /View> { typeof customRenderList === 'function' ? customRenderList ( ) : renderDeviceList ( ) } { React . isValidElement ( panelChildren ) && panelChildren } < /Animated.View>); } ; const handleTabChange = ( value ) => { setChosenTab ( value ) ; typeof onTabChange === 'function' && onTabChange ( value ) ; } ; const panResponderOnLayout = ( e ) => { const { height } = e . nativeEvent . layout ; panelHeight . current = height ; setListHeight ( panelHeight . current - tabsHeight . current - panelChildrenHeight ) ; } ; const tabsOnLayout = ( e ) => { const { height } = e . nativeEvent . layout ; tabsHeight . current = height ; setListHeight ( panelHeight . current - tabsHeight . current - panelChildrenHeight ) ; } ; const handleCarouselChange = ( val ) => { handleTabChange ( tabs [ val ] . key ) ; } ; const renderDeviceList = ( ) => { if ( ! swipeable ) { return renderList ( 1 ) ; } return ( < Carousel style = { { height : listHeight } } hasDots = { false } selectedIndex = { selectedIndex } useViewPagerOnAndroid = { false } carouselChange = { handleCarouselChange } > { tabs . map ( d => { return renderList ( d . key ) ; } ) } < /Carousel>); } ; const renderList = key => { return ( < FlatList key = { key } style = { styles . list } showsVerticalScrollIndicator = { false } initialNumToRender = { 20 } numColumns = { 2 } contentContainerStyle = { styles . listContent } columnWrapperStyle = { styles . listColumnWrapper } onScroll = { onScroll } data = { dataSource } keyExtractor = { item => item . devId } renderItem = { typeof customRenderItem === 'function' ? customRenderItem : renderItem } ListEmptyComponent = { ListEmptyComponent } scrollEnabled = { releaseTop === highestPosition } { ... flatListProps } />); } ; return ( < View style = { [ styles . container , containerStyle ] } > { React . isValidElement ( children ) && children } { isShowPullDown && ( typeof customRenderPullDown === 'function' ? customRenderPullDown ( ) : renderPullDown ( ) ) } { renderPanResponder ( ) } < /View>); } ; DeviceListPanel . defaultProps = { dataSource : [ ] , highestPosition : 0 , initialPosition : winHeight / 2 , autoShrinkDistance : 50 , isShowIconMore : true , swipeable : false , isShowPullDown : true , panelChildrenHeight : 0 , } ; export default DeviceListPanel ;