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'; 5*13cebe63S猫头猫import { 6*13cebe63S猫头猫 FlatList, 7*13cebe63S猫头猫 Gesture, 8*13cebe63S猫头猫 GestureDetector, 9*13cebe63S猫头猫 TapGestureHandler, 10*13cebe63S猫头猫} from 'react-native-gesture-handler'; 118d82ecd9S猫头猫import {fontSizeConst} from '@/constants/uiConst'; 128d82ecd9S猫头猫import {IconButtonWithGesture} from '@/components/base/iconButton'; 138d82ecd9S猫头猫import Loading from '@/components/base/loading'; 148d82ecd9S猫头猫import globalStyle from '@/constants/globalStyle'; 158d82ecd9S猫头猫import {showPanel} from '@/components/panels/usePanel'; 168d82ecd9S猫头猫import LyricManager from '@/core/lyricManager'; 178d82ecd9S猫头猫import TrackPlayer from '@/core/trackPlayer'; 188d82ecd9S猫头猫import {musicIsPaused} from '@/utils/trackUtils'; 198d82ecd9S猫头猫import delay from '@/utils/delay'; 208d82ecd9S猫头猫import DraggingTime from './draggingTime'; 218d82ecd9S猫头猫import LyricItemComponent from './lyricItem'; 22*13cebe63S猫头猫import PersistStatus from '@/core/persistStatus'; 23*13cebe63S猫头猫import LyricOperations from './lyricOperations'; 248d82ecd9S猫头猫 258d82ecd9S猫头猫const ITEM_HEIGHT = rpx(92); 268d82ecd9S猫头猫 278d82ecd9S猫头猫interface IItemHeights { 288d82ecd9S猫头猫 blankHeight?: number; 298d82ecd9S猫头猫 [k: number]: number; 308d82ecd9S猫头猫} 318d82ecd9S猫头猫 32*13cebe63S猫头猫interface IProps { 33*13cebe63S猫头猫 onTurnPageClick?: () => void; 34*13cebe63S猫头猫} 35*13cebe63S猫头猫 36*13cebe63S猫头猫export default function Lyric(props: IProps) { 37*13cebe63S猫头猫 const {onTurnPageClick} = props; 38*13cebe63S猫头猫 397e883dbbS猫头猫 const {loading, meta, lyrics, translationLyrics, hasTranslation} = 407e883dbbS猫头猫 LyricManager.useLyricState(); 418d82ecd9S猫头猫 const currentLrcItem = LyricManager.useCurrentLyric(); 42*13cebe63S猫头猫 const showTranslation = PersistStatus.useValue( 43*13cebe63S猫头猫 'lyric.showTranslation', 44*13cebe63S猫头猫 false, 45*13cebe63S猫头猫 ); 468d82ecd9S猫头猫 478d82ecd9S猫头猫 // 是否展示拖拽 488d82ecd9S猫头猫 const dragShownRef = useRef(false); 498d82ecd9S猫头猫 const [draggingIndex, setDraggingIndex, setDraggingIndexImmi] = 508d82ecd9S猫头猫 useDelayFalsy<number | undefined>(undefined, 2000); 518d82ecd9S猫头猫 const listRef = useRef<FlatList<ILyric.IParsedLrcItem> | null>(); 528d82ecd9S猫头猫 const musicState = TrackPlayer.useMusicState(); 538d82ecd9S猫头猫 548d82ecd9S猫头猫 const [layout, setLayout] = useState<LayoutRectangle>(); 55*13cebe63S猫头猫 const isMountedRef = useRef(true); 568d82ecd9S猫头猫 578d82ecd9S猫头猫 // 用来缓存高度 588d82ecd9S猫头猫 const itemHeightsRef = useRef<IItemHeights>({}); 598d82ecd9S猫头猫 608d82ecd9S猫头猫 // 设置空白组件,获取组件高度 618d82ecd9S猫头猫 const blankComponent = useMemo(() => { 628d82ecd9S猫头猫 return ( 638d82ecd9S猫头猫 <View 647aed04d4S猫头猫 style={styles.empty} 658d82ecd9S猫头猫 onLayout={evt => { 668d82ecd9S猫头猫 itemHeightsRef.current.blankHeight = 678d82ecd9S猫头猫 evt.nativeEvent.layout.height; 688d82ecd9S猫头猫 }} 698d82ecd9S猫头猫 /> 708d82ecd9S猫头猫 ); 718d82ecd9S猫头猫 }, []); 728d82ecd9S猫头猫 738d82ecd9S猫头猫 const handleLyricItemLayout = useCallback( 748d82ecd9S猫头猫 (index: number, height: number) => { 758d82ecd9S猫头猫 itemHeightsRef.current[index] = height; 768d82ecd9S猫头猫 }, 778d82ecd9S猫头猫 [], 788d82ecd9S猫头猫 ); 798d82ecd9S猫头猫 80*13cebe63S猫头猫 // 滚到当前item 81*13cebe63S猫头猫 const scrollToCurrentLrcItem = useCallback(() => { 82*13cebe63S猫头猫 if (!listRef.current) { 83*13cebe63S猫头猫 return; 84*13cebe63S猫头猫 } 85*13cebe63S猫头猫 const currentLrcItem = LyricManager.getCurrentLyric(); 86*13cebe63S猫头猫 const lyrics = LyricManager.getLyricState().lyrics; 87*13cebe63S猫头猫 if (currentLrcItem?.index === -1 || !currentLrcItem) { 88*13cebe63S猫头猫 listRef.current?.scrollToIndex({ 89*13cebe63S猫头猫 index: 0, 90*13cebe63S猫头猫 viewPosition: 0.5, 91*13cebe63S猫头猫 }); 92*13cebe63S猫头猫 } else { 93*13cebe63S猫头猫 listRef.current?.scrollToIndex({ 94*13cebe63S猫头猫 index: Math.min(currentLrcItem.index ?? 0, lyrics.length - 1), 95*13cebe63S猫头猫 viewPosition: 0.5, 96*13cebe63S猫头猫 }); 97*13cebe63S猫头猫 } 98*13cebe63S猫头猫 }, []); 99*13cebe63S猫头猫 100*13cebe63S猫头猫 const delayedScrollToCurrentLrcItem = useMemo(() => { 101*13cebe63S猫头猫 let sto: number; 102*13cebe63S猫头猫 103*13cebe63S猫头猫 return () => { 104*13cebe63S猫头猫 if (sto) { 105*13cebe63S猫头猫 clearTimeout(sto); 106*13cebe63S猫头猫 } 107*13cebe63S猫头猫 sto = setTimeout(() => { 108*13cebe63S猫头猫 if (isMountedRef.current) { 109*13cebe63S猫头猫 scrollToCurrentLrcItem(); 110*13cebe63S猫头猫 } 111*13cebe63S猫头猫 }, 200) as any; 112*13cebe63S猫头猫 }; 113*13cebe63S猫头猫 }, []); 114*13cebe63S猫头猫 1158d82ecd9S猫头猫 useEffect(() => { 1168d82ecd9S猫头猫 // 暂停且拖拽才返回 1178d82ecd9S猫头猫 if ( 1187e883dbbS猫头猫 lyrics.length === 0 || 1198d82ecd9S猫头猫 draggingIndex !== undefined || 1208d82ecd9S猫头猫 (draggingIndex === undefined && musicIsPaused(musicState)) || 1217e883dbbS猫头猫 lyrics[lyrics.length - 1].time < 1 1228d82ecd9S猫头猫 ) { 1238d82ecd9S猫头猫 return; 1248d82ecd9S猫头猫 } 1258d82ecd9S猫头猫 if (currentLrcItem?.index === -1 || !currentLrcItem) { 1268d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 1278d82ecd9S猫头猫 index: 0, 1288d82ecd9S猫头猫 viewPosition: 0.5, 1298d82ecd9S猫头猫 }); 1308d82ecd9S猫头猫 } else { 1318d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 1327e883dbbS猫头猫 index: Math.min(currentLrcItem.index ?? 0, lyrics.length - 1), 1338d82ecd9S猫头猫 viewPosition: 0.5, 1348d82ecd9S猫头猫 }); 1358d82ecd9S猫头猫 } 1368d82ecd9S猫头猫 // 音乐暂停状态不应该影响到滑动,所以不放在依赖里,但是这样写不好。。 1377e883dbbS猫头猫 }, [currentLrcItem, lyrics, draggingIndex]); 1388d82ecd9S猫头猫 139a48a4bb0S猫头猫 useEffect(() => { 140*13cebe63S猫头猫 scrollToCurrentLrcItem(); 141*13cebe63S猫头猫 return () => { 142*13cebe63S猫头猫 isMountedRef.current = false; 143*13cebe63S猫头猫 }; 144a48a4bb0S猫头猫 }, []); 145a48a4bb0S猫头猫 1468d82ecd9S猫头猫 // 开始滚动时拖拽生效 1478d82ecd9S猫头猫 const onScrollBeginDrag = () => { 1488d82ecd9S猫头猫 dragShownRef.current = true; 1498d82ecd9S猫头猫 }; 1508d82ecd9S猫头猫 1518d82ecd9S猫头猫 const onScrollEndDrag = async () => { 1528d82ecd9S猫头猫 if (draggingIndex !== undefined) { 1538d82ecd9S猫头猫 setDraggingIndex(undefined); 1548d82ecd9S猫头猫 } 1558d82ecd9S猫头猫 dragShownRef.current = false; 1568d82ecd9S猫头猫 }; 1578d82ecd9S猫头猫 1588d82ecd9S猫头猫 const onScroll = (e: any) => { 1598d82ecd9S猫头猫 if (dragShownRef.current) { 1608d82ecd9S猫头猫 const offset = 1618d82ecd9S猫头猫 e.nativeEvent.contentOffset.y + 1628d82ecd9S猫头猫 e.nativeEvent.layoutMeasurement.height / 2; 1638d82ecd9S猫头猫 1648d82ecd9S猫头猫 const itemHeights = itemHeightsRef.current; 1658d82ecd9S猫头猫 let height = itemHeights.blankHeight!; 1668d82ecd9S猫头猫 if (offset <= height) { 1678d82ecd9S猫头猫 setDraggingIndex(0); 1688d82ecd9S猫头猫 return; 1698d82ecd9S猫头猫 } 1707e883dbbS猫头猫 for (let i = 0; i < lyrics.length; ++i) { 1718d82ecd9S猫头猫 height += itemHeights[i] ?? 0; 1728d82ecd9S猫头猫 if (height > offset) { 1738d82ecd9S猫头猫 setDraggingIndex(i); 1748d82ecd9S猫头猫 return; 1758d82ecd9S猫头猫 } 1768d82ecd9S猫头猫 } 1778d82ecd9S猫头猫 } 1788d82ecd9S猫头猫 }; 1798d82ecd9S猫头猫 1808d82ecd9S猫头猫 const onLyricSeekPress = async () => { 1818d82ecd9S猫头猫 if (draggingIndex !== undefined) { 1827e883dbbS猫头猫 const time = lyrics[draggingIndex].time + +(meta?.offset ?? 0); 1838d82ecd9S猫头猫 if (time !== undefined && !isNaN(time)) { 1848d82ecd9S猫头猫 await TrackPlayer.seekTo(time); 1858d82ecd9S猫头猫 await TrackPlayer.play(); 1868d82ecd9S猫头猫 setDraggingIndexImmi(undefined); 1878d82ecd9S猫头猫 } 1888d82ecd9S猫头猫 } 1898d82ecd9S猫头猫 }; 1908d82ecd9S猫头猫 191*13cebe63S猫头猫 const tap = Gesture.Tap() 192*13cebe63S猫头猫 .onStart(() => { 193*13cebe63S猫头猫 onTurnPageClick?.(); 194*13cebe63S猫头猫 }) 195*13cebe63S猫头猫 .runOnJS(true); 196*13cebe63S猫头猫 1978d82ecd9S猫头猫 return ( 198*13cebe63S猫头猫 <> 199*13cebe63S猫头猫 <GestureDetector gesture={tap}> 2008d82ecd9S猫头猫 <View style={globalStyle.fwflex1}> 2018d82ecd9S猫头猫 {loading ? ( 2028d82ecd9S猫头猫 <Loading color="white" /> 2037e883dbbS猫头猫 ) : lyrics?.length ? ( 2048d82ecd9S猫头猫 <FlatList 2058d82ecd9S猫头猫 ref={_ => { 2068d82ecd9S猫头猫 listRef.current = _; 2078d82ecd9S猫头猫 }} 2088d82ecd9S猫头猫 onLayout={e => { 2098d82ecd9S猫头猫 setLayout(e.nativeEvent.layout); 2108d82ecd9S猫头猫 }} 2118d82ecd9S猫头猫 viewabilityConfig={{ 2128d82ecd9S猫头猫 itemVisiblePercentThreshold: 100, 2138d82ecd9S猫头猫 }} 2148d82ecd9S猫头猫 onScrollToIndexFailed={({index}) => { 2158d82ecd9S猫头猫 delay(120).then(() => { 2168d82ecd9S猫头猫 listRef.current?.scrollToIndex({ 217*13cebe63S猫头猫 index: Math.min( 218*13cebe63S猫头猫 index ?? 0, 219*13cebe63S猫头猫 lyrics.length - 1, 220*13cebe63S猫头猫 ), 2218d82ecd9S猫头猫 viewPosition: 0.5, 2228d82ecd9S猫头猫 }); 2238d82ecd9S猫头猫 }); 2248d82ecd9S猫头猫 }} 2258d82ecd9S猫头猫 fadingEdgeLength={120} 2268d82ecd9S猫头猫 ListHeaderComponent={blankComponent} 2278d82ecd9S猫头猫 ListFooterComponent={blankComponent} 2288d82ecd9S猫头猫 onScrollBeginDrag={onScrollBeginDrag} 2298d82ecd9S猫头猫 onMomentumScrollEnd={onScrollEndDrag} 2308d82ecd9S猫头猫 onScroll={onScroll} 2318d82ecd9S猫头猫 scrollEventThrottle={32} 2327aed04d4S猫头猫 style={styles.wrapper} 2337e883dbbS猫头猫 data={lyrics} 2348d82ecd9S猫头猫 initialNumToRender={30} 2358d82ecd9S猫头猫 overScrollMode="never" 2368d82ecd9S猫头猫 extraData={currentLrcItem} 2377e883dbbS猫头猫 renderItem={({item, index}) => { 2387e883dbbS猫头猫 let text = item.lrc; 2397e883dbbS猫头猫 2407e883dbbS猫头猫 if (showTranslation && hasTranslation) { 241*13cebe63S猫头猫 const transLrc = 242*13cebe63S猫头猫 translationLyrics?.[index]?.lrc; 2437e883dbbS猫头猫 if (transLrc) { 2447e883dbbS猫头猫 text += `\n${transLrc}`; 2457e883dbbS猫头猫 } 2467e883dbbS猫头猫 } 2477e883dbbS猫头猫 2487e883dbbS猫头猫 return ( 2498d82ecd9S猫头猫 <LyricItemComponent 2508d82ecd9S猫头猫 index={index} 2517e883dbbS猫头猫 text={text} 2528d82ecd9S猫头猫 onLayout={handleLyricItemLayout} 2538d82ecd9S猫头猫 light={draggingIndex === index} 254*13cebe63S猫头猫 highlight={ 255*13cebe63S猫头猫 currentLrcItem?.index === index 256*13cebe63S猫头猫 } 2578d82ecd9S猫头猫 /> 2587e883dbbS猫头猫 ); 2597e883dbbS猫头猫 }} 2608d82ecd9S猫头猫 /> 2618d82ecd9S猫头猫 ) : ( 2628d82ecd9S猫头猫 <View style={globalStyle.fullCenter}> 2637aed04d4S猫头猫 <Text style={styles.white}>暂无歌词</Text> 2648d82ecd9S猫头猫 <TapGestureHandler 2658d82ecd9S猫头猫 onActivated={() => { 2668d82ecd9S猫头猫 showPanel('SearchLrc', { 267*13cebe63S猫头猫 musicItem: 268*13cebe63S猫头猫 TrackPlayer.getCurrentMusic(), 2698d82ecd9S猫头猫 }); 2708d82ecd9S猫头猫 }}> 2717aed04d4S猫头猫 <Text style={styles.searchLyric}>搜索歌词</Text> 2728d82ecd9S猫头猫 </TapGestureHandler> 2738d82ecd9S猫头猫 </View> 2748d82ecd9S猫头猫 )} 2758d82ecd9S猫头猫 {draggingIndex !== undefined && ( 2768d82ecd9S猫头猫 <View 2778d82ecd9S猫头猫 style={[ 2787aed04d4S猫头猫 styles.draggingTime, 2798d82ecd9S猫头猫 layout?.height 2808d82ecd9S猫头猫 ? { 281*13cebe63S猫头猫 top: 282*13cebe63S猫头猫 (layout.height - ITEM_HEIGHT) / 2, 2838d82ecd9S猫头猫 } 2848d82ecd9S猫头猫 : null, 2858d82ecd9S猫头猫 ]}> 2868d82ecd9S猫头猫 <DraggingTime 2878d82ecd9S猫头猫 time={ 2887e883dbbS猫头猫 (lyrics[draggingIndex]?.time ?? 0) + 2898d82ecd9S猫头猫 +(meta?.offset ?? 0) 2908d82ecd9S猫头猫 } 2918d82ecd9S猫头猫 /> 2927aed04d4S猫头猫 <View style={styles.singleLine} /> 2938d82ecd9S猫头猫 2948d82ecd9S猫头猫 <IconButtonWithGesture 2957aed04d4S猫头猫 style={styles.playIcon} 2968d82ecd9S猫头猫 sizeType="small" 2978d82ecd9S猫头猫 name="play" 2988d82ecd9S猫头猫 onPress={onLyricSeekPress} 2998d82ecd9S猫头猫 /> 3008d82ecd9S猫头猫 </View> 3018d82ecd9S猫头猫 )} 3028d82ecd9S猫头猫 </View> 303*13cebe63S猫头猫 </GestureDetector> 304*13cebe63S猫头猫 <LyricOperations 305*13cebe63S猫头猫 scrollToCurrentLrcItem={delayedScrollToCurrentLrcItem} 306*13cebe63S猫头猫 /> 307*13cebe63S猫头猫 </> 3088d82ecd9S猫头猫 ); 3098d82ecd9S猫头猫} 3108d82ecd9S猫头猫 3117aed04d4S猫头猫const styles = StyleSheet.create({ 3128d82ecd9S猫头猫 wrapper: { 3138d82ecd9S猫头猫 width: '100%', 3148d82ecd9S猫头猫 marginVertical: rpx(48), 3158d82ecd9S猫头猫 flex: 1, 3168d82ecd9S猫头猫 }, 3178d82ecd9S猫头猫 empty: { 3188d82ecd9S猫头猫 paddingTop: '70%', 3198d82ecd9S猫头猫 }, 3208d82ecd9S猫头猫 white: { 3218d82ecd9S猫头猫 color: 'white', 3228d82ecd9S猫头猫 }, 3238d82ecd9S猫头猫 draggingTime: { 3248d82ecd9S猫头猫 position: 'absolute', 3258d82ecd9S猫头猫 width: '100%', 3268d82ecd9S猫头猫 height: ITEM_HEIGHT, 3278d82ecd9S猫头猫 top: '40%', 3288d82ecd9S猫头猫 marginTop: rpx(48), 3298d82ecd9S猫头猫 paddingHorizontal: rpx(18), 3308d82ecd9S猫头猫 right: 0, 3318d82ecd9S猫头猫 flexDirection: 'row', 3328d82ecd9S猫头猫 alignItems: 'center', 3338d82ecd9S猫头猫 justifyContent: 'space-between', 3348d82ecd9S猫头猫 }, 3358d82ecd9S猫头猫 draggingTimeText: { 3368d82ecd9S猫头猫 color: '#dddddd', 3378d82ecd9S猫头猫 fontSize: fontSizeConst.description, 3388d82ecd9S猫头猫 width: rpx(90), 3398d82ecd9S猫头猫 }, 3408d82ecd9S猫头猫 singleLine: { 3418d82ecd9S猫头猫 width: '67%', 3428d82ecd9S猫头猫 height: 1, 3438d82ecd9S猫头猫 backgroundColor: '#cccccc', 3448d82ecd9S猫头猫 opacity: 0.4, 3458d82ecd9S猫头猫 }, 3468d82ecd9S猫头猫 playIcon: { 3478d82ecd9S猫头猫 width: rpx(90), 3488d82ecd9S猫头猫 textAlign: 'right', 3498d82ecd9S猫头猫 color: 'white', 3508d82ecd9S猫头猫 }, 3518d82ecd9S猫头猫 searchLyric: { 3528d82ecd9S猫头猫 width: rpx(180), 3538d82ecd9S猫头猫 marginTop: rpx(14), 3548d82ecd9S猫头猫 paddingVertical: rpx(10), 3558d82ecd9S猫头猫 textAlign: 'center', 3568d82ecd9S猫头猫 alignSelf: 'center', 3578d82ecd9S猫头猫 color: '#66eeff', 3588d82ecd9S猫头猫 textDecorationLine: 'underline', 3598d82ecd9S猫头猫 }, 3608d82ecd9S猫头猫}); 361