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