mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-05 17:38:55 +08:00
新增“不喜欢歌曲”功能
This commit is contained in:
parent
30765d551e
commit
050949f183
@ -13,6 +13,7 @@
|
|||||||
- 新增列表设置-是否显示歌曲专辑名,默认关闭
|
- 新增列表设置-是否显示歌曲专辑名,默认关闭
|
||||||
- 新增列表设置-是否显示歌曲时长,默认开启
|
- 新增列表设置-是否显示歌曲时长,默认开启
|
||||||
- 新增是否允许通过歌词调整播放进度功能,默认关闭,可到播放详情页右上角设置开启
|
- 新增是否允许通过歌词调整播放进度功能,默认关闭,可到播放详情页右上角设置开启
|
||||||
|
- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲
|
||||||
- 新增设置-播放设置-是否启用音频卸载,该设置之前默认是启用的,现在添加开关允许将其关闭,若出现播放器问题可尝试将其关闭
|
- 新增设置-播放设置-是否启用音频卸载,该设置之前默认是启用的,现在添加开关允许将其关闭,若出现播放器问题可尝试将其关闭
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useMemo, useRef, useImperativeHandle, forwardRef, useState } from 'react'
|
import { useMemo, useRef, useImperativeHandle, forwardRef, useState } from 'react'
|
||||||
import { useI18n } from '@/lang'
|
import { useI18n } from '@/lang'
|
||||||
import Menu, { type MenuType, type Position } from '@/components/common/Menu'
|
import Menu, { type MenuType, type Position } from '@/components/common/Menu'
|
||||||
|
import { hasDislike } from '@/core/dislikeList'
|
||||||
|
|
||||||
export interface SelectInfo {
|
export interface SelectInfo {
|
||||||
musicInfo: LX.Music.MusicInfoOnline
|
musicInfo: LX.Music.MusicInfoOnline
|
||||||
@ -15,6 +16,7 @@ export interface ListMenuProps {
|
|||||||
onPlayLater: (selectInfo: SelectInfo) => void
|
onPlayLater: (selectInfo: SelectInfo) => void
|
||||||
onAdd: (selectInfo: SelectInfo) => void
|
onAdd: (selectInfo: SelectInfo) => void
|
||||||
onCopyName: (selectInfo: SelectInfo) => void
|
onCopyName: (selectInfo: SelectInfo) => void
|
||||||
|
onDislikeMusic: (selectInfo: SelectInfo) => void
|
||||||
}
|
}
|
||||||
export interface ListMenuType {
|
export interface ListMenuType {
|
||||||
show: (selectInfo: SelectInfo, position: Position) => void
|
show: (selectInfo: SelectInfo, position: Position) => void
|
||||||
@ -29,10 +31,12 @@ export default forwardRef<ListMenuType, ListMenuProps>((props: ListMenuProps, re
|
|||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
const menuRef = useRef<MenuType>(null)
|
const menuRef = useRef<MenuType>(null)
|
||||||
const selectInfoRef = useRef<SelectInfo>(initSelectInfo as SelectInfo)
|
const selectInfoRef = useRef<SelectInfo>(initSelectInfo as SelectInfo)
|
||||||
|
const [isDislikeMusic, setDislikeMusic] = useState(false)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
show(selectInfo, position) {
|
show(selectInfo, position) {
|
||||||
selectInfoRef.current = selectInfo
|
selectInfoRef.current = selectInfo
|
||||||
|
setDislikeMusic(hasDislike(selectInfo.musicInfo))
|
||||||
if (visible) menuRef.current?.show(position)
|
if (visible) menuRef.current?.show(position)
|
||||||
else {
|
else {
|
||||||
setVisible(true)
|
setVisible(true)
|
||||||
@ -50,8 +54,9 @@ export default forwardRef<ListMenuType, ListMenuProps>((props: ListMenuProps, re
|
|||||||
// { action: 'download', label: '下载' },
|
// { action: 'download', label: '下载' },
|
||||||
{ action: 'add', label: t('add_to') },
|
{ action: 'add', label: t('add_to') },
|
||||||
{ action: 'copyName', label: t('copy_name') },
|
{ action: 'copyName', label: t('copy_name') },
|
||||||
|
{ action: 'dislike', label: t('dislike'), disabled: isDislikeMusic },
|
||||||
] as const
|
] as const
|
||||||
}, [t])
|
}, [t, isDislikeMusic])
|
||||||
|
|
||||||
const handleMenuPress = ({ action }: typeof menus[number]) => {
|
const handleMenuPress = ({ action }: typeof menus[number]) => {
|
||||||
const selectInfo = selectInfoRef.current
|
const selectInfo = selectInfoRef.current
|
||||||
@ -68,6 +73,9 @@ export default forwardRef<ListMenuType, ListMenuProps>((props: ListMenuProps, re
|
|||||||
case 'copyName':
|
case 'copyName':
|
||||||
props.onCopyName(selectInfo)
|
props.onCopyName(selectInfo)
|
||||||
break
|
break
|
||||||
|
case 'dislike':
|
||||||
|
props.onDislikeMusic(selectInfo)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import ListMenu, { type ListMenuType, type Position, type SelectInfo } from './L
|
|||||||
import ListMusicMultiAdd, { type MusicMultiAddModalType as ListAddMultiType } from '@/components/MusicMultiAddModal'
|
import ListMusicMultiAdd, { type MusicMultiAddModalType as ListAddMultiType } from '@/components/MusicMultiAddModal'
|
||||||
import ListMusicAdd, { type MusicAddModalType as ListMusicAddType } from '@/components/MusicAddModal'
|
import ListMusicAdd, { type MusicAddModalType as ListMusicAddType } from '@/components/MusicAddModal'
|
||||||
import MultipleModeBar, { type MultipleModeBarType, type SelectMode } from './MultipleModeBar'
|
import MultipleModeBar, { type MultipleModeBarType, type SelectMode } from './MultipleModeBar'
|
||||||
import { handlePlay, handlePlayLater, handleShare } from './listAction'
|
import { handleDislikeMusic, handlePlay, handlePlayLater, handleShare } from './listAction'
|
||||||
import { createStyle } from '@/utils/tools'
|
import { createStyle } from '@/utils/tools'
|
||||||
|
|
||||||
export interface OnlineListProps {
|
export interface OnlineListProps {
|
||||||
@ -109,6 +109,7 @@ export default forwardRef<OnlineListType, OnlineListProps>(({
|
|||||||
onPlayLater={info => { hancelExitSelect(); handlePlayLater(info.musicInfo, info.selectedList, hancelExitSelect) }}
|
onPlayLater={info => { hancelExitSelect(); handlePlayLater(info.musicInfo, info.selectedList, hancelExitSelect) }}
|
||||||
onCopyName={info => { handleShare(info.musicInfo) }}
|
onCopyName={info => { handleShare(info.musicInfo) }}
|
||||||
onAdd={handleAddMusic}
|
onAdd={handleAddMusic}
|
||||||
|
onDislikeMusic={info => { void handleDislikeMusic(info.musicInfo) }}
|
||||||
/>
|
/>
|
||||||
{/* <LoadingMask ref={loadingMaskRef} /> */}
|
{/* <LoadingMask ref={loadingMaskRef} /> */}
|
||||||
</View>
|
</View>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { LIST_IDS } from '@/config/constant'
|
import { LIST_IDS } from '@/config/constant'
|
||||||
import { addListMusics } from '@/core/list'
|
import { addListMusics } from '@/core/list'
|
||||||
import { playList } from '@/core/player/player'
|
import { playList, playNext } from '@/core/player/player'
|
||||||
import { addTempPlayList } from '@/core/player/tempPlayList'
|
import { addTempPlayList } from '@/core/player/tempPlayList'
|
||||||
import settingState from '@/store/setting/state'
|
import settingState from '@/store/setting/state'
|
||||||
import { getListMusicSync } from '@/utils/listManage'
|
import { getListMusicSync } from '@/utils/listManage'
|
||||||
import { shareMusic } from '@/utils/tools'
|
import { shareMusic } from '@/utils/tools'
|
||||||
|
import { addDislikeInfo, hasDislike } from '@/core/dislikeList'
|
||||||
|
import playerState from '@/store/player/state'
|
||||||
|
|
||||||
export const handlePlay = (musicInfo: LX.Music.MusicInfoOnline) => {
|
export const handlePlay = (musicInfo: LX.Music.MusicInfoOnline) => {
|
||||||
void addListMusics(LIST_IDS.DEFAULT, [musicInfo], settingState.setting['list.addMusicLocationType']).then(() => {
|
void addListMusics(LIST_IDS.DEFAULT, [musicInfo], settingState.setting['list.addMusicLocationType']).then(() => {
|
||||||
@ -27,3 +29,10 @@ export const handleShare = (musicInfo: LX.Music.MusicInfoOnline) => {
|
|||||||
shareMusic(settingState.setting['common.shareType'], settingState.setting['download.fileName'], musicInfo)
|
shareMusic(settingState.setting['common.shareType'], settingState.setting['download.fileName'], musicInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleDislikeMusic = async(musicInfo: LX.Music.MusicInfoOnline) => {
|
||||||
|
await addDislikeInfo([{ name: musicInfo.name, singer: musicInfo.singer }])
|
||||||
|
if (hasDislike(playerState.playMusicInfo.musicInfo)) {
|
||||||
|
void playNext(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ export interface DialogProps {
|
|||||||
closeBtn?: boolean
|
closeBtn?: boolean
|
||||||
title?: string
|
title?: string
|
||||||
children: React.ReactNode | React.ReactNode[]
|
children: React.ReactNode | React.ReactNode[]
|
||||||
|
height?: number | `${number}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogType {
|
export interface DialogType {
|
||||||
@ -76,6 +77,7 @@ export default forwardRef<DialogType, DialogProps>(({
|
|||||||
closeBtn = true,
|
closeBtn = true,
|
||||||
title = '',
|
title = '',
|
||||||
children,
|
children,
|
||||||
|
height,
|
||||||
}: DialogProps, ref) => {
|
}: DialogProps, ref) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { keyboardShown, keyboardHeight } = useKeyboard()
|
const { keyboardShown, keyboardHeight } = useKeyboard()
|
||||||
@ -98,7 +100,7 @@ export default forwardRef<DialogType, DialogProps>(({
|
|||||||
return (
|
return (
|
||||||
<Modal onHide={onHide} keyHide={keyHide} bgHide={bgHide} bgColor="rgba(50,50,50,.3)" ref={modalRef}>
|
<Modal onHide={onHide} keyHide={keyHide} bgHide={bgHide} bgColor="rgba(50,50,50,.3)" ref={modalRef}>
|
||||||
<View style={{ ...styles.centeredView, paddingBottom: keyboardShown ? keyboardHeight : 0 }}>
|
<View style={{ ...styles.centeredView, paddingBottom: keyboardShown ? keyboardHeight : 0 }}>
|
||||||
<View style={{ ...styles.modalView, backgroundColor: theme['c-content-background'] }} onStartShouldSetResponder={() => true}>
|
<View style={{ ...styles.modalView, height, backgroundColor: theme['c-content-background'] }} onStartShouldSetResponder={() => true}>
|
||||||
<View style={{ ...styles.header, backgroundColor: theme['c-primary-light-100-alpha-100'] }}>
|
<View style={{ ...styles.header, backgroundColor: theme['c-primary-light-100-alpha-100'] }}>
|
||||||
<Text style={styles.title} size={13} color={theme['c-primary-light-1000']} numberOfLines={1}>{title}</Text>
|
<Text style={styles.title} size={13} color={theme['c-primary-light-1000']} numberOfLines={1}>{title}</Text>
|
||||||
{closeBtnComponent}
|
{closeBtnComponent}
|
||||||
|
@ -3,6 +3,7 @@ import { TextInput, View, TouchableOpacity, StyleSheet, type TextInputProps } fr
|
|||||||
import { Icon } from '@/components/common/Icon'
|
import { Icon } from '@/components/common/Icon'
|
||||||
import { createStyle } from '@/utils/tools'
|
import { createStyle } from '@/utils/tools'
|
||||||
import { useTheme } from '@/store/theme/hook'
|
import { useTheme } from '@/store/theme/hook'
|
||||||
|
import { setSpText } from '@/utils/pixelRatio'
|
||||||
|
|
||||||
const styles = createStyle({
|
const styles = createStyle({
|
||||||
content: {
|
content: {
|
||||||
@ -46,6 +47,7 @@ export interface InputProps extends TextInputProps {
|
|||||||
onChangeText?: (value: string) => void
|
onChangeText?: (value: string) => void
|
||||||
onClearText?: () => void
|
onClearText?: () => void
|
||||||
clearBtn?: boolean
|
clearBtn?: boolean
|
||||||
|
size?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ export interface InputType {
|
|||||||
isFocused: () => boolean
|
isFocused: () => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default forwardRef<InputType, InputProps>(({ onChangeText, onClearText, clearBtn, style, ...props }, ref) => {
|
export default forwardRef<InputType, InputProps>(({ onChangeText, onClearText, clearBtn, style, size = 14, ...props }, ref) => {
|
||||||
const inputRef = useRef<TextInput>(null)
|
const inputRef = useRef<TextInput>(null)
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
// const scaleClearBtn = useRef(new Animated.Value(0)).current
|
// const scaleClearBtn = useRef(new Animated.Value(0)).current
|
||||||
@ -113,7 +115,7 @@ export default forwardRef<InputType, InputProps>(({ onChangeText, onClearText, c
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
onChangeText={changeText}
|
onChangeText={changeText}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
style={StyleSheet.compose({ ...styles.input, color: theme['c-font'] }, style)}
|
style={StyleSheet.compose({ ...styles.input, color: theme['c-font'], fontSize: setSpText(size) }, style)}
|
||||||
placeholderTextColor={theme['c-primary-dark-100-alpha-600']}
|
placeholderTextColor={theme['c-primary-dark-100-alpha-600']}
|
||||||
selectionColor={theme['c-primary-light-100-alpha-300']}
|
selectionColor={theme['c-primary-light-100-alpha-300']}
|
||||||
ref={inputRef} {...props} />
|
ref={inputRef} {...props} />
|
||||||
|
@ -2,6 +2,11 @@ export const HEADER_HEIGHT = 42
|
|||||||
export const LIST_ITEM_HEIGHT = 54
|
export const LIST_ITEM_HEIGHT = 54
|
||||||
export const LIST_SCROLL_POSITION_KEY = '__LIST_SCROLL_POSITION_KEY__'
|
export const LIST_SCROLL_POSITION_KEY = '__LIST_SCROLL_POSITION_KEY__'
|
||||||
|
|
||||||
|
export const SPLIT_CHAR = {
|
||||||
|
DISLIKE_NAME: '@',
|
||||||
|
DISLIKE_NAME_ALIAS: '#',
|
||||||
|
} as const
|
||||||
|
|
||||||
export const LIST_IDS = {
|
export const LIST_IDS = {
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
LOVE: 'love',
|
LOVE: 'love',
|
||||||
@ -65,6 +70,8 @@ export const storageDataPrefix = {
|
|||||||
theme: '@theme',
|
theme: '@theme',
|
||||||
|
|
||||||
cheatTip: '@cheat_tip',
|
cheatTip: '@cheat_tip',
|
||||||
|
|
||||||
|
dislikeList: '@dislike_list',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
// v0.x.x 版本的 data keys
|
// v0.x.x 版本的 data keys
|
||||||
|
28
src/core/dislikeList.ts
Normal file
28
src/core/dislikeList.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// import { toRaw } from '@common/utils/vueTools'
|
||||||
|
import { SPLIT_CHAR } from '@/config/constant'
|
||||||
|
import { action, state } from '@/store/dislikeList'
|
||||||
|
import { getDislikeListRules, saveDislikeListRules } from '@/utils/data'
|
||||||
|
|
||||||
|
|
||||||
|
export const initDislikeInfo = async() => {
|
||||||
|
const rules = await getDislikeListRules()
|
||||||
|
action.initDislikeInfo(rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addDislikeInfo = async(infos: LX.Dislike.DislikeMusicInfo[]) => {
|
||||||
|
const rules = state.dislikeInfo.rules += '\n' + infos.map(info => `${info.name ?? ''}${SPLIT_CHAR.DISLIKE_NAME}${info.singer ?? ''}`).join('\n')
|
||||||
|
await saveDislikeListRules(rules)
|
||||||
|
return action.overwirteDislikeInfo(rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const overwirteDislikeInfo = async(rules: string) => {
|
||||||
|
await saveDislikeListRules(rules)
|
||||||
|
return action.overwirteDislikeInfo(rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const hasDislike = (info: LX.Music.MusicInfo | LX.Download.ListItem | null) => {
|
||||||
|
if (!info) return false
|
||||||
|
return action.hasDislike(info)
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { getUserLists, setUserList } from '@/core/list'
|
|||||||
import { setNavActiveId } from '../common'
|
import { setNavActiveId } from '../common'
|
||||||
import { getViewPrevState } from '@/utils/data'
|
import { getViewPrevState } from '@/utils/data'
|
||||||
import { bootLog } from '@/utils/bootLog'
|
import { bootLog } from '@/utils/bootLog'
|
||||||
|
import { initDislikeInfo } from '@/core/dislikeList'
|
||||||
// import { play, playList } from '../player/player'
|
// import { play, playList } from '../player/player'
|
||||||
|
|
||||||
// const initPrevPlayInfo = async(appSetting: LX.AppSetting) => {
|
// const initPrevPlayInfo = async(appSetting: LX.AppSetting) => {
|
||||||
@ -26,6 +27,7 @@ export default async(appSetting: LX.AppSetting) => {
|
|||||||
void musicSdkInit() // 初始化音乐sdk
|
void musicSdkInit() // 初始化音乐sdk
|
||||||
bootLog('User list init...')
|
bootLog('User list init...')
|
||||||
setUserList(await getUserLists()) // 获取用户列表
|
setUserList(await getUserLists()) // 获取用户列表
|
||||||
|
await initDislikeInfo() // 获取不喜欢列表
|
||||||
bootLog('User list inited.')
|
bootLog('User list inited.')
|
||||||
setNavActiveId((await getViewPrevState()).id)
|
setNavActiveId((await getViewPrevState()).id)
|
||||||
// await initPrevPlayInfo(appSetting).catch(err => log.error(err)) // 初始化上次的歌曲播放信息
|
// await initPrevPlayInfo(appSetting).catch(err => log.error(err)) // 初始化上次的歌曲播放信息
|
||||||
|
@ -317,6 +317,7 @@ export const playNext = async(isAutoToggle = false): Promise<void> => {
|
|||||||
list: currentList,
|
list: currentList,
|
||||||
playedList,
|
playedList,
|
||||||
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
||||||
|
isNext: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!filteredList.length) return handleToggleStop()
|
if (!filteredList.length) return handleToggleStop()
|
||||||
@ -410,6 +411,7 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
|||||||
list: currentList,
|
list: currentList,
|
||||||
playedList,
|
playedList,
|
||||||
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
playerMusicInfo: currentList[playInfo.playerPlayIndex],
|
||||||
|
isNext: false,
|
||||||
})
|
})
|
||||||
if (!filteredList.length) return handleToggleStop()
|
if (!filteredList.length) return handleToggleStop()
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { clearPlayedList } from './playedList'
|
import { clearPlayedList } from './playedList'
|
||||||
|
import { SPLIT_CHAR } from '@/config/constant'
|
||||||
|
import { state } from '@/store/dislikeList'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过滤列表中已播放的歌曲
|
* 过滤列表中已播放的歌曲
|
||||||
*/
|
*/
|
||||||
export const filterMusicList = ({ playedList, listId, list, playerMusicInfo }: {
|
export const filterMusicList = ({ playedList, listId, list, playerMusicInfo, dislikeInfo, isNext }: {
|
||||||
/**
|
/**
|
||||||
* 已播放列表
|
* 已播放列表
|
||||||
*/
|
*/
|
||||||
@ -24,15 +26,35 @@ export const filterMusicList = ({ playedList, listId, list, playerMusicInfo }: {
|
|||||||
* 播放器内当前歌曲(`playInfo.playerPlayIndex`指向的歌曲)
|
* 播放器内当前歌曲(`playInfo.playerPlayIndex`指向的歌曲)
|
||||||
*/
|
*/
|
||||||
playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem
|
playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不喜欢的歌曲名字列表
|
||||||
|
*/
|
||||||
|
dislikeInfo: Omit<LX.Dislike.DislikeInfo, 'rules'>
|
||||||
|
|
||||||
|
isNext: boolean
|
||||||
}) => {
|
}) => {
|
||||||
let playerIndex = -1
|
let playerIndex = -1
|
||||||
|
|
||||||
let canPlayList: Array<LX.Music.MusicInfo | LX.Download.ListItem> = []
|
let canPlayList: Array<LX.Music.MusicInfo | LX.Download.ListItem> = []
|
||||||
const filteredPlayedList = playedList.filter(pmInfo => pmInfo.listId == listId && !pmInfo.isTempPlay).map(({ musicInfo }) => musicInfo)
|
const filteredPlayedList = playedList.filter(pmInfo => pmInfo.listId == listId && !pmInfo.isTempPlay).map(({ musicInfo }) => musicInfo)
|
||||||
|
const hasDislike = (info: LX.Music.MusicInfo) => {
|
||||||
|
const name = info.name?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? ''
|
||||||
|
const singer = info.singer?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? ''
|
||||||
|
|
||||||
|
return dislikeInfo.musicNames.has(name) || dislikeInfo.singerNames.has(singer) ||
|
||||||
|
dislikeInfo.names.has(`${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let isDislike = false
|
||||||
const filteredList: Array<LX.Music.MusicInfo | LX.Download.ListItem> = list.filter(s => {
|
const filteredList: Array<LX.Music.MusicInfo | LX.Download.ListItem> = list.filter(s => {
|
||||||
// if (!assertApiSupport(s.source)) return false
|
// if (!assertApiSupport(s.source)) return false
|
||||||
if ('progress' in s && !s.isComplate) return false
|
if ('progress' in s) {
|
||||||
|
if (!s.isComplate) return false
|
||||||
|
} else if (hasDislike(s)) {
|
||||||
|
if (s.id != playerMusicInfo?.id) return false
|
||||||
|
isDislike = true
|
||||||
|
}
|
||||||
|
|
||||||
canPlayList.push(s)
|
canPlayList.push(s)
|
||||||
|
|
||||||
@ -44,7 +66,34 @@ export const filterMusicList = ({ playedList, listId, list, playerMusicInfo }: {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if (playerMusicInfo) {
|
if (playerMusicInfo) {
|
||||||
playerIndex = (filteredList.length ? filteredList : canPlayList).findIndex(m => m.id == playerMusicInfo.id)
|
if (isDislike) {
|
||||||
|
if (filteredList.length <= 1) {
|
||||||
|
filteredList.splice(0, 1)
|
||||||
|
if (canPlayList.length > 1) {
|
||||||
|
let currentMusicIndex = canPlayList.findIndex(m => m.id == playerMusicInfo.id)
|
||||||
|
if (isNext) {
|
||||||
|
playerIndex = currentMusicIndex - 1
|
||||||
|
if (playerIndex < 0 && canPlayList.length > 1) playerIndex = canPlayList.length - 2
|
||||||
|
} else {
|
||||||
|
playerIndex = currentMusicIndex
|
||||||
|
if (canPlayList.length <= 1) playerIndex = -1
|
||||||
|
}
|
||||||
|
canPlayList.splice(currentMusicIndex, 1)
|
||||||
|
} else canPlayList.splice(0, 1)
|
||||||
|
} else {
|
||||||
|
let currentMusicIndex = filteredList.findIndex(m => m.id == playerMusicInfo.id)
|
||||||
|
if (isNext) {
|
||||||
|
playerIndex = currentMusicIndex - 1
|
||||||
|
if (playerIndex < 0 && filteredList.length > 1) playerIndex = filteredList.length - 2
|
||||||
|
} else {
|
||||||
|
playerIndex = currentMusicIndex
|
||||||
|
if (filteredList.length <= 1) playerIndex = -1
|
||||||
|
}
|
||||||
|
filteredList.splice(currentMusicIndex, 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playerIndex = (filteredList.length ? filteredList : canPlayList).findIndex(m => m.id == playerMusicInfo.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
filteredList,
|
filteredList,
|
||||||
@ -56,11 +105,12 @@ export const filterMusicList = ({ playedList, listId, list, playerMusicInfo }: {
|
|||||||
/**
|
/**
|
||||||
* 过滤列表中已播放的歌曲
|
* 过滤列表中已播放的歌曲
|
||||||
*/
|
*/
|
||||||
export const filterList = ({ playedList, listId, list, playerMusicInfo }: {
|
export const filterList = ({ playedList, listId, list, playerMusicInfo, isNext }: {
|
||||||
playedList: LX.Player.PlayMusicInfo[] | readonly LX.Player.PlayMusicInfo[]
|
playedList: LX.Player.PlayMusicInfo[] | readonly LX.Player.PlayMusicInfo[]
|
||||||
listId: string
|
listId: string
|
||||||
list: Array<LX.Music.MusicInfo | LX.Download.ListItem>
|
list: Array<LX.Music.MusicInfo | LX.Download.ListItem>
|
||||||
playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem
|
playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem
|
||||||
|
isNext: boolean
|
||||||
}) => {
|
}) => {
|
||||||
// if (this.list.listName === null) return
|
// if (this.list.listName === null) return
|
||||||
// console.log(isCheckFile)
|
// console.log(isCheckFile)
|
||||||
@ -70,6 +120,8 @@ export const filterList = ({ playedList, listId, list, playerMusicInfo }: {
|
|||||||
playedList,
|
playedList,
|
||||||
// savePath: global.lx.setting['download.savePath'],
|
// savePath: global.lx.setting['download.savePath'],
|
||||||
playerMusicInfo,
|
playerMusicInfo,
|
||||||
|
dislikeInfo: { names: state.dislikeInfo.names, musicNames: state.dislikeInfo.musicNames, singerNames: state.dislikeInfo.singerNames },
|
||||||
|
isNext,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!filteredList.length && playedList.length) {
|
if (!filteredList.length && playedList.length) {
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"dialog_confirm": "OK",
|
"dialog_confirm": "OK",
|
||||||
"disagree": "Disagree",
|
"disagree": "Disagree",
|
||||||
"disagree_tip": "Cancelled...",
|
"disagree_tip": "Cancelled...",
|
||||||
|
"dislike": "Dislike",
|
||||||
"duplicate_list_tip": "You have previously favorited the list [{name}], do you want to update the songs?",
|
"duplicate_list_tip": "You have previously favorited the list [{name}], do you want to update the songs?",
|
||||||
"exit_app_tip": "Are you sure you want to quit the app?",
|
"exit_app_tip": "Are you sure you want to quit the app?",
|
||||||
"input_error": "Don't input indiscriminately 😡",
|
"input_error": "Don't input indiscriminately 😡",
|
||||||
@ -142,6 +143,10 @@
|
|||||||
"search_hot_search": "popular searches",
|
"search_hot_search": "popular searches",
|
||||||
"search_type_music": "Music",
|
"search_type_music": "Music",
|
||||||
"search_type_songlist": "Song list",
|
"search_type_songlist": "Song list",
|
||||||
|
"setting_dislike_list_tips": "1. If there is a \"@\" symbol in the song or singer's name, you need to replace it with \"#\"\n2. Specify a song of a singer: <Name>@<Singer>\n3. Specify a song: <Name>\n4. Specify a certain singer:@<Singer>",
|
||||||
|
"setting__other_dislike_list": "Dislike song rule",
|
||||||
|
"setting__other_dislike_list_label": "Number of rules: {num}",
|
||||||
|
"setting__other_dislike_list_saved_tip": "Saved",
|
||||||
"setting__other_lyric_raw_clear_btn": "Clear lyrics cache",
|
"setting__other_lyric_raw_clear_btn": "Clear lyrics cache",
|
||||||
"setting__other_lyric_raw_label": "Number of lyrics:",
|
"setting__other_lyric_raw_label": "Number of lyrics:",
|
||||||
"setting__other_meta_cache": "Other cache management",
|
"setting__other_meta_cache": "Other cache management",
|
||||||
@ -240,6 +245,7 @@
|
|||||||
"setting_other_cache_clear_success_tip": "Cache clearing completed",
|
"setting_other_cache_clear_success_tip": "Cache clearing completed",
|
||||||
"setting_other_cache_getting": "Statistics cached...",
|
"setting_other_cache_getting": "Statistics cached...",
|
||||||
"setting_other_cache_size": "Currently used cache size: ",
|
"setting_other_cache_size": "Currently used cache size: ",
|
||||||
|
"setting_other_dislike_list_show_btn": "Edit dislike song rules",
|
||||||
"setting_other_log": "Error log (log when abnormal operation occurs)",
|
"setting_other_log": "Error log (log when abnormal operation occurs)",
|
||||||
"setting_other_log_btn_clean": "Clear",
|
"setting_other_log_btn_clean": "Clear",
|
||||||
"setting_other_log_btn_hide": "Close",
|
"setting_other_log_btn_hide": "Close",
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
"dialog_confirm": "好的",
|
"dialog_confirm": "好的",
|
||||||
"disagree": "我就不",
|
"disagree": "我就不",
|
||||||
"disagree_tip": "那算了... 🙄",
|
"disagree_tip": "那算了... 🙄",
|
||||||
|
"dislike": "不喜欢",
|
||||||
"duplicate_list_tip": "你之前已收藏过该列表 [{name}],是否需要更新里面的歌曲?",
|
"duplicate_list_tip": "你之前已收藏过该列表 [{name}],是否需要更新里面的歌曲?",
|
||||||
"exit_app_tip": "确定要退出应用吗?",
|
"exit_app_tip": "确定要退出应用吗?",
|
||||||
"input_error": "不要乱输好吧😡",
|
"input_error": "不要乱输好吧😡",
|
||||||
@ -142,6 +143,9 @@
|
|||||||
"search_hot_search": "热门搜索",
|
"search_hot_search": "热门搜索",
|
||||||
"search_type_music": "歌曲",
|
"search_type_music": "歌曲",
|
||||||
"search_type_songlist": "歌单",
|
"search_type_songlist": "歌单",
|
||||||
|
"setting__other_dislike_list": "不喜欢的歌曲规则",
|
||||||
|
"setting__other_dislike_list_label": "规则数量:{num}",
|
||||||
|
"setting__other_dislike_list_saved_tip": "已保存",
|
||||||
"setting__other_lyric_raw_clear_btn": "清理歌词缓存",
|
"setting__other_lyric_raw_clear_btn": "清理歌词缓存",
|
||||||
"setting__other_lyric_raw_label": "歌词数量:",
|
"setting__other_lyric_raw_label": "歌词数量:",
|
||||||
"setting__other_meta_cache": "其他缓存管理",
|
"setting__other_meta_cache": "其他缓存管理",
|
||||||
@ -234,12 +238,14 @@
|
|||||||
"setting_lyric_desktop_theme": "歌词主题色",
|
"setting_lyric_desktop_theme": "歌词主题色",
|
||||||
"setting_lyric_desktop_toggle_anima": "显示歌词切换动画",
|
"setting_lyric_desktop_toggle_anima": "显示歌词切换动画",
|
||||||
"setting_lyric_desktop_view_width": "窗口百分比宽度",
|
"setting_lyric_desktop_view_width": "窗口百分比宽度",
|
||||||
|
"setting_dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:<歌曲名>@<歌手名>\n3. 指定某首歌:<歌曲名>\n4. 指定某歌手:@<歌手名>",
|
||||||
"setting_other": "其他",
|
"setting_other": "其他",
|
||||||
"setting_other_cache": "缓存管理(包括歌曲、歌词、错误日志等缓存,没有歌曲播放相关的问题不建议清理)",
|
"setting_other_cache": "缓存管理(包括歌曲、歌词、错误日志等缓存,没有歌曲播放相关的问题不建议清理)",
|
||||||
"setting_other_cache_clear_btn": "清理缓存",
|
"setting_other_cache_clear_btn": "清理缓存",
|
||||||
"setting_other_cache_clear_success_tip": "缓存清理完成",
|
"setting_other_cache_clear_success_tip": "缓存清理完成",
|
||||||
"setting_other_cache_getting": "统计缓存中...",
|
"setting_other_cache_getting": "统计缓存中...",
|
||||||
"setting_other_cache_size": "当前已用缓存大小:",
|
"setting_other_cache_size": "当前已用缓存大小:",
|
||||||
|
"setting_other_dislike_list_show_btn": "编辑不喜欢歌曲规则",
|
||||||
"setting_other_log": "错误日志(运行发生异常时的日志)",
|
"setting_other_log": "错误日志(运行发生异常时的日志)",
|
||||||
"setting_other_log_btn_clean": "清空",
|
"setting_other_log_btn_clean": "清空",
|
||||||
"setting_other_log_btn_hide": "关闭",
|
"setting_other_log_btn_hide": "关闭",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useMemo, useRef, useImperativeHandle, forwardRef, useState } from 'react'
|
import { useMemo, useRef, useImperativeHandle, forwardRef, useState } from 'react'
|
||||||
import { useI18n } from '@/lang'
|
import { useI18n } from '@/lang'
|
||||||
import Menu, { type MenuType, type Position } from '@/components/common/Menu'
|
import Menu, { type MenuType, type Position } from '@/components/common/Menu'
|
||||||
|
import { hasDislike } from '@/core/dislikeList'
|
||||||
|
|
||||||
export interface SelectInfo {
|
export interface SelectInfo {
|
||||||
musicInfo: LX.Music.MusicInfo
|
musicInfo: LX.Music.MusicInfo
|
||||||
@ -18,6 +19,7 @@ export interface ListMenuProps {
|
|||||||
onMove: (selectInfo: SelectInfo) => void
|
onMove: (selectInfo: SelectInfo) => void
|
||||||
onCopyName: (selectInfo: SelectInfo) => void
|
onCopyName: (selectInfo: SelectInfo) => void
|
||||||
onChangePosition: (selectInfo: SelectInfo) => void
|
onChangePosition: (selectInfo: SelectInfo) => void
|
||||||
|
onDislikeMusic: (selectInfo: SelectInfo) => void
|
||||||
onRemove: (selectInfo: SelectInfo) => void
|
onRemove: (selectInfo: SelectInfo) => void
|
||||||
}
|
}
|
||||||
export interface ListMenuType {
|
export interface ListMenuType {
|
||||||
@ -33,10 +35,12 @@ export default forwardRef<ListMenuType, ListMenuProps>((props, ref) => {
|
|||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
const menuRef = useRef<MenuType>(null)
|
const menuRef = useRef<MenuType>(null)
|
||||||
const selectInfoRef = useRef<SelectInfo>(initSelectInfo as SelectInfo)
|
const selectInfoRef = useRef<SelectInfo>(initSelectInfo as SelectInfo)
|
||||||
|
const [isDislikeMusic, setDislikeMusic] = useState(false)
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
show(selectInfo, position) {
|
show(selectInfo, position) {
|
||||||
selectInfoRef.current = selectInfo
|
selectInfoRef.current = selectInfo
|
||||||
|
setDislikeMusic(hasDislike(selectInfo.musicInfo))
|
||||||
if (visible) menuRef.current?.show(position)
|
if (visible) menuRef.current?.show(position)
|
||||||
else {
|
else {
|
||||||
setVisible(true)
|
setVisible(true)
|
||||||
@ -56,9 +60,10 @@ export default forwardRef<ListMenuType, ListMenuProps>((props, ref) => {
|
|||||||
{ action: 'move', label: t('move_to') },
|
{ action: 'move', label: t('move_to') },
|
||||||
{ action: 'copyName', label: t('copy_name') },
|
{ action: 'copyName', label: t('copy_name') },
|
||||||
{ action: 'changePosition', label: t('change_position') },
|
{ action: 'changePosition', label: t('change_position') },
|
||||||
|
{ action: 'dislike', label: t('dislike'), disabled: isDislikeMusic },
|
||||||
{ action: 'remove', label: t('delete') },
|
{ action: 'remove', label: t('delete') },
|
||||||
] as const
|
] as const
|
||||||
}, [t])
|
}, [t, isDislikeMusic])
|
||||||
|
|
||||||
const handleMenuPress = ({ action }: typeof menus[number]) => {
|
const handleMenuPress = ({ action }: typeof menus[number]) => {
|
||||||
const selectInfo = selectInfoRef.current
|
const selectInfo = selectInfoRef.current
|
||||||
@ -91,6 +96,9 @@ export default forwardRef<ListMenuType, ListMenuProps>((props, ref) => {
|
|||||||
props.onChangePosition(selectInfo)
|
props.onChangePosition(selectInfo)
|
||||||
// setVIsibleMusicPosition(true)
|
// setVIsibleMusicPosition(true)
|
||||||
break
|
break
|
||||||
|
case 'dislike':
|
||||||
|
props.onDislikeMusic(selectInfo)
|
||||||
|
break
|
||||||
case 'remove':
|
case 'remove':
|
||||||
props.onRemove(selectInfo)
|
props.onRemove(selectInfo)
|
||||||
break
|
break
|
||||||
|
@ -2,7 +2,7 @@ import { useRef } from 'react'
|
|||||||
|
|
||||||
import listState from '@/store/list/state'
|
import listState from '@/store/list/state'
|
||||||
import ListMenu, { type ListMenuType, type Position, type SelectInfo } from './ListMenu'
|
import ListMenu, { type ListMenuType, type Position, type SelectInfo } from './ListMenu'
|
||||||
import { handlePlay, handlePlayLater, handleRemove, handleShare, handleUpdateMusicPosition } from './listAction'
|
import { handleDislikeMusic, handlePlay, handlePlayLater, handleRemove, handleShare, handleUpdateMusicPosition } from './listAction'
|
||||||
import List, { type ListType } from './List'
|
import List, { type ListType } from './List'
|
||||||
import ListMusicAdd, { type MusicAddModalType as ListMusicAddType } from '@/components/MusicAddModal'
|
import ListMusicAdd, { type MusicAddModalType as ListMusicAddType } from '@/components/MusicAddModal'
|
||||||
import ListMusicMultiAdd, { type MusicMultiAddModalType as ListAddMultiType } from '@/components/MusicMultiAddModal'
|
import ListMusicMultiAdd, { type MusicMultiAddModalType as ListAddMultiType } from '@/components/MusicMultiAddModal'
|
||||||
@ -139,6 +139,7 @@ export default () => {
|
|||||||
onPlay={info => { handlePlay(info.listId, info.index) }}
|
onPlay={info => { handlePlay(info.listId, info.index) }}
|
||||||
onPlayLater={info => { hancelExitSelect(); handlePlayLater(info.listId, info.musicInfo, info.selectedList, hancelExitSelect) }}
|
onPlayLater={info => { hancelExitSelect(); handlePlayLater(info.listId, info.musicInfo, info.selectedList, hancelExitSelect) }}
|
||||||
onRemove={info => { hancelExitSelect(); handleRemove(info.listId, info.musicInfo, info.selectedList, hancelExitSelect) }}
|
onRemove={info => { hancelExitSelect(); handleRemove(info.listId, info.musicInfo, info.selectedList, hancelExitSelect) }}
|
||||||
|
onDislikeMusic={info => { void handleDislikeMusic(info.musicInfo) }}
|
||||||
onCopyName={info => { handleShare(info.musicInfo) }}
|
onCopyName={info => { handleShare(info.musicInfo) }}
|
||||||
onAdd={handleAddMusic}
|
onAdd={handleAddMusic}
|
||||||
onMove={handleMoveMusic}
|
onMove={handleMoveMusic}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { removeListMusics, updateListMusicPosition } from '@/core/list'
|
import { removeListMusics, updateListMusicPosition } from '@/core/list'
|
||||||
import { playList } from '@/core/player/player'
|
import { playList, playNext } from '@/core/player/player'
|
||||||
import { addTempPlayList } from '@/core/player/tempPlayList'
|
import { addTempPlayList } from '@/core/player/tempPlayList'
|
||||||
import settingState from '@/store/setting/state'
|
import settingState from '@/store/setting/state'
|
||||||
import { similar, sortInsert } from '@/utils'
|
import { similar, sortInsert } from '@/utils'
|
||||||
import { confirmDialog, shareMusic } from '@/utils/tools'
|
import { confirmDialog, shareMusic } from '@/utils/tools'
|
||||||
|
import { addDislikeInfo, hasDislike } from '@/core/dislikeList'
|
||||||
|
import playerState from '@/store/player/state'
|
||||||
|
|
||||||
import type { SelectInfo } from './ListMenu'
|
import type { SelectInfo } from './ListMenu'
|
||||||
|
|
||||||
@ -69,3 +71,9 @@ export const searchListMusic = (list: LX.Music.MusicInfo[], text: string) => {
|
|||||||
return sortedList.map(item => item.data).reverse()
|
return sortedList.map(item => item.data).reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleDislikeMusic = async(musicInfo: SelectInfo['musicInfo']) => {
|
||||||
|
await addDislikeInfo([{ name: musicInfo.name, singer: musicInfo.singer }])
|
||||||
|
if (hasDislike(playerState.playMusicInfo.musicInfo)) {
|
||||||
|
void playNext(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,180 @@
|
|||||||
|
import { useRef, useImperativeHandle, forwardRef, useState, useCallback } from 'react'
|
||||||
|
import Text from '@/components/common/Text'
|
||||||
|
import { type LayoutChangeEvent, View } from 'react-native'
|
||||||
|
import Input, { type InputType } from '@/components/common/Input'
|
||||||
|
import { createStyle } from '@/utils/tools'
|
||||||
|
import { useTheme } from '@/store/theme/hook'
|
||||||
|
import { useI18n } from '@/lang'
|
||||||
|
import Dialog, { type DialogType } from '@/components/common/Dialog'
|
||||||
|
import Button from '@/components/common/Button'
|
||||||
|
|
||||||
|
interface RuleInputType {
|
||||||
|
setText: (text: string) => void
|
||||||
|
getText: () => string
|
||||||
|
focus: () => void
|
||||||
|
}
|
||||||
|
const RuleInput = forwardRef<RuleInputType, {}>((props, ref) => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const t = useI18n()
|
||||||
|
const [text, setText] = useState('')
|
||||||
|
const inputRef = useRef<InputType>(null)
|
||||||
|
const [height, setHeight] = useState(100)
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getText() {
|
||||||
|
return text.trim()
|
||||||
|
},
|
||||||
|
setText(text) {
|
||||||
|
setText(text)
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
|
||||||
|
setHeight(nativeEvent.layout.height)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.inputContent} onLayout={handleLayout}>
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder={t('songlist_open_input_placeholder')}
|
||||||
|
value={text}
|
||||||
|
onChangeText={setText}
|
||||||
|
multiline
|
||||||
|
textAlignVertical="top"
|
||||||
|
size={12}
|
||||||
|
style={{ ...styles.input, height, backgroundColor: theme['c-primary-input-background'] }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export interface DislikeEditModalProps {
|
||||||
|
onSave: (rules: string) => void
|
||||||
|
// onSourceChange: SourceSelectorProps['onSourceChange']
|
||||||
|
}
|
||||||
|
export interface DislikeEditModalType {
|
||||||
|
show: (rules: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default forwardRef<DislikeEditModalType, DislikeEditModalProps>(({ onSave }, ref) => {
|
||||||
|
const dialogRef = useRef<DialogType>(null)
|
||||||
|
// const sourceSelectorRef = useRef<SourceSelectorType>(null)
|
||||||
|
const inputRef = useRef<RuleInputType>(null)
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
const theme = useTheme()
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const handleShow = (rules: string) => {
|
||||||
|
dialogRef.current?.setVisible(true)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
inputRef.current?.setText(rules)
|
||||||
|
// sourceSelectorRef.current?.setSource(source)
|
||||||
|
// setTimeout(() => {
|
||||||
|
// inputRef.current?.focus()
|
||||||
|
// }, 300)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
show(rules) {
|
||||||
|
if (visible) handleShow(rules)
|
||||||
|
else {
|
||||||
|
setVisible(true)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
handleShow(rules)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
dialogRef.current?.setVisible(false)
|
||||||
|
}
|
||||||
|
const handleConfirm = () => {
|
||||||
|
let rules = inputRef.current?.getText() ?? ''
|
||||||
|
handleCancel()
|
||||||
|
onSave(rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
visible
|
||||||
|
? (
|
||||||
|
<Dialog height='80%' ref={dialogRef} bgHide={false}>
|
||||||
|
<View style={styles.content}>
|
||||||
|
<RuleInput ref={inputRef} />
|
||||||
|
<Text style={styles.inputTipText} size={12} color={theme['c-600']}>{t('setting_dislike_list_tips')}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.btns}>
|
||||||
|
<Button style={{ ...styles.btn, backgroundColor: theme['c-button-background'] }} onPress={handleCancel}>
|
||||||
|
<Text size={14} color={theme['c-button-font']}>{t('cancel')}</Text>
|
||||||
|
</Button>
|
||||||
|
<Button style={{ ...styles.btn, backgroundColor: theme['c-button-background'] }} onPress={handleConfirm}>
|
||||||
|
<Text size={14} color={theme['c-button-font']}>{t('confirm')}</Text>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const styles = createStyle({
|
||||||
|
content: {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingTop: 15,
|
||||||
|
paddingBottom: 10,
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 38,
|
||||||
|
},
|
||||||
|
// selector: {
|
||||||
|
// borderTopLeftRadius: 4,
|
||||||
|
// borderBottomLeftRadius: 4,
|
||||||
|
// },
|
||||||
|
inputContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
flexShrink: 1,
|
||||||
|
// backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
minWidth: 290,
|
||||||
|
// borderRadius: 4,
|
||||||
|
// borderTopRightRadius: 4,
|
||||||
|
// borderBottomRightRadius: 4,
|
||||||
|
paddingTop: 5,
|
||||||
|
paddingBottom: 5,
|
||||||
|
},
|
||||||
|
inputTipText: {
|
||||||
|
marginTop: 8,
|
||||||
|
// lineHeight: 18,
|
||||||
|
// backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
},
|
||||||
|
|
||||||
|
btns: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingBottom: 15,
|
||||||
|
paddingLeft: 15,
|
||||||
|
// paddingRight: 15,
|
||||||
|
},
|
||||||
|
btn: {
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: 8,
|
||||||
|
paddingBottom: 8,
|
||||||
|
paddingLeft: 10,
|
||||||
|
paddingRight: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: 4,
|
||||||
|
marginRight: 15,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
|||||||
|
import { memo, useRef } from 'react'
|
||||||
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
|
// import { gzip, ungzip } from 'pako'
|
||||||
|
|
||||||
|
import SubTitle from '../../components/SubTitle'
|
||||||
|
import Button from '../../components/Button'
|
||||||
|
import { toast } from '@/utils/tools'
|
||||||
|
import { useI18n } from '@/lang'
|
||||||
|
// import Text from '@/components/common/Text'
|
||||||
|
import { state } from '@/store/dislikeList'
|
||||||
|
import DislikeEditModal, { type DislikeEditModalType } from './DislikeEditModal'
|
||||||
|
import { overwirteDislikeInfo } from '@/core/dislikeList'
|
||||||
|
|
||||||
|
export default memo(() => {
|
||||||
|
const t = useI18n()
|
||||||
|
const modalRef = useRef<DislikeEditModalType>(null)
|
||||||
|
// const [ruleNum, setRuleNum] = useState(state.dislikeInfo.musicNames.size + state.dislikeInfo.singerNames.size + state.dislikeInfo.names.size)
|
||||||
|
|
||||||
|
const handleShow = () => {
|
||||||
|
modalRef.current?.show(state.dislikeInfo.rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = async(rules: string) => {
|
||||||
|
if (state.dislikeInfo.rules.trim() == rules.trim()) return
|
||||||
|
await overwirteDislikeInfo(rules)
|
||||||
|
toast(t('setting__other_dislike_list_saved_tip'))
|
||||||
|
// setRuleNum(state.dislikeInfo.musicNames.size + state.dislikeInfo.singerNames.size + state.dislikeInfo.names.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubTitle title={t('setting__other_dislike_list')}>
|
||||||
|
{/* <View style={styles.ruleNum}>
|
||||||
|
<Text>{t('setting__other_dislike_list_label', { num: ruleNum })}</Text>
|
||||||
|
</View> */}
|
||||||
|
<View style={styles.btn}>
|
||||||
|
<Button onPress={handleShow}>{t('setting_other_dislike_list_show_btn')}</Button>
|
||||||
|
</View>
|
||||||
|
<DislikeEditModal ref={modalRef} onSave={handleSave} />
|
||||||
|
</SubTitle>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
ruleNum: {
|
||||||
|
marginBottom: 5,
|
||||||
|
},
|
||||||
|
btn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
})
|
@ -3,6 +3,7 @@ import { memo } from 'react'
|
|||||||
import Section from '../../components/Section'
|
import Section from '../../components/Section'
|
||||||
import ResourceCache from './ResourceCache'
|
import ResourceCache from './ResourceCache'
|
||||||
import MetaCache from './MetaCache'
|
import MetaCache from './MetaCache'
|
||||||
|
import DislikeList from './DislikeList'
|
||||||
import Log from './Log'
|
import Log from './Log'
|
||||||
// import MaxCache from './MaxCache'
|
// import MaxCache from './MaxCache'
|
||||||
import { useI18n } from '@/lang'
|
import { useI18n } from '@/lang'
|
||||||
@ -14,6 +15,7 @@ export default memo(() => {
|
|||||||
<Section title={t('setting_other')}>
|
<Section title={t('setting_other')}>
|
||||||
<ResourceCache />
|
<ResourceCache />
|
||||||
<MetaCache />
|
<MetaCache />
|
||||||
|
<DislikeList />
|
||||||
<Log />
|
<Log />
|
||||||
{/* <MaxCache /> */}
|
{/* <MaxCache /> */}
|
||||||
</Section>
|
</Section>
|
||||||
|
60
src/store/dislikeList/action.ts
Normal file
60
src/store/dislikeList/action.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { state } from './state'
|
||||||
|
import { SPLIT_CHAR } from '@/config/constant'
|
||||||
|
|
||||||
|
|
||||||
|
export const hasDislike = (info: LX.Music.MusicInfo | LX.Download.ListItem) => {
|
||||||
|
if ('progress' in info) info = info.metadata.musicInfo
|
||||||
|
const name = info.name?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? ''
|
||||||
|
const singer = info.singer?.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim() ?? ''
|
||||||
|
|
||||||
|
return state.dislikeInfo.musicNames.has(name) || state.dislikeInfo.singerNames.has(singer) ||
|
||||||
|
state.dislikeInfo.names.has(`${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initDislikeInfo = (rules: string) => {
|
||||||
|
// state.dislikeInfo.names = names
|
||||||
|
// state.dislikeInfo.singerNames = singerNames
|
||||||
|
// state.dislikeInfo.musicNames = musicNames
|
||||||
|
state.dislikeInfo.rules = rules
|
||||||
|
initNameSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
const initNameSet = () => {
|
||||||
|
state.dislikeInfo.names.clear()
|
||||||
|
state.dislikeInfo.musicNames.clear()
|
||||||
|
state.dislikeInfo.singerNames.clear()
|
||||||
|
const list: string[] = []
|
||||||
|
for (const item of state.dislikeInfo.rules.split('\n')) {
|
||||||
|
if (!item) continue
|
||||||
|
let [name, singer] = item.split(SPLIT_CHAR.DISLIKE_NAME)
|
||||||
|
if (name) {
|
||||||
|
name = name.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim()
|
||||||
|
if (singer) {
|
||||||
|
singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim()
|
||||||
|
const rule = `${name}${SPLIT_CHAR.DISLIKE_NAME}${singer}`
|
||||||
|
state.dislikeInfo.names.add(rule)
|
||||||
|
list.push(rule)
|
||||||
|
} else {
|
||||||
|
state.dislikeInfo.musicNames.add(name)
|
||||||
|
list.push(name)
|
||||||
|
}
|
||||||
|
} else if (singer) {
|
||||||
|
singer = singer.replaceAll(SPLIT_CHAR.DISLIKE_NAME, SPLIT_CHAR.DISLIKE_NAME_ALIAS).toLocaleLowerCase().trim()
|
||||||
|
state.dislikeInfo.singerNames.add(singer)
|
||||||
|
list.push(`${SPLIT_CHAR.DISLIKE_NAME}${singer}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.dislikeInfo.rules = Array.from(new Set(list)).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const addDislikeInfo = (infos: LX.Dislike.DislikeMusicInfo[]) => {
|
||||||
|
// state.dislikeInfo.rules += '\n' + infos.map(info => `${info.name ?? ''}${SPLIT_CHAR.DISLIKE_NAME}${info.singer ?? ''}`).join('\n')
|
||||||
|
// initNameSet()
|
||||||
|
// return state.dislikeInfo.rules
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const overwirteDislikeInfo = (rules: string) => {
|
||||||
|
state.dislikeInfo.rules = rules
|
||||||
|
initNameSet()
|
||||||
|
return state.dislikeInfo.rules
|
||||||
|
}
|
3
src/store/dislikeList/index.ts
Normal file
3
src/store/dislikeList/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
export * as action from './action'
|
||||||
|
export * from './state'
|
19
src/store/dislikeList/state.ts
Normal file
19
src/store/dislikeList/state.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
interface InitState {
|
||||||
|
dislikeInfo: LX.Dislike.DislikeInfo
|
||||||
|
}
|
||||||
|
const state: InitState = {
|
||||||
|
dislikeInfo: {
|
||||||
|
names: new Set(),
|
||||||
|
musicNames: new Set(),
|
||||||
|
singerNames: new Set(),
|
||||||
|
rules: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
35
src/types/dislike_list.d.ts
vendored
Normal file
35
src/types/dislike_list.d.ts
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
|
||||||
|
declare namespace LX {
|
||||||
|
namespace Dislike {
|
||||||
|
// interface ListItemMusicText {
|
||||||
|
// id?: string
|
||||||
|
// // type: 'music'
|
||||||
|
// name: string | null
|
||||||
|
// singer: string | null
|
||||||
|
// }
|
||||||
|
// interface ListItemMusic {
|
||||||
|
// id?: number
|
||||||
|
// type: 'musicId'
|
||||||
|
// musicId: string
|
||||||
|
// meta: LX.Music.MusicInfo
|
||||||
|
// }
|
||||||
|
// type ListItem = ListItemMusicText
|
||||||
|
// type ListItem = string
|
||||||
|
// type ListItem = ListItemMusic | ListItemMusicText
|
||||||
|
|
||||||
|
interface DislikeMusicInfo {
|
||||||
|
name: string
|
||||||
|
singer: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DislikeInfo {
|
||||||
|
// musicIds: Set<string>
|
||||||
|
names: Set<string>
|
||||||
|
musicNames: Set<string>
|
||||||
|
singerNames: Set<string>
|
||||||
|
// list: LX.Dislike.ListItem[]
|
||||||
|
rules: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ const syncAuthKeyPrefix = storageDataPrefix.syncAuthKey
|
|||||||
const syncHostPrefix = storageDataPrefix.syncHost
|
const syncHostPrefix = storageDataPrefix.syncHost
|
||||||
const syncHostHistoryPrefix = storageDataPrefix.syncHostHistory
|
const syncHostHistoryPrefix = storageDataPrefix.syncHostHistory
|
||||||
const listPrefix = storageDataPrefix.list
|
const listPrefix = storageDataPrefix.list
|
||||||
|
const dislikeListPrefix = storageDataPrefix.dislikeList
|
||||||
|
|
||||||
// const defaultListKey = listPrefix + 'default'
|
// const defaultListKey = listPrefix + 'default'
|
||||||
// const loveListKey = listPrefix + 'love'
|
// const loveListKey = listPrefix + 'love'
|
||||||
@ -363,6 +364,21 @@ export const clearOtherSource = async(keys?: string[]) => {
|
|||||||
await removeDataMultiple(keys)
|
await removeDataMultiple(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取不喜欢列表信息
|
||||||
|
* @returns 不喜欢列表信息
|
||||||
|
*/
|
||||||
|
export const getDislikeListRules = async() => {
|
||||||
|
return await getData<string>(dislikeListPrefix) ?? ''
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 保存列表信息
|
||||||
|
* @param rules 规则信息
|
||||||
|
*/
|
||||||
|
export const saveDislikeListRules = async(rules: string) => {
|
||||||
|
await saveData(dislikeListPrefix, rules)
|
||||||
|
}
|
||||||
|
|
||||||
// export const clearMusicUrlAndLyric = async() => {
|
// export const clearMusicUrlAndLyric = async() => {
|
||||||
// let keys = (await getAllKeys()).filter(key => key.startsWith(storageDataPrefix.musicUrl) || key.startsWith(storageDataPrefix.lyric))
|
// let keys = (await getAllKeys()).filter(key => key.startsWith(storageDataPrefix.musicUrl) || key.startsWith(storageDataPrefix.lyric))
|
||||||
// await removeDataMultiple(keys)
|
// await removeDataMultiple(keys)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user