mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-05 08:48:55 +08:00
242 lines
7.0 KiB
TypeScript
242 lines
7.0 KiB
TypeScript
import { useImperativeHandle, forwardRef, useState, useCallback, useRef } from 'react'
|
|
import { View } from 'react-native'
|
|
import { TEMP_FILE_PATH, createStyle, toast } from '@/utils/tools'
|
|
import InputItem from './InputItem'
|
|
import { useI18n } from '@/lang'
|
|
import TextAreaItem from './TextAreaItem'
|
|
import PicItem from './PicItem'
|
|
import { useTheme } from '@/store/theme/hook'
|
|
import ParseName from './ParseName'
|
|
import { downloadFile, mkdir, stat } from '@/utils/fs'
|
|
import { useUnmounted } from '@/utils/hooks'
|
|
import { getLyricInfo, getPicUrl } from '@/core/music/local'
|
|
import settingState from '@/store/setting/state'
|
|
|
|
export interface Metadata {
|
|
name: string // 歌曲名
|
|
singer: string // 艺术家名
|
|
albumName: string // 歌曲专辑名称
|
|
pic: string
|
|
lyric: string
|
|
interval: string
|
|
}
|
|
export const defaultData = {
|
|
name: '',
|
|
singer: '',
|
|
albumName: '',
|
|
pic: '',
|
|
lyric: '',
|
|
interval: '',
|
|
}
|
|
|
|
export interface MetadataFormType {
|
|
setForm: (path: string, metadata: Metadata) => void
|
|
getForm: () => Metadata
|
|
}
|
|
|
|
const matcheingPic = new Set<string>()
|
|
const matcheingLrc = new Set<string>()
|
|
export default forwardRef<MetadataFormType, {}>((props, ref) => {
|
|
const t = useI18n()
|
|
const [fileName, setFileName] = useState('')
|
|
const filePath = useRef('')
|
|
const [data, setData] = useState({ ...defaultData })
|
|
const theme = useTheme()
|
|
const isUnmounted = useUnmounted()
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
setForm(path, data) {
|
|
filePath.current = path
|
|
// setPath(path)
|
|
void stat(path).then(info => {
|
|
if (isUnmounted.current) return
|
|
setFileName(info.name)
|
|
})
|
|
setData(data)
|
|
},
|
|
getForm() {
|
|
return {
|
|
...data,
|
|
name: data.name.trim(),
|
|
singer: data.singer.trim(),
|
|
albumName: data.albumName.trim(),
|
|
}
|
|
},
|
|
}))
|
|
|
|
const handleUpdateName = useCallback((name: string) => {
|
|
if (name.length > 150) name = name.substring(0, 150)
|
|
setData(data => {
|
|
return { ...data, name }
|
|
})
|
|
}, [])
|
|
const handleUpdateSinger = useCallback((singer: string) => {
|
|
if (singer.length > 150) singer = singer.substring(0, 150)
|
|
setData(data => {
|
|
return { ...data, singer }
|
|
})
|
|
}, [])
|
|
const handleUpdateAlbumName = useCallback((albumName: string) => {
|
|
if (albumName.length > 150) albumName = albumName.substring(0, 150)
|
|
setData(data => {
|
|
return { ...data, albumName }
|
|
})
|
|
}, [])
|
|
const handleOnlineMatchPic = useCallback(() => {
|
|
let path = filePath.current
|
|
if (matcheingPic.has(path)) return
|
|
matcheingPic.add(path)
|
|
void getPicUrl({
|
|
skipFilePic: true,
|
|
musicInfo: {
|
|
id: path,
|
|
interval: data.interval,
|
|
meta: {
|
|
albumName: data.albumName,
|
|
ext: '',
|
|
filePath: path,
|
|
songId: path,
|
|
},
|
|
name: data.name,
|
|
singer: data.singer,
|
|
source: 'local',
|
|
},
|
|
isRefresh: false,
|
|
}).then(async(pic) => {
|
|
if (isUnmounted.current || path != filePath.current) return
|
|
let ext = pic.split('?')[0]
|
|
ext = ext.substring(ext.lastIndexOf('.') + 1)
|
|
if (ext.length > 5) ext = 'jpeg'
|
|
await mkdir(TEMP_FILE_PATH)
|
|
const picPath = `${TEMP_FILE_PATH}/${Math.random().toString().substring(5)}.${ext}`
|
|
return downloadFile(pic, picPath, {
|
|
connectionTimeout: 10000,
|
|
readTimeout: 10000,
|
|
}).promise.then((res) => {
|
|
if (isUnmounted.current || path != filePath.current) return
|
|
toast(t('metadata_edit_modal_form_match_pic_success'))
|
|
setData(data => {
|
|
return { ...data, pic: picPath }
|
|
})
|
|
})
|
|
}).catch((err) => {
|
|
console.log(err)
|
|
if (isUnmounted.current || path != filePath.current) return
|
|
toast(t('metadata_edit_modal_form_match_pic_failed'))
|
|
}).finally(() => {
|
|
matcheingPic.delete(path)
|
|
})
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [data.albumName, data.name, data.singer, t])
|
|
const handleOnlineMatchLyric = useCallback(() => {
|
|
let path = filePath.current
|
|
if (matcheingLrc.has(path)) return
|
|
matcheingLrc.add(path)
|
|
void getLyricInfo({
|
|
skipFileLyric: true,
|
|
musicInfo: {
|
|
id: path,
|
|
interval: data.interval,
|
|
meta: {
|
|
albumName: data.albumName,
|
|
ext: '',
|
|
filePath: path,
|
|
songId: path,
|
|
},
|
|
name: data.name,
|
|
singer: data.singer,
|
|
source: 'local',
|
|
},
|
|
isRefresh: false,
|
|
}).then(async({ lyric, tlyric, rlyric }) => {
|
|
if (isUnmounted.current || path != filePath.current) return
|
|
toast(t('metadata_edit_modal_form_match_lyric_success'))
|
|
let lrc = [
|
|
lyric,
|
|
settingState.setting['player.isShowLyricTranslation'] && tlyric ? tlyric : '',
|
|
settingState.setting['player.isShowLyricRoma'] && rlyric ? rlyric : '',
|
|
]
|
|
setData(data => {
|
|
return { ...data, lyric: lrc.join('\n\n').trim() }
|
|
})
|
|
}).catch(() => {
|
|
if (isUnmounted.current || path != filePath.current) return
|
|
toast(t('metadata_edit_modal_form_match_lyric_failed'))
|
|
}).finally(() => {
|
|
matcheingLrc.delete(path)
|
|
})
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [data.albumName, data.name, data.singer, t])
|
|
const handleUpdatePic = useCallback((path: string) => {
|
|
setData(data => {
|
|
return { ...data, pic: path }
|
|
})
|
|
}, [])
|
|
const handleUpdateLyric = useCallback((lyric: string) => {
|
|
setData(data => {
|
|
return { ...data, lyric }
|
|
})
|
|
}, [])
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<TextAreaItem
|
|
value={fileName}
|
|
label={global.i18n.t('metadata_edit_modal_file_name')}
|
|
numberOfLines={2}
|
|
scrollEnabled
|
|
style={{ ...styles.pathText, color: theme['c-primary-font'] }}
|
|
/>
|
|
|
|
<InputItem
|
|
value={data.name}
|
|
label={t('metadata_edit_modal_form_name')}
|
|
onChanged={handleUpdateName}
|
|
keyboardType="name-phone-pad" />
|
|
<InputItem
|
|
value={data.singer}
|
|
label={t('metadata_edit_modal_form_singer')}
|
|
onChanged={handleUpdateSinger}
|
|
keyboardType="name-phone-pad" />
|
|
<ParseName
|
|
fileName={fileName}
|
|
onNameChanged={handleUpdateName}
|
|
onSingerChanged={handleUpdateSinger}
|
|
/>
|
|
<InputItem
|
|
value={data.albumName}
|
|
label={t('metadata_edit_modal_form_album_name')}
|
|
onChanged={handleUpdateAlbumName}
|
|
keyboardType="name-phone-pad" />
|
|
|
|
<PicItem
|
|
value={data.pic}
|
|
label={t('metadata_edit_modal_form_pic')}
|
|
onOnlineMatch={handleOnlineMatchPic}
|
|
onChanged={handleUpdatePic} />
|
|
<TextAreaItem
|
|
value={data.lyric}
|
|
label={t('metadata_edit_modal_form_lyric')}
|
|
onOnlineMatch={handleOnlineMatchLyric}
|
|
onChanged={handleUpdateLyric}
|
|
numberOfLines={6}
|
|
keyboardType="default" />
|
|
</View>
|
|
)
|
|
})
|
|
|
|
const styles = createStyle({
|
|
container: {
|
|
flexGrow: 1,
|
|
flexShrink: 1,
|
|
flexDirection: 'column',
|
|
width: 360,
|
|
maxWidth: '100%',
|
|
},
|
|
pathText: {
|
|
height: 60,
|
|
},
|
|
})
|
|
|
|
|