better/shared list player controls w/shuffle

also faked download list/song (by star for now, also faked)
This commit is contained in:
austinried 2021-07-23 17:46:29 +09:00
parent 4f69b36c7b
commit 1ed9ac0870
5 changed files with 128 additions and 27 deletions

View File

@ -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

View 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

View File

@ -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,

View File

@ -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,

View File

@ -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: {