18d82ecd9S猫头猫import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; 28d82ecd9S猫头猫import {LayoutRectangle, StyleSheet, Text, View} from 'react-native'; 38d82ecd9S猫头猫import rpx from '@/utils/rpx'; 48d82ecd9S猫头猫import useDelayFalsy from '@/hooks/useDelayFalsy'; 58d82ecd9S猫头猫import {FlatList, TapGestureHandler} from 'react-native-gesture-handler'; 68d82ecd9S猫头猫import {fontSizeConst} from '@/constants/uiConst'; 78d82ecd9S猫头猫import {IconButtonWithGesture} from '@/components/base/iconButton'; 88d82ecd9S猫头猫import Loading from '@/components/base/loading'; 98d82ecd9S猫头猫import globalStyle from '@/constants/globalStyle'; 108d82ecd9S猫头猫import {showPanel} from '@/components/panels/usePanel'; 118d82ecd9S猫头猫import LyricManager from '@/core/lyricManager'; 128d82ecd9S猫头猫import TrackPlayer from '@/core/trackPlayer'; 138d82ecd9S猫头猫import {musicIsPaused} from '@/utils/trackUtils'; 148d82ecd9S猫头猫import delay from '@/utils/delay'; 158d82ecd9S猫头猫import DraggingTime from './draggingTime'; 168d82ecd9S猫头猫import LyricItemComponent from './lyricItem'; 178d82ecd9S猫头猫 188d82ecd9S猫头猫const ITEM_HEIGHT = rpx(92); 198d82ecd9S猫头猫 208d82ecd9S猫头猫interface IItemHeights { 218d82ecd9S猫头猫 blankHeight?: number; 228d82ecd9S猫头猫 [k: number]: number; 238d82ecd9S猫头猫} 248d82ecd9S猫头猫 258d82ecd9S猫头猫export default function Lyric() { 268d82ecd9S猫头猫 const {loading, meta, lyrics: lyric} = LyricManager.useLyricState(); 278d82ecd9S猫头猫 const currentLrcItem = LyricManager.useCurrentLyric(); 288d82ecd9S猫头猫 298d82ecd9S猫头猫 // 是否展示拖拽 308d82ecd9S猫头猫 const dragShownRef = useRef(false); 318d82ecd9S猫头猫 const [draggingIndex, setDraggingIndex, setDraggingIndexImmi] = 328d82ecd9S猫头猫 useDelayFalsy<number | undefined>(undefined, 2000); 338d82ecd9S猫头猫 const listRef = useRef<FlatList<ILyric.IParsedLrcItem> | null>(); 348d82ecd9S猫头猫 const musicState = TrackPlayer.useMusicState(); 358d82ecd9S猫头猫 368d82ecd9S猫头猫 const [layout, setLayout] = useState<LayoutRectangle>(); 378d82ecd9S猫头猫 388d82ecd9S猫头猫 // 用来缓存高度 398d82ecd9S猫头猫 const itemHeightsRef = useRef<IItemHeights>({}); 408d82ecd9S猫头猫 418d82ecd9S猫头猫 // 设置空白组件,获取组件高度 428d82ecd9S猫头猫 const blankComponent = useMemo(() => { 438d82ecd9S猫头猫 return ( 448d82ecd9S猫头猫 <View 45*7aed04d4S猫头猫 style={styles.empty} 468d82ecd9S猫头猫 onLayout={evt => { 478d82ecd9S猫头猫 itemHeightsRef.current.blankHeight = 488d82ecd9S猫头猫 evt.nativeEvent.layout.height; 498d82ecd9S猫头猫 }} 508d82ecd9S猫头猫 /> 518d82ecd9S猫头猫 ); 528d82ecd9S猫头猫 }, []); 538d82ecd9S猫头猫 548d82ecd9S猫头猫 const handleLyricItemLayout = useCallback( 558d82ecd9S猫头猫 (index: number, height: number) => { 568d82ecd9S猫头猫 itemHeightsRef.current[index] = height; 578d82ecd9S猫头猫 }, 588d82ecd9S猫头猫 [], 598d82ecd9S猫头猫 ); 608d82ecd9S猫头猫 618d82ecd9S猫头猫 useEffect(() => { 628d82ecd9S猫头猫 // 暂停且拖拽才返回 638d82ecd9S猫头猫 if ( 648d82ecd9S猫头猫 lyric.length === 0 || 658d82ecd9S猫头猫 draggingIndex !== undefined || 668d82ecd9S猫头猫 (draggingIndex === undefined && musicIsPaused(musicState)) || 678d82ecd9S猫头猫 lyric[lyric.length - 1].time < 1 688d82ecd9S猫头猫 ) { 698d82ecd9S猫头猫 return; 708d82ecd9S猫头猫 } 718d82ecd9S猫头猫 if (currentLrcItem?.index === -1 || !currentLrcItem) { 728d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 738d82ecd9S猫头猫 index: 0, 748d82ecd9S猫头猫 viewPosition: 0.5, 758d82ecd9S猫头猫 }); 768d82ecd9S猫头猫 } else { 778d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 788d82ecd9S猫头猫 index: Math.min(currentLrcItem.index ?? 0, lyric.length - 1), 798d82ecd9S猫头猫 viewPosition: 0.5, 808d82ecd9S猫头猫 }); 818d82ecd9S猫头猫 } 828d82ecd9S猫头猫 // 音乐暂停状态不应该影响到滑动,所以不放在依赖里,但是这样写不好。。 838d82ecd9S猫头猫 }, [currentLrcItem, lyric, draggingIndex]); 848d82ecd9S猫头猫 85a48a4bb0S猫头猫 useEffect(() => { 86a48a4bb0S猫头猫 if (currentLrcItem?.index !== undefined && currentLrcItem.index > -1) { 87a48a4bb0S猫头猫 listRef.current?.scrollToIndex({ 88a48a4bb0S猫头猫 index: currentLrcItem.index, 89a48a4bb0S猫头猫 viewPosition: 0.5, 90a48a4bb0S猫头猫 animated: false, 91a48a4bb0S猫头猫 }); 92a48a4bb0S猫头猫 } 93a48a4bb0S猫头猫 }, []); 94a48a4bb0S猫头猫 958d82ecd9S猫头猫 // 开始滚动时拖拽生效 968d82ecd9S猫头猫 const onScrollBeginDrag = () => { 978d82ecd9S猫头猫 dragShownRef.current = true; 988d82ecd9S猫头猫 }; 998d82ecd9S猫头猫 1008d82ecd9S猫头猫 const onScrollEndDrag = async () => { 1018d82ecd9S猫头猫 if (draggingIndex !== undefined) { 1028d82ecd9S猫头猫 setDraggingIndex(undefined); 1038d82ecd9S猫头猫 } 1048d82ecd9S猫头猫 dragShownRef.current = false; 1058d82ecd9S猫头猫 }; 1068d82ecd9S猫头猫 1078d82ecd9S猫头猫 const onScroll = (e: any) => { 1088d82ecd9S猫头猫 if (dragShownRef.current) { 1098d82ecd9S猫头猫 const offset = 1108d82ecd9S猫头猫 e.nativeEvent.contentOffset.y + 1118d82ecd9S猫头猫 e.nativeEvent.layoutMeasurement.height / 2; 1128d82ecd9S猫头猫 1138d82ecd9S猫头猫 const itemHeights = itemHeightsRef.current; 1148d82ecd9S猫头猫 let height = itemHeights.blankHeight!; 1158d82ecd9S猫头猫 if (offset <= height) { 1168d82ecd9S猫头猫 setDraggingIndex(0); 1178d82ecd9S猫头猫 return; 1188d82ecd9S猫头猫 } 1198d82ecd9S猫头猫 for (let i = 0; i < lyric.length; ++i) { 1208d82ecd9S猫头猫 height += itemHeights[i] ?? 0; 1218d82ecd9S猫头猫 if (height > offset) { 1228d82ecd9S猫头猫 setDraggingIndex(i); 1238d82ecd9S猫头猫 return; 1248d82ecd9S猫头猫 } 1258d82ecd9S猫头猫 } 1268d82ecd9S猫头猫 } 1278d82ecd9S猫头猫 }; 1288d82ecd9S猫头猫 1298d82ecd9S猫头猫 const onLyricSeekPress = async () => { 1308d82ecd9S猫头猫 if (draggingIndex !== undefined) { 1318d82ecd9S猫头猫 const time = lyric[draggingIndex].time + +(meta?.offset ?? 0); 1328d82ecd9S猫头猫 if (time !== undefined && !isNaN(time)) { 1338d82ecd9S猫头猫 await TrackPlayer.seekTo(time); 1348d82ecd9S猫头猫 await TrackPlayer.play(); 1358d82ecd9S猫头猫 setDraggingIndexImmi(undefined); 1368d82ecd9S猫头猫 } 1378d82ecd9S猫头猫 } 1388d82ecd9S猫头猫 }; 1398d82ecd9S猫头猫 1408d82ecd9S猫头猫 return ( 1418d82ecd9S猫头猫 <View style={globalStyle.fwflex1}> 1428d82ecd9S猫头猫 {loading ? ( 1438d82ecd9S猫头猫 <Loading color="white" /> 1448d82ecd9S猫头猫 ) : lyric?.length ? ( 1458d82ecd9S猫头猫 <FlatList 1468d82ecd9S猫头猫 ref={_ => { 1478d82ecd9S猫头猫 listRef.current = _; 1488d82ecd9S猫头猫 }} 1498d82ecd9S猫头猫 onLayout={e => { 1508d82ecd9S猫头猫 setLayout(e.nativeEvent.layout); 1518d82ecd9S猫头猫 }} 1528d82ecd9S猫头猫 viewabilityConfig={{ 1538d82ecd9S猫头猫 itemVisiblePercentThreshold: 100, 1548d82ecd9S猫头猫 }} 1558d82ecd9S猫头猫 onScrollToIndexFailed={({index}) => { 1568d82ecd9S猫头猫 delay(120).then(() => { 1578d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 1588d82ecd9S猫头猫 index: Math.min(index ?? 0, lyric.length - 1), 1598d82ecd9S猫头猫 viewPosition: 0.5, 1608d82ecd9S猫头猫 }); 1618d82ecd9S猫头猫 }); 1628d82ecd9S猫头猫 }} 1638d82ecd9S猫头猫 fadingEdgeLength={120} 1648d82ecd9S猫头猫 ListHeaderComponent={blankComponent} 1658d82ecd9S猫头猫 ListFooterComponent={blankComponent} 1668d82ecd9S猫头猫 onScrollBeginDrag={onScrollBeginDrag} 1678d82ecd9S猫头猫 onMomentumScrollEnd={onScrollEndDrag} 1688d82ecd9S猫头猫 onScroll={onScroll} 1698d82ecd9S猫头猫 scrollEventThrottle={32} 170*7aed04d4S猫头猫 style={styles.wrapper} 1718d82ecd9S猫头猫 data={lyric} 1728d82ecd9S猫头猫 initialNumToRender={30} 1738d82ecd9S猫头猫 overScrollMode="never" 1748d82ecd9S猫头猫 extraData={currentLrcItem} 1758d82ecd9S猫头猫 renderItem={({item, index}) => ( 1768d82ecd9S猫头猫 <LyricItemComponent 1778d82ecd9S猫头猫 index={index} 1788d82ecd9S猫头猫 text={item.lrc} 1798d82ecd9S猫头猫 onLayout={handleLyricItemLayout} 1808d82ecd9S猫头猫 light={draggingIndex === index} 1818d82ecd9S猫头猫 highlight={currentLrcItem?.index === index} 1828d82ecd9S猫头猫 /> 1838d82ecd9S猫头猫 )} 1848d82ecd9S猫头猫 /> 1858d82ecd9S猫头猫 ) : ( 1868d82ecd9S猫头猫 <View style={globalStyle.fullCenter}> 187*7aed04d4S猫头猫 <Text style={styles.white}>暂无歌词</Text> 1888d82ecd9S猫头猫 <TapGestureHandler 1898d82ecd9S猫头猫 onActivated={() => { 1908d82ecd9S猫头猫 showPanel('SearchLrc', { 1918d82ecd9S猫头猫 musicItem: TrackPlayer.getCurrentMusic(), 1928d82ecd9S猫头猫 }); 1938d82ecd9S猫头猫 }}> 194*7aed04d4S猫头猫 <Text style={styles.searchLyric}>搜索歌词</Text> 1958d82ecd9S猫头猫 </TapGestureHandler> 1968d82ecd9S猫头猫 </View> 1978d82ecd9S猫头猫 )} 1988d82ecd9S猫头猫 {draggingIndex !== undefined && ( 1998d82ecd9S猫头猫 <View 2008d82ecd9S猫头猫 style={[ 201*7aed04d4S猫头猫 styles.draggingTime, 2028d82ecd9S猫头猫 layout?.height 2038d82ecd9S猫头猫 ? { 2048d82ecd9S猫头猫 top: (layout.height - ITEM_HEIGHT) / 2, 2058d82ecd9S猫头猫 } 2068d82ecd9S猫头猫 : null, 2078d82ecd9S猫头猫 ]}> 2088d82ecd9S猫头猫 <DraggingTime 2098d82ecd9S猫头猫 time={ 2108d82ecd9S猫头猫 (lyric[draggingIndex]?.time ?? 0) + 2118d82ecd9S猫头猫 +(meta?.offset ?? 0) 2128d82ecd9S猫头猫 } 2138d82ecd9S猫头猫 /> 214*7aed04d4S猫头猫 <View style={styles.singleLine} /> 2158d82ecd9S猫头猫 2168d82ecd9S猫头猫 <IconButtonWithGesture 217*7aed04d4S猫头猫 style={styles.playIcon} 2188d82ecd9S猫头猫 sizeType="small" 2198d82ecd9S猫头猫 name="play" 2208d82ecd9S猫头猫 onPress={onLyricSeekPress} 2218d82ecd9S猫头猫 /> 2228d82ecd9S猫头猫 </View> 2238d82ecd9S猫头猫 )} 2248d82ecd9S猫头猫 </View> 2258d82ecd9S猫头猫 ); 2268d82ecd9S猫头猫} 2278d82ecd9S猫头猫 228*7aed04d4S猫头猫const styles = StyleSheet.create({ 2298d82ecd9S猫头猫 wrapper: { 2308d82ecd9S猫头猫 width: '100%', 2318d82ecd9S猫头猫 marginVertical: rpx(48), 2328d82ecd9S猫头猫 flex: 1, 2338d82ecd9S猫头猫 }, 2348d82ecd9S猫头猫 empty: { 2358d82ecd9S猫头猫 paddingTop: '70%', 2368d82ecd9S猫头猫 }, 2378d82ecd9S猫头猫 white: { 2388d82ecd9S猫头猫 color: 'white', 2398d82ecd9S猫头猫 }, 2408d82ecd9S猫头猫 draggingTime: { 2418d82ecd9S猫头猫 position: 'absolute', 2428d82ecd9S猫头猫 width: '100%', 2438d82ecd9S猫头猫 height: ITEM_HEIGHT, 2448d82ecd9S猫头猫 top: '40%', 2458d82ecd9S猫头猫 marginTop: rpx(48), 2468d82ecd9S猫头猫 paddingHorizontal: rpx(18), 2478d82ecd9S猫头猫 right: 0, 2488d82ecd9S猫头猫 flexDirection: 'row', 2498d82ecd9S猫头猫 alignItems: 'center', 2508d82ecd9S猫头猫 justifyContent: 'space-between', 2518d82ecd9S猫头猫 }, 2528d82ecd9S猫头猫 draggingTimeText: { 2538d82ecd9S猫头猫 color: '#dddddd', 2548d82ecd9S猫头猫 fontSize: fontSizeConst.description, 2558d82ecd9S猫头猫 width: rpx(90), 2568d82ecd9S猫头猫 }, 2578d82ecd9S猫头猫 singleLine: { 2588d82ecd9S猫头猫 width: '67%', 2598d82ecd9S猫头猫 height: 1, 2608d82ecd9S猫头猫 backgroundColor: '#cccccc', 2618d82ecd9S猫头猫 opacity: 0.4, 2628d82ecd9S猫头猫 }, 2638d82ecd9S猫头猫 playIcon: { 2648d82ecd9S猫头猫 width: rpx(90), 2658d82ecd9S猫头猫 textAlign: 'right', 2668d82ecd9S猫头猫 color: 'white', 2678d82ecd9S猫头猫 }, 2688d82ecd9S猫头猫 searchLyric: { 2698d82ecd9S猫头猫 width: rpx(180), 2708d82ecd9S猫头猫 marginTop: rpx(14), 2718d82ecd9S猫头猫 paddingVertical: rpx(10), 2728d82ecd9S猫头猫 textAlign: 'center', 2738d82ecd9S猫头猫 alignSelf: 'center', 2748d82ecd9S猫头猫 color: '#66eeff', 2758d82ecd9S猫头猫 textDecorationLine: 'underline', 2768d82ecd9S猫头猫 }, 2778d82ecd9S猫头猫}); 278