mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 01:19:28 +01:00
added top songs to artist view
This commit is contained in:
parent
62a721ba4d
commit
de342c0830
@ -14,7 +14,7 @@ interface ArtistArtSizeProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ArtistArtXUpProps extends ArtistArtSizeProps {
|
interface ArtistArtXUpProps extends ArtistArtSizeProps {
|
||||||
coverArtUris: string[]
|
albumCoverUris: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArtistArtProps extends ArtistArtSizeProps {
|
interface ArtistArtProps extends ArtistArtSizeProps {
|
||||||
@ -39,7 +39,7 @@ const PlaceholderContainer: React.FC<ArtistArtSizeProps> = ({ height, width, chi
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const FourUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) => {
|
const FourUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => {
|
||||||
const halfHeight = height / 2
|
const halfHeight = height / 2
|
||||||
const halfWidth = width / 2
|
const halfWidth = width / 2
|
||||||
|
|
||||||
@ -47,24 +47,24 @@ const FourUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) =
|
|||||||
<PlaceholderContainer height={height} width={width}>
|
<PlaceholderContainer height={height} width={width}>
|
||||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[0] }}
|
source={{ uri: albumCoverUris[0] }}
|
||||||
style={{ height: halfHeight, width: halfWidth }}
|
style={{ height: halfHeight, width: halfWidth }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[1] }}
|
source={{ uri: albumCoverUris[1] }}
|
||||||
style={{ height: halfHeight, width: halfWidth }}
|
style={{ height: halfHeight, width: halfWidth }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[2] }}
|
source={{ uri: albumCoverUris[2] }}
|
||||||
style={{ height: halfHeight, width: halfWidth }}
|
style={{ height: halfHeight, width: halfWidth }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[3] }}
|
source={{ uri: albumCoverUris[3] }}
|
||||||
style={{ height: halfHeight, width: halfWidth }}
|
style={{ height: halfHeight, width: halfWidth }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
@ -73,7 +73,7 @@ const FourUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) =
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const ThreeUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) => {
|
const ThreeUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => {
|
||||||
const halfHeight = height / 2
|
const halfHeight = height / 2
|
||||||
const halfWidth = width / 2
|
const halfWidth = width / 2
|
||||||
|
|
||||||
@ -81,19 +81,19 @@ const ThreeUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris })
|
|||||||
<PlaceholderContainer height={height} width={width}>
|
<PlaceholderContainer height={height} width={width}>
|
||||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[0] }}
|
source={{ uri: albumCoverUris[0] }}
|
||||||
style={{ height: halfHeight, width }}
|
style={{ height: halfHeight, width }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[1] }}
|
source={{ uri: albumCoverUris[1] }}
|
||||||
style={{ height: halfHeight, width: halfWidth }}
|
style={{ height: halfHeight, width: halfWidth }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[2] }}
|
source={{ uri: albumCoverUris[2] }}
|
||||||
style={{ height: halfHeight, width: halfWidth }}
|
style={{ height: halfHeight, width: halfWidth }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
@ -102,21 +102,21 @@ const ThreeUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris })
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const TwoUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) => {
|
const TwoUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => {
|
||||||
const halfHeight = height / 2
|
const halfHeight = height / 2
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlaceholderContainer height={height} width={width}>
|
<PlaceholderContainer height={height} width={width}>
|
||||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[0] }}
|
source={{ uri: albumCoverUris[0] }}
|
||||||
style={{ height: halfHeight, width }}
|
style={{ height: halfHeight, width }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{ uri: coverArtUris[1] }}
|
source={{ uri: albumCoverUris[1] }}
|
||||||
style={{ height: halfHeight, width }}
|
style={{ height: halfHeight, width }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
/>
|
/>
|
||||||
@ -125,9 +125,9 @@ const TwoUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) =>
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const OneUp = React.memo<ArtistArtXUpProps>(({ height, width, coverArtUris }) => (
|
const OneUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => (
|
||||||
<PlaceholderContainer height={height} width={width}>
|
<PlaceholderContainer height={height} width={width}>
|
||||||
<FastImage source={{ uri: coverArtUris[0] }} style={{ height, width }} resizeMode={FastImage.resizeMode.cover} />
|
<FastImage source={{ uri: albumCoverUris[0] }} style={{ height, width }} resizeMode={FastImage.resizeMode.cover} />
|
||||||
</PlaceholderContainer>
|
</PlaceholderContainer>
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -141,22 +141,22 @@ const ArtistArt = React.memo<ArtistArtProps>(({ id, height, width }) => {
|
|||||||
const Placeholder = () => {
|
const Placeholder = () => {
|
||||||
const none = <NoneUp height={height} width={width} />
|
const none = <NoneUp height={height} width={width} />
|
||||||
|
|
||||||
if (!artistArt || !artistArt.coverArtUris) {
|
if (!artistArt || !artistArt.albumCoverUris) {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
const { coverArtUris } = artistArt
|
const { albumCoverUris } = artistArt
|
||||||
|
|
||||||
if (coverArtUris.length >= 4) {
|
if (albumCoverUris.length >= 4) {
|
||||||
return <FourUp height={height} width={width} coverArtUris={coverArtUris} />
|
return <FourUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||||
}
|
}
|
||||||
if (coverArtUris.length === 3) {
|
if (albumCoverUris.length === 3) {
|
||||||
return <ThreeUp height={height} width={width} coverArtUris={coverArtUris} />
|
return <ThreeUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||||
}
|
}
|
||||||
if (coverArtUris.length === 2) {
|
if (albumCoverUris.length === 2) {
|
||||||
return <TwoUp height={height} width={width} coverArtUris={coverArtUris} />
|
return <TwoUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||||
}
|
}
|
||||||
if (coverArtUris.length === 1) {
|
if (albumCoverUris.length === 1) {
|
||||||
return <OneUp height={height} width={width} coverArtUris={coverArtUris} />
|
return <OneUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return none
|
return none
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { ActivityIndicator, LayoutChangeEvent, StyleSheet, View } from 'react-native'
|
import { ActivityIndicator, LayoutChangeEvent, StyleSheet, View, ViewStyle } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
||||||
@ -12,7 +12,8 @@ const CoverArt: React.FC<{
|
|||||||
width?: string | number
|
width?: string | number
|
||||||
coverArtUri?: string
|
coverArtUri?: string
|
||||||
resizeMode?: keyof typeof FastImage.resizeMode
|
resizeMode?: keyof typeof FastImage.resizeMode
|
||||||
}> = ({ PlaceholderComponent, placeholderIcon, height, width, coverArtUri, resizeMode }) => {
|
style?: ViewStyle
|
||||||
|
}> = ({ PlaceholderComponent, placeholderIcon, height, width, coverArtUri, resizeMode, style }) => {
|
||||||
const [placeholderVisible, setPlaceholderVisible] = useState(false)
|
const [placeholderVisible, setPlaceholderVisible] = useState(false)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [layout, setLayout] = useState({ x: 0, y: 0, width: 0, height: 0 })
|
const [layout, setLayout] = useState({ x: 0, y: 0, width: 0, height: 0 })
|
||||||
@ -47,7 +48,7 @@ const CoverArt: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ ...styles.container, height, width }} onLayout={onLayout}>
|
<View style={[style, { height, width }]} onLayout={onLayout}>
|
||||||
{coverArtUri ? <Image /> : <></>}
|
{coverArtUri ? <Image /> : <></>}
|
||||||
<View style={{ ...styles.placeholderContainer, opacity: placeholderVisible ? 1 : 0 }}>
|
<View style={{ ...styles.placeholderContainer, opacity: placeholderVisible ? 1 : 0 }}>
|
||||||
{PlaceholderComponent ? <PlaceholderComponent /> : <Placeholder />}
|
{PlaceholderComponent ? <PlaceholderComponent /> : <Placeholder />}
|
||||||
|
|||||||
84
app/components/SongItem.tsx
Normal file
84
app/components/SongItem.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Song } from '@app/models/music'
|
||||||
|
import { currentTrackAtom } from '@app/state/trackplayer'
|
||||||
|
import colors from '@app/styles/colors'
|
||||||
|
import font from '@app/styles/font'
|
||||||
|
import { useAtomValue } from 'jotai/utils'
|
||||||
|
import React from 'react'
|
||||||
|
import { GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import IconFA from 'react-native-vector-icons/FontAwesome'
|
||||||
|
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
|
import CoverArt from './CoverArt'
|
||||||
|
import PressableOpacity from './PressableOpacity'
|
||||||
|
|
||||||
|
const SongItem: React.FC<{
|
||||||
|
song: Song
|
||||||
|
onPress?: (event: GestureResponderEvent) => void
|
||||||
|
showArt?: boolean
|
||||||
|
subtitle?: 'artist' | 'album'
|
||||||
|
}> = ({ song, onPress, showArt, subtitle }) => {
|
||||||
|
const currentTrack = useAtomValue(currentTrackAtom)
|
||||||
|
|
||||||
|
subtitle = subtitle || 'artist'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<PressableOpacity onPress={onPress} style={styles.item}>
|
||||||
|
{showArt ? <CoverArt coverArtUri={song.coverArtThumbUri} style={styles.art} height={50} width={50} /> : <></>}
|
||||||
|
<View style={styles.text}>
|
||||||
|
<Text style={[styles.title, { color: currentTrack?.id === song.id ? colors.accent : colors.text.primary }]}>
|
||||||
|
{song.title}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.subtitle}>{song[subtitle]}</Text>
|
||||||
|
</View>
|
||||||
|
</PressableOpacity>
|
||||||
|
<View style={styles.controls}>
|
||||||
|
<PressableOpacity onPress={undefined}>
|
||||||
|
<IconFA name="star-o" size={26} color={colors.text.primary} />
|
||||||
|
</PressableOpacity>
|
||||||
|
<PressableOpacity onPress={undefined} style={styles.more}>
|
||||||
|
<IconMat name="more-vert" size={32} color="white" />
|
||||||
|
</PressableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
marginBottom: 14,
|
||||||
|
minHeight: 50,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
},
|
||||||
|
art: {
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontFamily: font.semiBold,
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: font.regular,
|
||||||
|
color: colors.text.secondary,
|
||||||
|
},
|
||||||
|
controls: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 10,
|
||||||
|
},
|
||||||
|
more: {
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default React.memo(SongItem)
|
||||||
@ -9,12 +9,14 @@ export interface ArtistInfo extends Artist {
|
|||||||
|
|
||||||
mediumImageUrl?: string
|
mediumImageUrl?: string
|
||||||
largeImageUrl?: string
|
largeImageUrl?: string
|
||||||
coverArtUris: string[]
|
albumCoverUris: string[]
|
||||||
|
|
||||||
|
topSongs: Song[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArtistArt {
|
export interface ArtistArt {
|
||||||
uri?: string
|
uri?: string
|
||||||
coverArtUris: string[]
|
albumCoverUris: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AlbumListItem {
|
export interface AlbumListItem {
|
||||||
|
|||||||
@ -1,78 +1,16 @@
|
|||||||
|
import Button from '@app/components/Button'
|
||||||
|
import CoverArt from '@app/components/CoverArt'
|
||||||
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
|
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
||||||
|
import SongItem from '@app/components/SongItem'
|
||||||
|
import { albumAtomFamily } from '@app/state/music'
|
||||||
|
import { useSetQueue } from '@app/state/trackplayer'
|
||||||
|
import colors from '@app/styles/colors'
|
||||||
|
import font from '@app/styles/font'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { ActivityIndicator, GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
|
||||||
import IconFA from 'react-native-vector-icons/FontAwesome'
|
|
||||||
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
|
||||||
import { albumAtomFamily } from '@app/state/music'
|
|
||||||
import { currentTrackAtom, useSetQueue } from '@app/state/trackplayer'
|
|
||||||
import colors from '@app/styles/colors'
|
|
||||||
import font from '@app/styles/font'
|
|
||||||
import Button from '@app/components/Button'
|
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
|
||||||
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
|
||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
|
||||||
import CoverArt from '@app/components/CoverArt'
|
|
||||||
|
|
||||||
const SongItem: React.FC<{
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
artist?: string
|
|
||||||
track?: number
|
|
||||||
onPress: (event: GestureResponderEvent) => void
|
|
||||||
}> = ({ id, title, artist, onPress }) => {
|
|
||||||
const currentTrack = useAtomValue(currentTrackAtom)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={songStyles.container}>
|
|
||||||
<PressableOpacity onPress={onPress} style={songStyles.text}>
|
|
||||||
<Text style={{ ...songStyles.title, color: currentTrack?.id === id ? colors.accent : colors.text.primary }}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Text style={songStyles.subtitle}>{artist}</Text>
|
|
||||||
</PressableOpacity>
|
|
||||||
<View style={songStyles.controls}>
|
|
||||||
<PressableOpacity onPress={undefined}>
|
|
||||||
<IconFA name="star-o" size={26} color={colors.text.primary} />
|
|
||||||
</PressableOpacity>
|
|
||||||
<PressableOpacity onPress={undefined} style={songStyles.more}>
|
|
||||||
<IconMat name="more-vert" size={32} color="white" />
|
|
||||||
</PressableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const songStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
marginTop: 20,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontFamily: font.semiBold,
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
fontSize: 14,
|
|
||||||
fontFamily: font.regular,
|
|
||||||
color: colors.text.secondary,
|
|
||||||
},
|
|
||||||
controls: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginLeft: 10,
|
|
||||||
},
|
|
||||||
more: {
|
|
||||||
marginLeft: 8,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const AlbumDetails: React.FC<{
|
const AlbumDetails: React.FC<{
|
||||||
id: string
|
id: string
|
||||||
@ -111,14 +49,7 @@ const AlbumDetails: React.FC<{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(s => (
|
.map(s => (
|
||||||
<SongItem
|
<SongItem key={s.id} song={s} onPress={() => setQueue(album.songs, album.name, s.id)} />
|
||||||
key={s.id}
|
|
||||||
id={s.id}
|
|
||||||
title={s.title}
|
|
||||||
artist={s.artist}
|
|
||||||
track={s.track}
|
|
||||||
onPress={() => setQueue(album.songs, album.name, s.id)}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -181,7 +112,7 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
songs: {
|
songs: {
|
||||||
marginTop: 10,
|
marginTop: 26,
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientScrollView from '@app/components/GradientScrollView'
|
import GradientScrollView from '@app/components/GradientScrollView'
|
||||||
import PressableOpacity from '@app/components/PressableOpacity'
|
import PressableOpacity from '@app/components/PressableOpacity'
|
||||||
|
import SongItem from '@app/components/SongItem'
|
||||||
import { Album } from '@app/models/music'
|
import { Album } from '@app/models/music'
|
||||||
import { artistInfoAtomFamily } from '@app/state/music'
|
import { artistInfoAtomFamily } from '@app/state/music'
|
||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
@ -22,7 +23,6 @@ const AlbumItem = React.memo<{
|
|||||||
return (
|
return (
|
||||||
<PressableOpacity
|
<PressableOpacity
|
||||||
onPress={() => navigation.navigate('AlbumView', { id: album.id, title: album.name })}
|
onPress={() => navigation.navigate('AlbumView', { id: album.id, title: album.name })}
|
||||||
key={album.id}
|
|
||||||
style={[styles.albumItem, { width }]}>
|
style={[styles.albumItem, { width }]}>
|
||||||
<CoverArt coverArtUri={album.coverArtThumbUri} height={height} width={width} />
|
<CoverArt coverArtUri={album.coverArtThumbUri} height={height} width={width} />
|
||||||
<Text style={styles.albumTitle}>{album.name}</Text>
|
<Text style={styles.albumTitle}>{album.name}</Text>
|
||||||
@ -52,6 +52,10 @@ const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
|
|||||||
<Text style={styles.title}>{artist.name}</Text>
|
<Text style={styles.title}>{artist.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.header}>Top Songs</Text>
|
||||||
|
{artist.topSongs.map(s => (
|
||||||
|
<SongItem key={s.id} song={s} showArt={true} subtitle="album" />
|
||||||
|
))}
|
||||||
<Text style={styles.header}>Albums</Text>
|
<Text style={styles.header}>Albums</Text>
|
||||||
<View style={styles.albums} onLayout={layout.onLayout}>
|
<View style={styles.albums} onLayout={layout.onLayout}>
|
||||||
{artist.albums.map(a => (
|
{artist.albums.map(a => (
|
||||||
@ -112,7 +116,8 @@ const styles = StyleSheet.create({
|
|||||||
fontFamily: font.bold,
|
fontFamily: font.bold,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
color: colors.text.primary,
|
color: colors.text.primary,
|
||||||
marginTop: 14,
|
marginTop: 20,
|
||||||
|
marginBottom: 14,
|
||||||
},
|
},
|
||||||
artistImage: {
|
artistImage: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -120,7 +125,6 @@ const styles = StyleSheet.create({
|
|||||||
height: artistImageHeight,
|
height: artistImageHeight,
|
||||||
},
|
},
|
||||||
albums: {
|
albums: {
|
||||||
marginTop: 14,
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
|||||||
@ -131,7 +131,9 @@ export const artistInfoAtomFamily = atomFamily((id: string) =>
|
|||||||
client.getArtist({ id }),
|
client.getArtist({ id }),
|
||||||
client.getArtistInfo2({ id }),
|
client.getArtistInfo2({ id }),
|
||||||
])
|
])
|
||||||
return mapArtistInfo(artistResponse.data, artistInfoResponse.data.artistInfo, client)
|
const topSongsResponse = await client.getTopSongs({ artist: artistResponse.data.artist.name, count: 50 })
|
||||||
|
|
||||||
|
return mapArtistInfo(artistResponse.data, artistInfoResponse.data.artistInfo, topSongsResponse.data.songs, client)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -142,7 +144,7 @@ export const artistArtAtomFamily = atomFamily((id: string) =>
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const coverArtUris = artistInfo.albums
|
const albumCoverUris = artistInfo.albums
|
||||||
.filter(a => a.coverArtThumbUri !== undefined)
|
.filter(a => a.coverArtThumbUri !== undefined)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (b.year && a.year) {
|
if (b.year && a.year) {
|
||||||
@ -154,7 +156,7 @@ export const artistArtAtomFamily = atomFamily((id: string) =>
|
|||||||
.map(a => a.coverArtThumbUri) as string[]
|
.map(a => a.coverArtThumbUri) as string[]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
coverArtUris,
|
albumCoverUris,
|
||||||
uri: artistInfo.largeImageUrl,
|
uri: artistInfo.largeImageUrl,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -171,12 +173,13 @@ function mapArtistID3toArtist(artist: ArtistID3Element): Artist {
|
|||||||
function mapArtistInfo(
|
function mapArtistInfo(
|
||||||
artistResponse: GetArtistResponse,
|
artistResponse: GetArtistResponse,
|
||||||
info: ArtistInfo2Element,
|
info: ArtistInfo2Element,
|
||||||
|
topSongs: ChildElement[],
|
||||||
client: SubsonicApiClient,
|
client: SubsonicApiClient,
|
||||||
): ArtistInfo {
|
): ArtistInfo {
|
||||||
const { artist, albums } = artistResponse
|
const { artist, albums } = artistResponse
|
||||||
|
|
||||||
const mappedAlbums = albums.map(a => mapAlbumID3toAlbum(a, client))
|
const mappedAlbums = albums.map(a => mapAlbumID3toAlbum(a, client))
|
||||||
const coverArtUris = mappedAlbums
|
const albumCoverUris = mappedAlbums
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a.year && b.year) {
|
if (a.year && b.year) {
|
||||||
return b.year - a.year
|
return b.year - a.year
|
||||||
@ -190,9 +193,10 @@ function mapArtistInfo(
|
|||||||
return {
|
return {
|
||||||
...mapArtistID3toArtist(artist),
|
...mapArtistID3toArtist(artist),
|
||||||
albums: mappedAlbums,
|
albums: mappedAlbums,
|
||||||
coverArtUris,
|
albumCoverUris,
|
||||||
mediumImageUrl: info.mediumImageUrl,
|
mediumImageUrl: info.mediumImageUrl,
|
||||||
largeImageUrl: info.largeImageUrl,
|
largeImageUrl: info.largeImageUrl,
|
||||||
|
topSongs: topSongs.map(c => mapChildToSong(c, client)).slice(0, 5),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
GetCoverArtParams,
|
GetCoverArtParams,
|
||||||
GetIndexesParams,
|
GetIndexesParams,
|
||||||
GetMusicDirectoryParams,
|
GetMusicDirectoryParams,
|
||||||
|
GetTopSongsParams,
|
||||||
StreamParams,
|
StreamParams,
|
||||||
} from '@app/subsonic/params'
|
} from '@app/subsonic/params'
|
||||||
import {
|
import {
|
||||||
@ -22,6 +23,7 @@ import {
|
|||||||
GetArtistsResponse,
|
GetArtistsResponse,
|
||||||
GetIndexesResponse,
|
GetIndexesResponse,
|
||||||
GetMusicDirectoryResponse,
|
GetMusicDirectoryResponse,
|
||||||
|
GetTopSongsResponse,
|
||||||
SubsonicResponse,
|
SubsonicResponse,
|
||||||
} from '@app/subsonic/responses'
|
} from '@app/subsonic/responses'
|
||||||
import { Server } from '@app/models/settings'
|
import { Server } from '@app/models/settings'
|
||||||
@ -165,6 +167,11 @@ export class SubsonicApiClient {
|
|||||||
return new SubsonicResponse<GetArtistResponse>(xml, new GetArtistResponse(xml))
|
return new SubsonicResponse<GetArtistResponse>(xml, new GetArtistResponse(xml))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTopSongs(params: GetTopSongsParams): Promise<SubsonicResponse<GetTopSongsResponse>> {
|
||||||
|
const xml = await this.apiGetXml('getTopSongs', params)
|
||||||
|
return new SubsonicResponse<GetTopSongsResponse>(xml, new GetTopSongsResponse(xml))
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Album/song lists
|
// Album/song lists
|
||||||
//
|
//
|
||||||
|
|||||||
@ -27,6 +27,11 @@ export type GetArtistParams = {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GetTopSongsParams = {
|
||||||
|
artist: string
|
||||||
|
count?: number
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Album/song lists
|
// Album/song lists
|
||||||
//
|
//
|
||||||
|
|||||||
@ -116,6 +116,17 @@ export class GetAlbumResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GetTopSongsResponse {
|
||||||
|
songs: ChildElement[] = []
|
||||||
|
|
||||||
|
constructor(xml: Document) {
|
||||||
|
const childElements = xml.getElementsByTagName('song')
|
||||||
|
for (let i = 0; i < childElements.length; i++) {
|
||||||
|
this.songs.push(new ChildElement(childElements[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Album/song lists
|
// Album/song lists
|
||||||
//
|
//
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user