xref: /MusicFree/src/pages/musicDetail/components/content/lyric/index.tsx (revision 7aed04d4f1a71bc87732a88b0583e6ae702976b2)
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