reorg, remove old music slice files

This commit is contained in:
austinried
2022-03-20 16:16:16 +09:00
parent 2969b6c768
commit ba37348fc3
23 changed files with 139 additions and 345 deletions

View File

@@ -1,6 +1,6 @@
import PressableOpacity from '@app/components/PressableOpacity'
import { useStar } from '@app/hooks/music'
import { AlbumListItem, Artist, Song, StarrableItemType } from '@app/models/music'
import { StarrableItemType, Song, Artist, Album } from '@app/models/library'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { NavigationProp, useNavigation } from '@react-navigation/native'
@@ -199,7 +199,7 @@ const OptionViewAlbum = React.memo<{
// ))
export type AlbumContextPressableProps = ContextMenuProps & {
album: AlbumListItem
album: Album
}
export const AlbumContextPressable: React.FC<AlbumContextPressableProps> = props => {

View File

@@ -8,10 +8,10 @@ import Animated from 'react-native-reanimated'
import PressableOpacity from './PressableOpacity'
import IconMat from 'react-native-vector-icons/MaterialIcons'
import { ReactComponentLike } from 'prop-types'
import { AlbumListItem, Song } from '@app/models/music'
import { AlbumContextPressable, NowPlayingContextPressable } from './ContextMenu'
import { Album, Song } from '@app/models/library'
export type HeaderContextItem = Song | AlbumListItem
export type HeaderContextItem = Song | Album
const More = React.memo<{ contextItem?: HeaderContextItem }>(({ contextItem }) => {
const moreIcon = <IconMat name="more-vert" color="white" size={25} />

View File

@@ -1,5 +1,5 @@
import { useIsPlaying } from '@app/hooks/trackplayer'
import { AlbumListItem, Artist, ListableItem, Song } from '@app/models/music'
import { Album, Artist, ListableItem, Song } from '@app/models/library'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { useNavigation } from '@react-navigation/native'
@@ -97,7 +97,7 @@ const ListItem: React.FC<{
)
const albumPressable = useCallback(
({ children }) => (
<AlbumContextPressable album={item as AlbumListItem} onPress={onPress} triggerWrapperStyle={styles.item}>
<AlbumContextPressable album={item as Album} onPress={onPress} triggerWrapperStyle={styles.item}>
{children}
</AlbumContextPressable>
),

View File

@@ -1,5 +1,5 @@
import Button from '@app/components/Button'
import { Song } from '@app/models/music'
import { Song } from '@app/models/library'
import { useStore } from '@app/state/store'
import { QueueContextType, selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors'

View File

@@ -1,4 +1,4 @@
import { Album, PlaylistListItem, Artist, Song } from './music'
import { Album, Playlist, Artist, Song } from './library'
export enum CacheItemType {
coverArt = 'coverArt',
@@ -27,7 +27,7 @@ export type DownloadedAlbum = Album & {
songs: string[]
}
export type DownloadedPlaylist = PlaylistListItem & {
export type DownloadedPlaylist = Playlist & {
songs: string[]
}

View File

@@ -6,7 +6,12 @@ export interface Artist {
coverArt?: string
}
export interface AlbumListItem {
export interface ArtistInfo {
id: string
largeImageUrl?: string
}
export interface Album {
itemType: 'album'
id: string
name: string
@@ -14,14 +19,10 @@ export interface AlbumListItem {
artistId?: string
starred?: Date
coverArt?: string
}
export interface Album extends AlbumListItem {
coverArt?: string
year?: number
}
export interface PlaylistListItem {
export interface Playlist {
itemType: 'playlist'
id: string
name: string
@@ -41,11 +42,15 @@ export interface Song {
discNumber?: number
duration?: number
starred?: Date
// streamUri: string
coverArt?: string
}
export type ListableItem = Song | AlbumListItem | Artist | PlaylistListItem
export interface SearchResults {
artists: string[]
albums: string[]
songs: string[]
}
export type StarrableItemType = 'song' | 'album' | 'artist'
export type StarrableItemType = 'album' | 'song' | 'artist'
export type ListableItem = Album | Song | Artist | Playlist

14
app/models/state.ts Normal file
View File

@@ -0,0 +1,14 @@
export interface ById<T> {
[id: string]: T
}
export type OneToMany = ById<string[]>
export interface OrderedById<T> {
byId: ById<T>
allIds: string[]
}
export interface PaginatedList {
[offset: number]: string[]
}

View File

@@ -5,13 +5,13 @@ import GradientScrollView from '@app/components/GradientScrollView'
import Header from '@app/components/Header'
import HeaderBar from '@app/components/HeaderBar'
import ListItem from '@app/components/ListItem'
import { Album, Song } from '@app/models/music'
import { mapById } from '@app/state/library'
import { Album, Song } from '@app/models/library'
import { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors'
import dimensions from '@app/styles/dimensions'
import font from '@app/styles/font'
import { mapById } from '@app/util/state'
import { useLayout } from '@react-native-community/hooks'
import { useNavigation } from '@react-navigation/native'
import React, { useCallback, useEffect } from 'react'

View File

@@ -4,13 +4,13 @@ import GradientScrollView from '@app/components/GradientScrollView'
import Header from '@app/components/Header'
import NothingHere from '@app/components/NothingHere'
import { useActiveServerRefresh } from '@app/hooks/server'
import { AlbumListItem } from '@app/models/music'
import { mapById } from '@app/state/library'
import { Album } from '@app/models/library'
import { selectSettings } from '@app/state/settings'
import { useStore, useStoreDeep } from '@app/state/store'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { GetAlbumList2TypeBase, GetAlbumListType } from '@app/subsonic/params'
import { mapById } from '@app/util/state'
import { useNavigation } from '@react-navigation/native'
import equal from 'fast-deep-equal/es6/react'
import produce from 'immer'
@@ -26,7 +26,7 @@ const titles: { [key in GetAlbumListType]?: string } = {
}
const AlbumItem = React.memo<{
album: AlbumListItem
album: Album
}>(({ album }) => {
const navigation = useNavigation()

View File

@@ -3,19 +3,19 @@ import CoverArt from '@app/components/CoverArt'
import FilterButton, { OptionData } from '@app/components/FilterButton'
import GradientFlatList from '@app/components/GradientFlatList'
import { useFetchPaginatedList } from '@app/hooks/list'
import { Album, AlbumListItem } from '@app/models/music'
import { mapById } from '@app/state/library'
import { Album } from '@app/models/library'
import { selectSettings } from '@app/state/settings'
import { useStore, useStoreDeep } from '@app/state/store'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { GetAlbumList2Params, GetAlbumList2Type } from '@app/subsonic/params'
import { mapById } from '@app/util/state'
import { useNavigation } from '@react-navigation/native'
import React, { useCallback } from 'react'
import { StyleSheet, Text, useWindowDimensions, View } from 'react-native'
const AlbumItem = React.memo<{
album: AlbumListItem
album: Album
size: number
height: number
}>(({ album, size, height }) => {

View File

@@ -2,7 +2,7 @@ import FilterButton, { OptionData } from '@app/components/FilterButton'
import GradientFlatList from '@app/components/GradientFlatList'
import ListItem from '@app/components/ListItem'
import { useFetchList2 } from '@app/hooks/list'
import { Artist } from '@app/models/music'
import { Artist } from '@app/models/library'
import { ArtistFilterType } from '@app/models/settings'
import { selectSettings } from '@app/state/settings'
import { useStore, useStoreDeep } from '@app/state/store'

View File

@@ -1,12 +1,12 @@
import GradientFlatList from '@app/components/GradientFlatList'
import ListItem from '@app/components/ListItem'
import { useFetchList2 } from '@app/hooks/list'
import { PlaylistListItem } from '@app/models/music'
import { Playlist } from '@app/models/library'
import { useStore, useStoreDeep } from '@app/state/store'
import React from 'react'
import { StyleSheet } from 'react-native'
const PlaylistRenderItem: React.FC<{ item: PlaylistListItem }> = ({ item }) => (
const PlaylistRenderItem: React.FC<{ item: Playlist }> = ({ item }) => (
<ListItem item={item} showArt={true} showStar={false} listStyle="big" style={styles.listItem} />
)

View File

@@ -2,7 +2,7 @@ import GradientFlatList from '@app/components/GradientFlatList'
import ListItem from '@app/components/ListItem'
import NowPlayingBar from '@app/components/NowPlayingBar'
import { useSkipTo } from '@app/hooks/trackplayer'
import { Song } from '@app/models/music'
import { Song } from '@app/models/library'
import { useStore } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import { selectTrackPlayerMap } from '@app/state/trackplayermap'

View File

@@ -5,11 +5,12 @@ import ListItem from '@app/components/ListItem'
import NothingHere from '@app/components/NothingHere'
import TextInput from '@app/components/TextInput'
import { useActiveServerRefresh } from '@app/hooks/server'
import { Album, Artist, mapById, SearchResults, Song } from '@app/state/library'
import { Song, Album, Artist, SearchResults } from '@app/models/library'
import { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors'
import font from '@app/styles/font'
import { mapById } from '@app/util/state'
import { useFocusEffect, useNavigation } from '@react-navigation/native'
import debounce from 'lodash.debounce'
import React, { useCallback, useMemo, useRef, useState } from 'react'

View File

@@ -1,10 +1,11 @@
import GradientFlatList from '@app/components/GradientFlatList'
import ListItem from '@app/components/ListItem'
import { useFetchPaginatedList } from '@app/hooks/list'
import { Album, Artist, Song, mapById } from '@app/state/library'
import { Album, Artist, Song } from '@app/models/library'
import { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import { Search3Params } from '@app/subsonic/params'
import { mapById } from '@app/util/state'
import { useNavigation } from '@react-navigation/native'
import React, { useCallback, useEffect } from 'react'
import { StyleSheet } from 'react-native'

View File

@@ -6,7 +6,7 @@ import ListItem from '@app/components/ListItem'
import ListPlayerControls from '@app/components/ListPlayerControls'
import NothingHere from '@app/components/NothingHere'
import { useCoverArtFile } from '@app/hooks/cache'
import { Album, PlaylistListItem, Song } from '@app/models/music'
import { Song, Album, Playlist } from '@app/models/library'
import { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors'
@@ -46,7 +46,7 @@ const SongRenderItem: React.FC<{
const SongListDetails = React.memo<{
title: string
type: SongListType
songList?: Album | PlaylistListItem
songList?: Album | Playlist
songs?: Song[]
subtitle?: string
}>(({ title, songList, songs, subtitle, type }) => {

View File

@@ -1,3 +1,5 @@
import { Album, Artist, ArtistInfo, Playlist, SearchResults, Song } from '@app/models/library'
import { ById, OneToMany } from '@app/models/state'
import { Store } from '@app/state/store'
import {
AlbumID3Element,
@@ -20,141 +22,12 @@ import {
Search3Response,
SubsonicResponse,
} from '@app/subsonic/responses'
import { reduceById, mergeById } from '@app/util/state'
import produce from 'immer'
import { WritableDraft } from 'immer/dist/types/types-external'
import merge from 'lodash.merge'
import pick from 'lodash.pick'
import { GetState, SetState } from 'zustand'
export interface ById<T> {
[id: string]: T
}
export type OneToMany = ById<string[]>
export interface OrderedById<T> {
byId: ById<T>
allIds: string[]
}
export interface PaginatedList {
[offset: number]: string[]
}
export interface Artist {
itemType: 'artist'
id: string
name: string
starred?: Date
coverArt?: string
}
export interface ArtistInfo {
id: string
largeImageUrl?: string
}
export interface Album {
itemType: 'album'
id: string
name: string
artist?: string
artistId?: string
starred?: Date
coverArt?: string
year?: number
}
export interface Playlist {
itemType: 'playlist'
id: string
name: string
comment?: string
coverArt?: string
}
export interface Song {
itemType: 'song'
id: string
album?: string
albumId?: string
artist?: string
artistId?: string
title: string
track?: number
discNumber?: number
duration?: number
starred?: Date
coverArt?: string
}
export interface SearchResults {
artists: string[]
albums: string[]
songs: string[]
}
function mapArtist(artist: ArtistID3Element): Artist {
return {
itemType: 'artist',
id: artist.id,
name: artist.name,
starred: artist.starred,
coverArt: artist.coverArt,
}
}
function mapArtistInfo(id: string, info: ArtistInfo2Element): ArtistInfo {
return {
id,
largeImageUrl: info.largeImageUrl,
}
}
function mapAlbum(album: AlbumID3Element): Album {
return {
itemType: 'album',
id: album.id,
name: album.name,
artist: album.artist,
artistId: album.artistId,
starred: album.starred,
coverArt: album.coverArt,
year: album.year,
}
}
function mapPlaylist(playlist: PlaylistElement): Playlist {
return {
itemType: 'playlist',
id: playlist.id,
name: playlist.name,
comment: playlist.comment,
coverArt: playlist.coverArt,
}
}
function mapSong(song: ChildElement): Song {
return {
itemType: 'song',
id: song.id,
album: song.album,
albumId: song.albumId,
artist: song.artist,
artistId: song.artistId,
title: song.title,
track: song.track,
discNumber: song.discNumber,
duration: song.duration,
starred: song.starred,
coverArt: song.coverArt,
}
}
function mapId(entities: { id: string }[]): string[] {
return entities.map(e => e.id)
}
export type LibrarySlice = {
entities: {
artists: ById<Artist>
@@ -191,21 +64,6 @@ export type LibrarySlice = {
unstar: (params: StarParams) => Promise<void>
}
function reduceById<T extends { id: string }>(collection: T[]): ById<T> {
return collection.reduce((acc, value) => {
acc[value.id] = value
return acc
}, {} as ById<T>)
}
function mergeById<T extends { [id: string]: unknown }>(object: T, source: T): void {
merge(object, source)
}
export function mapById<T>(object: ById<T>, ids: string[]): T[] {
return ids.map(id => object[id]).filter(a => a !== undefined)
}
const defaultEntities = () => ({
artists: {},
artistAlbums: {},
@@ -576,3 +434,64 @@ export const createLibrarySlice = (set: SetState<Store>, get: GetState<Store>):
}
},
})
function mapArtist(artist: ArtistID3Element): Artist {
return {
itemType: 'artist',
id: artist.id,
name: artist.name,
starred: artist.starred,
coverArt: artist.coverArt,
}
}
function mapArtistInfo(id: string, info: ArtistInfo2Element): ArtistInfo {
return {
id,
largeImageUrl: info.largeImageUrl,
}
}
function mapAlbum(album: AlbumID3Element): Album {
return {
itemType: 'album',
id: album.id,
name: album.name,
artist: album.artist,
artistId: album.artistId,
starred: album.starred,
coverArt: album.coverArt,
year: album.year,
}
}
function mapPlaylist(playlist: PlaylistElement): Playlist {
return {
itemType: 'playlist',
id: playlist.id,
name: playlist.name,
comment: playlist.comment,
coverArt: playlist.coverArt,
}
}
function mapSong(song: ChildElement): Song {
return {
itemType: 'song',
id: song.id,
album: song.album,
albumId: song.albumId,
artist: song.artist,
artistId: song.artistId,
title: song.title,
track: song.track,
discNumber: song.discNumber,
duration: song.duration,
starred: song.starred,
coverArt: song.coverArt,
}
}
function mapId(entities: { id: string }[]): string[] {
return entities.map(e => e.id)
}

View File

@@ -1,67 +0,0 @@
import { Store } from '@app/state/store'
import produce from 'immer'
import { GetState, SetState } from 'zustand'
export type MusicSlice = {
albumIdCoverArt: { [id: string]: string | undefined }
albumIdCoverArtRequests: { [id: string]: Promise<void> }
fetchAlbumCoverArt: (id: string) => Promise<void>
getAlbumCoverArt: (id: string | undefined) => Promise<string | undefined>
}
export const createMusicSlice = (set: SetState<Store>, get: GetState<Store>): MusicSlice => ({
albumIdCoverArt: {},
albumIdCoverArtRequests: {},
fetchAlbumCoverArt: async id => {
const client = get().client
if (!client) {
return
}
const inProgress = get().albumIdCoverArtRequests[id]
if (inProgress !== undefined) {
return await inProgress
}
const promise = new Promise<void>(async resolve => {
try {
const response = await client.getAlbum({ id })
set(
produce<MusicSlice>(state => {
state.albumIdCoverArt[id] = response.data.album.coverArt
}),
)
} finally {
resolve()
}
}).then(() => {
set(
produce<MusicSlice>(state => {
delete state.albumIdCoverArtRequests[id]
}),
)
})
set(
produce<MusicSlice>(state => {
state.albumIdCoverArtRequests[id] = promise
}),
)
return await promise
},
getAlbumCoverArt: async id => {
if (!id) {
return
}
const existing = get().albumIdCoverArt[id]
if (existing) {
return existing
}
await get().fetchAlbumCoverArt(id)
return get().albumIdCoverArt[id]
},
})

View File

@@ -1,90 +0,0 @@
import { AlbumListItem, Artist, PlaylistListItem, Song } from '@app/models/music'
import { AlbumID3Element, ArtistID3Element, ChildElement, PlaylistElement } from '@app/subsonic/elements'
import { GetState, SetState } from 'zustand'
import { Store } from './store'
export type MusicMapSlice = {
mapChildToSong: (child: ChildElement, coverArt?: string) => Promise<Song>
mapChildrenToSongs: (children: ChildElement[], coverArt?: string) => Promise<Song[]>
mapArtistID3toArtist: (artist: ArtistID3Element) => Artist
mapAlbumID3toAlbumListItem: (album: AlbumID3Element) => AlbumListItem
mapAlbumID3toAlbum: (album: AlbumID3Element) => AlbumListItem
mapPlaylistListItem: (playlist: PlaylistElement) => PlaylistListItem
}
export const createMusicMapSlice = (set: SetState<Store>, get: GetState<Store>): MusicMapSlice => ({
mapChildToSong: async (child, coverArt) => {
return {
itemType: 'song',
id: child.id,
album: child.album,
albumId: child.albumId,
artist: child.artist,
artistId: child.artistId,
title: child.title,
track: child.track,
discNumber: child.discNumber,
duration: child.duration,
starred: child.starred,
coverArt: coverArt || (await get().getAlbumCoverArt(child.albumId)),
streamUri: get().buildStreamUri(child.id),
}
},
mapChildrenToSongs: async (children, coverArt) => {
const albumIds = children.reduce((acc, val) => {
if (val.albumId && !(val.albumId in acc)) {
acc[val.albumId] = get().getAlbumCoverArt(val.albumId)
}
return acc
}, {} as Record<string, Promise<string | undefined>>)
await Promise.all(Object.values(albumIds))
const songs: Song[] = []
for (const child of children) {
songs.push(await get().mapChildToSong(child, coverArt || (await get().getAlbumCoverArt(child.albumId))))
}
return songs
},
mapArtistID3toArtist: artist => {
return {
itemType: 'artist',
id: artist.id,
name: artist.name,
starred: artist.starred,
coverArt: artist.coverArt,
}
},
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,
}
},
mapPlaylistListItem: playlist => {
return {
itemType: 'playlist',
id: playlist.id,
name: playlist.name,
comment: playlist.comment,
coverArt: playlist.coverArt,
}
},
})

View File

@@ -1,4 +1,3 @@
import { createMusicSlice, MusicSlice } from '@app/state/music'
import { createSettingsSlice, SettingsSlice } from '@app/state/settings'
import AsyncStorage from '@react-native-async-storage/async-storage'
import equal from 'fast-deep-equal/es6/react'
@@ -7,16 +6,13 @@ import { persist, subscribeWithSelector } from 'zustand/middleware'
import { CacheSlice, createCacheSlice } from './cache'
import { createLibrarySlice, LibrarySlice } from './library'
import migrations from './migrations'
import { createMusicMapSlice, MusicMapSlice } from './musicmap'
import { createTrackPlayerSlice, TrackPlayerSlice } from './trackplayer'
import { createTrackPlayerMapSlice, TrackPlayerMapSlice } from './trackplayermap'
const DB_VERSION = migrations.length
export type Store = SettingsSlice &
MusicSlice &
LibrarySlice &
MusicMapSlice &
TrackPlayerSlice &
TrackPlayerMapSlice &
CacheSlice & {
@@ -34,9 +30,7 @@ export const useStore = create<
persist(
(set, get) => ({
...createSettingsSlice(set, get),
...createMusicSlice(set, get),
...createLibrarySlice(set, get),
...createMusicMapSlice(set, get),
...createTrackPlayerSlice(set, get),
...createTrackPlayerMapSlice(set, get),
...createCacheSlice(set, get),

View File

@@ -1,5 +1,5 @@
import { NoClientError } from '@app/models/error'
import { Song } from '@app/models/music'
import { Song } from '@app/models/library'
import PromiseQueue from '@app/util/PromiseQueue'
import produce from 'immer'
import TrackPlayer, { PlayerOptions, RepeatMode, State, Track } from 'react-native-track-player'

View File

@@ -1,4 +1,4 @@
import { Song } from '@app/models/music'
import { Song } from '@app/models/library'
import userAgent from '@app/util/userAgent'
import { GetState, SetState } from 'zustand'
import { Store } from './store'

17
app/util/state.ts Normal file
View File

@@ -0,0 +1,17 @@
import { ById } from '@app/models/state'
import merge from 'lodash.merge'
export function reduceById<T extends { id: string }>(collection: T[]): ById<T> {
return collection.reduce((acc, value) => {
acc[value.id] = value
return acc
}, {} as ById<T>)
}
export function mergeById<T extends { [id: string]: unknown }>(object: T, source: T): void {
merge(object, source)
}
export function mapById<T>(object: ById<T>, ids: string[]): T[] {
return ids.map(id => object[id]).filter(a => a !== undefined)
}