xref: /MusicFree/src/components/panels/types/musicItemLyricOptions.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 { ImgAsset } from "@/constants/assetsConst";
7import Clipboard from "@react-native-clipboard/clipboard";
8import { getMediaKey } from "@/utils/mediaItem";
9import FastImage from "@/components/base/fastImage";
10import Toast from "@/utils/toast";
11import toast from "@/utils/toast";
12
13import { useSafeAreaInsets } from "react-native-safe-area-context";
14import PanelBase from "../base/panelBase";
15import { FlatList } from "react-native-gesture-handler";
16import Divider from "@/components/base/divider";
17import { iconSizeConst } from "@/constants/uiConst";
18import Config from "@/core/config.ts";
19import mediaCache from "@/core/mediaCache";
20import LyricManager from "@/core/lyricManager";
21import { IIconName } from "@/components/base/icon.tsx";
22import LyricUtil from "@/native/lyricUtil";
23import { hidePanel } from "@/components/panels/usePanel.ts";
24import { getDocumentAsync } from "expo-document-picker";
25import { readAsStringAsync } from "expo-file-system";
26import { checkAndCreateDir } from "@/utils/fileUtils.ts";
27import pathConst from "@/constants/pathConst.ts";
28import CryptoJs from "crypto-js";
29import RNFS from "react-native-fs";
30
31interface IMusicItemLyricOptionsProps {
32    /** 歌曲信息 */
33    musicItem: IMusic.IMusicItem;
34}
35
36const ITEM_HEIGHT = rpx(96);
37
38interface IOption {
39    icon: IIconName;
40    title: string;
41    onPress?: () => void;
42    show?: boolean;
43}
44
45export default function MusicItemLyricOptions(
46    props: IMusicItemLyricOptionsProps,
47) {
48    const {musicItem} = props ?? {};
49
50    const platformHash = CryptoJs.MD5(musicItem.platform).toString(
51        CryptoJs.enc.Hex,
52    );
53    const idHash: string = CryptoJs.MD5(musicItem.id).toString(
54        CryptoJs.enc.Hex,
55    );
56
57    const safeAreaInsets = useSafeAreaInsets();
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: 'lyric',
105            title: `${
106                Config.getConfig('lyric.showStatusBarLyric') ? '关闭' : '开启'
107            }桌面歌词`,
108            async onPress() {
109                const showStatusBarLyric = Config.getConfig('lyric.showStatusBarLyric');
110                if (!showStatusBarLyric) {
111                    const hasPermission =
112                        await LyricUtil.checkSystemAlertPermission();
113
114                    if (hasPermission) {
115                        const statusBarLyricConfig = {
116                            topPercent: Config.getConfig("lyric.topPercent"),
117                            leftPercent: Config.getConfig("lyric.leftPercent"),
118                            align: Config.getConfig("lyric.align"),
119                            color: Config.getConfig("lyric.color"),
120                            backgroundColor: Config.getConfig("lyric.backgroundColor"),
121                            widthPercent: Config.getConfig("lyric.widthPercent"),
122                            fontSize: Config.getConfig("lyric.fontSize")
123                        };
124                        LyricUtil.showStatusBarLyric(
125                          "MusicFree",
126                          statusBarLyricConfig ?? {}
127                        );
128                        Config.setConfig('lyric.showStatusBarLyric', true);
129                    } else {
130                        LyricUtil.requestSystemAlertPermission().finally(() => {
131                            Toast.warn('开启桌面歌词失败,无悬浮窗权限');
132                        });
133                    }
134                } else {
135                    LyricUtil.hideStatusBarLyric();
136                    Config.setConfig('lyric.showStatusBarLyric', false);
137                }
138                hidePanel();
139            },
140        },
141        {
142            icon: 'arrow-up-tray',
143            title: '上传本地歌词',
144            async onPress() {
145                try {
146                    const result = await getDocumentAsync({
147                        copyToCacheDirectory: true,
148                    });
149                    if (result.canceled) {
150                        return;
151                    }
152                    const pickedDoc = result.assets[0].uri;
153                    const lyricContent = await readAsStringAsync(pickedDoc, {
154                        encoding: 'utf8',
155                    });
156
157                    // 调用rnfs写到external storage
158                    await checkAndCreateDir(
159                        pathConst.localLrcPath + platformHash,
160                    );
161
162                    await RNFS.writeFile(
163                        pathConst.localLrcPath +
164                            platformHash +
165                            '/' +
166                            idHash +
167                            '.lrc',
168                        lyricContent,
169                        'utf8',
170                    );
171                    toast.success('设置成功');
172                    LyricManager.refreshLyric(false, true);
173                    hidePanel();
174                } catch (e: any) {
175                    console.log(e);
176                    toast.warn('设置失败' + e.message);
177                }
178            },
179        },
180        {
181            icon: 'arrow-up-tray',
182            title: '上传本地歌词翻译',
183            async onPress() {
184                try {
185                    const result = await getDocumentAsync({
186                        copyToCacheDirectory: true,
187                    });
188                    if (result.canceled) {
189                        return;
190                    }
191                    const pickedDoc = result.assets[0].uri;
192                    const lyricContent = await readAsStringAsync(pickedDoc, {
193                        encoding: 'utf8',
194                    });
195
196                    // 调用rnfs写到external storage
197                    await checkAndCreateDir(
198                        pathConst.localLrcPath + platformHash,
199                    );
200
201                    await RNFS.writeFile(
202                        pathConst.localLrcPath +
203                            platformHash +
204                            '/' +
205                            idHash +
206                            '.tran.lrc',
207                        lyricContent,
208                        'utf8',
209                    );
210                    toast.success('设置成功');
211                    LyricManager.refreshLyric(false, true);
212                    hidePanel();
213                } catch (e: any) {
214                    console.log(e);
215                    toast.warn('设置失败' + e.message);
216                }
217            },
218        },
219        {
220            icon: 'trash-outline',
221            title: '删除本地歌词',
222            async onPress() {
223                try {
224                    const basePath =
225                        pathConst.localLrcPath + platformHash + '/' + idHash;
226
227                    await RNFS.unlink(basePath + '.lrc').catch(() => {});
228                    await RNFS.unlink(basePath + '.tran.lrc').catch(() => {});
229
230                    toast.success('删除成功');
231                    LyricManager.refreshLyric(false, true);
232                    hidePanel();
233                } catch (e: any) {
234                    console.log(e);
235                    toast.warn('删除失败' + e.message);
236                }
237            },
238        },
239    ];
240
241    return (
242        <PanelBase
243            renderBody={() => (
244                <>
245                    <View style={style.header}>
246                        <FastImage
247                            style={style.artwork}
248                            uri={musicItem?.artwork}
249                            emptySrc={ImgAsset.albumDefault}
250                        />
251                        <View style={style.content}>
252                            <ThemeText numberOfLines={2} style={style.title}>
253                                {musicItem?.title}
254                            </ThemeText>
255                            <ThemeText
256                                fontColor="textSecondary"
257                                fontSize="description"
258                                numberOfLines={2}>
259                                {musicItem?.artist}{' '}
260                                {musicItem?.album ? `- ${musicItem.album}` : ''}
261                            </ThemeText>
262                        </View>
263                    </View>
264                    <Divider />
265                    <View style={style.wrapper}>
266                        <FlatList
267                            data={options}
268                            getItemLayout={(_, index) => ({
269                                length: ITEM_HEIGHT,
270                                offset: ITEM_HEIGHT * index,
271                                index,
272                            })}
273                            ListFooterComponent={<View style={style.footer} />}
274                            style={[
275                                style.listWrapper,
276                                {
277                                    marginBottom: safeAreaInsets.bottom,
278                                },
279                            ]}
280                            keyExtractor={_ => _.title}
281                            renderItem={({item}) =>
282                                item.show !== false ? (
283                                    <ListItem
284                                        withHorizontalPadding
285                                        heightType="small"
286                                        onPress={item.onPress}>
287                                        <ListItem.ListItemIcon
288                                            width={rpx(48)}
289                                            icon={item.icon}
290                                            iconSize={iconSizeConst.light}
291                                        />
292                                        <ListItem.Content title={item.title} />
293                                    </ListItem>
294                                ) : null
295                            }
296                        />
297                    </View>
298                </>
299            )}
300        />
301    );
302}
303
304const style = StyleSheet.create({
305    wrapper: {
306        width: rpx(750),
307        flex: 1,
308    },
309    header: {
310        width: rpx(750),
311        height: rpx(200),
312        flexDirection: 'row',
313        padding: rpx(24),
314    },
315    listWrapper: {
316        paddingTop: rpx(12),
317    },
318    artwork: {
319        width: rpx(140),
320        height: rpx(140),
321        borderRadius: rpx(16),
322    },
323    content: {
324        marginLeft: rpx(36),
325        width: rpx(526),
326        height: rpx(140),
327        justifyContent: 'space-around',
328    },
329    title: {
330        paddingRight: rpx(24),
331    },
332    footer: {
333        width: rpx(750),
334        height: rpx(30),
335    },
336});
337