mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 06:52:43 +01:00
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:
@@ -37,7 +37,7 @@ function BackgroundHeaderFlatList<ItemT>(props: BackgroundHeaderFlatListProp<Ite
|
|||||||
</props.BackgroundComponent>
|
</props.BackgroundComponent>
|
||||||
}
|
}
|
||||||
ListHeaderComponentStyle={[headerStyle]}
|
ListHeaderComponentStyle={[headerStyle]}
|
||||||
ListEmptyComponent={<NothingHere style={styles.nothing} />}
|
ListEmptyComponent={props.ListEmptyComponent || <NothingHere style={styles.nothing} />}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useArtistArtFile, useCoverArtFile } from '@app/hooks/cache'
|
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 colors from '@app/styles/colors'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +18,6 @@ type BaseProps = {
|
|||||||
imageStyle?: ImageStyle
|
imageStyle?: ImageStyle
|
||||||
resizeMode?: ImageResizeMode
|
resizeMode?: ImageResizeMode
|
||||||
round?: boolean
|
round?: boolean
|
||||||
size?: CacheImageSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArtistCoverArtProps = BaseProps & {
|
type ArtistCoverArtProps = BaseProps & {
|
||||||
@@ -63,13 +62,13 @@ const ImageSource = React.memo<{ cache?: { file?: CacheFile; request?: CacheRequ
|
|||||||
)
|
)
|
||||||
|
|
||||||
const ArtistImage = React.memo<ArtistCoverArtProps>(props => {
|
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 }} />
|
return <ImageSource cache={cache} {...props} imageStyle={{ ...styles.artistImage, ...props.imageStyle }} />
|
||||||
})
|
})
|
||||||
|
|
||||||
const CoverArtImage = React.memo<CoverArtProps>(props => {
|
const CoverArtImage = React.memo<CoverArtProps>(props => {
|
||||||
const cache = useCoverArtFile(props.coverArt, props.size)
|
const cache = useCoverArtFile(props.coverArt)
|
||||||
|
|
||||||
return <ImageSource cache={cache} {...props} />
|
return <ImageSource cache={cache} {...props} />
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ const ListPlayerControls = React.memo<{
|
|||||||
<View style={styles.controlsCenter}>
|
<View style={styles.controlsCenter}>
|
||||||
<Button
|
<Button
|
||||||
title={`Play ${typeName}`}
|
title={`Play ${typeName}`}
|
||||||
|
disabled={songs.length === 0}
|
||||||
onPress={() => setQueue(songs, queueName, queueContextType, queueContextId, undefined, false)}
|
onPress={() => setQueue(songs, queueName, queueContextType, queueContextId, undefined, false)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.controlsSide}>
|
<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" />
|
<Icon name="shuffle" size={26} color="white" />
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CacheImageSize, CacheItemTypeKey } from '@app/models/cache'
|
import { CacheItemTypeKey } from '@app/models/cache'
|
||||||
import { selectCache } from '@app/state/cache'
|
import { selectCache } from '@app/state/cache'
|
||||||
import { selectSettings } from '@app/state/settings'
|
import { selectSettings } from '@app/state/settings'
|
||||||
import { Store, useStore, useStoreDeep } from '@app/state/store'
|
import { Store, useStore, useStoreDeep } from '@app/state/store'
|
||||||
@@ -35,20 +35,15 @@ const useFileRequest = (key: CacheItemTypeKey, id: string) => {
|
|||||||
return { file, request }
|
return { file, request }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCoverArtFile = (coverArt = '-1', size: CacheImageSize = 'thumbnail') => {
|
export const useCoverArtFile = (coverArt = '-1') => {
|
||||||
const type: CacheItemTypeKey = size === 'original' ? 'coverArt' : 'coverArtThumb'
|
const type: CacheItemTypeKey = 'coverArt'
|
||||||
const { file, request } = useFileRequest(type, coverArt)
|
const { file, request } = useFileRequest(type, coverArt)
|
||||||
const client = useStore(selectSettings.client)
|
const client = useStore(selectSettings.client)
|
||||||
const cacheItem = useStore(selectCache.cacheItem)
|
const cacheItem = useStore(selectCache.cacheItem)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!file && client) {
|
if (!file && client) {
|
||||||
cacheItem(type, coverArt, () =>
|
cacheItem(type, coverArt, () => client.getCoverArtUri({ id: coverArt }))
|
||||||
client.getCoverArtUri({
|
|
||||||
id: coverArt,
|
|
||||||
size: type === 'coverArtThumb' ? '256' : undefined,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
// intentionally leaving file out so it doesn't re-render if the request fails
|
// intentionally leaving file out so it doesn't re-render if the request fails
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -57,8 +52,8 @@ export const useCoverArtFile = (coverArt = '-1', size: CacheImageSize = 'thumbna
|
|||||||
return { file, request }
|
return { file, request }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumbnail') => {
|
export const useArtistArtFile = (artistId: string) => {
|
||||||
const type: CacheItemTypeKey = size === 'original' ? 'artistArt' : 'artistArtThumb'
|
const type: CacheItemTypeKey = 'artistArt'
|
||||||
const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
|
const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
|
||||||
const artistInfo = useStoreDeep(store => store.entities.artistInfo[artistId])
|
const artistInfo = useStoreDeep(store => store.entities.artistInfo[artistId])
|
||||||
const { file, request } = useFileRequest(type, artistId)
|
const { file, request } = useFileRequest(type, artistId)
|
||||||
@@ -70,10 +65,8 @@ export const useArtistArtFile = (artistId: string, size: CacheImageSize = 'thumb
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file) {
|
if (!file && artistInfo.largeImageUrl) {
|
||||||
cacheItem(type, artistId, async () => {
|
cacheItem(type, artistId, artistInfo.largeImageUrl)
|
||||||
return type === 'artistArtThumb' ? artistInfo?.smallImageUrl : artistInfo?.largeImageUrl
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
// intentionally leaving file out so it doesn't re-render if the request fails
|
// intentionally leaving file out so it doesn't re-render if the request fails
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Header from '@app/components/Header'
|
|||||||
import HeaderBar from '@app/components/HeaderBar'
|
import HeaderBar from '@app/components/HeaderBar'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import { Album, Song } from '@app/models/music'
|
import { Album, Song } from '@app/models/music'
|
||||||
|
import { mapById } from '@app/state/library'
|
||||||
import { useStore, useStoreDeep } from '@app/state/store'
|
import { useStore, useStoreDeep } from '@app/state/store'
|
||||||
import { selectTrackPlayer } from '@app/state/trackplayer'
|
import { selectTrackPlayer } from '@app/state/trackplayer'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
@@ -13,7 +14,6 @@ import dimensions from '@app/styles/dimensions'
|
|||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import { useLayout } from '@react-native-community/hooks'
|
import { useLayout } from '@react-native-community/hooks'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import pick from 'lodash.pick'
|
|
||||||
import React, { useCallback, useEffect } from 'react'
|
import React, { useCallback, useEffect } from 'react'
|
||||||
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
||||||
import { useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
|
import { useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
|
||||||
@@ -52,7 +52,7 @@ const TopSongs = React.memo<{
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header>Top Songs</Header>
|
<Header>Top Songs</Header>
|
||||||
{songs.map((s, i) => (
|
{songs.slice(0, 5).map((s, i) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={i}
|
key={i}
|
||||||
item={s}
|
item={s}
|
||||||
@@ -68,28 +68,11 @@ const TopSongs = React.memo<{
|
|||||||
})
|
})
|
||||||
|
|
||||||
const ArtistAlbums = React.memo<{
|
const ArtistAlbums = React.memo<{
|
||||||
id: string
|
albums: Album[]
|
||||||
}>(({ id }) => {
|
}>(({ albums }) => {
|
||||||
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)
|
|
||||||
const albumsLayout = useLayout()
|
const albumsLayout = useLayout()
|
||||||
|
|
||||||
useEffect(() => {
|
const sortedAlbums = [...albums]
|
||||||
if (!albums) {
|
|
||||||
fetchArtist(id)
|
|
||||||
}
|
|
||||||
}, [albums, fetchArtist, id])
|
|
||||||
|
|
||||||
const sortedAlbums = (albums ? Object.values(albums) : [])
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.sort((a, b) => (b.year || 0) - (a.year || 0))
|
.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 ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) => {
|
||||||
const artist = useStoreDeep(useCallback(store => store.entities.artists[id], [id]))
|
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 fetchArtist = useStore(store => store.fetchLibraryArtist)
|
||||||
const fetchArtistInfo = useStore(store => store.fetchLibraryArtistInfo)
|
const fetchTopSongs = useStore(store => store.fetchLibraryArtistTopSongs)
|
||||||
|
|
||||||
const coverLayout = useLayout()
|
const coverLayout = useLayout()
|
||||||
const headerOpacity = useSharedValue(0)
|
const headerOpacity = useSharedValue(0)
|
||||||
@@ -136,16 +126,16 @@ const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!artist) {
|
if (!artist || !albumIds) {
|
||||||
fetchArtist(id)
|
fetchArtist(id)
|
||||||
}
|
}
|
||||||
}, [artist, fetchArtist, id])
|
}, [artist, albumIds, fetchArtist, id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!artistInfo) {
|
if (artist && !topSongIds) {
|
||||||
fetchArtistInfo(id)
|
fetchTopSongs(artist.name)
|
||||||
}
|
}
|
||||||
}, [artistInfo, fetchArtistInfo, id])
|
}, [artist, fetchTopSongs, topSongIds])
|
||||||
|
|
||||||
if (!artist) {
|
if (!artist) {
|
||||||
return <ArtistViewFallback />
|
return <ArtistViewFallback />
|
||||||
@@ -160,17 +150,23 @@ const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) =>
|
|||||||
style={styles.scroll}
|
style={styles.scroll}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
onScroll={onScroll}>
|
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}>
|
<View style={styles.titleContainer}>
|
||||||
<Text style={styles.title}>{artist.name}</Text>
|
<Text style={styles.title}>{artist.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.contentContainer}>
|
<View style={styles.contentContainer}>
|
||||||
{/* {artist.topSongs.length > 0 ? (
|
{topSongs && albums ? (
|
||||||
<TopSongs songs={artist.topSongs} name={artist.name} artistId={artist.id} />
|
topSongs.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<TopSongs songs={topSongs} name={artist.name} artistId={artist.id} />
|
||||||
|
<ArtistAlbums albums={albums} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<ArtistAlbums albums={albums} />
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<ActivityIndicator size="large" color={colors.accent} style={styles.loading} />
|
||||||
)} */}
|
)}
|
||||||
<ArtistAlbums id={id} />
|
|
||||||
</View>
|
</View>
|
||||||
</GradientScrollView>
|
</GradientScrollView>
|
||||||
</View>
|
</View>
|
||||||
@@ -245,6 +241,9 @@ const styles = StyleSheet.create({
|
|||||||
fontFamily: font.regular,
|
fontFamily: font.regular,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
|
loading: {
|
||||||
|
marginTop: 30,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default ArtistView
|
export default ArtistView
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const SongCoverArt = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={coverArtStyles.container}>
|
<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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import HeaderBar from '@app/components/HeaderBar'
|
|||||||
import ImageGradientFlatList from '@app/components/ImageGradientFlatList'
|
import ImageGradientFlatList from '@app/components/ImageGradientFlatList'
|
||||||
import ListItem from '@app/components/ListItem'
|
import ListItem from '@app/components/ListItem'
|
||||||
import ListPlayerControls from '@app/components/ListPlayerControls'
|
import ListPlayerControls from '@app/components/ListPlayerControls'
|
||||||
|
import NothingHere from '@app/components/NothingHere'
|
||||||
import { useCoverArtFile } from '@app/hooks/cache'
|
import { useCoverArtFile } from '@app/hooks/cache'
|
||||||
import { Album, PlaylistListItem, Song } from '@app/models/music'
|
import { Album, PlaylistListItem, Song } from '@app/models/music'
|
||||||
import { useStore, useStoreDeep } from '@app/state/store'
|
import { useStore, useStoreDeep } from '@app/state/store'
|
||||||
@@ -49,15 +50,15 @@ const SongListDetails = React.memo<{
|
|||||||
songs?: Song[]
|
songs?: Song[]
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
}>(({ title, songList, songs, subtitle, type }) => {
|
}>(({ title, songList, songs, subtitle, type }) => {
|
||||||
const coverArtFile = useCoverArtFile(songList?.coverArt, 'thumbnail')
|
const coverArtFile = useCoverArtFile(songList?.coverArt)
|
||||||
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
|
const [headerColor, setHeaderColor] = useState<string | undefined>(undefined)
|
||||||
const setQueue = useStore(selectTrackPlayer.setQueue)
|
const setQueue = useStore(selectTrackPlayer.setQueue)
|
||||||
|
|
||||||
if (!songList || !songs) {
|
if (!songList) {
|
||||||
return <SongListDetailsFallback />
|
return <SongListDetailsFallback />
|
||||||
}
|
}
|
||||||
|
|
||||||
const _songs = [...songs]
|
const _songs = [...(songs || [])]
|
||||||
let typeName = ''
|
let typeName = ''
|
||||||
|
|
||||||
if (type === 'album') {
|
if (type === 'album') {
|
||||||
@@ -101,21 +102,26 @@ const SongListDetails = React.memo<{
|
|||||||
overScrollMode="never"
|
overScrollMode="never"
|
||||||
windowSize={7}
|
windowSize={7}
|
||||||
contentMarginTop={26}
|
contentMarginTop={26}
|
||||||
|
ListEmptyComponent={
|
||||||
|
songs ? (
|
||||||
|
<NothingHere style={styles.nothing} />
|
||||||
|
) : (
|
||||||
|
<ActivityIndicator size="large" color={colors.accent} style={styles.listLoading} />
|
||||||
|
)
|
||||||
|
}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<View style={styles.content}>
|
<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>
|
<Text style={styles.title}>{songList.name}</Text>
|
||||||
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
{subtitle ? <Text style={styles.subtitle}>{subtitle}</Text> : <></>}
|
||||||
{songs.length > 0 && (
|
<ListPlayerControls
|
||||||
<ListPlayerControls
|
style={styles.controls}
|
||||||
style={styles.controls}
|
songs={_songs}
|
||||||
songs={_songs}
|
typeName={typeName}
|
||||||
typeName={typeName}
|
queueName={songList.name}
|
||||||
queueName={songList.name}
|
queueContextId={songList.id}
|
||||||
queueContextId={songList.id}
|
queueContextType={type}
|
||||||
queueContextType={type}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -235,6 +241,12 @@ const styles = StyleSheet.create({
|
|||||||
listItem: {
|
listItem: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
},
|
},
|
||||||
|
nothing: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
listLoading: {
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default SongListView
|
export default SongListView
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export interface Artist {
|
|||||||
|
|
||||||
export interface ArtistInfo {
|
export interface ArtistInfo {
|
||||||
id: string
|
id: string
|
||||||
smallImageUrl?: string
|
|
||||||
largeImageUrl?: string
|
largeImageUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +107,6 @@ function mapArtist(artist: ArtistID3Element): Artist {
|
|||||||
function mapArtistInfo(id: string, info: ArtistInfo2Element): ArtistInfo {
|
function mapArtistInfo(id: string, info: ArtistInfo2Element): ArtistInfo {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
smallImageUrl: info.smallImageUrl,
|
|
||||||
largeImageUrl: info.largeImageUrl,
|
largeImageUrl: info.largeImageUrl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +117,7 @@ function mapAlbum(album: AlbumID3Element): Album {
|
|||||||
id: album.id,
|
id: album.id,
|
||||||
name: album.name,
|
name: album.name,
|
||||||
artist: album.artist,
|
artist: album.artist,
|
||||||
artistId: album.artist,
|
artistId: album.artistId,
|
||||||
starred: album.starred,
|
starred: album.starred,
|
||||||
coverArt: album.coverArt,
|
coverArt: album.coverArt,
|
||||||
year: album.year,
|
year: album.year,
|
||||||
|
|||||||
Reference in New Issue
Block a user