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'
const Button: React.FC<{
title: string
title?: string
buttonStyle?: 'hollow' | 'highlight'
onPress: (event: GestureResponderEvent) => void
}> = ({ title, onPress }) => {
}> = ({ title, buttonStyle, onPress, children }) => {
return (
<PressableOpacity onPress={onPress} style={styles.container}>
<Text style={styles.text}>{title}</Text>
<PressableOpacity
onPress={onPress}
style={[styles.container, buttonStyle !== undefined ? styles[buttonStyle] : {}]}>
{title ? <Text style={styles.text}>{title}</Text> : children}
</PressableOpacity>
)
}
@ -18,16 +21,26 @@ const Button: React.FC<{
const styles = StyleSheet.create({
container: {
backgroundColor: colors.accent,
paddingHorizontal: 24,
paddingHorizontal: 10,
minHeight: 42,
justifyContent: 'center',
borderRadius: 1000,
},
hollow: {
backgroundColor: 'transparent',
borderColor: colors.text.secondary,
borderWidth: 1.5,
},
highlight: {
borderColor: colors.text.primary,
borderWidth: 1.5,
},
text: {
fontSize: 15,
fontSize: 16,
fontFamily: font.bold,
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 font from '@app/styles/font'
import { useAtomValue } from 'jotai/utils'
import React from 'react'
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'
@ -16,23 +18,42 @@ const SongItem: React.FC<{
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}>
<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 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={undefined}>
<IconFA name="star-o" size={26} color={colors.text.secondary} />
<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>
@ -60,10 +81,23 @@ const styles = StyleSheet.create({
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,

View File

@ -1,7 +1,7 @@
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 ListPlayerControls from '@app/components/ListPlayerControls'
import NothingHere from '@app/components/NothingHere'
import SongItem from '@app/components/SongItem'
import { albumAtomFamily } from '@app/state/music'
@ -25,10 +25,7 @@ const AlbumDetails: React.FC<{
const Songs = () => (
<>
<View style={styles.controls}>
<Button title="Play Album" onPress={() => setQueue(album.songs, album.name, undefined, false)} />
<Button title="Shuffle" onPress={() => setQueue(album.songs, album.name, undefined, true)} />
</View>
<ListPlayerControls songs={album.songs} typeName="Album" queueName={album.name} />
<View style={styles.songs}>
{album.songs
.sort((a, b) => {
@ -114,9 +111,6 @@ const styles = StyleSheet.create({
height: 220,
width: 220,
},
controls: {
flexDirection: 'row',
},
songs: {
marginTop: 26,
marginBottom: 30,

View File

@ -1,7 +1,7 @@
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 ListPlayerControls from '@app/components/ListPlayerControls'
import NothingHere from '@app/components/NothingHere'
import SongItem from '@app/components/SongItem'
import { playlistAtomFamily } from '@app/state/music'
@ -25,9 +25,12 @@ const PlaylistDetails: React.FC<{
const Songs = () => (
<>
<View style={styles.controls}>
<Button title="Play Playlist" onPress={() => setQueue(playlist.songs, playlist.name)} />
</View>
<ListPlayerControls
songs={playlist.songs}
typeName="Playlist"
queueName={playlist.name}
style={styles.controls}
/>
<View style={styles.songs}>
{playlist.songs.map((s, 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',
},
controls: {
flexDirection: 'row',
marginTop: 20,
},
songs: {