mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 17:39:27 +01:00
better/shared list player controls w/shuffle
also faked download list/song (by star for now, also faked)
This commit is contained in:
parent
4f69b36c7b
commit
1ed9ac0870
@ -5,12 +5,15 @@ import { GestureResponderEvent, StyleSheet, Text } from 'react-native'
|
|||||||
import PressableOpacity from './PressableOpacity'
|
import PressableOpacity from './PressableOpacity'
|
||||||
|
|
||||||
const Button: React.FC<{
|
const Button: React.FC<{
|
||||||
title: string
|
title?: string
|
||||||
|
buttonStyle?: 'hollow' | 'highlight'
|
||||||
onPress: (event: GestureResponderEvent) => void
|
onPress: (event: GestureResponderEvent) => void
|
||||||
}> = ({ title, onPress }) => {
|
}> = ({ title, buttonStyle, onPress, children }) => {
|
||||||
return (
|
return (
|
||||||
<PressableOpacity onPress={onPress} style={styles.container}>
|
<PressableOpacity
|
||||||
<Text style={styles.text}>{title}</Text>
|
onPress={onPress}
|
||||||
|
style={[styles.container, buttonStyle !== undefined ? styles[buttonStyle] : {}]}>
|
||||||
|
{title ? <Text style={styles.text}>{title}</Text> : children}
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -18,16 +21,26 @@ const Button: React.FC<{
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
backgroundColor: colors.accent,
|
backgroundColor: colors.accent,
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 10,
|
||||||
minHeight: 42,
|
minHeight: 42,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
borderRadius: 1000,
|
borderRadius: 1000,
|
||||||
},
|
},
|
||||||
|
hollow: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderColor: colors.text.secondary,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
borderColor: colors.text.primary,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
},
|
||||||
text: {
|
text: {
|
||||||
fontSize: 15,
|
fontSize: 16,
|
||||||
fontFamily: font.bold,
|
fontFamily: font.bold,
|
||||||
color: colors.text.primary,
|
color: colors.text.primary,
|
||||||
|
paddingHorizontal: 14,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default React.memo(Button)
|
export default Button
|
||||||
|
|||||||
58
app/components/ListPlayerControls.tsx
Normal file
58
app/components/ListPlayerControls.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import Button from '@app/components/Button'
|
||||||
|
import { Song } from '@app/models/music'
|
||||||
|
import { useSetQueue } from '@app/state/trackplayer'
|
||||||
|
import colors from '@app/styles/colors'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { StyleSheet, View, ViewStyle } from 'react-native'
|
||||||
|
import Icon from 'react-native-vector-icons/Ionicons'
|
||||||
|
import IconMat from 'react-native-vector-icons/MaterialIcons'
|
||||||
|
|
||||||
|
const ListPlayerControls = React.memo<{
|
||||||
|
songs: Song[]
|
||||||
|
typeName: string
|
||||||
|
queueName: string
|
||||||
|
style?: ViewStyle
|
||||||
|
}>(({ songs, typeName, queueName, style }) => {
|
||||||
|
const [downloaded, setDownloaded] = useState(false)
|
||||||
|
const setQueue = useSetQueue()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.controls, style]}>
|
||||||
|
<View style={styles.controlsSide}>
|
||||||
|
<Button buttonStyle={downloaded ? 'highlight' : 'hollow'} onPress={() => setDownloaded(!downloaded)}>
|
||||||
|
{downloaded ? (
|
||||||
|
<IconMat name="file-download-done" size={26} color={colors.text.primary} />
|
||||||
|
) : (
|
||||||
|
<IconMat name="file-download" size={26} color={colors.text.secondary} />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<View style={styles.controlsCenter}>
|
||||||
|
<Button title={`Play ${typeName}`} onPress={() => setQueue(songs, queueName, undefined, false)} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.controlsSide}>
|
||||||
|
<Button onPress={() => setQueue(songs, queueName, undefined, true)}>
|
||||||
|
<Icon name="shuffle" size={26} color="white" />
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
controls: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
controlsSide: {
|
||||||
|
flex: 4,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
controlsCenter: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ListPlayerControls
|
||||||
@ -3,9 +3,11 @@ import { currentTrackAtom } from '@app/state/trackplayer'
|
|||||||
import colors from '@app/styles/colors'
|
import colors from '@app/styles/colors'
|
||||||
import font from '@app/styles/font'
|
import font from '@app/styles/font'
|
||||||
import { useAtomValue } from 'jotai/utils'
|
import { useAtomValue } from 'jotai/utils'
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
import { GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
|
||||||
import IconFA from 'react-native-vector-icons/FontAwesome'
|
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 CoverArt from './CoverArt'
|
||||||
import PressableOpacity from './PressableOpacity'
|
import PressableOpacity from './PressableOpacity'
|
||||||
|
|
||||||
@ -16,23 +18,42 @@ const SongItem: React.FC<{
|
|||||||
subtitle?: 'artist' | 'album'
|
subtitle?: 'artist' | 'album'
|
||||||
}> = ({ song, onPress, showArt, subtitle }) => {
|
}> = ({ song, onPress, showArt, subtitle }) => {
|
||||||
const currentTrack = useAtomValue(currentTrackAtom)
|
const currentTrack = useAtomValue(currentTrackAtom)
|
||||||
|
const [starred, setStarred] = useState(false)
|
||||||
|
|
||||||
subtitle = subtitle || 'artist'
|
subtitle = subtitle || 'artist'
|
||||||
|
const playing = currentTrack?.id === song.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<PressableOpacity onPress={onPress} style={styles.item}>
|
<PressableOpacity onPress={onPress} style={styles.item}>
|
||||||
{showArt ? <CoverArt coverArtUri={song.coverArtThumbUri} style={styles.art} /> : <></>}
|
{showArt ? <CoverArt coverArtUri={song.coverArtThumbUri} style={styles.art} /> : <></>}
|
||||||
<View style={styles.text}>
|
<View style={styles.text}>
|
||||||
<Text style={[styles.title, { color: currentTrack?.id === song.id ? colors.accent : colors.text.primary }]}>
|
<View style={styles.textLine}>
|
||||||
{song.title}
|
{playing ? <IconFA5 name="play" size={10} color={colors.accent} style={styles.playingIcon} /> : <></>}
|
||||||
</Text>
|
<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>
|
<Text style={styles.subtitle}>{song[subtitle]}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
<View style={styles.controls}>
|
<View style={styles.controls}>
|
||||||
<PressableOpacity onPress={undefined}>
|
<PressableOpacity onPress={() => setStarred(!starred)}>
|
||||||
|
{starred ? (
|
||||||
|
<IconFA name="star" size={26} color={colors.accent} />
|
||||||
|
) : (
|
||||||
<IconFA name="star-o" size={26} color={colors.text.secondary} />
|
<IconFA name="star-o" size={26} color={colors.text.secondary} />
|
||||||
|
)}
|
||||||
</PressableOpacity>
|
</PressableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -60,10 +81,23 @@ const styles = StyleSheet.create({
|
|||||||
text: {
|
text: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
textLine: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontFamily: font.semiBold,
|
fontFamily: font.semiBold,
|
||||||
},
|
},
|
||||||
|
playingIcon: {
|
||||||
|
marginRight: 5,
|
||||||
|
marginLeft: 1,
|
||||||
|
},
|
||||||
|
downloadedIcon: {
|
||||||
|
marginRight: 2,
|
||||||
|
marginLeft: -3,
|
||||||
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: font.regular,
|
fontFamily: font.regular,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Button from '@app/components/Button'
|
|
||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
||||||
|
import ListPlayerControls from '@app/components/ListPlayerControls'
|
||||||
import NothingHere from '@app/components/NothingHere'
|
import NothingHere from '@app/components/NothingHere'
|
||||||
import SongItem from '@app/components/SongItem'
|
import SongItem from '@app/components/SongItem'
|
||||||
import { albumAtomFamily } from '@app/state/music'
|
import { albumAtomFamily } from '@app/state/music'
|
||||||
@ -25,10 +25,7 @@ const AlbumDetails: React.FC<{
|
|||||||
|
|
||||||
const Songs = () => (
|
const Songs = () => (
|
||||||
<>
|
<>
|
||||||
<View style={styles.controls}>
|
<ListPlayerControls songs={album.songs} typeName="Album" queueName={album.name} />
|
||||||
<Button title="Play Album" onPress={() => setQueue(album.songs, album.name, undefined, false)} />
|
|
||||||
<Button title="Shuffle" onPress={() => setQueue(album.songs, album.name, undefined, true)} />
|
|
||||||
</View>
|
|
||||||
<View style={styles.songs}>
|
<View style={styles.songs}>
|
||||||
{album.songs
|
{album.songs
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@ -114,9 +111,6 @@ const styles = StyleSheet.create({
|
|||||||
height: 220,
|
height: 220,
|
||||||
width: 220,
|
width: 220,
|
||||||
},
|
},
|
||||||
controls: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
songs: {
|
songs: {
|
||||||
marginTop: 26,
|
marginTop: 26,
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Button from '@app/components/Button'
|
|
||||||
import CoverArt from '@app/components/CoverArt'
|
import CoverArt from '@app/components/CoverArt'
|
||||||
import GradientBackground from '@app/components/GradientBackground'
|
import GradientBackground from '@app/components/GradientBackground'
|
||||||
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
import ImageGradientScrollView from '@app/components/ImageGradientScrollView'
|
||||||
|
import ListPlayerControls from '@app/components/ListPlayerControls'
|
||||||
import NothingHere from '@app/components/NothingHere'
|
import NothingHere from '@app/components/NothingHere'
|
||||||
import SongItem from '@app/components/SongItem'
|
import SongItem from '@app/components/SongItem'
|
||||||
import { playlistAtomFamily } from '@app/state/music'
|
import { playlistAtomFamily } from '@app/state/music'
|
||||||
@ -25,9 +25,12 @@ const PlaylistDetails: React.FC<{
|
|||||||
|
|
||||||
const Songs = () => (
|
const Songs = () => (
|
||||||
<>
|
<>
|
||||||
<View style={styles.controls}>
|
<ListPlayerControls
|
||||||
<Button title="Play Playlist" onPress={() => setQueue(playlist.songs, playlist.name)} />
|
songs={playlist.songs}
|
||||||
</View>
|
typeName="Playlist"
|
||||||
|
queueName={playlist.name}
|
||||||
|
style={styles.controls}
|
||||||
|
/>
|
||||||
<View style={styles.songs}>
|
<View style={styles.songs}>
|
||||||
{playlist.songs.map((s, i) => (
|
{playlist.songs.map((s, i) => (
|
||||||
<SongItem key={i} song={s} showArt={true} onPress={() => setQueue(playlist.songs, playlist.name, i)} />
|
<SongItem key={i} song={s} showArt={true} onPress={() => setQueue(playlist.songs, playlist.name, i)} />
|
||||||
@ -107,7 +110,6 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
controls: {
|
controls: {
|
||||||
flexDirection: 'row',
|
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
songs: {
|
songs: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user