xref: /MusicFree/src/components/musicBar/index.tsx (revision e0caf3427c927f4cf2efda9969f9a6f4fb0ef565)
1import React, {memo, useEffect, useState} from 'react';
2import {Keyboard, Pressable, StyleSheet, Text, View} from 'react-native';
3import rpx from '@/utils/rpx';
4import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
5import MusicQueue from '@/core/musicQueue';
6import {CircularProgressBase} from 'react-native-circular-progress-indicator';
7import {ROUTE_PATH, useNavigate} from '@/entry/router';
8
9import musicIsPaused from '@/utils/musicIsPaused';
10
11import Color from 'color';
12import ThemeText from '../base/themeText';
13import {ImgAsset} from '@/constants/assetsConst';
14import {useSafeAreaInsets} from 'react-native-safe-area-context';
15import {showPanel} from '../panels/usePanel';
16import FastImage from '../base/fastImage';
17import useColors from '@/hooks/useColors';
18import IconButton from '../base/iconButton';
19
20function CircularPlayBtn() {
21    const progress = MusicQueue.useProgress();
22    const musicState = MusicQueue.usePlaybackState();
23    const colors = useColors();
24
25    const isPaused = musicIsPaused(musicState);
26
27    return (
28        <CircularProgressBase
29            activeStrokeWidth={rpx(4)}
30            inActiveStrokeWidth={rpx(2)}
31            inActiveStrokeOpacity={0.2}
32            value={
33                progress?.duration
34                    ? (100 * progress.position) / progress.duration
35                    : 0
36            }
37            duration={100}
38            radius={rpx(36)}
39            activeStrokeColor={colors.musicBarText}
40            inActiveStrokeColor={colors.textSecondary}>
41            <IconButton
42                accessibilityLabel={isPaused ? '播放' : '暂停'}
43                name={isPaused ? 'play' : 'pause'}
44                sizeType={'normal'}
45                color={colors.musicBarText}
46                onPress={async () => {
47                    if (isPaused) {
48                        await MusicQueue.play();
49                    } else {
50                        await MusicQueue.pause();
51                    }
52                }}
53            />
54        </CircularProgressBase>
55    );
56}
57function MusicBar() {
58    const musicItem = MusicQueue.useCurrentMusicItem();
59
60    const [showKeyboard, setKeyboardStatus] = useState(false);
61
62    const navigate = useNavigate();
63    const colors = useColors();
64    const safeAreaInsets = useSafeAreaInsets();
65
66    useEffect(() => {
67        const showSubscription = Keyboard.addListener('keyboardDidShow', () => {
68            setKeyboardStatus(true);
69        });
70        const hideSubscription = Keyboard.addListener('keyboardDidHide', () => {
71            setKeyboardStatus(false);
72        });
73
74        return () => {
75            showSubscription.remove();
76            hideSubscription.remove();
77        };
78    }, []);
79
80    return (
81        <>
82            {musicItem && !showKeyboard && (
83                <Pressable
84                    style={[
85                        style.wrapper,
86                        {
87                            backgroundColor: colors.musicBar,
88                            paddingLeft: safeAreaInsets.left + rpx(24),
89                            paddingRight: safeAreaInsets.right + rpx(24),
90                        },
91                    ]}
92                    accessible
93                    accessibilityLabel={`歌曲: ${musicItem.title} 歌手: ${musicItem.artist}`}
94                    onPress={() => {
95                        navigate(ROUTE_PATH.MUSIC_DETAIL);
96                    }}>
97                    <View style={style.artworkWrapper}>
98                        <FastImage
99                            style={style.artworkImg}
100                            uri={musicItem.artwork}
101                            emptySrc={ImgAsset.albumDefault}
102                        />
103                    </View>
104                    <Text
105                        ellipsizeMode="tail"
106                        accessible={false}
107                        style={style.textWrapper}
108                        numberOfLines={1}>
109                        <ThemeText fontSize="content" fontColor="musicBarText">
110                            {musicItem?.title}
111                        </ThemeText>
112                        {musicItem?.artist && (
113                            <ThemeText
114                                fontSize="description"
115                                color={Color(colors.musicBarText)
116                                    .alpha(0.6)
117                                    .toString()}>
118                                {' '}
119                                -{musicItem.artist}
120                            </ThemeText>
121                        )}
122                    </Text>
123                    <View style={style.actionGroup}>
124                        <CircularPlayBtn />
125                        <Icon
126                            accessible
127                            accessibilityLabel="播放列表"
128                            name="playlist-music"
129                            size={rpx(56)}
130                            onPress={() => {
131                                showPanel('PlayList');
132                            }}
133                            style={[
134                                style.actionIcon,
135                                {color: colors.musicBarText},
136                            ]}
137                        />
138                    </View>
139                </Pressable>
140            )}
141        </>
142    );
143}
144
145export default memo(MusicBar, () => true);
146
147const style = StyleSheet.create({
148    wrapper: {
149        width: '100%',
150        height: rpx(132),
151        flexDirection: 'row',
152        alignItems: 'center',
153        paddingHorizontal: rpx(24),
154    },
155    artworkWrapper: {
156        height: rpx(120),
157        width: rpx(120),
158        justifyContent: 'center',
159    },
160    textWrapper: {
161        flexGrow: 1,
162        flexShrink: 1,
163    },
164    actionGroup: {
165        width: rpx(200),
166        justifyContent: 'flex-end',
167        flexDirection: 'row',
168        alignItems: 'center',
169    },
170    actionIcon: {
171        marginLeft: rpx(36),
172    },
173    artworkImg: {
174        width: rpx(96),
175        height: rpx(96),
176        borderRadius: rpx(48),
177    },
178});
179