xref: /MusicFree/src/pages/musicDetail/components/content/lyric/index.tsx (revision 8d82ecd9ebd88f4516b7a450e82025018c9ae12d)
1*8d82ecd9S猫头猫import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2*8d82ecd9S猫头猫import {LayoutRectangle, StyleSheet, Text, View} from 'react-native';
3*8d82ecd9S猫头猫import rpx from '@/utils/rpx';
4*8d82ecd9S猫头猫import useDelayFalsy from '@/hooks/useDelayFalsy';
5*8d82ecd9S猫头猫import {FlatList, TapGestureHandler} from 'react-native-gesture-handler';
6*8d82ecd9S猫头猫import {fontSizeConst} from '@/constants/uiConst';
7*8d82ecd9S猫头猫import {IconButtonWithGesture} from '@/components/base/iconButton';
8*8d82ecd9S猫头猫import Loading from '@/components/base/loading';
9*8d82ecd9S猫头猫import globalStyle from '@/constants/globalStyle';
10*8d82ecd9S猫头猫import {showPanel} from '@/components/panels/usePanel';
11*8d82ecd9S猫头猫import LyricManager from '@/core/lyricManager';
12*8d82ecd9S猫头猫import TrackPlayer from '@/core/trackPlayer';
13*8d82ecd9S猫头猫import {musicIsPaused} from '@/utils/trackUtils';
14*8d82ecd9S猫头猫import delay from '@/utils/delay';
15*8d82ecd9S猫头猫import DraggingTime from './draggingTime';
16*8d82ecd9S猫头猫import LyricItemComponent from './lyricItem';
17*8d82ecd9S猫头猫
18*8d82ecd9S猫头猫const ITEM_HEIGHT = rpx(92);
19*8d82ecd9S猫头猫
20*8d82ecd9S猫头猫interface IItemHeights {
21*8d82ecd9S猫头猫    blankHeight?: number;
22*8d82ecd9S猫头猫    [k: number]: number;
23*8d82ecd9S猫头猫}
24*8d82ecd9S猫头猫
25*8d82ecd9S猫头猫export default function Lyric() {
26*8d82ecd9S猫头猫    const {loading, meta, lyrics: lyric} = LyricManager.useLyricState();
27*8d82ecd9S猫头猫    const currentLrcItem = LyricManager.useCurrentLyric();
28*8d82ecd9S猫头猫
29*8d82ecd9S猫头猫    // 是否展示拖拽
30*8d82ecd9S猫头猫    const dragShownRef = useRef(false);
31*8d82ecd9S猫头猫    const [draggingIndex, setDraggingIndex, setDraggingIndexImmi] =
32*8d82ecd9S猫头猫        useDelayFalsy<number | undefined>(undefined, 2000);
33*8d82ecd9S猫头猫    const listRef = useRef<FlatList<ILyric.IParsedLrcItem> | null>();
34*8d82ecd9S猫头猫    const musicState = TrackPlayer.useMusicState();
35*8d82ecd9S猫头猫
36*8d82ecd9S猫头猫    const [layout, setLayout] = useState<LayoutRectangle>();
37*8d82ecd9S猫头猫
38*8d82ecd9S猫头猫    // 用来缓存高度
39*8d82ecd9S猫头猫    const itemHeightsRef = useRef<IItemHeights>({});
40*8d82ecd9S猫头猫
41*8d82ecd9S猫头猫    // 设置空白组件,获取组件高度
42*8d82ecd9S猫头猫    const blankComponent = useMemo(() => {
43*8d82ecd9S猫头猫        return (
44*8d82ecd9S猫头猫            <View
45*8d82ecd9S猫头猫                style={style.empty}
46*8d82ecd9S猫头猫                onLayout={evt => {
47*8d82ecd9S猫头猫                    itemHeightsRef.current.blankHeight =
48*8d82ecd9S猫头猫                        evt.nativeEvent.layout.height;
49*8d82ecd9S猫头猫                }}
50*8d82ecd9S猫头猫            />
51*8d82ecd9S猫头猫        );
52*8d82ecd9S猫头猫    }, []);
53*8d82ecd9S猫头猫
54*8d82ecd9S猫头猫    const handleLyricItemLayout = useCallback(
55*8d82ecd9S猫头猫        (index: number, height: number) => {
56*8d82ecd9S猫头猫            itemHeightsRef.current[index] = height;
57*8d82ecd9S猫头猫        },
58*8d82ecd9S猫头猫        [],
59*8d82ecd9S猫头猫    );
60*8d82ecd9S猫头猫
61*8d82ecd9S猫头猫    useEffect(() => {
62*8d82ecd9S猫头猫        // 暂停且拖拽才返回
63*8d82ecd9S猫头猫        if (
64*8d82ecd9S猫头猫            lyric.length === 0 ||
65*8d82ecd9S猫头猫            draggingIndex !== undefined ||
66*8d82ecd9S猫头猫            (draggingIndex === undefined && musicIsPaused(musicState)) ||
67*8d82ecd9S猫头猫            lyric[lyric.length - 1].time < 1
68*8d82ecd9S猫头猫        ) {
69*8d82ecd9S猫头猫            return;
70*8d82ecd9S猫头猫        }
71*8d82ecd9S猫头猫        if (currentLrcItem?.index === -1 || !currentLrcItem) {
72*8d82ecd9S猫头猫            listRef.current?.scrollToIndex({
73*8d82ecd9S猫头猫                index: 0,
74*8d82ecd9S猫头猫                viewPosition: 0.5,
75*8d82ecd9S猫头猫            });
76*8d82ecd9S猫头猫        } else {
77*8d82ecd9S猫头猫            listRef.current?.scrollToIndex({
78*8d82ecd9S猫头猫                index: Math.min(currentLrcItem.index ?? 0, lyric.length - 1),
79*8d82ecd9S猫头猫                viewPosition: 0.5,
80*8d82ecd9S猫头猫            });
81*8d82ecd9S猫头猫        }
82*8d82ecd9S猫头猫        // 音乐暂停状态不应该影响到滑动,所以不放在依赖里,但是这样写不好。。
83*8d82ecd9S猫头猫    }, [currentLrcItem, lyric, draggingIndex]);
84*8d82ecd9S猫头猫
85*8d82ecd9S猫头猫    // 开始滚动时拖拽生效
86*8d82ecd9S猫头猫    const onScrollBeginDrag = () => {
87*8d82ecd9S猫头猫        dragShownRef.current = true;
88*8d82ecd9S猫头猫    };
89*8d82ecd9S猫头猫
90*8d82ecd9S猫头猫    const onScrollEndDrag = async () => {
91*8d82ecd9S猫头猫        if (draggingIndex !== undefined) {
92*8d82ecd9S猫头猫            setDraggingIndex(undefined);
93*8d82ecd9S猫头猫        }
94*8d82ecd9S猫头猫        dragShownRef.current = false;
95*8d82ecd9S猫头猫    };
96*8d82ecd9S猫头猫
97*8d82ecd9S猫头猫    const onScroll = (e: any) => {
98*8d82ecd9S猫头猫        if (dragShownRef.current) {
99*8d82ecd9S猫头猫            const offset =
100*8d82ecd9S猫头猫                e.nativeEvent.contentOffset.y +
101*8d82ecd9S猫头猫                e.nativeEvent.layoutMeasurement.height / 2;
102*8d82ecd9S猫头猫
103*8d82ecd9S猫头猫            const itemHeights = itemHeightsRef.current;
104*8d82ecd9S猫头猫            let height = itemHeights.blankHeight!;
105*8d82ecd9S猫头猫            if (offset <= height) {
106*8d82ecd9S猫头猫                setDraggingIndex(0);
107*8d82ecd9S猫头猫                return;
108*8d82ecd9S猫头猫            }
109*8d82ecd9S猫头猫            for (let i = 0; i < lyric.length; ++i) {
110*8d82ecd9S猫头猫                height += itemHeights[i] ?? 0;
111*8d82ecd9S猫头猫                if (height > offset) {
112*8d82ecd9S猫头猫                    setDraggingIndex(i);
113*8d82ecd9S猫头猫                    return;
114*8d82ecd9S猫头猫                }
115*8d82ecd9S猫头猫            }
116*8d82ecd9S猫头猫        }
117*8d82ecd9S猫头猫    };
118*8d82ecd9S猫头猫
119*8d82ecd9S猫头猫    const onLyricSeekPress = async () => {
120*8d82ecd9S猫头猫        if (draggingIndex !== undefined) {
121*8d82ecd9S猫头猫            const time = lyric[draggingIndex].time + +(meta?.offset ?? 0);
122*8d82ecd9S猫头猫            if (time !== undefined && !isNaN(time)) {
123*8d82ecd9S猫头猫                await TrackPlayer.seekTo(time);
124*8d82ecd9S猫头猫                await TrackPlayer.play();
125*8d82ecd9S猫头猫                setDraggingIndexImmi(undefined);
126*8d82ecd9S猫头猫            }
127*8d82ecd9S猫头猫        }
128*8d82ecd9S猫头猫    };
129*8d82ecd9S猫头猫
130*8d82ecd9S猫头猫    console.log(draggingIndex, 'DD');
131*8d82ecd9S猫头猫
132*8d82ecd9S猫头猫    return (
133*8d82ecd9S猫头猫        <View style={globalStyle.fwflex1}>
134*8d82ecd9S猫头猫            {loading ? (
135*8d82ecd9S猫头猫                <Loading color="white" />
136*8d82ecd9S猫头猫            ) : lyric?.length ? (
137*8d82ecd9S猫头猫                <FlatList
138*8d82ecd9S猫头猫                    ref={_ => {
139*8d82ecd9S猫头猫                        listRef.current = _;
140*8d82ecd9S猫头猫                    }}
141*8d82ecd9S猫头猫                    onLayout={e => {
142*8d82ecd9S猫头猫                        setLayout(e.nativeEvent.layout);
143*8d82ecd9S猫头猫                    }}
144*8d82ecd9S猫头猫                    viewabilityConfig={{
145*8d82ecd9S猫头猫                        itemVisiblePercentThreshold: 100,
146*8d82ecd9S猫头猫                    }}
147*8d82ecd9S猫头猫                    onScrollToIndexFailed={({index}) => {
148*8d82ecd9S猫头猫                        delay(120).then(() => {
149*8d82ecd9S猫头猫                            listRef.current?.scrollToIndex({
150*8d82ecd9S猫头猫                                index: Math.min(index ?? 0, lyric.length - 1),
151*8d82ecd9S猫头猫                                viewPosition: 0.5,
152*8d82ecd9S猫头猫                            });
153*8d82ecd9S猫头猫                        });
154*8d82ecd9S猫头猫                    }}
155*8d82ecd9S猫头猫                    fadingEdgeLength={120}
156*8d82ecd9S猫头猫                    ListHeaderComponent={blankComponent}
157*8d82ecd9S猫头猫                    ListFooterComponent={blankComponent}
158*8d82ecd9S猫头猫                    onScrollBeginDrag={onScrollBeginDrag}
159*8d82ecd9S猫头猫                    onMomentumScrollEnd={onScrollEndDrag}
160*8d82ecd9S猫头猫                    onScroll={onScroll}
161*8d82ecd9S猫头猫                    scrollEventThrottle={32}
162*8d82ecd9S猫头猫                    style={style.wrapper}
163*8d82ecd9S猫头猫                    data={lyric}
164*8d82ecd9S猫头猫                    initialNumToRender={30}
165*8d82ecd9S猫头猫                    overScrollMode="never"
166*8d82ecd9S猫头猫                    extraData={currentLrcItem}
167*8d82ecd9S猫头猫                    renderItem={({item, index}) => (
168*8d82ecd9S猫头猫                        <LyricItemComponent
169*8d82ecd9S猫头猫                            index={index}
170*8d82ecd9S猫头猫                            text={item.lrc}
171*8d82ecd9S猫头猫                            onLayout={handleLyricItemLayout}
172*8d82ecd9S猫头猫                            light={draggingIndex === index}
173*8d82ecd9S猫头猫                            highlight={currentLrcItem?.index === index}
174*8d82ecd9S猫头猫                        />
175*8d82ecd9S猫头猫                    )}
176*8d82ecd9S猫头猫                />
177*8d82ecd9S猫头猫            ) : (
178*8d82ecd9S猫头猫                <View style={globalStyle.fullCenter}>
179*8d82ecd9S猫头猫                    <Text style={style.white}>暂无歌词</Text>
180*8d82ecd9S猫头猫                    <TapGestureHandler
181*8d82ecd9S猫头猫                        onActivated={() => {
182*8d82ecd9S猫头猫                            showPanel('SearchLrc', {
183*8d82ecd9S猫头猫                                musicItem: TrackPlayer.getCurrentMusic(),
184*8d82ecd9S猫头猫                            });
185*8d82ecd9S猫头猫                        }}>
186*8d82ecd9S猫头猫                        <Text style={style.searchLyric}>搜索歌词</Text>
187*8d82ecd9S猫头猫                    </TapGestureHandler>
188*8d82ecd9S猫头猫                </View>
189*8d82ecd9S猫头猫            )}
190*8d82ecd9S猫头猫            {draggingIndex !== undefined && (
191*8d82ecd9S猫头猫                <View
192*8d82ecd9S猫头猫                    style={[
193*8d82ecd9S猫头猫                        style.draggingTime,
194*8d82ecd9S猫头猫                        layout?.height
195*8d82ecd9S猫头猫                            ? {
196*8d82ecd9S猫头猫                                  top: (layout.height - ITEM_HEIGHT) / 2,
197*8d82ecd9S猫头猫                              }
198*8d82ecd9S猫头猫                            : null,
199*8d82ecd9S猫头猫                    ]}>
200*8d82ecd9S猫头猫                    <DraggingTime
201*8d82ecd9S猫头猫                        time={
202*8d82ecd9S猫头猫                            (lyric[draggingIndex]?.time ?? 0) +
203*8d82ecd9S猫头猫                            +(meta?.offset ?? 0)
204*8d82ecd9S猫头猫                        }
205*8d82ecd9S猫头猫                    />
206*8d82ecd9S猫头猫                    <View style={style.singleLine} />
207*8d82ecd9S猫头猫
208*8d82ecd9S猫头猫                    <IconButtonWithGesture
209*8d82ecd9S猫头猫                        style={style.playIcon}
210*8d82ecd9S猫头猫                        sizeType="small"
211*8d82ecd9S猫头猫                        name="play"
212*8d82ecd9S猫头猫                        onPress={onLyricSeekPress}
213*8d82ecd9S猫头猫                    />
214*8d82ecd9S猫头猫                </View>
215*8d82ecd9S猫头猫            )}
216*8d82ecd9S猫头猫        </View>
217*8d82ecd9S猫头猫    );
218*8d82ecd9S猫头猫}
219*8d82ecd9S猫头猫
220*8d82ecd9S猫头猫const style = StyleSheet.create({
221*8d82ecd9S猫头猫    wrapper: {
222*8d82ecd9S猫头猫        width: '100%',
223*8d82ecd9S猫头猫        marginVertical: rpx(48),
224*8d82ecd9S猫头猫        flex: 1,
225*8d82ecd9S猫头猫    },
226*8d82ecd9S猫头猫    empty: {
227*8d82ecd9S猫头猫        paddingTop: '70%',
228*8d82ecd9S猫头猫    },
229*8d82ecd9S猫头猫    white: {
230*8d82ecd9S猫头猫        color: 'white',
231*8d82ecd9S猫头猫    },
232*8d82ecd9S猫头猫    draggingTime: {
233*8d82ecd9S猫头猫        position: 'absolute',
234*8d82ecd9S猫头猫        width: '100%',
235*8d82ecd9S猫头猫        height: ITEM_HEIGHT,
236*8d82ecd9S猫头猫        top: '40%',
237*8d82ecd9S猫头猫        marginTop: rpx(48),
238*8d82ecd9S猫头猫        paddingHorizontal: rpx(18),
239*8d82ecd9S猫头猫        right: 0,
240*8d82ecd9S猫头猫        flexDirection: 'row',
241*8d82ecd9S猫头猫        alignItems: 'center',
242*8d82ecd9S猫头猫        justifyContent: 'space-between',
243*8d82ecd9S猫头猫    },
244*8d82ecd9S猫头猫    draggingTimeText: {
245*8d82ecd9S猫头猫        color: '#dddddd',
246*8d82ecd9S猫头猫        fontSize: fontSizeConst.description,
247*8d82ecd9S猫头猫        width: rpx(90),
248*8d82ecd9S猫头猫    },
249*8d82ecd9S猫头猫    singleLine: {
250*8d82ecd9S猫头猫        width: '67%',
251*8d82ecd9S猫头猫        height: 1,
252*8d82ecd9S猫头猫        backgroundColor: '#cccccc',
253*8d82ecd9S猫头猫        opacity: 0.4,
254*8d82ecd9S猫头猫    },
255*8d82ecd9S猫头猫    playIcon: {
256*8d82ecd9S猫头猫        width: rpx(90),
257*8d82ecd9S猫头猫        textAlign: 'right',
258*8d82ecd9S猫头猫        color: 'white',
259*8d82ecd9S猫头猫    },
260*8d82ecd9S猫头猫    searchLyric: {
261*8d82ecd9S猫头猫        width: rpx(180),
262*8d82ecd9S猫头猫        marginTop: rpx(14),
263*8d82ecd9S猫头猫        paddingVertical: rpx(10),
264*8d82ecd9S猫头猫        textAlign: 'center',
265*8d82ecd9S猫头猫        alignSelf: 'center',
266*8d82ecd9S猫头猫        color: '#66eeff',
267*8d82ecd9S猫头猫        textDecorationLine: 'underline',
268*8d82ecd9S猫头猫    },
269*8d82ecd9S猫头猫});
270