mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-05 04:28:55 +08:00
重复歌曲列表添加多选操作
This commit is contained in:
parent
163daa1d66
commit
ddead116fc
@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在“我的列表”里的列表名菜单中使用
|
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在“我的列表”里的列表名菜单中使用(注:该功能与PC端的区别是可以点击歌曲名多选删除)
|
||||||
|
@ -2,16 +2,17 @@ import { useRef, useImperativeHandle, forwardRef, useState, useCallback, memo, u
|
|||||||
import Text from '@/components/common/Text'
|
import Text from '@/components/common/Text'
|
||||||
import { createStyle } from '@/utils/tools'
|
import { createStyle } from '@/utils/tools'
|
||||||
import Dialog, { type DialogType } from '@/components/common/Dialog'
|
import Dialog, { type DialogType } from '@/components/common/Dialog'
|
||||||
import { FlatList, View, type FlatListProps as _FlatListProps } from 'react-native'
|
import { FlatList, TouchableOpacity, View, type FlatListProps as _FlatListProps } from 'react-native'
|
||||||
import { scaleSizeH } from '@/utils/pixelRatio'
|
import { scaleSizeH } from '@/utils/pixelRatio'
|
||||||
import { useTheme } from '@/store/theme/hook'
|
import { useTheme } from '@/store/theme/hook'
|
||||||
import { type DuplicateMusicItem, filterDuplicateMusic } from './utils'
|
import { type DuplicateMusicItem, filterDuplicateMusic } from './utils'
|
||||||
import { getListMusics, removeListMusics } from '@/core/list'
|
import { getListMusics, removeListMusics } from '@/core/list'
|
||||||
import Button from '@/components/common/Button'
|
|
||||||
import { Icon } from '@/components/common/Icon'
|
import { Icon } from '@/components/common/Icon'
|
||||||
import { useUnmounted } from '@/utils/hooks'
|
import { useUnmounted } from '@/utils/hooks'
|
||||||
import { playList } from '@/core/player/player'
|
import { playList } from '@/core/player/player'
|
||||||
import { useI18n } from '@/lang'
|
import { useI18n } from '@/lang'
|
||||||
|
import { handleRemove } from '../MusicList/listAction'
|
||||||
|
import Button from '@/components/common/Button'
|
||||||
|
|
||||||
type FlatListProps = _FlatListProps<DuplicateMusicItem>
|
type FlatListProps = _FlatListProps<DuplicateMusicItem>
|
||||||
const ITEM_HEIGHT = scaleSizeH(56)
|
const ITEM_HEIGHT = scaleSizeH(56)
|
||||||
@ -37,20 +38,23 @@ const Empty = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItem = memo(({ info, index, onRemove, onPlay }: {
|
const ListItem = memo(({ info, index, onRemove, onPlay, selectedList, onPress }: {
|
||||||
info: DuplicateMusicItem
|
info: DuplicateMusicItem
|
||||||
index: number
|
index: number
|
||||||
|
selectedList: DuplicateMusicItem[]
|
||||||
onPlay: (info: DuplicateMusicItem) => void
|
onPlay: (info: DuplicateMusicItem) => void
|
||||||
onRemove: (idx: number) => void
|
onRemove: (idx: number) => void
|
||||||
|
onPress: (info: DuplicateMusicItem) => void
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const isSelected = selectedList.includes(info)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ ...styles.listItem, height: ITEM_HEIGHT }} onStartShouldSetResponder={() => true}>
|
<View style={{ ...styles.listItem, height: ITEM_HEIGHT, backgroundColor: isSelected ? theme['c-primary-background-hover'] : 'rgba(0,0,0,0)' }} onStartShouldSetResponder={() => true}>
|
||||||
<View style={styles.listItemLabel}>
|
{/* <View style={styles.listItemLabel}>
|
||||||
<Text style={styles.sn} size={13} color={theme['c-300']}>{info.index + 1}</Text>
|
<Text style={styles.sn} size={13} color={theme['c-300']}>{info.index + 1}</Text>
|
||||||
</View>
|
</View> */}
|
||||||
<View style={styles.listItemInfo}>
|
<TouchableOpacity style={styles.listItemInfo} onPress={() => { onPress(info) }}>
|
||||||
<Text color={theme['c-font']} size={14} numberOfLines={1}>{info.musicInfo.name}</Text>
|
<Text color={theme['c-font']} size={14} numberOfLines={1}>{info.musicInfo.name}</Text>
|
||||||
<View style={styles.listItemAlbum}>
|
<View style={styles.listItemAlbum}>
|
||||||
<Text color={theme['c-font']} size={12} numberOfLines={1}>
|
<Text color={theme['c-font']} size={12} numberOfLines={1}>
|
||||||
@ -62,12 +66,10 @@ const ListItem = memo(({ info, index, onRemove, onPlay }: {
|
|||||||
}
|
}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</TouchableOpacity>
|
||||||
<View style={styles.listItemLabel}>
|
<View style={styles.listItemLabel}>
|
||||||
<Text style={styles.sn} size={13} color={theme['c-300']}>{ info.musicInfo.source }</Text>
|
<Text style={styles.listItemLabelText} size={13} color={theme['c-300']}>{ info.musicInfo.source }</Text>
|
||||||
</View>
|
<Text style={styles.listItemLabelText} size={13} color={theme['c-300']}>{info.musicInfo.interval}</Text>
|
||||||
<View style={styles.listItemLabel}>
|
|
||||||
<Text style={styles.sn} size={13} color={theme['c-300']}>{info.musicInfo.interval}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.listItemBtns}>
|
<View style={styles.listItemBtns}>
|
||||||
<Button style={styles.listItemBtn} onPress={() => { onPlay(info) }}>
|
<Button style={styles.listItemBtn} onPress={() => { onPlay(info) }}>
|
||||||
@ -79,10 +81,34 @@ const ListItem = memo(({ info, index, onRemove, onPlay }: {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
}, (prevProps, nextProps) => {
|
||||||
|
return prevProps.info === nextProps.info &&
|
||||||
|
prevProps.index === nextProps.index &&
|
||||||
|
nextProps.selectedList.includes(nextProps.info) == prevProps.selectedList.includes(nextProps.info)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleRemoveList = (list: DuplicateMusicItem[], index: number) => {
|
||||||
|
let prev = list[index - 1]
|
||||||
|
let cur = list[index]
|
||||||
|
let next = list[index + 1]
|
||||||
|
let count = 1
|
||||||
|
if (prev?.group != cur.group) {
|
||||||
|
if (next?.group == cur.group && list[index + 2]?.group != cur.group) {
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
} else if (next?.group != cur.group) {
|
||||||
|
if (prev?.group == cur.group && list[index - 2]?.group != cur.group) {
|
||||||
|
index -= 1
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.splice(index, count)
|
||||||
|
}
|
||||||
const List = ({ listId }: { listId: string }) => {
|
const List = ({ listId }: { listId: string }) => {
|
||||||
const [list, setList] = useState<DuplicateMusicItem[]>([])
|
const [list, setList] = useState<DuplicateMusicItem[]>([])
|
||||||
|
const [selectedList, setSelectedList] = useState<DuplicateMusicItem[]>([])
|
||||||
|
const dataRef = useRef<[DuplicateMusicItem[], DuplicateMusicItem[]]>([[], []])
|
||||||
const isUnmountedRef = useUnmounted()
|
const isUnmountedRef = useUnmounted()
|
||||||
|
|
||||||
const handleFilterList = useCallback(() => {
|
const handleFilterList = useCallback(() => {
|
||||||
@ -91,29 +117,67 @@ const List = ({ listId }: { listId: string }) => {
|
|||||||
if (isUnmountedRef.current) return
|
if (isUnmountedRef.current) return
|
||||||
void filterDuplicateMusic(list).then((l) => {
|
void filterDuplicateMusic(list).then((l) => {
|
||||||
if (isUnmountedRef.current) return
|
if (isUnmountedRef.current) return
|
||||||
setList(l)
|
setSelectedList(dataRef.current[1] = [])
|
||||||
|
setList(dataRef.current[0] = l)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, [isUnmountedRef, listId])
|
}, [isUnmountedRef, listId])
|
||||||
const handlePlay = useCallback((info: DuplicateMusicItem) => {
|
const handlePlay = useCallback((info: DuplicateMusicItem) => {
|
||||||
const { index: musicInfoIndex } = info
|
const { musicInfo } = info
|
||||||
void playList(listId, musicInfoIndex)
|
void getListMusics(listId).then((list) => {
|
||||||
}, [listId])
|
const idx = list.findIndex(m => m.id == musicInfo.id)
|
||||||
const handleRemove = useCallback((index: number) => {
|
if (idx < 0) return
|
||||||
setList(list => {
|
void playList(listId, idx)
|
||||||
const { musicInfo: targetMusicInfo } = list.splice(index, 1)[0]
|
|
||||||
void removeListMusics(listId, [targetMusicInfo.id]).then(() => {
|
|
||||||
handleFilterList()
|
|
||||||
})
|
|
||||||
return [...list]
|
|
||||||
})
|
})
|
||||||
}, [handleFilterList, listId])
|
}, [listId])
|
||||||
|
const handleRemovePress = useCallback((index: number) => {
|
||||||
|
const selectedList = dataRef.current[1]
|
||||||
|
const list = dataRef.current[0]
|
||||||
|
if (selectedList.length) {
|
||||||
|
handleRemove(listId, list[index].musicInfo, selectedList.map(m => m.musicInfo), () => {
|
||||||
|
let newList = [...list]
|
||||||
|
for (const item of selectedList) {
|
||||||
|
let idx = newList.indexOf(item)
|
||||||
|
if (idx < 0) continue
|
||||||
|
handleRemoveList(newList, idx)
|
||||||
|
}
|
||||||
|
setList(dataRef.current[0] = newList)
|
||||||
|
setSelectedList(dataRef.current[1] = [])
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let newList = [...list]
|
||||||
|
let curItem = list[index]
|
||||||
|
const rmItem = handleRemoveList(newList, index)
|
||||||
|
let newSelectList = [...selectedList]
|
||||||
|
for (const item of rmItem) {
|
||||||
|
let idx = newSelectList.indexOf(item)
|
||||||
|
if (idx < 0) continue
|
||||||
|
newSelectList.splice(idx, 1)
|
||||||
|
}
|
||||||
|
setSelectedList(dataRef.current[1] = newSelectList)
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
void removeListMusics(listId, [curItem.musicInfo.id])
|
||||||
|
})
|
||||||
|
setList(dataRef.current[0] = newList)
|
||||||
|
}, [listId])
|
||||||
|
const handleSelect = useCallback((info: DuplicateMusicItem) => {
|
||||||
|
setSelectedList(selectedList => {
|
||||||
|
let nList = [...selectedList]
|
||||||
|
let idx = nList.indexOf(info)
|
||||||
|
if (idx < 0) nList.push(info)
|
||||||
|
else nList.splice(idx, 1)
|
||||||
|
dataRef.current[1] = nList
|
||||||
|
return nList
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(handleFilterList, [handleFilterList])
|
useEffect(handleFilterList, [handleFilterList])
|
||||||
|
|
||||||
const renderItem = useCallback(({ item, index }: { item: DuplicateMusicItem, index: number }) => {
|
const renderItem = useCallback(({ item, index }: { item: DuplicateMusicItem, index: number }) => {
|
||||||
return <ListItem info={item} index={index} onPlay={handlePlay} onRemove={handleRemove} />
|
return <ListItem info={item} index={index} onPlay={handlePlay} onRemove={handleRemovePress} selectedList={selectedList} onPress={handleSelect} />
|
||||||
}, [handlePlay, handleRemove])
|
}, [handlePlay, handleRemovePress, handleSelect, selectedList])
|
||||||
const getkey = useCallback<NonNullable<FlatListProps['keyExtractor']>>(item => item.id, [])
|
const getkey = useCallback<NonNullable<FlatListProps['keyExtractor']>>(item => item.id, [])
|
||||||
const getItemLayout = useCallback<NonNullable<FlatListProps['getItemLayout']>>((data, index) => {
|
const getItemLayout = useCallback<NonNullable<FlatListProps['getItemLayout']>>((data, index) => {
|
||||||
return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
|
return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
|
||||||
@ -123,8 +187,10 @@ const List = ({ listId }: { listId: string }) => {
|
|||||||
list.length ? (
|
list.length ? (
|
||||||
<FlatList
|
<FlatList
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
|
maxToRenderPerBatch={4}
|
||||||
|
windowSize={8}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
keyboardShouldPersistTaps={'always'}
|
initialNumToRender={12}
|
||||||
data={list}
|
data={list}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={getkey}
|
keyExtractor={getkey}
|
||||||
@ -219,18 +285,20 @@ const styles = createStyle({
|
|||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
sn: {
|
// sn: {
|
||||||
width: 38,
|
// width: 38,
|
||||||
// fontSize: 12,
|
// // fontSize: 12,
|
||||||
textAlign: 'center',
|
// textAlign: 'center',
|
||||||
// backgroundColor: 'rgba(0,0,0,0.2)',
|
// // backgroundColor: 'rgba(0,0,0,0.2)',
|
||||||
paddingLeft: 3,
|
// paddingLeft: 3,
|
||||||
paddingRight: 3,
|
// paddingRight: 3,
|
||||||
},
|
// },
|
||||||
listItemInfo: {
|
listItemInfo: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
// backgroundColor: 'rgba(0,0,0,0.2)',
|
// backgroundColor: 'rgba(0,0,0,0.2)',
|
||||||
|
paddingLeft: 15,
|
||||||
|
paddingRight: 5,
|
||||||
},
|
},
|
||||||
listItemAlbum: {
|
listItemAlbum: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -239,6 +307,9 @@ const styles = createStyle({
|
|||||||
listItemLabel: {
|
listItemLabel: {
|
||||||
flex: 0,
|
flex: 0,
|
||||||
},
|
},
|
||||||
|
listItemLabelText: {
|
||||||
|
paddingHorizontal: 5,
|
||||||
|
},
|
||||||
listItemBtns: {
|
listItemBtns: {
|
||||||
flex: 0,
|
flex: 0,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -246,7 +317,7 @@ const styles = createStyle({
|
|||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
},
|
},
|
||||||
listItemBtn: {
|
listItemBtn: {
|
||||||
padding: 5,
|
padding: 8,
|
||||||
},
|
},
|
||||||
noitem: {
|
noitem: {
|
||||||
paddingVertical: 35,
|
paddingVertical: 35,
|
||||||
|
@ -94,6 +94,7 @@ const variantRxp2 = /\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[/g
|
|||||||
export interface DuplicateMusicItem {
|
export interface DuplicateMusicItem {
|
||||||
id: string
|
id: string
|
||||||
index: number
|
index: number
|
||||||
|
group: string
|
||||||
musicInfo: LX.Music.MusicInfo
|
musicInfo: LX.Music.MusicInfo
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -112,6 +113,7 @@ export const filterDuplicateMusic = async(list: LX.Music.MusicInfo[], isFilterVa
|
|||||||
id: musicInfo.id,
|
id: musicInfo.id,
|
||||||
index,
|
index,
|
||||||
musicInfo,
|
musicInfo,
|
||||||
|
group: name,
|
||||||
})
|
})
|
||||||
duplicateList.add(name)
|
duplicateList.add(name)
|
||||||
} else {
|
} else {
|
||||||
@ -119,6 +121,7 @@ export const filterDuplicateMusic = async(list: LX.Music.MusicInfo[], isFilterVa
|
|||||||
id: musicInfo.id,
|
id: musicInfo.id,
|
||||||
index,
|
index,
|
||||||
musicInfo,
|
musicInfo,
|
||||||
|
group: name,
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user