mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
impl star/unstar starred state
This commit is contained in:
parent
19c862b983
commit
0a3d542156
@ -1,10 +1,12 @@
|
||||
import { useStarred } from '@app/hooks/music'
|
||||
import { AlbumListItem, Artist, ListableItem, Song } from '@app/models/music'
|
||||
import { selectMusic } from '@app/state/music'
|
||||
import { useStore } from '@app/state/store'
|
||||
import { selectTrackPlayer } from '@app/state/trackplayer'
|
||||
import colors from '@app/styles/colors'
|
||||
import font from '@app/styles/font'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import IconFA from 'react-native-vector-icons/FontAwesome'
|
||||
@ -47,8 +49,8 @@ const ListItem: React.FC<{
|
||||
listStyle?: 'big' | 'small'
|
||||
subtitle?: string
|
||||
}> = ({ item, onPress, showArt, showStar, subtitle, listStyle }) => {
|
||||
const [starred, setStarred] = useState(false)
|
||||
const navigation = useNavigation()
|
||||
const starred = useStarred(item.id, item.itemType)
|
||||
|
||||
showStar = showStar === undefined ? true : showStar
|
||||
listStyle = listStyle || 'small'
|
||||
@ -124,6 +126,11 @@ const ListItem: React.FC<{
|
||||
PressableComponent = artistPressable
|
||||
}
|
||||
|
||||
const starItem = useStore(selectMusic.starItem)
|
||||
const toggleStarred = useCallback(() => {
|
||||
starItem(item.id, item.itemType, starred)
|
||||
}, [item.id, item.itemType, starItem, starred])
|
||||
|
||||
return (
|
||||
<View style={[styles.container, sizeStyle.container]}>
|
||||
<PressableComponent>
|
||||
@ -163,7 +170,7 @@ const ListItem: React.FC<{
|
||||
</PressableComponent>
|
||||
<View style={styles.controls}>
|
||||
{showStar ? (
|
||||
<PressableOpacity onPress={() => setStarred(!starred)} style={styles.controlItem}>
|
||||
<PressableOpacity onPress={toggleStarred} style={styles.controlItem}>
|
||||
{starred ? (
|
||||
<IconFA name="star" size={26} color={colors.accent} />
|
||||
) : (
|
||||
|
||||
@ -38,6 +38,21 @@ export const usePlaylistWithSongs = (id: string) => {
|
||||
return playlist
|
||||
}
|
||||
|
||||
export const useStarred = (id: string, type: string) => {
|
||||
const starred = useStore(
|
||||
useCallback(
|
||||
(state: Store) => {
|
||||
if (!(type in state.starred)) {
|
||||
return false
|
||||
}
|
||||
return !!state.starred[type][id]
|
||||
},
|
||||
[type, id],
|
||||
),
|
||||
)
|
||||
return starred
|
||||
}
|
||||
|
||||
export const useCoverArtUri = () => {
|
||||
const server = useStore(selectSettings.activeServer)
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
SearchResults,
|
||||
} from '@app/models/music'
|
||||
import { Store } from '@app/state/store'
|
||||
import { GetAlbumList2Type } from '@app/subsonic/params'
|
||||
import { GetAlbumList2Type, StarParams } from '@app/subsonic/params'
|
||||
import produce from 'immer'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
@ -24,18 +24,13 @@ export type MusicSlice = {
|
||||
//
|
||||
// family-style state
|
||||
//
|
||||
cacheSize: number
|
||||
|
||||
artistInfo: { [id: string]: ArtistInfo | undefined }
|
||||
artistInfoCache: string[]
|
||||
artistInfo: { [id: string]: ArtistInfo }
|
||||
fetchArtistInfo: (id: string) => Promise<ArtistInfo | undefined>
|
||||
|
||||
albumsWithSongs: { [id: string]: AlbumWithSongs | undefined }
|
||||
albumsWithSongsCache: string[]
|
||||
albumsWithSongs: { [id: string]: AlbumWithSongs }
|
||||
fetchAlbumWithSongs: (id: string) => Promise<AlbumWithSongs | undefined>
|
||||
|
||||
playlistsWithSongs: { [id: string]: PlaylistWithSongs | undefined }
|
||||
playlistsWithSongsCache: string[]
|
||||
playlistsWithSongs: { [id: string]: PlaylistWithSongs }
|
||||
fetchPlaylistWithSongs: (id: string) => Promise<PlaylistWithSongs | undefined>
|
||||
|
||||
//
|
||||
@ -62,6 +57,9 @@ export type MusicSlice = {
|
||||
homeListsUpdating: boolean
|
||||
fetchHomeLists: () => Promise<void>
|
||||
clearHomeLists: () => void
|
||||
|
||||
starred: { [type: string]: { [id: string]: boolean } }
|
||||
starItem: (id: string, type: string, unstar?: boolean) => Promise<void>
|
||||
}
|
||||
|
||||
export const selectMusic = {
|
||||
@ -90,13 +88,25 @@ export const selectMusic = {
|
||||
homeListsUpdating: (store: MusicSlice) => store.homeListsUpdating,
|
||||
fetchHomeLists: (store: MusicSlice) => store.fetchHomeLists,
|
||||
clearHomeLists: (store: MusicSlice) => store.clearHomeLists,
|
||||
|
||||
starItem: (store: MusicSlice) => store.starItem,
|
||||
}
|
||||
|
||||
function reduceStarred(
|
||||
starredType: { [id: string]: boolean },
|
||||
items: { id: string; starred?: Date }[],
|
||||
): { [id: string]: boolean } {
|
||||
return {
|
||||
...starredType,
|
||||
...items.reduce((acc, val) => {
|
||||
acc[val.id] = !!val.starred
|
||||
return acc
|
||||
}, {} as { [id: string]: boolean }),
|
||||
}
|
||||
}
|
||||
|
||||
export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): MusicSlice => ({
|
||||
cacheSize: 100,
|
||||
|
||||
artistInfo: {},
|
||||
artistInfoCache: [],
|
||||
|
||||
fetchArtistInfo: async id => {
|
||||
const client = get().client
|
||||
@ -119,11 +129,10 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
if (state.artistInfoCache.length >= state.cacheSize) {
|
||||
delete state.albumsWithSongs[state.artistInfoCache.shift() as string]
|
||||
}
|
||||
state.artistInfo[id] = artistInfo
|
||||
state.artistInfoCache.push(id)
|
||||
state.starred.song = reduceStarred(state.starred.song, artistInfo.topSongs)
|
||||
state.starred.artist = reduceStarred(state.starred.artist, [artistInfo])
|
||||
state.starred.album = reduceStarred(state.starred.album, artistInfo.albums)
|
||||
}),
|
||||
)
|
||||
return artistInfo
|
||||
@ -133,7 +142,6 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
},
|
||||
|
||||
albumsWithSongs: {},
|
||||
albumsWithSongsCache: [],
|
||||
|
||||
fetchAlbumWithSongs: async id => {
|
||||
const client = get().client
|
||||
@ -147,11 +155,9 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
if (state.albumsWithSongsCache.length >= state.cacheSize) {
|
||||
delete state.albumsWithSongs[state.albumsWithSongsCache.shift() as string]
|
||||
}
|
||||
state.albumsWithSongs[id] = album
|
||||
state.albumsWithSongsCache.push(id)
|
||||
state.starred.song = reduceStarred(state.starred.song, album.songs)
|
||||
state.starred.album = reduceStarred(state.starred.album, [album])
|
||||
}),
|
||||
)
|
||||
return album
|
||||
@ -161,7 +167,6 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
},
|
||||
|
||||
playlistsWithSongs: {},
|
||||
playlistsWithSongsCache: [],
|
||||
|
||||
fetchPlaylistWithSongs: async id => {
|
||||
const client = get().client
|
||||
@ -175,11 +180,8 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
if (state.playlistsWithSongsCache.length >= state.cacheSize) {
|
||||
delete state.playlistsWithSongs[state.playlistsWithSongsCache.shift() as string]
|
||||
}
|
||||
state.playlistsWithSongs[id] = playlist
|
||||
state.playlistsWithSongsCache.push(id)
|
||||
state.starred.song = reduceStarred(state.starred.song, playlist.songs)
|
||||
}),
|
||||
)
|
||||
return playlist
|
||||
@ -204,7 +206,12 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getArtists()
|
||||
set({ artists: response.data.artists.map(mapArtistID3toArtist) })
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.artists = response.data.artists.map(mapArtistID3toArtist)
|
||||
state.starred.artist = reduceStarred(state.starred.artist, state.artists)
|
||||
}),
|
||||
)
|
||||
} finally {
|
||||
set({ artistsUpdating: false })
|
||||
}
|
||||
@ -248,7 +255,12 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size, offset })
|
||||
set({ albums: response.data.albums.map(mapAlbumID3toAlbumListItem) })
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.albums = response.data.albums.map(mapAlbumID3toAlbumListItem)
|
||||
state.starred.albums = reduceStarred(state.starred.albums, state.albums)
|
||||
}),
|
||||
)
|
||||
} finally {
|
||||
set({ albumsUpdating: false })
|
||||
}
|
||||
@ -279,13 +291,18 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.search3({ query })
|
||||
set({
|
||||
searchResults: {
|
||||
artists: response.data.artists.map(mapArtistID3toArtist),
|
||||
albums: response.data.albums.map(mapAlbumID3toAlbumListItem),
|
||||
songs: response.data.songs.map(a => mapChildToSong(a, client)),
|
||||
},
|
||||
})
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.searchResults = {
|
||||
artists: response.data.artists.map(mapArtistID3toArtist),
|
||||
albums: response.data.albums.map(mapAlbumID3toAlbumListItem),
|
||||
songs: response.data.songs.map(a => mapChildToSong(a, client)),
|
||||
}
|
||||
state.starred.song = reduceStarred(state.starred.song, state.searchResults.songs)
|
||||
state.starred.artist = reduceStarred(state.starred.artist, state.searchResults.artists)
|
||||
state.starred.album = reduceStarred(state.starred.album, state.searchResults.albums)
|
||||
}),
|
||||
)
|
||||
} finally {
|
||||
set({ searchResultsUpdating: false })
|
||||
}
|
||||
@ -324,6 +341,7 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.homeLists[type] = response.data.albums.map(mapAlbumID3toAlbumListItem)
|
||||
state.starred.album = reduceStarred(state.starred.album, state.homeLists[type])
|
||||
}),
|
||||
)
|
||||
}),
|
||||
@ -338,4 +356,54 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
clearHomeLists: () => {
|
||||
set({ homeLists: {} })
|
||||
},
|
||||
|
||||
starred: {
|
||||
song: {},
|
||||
album: {},
|
||||
artist: {},
|
||||
},
|
||||
|
||||
starItem: async (id, type, unstar = false) => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
let params: StarParams
|
||||
switch (type) {
|
||||
case 'song':
|
||||
params = { id }
|
||||
break
|
||||
case 'album':
|
||||
params = { albumId: id }
|
||||
break
|
||||
case 'artist':
|
||||
params = { artistId: id }
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
const setStarred = (starred: boolean) => {
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.starred[type] = {
|
||||
...state.starred[type],
|
||||
[id]: starred,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
setStarred(!unstar)
|
||||
if (unstar) {
|
||||
await client.unstar(params)
|
||||
} else {
|
||||
await client.star(params)
|
||||
}
|
||||
} catch {
|
||||
setStarred(unstar)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
GetTopSongsParams,
|
||||
ScrobbleParams,
|
||||
Search3Params,
|
||||
StarParams,
|
||||
StreamParams,
|
||||
} from '@app/subsonic/params'
|
||||
import {
|
||||
@ -233,6 +234,16 @@ export class SubsonicApiClient {
|
||||
return new SubsonicResponse<undefined>(xml, undefined)
|
||||
}
|
||||
|
||||
async star(params: StarParams): Promise<SubsonicResponse<undefined>> {
|
||||
const xml = await this.apiGetXml('star', params)
|
||||
return new SubsonicResponse<undefined>(xml, undefined)
|
||||
}
|
||||
|
||||
async unstar(params: StarParams): Promise<SubsonicResponse<undefined>> {
|
||||
const xml = await this.apiGetXml('unstar', params)
|
||||
return new SubsonicResponse<undefined>(xml, undefined)
|
||||
}
|
||||
|
||||
//
|
||||
// Searching
|
||||
//
|
||||
|
||||
@ -110,6 +110,12 @@ export type ScrobbleParams = {
|
||||
submission?: boolean
|
||||
}
|
||||
|
||||
export type StarParams = {
|
||||
id?: string
|
||||
albumId?: string
|
||||
artistId?: string
|
||||
}
|
||||
|
||||
//
|
||||
// Searching
|
||||
//
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user