新增通过歌单链接打开歌单的功能

This commit is contained in:
lyswhut 2021-05-22 15:56:42 +08:00
parent 0a6a6d9243
commit c0785190e0
13 changed files with 201 additions and 16 deletions

View File

@ -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...')

View File

@ -1,3 +1,7 @@
### 新增
- 新增通过歌单链接打开歌单的功能
### 优化
- 切换到播放详情歌词界面时将阻止屏幕息屏

View File

@ -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",

View File

@ -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": "收藏排行榜",

View File

@ -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>
)

View 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,
},
})

View File

@ -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: {

View 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

View File

@ -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,7 +30,9 @@ export default ({ animatePlayed }) => {
}, [getListDetail])
return (
<OnlineList
isGetListDetailFailed
? <Failed />
: <OnlineList
list={isVisibleListDetail && animatePlayed ? listDetailData.list : []}
page={-1}
// isEnd={listDetailData.isEnd}

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,