From 79a42b9adbadbf3c4c3a39f104a29d0fb026db05 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 19 Aug 2021 11:21:53 +0900 Subject: [PATCH] swtiched back to 2 sizes for images siwtched to Image for now to avoid double caching from FastImage --- app/components/CoverArt.tsx | 31 +++++++++------ app/hooks/cache.ts | 73 ++++++++++++++++++++++++++++++++++ app/hooks/music.ts | 62 ----------------------------- app/models/cache.ts | 39 ++++++++++++++++++ app/models/music.ts | 35 +--------------- app/screens/ArtistView.tsx | 1 + app/screens/LibraryAlbums.tsx | 5 ++- app/screens/NowPlayingView.tsx | 2 +- app/screens/SongListView.tsx | 7 ++-- app/state/cache.ts | 12 +++++- app/state/musicmap.ts | 1 + app/subsonic/api.ts | 3 ++ 12 files changed, 155 insertions(+), 116 deletions(-) create mode 100644 app/hooks/cache.ts create mode 100644 app/models/cache.ts diff --git a/app/components/CoverArt.tsx b/app/components/CoverArt.tsx index cfe42a8..35b2e40 100644 --- a/app/components/CoverArt.tsx +++ b/app/components/CoverArt.tsx @@ -1,15 +1,16 @@ -import { useArtistArtFile, useCoverArtFile } from '@app/hooks/music' -import { CacheFile, CacheRequest } from '@app/models/music' +import { useArtistArtFile, useCoverArtFile } from '@app/hooks/cache' +import { CacheFile, CacheImageSize, CacheRequest } from '@app/models/cache' import colors from '@app/styles/colors' import React, { useState } from 'react' -import { ActivityIndicator, StyleSheet, View, ViewStyle } from 'react-native' -import FastImage, { ImageStyle } from 'react-native-fast-image' +import { ActivityIndicator, StyleSheet, View, ViewStyle, Image, ImageStyle, ImageSourcePropType } from 'react-native' +import FastImage from 'react-native-fast-image' type BaseProps = { style?: ViewStyle imageStyle?: ImageStyle resizeMode?: keyof typeof FastImage.resizeMode round?: boolean + size?: CacheImageSize } type ArtistCoverArtProps = BaseProps & { @@ -22,21 +23,27 @@ type CoverArtProps = BaseProps & { coverArt?: string } -const Image = React.memo<{ cache?: { file?: CacheFile; request?: CacheRequest } } & BaseProps>( +const ImageSource = React.memo<{ cache?: { file?: CacheFile; request?: CacheRequest } } & BaseProps>( ({ cache, style, imageStyle, resizeMode }) => { const [error, setError] = useState(false) - let source + if (error) { + console.log('error!') + console.log(cache?.file?.path) + } + + let source: ImageSourcePropType if (!error && cache?.file && !cache?.request?.promise) { - source = { uri: `file://${cache.file.path}` } + source = { uri: `file://${cache.file.path}`, cache: 'reload' } } else { source = require('@res/fallback.png') } return ( <> - setError(true)} @@ -53,15 +60,15 @@ const Image = React.memo<{ cache?: { file?: CacheFile; request?: CacheRequest } ) const ArtistImage = React.memo(props => { - const cache = useArtistArtFile(props.artistId) + const cache = useArtistArtFile(props.artistId, props.size) - return + return }) const CoverArtImage = React.memo(props => { - const cache = useCoverArtFile(props.coverArt) + const cache = useCoverArtFile(props.coverArt, props.size) - return + return }) const CoverArt = React.memo(props => { diff --git a/app/hooks/cache.ts b/app/hooks/cache.ts new file mode 100644 index 0000000..b86ea1f --- /dev/null +++ b/app/hooks/cache.ts @@ -0,0 +1,73 @@ +import { CacheImageSize, CacheItemTypeKey } from '@app/models/cache' +import { selectCache } from '@app/state/cache' +import { selectSettings } from '@app/state/settings' +import { useStore, Store } from '@app/state/store' +import { useCallback, useEffect } from 'react' +import { useArtistInfo } from './music' + +const useFileRequest = (key: CacheItemTypeKey, id: string) => { + const file = useStore( + useCallback( + (store: Store) => { + const activeServerId = store.settings.activeServer + if (!activeServerId) { + return + } + + return store.cacheFiles[activeServerId][key][id] + }, + [key, id], + ), + ) + const request = useStore( + useCallback( + (store: Store) => { + const activeServerId = store.settings.activeServer + if (!activeServerId) { + return + } + + return store.cacheRequests[activeServerId][key][id] + }, + [key, id], + ), + ) + + return { file, request } +} + +export const useCoverArtFile = (coverArt = '-1', size: CacheImageSize = 'thumbnail') => { + const type: CacheItemTypeKey = size === 'original' ? 'coverArt' : 'coverArtThumb' + const { file, request } = useFileRequest(type, coverArt) + const client = useStore(selectSettings.client) + const cacheItem = useStore(selectCache.cacheItem) + + useEffect(() => { + if (!file && client) { + cacheItem(type, coverArt, () => + client.getCoverArtUri({ + id: coverArt, + size: type === 'coverArtThumb' ? '256' : undefined, + }), + ) + } + }, [cacheItem, client, coverArt, file, type]) + + return { file, request } +} + +export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumbnail') => { + const type: CacheItemTypeKey = size === 'original' ? 'artistArt' : 'artistArtThumb' + const artistInfo = useArtistInfo(artistId) + const { file, request } = useFileRequest(type, artistId) + const cacheItem = useStore(selectCache.cacheItem) + + useEffect(() => { + const url = type === 'artistArtThumb' ? artistInfo?.smallImageUrl : artistInfo?.largeImageUrl + if (!file && artistInfo && url) { + cacheItem(type, artistId, url) + } + }, [artistId, artistInfo, cacheItem, file, type]) + + return { file, request } +} diff --git a/app/hooks/music.ts b/app/hooks/music.ts index 83d59ff..c5bee1f 100644 --- a/app/hooks/music.ts +++ b/app/hooks/music.ts @@ -1,7 +1,4 @@ -import { CacheItemTypeKey } from '@app/models/music' -import { selectCache } from '@app/state/cache' import { selectMusic } from '@app/state/music' -import { selectSettings } from '@app/state/settings' import { Store, useStore } from '@app/state/store' import { useCallback, useEffect } from 'react' @@ -63,62 +60,3 @@ export const useStarred = (id: string, type: string) => { ), ) } - -const useFileRequest = (key: CacheItemTypeKey, id: string) => { - const file = useStore( - useCallback( - (store: Store) => { - const activeServerId = store.settings.activeServer - if (!activeServerId) { - return - } - - return store.cacheFiles[activeServerId][key][id] - }, - [key, id], - ), - ) - const request = useStore( - useCallback( - (store: Store) => { - const activeServerId = store.settings.activeServer - if (!activeServerId) { - return - } - - return store.cacheRequests[activeServerId][key][id] - }, - [key, id], - ), - ) - - return { file, request } -} - -export const useCoverArtFile = (coverArt: string = '-1') => { - const { file, request } = useFileRequest('coverArt', coverArt) - const client = useStore(selectSettings.client) - const cacheItem = useStore(selectCache.cacheItem) - - useEffect(() => { - if (!file && client) { - cacheItem('coverArt', coverArt, () => client.getCoverArtUri({ id: coverArt })) - } - }, [cacheItem, client, coverArt, file]) - - return { file, request } -} - -export const useArtistArtFile = (artistId: string) => { - const artistInfo = useArtistInfo(artistId) - const { file, request } = useFileRequest('artistArt', artistId) - const cacheItem = useStore(selectCache.cacheItem) - - useEffect(() => { - if (!file && artistInfo && artistInfo.largeImageUrl) { - cacheItem('artistArt', artistId, artistInfo.largeImageUrl) - } - }, [artistId, artistInfo, artistInfo?.largeImageUrl, cacheItem, file]) - - return { file, request } -} diff --git a/app/models/cache.ts b/app/models/cache.ts new file mode 100644 index 0000000..cff273e --- /dev/null +++ b/app/models/cache.ts @@ -0,0 +1,39 @@ +import { Album, PlaylistListItem, Artist, Song } from './music' + +export enum CacheItemType { + coverArt = 'coverArt', + coverArtThumb = 'coverArtThumb', + artistArt = 'artistArt', + artistArtThumb = 'artistArtThumb', + song = 'song', +} + +export type CacheItemTypeKey = keyof typeof CacheItemType + +export type CacheImageSize = 'thumbnail' | 'original' + +export type CacheFile = { + path: string + date: number + permanent: boolean +} + +export type CacheRequest = { + progress: number + promise?: Promise +} + +export type DownloadedAlbum = Album & { + songs: string[] +} + +export type DownloadedPlaylist = PlaylistListItem & { + songs: string[] +} + +export type DownloadedArtist = Artist & { + topSongs: string[] + albums: string[] +} + +export type DownloadedSong = Song diff --git a/app/models/music.ts b/app/models/music.ts index d41e55e..7bb94d4 100644 --- a/app/models/music.ts +++ b/app/models/music.ts @@ -8,6 +8,7 @@ export interface Artist { export interface ArtistInfo extends Artist { albums: Album[] + smallImageUrl?: string largeImageUrl?: string topSongs: Song[] } @@ -70,37 +71,3 @@ export type ListableItem = Song | AlbumListItem | Artist | PlaylistListItem export type HomeLists = { [key: string]: AlbumListItem[] } export type StarrableItemType = 'song' | 'album' | 'artist' - -export enum CacheItemType { - coverArt = 'coverArt', - artistArt = 'artistArt', - song = 'song', -} - -export type CacheItemTypeKey = keyof typeof CacheItemType - -export type CacheFile = { - path: string - date: number - permanent: boolean -} - -export type CacheRequest = { - progress: number - promise?: Promise -} - -export type DownloadedAlbum = Album & { - songs: string[] -} - -export type DownloadedPlaylist = PlaylistListItem & { - songs: string[] -} - -export type DownloadedArtist = Artist & { - topSongs: string[] - albums: string[] -} - -export type DownloadedSong = Song diff --git a/app/screens/ArtistView.tsx b/app/screens/ArtistView.tsx index 450175b..664afbf 100644 --- a/app/screens/ArtistView.tsx +++ b/app/screens/ArtistView.tsx @@ -118,6 +118,7 @@ const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) => onScroll={onScroll}> { const fetchAlbums = useStore(selectMusic.fetchAlbums) - const { list, refreshing, refresh, fetchNextPage } = useFetchPaginatedList(fetchAlbums, 60) + const { list, refreshing, refresh, fetchNextPage } = useFetchPaginatedList(fetchAlbums, 300) const layout = useWindowDimensions() @@ -69,7 +69,8 @@ const AlbumsList = () => { onRefresh={refresh} overScrollMode="never" onEndReached={fetchNextPage} - onEndReachedThreshold={1} + onEndReachedThreshold={6} + disableVirtualization={true} getItemLayout={(_data, index) => ({ length: height, offset: height * Math.floor(index / 3), diff --git a/app/screens/NowPlayingView.tsx b/app/screens/NowPlayingView.tsx index a2bd8a9..d2fe590 100644 --- a/app/screens/NowPlayingView.tsx +++ b/app/screens/NowPlayingView.tsx @@ -95,7 +95,7 @@ const SongCoverArt = () => { return ( - + ) } diff --git a/app/screens/SongListView.tsx b/app/screens/SongListView.tsx index f731be6..aae8d3d 100644 --- a/app/screens/SongListView.tsx +++ b/app/screens/SongListView.tsx @@ -4,7 +4,8 @@ import ImageGradientScrollView from '@app/components/ImageGradientScrollView' import ListItem from '@app/components/ListItem' import ListPlayerControls from '@app/components/ListPlayerControls' import NothingHere from '@app/components/NothingHere' -import { useAlbumWithSongs, useCoverArtFile, usePlaylistWithSongs } from '@app/hooks/music' +import { useCoverArtFile } from '@app/hooks/cache' +import { useAlbumWithSongs, usePlaylistWithSongs } from '@app/hooks/music' import { AlbumWithSongs, PlaylistWithSongs, Song } from '@app/models/music' import { useStore } from '@app/state/store' import { selectTrackPlayer } from '@app/state/trackplayer' @@ -74,7 +75,7 @@ const SongListDetails = React.memo<{ songList?: AlbumWithSongs | PlaylistWithSongs subtitle?: string }>(({ songList, subtitle, type }) => { - const coverArtFile = useCoverArtFile(songList?.coverArt) + const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail') if (!songList) { return @@ -83,7 +84,7 @@ const SongListDetails = React.memo<{ return ( - + {songList.name} {subtitle ? {subtitle} : <>} {songList.songs.length > 0 ? ( diff --git a/app/state/cache.ts b/app/state/cache.ts index 49c1546..c3aac7d 100644 --- a/app/state/cache.ts +++ b/app/state/cache.ts @@ -1,4 +1,4 @@ -import { CacheFile, CacheItemType, CacheItemTypeKey, CacheRequest } from '@app/models/music' +import { CacheFile, CacheItemType, CacheItemTypeKey, CacheRequest } from '@app/models/cache' import { mkdir, rmdir } from '@app/util/fs' import PromiseQueue from '@app/util/PromiseQueue' import produce from 'immer' @@ -6,7 +6,7 @@ import RNFS from 'react-native-fs' import { GetState, SetState } from 'zustand' import { Store } from './store' -const imageDownloadQueue = new PromiseQueue(20) +const imageDownloadQueue = new PromiseQueue(50) const songDownloadQueue = new PromiseQueue(1) export type CacheDownload = CacheFile & CacheRequest @@ -168,7 +168,9 @@ export const createCacheSlice = (set: SetState, get: GetState): Ca state.cacheFiles[serverId] = { song: {}, coverArt: {}, + coverArtThumb: {}, artistArt: {}, + artistArtThumb: {}, } }), ) @@ -183,14 +185,18 @@ export const createCacheSlice = (set: SetState, get: GetState): Ca state.cacheDirs[serverId] = { song: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/song`, coverArt: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/coverArt`, + coverArtThumb: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/coverArtThumb`, artistArt: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/artistArt`, + artistArtThumb: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/artistArtThumb`, } } if (!state.cacheRequests[serverId]) { state.cacheRequests[serverId] = { song: {}, coverArt: {}, + coverArtThumb: {}, artistArt: {}, + artistArtThumb: {}, } } }), @@ -257,7 +263,9 @@ export const createCacheSlice = (set: SetState, get: GetState): Ca set( produce(state => { state.cacheFiles[serverId].coverArt = {} + state.cacheFiles[serverId].coverArtThumb = {} state.cacheFiles[serverId].artistArt = {} + state.cacheFiles[serverId].artistArtThumb = {} }), ) } diff --git a/app/state/musicmap.ts b/app/state/musicmap.ts index 18adf2c..dbe0c13 100644 --- a/app/state/musicmap.ts +++ b/app/state/musicmap.ts @@ -79,6 +79,7 @@ export const createMusicMapSlice = (set: SetState, get: GetState): return { ...get().mapArtistID3toArtist(artist), albums: mappedAlbums, + smallImageUrl: info.smallImageUrl, largeImageUrl: info.largeImageUrl, topSongs: (await get().mapChildrenToSongs(topSongs)).slice(0, 5), } diff --git a/app/subsonic/api.ts b/app/subsonic/api.ts index 59da120..934ffb0 100644 --- a/app/subsonic/api.ts +++ b/app/subsonic/api.ts @@ -120,6 +120,9 @@ export class SubsonicApiClient { const params = new URLSearchParams() for (const key of keys) { + if (obj[key] === undefined || obj[key] === null) { + continue + } params.append(key, String(obj[key])) }