mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
refactored music mapping into state
easier to access client/settings
This commit is contained in:
parent
faabe00c7e
commit
88d0c6089e
@ -1,14 +1,3 @@
|
||||
import { SubsonicApiClient } from '@app/subsonic/api'
|
||||
import {
|
||||
AlbumID3Element,
|
||||
ArtistID3Element,
|
||||
ArtistInfo2Element,
|
||||
ChildElement,
|
||||
PlaylistElement,
|
||||
PlaylistWithSongsElement,
|
||||
} from '@app/subsonic/elements'
|
||||
import { GetArtistResponse } from '@app/subsonic/responses'
|
||||
|
||||
export interface Artist {
|
||||
itemType: 'artist'
|
||||
id: string
|
||||
@ -115,97 +104,3 @@ export type DownloadedArtist = Artist & {
|
||||
}
|
||||
|
||||
export type DownloadedSong = Song
|
||||
|
||||
export function mapArtistID3toArtist(artist: ArtistID3Element): Artist {
|
||||
return {
|
||||
itemType: 'artist',
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
starred: artist.starred,
|
||||
coverArt: artist.coverArt,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapArtistInfo(
|
||||
artistResponse: GetArtistResponse,
|
||||
info: ArtistInfo2Element,
|
||||
topSongs: ChildElement[],
|
||||
client: SubsonicApiClient,
|
||||
): ArtistInfo {
|
||||
const { artist, albums } = artistResponse
|
||||
|
||||
const mappedAlbums = albums.map(mapAlbumID3toAlbum)
|
||||
|
||||
return {
|
||||
...mapArtistID3toArtist(artist),
|
||||
albums: mappedAlbums,
|
||||
largeImageUrl: info.largeImageUrl,
|
||||
topSongs: topSongs.map(s => mapChildToSong(s, client)).slice(0, 5),
|
||||
}
|
||||
}
|
||||
|
||||
export function mapAlbumID3toAlbumListItem(album: AlbumID3Element): AlbumListItem {
|
||||
return {
|
||||
itemType: 'album',
|
||||
id: album.id,
|
||||
name: album.name,
|
||||
artist: album.artist,
|
||||
artistId: album.artistId,
|
||||
starred: album.starred,
|
||||
coverArt: album.coverArt,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapAlbumID3toAlbum(album: AlbumID3Element): Album {
|
||||
return {
|
||||
...mapAlbumID3toAlbumListItem(album),
|
||||
coverArt: album.coverArt,
|
||||
year: album.year,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapChildToSong(child: ChildElement, client: SubsonicApiClient): Song {
|
||||
return {
|
||||
itemType: 'song',
|
||||
id: child.id,
|
||||
album: child.album,
|
||||
albumId: child.albumId,
|
||||
artist: child.artist,
|
||||
artistId: child.artistId,
|
||||
title: child.title,
|
||||
track: child.track,
|
||||
duration: child.duration,
|
||||
starred: child.starred,
|
||||
coverArt: child.coverArt,
|
||||
streamUri: client.streamUri({ id: child.id }),
|
||||
}
|
||||
}
|
||||
|
||||
export function mapAlbumID3WithSongstoAlbumWithSongs(
|
||||
album: AlbumID3Element,
|
||||
songs: ChildElement[],
|
||||
client: SubsonicApiClient,
|
||||
): AlbumWithSongs {
|
||||
return {
|
||||
...mapAlbumID3toAlbum(album),
|
||||
songs: songs.map(s => mapChildToSong(s, client)),
|
||||
}
|
||||
}
|
||||
|
||||
export function mapPlaylistListItem(playlist: PlaylistElement): PlaylistListItem {
|
||||
return {
|
||||
itemType: 'playlist',
|
||||
id: playlist.id,
|
||||
name: playlist.name,
|
||||
comment: playlist.comment,
|
||||
coverArt: playlist.coverArt,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapPlaylistWithSongs(playlist: PlaylistWithSongsElement, client: SubsonicApiClient): PlaylistWithSongs {
|
||||
return {
|
||||
...mapPlaylistListItem(playlist),
|
||||
songs: playlist.songs.map(s => mapChildToSong(s, client)),
|
||||
coverArt: playlist.coverArt,
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,17 +4,9 @@ import {
|
||||
Artist,
|
||||
ArtistInfo,
|
||||
HomeLists,
|
||||
mapAlbumID3toAlbumListItem,
|
||||
mapAlbumID3WithSongstoAlbumWithSongs,
|
||||
mapArtistID3toArtist,
|
||||
mapArtistInfo,
|
||||
mapChildToSong,
|
||||
mapPlaylistListItem,
|
||||
mapPlaylistWithSongs,
|
||||
PlaylistListItem,
|
||||
PlaylistWithSongs,
|
||||
SearchResults,
|
||||
Song,
|
||||
StarrableItemType,
|
||||
} from '@app/models/music'
|
||||
import { Store } from '@app/state/store'
|
||||
@ -72,7 +64,6 @@ export type MusicSlice = {
|
||||
albumIdCoverArtRequests: { [id: string]: Promise<void> }
|
||||
fetchAlbumCoverArt: (id: string) => Promise<void>
|
||||
getAlbumCoverArt: (id: string | undefined) => Promise<string | undefined>
|
||||
mapSongCoverArtFromAlbum: (songs: Song[]) => Promise<Song[]>
|
||||
}
|
||||
|
||||
export const selectMusic = {
|
||||
@ -133,15 +124,12 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
client.getArtistInfo2({ id }),
|
||||
])
|
||||
const topSongsResponse = await client.getTopSongs({ artist: artistResponse.data.artist.name, count: 50 })
|
||||
const artistInfo = mapArtistInfo(
|
||||
const artistInfo = await get().mapArtistInfo(
|
||||
artistResponse.data,
|
||||
artistInfoResponse.data.artistInfo,
|
||||
topSongsResponse.data.songs,
|
||||
client,
|
||||
)
|
||||
|
||||
artistInfo.topSongs = await get().mapSongCoverArtFromAlbum(artistInfo.topSongs)
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.artistInfo[id] = artistInfo
|
||||
@ -167,9 +155,7 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getAlbum({ id })
|
||||
const album = mapAlbumID3WithSongstoAlbumWithSongs(response.data.album, response.data.songs, client)
|
||||
|
||||
album.songs = await get().mapSongCoverArtFromAlbum(album.songs)
|
||||
const album = await get().mapAlbumID3WithSongstoAlbumWithSongs(response.data.album, response.data.songs)
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
@ -194,9 +180,7 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getPlaylist({ id })
|
||||
const playlist = mapPlaylistWithSongs(response.data.playlist, client)
|
||||
|
||||
playlist.songs = await get().mapSongCoverArtFromAlbum(playlist.songs)
|
||||
const playlist = await get().mapPlaylistWithSongs(response.data.playlist)
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
@ -226,9 +210,11 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getArtists()
|
||||
const artists = response.data.artists.map(get().mapArtistID3toArtist)
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.artists = response.data.artists.map(mapArtistID3toArtist)
|
||||
state.artists = artists
|
||||
state.starredArtists = reduceStarred(state.starredArtists, state.artists)
|
||||
}),
|
||||
)
|
||||
@ -253,7 +239,8 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getPlaylists()
|
||||
set({ playlists: response.data.playlists.map(mapPlaylistListItem) })
|
||||
const playlists = response.data.playlists.map(get().mapPlaylistListItem)
|
||||
set({ playlists })
|
||||
} finally {
|
||||
set({ playlistsUpdating: false })
|
||||
}
|
||||
@ -275,9 +262,10 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size, offset })
|
||||
const albums = response.data.albums.map(get().mapAlbumID3toAlbumListItem)
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.albums = response.data.albums.map(mapAlbumID3toAlbumListItem)
|
||||
state.albums = albums
|
||||
state.starredAlbums = reduceStarred(state.starredAlbums, state.albums)
|
||||
}),
|
||||
)
|
||||
@ -311,14 +299,14 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
|
||||
try {
|
||||
const response = await client.search3({ query })
|
||||
const songs = await get().mapSongCoverArtFromAlbum(response.data.songs.map(a => mapChildToSong(a, client)))
|
||||
|
||||
const artists = response.data.artists.map(get().mapArtistID3toArtist)
|
||||
const albums = response.data.albums.map(get().mapAlbumID3toAlbumListItem)
|
||||
const songs = await get().mapChildrenToSongs(response.data.songs)
|
||||
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.searchResults = {
|
||||
artists: response.data.artists.map(mapArtistID3toArtist),
|
||||
albums: response.data.albums.map(mapAlbumID3toAlbumListItem),
|
||||
songs: songs,
|
||||
}
|
||||
state.searchResults = { artists, albums, songs }
|
||||
state.starredSongs = reduceStarred(state.starredSongs, state.searchResults.songs)
|
||||
state.starredArtists = reduceStarred(state.starredArtists, state.searchResults.artists)
|
||||
state.starredAlbums = reduceStarred(state.starredAlbums, state.searchResults.albums)
|
||||
@ -359,9 +347,10 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
for (const type of types) {
|
||||
promises.push(
|
||||
client.getAlbumList2({ type: type as GetAlbumList2Type, size: 20 }).then(response => {
|
||||
const list = response.data.albums.map(get().mapAlbumID3toAlbumListItem)
|
||||
set(
|
||||
produce<MusicSlice>(state => {
|
||||
state.homeLists[type] = response.data.albums.map(mapAlbumID3toAlbumListItem)
|
||||
state.homeLists[type] = list
|
||||
state.starredAlbums = reduceStarred(state.starredAlbums, state.homeLists[type])
|
||||
}),
|
||||
)
|
||||
@ -492,15 +481,4 @@ export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): Mu
|
||||
await get().fetchAlbumCoverArt(id)
|
||||
return get().albumIdCoverArt[id]
|
||||
},
|
||||
|
||||
mapSongCoverArtFromAlbum: async songs => {
|
||||
const mapped: Song[] = []
|
||||
for (const s of songs) {
|
||||
mapped.push({
|
||||
...s,
|
||||
coverArt: await get().getAlbumCoverArt(s.albumId),
|
||||
})
|
||||
}
|
||||
return mapped
|
||||
},
|
||||
})
|
||||
|
||||
142
app/state/musicmap.ts
Normal file
142
app/state/musicmap.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import {
|
||||
AlbumListItem,
|
||||
AlbumWithSongs,
|
||||
Artist,
|
||||
ArtistInfo,
|
||||
PlaylistListItem,
|
||||
PlaylistWithSongs,
|
||||
Song,
|
||||
} from '@app/models/music'
|
||||
import {
|
||||
AlbumID3Element,
|
||||
ArtistID3Element,
|
||||
ArtistInfo2Element,
|
||||
ChildElement,
|
||||
PlaylistElement,
|
||||
PlaylistWithSongsElement,
|
||||
} from '@app/subsonic/elements'
|
||||
import { GetArtistResponse } from '@app/subsonic/responses'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
import { Store } from './store'
|
||||
|
||||
export type MusicMapSlice = {
|
||||
mapChildToSong: (child: ChildElement) => Promise<Song>
|
||||
mapChildrenToSongs: (children: ChildElement[]) => Promise<Song[]>
|
||||
mapArtistID3toArtist: (artist: ArtistID3Element) => Artist
|
||||
mapArtistInfo: (
|
||||
artistResponse: GetArtistResponse,
|
||||
info: ArtistInfo2Element,
|
||||
topSongs: ChildElement[],
|
||||
) => Promise<ArtistInfo>
|
||||
mapAlbumID3toAlbumListItem: (album: AlbumID3Element) => AlbumListItem
|
||||
mapAlbumID3toAlbum: (album: AlbumID3Element) => AlbumListItem
|
||||
mapAlbumID3WithSongstoAlbumWithSongs: (album: AlbumID3Element, songs: ChildElement[]) => Promise<AlbumWithSongs>
|
||||
mapPlaylistListItem: (playlist: PlaylistElement) => PlaylistListItem
|
||||
mapPlaylistWithSongs: (playlist: PlaylistWithSongsElement) => Promise<PlaylistWithSongs>
|
||||
}
|
||||
|
||||
class NoClientError extends Error {
|
||||
constructor() {
|
||||
super('no client in state')
|
||||
}
|
||||
}
|
||||
|
||||
export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>): MusicMapSlice => ({
|
||||
mapChildToSong: async child => {
|
||||
const client = get().client
|
||||
if (!client) {
|
||||
throw new NoClientError()
|
||||
}
|
||||
|
||||
return {
|
||||
itemType: 'song',
|
||||
id: child.id,
|
||||
album: child.album,
|
||||
albumId: child.albumId,
|
||||
artist: child.artist,
|
||||
artistId: child.artistId,
|
||||
title: child.title,
|
||||
track: child.track,
|
||||
duration: child.duration,
|
||||
starred: child.starred,
|
||||
coverArt: await get().getAlbumCoverArt(child.albumId),
|
||||
streamUri: client.streamUri({ id: child.id }),
|
||||
}
|
||||
},
|
||||
|
||||
mapChildrenToSongs: async children => {
|
||||
const songMaps: Promise<Song>[] = []
|
||||
for (const child of children) {
|
||||
songMaps.push(get().mapChildToSong(child))
|
||||
}
|
||||
return await Promise.all(songMaps)
|
||||
},
|
||||
|
||||
mapArtistID3toArtist: artist => {
|
||||
return {
|
||||
itemType: 'artist',
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
starred: artist.starred,
|
||||
coverArt: artist.coverArt,
|
||||
}
|
||||
},
|
||||
|
||||
mapArtistInfo: async (artistResponse, info, topSongs) => {
|
||||
const { artist, albums } = artistResponse
|
||||
|
||||
const mappedAlbums = albums.map(get().mapAlbumID3toAlbum)
|
||||
|
||||
return {
|
||||
...get().mapArtistID3toArtist(artist),
|
||||
albums: mappedAlbums,
|
||||
largeImageUrl: info.largeImageUrl,
|
||||
topSongs: (await get().mapChildrenToSongs(topSongs)).slice(0, 5),
|
||||
}
|
||||
},
|
||||
|
||||
mapAlbumID3toAlbumListItem: album => {
|
||||
return {
|
||||
itemType: 'album',
|
||||
id: album.id,
|
||||
name: album.name,
|
||||
artist: album.artist,
|
||||
artistId: album.artistId,
|
||||
starred: album.starred,
|
||||
coverArt: album.coverArt,
|
||||
}
|
||||
},
|
||||
|
||||
mapAlbumID3toAlbum: album => {
|
||||
return {
|
||||
...get().mapAlbumID3toAlbumListItem(album),
|
||||
coverArt: album.coverArt,
|
||||
year: album.year,
|
||||
}
|
||||
},
|
||||
|
||||
mapAlbumID3WithSongstoAlbumWithSongs: async (album, songs) => {
|
||||
return {
|
||||
...get().mapAlbumID3toAlbum(album),
|
||||
songs: await get().mapChildrenToSongs(songs),
|
||||
}
|
||||
},
|
||||
|
||||
mapPlaylistListItem: playlist => {
|
||||
return {
|
||||
itemType: 'playlist',
|
||||
id: playlist.id,
|
||||
name: playlist.name,
|
||||
comment: playlist.comment,
|
||||
coverArt: playlist.coverArt,
|
||||
}
|
||||
},
|
||||
|
||||
mapPlaylistWithSongs: async playlist => {
|
||||
return {
|
||||
...get().mapPlaylistListItem(playlist),
|
||||
songs: await get().mapChildrenToSongs(playlist.songs),
|
||||
coverArt: playlist.coverArt,
|
||||
}
|
||||
},
|
||||
})
|
||||
@ -4,10 +4,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import create from 'zustand'
|
||||
import { persist, StateStorage } from 'zustand/middleware'
|
||||
import { CacheSlice, createCacheSlice } from './cache'
|
||||
import { createMusicMapSlice, MusicMapSlice } from './musicmap'
|
||||
import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer'
|
||||
|
||||
export type Store = SettingsSlice &
|
||||
MusicSlice &
|
||||
MusicMapSlice &
|
||||
TrackPlayerSlice &
|
||||
CacheSlice & {
|
||||
hydrated: boolean
|
||||
@ -37,6 +39,7 @@ export const useStore = create<Store>(
|
||||
(set, get) => ({
|
||||
...createSettingsSlice(set, get),
|
||||
...createMusicSlice(set, get),
|
||||
...createMusicMapSlice(set, get),
|
||||
...createTrackPlayerSlice(set, get),
|
||||
...createCacheSlice(set, get),
|
||||
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
|
||||
export async function getItem(key: string): Promise<any | null> {
|
||||
try {
|
||||
const item = await AsyncStorage.getItem(key)
|
||||
return item ? JSON.parse(item) : null
|
||||
} catch (e) {
|
||||
console.error(`getItem error (key: ${key})`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function multiGet(keys: string[]): Promise<[string, any | null][]> {
|
||||
try {
|
||||
const items = await AsyncStorage.multiGet(keys)
|
||||
return items.map(x => [x[0], x[1] ? JSON.parse(x[1]) : null])
|
||||
} catch (e) {
|
||||
console.error('multiGet error', e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function setItem(key: string, item: any): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(key, JSON.stringify(item))
|
||||
} catch (e) {
|
||||
console.error(`setItem error (key: ${key})`, e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function multiSet(items: string[][]): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.multiSet(items.map(x => [x[0], JSON.stringify(x[1])]))
|
||||
} catch (e) {
|
||||
console.error('multiSet error', e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllKeys(): Promise<string[]> {
|
||||
try {
|
||||
return await AsyncStorage.getAllKeys()
|
||||
} catch (e) {
|
||||
console.error('getAllKeys error', e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function multiRemove(keys: string[]): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.multiRemove(keys)
|
||||
} catch (e) {
|
||||
console.error('multiRemove error', e)
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { DownloadedSong } from '@app/models/music'
|
||||
import { getItem, multiGet, multiSet } from '@app/storage/asyncstorage'
|
||||
|
||||
const key = {
|
||||
downloadedSongKeys: '@downloadedSongKeys',
|
||||
downloadedAlbumKeys: '@downloadedAlbumKeys',
|
||||
downloadedArtistKeys: '@downloadedArtistKeys',
|
||||
downloadedPlaylistKeys: '@downloadedPlaylistKeys',
|
||||
}
|
||||
|
||||
export async function getDownloadedSongs(): Promise<DownloadedSong[]> {
|
||||
const keysItem = await getItem(key.downloadedSongKeys)
|
||||
const keys: string[] = keysItem ? JSON.parse(keysItem) : []
|
||||
|
||||
const items = await multiGet(keys)
|
||||
return items.map(x => {
|
||||
const parsed = JSON.parse(x[1] as string)
|
||||
return {
|
||||
id: x[0],
|
||||
type: 'song',
|
||||
...parsed,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function setDownloadedSongs(items: DownloadedSong[]): Promise<void> {
|
||||
await multiSet([
|
||||
[key.downloadedSongKeys, JSON.stringify(items.map(x => x.id))],
|
||||
...items.map(x => [
|
||||
x.id,
|
||||
JSON.stringify({
|
||||
name: x.name,
|
||||
album: x.album,
|
||||
artist: x.artist,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user