use only original/large imges for covers/artist

fix view artist from context menu
add loading indicators to song list and artist views (show info we have right away)
This commit is contained in:
austinried
2022-03-20 15:27:27 +09:00
parent a15159014c
commit c9aea9065c
8 changed files with 78 additions and 74 deletions

View File

@@ -37,7 +37,7 @@ function BackgroundHeaderFlatList<ItemT>(props: BackgroundHeaderFlatListProp<Ite
</props.BackgroundComponent>
}
ListHeaderComponentStyle={[headerStyle]}
ListEmptyComponent={<NothingHere style={styles.nothing} />}
ListEmptyComponent={props.ListEmptyComponent || <NothingHere style={styles.nothing} />}
/>
)
}

View File

@@ -1,5 +1,5 @@
import { useArtistArtFile, useCoverArtFile } from '@app/hooks/cache'
import { CacheFile, CacheImageSize, CacheRequest } from '@app/models/cache'
import { CacheFile, CacheRequest } from '@app/models/cache'
import colors from '@app/styles/colors'
import React, { useState } from 'react'
import {
@@ -18,7 +18,6 @@ type BaseProps = {
imageStyle?: ImageStyle
resizeMode?: ImageResizeMode
round?: boolean
size?: CacheImageSize
}
type ArtistCoverArtProps = BaseProps & {
@@ -63,13 +62,13 @@ const ImageSource = React.memo<{ cache?: { file?: CacheFile; request?: CacheRequ
)
const ArtistImage = React.memo<ArtistCoverArtProps>(props => {
const cache = useArtistArtFile(props.artistId, props.size)
const cache = useArtistArtFile(props.artistId)
return <ImageSource cache={cache} {...props} imageStyle={{ ...styles.artistImage, ...props.imageStyle }} />
})
const CoverArtImage = React.memo<CoverArtProps>(props => {
const cache = useCoverArtFile(props.coverArt, props.size)
const cache = useCoverArtFile(props.coverArt)
return <ImageSource cache={cache} {...props} />
})

View File

@@ -36,11 +36,14 @@ const ListPlayerControls = React.memo<{
<View style={styles.controlsCenter}>
<Button
title={`Play ${typeName}`}
disabled={songs.length === 0}
onPress={() => setQueue(songs, queueName, queueContextType, queueContextId, undefined, false)}
/>
</View>
<View style={styles.controlsSide}>
<Button onPress={() => setQueue(songs, queueName, queueContextType, queueContextId, undefined, true)}>
<Button
disabled={songs.length === 0}
onPress={() => setQueue(songs, queueName, queueContextType, queueContextId, undefined, true)}>
<Icon name="shuffle" size={26} color="white" />
</Button>
</View>

View File

@@ -1,4 +1,4 @@
import { CacheImageSize, CacheItemTypeKey } from '@app/models/cache'
import { CacheItemTypeKey } from '@app/models/cache'
import { selectCache } from '@app/state/cache'
import { selectSettings } from '@app/state/settings'
import { Store, useStore, useStoreDeep } from '@app/state/store'
@@ -35,20 +35,15 @@ const useFileRequest = (key: CacheItemTypeKey, id: string) => {
return { file, request }
}
export const useCoverArtFile = (coverArt = '-1', size: CacheImageSize = 'thumbnail') => {
const type: CacheItemTypeKey = size === 'original' ? 'coverArt' : 'coverArtThumb'
export const useCoverArtFile = (coverArt = '-1') => {
const type: CacheItemTypeKey = 'coverArt'
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(type, coverArt, () => client.getCoverArtUri({ id: coverArt }))
}
// intentionally leaving file out so it doesn't re-render if the request fails
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -57,8 +52,8 @@ export const useCoverArtFile = (coverArt = '-1', size: CacheImageSize = 'thumbna
return { file, request }
}
export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumbnail') => {
const type: CacheItemTypeKey = size === 'original' ? 'artistArt' : 'artistArtThumb'
export const useArtistArtFile = (artistId: string) => {
const type: CacheItemTypeKey = 'artistArt'
const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
const artistInfo = useStoreDeep(store => store.entities.artistInfo[artistId])
const { file, request } = useFileRequest(type, artistId)
@@ -70,10 +65,8 @@ export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumb
return
}
if (!file) {
cacheItem(type, artistId, async () => {
return type === 'artistArtThumb' ? artistInfo?.smallImageUrl : artistInfo?.largeImageUrl
})
if (!file && artistInfo.largeImageUrl) {
cacheItem(type, artistId, artistInfo.largeImageUrl)
}
// intentionally leaving file out so it doesn't re-render if the request fails
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -6,6 +6,7 @@ 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 { useStore, useStoreDeep } from '@app/state/store'
import { selectTrackPlayer } from '@app/state/trackplayer'
import colors from '@app/styles/colors'
@@ -13,7 +14,6 @@ import dimensions from '@app/styles/dimensions'
import font from '@app/styles/font'
import { useLayout } from '@react-native-community/hooks'
import { useNavigation } from '@react-navigation/native'
import pick from 'lodash.pick'
import React, { useCallback, useEffect } from 'react'
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
import { useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
@@ -52,7 +52,7 @@ const TopSongs = React.memo<{
return (
<>
<Header>Top Songs</Header>
{songs.map((s, i) => (
{songs.slice(0, 5).map((s, i) => (
<ListItem
key={i}
item={s}
@@ -68,28 +68,11 @@ const TopSongs = React.memo<{
})
const ArtistAlbums = React.memo<{
id: string
}>(({ id }) => {
const albums = useStoreDeep(
useCallback(
store => {
const ids = store.entities.artistAlbums[id]
return ids ? pick(store.entities.albums, ids) : undefined
},
[id],
),
)
const fetchArtist = useStore(store => store.fetchLibraryArtist)
albums: Album[]
}>(({ albums }) => {
const albumsLayout = useLayout()
useEffect(() => {
if (!albums) {
fetchArtist(id)
}
}, [albums, fetchArtist, id])
const sortedAlbums = (albums ? Object.values(albums) : [])
const sortedAlbums = [...albums]
.sort((a, b) => a.name.localeCompare(b.name))
.sort((a, b) => (b.year || 0) - (a.year || 0))
@@ -115,10 +98,17 @@ const ArtistViewFallback = React.memo(() => (
const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) => {
const artist = useStoreDeep(useCallback(store => store.entities.artists[id], [id]))
const artistInfo = useStoreDeep(useCallback(store => store.entities.artistInfo[id], [id]))
const topSongIds = useStoreDeep(useCallback(store => store.entities.artistNameTopSongs[artist?.name], [artist?.name]))
const topSongs = useStoreDeep(
useCallback(store => (topSongIds ? mapById(store.entities.songs, topSongIds) : undefined), [topSongIds]),
)
const albumIds = useStoreDeep(useCallback(store => store.entities.artistAlbums[id], [id]))
const albums = useStoreDeep(
useCallback(store => (albumIds ? mapById(store.entities.albums, albumIds) : undefined), [albumIds]),
)
const fetchArtist = useStore(store => store.fetchLibraryArtist)
const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
const fetchTopSongs = useStore(store => store.fetchLibraryArtistTopSongs)
const coverLayout = useLayout()
const headerOpacity = useSharedValue(0)
@@ -136,16 +126,16 @@ const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) =>
})
useEffect(() => {
if (!artist) {
if (!artist || !albumIds) {
fetchArtist(id)
}
}, [artist, fetchArtist, id])
}, [artist, albumIds, fetchArtist, id])
useEffect(() => {
if (!artistInfo) {
fetchArtistInfo(id)
if (artist && !topSongIds) {
fetchTopSongs(artist.name)
}
}, [artistInfo, fetchArtistInfo, id])
}, [artist, fetchTopSongs, topSongIds])
if (!artist) {
return <ArtistViewFallback />
@@ -160,17 +150,23 @@ const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) =>
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
onScroll={onScroll}>
<CoverArt type="artist" size="original" artistId={artist.id} style={styles.artistCover} resizeMode={'cover'} />
<CoverArt type="artist" artistId={artist.id} style={styles.artistCover} resizeMode={'cover'} />
<View style={styles.titleContainer}>
<Text style={styles.title}>{artist.name}</Text>
</View>
<View style={styles.contentContainer}>
{/* {artist.topSongs.length > 0 ? (
<TopSongs songs={artist.topSongs} name={artist.name} artistId={artist.id} />
{topSongs && albums ? (
topSongs.length > 0 ? (
<>
<TopSongs songs={topSongs} name={artist.name} artistId={artist.id} />
<ArtistAlbums albums={albums} />
</>
) : (
<></>
)} */}
<ArtistAlbums id={id} />
<ArtistAlbums albums={albums} />
)
) : (
<ActivityIndicator size="large" color={colors.accent} style={styles.loading} />
)}
</View>
</GradientScrollView>
</View>
@@ -245,6 +241,9 @@ const styles = StyleSheet.create({
fontFamily: font.regular,
textAlign: 'center',
},
loading: {
marginTop: 30,
},
})
export default ArtistView

View File

@@ -96,7 +96,7 @@ const SongCoverArt = () => {
return (
<View style={coverArtStyles.container}>
<CoverArt type="cover" size="original" coverArt={track?.coverArt} style={coverArtStyles.image} />
<CoverArt type="cover" coverArt={track?.coverArt} style={coverArtStyles.image} />
</View>
)
}

View File

@@ -4,6 +4,7 @@ import HeaderBar from '@app/components/HeaderBar'
import ImageGradientFlatList from '@app/components/ImageGradientFlatList'
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 { useStore, useStoreDeep } from '@app/state/store'
@@ -49,15 +50,15 @@ const SongListDetails = React.memo<{
songs?: Song[]
subtitle?: string
}>(({ title, songList, songs, subtitle, type }) => {
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
const coverArtFile = useCoverArtFile(songList?.coverArt)
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
const setQueue = useStore(selectTrackPlayer.setQueue)
if (!songList || !songs) {
if (!songList) {
return <SongListDetailsFallback />
}
const _songs = [...songs]
const _songs = [...(songs || [])]
let typeName = ''
if (type === 'album') {
@@ -101,12 +102,18 @@ const SongListDetails = React.memo<{
overScrollMode="never"
windowSize={7}
contentMarginTop={26}
ListEmptyComponent={
songs ? (
<NothingHere style={styles.nothing} />
) : (
<ActivityIndicator size="large" color={colors.accent} style={styles.listLoading} />
)
}
ListHeaderComponent={
<View style={styles.content}>
<CoverArt type="cover" size="original" coverArt={songList.coverArt} style={styles.cover} />
<CoverArt type="cover" coverArt={songList.coverArt} style={styles.cover} />
<Text style={styles.title}>{songList.name}</Text>
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
{songs.length > 0 && (
<ListPlayerControls
style={styles.controls}
songs={_songs}
@@ -115,7 +122,6 @@ const SongListDetails = React.memo<{
queueContextId={songList.id}
queueContextType={type}
/>
)}
</View>
}
/>
@@ -235,6 +241,12 @@ const styles = StyleSheet.create({
listItem: {
paddingHorizontal: 20,
},
nothing: {
width: '100%',
},
listLoading: {
marginTop: 10,
},
})
export default SongListView

View File

@@ -51,7 +51,6 @@ export interface Artist {
export interface ArtistInfo {
id: string
smallImageUrl?: string
largeImageUrl?: string
}
@@ -108,7 +107,6 @@ function mapArtist(artist: ArtistID3Element): Artist {
function mapArtistInfo(id: string, info: ArtistInfo2Element): ArtistInfo {
return {
id,
smallImageUrl: info.smallImageUrl,
largeImageUrl: info.largeImageUrl,
}
}
@@ -119,7 +117,7 @@ function mapAlbum(album: AlbumID3Element): Album {
id: album.id,
name: album.name,
artist: album.artist,
artistId: album.artist,
artistId: album.artistId,
starred: album.starred,
coverArt: album.coverArt,
year: album.year,