重复歌曲列表添加多选操作

This commit is contained in:
lyswhut 2024-07-12 19:25:11 +08:00
parent 163daa1d66
commit ddead116fc
3 changed files with 111 additions and 37 deletions

View File

@ -3,4 +3,4 @@
### 新增 ### 新增
- 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在“我的列表”里的列表名菜单中使用 - 新增重复歌曲列表,可以方便移除我的列表中的重复歌曲,此列表会列出目标列表里歌曲名相同的歌曲,可在“我的列表”里的列表名菜单中使用该功能与PC端的区别是可以点击歌曲名多选删除

View File

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

View File

@ -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,
}]) }])
} }
} }