mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 06:52:43 +01:00
redid cover art (again...) and impl a ListItem
This commit is contained in:
@@ -1,207 +0,0 @@
|
||||
import CoverArt from '@app/components/CoverArt'
|
||||
import { artistArtAtomFamily } from '@app/state/music'
|
||||
import colors from '@app/styles/colors'
|
||||
import { useLayout } from '@react-native-community/hooks'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import React from 'react'
|
||||
import { ActivityIndicator, StyleSheet, View } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
||||
|
||||
interface ArtistArtSizeProps {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
|
||||
interface ArtistArtXUpProps extends ArtistArtSizeProps {
|
||||
albumCoverUris: string[]
|
||||
}
|
||||
|
||||
interface ArtistArtProps extends ArtistArtSizeProps {
|
||||
id: string
|
||||
round?: boolean
|
||||
}
|
||||
|
||||
const PlaceholderContainer: React.FC<ArtistArtSizeProps> = ({ height, width, children }) => {
|
||||
const layout = useLayout()
|
||||
|
||||
return (
|
||||
<LinearGradient
|
||||
onLayout={layout.onLayout}
|
||||
colors={[colors.accent, colors.accentLow]}
|
||||
style={[styles.placeholderContainer, { height, width }]}>
|
||||
<IconFA5 name="microphone" color="black" size={layout.width / 1.8} style={styles.placeholderIcon} />
|
||||
{children}
|
||||
</LinearGradient>
|
||||
)
|
||||
}
|
||||
|
||||
const FourUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => {
|
||||
const halfHeight = height / 2
|
||||
const halfWidth = width / 2
|
||||
|
||||
return (
|
||||
<PlaceholderContainer height={height} width={width}>
|
||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[0] }}
|
||||
style={{ height: halfHeight, width: halfWidth }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[1] }}
|
||||
style={{ height: halfHeight, width: halfWidth }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[2] }}
|
||||
style={{ height: halfHeight, width: halfWidth }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[3] }}
|
||||
style={{ height: halfHeight, width: halfWidth }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
</PlaceholderContainer>
|
||||
)
|
||||
})
|
||||
|
||||
const ThreeUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => {
|
||||
const halfHeight = height / 2
|
||||
const halfWidth = width / 2
|
||||
|
||||
return (
|
||||
<PlaceholderContainer height={height} width={width}>
|
||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[0] }}
|
||||
style={{ height: halfHeight, width }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[1] }}
|
||||
style={{ height: halfHeight, width: halfWidth }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[2] }}
|
||||
style={{ height: halfHeight, width: halfWidth }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
</PlaceholderContainer>
|
||||
)
|
||||
})
|
||||
|
||||
const TwoUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => {
|
||||
const halfHeight = height / 2
|
||||
|
||||
return (
|
||||
<PlaceholderContainer height={height} width={width}>
|
||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[0] }}
|
||||
style={{ height: halfHeight, width }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.artRow, { width, height: halfHeight }]}>
|
||||
<FastImage
|
||||
source={{ uri: albumCoverUris[1] }}
|
||||
style={{ height: halfHeight, width }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
</PlaceholderContainer>
|
||||
)
|
||||
})
|
||||
|
||||
const OneUp = React.memo<ArtistArtXUpProps>(({ height, width, albumCoverUris }) => (
|
||||
<PlaceholderContainer height={height} width={width}>
|
||||
<FastImage source={{ uri: albumCoverUris[0] }} style={{ height, width }} resizeMode={FastImage.resizeMode.cover} />
|
||||
</PlaceholderContainer>
|
||||
))
|
||||
|
||||
const NoneUp = React.memo<ArtistArtSizeProps>(({ height, width }) => (
|
||||
<PlaceholderContainer height={height} width={width} />
|
||||
))
|
||||
|
||||
const ArtistArt = React.memo<ArtistArtProps>(({ id, height, width, round }) => {
|
||||
const artistArt = useAtomValue(artistArtAtomFamily(id))
|
||||
|
||||
round = round === undefined ? true : round
|
||||
|
||||
const Placeholder = () => {
|
||||
if (!artistArt) {
|
||||
return <NoneUp height={height} width={width} />
|
||||
}
|
||||
const { albumCoverUris } = artistArt
|
||||
|
||||
if (albumCoverUris.length >= 4) {
|
||||
return <FourUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||
}
|
||||
if (albumCoverUris.length === 3) {
|
||||
return <ThreeUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||
}
|
||||
if (albumCoverUris.length === 2) {
|
||||
return <TwoUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||
}
|
||||
if (albumCoverUris.length === 1) {
|
||||
return <OneUp height={height} width={width} albumCoverUris={albumCoverUris} />
|
||||
}
|
||||
|
||||
return <NoneUp height={height} width={width} />
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, round ? { borderRadius: height / 2 } : {}]}>
|
||||
<CoverArt
|
||||
FallbackComponent={Placeholder}
|
||||
style={{ height, width }}
|
||||
coverArtUri={artistArt?.uri}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const ArtistArtFallback = React.memo<ArtistArtProps>(({ height, width }) => (
|
||||
<View style={[styles.fallback, { height, width }]}>
|
||||
<ActivityIndicator size="large" color={colors.accent} />
|
||||
</View>
|
||||
))
|
||||
|
||||
const ArtistArtLoader: React.FC<ArtistArtProps> = props => (
|
||||
<React.Suspense fallback={<ArtistArtFallback {...props} />}>
|
||||
<ArtistArt {...props} />
|
||||
</React.Suspense>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
placeholderContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
placeholderIcon: {
|
||||
position: 'absolute',
|
||||
},
|
||||
artRow: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
container: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
fallback: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
export default React.memo(ArtistArtLoader)
|
||||
@@ -1,69 +1,124 @@
|
||||
import { artistInfoAtomFamily, useCoverArtUri } from '@app/state/music'
|
||||
import colors from '@app/styles/colors'
|
||||
import React, { useState } from 'react'
|
||||
import { ActivityIndicator, StyleSheet, View } from 'react-native'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ActivityIndicator, StyleSheet, View, ViewStyle } from 'react-native'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
|
||||
type CoverImageProps = {
|
||||
uri?: string
|
||||
style?: ImageStyle
|
||||
type BaseProps = {
|
||||
imageSize?: 'thumbnail' | 'original'
|
||||
style?: ViewStyle
|
||||
imageStyle?: ImageStyle
|
||||
resizeMode?: keyof typeof FastImage.resizeMode
|
||||
onProgress?: () => void
|
||||
onLoadEnd?: () => void
|
||||
onError?: () => void
|
||||
round?: boolean
|
||||
}
|
||||
|
||||
const CoverImage = React.memo<CoverImageProps>(({ uri, style, resizeMode, onProgress, onLoadEnd, onError }) => (
|
||||
<FastImage
|
||||
source={{ uri }}
|
||||
style={style}
|
||||
resizeMode={resizeMode || FastImage.resizeMode.contain}
|
||||
onProgress={onProgress}
|
||||
onLoadEnd={onLoadEnd}
|
||||
onError={onError}
|
||||
/>
|
||||
))
|
||||
type BaseImageProps = BaseProps & {
|
||||
enableLoading: () => void
|
||||
disableLoading: () => void
|
||||
}
|
||||
|
||||
const Fallback = React.memo<{}>(({}) => {
|
||||
return <LinearGradient colors={[colors.accent, colors.accentLow]} style={styles.fallback} />
|
||||
type ArtistIdProp = {
|
||||
artistId: string
|
||||
}
|
||||
|
||||
type CoverArtProp = {
|
||||
coverArt?: string
|
||||
}
|
||||
|
||||
type ArtistIdImageProps = BaseImageProps & ArtistIdProp
|
||||
type CoverArtImageProps = BaseImageProps & CoverArtProp
|
||||
|
||||
type CoverArtProps = BaseProps & CoverArtProp & Partial<ArtistIdProp>
|
||||
|
||||
const ArtistIdImageLoaded = React.memo<ArtistIdImageProps>(
|
||||
({ artistId, imageSize, style, imageStyle, resizeMode, enableLoading, disableLoading }) => {
|
||||
const artistInfo = useAtomValue(artistInfoAtomFamily(artistId))
|
||||
|
||||
const uri = imageSize === 'thumbnail' ? artistInfo?.smallImageUrl : artistInfo?.largeImageUrl
|
||||
|
||||
return (
|
||||
<FastImage
|
||||
source={{ uri }}
|
||||
style={[{ height: style?.height, width: style?.width }, imageStyle]}
|
||||
resizeMode={resizeMode || FastImage.resizeMode.contain}
|
||||
onProgress={enableLoading}
|
||||
onLoadEnd={disableLoading}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const ArtistIdImageFallback: React.FC<{
|
||||
enableLoading: () => void
|
||||
}> = ({ enableLoading }) => {
|
||||
useEffect(() => {
|
||||
enableLoading()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
return <></>
|
||||
}
|
||||
|
||||
const ArtistIdImage = React.memo<ArtistIdImageProps>(props => {
|
||||
return (
|
||||
<React.Suspense fallback={<ArtistIdImageFallback enableLoading={props.enableLoading} />}>
|
||||
<ArtistIdImageLoaded {...props} />
|
||||
</React.Suspense>
|
||||
)
|
||||
})
|
||||
|
||||
const CoverArt: React.FC<{
|
||||
FallbackComponent?: () => JSX.Element
|
||||
placeholderIcon?: string
|
||||
height?: string | number
|
||||
width?: string | number
|
||||
coverArtUri?: string
|
||||
resizeMode?: keyof typeof FastImage.resizeMode
|
||||
style?: ImageStyle
|
||||
}> = ({ FallbackComponent, coverArtUri, resizeMode, style }) => {
|
||||
const CoverArtImage = React.memo<CoverArtImageProps>(
|
||||
({ coverArt, imageSize, style, imageStyle, resizeMode, enableLoading, disableLoading }) => {
|
||||
const coverArtUri = useCoverArtUri()
|
||||
|
||||
return (
|
||||
<FastImage
|
||||
source={{ uri: coverArtUri(coverArt, imageSize) }}
|
||||
style={[{ height: style?.height, width: style?.width }, imageStyle]}
|
||||
resizeMode={resizeMode || FastImage.resizeMode.contain}
|
||||
onProgress={enableLoading}
|
||||
onLoadEnd={disableLoading}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const CoverArt: React.FC<CoverArtProps> = ({ coverArt, artistId, resizeMode, imageSize, style, imageStyle, round }) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [fallbackVisible, setFallbackVisible] = useState(false)
|
||||
|
||||
const enableLoading = React.useCallback(() => setLoading(true), [])
|
||||
const disableLoading = React.useCallback(() => setLoading(false), [])
|
||||
const enableFallback = React.useCallback(() => setFallbackVisible(true), [])
|
||||
|
||||
imageSize = imageSize === undefined ? 'thumbnail' : 'original'
|
||||
round = round === undefined ? artistId !== undefined : round
|
||||
|
||||
const viewStyles = [style]
|
||||
if (round) {
|
||||
viewStyles.push(styles.round)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
<CoverImage
|
||||
uri={coverArtUri}
|
||||
style={style}
|
||||
resizeMode={resizeMode}
|
||||
onProgress={enableLoading}
|
||||
onLoadEnd={disableLoading}
|
||||
onError={enableFallback}
|
||||
/>
|
||||
{fallbackVisible ? (
|
||||
FallbackComponent ? (
|
||||
<View style={styles.fallback}>
|
||||
<FallbackComponent />
|
||||
</View>
|
||||
) : (
|
||||
<Fallback />
|
||||
)
|
||||
<View style={viewStyles}>
|
||||
{artistId ? (
|
||||
<ArtistIdImage
|
||||
artistId={artistId}
|
||||
imageSize={imageSize}
|
||||
style={style}
|
||||
imageStyle={imageStyle}
|
||||
resizeMode={resizeMode}
|
||||
enableLoading={enableLoading}
|
||||
disableLoading={disableLoading}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
<CoverArtImage
|
||||
coverArt={coverArt}
|
||||
imageSize={imageSize}
|
||||
style={style}
|
||||
imageStyle={imageStyle}
|
||||
resizeMode={resizeMode}
|
||||
enableLoading={enableLoading}
|
||||
disableLoading={disableLoading}
|
||||
/>
|
||||
)}
|
||||
<ActivityIndicator animating={loading} size="large" color={colors.accent} style={styles.indicator} />
|
||||
</View>
|
||||
@@ -71,16 +126,9 @@ const CoverArt: React.FC<{
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
fallback: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
round: {
|
||||
overflow: 'hidden',
|
||||
borderRadius: 1000,
|
||||
},
|
||||
indicator: {
|
||||
height: '100%',
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react'
|
||||
import { FlatList, FlatListProps, useWindowDimensions } from 'react-native'
|
||||
import { FlatList, FlatListProps, StyleSheet, useWindowDimensions } from 'react-native'
|
||||
import colors from '@app/styles/colors'
|
||||
import GradientBackground from '@app/components/GradientBackground'
|
||||
|
||||
function GradientFlatList<ItemT>(props: FlatListProps<ItemT>) {
|
||||
const layout = useWindowDimensions()
|
||||
|
||||
const contentContainerStyle = StyleSheet.flatten(props.contentContainerStyle)
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
{...props}
|
||||
@@ -16,6 +18,8 @@ function GradientFlatList<ItemT>(props: FlatListProps<ItemT>) {
|
||||
ListHeaderComponent={() => <GradientBackground position="relative" />}
|
||||
ListHeaderComponentStyle={{
|
||||
marginBottom: -layout.height,
|
||||
marginHorizontal: -(contentContainerStyle.paddingHorizontal || 0),
|
||||
top: -(contentContainerStyle.paddingTop || 0),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
22
app/components/Header.tsx
Normal file
22
app/components/Header.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import colors from '@app/styles/colors'
|
||||
import font from '@app/styles/font'
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text, TextStyle } from 'react-native'
|
||||
|
||||
const Header: React.FC<{
|
||||
style?: TextStyle
|
||||
}> = ({ children, style }) => {
|
||||
return <Text style={[styles.text, style]}>{children}</Text>
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
fontFamily: font.bold,
|
||||
fontSize: 24,
|
||||
color: colors.text.primary,
|
||||
marginTop: 18,
|
||||
marginBottom: 12,
|
||||
},
|
||||
})
|
||||
|
||||
export default Header
|
||||
171
app/components/ListItem.tsx
Normal file
171
app/components/ListItem.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { AlbumListItem, Artist, PlaylistListItem, 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, { useState } from 'react'
|
||||
import { GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
||||
import IconFA from 'react-native-vector-icons/FontAwesome'
|
||||
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
||||
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||
import CoverArt from './CoverArt'
|
||||
import PressableOpacity from './PressableOpacity'
|
||||
|
||||
const TitleTextSong = React.memo<{
|
||||
id: string
|
||||
title?: string
|
||||
}>(({ id, title }) => {
|
||||
const currentTrack = useAtomValue(currentTrackAtom)
|
||||
const playing = currentTrack?.id === id
|
||||
|
||||
return (
|
||||
<View style={styles.textLine}>
|
||||
{playing ? <IconFA5 name="play" size={10} color={colors.accent} style={styles.playingIcon} /> : <></>}
|
||||
<Text style={[styles.title, { color: playing ? colors.accent : colors.text.primary }]}>{title}</Text>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const TitleText = React.memo<{
|
||||
title?: string
|
||||
}>(({ title }) => {
|
||||
return (
|
||||
<View style={styles.textLine}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const ListItem: React.FC<{
|
||||
item: Song | AlbumListItem | Artist | PlaylistListItem
|
||||
onPress?: (event: GestureResponderEvent) => void
|
||||
showArt?: boolean
|
||||
showStar?: boolean
|
||||
listStyle?: 'big' | 'small'
|
||||
subtitle?: string
|
||||
}> = ({ item, onPress, showArt, showStar, subtitle, listStyle }) => {
|
||||
const [starred, setStarred] = useState(false)
|
||||
|
||||
showStar = showStar === undefined ? true : showStar
|
||||
listStyle = listStyle || 'small'
|
||||
|
||||
const artSource = item.itemType === 'artist' ? { artistId: item.id } : { coverArt: item.coverArt }
|
||||
const sizeStyle = listStyle === 'big' ? bigStyles : smallStyles
|
||||
|
||||
return (
|
||||
<View style={[styles.container, sizeStyle.container]}>
|
||||
<PressableOpacity onPress={onPress} style={styles.item}>
|
||||
{showArt ? <CoverArt {...artSource} style={{ ...styles.art, ...sizeStyle.art }} resizeMode="cover" /> : <></>}
|
||||
<View style={styles.text}>
|
||||
{item.itemType === 'song' ? (
|
||||
<TitleTextSong id={item.id} title={item.title} />
|
||||
) : (
|
||||
<TitleText title={item.name} />
|
||||
)}
|
||||
{subtitle ? (
|
||||
<View style={styles.textLine}>
|
||||
{starred ? (
|
||||
<IconMat
|
||||
name="file-download-done"
|
||||
size={17}
|
||||
color={colors.text.secondary}
|
||||
style={styles.downloadedIcon}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Text style={styles.subtitle}>{subtitle}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</View>
|
||||
</PressableOpacity>
|
||||
<View style={styles.controls}>
|
||||
{showStar ? (
|
||||
<PressableOpacity onPress={() => setStarred(!starred)} style={styles.controlItem}>
|
||||
{starred ? (
|
||||
<IconFA name="star" size={26} color={colors.accent} />
|
||||
) : (
|
||||
<IconFA name="star-o" size={26} color={colors.text.secondary} />
|
||||
)}
|
||||
</PressableOpacity>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginBottom: 14,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
item: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
art: {
|
||||
marginRight: 10,
|
||||
},
|
||||
text: {
|
||||
flex: 1,
|
||||
},
|
||||
textLine: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontFamily: font.semiBold,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
playingIcon: {
|
||||
marginRight: 5,
|
||||
marginLeft: 1,
|
||||
},
|
||||
downloadedIcon: {
|
||||
marginRight: 2,
|
||||
marginLeft: -3,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
fontFamily: font.regular,
|
||||
color: colors.text.secondary,
|
||||
},
|
||||
controls: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
controlItem: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
})
|
||||
|
||||
const smallStyles = StyleSheet.create({
|
||||
container: {
|
||||
minHeight: 50,
|
||||
},
|
||||
art: {
|
||||
height: 50,
|
||||
width: 50,
|
||||
},
|
||||
})
|
||||
|
||||
const bigStyles = StyleSheet.create({
|
||||
container: {
|
||||
minHeight: 70,
|
||||
},
|
||||
art: {
|
||||
height: 70,
|
||||
width: 70,
|
||||
},
|
||||
})
|
||||
|
||||
export default React.memo(ListItem)
|
||||
@@ -70,7 +70,7 @@ const NowPlayingBar = () => {
|
||||
<View style={styles.subContainer}>
|
||||
<CoverArt
|
||||
style={{ height: styles.subContainer.height, width: styles.subContainer.height }}
|
||||
coverArtUri={track?.artworkThumb}
|
||||
coverArt={track?.coverArt || '-1'}
|
||||
/>
|
||||
<View style={styles.detailsContainer}>
|
||||
<Text numberOfLines={1} style={styles.detailsTitle}>
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
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, { useState } from 'react'
|
||||
import { GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
||||
import IconFA from 'react-native-vector-icons/FontAwesome'
|
||||
import IconFA5 from 'react-native-vector-icons/FontAwesome5'
|
||||
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)
|
||||
const [starred, setStarred] = useState(false)
|
||||
|
||||
subtitle = subtitle || 'artist'
|
||||
const playing = currentTrack?.id === song.id
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<PressableOpacity onPress={onPress} style={styles.item}>
|
||||
{showArt ? <CoverArt coverArtUri={song.coverArtThumbUri} style={styles.art} /> : <></>}
|
||||
<View style={styles.text}>
|
||||
<View style={styles.textLine}>
|
||||
{playing ? <IconFA5 name="play" size={10} color={colors.accent} style={styles.playingIcon} /> : <></>}
|
||||
<Text style={[styles.title, { color: playing ? colors.accent : colors.text.primary }]}>{song.title}</Text>
|
||||
</View>
|
||||
<View style={styles.textLine}>
|
||||
{starred ? (
|
||||
<IconMat
|
||||
name="file-download-done"
|
||||
size={17}
|
||||
color={colors.text.secondary}
|
||||
style={styles.downloadedIcon}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Text style={styles.subtitle}>{song[subtitle]}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</PressableOpacity>
|
||||
<View style={styles.controls}>
|
||||
<PressableOpacity onPress={() => setStarred(!starred)}>
|
||||
{starred ? (
|
||||
<IconFA name="star" size={26} color={colors.accent} />
|
||||
) : (
|
||||
<IconFA name="star-o" size={26} color={colors.text.secondary} />
|
||||
)}
|
||||
</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,
|
||||
height: 50,
|
||||
width: 50,
|
||||
},
|
||||
text: {
|
||||
flex: 1,
|
||||
},
|
||||
textLine: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontFamily: font.semiBold,
|
||||
},
|
||||
playingIcon: {
|
||||
marginRight: 5,
|
||||
marginLeft: 1,
|
||||
},
|
||||
downloadedIcon: {
|
||||
marginRight: 2,
|
||||
marginLeft: -3,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
fontFamily: font.regular,
|
||||
color: colors.text.secondary,
|
||||
},
|
||||
controls: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginLeft: 16,
|
||||
},
|
||||
more: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
})
|
||||
|
||||
export default React.memo(SongItem)
|
||||
Reference in New Issue
Block a user