xref: /MusicFree/src/components/panels/types/musicItemOptions.tsx (revision 41ddce918e1138d8f16e522cc7c19ac86ceca698)
1import React from "react";
2import { StyleSheet, View } from "react-native";
3import rpx from "@/utils/rpx";
4import ListItem from "@/components/base/listItem";
5import ThemeText from "@/components/base/themeText";
6import Download from "@/core/download";
7import { ImgAsset } from "@/constants/assetsConst";
8import Clipboard from "@react-native-clipboard/clipboard";
9
10import MediaMeta from "@/core/mediaExtra";
11import { getMediaKey } from "@/utils/mediaItem";
12import FastImage from "@/components/base/fastImage";
13import Toast from "@/utils/toast";
14import LocalMusicSheet from "@/core/localMusicSheet";
15import { localMusicSheetId, musicHistorySheetId } from "@/constants/commonConst";
16import { ROUTE_PATH } from "@/core/router";
17
18import { useSafeAreaInsets } from "react-native-safe-area-context";
19import PanelBase from "../base/panelBase";
20import { FlatList } from "react-native-gesture-handler";
21import musicHistory from "@/core/musicHistory";
22import { showDialog } from "@/components/dialogs/useDialog";
23import { hidePanel, showPanel } from "../usePanel";
24import Divider from "@/components/base/divider";
25import { iconSizeConst } from "@/constants/uiConst";
26import Config from "@/core/config.ts";
27import TrackPlayer from "@/core/trackPlayer";
28import mediaCache from "@/core/mediaCache";
29import LyricManager from "@/core/lyricManager";
30import { IIconName } from "@/components/base/icon.tsx";
31import MusicSheet from "@/core/musicSheet";
32
33interface IMusicItemOptionsProps {
34    /** 歌曲信息 */
35    musicItem: IMusic.IMusicItem;
36    /** 歌曲所在歌单 */
37    musicSheet?: IMusic.IMusicSheetItem;
38    /** 来源 */
39    from?: string;
40}
41
42const ITEM_HEIGHT = rpx(96);
43
44interface IOption {
45    icon: IIconName;
46    title: string;
47    onPress?: () => void;
48    show?: boolean;
49}
50
51export default function MusicItemOptions(props: IMusicItemOptionsProps) {
52    const {musicItem, musicSheet, from} = props ?? {};
53
54    const safeAreaInsets = useSafeAreaInsets();
55
56    const downloaded = LocalMusicSheet.isLocalMusic(musicItem);
57    const associatedLrc = MediaMeta.get(musicItem)?.associatedLrc;
58
59    const options: IOption[] = [
60        {
61            icon: 'identification',
62            title: `ID: ${getMediaKey(musicItem)}`,
63            onPress: () => {
64                mediaCache.setMediaCache(musicItem);
65                Clipboard.setString(
66                    JSON.stringify(
67                        {
68                            platform: musicItem.platform,
69                            id: musicItem.id,
70                        },
71                        null,
72                        '',
73                    ),
74                );
75                Toast.success('已复制到剪切板');
76            },
77        },
78        {
79            icon: 'user',
80            title: `作者: ${musicItem.artist}`,
81            onPress: () => {
82                try {
83                    Clipboard.setString(musicItem.artist.toString());
84                    Toast.success('已复制到剪切板');
85                } catch {
86                    Toast.warn('复制失败');
87                }
88            },
89        },
90        {
91            icon: 'album-outline',
92            show: !!musicItem.album,
93            title: `专辑: ${musicItem.album}`,
94            onPress: () => {
95                try {
96                    Clipboard.setString(musicItem.album.toString());
97                    Toast.success('已复制到剪切板');
98                } catch {
99                    Toast.warn('复制失败');
100                }
101            },
102        },
103        {
104            icon: 'motion-play',
105            title: '下一首播放',
106            onPress: () => {
107                TrackPlayer.addNext(musicItem);
108                hidePanel();
109            },
110        },
111        {
112            icon: 'folder-plus',
113            title: '添加到歌单',
114            onPress: () => {
115                showPanel('AddToMusicSheet', {musicItem});
116            },
117        },
118        {
119            icon: 'arrow-down-tray',
120            title: '下载',
121            show: !downloaded,
122            onPress: async () => {
123                showPanel('MusicQuality', {
124                    musicItem,
125                    type: 'download',
126                    async onQualityPress(quality) {
127                        Download.downloadMusic(musicItem, quality);
128                    },
129                });
130            },
131        },
132        {
133            icon: 'check-circle-outline',
134            title: '已下载',
135            show: !!downloaded,
136        },
137        {
138            icon: 'trash-outline',
139            title: '删除',
140            show: !!musicSheet,
141            onPress: async () => {
142                if (musicSheet?.id === localMusicSheetId) {
143                    await LocalMusicSheet.removeMusic(musicItem);
144                } else if (musicSheet?.id === musicHistorySheetId) {
145                    await musicHistory.removeMusic(musicItem);
146                } else {
147                    await MusicSheet.removeMusic(musicSheet!.id, musicItem);
148                }
149                Toast.success('已删除');
150                hidePanel();
151            },
152        },
153        {
154            icon: 'trash-outline',
155            title: '删除本地下载',
156            show: !!downloaded,
157            onPress: () => {
158                showDialog('SimpleDialog', {
159                    title: '删除本地下载',
160                    content: '将会删除已下载的本地文件,确定继续吗?',
161                    async onOk() {
162                        try {
163                            await LocalMusicSheet.removeMusic(musicItem, true);
164                            Toast.success('已删除本地下载');
165                        } catch (e: any) {
166                            Toast.warn(`删除失败 ${e?.message ?? e}`);
167                        }
168                    },
169                });
170                hidePanel();
171            },
172        },
173        {
174            icon: 'link',
175            title: associatedLrc
176                ? `已关联歌词 ${associatedLrc.platform}@${associatedLrc.id}`
177                : '关联歌词',
178            onPress: async () => {
179                if (
180                    Config.getConfig('basic.associateLyricType') === 'input'
181                ) {
182                    showPanel('AssociateLrc', {
183                        musicItem,
184                    });
185                } else {
186                    showPanel('SearchLrc', {
187                        musicItem,
188                    });
189                }
190            },
191        },
192        {
193            icon: 'link-slash',
194            title: '解除关联歌词',
195            show: !!associatedLrc,
196            onPress: async () => {
197                MediaMeta.update(musicItem, {
198                    associatedLrc: undefined,
199                });
200                LyricManager.refreshLyric(false, true);
201                Toast.success('已解除关联歌词');
202                hidePanel();
203            },
204        },
205        {
206            icon: 'alarm-outline',
207            title: '定时关闭',
208            show: from === ROUTE_PATH.MUSIC_DETAIL,
209            onPress: () => {
210                showPanel('TimingClose');
211            },
212        },
213        {
214            icon: 'archive-box-x-mark',
215            title: '清除插件缓存(播放异常时使用)',
216            onPress: () => {
217                mediaCache.removeMediaCache(musicItem);
218                Toast.success('缓存已清除');
219            },
220        },
221    ];
222
223    return (
224        <PanelBase
225            renderBody={() => (
226                <>
227                    <View style={style.header}>
228                        <FastImage
229                            style={style.artwork}
230                            uri={musicItem?.artwork}
231                            emptySrc={ImgAsset.albumDefault}
232                        />
233                        <View style={style.content}>
234                            <ThemeText numberOfLines={2} style={style.title}>
235                                {musicItem?.title}
236                            </ThemeText>
237                            <ThemeText
238                                fontColor="textSecondary"
239                                numberOfLines={2}
240                                fontSize="description">
241                                {musicItem?.artist}{' '}
242                                {musicItem?.album ? `- ${musicItem.album}` : ''}
243                            </ThemeText>
244                        </View>
245                    </View>
246                    <Divider />
247                    <View style={style.wrapper}>
248                        <FlatList
249                            data={options}
250                            getItemLayout={(_, index) => ({
251                                length: ITEM_HEIGHT,
252                                offset: ITEM_HEIGHT * index,
253                                index,
254                            })}
255                            ListFooterComponent={<View style={style.footer} />}
256                            style={[
257                                style.listWrapper,
258                                {
259                                    marginBottom: safeAreaInsets.bottom,
260                                },
261                            ]}
262                            keyExtractor={_ => _.title}
263                            renderItem={({item}) =>
264                                item.show !== false ? (
265                                    <ListItem
266                                        withHorizontalPadding
267                                        heightType="small"
268                                        onPress={item.onPress}>
269                                        <ListItem.ListItemIcon
270                                            width={rpx(48)}
271                                            icon={item.icon}
272                                            iconSize={iconSizeConst.light}
273                                        />
274                                        <ListItem.Content title={item.title} />
275                                    </ListItem>
276                                ) : null
277                            }
278                        />
279                    </View>
280                </>
281            )}
282        />
283    );
284}
285
286const style = StyleSheet.create({
287    wrapper: {
288        width: rpx(750),
289        flex: 1,
290    },
291    header: {
292        width: rpx(750),
293        height: rpx(200),
294        flexDirection: 'row',
295        padding: rpx(24),
296    },
297    listWrapper: {
298        paddingTop: rpx(12),
299    },
300    artwork: {
301        width: rpx(140),
302        height: rpx(140),
303        borderRadius: rpx(16),
304    },
305    content: {
306        marginLeft: rpx(36),
307        width: rpx(526),
308        height: rpx(140),
309        justifyContent: 'space-around',
310    },
311    title: {
312        paddingRight: rpx(24),
313    },
314    footer: {
315        width: rpx(750),
316        height: rpx(30),
317    },
318});
319