mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-03 06:42:09 +08:00
新增通过歌单链接打开歌单的功能
This commit is contained in:
parent
0a6a6d9243
commit
c0785190e0
2
index.js
2
index.js
@ -19,7 +19,7 @@ import { init as initMusicTools } from '@/utils/music'
|
||||
import { init as initLyric } from '@/plugins/lyric'
|
||||
import { init as initI18n, supportedLngs } from '@/plugins/i18n'
|
||||
import { deviceLanguage, getPlayInfo } from '@/utils/tools'
|
||||
import { LIST_ID_PLAY_TEMP, LIST_ID_PLAY_LATER } from '@/config/constant'
|
||||
import { LIST_ID_PLAY_TEMP } from '@/config/constant'
|
||||
|
||||
console.log('starting app...')
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
### 新增
|
||||
|
||||
- 新增通过歌单链接打开歌单的功能
|
||||
|
||||
### 优化
|
||||
|
||||
- 切换到播放详情歌词界面时将阻止屏幕息屏
|
||||
|
@ -30,6 +30,10 @@
|
||||
|
||||
"play_detail_todo_tip": "What do you want? No, this function has not been implemented yet 😛, But you can try to locate the currently playing song by long pressing (only valid for playing songs in \"My List\")",
|
||||
|
||||
"load_failed": "Ah, loading failed 😥",
|
||||
"songlist_open": "Import",
|
||||
"songlist_open_input_placeholder": "Enter the playlist link or playlist ID",
|
||||
"songlist_open_input_tip": "1. Cross-source opening of the playlist is not supported. Please confirm whether the playlist to be opened corresponds to the current playlist source\n2. If you encounter a playlist link that cannot be opened, feedback is welcome\n3, Kugou source Open with playlist ID is not supported, but Kugou code is supported",
|
||||
"collect_success": "Collection success",
|
||||
"collect_songlist": "Collection Songlist",
|
||||
"collect_toplist": "Collection Toplist",
|
||||
|
@ -30,6 +30,10 @@
|
||||
|
||||
"play_detail_todo_tip": "你想干嘛?不可以的,这个功能还没有实现哦😛,不过你可以试着长按来定位当前播放的歌曲(仅对播放“我的列表”里的歌曲有效哦)",
|
||||
|
||||
"load_failed": "啊 加载失败了 😥",
|
||||
"songlist_open": "导入",
|
||||
"songlist_open_input_placeholder": "输入歌单链接或歌单ID",
|
||||
"songlist_open_input_tip": "1、不支持跨源打开歌单,请确认要打开的歌单与当前歌单源是否对应\n2、若遇到无法打开的歌单链接,欢迎反馈\n3、酷狗源不支持用歌单ID打开,但支持酷狗码打开",
|
||||
"collect_success": "收藏成功",
|
||||
"collect_songlist": "收藏歌单",
|
||||
"collect_toplist": "收藏排行榜",
|
||||
|
@ -1,19 +1,21 @@
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { View, Text, StyleSheet } from 'react-native'
|
||||
|
||||
import { useGetter, useDispatch } from '@/store'
|
||||
// import { useGetter, useDispatch } from '@/store'
|
||||
import SourceSelector from './SourceSelector'
|
||||
import SortTab from './SortTab'
|
||||
import Tag from './Tag'
|
||||
import OpenList from './OpenList'
|
||||
// import { BorderWidths } from '@/theme'
|
||||
|
||||
export default () => {
|
||||
const theme = useGetter('common', 'theme')
|
||||
// const theme = useGetter('common', 'theme')
|
||||
|
||||
return (
|
||||
<View style={{ ...styles.container }}>
|
||||
<SortTab />
|
||||
<Tag />
|
||||
<OpenList />
|
||||
<SourceSelector />
|
||||
</View>
|
||||
)
|
||||
|
98
src/screens/Home/SongList/List/OpenList.js
Normal file
98
src/screens/Home/SongList/List/OpenList.js
Normal file
@ -0,0 +1,98 @@
|
||||
import React, { useCallback, memo, useMemo, useEffect, useState, useRef } from 'react'
|
||||
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'
|
||||
// import Icon from '@/components/common/Icon'
|
||||
import { useGetter, useDispatch } from '@/store'
|
||||
import { useTranslation } from '@/plugins/i18n'
|
||||
import Button from '@/components/common/Button'
|
||||
import ConfirmAlert from '@/components/common/ConfirmAlert'
|
||||
import Input from '@/components/common/Input'
|
||||
|
||||
export default memo(() => {
|
||||
const theme = useGetter('common', 'theme')
|
||||
const { t } = useTranslation()
|
||||
const [visibleAlert, setVisibleAlert] = useState(false)
|
||||
const [text, setText] = useState('')
|
||||
const setSelectListInfo = useDispatch('songList', 'setSelectListInfo')
|
||||
const setVisibleListDetail = useDispatch('songList', 'setVisibleListDetail')
|
||||
const songListSource = useGetter('songList', 'songListSource')
|
||||
|
||||
const handleShowMusicAddModal = () => {
|
||||
setText('')
|
||||
setVisibleAlert(true)
|
||||
}
|
||||
|
||||
const handleCancelOpen = useCallback(() => {
|
||||
setVisibleAlert(false)
|
||||
}, [])
|
||||
const handleOpen = useCallback(() => {
|
||||
if (!text.length) return
|
||||
setSelectListInfo({
|
||||
play_count: null,
|
||||
id: text,
|
||||
author: '',
|
||||
name: '',
|
||||
img: null,
|
||||
desc: '',
|
||||
source: songListSource,
|
||||
})
|
||||
setVisibleListDetail(true)
|
||||
setVisibleAlert(false)
|
||||
}, [setSelectListInfo, setVisibleListDetail, songListSource, text])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button style={{ ...styles.button }} onPress={handleShowMusicAddModal}>
|
||||
<Text style={{ ...styles.buttonText, color: theme.normal }}>{t('songlist_open')}</Text>
|
||||
</Button>
|
||||
<ConfirmAlert
|
||||
visible={visibleAlert}
|
||||
onCancel={handleCancelOpen}
|
||||
onConfirm={handleOpen}
|
||||
>
|
||||
<View style={styles.alertContent}>
|
||||
<Input
|
||||
placeholder={t('songlist_open_input_placeholder')}
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
style={{ ...styles.input, backgroundColor: theme.secondary40 }}
|
||||
/>
|
||||
<Text style={{ ...styles.inputTipText, color: theme.normal20 }}>{t('songlist_open_input_tip')}</Text>
|
||||
</View>
|
||||
</ConfirmAlert>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
// backgroundColor: '#ccc',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 14,
|
||||
},
|
||||
|
||||
alertContent: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
input: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
minWidth: 240,
|
||||
borderRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
fontSize: 12,
|
||||
},
|
||||
inputTipText: {
|
||||
marginTop: 5,
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
},
|
||||
})
|
@ -91,9 +91,9 @@ const styles = StyleSheet.create({
|
||||
height: 38,
|
||||
lineHeight: 38,
|
||||
textAlign: 'center',
|
||||
minWidth: 70,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
// minWidth: 70,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
},
|
||||
|
||||
container: {
|
||||
|
48
src/screens/Home/SongList/ListDetail/Failed.js
Normal file
48
src/screens/Home/SongList/ListDetail/Failed.js
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { memo } from 'react'
|
||||
import { View, Text, StyleSheet, ImageBackground } from 'react-native'
|
||||
import { useGetter, useDispatch } from '@/store'
|
||||
import { useTranslation } from '@/plugins/i18n'
|
||||
import Button from '@/components/common/Button'
|
||||
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation()
|
||||
const theme = useGetter('common', 'theme')
|
||||
const setVisibleListDetail = useDispatch('songList', 'setVisibleListDetail')
|
||||
const handleBack = () => {
|
||||
setVisibleListDetail(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={{ ...styles.text, color: theme.normal20 }}>{t('load_failed')}</Text>
|
||||
<Button onPress={handleBack} style={{ ...styles.controlBtn, backgroundColor: theme.secondary40 }}>
|
||||
<Text style={{ ...styles.controlBtnText, color: theme.secondary }}>{t('back')}</Text>
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
text: {
|
||||
fontSize: 18,
|
||||
marginBottom: '20%',
|
||||
},
|
||||
controlBtn: {
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
paddingLeft: 25,
|
||||
paddingRight: 25,
|
||||
borderRadius: 4,
|
||||
},
|
||||
controlBtnText: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
export default Header
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback, useRef } from 'react'
|
||||
// import { View, Text, StyleSheet, Animated, FlatList, ImageBackground } from 'react-native'
|
||||
import ListDetailHeader from './Header'
|
||||
import Failed from './Failed'
|
||||
import { useGetter, useDispatch } from '@/store'
|
||||
import OnlineList from '@/components/OnlineList'
|
||||
|
||||
@ -8,6 +9,7 @@ import OnlineList from '@/components/OnlineList'
|
||||
export default ({ animatePlayed }) => {
|
||||
const [isListRefreshing, setIsListRefreshing] = useState(false)
|
||||
const isVisibleListDetail = useGetter('songList', 'isVisibleListDetail')
|
||||
const isGetListDetailFailed = useGetter('songList', 'isGetListDetailFailed')
|
||||
|
||||
const selectListInfo = useGetter('songList', 'selectListInfo')
|
||||
const selectListInfoRef = useRef(selectListInfo)
|
||||
@ -28,15 +30,17 @@ export default ({ animatePlayed }) => {
|
||||
}, [getListDetail])
|
||||
|
||||
return (
|
||||
<OnlineList
|
||||
list={isVisibleListDetail && animatePlayed ? listDetailData.list : []}
|
||||
page={-1}
|
||||
// isEnd={listDetailData.isEnd}
|
||||
isListRefreshing={isListRefreshing}
|
||||
onRefresh={handleListRefresh}
|
||||
onLoadMore={handleListLoadMore}
|
||||
isLoading={listDetailData.isLoading}
|
||||
ListHeaderComponent={<ListDetailHeader />}
|
||||
isGetListDetailFailed
|
||||
? <Failed />
|
||||
: <OnlineList
|
||||
list={isVisibleListDetail && animatePlayed ? listDetailData.list : []}
|
||||
page={-1}
|
||||
// isEnd={listDetailData.isEnd}
|
||||
isListRefreshing={isListRefreshing}
|
||||
onRefresh={handleListRefresh}
|
||||
onLoadMore={handleListLoadMore}
|
||||
isLoading={listDetailData.isLoading}
|
||||
ListHeaderComponent={<ListDetailHeader />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { useGetter, useDispatch } from '@/store'
|
||||
|
||||
|
||||
export default () => {
|
||||
// const isGetDetailFailedRef = useRef(false)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [animatePlayed, setAnimatPlayed] = useState(true)
|
||||
const animFade = useRef(new Animated.Value(0)).current
|
||||
|
@ -15,6 +15,7 @@ export const TYPES = {
|
||||
setListDetailLoading: null,
|
||||
setListEnd: null,
|
||||
setListDetailEnd: null,
|
||||
setGetListDetailFailed: null,
|
||||
}
|
||||
|
||||
for (const key of Object.keys(TYPES)) {
|
||||
@ -112,6 +113,7 @@ export const getListDetail = ({ id, page, isRefresh = false }) => (dispatch, get
|
||||
if (isRefresh && cache.has(listKey)) cache.delete(listKey)
|
||||
if (!cache.has(listKey)) cache.set(listKey, new Map())
|
||||
|
||||
dispatch(setGetListDetailFailed(false))
|
||||
const listCache = cache.get(listKey)
|
||||
if (listCache.has(pageKey)) {
|
||||
return Promise.resolve(listCache.get(pageKey).data).then(result => dispatch(setListDetail({ result, listKey, pageKey, source, id, page })))
|
||||
@ -122,6 +124,12 @@ export const getListDetail = ({ id, page, isRefresh = false }) => (dispatch, get
|
||||
return getListDetailLimit({ source, id, page }).then(result => {
|
||||
dispatch(setListDetail({ result, listKey, pageKey, source, id, page }))
|
||||
// listCache.set(pageKey, result)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
if (page == 1) {
|
||||
dispatch(setGetListDetailFailed(true))
|
||||
}
|
||||
return Promise.reject(err)
|
||||
}).finally(() => {
|
||||
const state = getState().songList
|
||||
if (state.listDetail.pageKey != pageKey) return
|
||||
@ -169,6 +177,12 @@ export const setSelectListInfo = info => {
|
||||
payload: info,
|
||||
}
|
||||
}
|
||||
export const setGetListDetailFailed = isFailed => {
|
||||
return {
|
||||
type: TYPES.setGetListDetailFailed,
|
||||
payload: isFailed,
|
||||
}
|
||||
}
|
||||
export const setTags = ({ tags, source }) => {
|
||||
return {
|
||||
type: TYPES.setTags,
|
||||
|
@ -14,6 +14,7 @@ export const sortList = state => state.songList.sortList
|
||||
export const tags = state => state.songList.tags
|
||||
|
||||
export const isVisibleListDetail = state => state.songList.isVisibleListDetail
|
||||
export const isGetListDetailFailed = state => state.songList.isGetListDetailFailed
|
||||
export const selectListInfo = state => state.songList.selectListInfo
|
||||
export const listData = state => state.songList.list
|
||||
export const listDetailData = state => state.songList.listDetail
|
||||
|
@ -37,6 +37,7 @@ const initialState = {
|
||||
},
|
||||
selectListInfo: {},
|
||||
isVisibleListDetail: false,
|
||||
isGetListDetailFailed: false,
|
||||
}
|
||||
|
||||
sources.forEach(source => {
|
||||
@ -146,6 +147,12 @@ const mutations = {
|
||||
},
|
||||
}
|
||||
},
|
||||
[TYPES.setGetListDetailFailed](state, isFailed) {
|
||||
return {
|
||||
...state,
|
||||
isGetListDetailFailed: isFailed,
|
||||
}
|
||||
},
|
||||
[TYPES.setListLoading](state, isLoading) {
|
||||
return {
|
||||
...state,
|
||||
|
Loading…
x
Reference in New Issue
Block a user