diff --git a/src/components/common/AlbumArt.tsx b/src/components/common/AlbumArt.tsx index f78fecd..9b9ed2b 100644 --- a/src/components/common/AlbumArt.tsx +++ b/src/components/common/AlbumArt.tsx @@ -1,14 +1,21 @@ +import { useAtomValue } from 'jotai/utils'; import React from 'react'; +import { ActivityIndicator, View } from 'react-native'; import FastImage from 'react-native-fast-image'; import LinearGradient from 'react-native-linear-gradient'; +import { albumArtAtomFamily } from '../../state/music'; import colors from '../../styles/colors'; import CoverArt from './CoverArt'; -const AlbumArt: React.FC<{ - height: number, - width: number, - coverArtUri?: string -}> = ({ height, width, coverArtUri }) => { +interface AlbumArtProps { + id: string; + height: number; + width: number; +} + +const AlbumArt: React.FC = ({ id, height, width }) => { + const albumArt = useAtomValue(albumArtAtomFamily(id)); + const Placeholder = () => ( 128 ? albumArt?.uri : albumArt?.thumbUri} /> ); } -export default React.memo(AlbumArt); +const AlbumArtFallback: React.FC = ({ height, width }) => ( + + + +); + +const AlbumArtLoader: React.FC = (props) => ( + }> + + +); + +export default React.memo(AlbumArtLoader); diff --git a/src/components/common/AlbumView.tsx b/src/components/common/AlbumView.tsx index 9522379..e349b8d 100644 --- a/src/components/common/AlbumView.tsx +++ b/src/components/common/AlbumView.tsx @@ -1,7 +1,7 @@ import { useNavigation } from '@react-navigation/native'; import { useAtomValue } from 'jotai/utils'; import React, { useEffect, useState } from 'react'; -import { GestureResponderEvent, Image, Pressable, ScrollView, Text, useWindowDimensions, View } from 'react-native'; +import { GestureResponderEvent, Image, Pressable, Text, useWindowDimensions, View } from 'react-native'; import { useCurrentTrackId, useSetQueue } from '../../hooks/player'; import { albumAtomFamily } from '../../state/music'; import colors from '../../styles/colors'; @@ -100,12 +100,7 @@ const AlbumDetails: React.FC<{ paddingTop: coverSize / 8, }} > - - + = ({ height, width, children}) => ( +interface ArtistArtSizeProps { + height: number; + width: number; +}; + +interface ArtistArtXUpProps extends ArtistArtSizeProps { + coverArtUris: string[]; +} + +interface ArtistArtProps extends ArtistArtSizeProps { + id: string; +} + +const PlaceholderContainer: React.FC = ({ height, width, children }) => ( ); -const FourUp: React.FC<{ - height: number, - width: number, - coverArtUris: string[]; -}> = ({ height, width, coverArtUris }) => { +const FourUp: React.FC = ({ height, width, coverArtUris }) => { const halfHeight = height / 2; const halfWidth = width / 2; @@ -59,11 +68,7 @@ const FourUp: React.FC<{ ); }; -const ThreeUp: React.FC<{ - height: number, - width: number, - coverArtUris: string[]; -}> = ({ height, width, coverArtUris }) => { +const ThreeUp: React.FC = ({ height, width, coverArtUris }) => { const halfHeight = height / 2; const halfWidth = width / 2; @@ -92,11 +97,7 @@ const ThreeUp: React.FC<{ ); }; -const TwoUp: React.FC<{ - height: number, - width: number, - coverArtUris: string[]; -}> = ({ height, width, coverArtUris }) => { +const TwoUp: React.FC = ({ height, width, coverArtUris }) => { const halfHeight = height / 2; return ( @@ -119,11 +120,7 @@ const TwoUp: React.FC<{ ); }; -const OneUp: React.FC<{ - height: number, - width: number, - coverArtUris: string[]; -}> = ({ height, width, coverArtUris }) => { +const OneUp: React.FC = ({ height, width, coverArtUris }) => { return ( = ({ height, width }) => { +const NoneUp: React.FC = ({ height, width }) => { return ( = ({ height, width, mediumImageUrl, coverArtUris }) => { +const ArtistArt: React.FC = ({ id, height, width }) => { + const artistArt = useAtomValue(artistArtAtomFamily(id)); + const Placeholder = () => { - if (coverArtUris && coverArtUris.length >= 4) { + const none = ; + + if (!artistArt || !artistArt.coverArtUris) { + return none; + } + const { coverArtUris } = artistArt; + + if (coverArtUris.length >= 4) { return ; } - if (coverArtUris && coverArtUris.length === 3) { + if (coverArtUris.length === 3) { return ; } - if (coverArtUris && coverArtUris.length === 2) { + if (coverArtUris.length === 2) { return ; } - if (coverArtUris && coverArtUris.length === 1) { + if (coverArtUris.length === 1) { return ; } - return ; + + return none; } return ( @@ -184,10 +183,26 @@ const ArtistArt: React.FC<{ PlaceholderComponent={Placeholder} height={height} width={width} - coverArtUri={mediumImageUrl} + coverArtUri={artistArt?.uri} /> ); } -export default React.memo(ArtistArt); +const ArtistArtFallback: React.FC = ({ height, width }) => ( + + + +); + +const ArtistArtLoader: React.FC = (props) => ( + }> + + +); + +export default React.memo(ArtistArtLoader); diff --git a/src/components/common/ArtistView.tsx b/src/components/common/ArtistView.tsx index 6ce2de5..1b91613 100644 --- a/src/components/common/ArtistView.tsx +++ b/src/components/common/ArtistView.tsx @@ -25,12 +25,7 @@ const ArtistDetails: React.FC<{ id: string }> = ({ id }) => { }} > {artist.name} - + ) } diff --git a/src/components/library/AlbumsTab.tsx b/src/components/library/AlbumsTab.tsx index 1edf13b..92fcd4c 100644 --- a/src/components/library/AlbumsTab.tsx +++ b/src/components/library/AlbumsTab.tsx @@ -10,10 +10,9 @@ import GradientFlatList from '../common/GradientFlatList'; const AlbumItem: React.FC<{ id: string; - name: string, - artist?: string, - coverArtUri?: string -} > = ({ id, name, artist, coverArtUri }) => { + name: string; + artist?: string; +}> = ({ id, name, artist, }) => { const navigation = useNavigation(); const size = 125; @@ -27,11 +26,7 @@ const AlbumItem: React.FC<{ }} onPress={() => navigation.navigate('AlbumView', { id, title: name })} > - + = ({ item }) => ( - + ); const AlbumsList = () => { diff --git a/src/components/library/ArtistsTab.tsx b/src/components/library/ArtistsTab.tsx index d71f5a0..4748c85 100644 --- a/src/components/library/ArtistsTab.tsx +++ b/src/components/library/ArtistsTab.tsx @@ -11,7 +11,6 @@ import GradientFlatList from '../common/GradientFlatList'; const ArtistItem: React.FC<{ item: Artist }> = ({ item }) => { const navigation = useNavigation(); - const artistInfo = useAtomValue(artistInfoAtomFamily(item.id)); return ( = ({ item }) => { }} onPress={() => navigation.navigate('ArtistView', { id: item.id, title: item.name })} > - - {/* */} + { const tracks1 = tracks.slice(0, playIndex); const tracks2 = tracks.slice(playIndex); - console.log('tracks1: ' + JSON.stringify(tracks1.map(t => t.title))); - console.log('tracks2: ' + JSON.stringify(tracks2.map(t => t.title))); - await TrackPlayer.add(tracks2); await TrackPlayer.play(); await TrackPlayer.add(tracks1, playId); const queue = await TrackPlayer.getQueue(); - console.log('queue: ' + JSON.stringify(queue.map(t => t.title))); } } } diff --git a/src/models/music.ts b/src/models/music.ts index f846f9a..c3d1b57 100644 --- a/src/models/music.ts +++ b/src/models/music.ts @@ -12,6 +12,11 @@ export interface ArtistInfo extends Artist { coverArtUris: string[]; } +export interface ArtistArt { + uri?: string; + coverArtUris: string[]; +} + export interface Album { id: string; artistId?: string; @@ -24,6 +29,11 @@ export interface Album { year?: number; } +export interface AlbumArt { + uri?: string; + thumbUri?: string; +} + export interface AlbumWithSongs extends Album { songs: Song[]; } diff --git a/src/state/music.ts b/src/state/music.ts index d3a2f82..13cf431 100644 --- a/src/state/music.ts +++ b/src/state/music.ts @@ -1,6 +1,6 @@ import { atom, useAtom } from 'jotai'; import { atomFamily, useAtomValue, useUpdateAtom } from 'jotai/utils'; -import { Album as Album, AlbumWithSongs, Artist, ArtistInfo, Song } from '../models/music'; +import { Album, AlbumArt, AlbumWithSongs, Artist, ArtistArt, ArtistInfo, Song } from '../models/music'; import { SubsonicApiClient } from '../subsonic/api'; import { AlbumID3Element, ArtistID3Element, ArtistInfo2Element, ChildElement } from '../subsonic/elements'; import { GetArtistResponse } from '../subsonic/responses'; @@ -73,6 +73,26 @@ export const albumAtomFamily = atomFamily((id: string) => atom atom(async (get) => { + const server = get(activeServerAtom); + if (!server) { + return undefined; + } + + const albums = get(albumsAtom); + const album = albums.find(a => a.id === id); + if (!album) { + return undefined; + } + + const client = new SubsonicApiClient(server); + + return { + uri: album.coverArt ? client.getCoverArtUri({ id: album.coverArt }) : undefined, + thumbUri: album.coverArt ? client.getCoverArtUri({ id: album.coverArt, size: '256' }) : undefined, + }; +})); + export const artistInfoAtomFamily = atomFamily((id: string) => atom(async (get) => { const server = get(activeServerAtom); if (!server) { @@ -87,6 +107,29 @@ export const artistInfoAtomFamily = atomFamily((id: string) => atom atom(async (get) => { + const artistInfo = get(artistInfoAtomFamily(id)); + if (!artistInfo) { + return undefined; + } + + const coverArtUris = artistInfo.albums + .filter(a => a.coverArtThumbUri !== undefined) + .sort((a, b) => { + if (b.year && a.year) { + return b.year - a.year; + } else { + return a.name.localeCompare(b.name) - 9000; + } + }) + .map(a => a.coverArtThumbUri) as string[]; + + return { + coverArtUris, + uri: artistInfo.mediumImageUrl, + }; +})); + function mapArtistInfo( artistResponse: GetArtistResponse, artistInfo: ArtistInfo2Element, diff --git a/src/subsonic/api.ts b/src/subsonic/api.ts index d5a7967..91adc4d 100644 --- a/src/subsonic/api.ts +++ b/src/subsonic/api.ts @@ -100,7 +100,7 @@ export class SubsonicApiClient { const response = await fetch(this.buildUrl(method, params)); const text = await response.text(); - console.log(text); + // console.log(text); const xml = new DOMParser().parseFromString(text); if (xml.documentElement.getAttribute('status') !== 'ok') {