mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-04 00: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 initLyric } from '@/plugins/lyric'
|
||||||
import { init as initI18n, supportedLngs } from '@/plugins/i18n'
|
import { init as initI18n, supportedLngs } from '@/plugins/i18n'
|
||||||
import { deviceLanguage, getPlayInfo } from '@/utils/tools'
|
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...')
|
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\")",
|
"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_success": "Collection success",
|
||||||
"collect_songlist": "Collection Songlist",
|
"collect_songlist": "Collection Songlist",
|
||||||
"collect_toplist": "Collection Toplist",
|
"collect_toplist": "Collection Toplist",
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
|
|
||||||
"play_detail_todo_tip": "你想干嘛?不可以的,这个功能还没有实现哦😛,不过你可以试着长按来定位当前播放的歌曲(仅对播放“我的列表”里的歌曲有效哦)",
|
"play_detail_todo_tip": "你想干嘛?不可以的,这个功能还没有实现哦😛,不过你可以试着长按来定位当前播放的歌曲(仅对播放“我的列表”里的歌曲有效哦)",
|
||||||
|
|
||||||
|
"load_failed": "啊 加载失败了 😥",
|
||||||
|
"songlist_open": "导入",
|
||||||
|
"songlist_open_input_placeholder": "输入歌单链接或歌单ID",
|
||||||
|
"songlist_open_input_tip": "1、不支持跨源打开歌单,请确认要打开的歌单与当前歌单源是否对应\n2、若遇到无法打开的歌单链接,欢迎反馈\n3、酷狗源不支持用歌单ID打开,但支持酷狗码打开",
|
||||||
"collect_success": "收藏成功",
|
"collect_success": "收藏成功",
|
||||||
"collect_songlist": "收藏歌单",
|
"collect_songlist": "收藏歌单",
|
||||||
"collect_toplist": "收藏排行榜",
|
"collect_toplist": "收藏排行榜",
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { View, Text, StyleSheet } from 'react-native'
|
import { View, Text, StyleSheet } from 'react-native'
|
||||||
|
|
||||||
import { useGetter, useDispatch } from '@/store'
|
// import { useGetter, useDispatch } from '@/store'
|
||||||
import SourceSelector from './SourceSelector'
|
import SourceSelector from './SourceSelector'
|
||||||
import SortTab from './SortTab'
|
import SortTab from './SortTab'
|
||||||
import Tag from './Tag'
|
import Tag from './Tag'
|
||||||
|
import OpenList from './OpenList'
|
||||||
// import { BorderWidths } from '@/theme'
|
// import { BorderWidths } from '@/theme'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const theme = useGetter('common', 'theme')
|
// const theme = useGetter('common', 'theme')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ ...styles.container }}>
|
<View style={{ ...styles.container }}>
|
||||||
<SortTab />
|
<SortTab />
|
||||||
<Tag />
|
<Tag />
|
||||||
|
<OpenList />
|
||||||
<SourceSelector />
|
<SourceSelector />
|
||||||
</View>
|
</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,
|
height: 38,
|
||||||
lineHeight: 38,
|
lineHeight: 38,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
minWidth: 70,
|
// minWidth: 70,
|
||||||
paddingLeft: 10,
|
paddingLeft: 12,
|
||||||
paddingRight: 10,
|
paddingRight: 12,
|
||||||
},
|
},
|
||||||
|
|
||||||
container: {
|
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 React, { useState, useCallback, useRef } from 'react'
|
||||||
// import { View, Text, StyleSheet, Animated, FlatList, ImageBackground } from 'react-native'
|
// import { View, Text, StyleSheet, Animated, FlatList, ImageBackground } from 'react-native'
|
||||||
import ListDetailHeader from './Header'
|
import ListDetailHeader from './Header'
|
||||||
|
import Failed from './Failed'
|
||||||
import { useGetter, useDispatch } from '@/store'
|
import { useGetter, useDispatch } from '@/store'
|
||||||
import OnlineList from '@/components/OnlineList'
|
import OnlineList from '@/components/OnlineList'
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ import OnlineList from '@/components/OnlineList'
|
|||||||
export default ({ animatePlayed }) => {
|
export default ({ animatePlayed }) => {
|
||||||
const [isListRefreshing, setIsListRefreshing] = useState(false)
|
const [isListRefreshing, setIsListRefreshing] = useState(false)
|
||||||
const isVisibleListDetail = useGetter('songList', 'isVisibleListDetail')
|
const isVisibleListDetail = useGetter('songList', 'isVisibleListDetail')
|
||||||
|
const isGetListDetailFailed = useGetter('songList', 'isGetListDetailFailed')
|
||||||
|
|
||||||
const selectListInfo = useGetter('songList', 'selectListInfo')
|
const selectListInfo = useGetter('songList', 'selectListInfo')
|
||||||
const selectListInfoRef = useRef(selectListInfo)
|
const selectListInfoRef = useRef(selectListInfo)
|
||||||
@ -28,15 +30,17 @@ export default ({ animatePlayed }) => {
|
|||||||
}, [getListDetail])
|
}, [getListDetail])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OnlineList
|
isGetListDetailFailed
|
||||||
list={isVisibleListDetail && animatePlayed ? listDetailData.list : []}
|
? <Failed />
|
||||||
page={-1}
|
: <OnlineList
|
||||||
// isEnd={listDetailData.isEnd}
|
list={isVisibleListDetail && animatePlayed ? listDetailData.list : []}
|
||||||
isListRefreshing={isListRefreshing}
|
page={-1}
|
||||||
onRefresh={handleListRefresh}
|
// isEnd={listDetailData.isEnd}
|
||||||
onLoadMore={handleListLoadMore}
|
isListRefreshing={isListRefreshing}
|
||||||
isLoading={listDetailData.isLoading}
|
onRefresh={handleListRefresh}
|
||||||
ListHeaderComponent={<ListDetailHeader />}
|
onLoadMore={handleListLoadMore}
|
||||||
|
isLoading={listDetailData.isLoading}
|
||||||
|
ListHeaderComponent={<ListDetailHeader />}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import { useGetter, useDispatch } from '@/store'
|
|||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
// const isGetDetailFailedRef = useRef(false)
|
|
||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
const [animatePlayed, setAnimatPlayed] = useState(true)
|
const [animatePlayed, setAnimatPlayed] = useState(true)
|
||||||
const animFade = useRef(new Animated.Value(0)).current
|
const animFade = useRef(new Animated.Value(0)).current
|
||||||
|
@ -15,6 +15,7 @@ export const TYPES = {
|
|||||||
setListDetailLoading: null,
|
setListDetailLoading: null,
|
||||||
setListEnd: null,
|
setListEnd: null,
|
||||||
setListDetailEnd: null,
|
setListDetailEnd: null,
|
||||||
|
setGetListDetailFailed: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(TYPES)) {
|
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 (isRefresh && cache.has(listKey)) cache.delete(listKey)
|
||||||
if (!cache.has(listKey)) cache.set(listKey, new Map())
|
if (!cache.has(listKey)) cache.set(listKey, new Map())
|
||||||
|
|
||||||
|
dispatch(setGetListDetailFailed(false))
|
||||||
const listCache = cache.get(listKey)
|
const listCache = cache.get(listKey)
|
||||||
if (listCache.has(pageKey)) {
|
if (listCache.has(pageKey)) {
|
||||||
return Promise.resolve(listCache.get(pageKey).data).then(result => dispatch(setListDetail({ result, listKey, pageKey, source, id, page })))
|
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 => {
|
return getListDetailLimit({ source, id, page }).then(result => {
|
||||||
dispatch(setListDetail({ result, listKey, pageKey, source, id, page }))
|
dispatch(setListDetail({ result, listKey, pageKey, source, id, page }))
|
||||||
// listCache.set(pageKey, result)
|
// listCache.set(pageKey, result)
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
if (page == 1) {
|
||||||
|
dispatch(setGetListDetailFailed(true))
|
||||||
|
}
|
||||||
|
return Promise.reject(err)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
const state = getState().songList
|
const state = getState().songList
|
||||||
if (state.listDetail.pageKey != pageKey) return
|
if (state.listDetail.pageKey != pageKey) return
|
||||||
@ -169,6 +177,12 @@ export const setSelectListInfo = info => {
|
|||||||
payload: info,
|
payload: info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export const setGetListDetailFailed = isFailed => {
|
||||||
|
return {
|
||||||
|
type: TYPES.setGetListDetailFailed,
|
||||||
|
payload: isFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
export const setTags = ({ tags, source }) => {
|
export const setTags = ({ tags, source }) => {
|
||||||
return {
|
return {
|
||||||
type: TYPES.setTags,
|
type: TYPES.setTags,
|
||||||
|
@ -14,6 +14,7 @@ export const sortList = state => state.songList.sortList
|
|||||||
export const tags = state => state.songList.tags
|
export const tags = state => state.songList.tags
|
||||||
|
|
||||||
export const isVisibleListDetail = state => state.songList.isVisibleListDetail
|
export const isVisibleListDetail = state => state.songList.isVisibleListDetail
|
||||||
|
export const isGetListDetailFailed = state => state.songList.isGetListDetailFailed
|
||||||
export const selectListInfo = state => state.songList.selectListInfo
|
export const selectListInfo = state => state.songList.selectListInfo
|
||||||
export const listData = state => state.songList.list
|
export const listData = state => state.songList.list
|
||||||
export const listDetailData = state => state.songList.listDetail
|
export const listDetailData = state => state.songList.listDetail
|
||||||
|
@ -37,6 +37,7 @@ const initialState = {
|
|||||||
},
|
},
|
||||||
selectListInfo: {},
|
selectListInfo: {},
|
||||||
isVisibleListDetail: false,
|
isVisibleListDetail: false,
|
||||||
|
isGetListDetailFailed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
sources.forEach(source => {
|
sources.forEach(source => {
|
||||||
@ -146,6 +147,12 @@ const mutations = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[TYPES.setGetListDetailFailed](state, isFailed) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isGetListDetailFailed: isFailed,
|
||||||
|
}
|
||||||
|
},
|
||||||
[TYPES.setListLoading](state, isLoading) {
|
[TYPES.setListLoading](state, isLoading) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user