diff --git a/res/next.png b/res/next.png
new file mode 100644
index 0000000..2036056
Binary files /dev/null and b/res/next.png differ
diff --git a/res/pause-fill.png b/res/pause-fill.png
new file mode 100644
index 0000000..ebd877a
Binary files /dev/null and b/res/pause-fill.png differ
diff --git a/res/play-fill.png b/res/play-fill.png
new file mode 100644
index 0000000..04b82a4
Binary files /dev/null and b/res/play-fill.png differ
diff --git a/src/components/NowPlayingBar.tsx b/src/components/NowPlayingBar.tsx
new file mode 100644
index 0000000..9d04826
--- /dev/null
+++ b/src/components/NowPlayingBar.tsx
@@ -0,0 +1,140 @@
+import React from 'react'
+import { Pressable, StyleSheet, Text, View } from 'react-native'
+import { useNavigation } from '@react-navigation/native'
+import { useAtomValue } from 'jotai/utils'
+import { currentTrackAtom, playerStateAtom, usePause, usePlay, useProgress } from '../state/trackplayer'
+import CoverArt from './common/CoverArt'
+import colors from '../styles/colors'
+import { Font } from '../styles/text'
+import PressableImage from './common/PressableImage'
+import { State } from 'react-native-track-player'
+
+const ProgressBar = () => {
+ const { position, duration } = useProgress()
+
+ let progress = 0
+ if (duration > 0) {
+ progress = position / duration
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+const progressStyles = StyleSheet.create({
+ container: {
+ height: 1,
+ flexDirection: 'row',
+ },
+ left: {
+ backgroundColor: colors.text.primary,
+ },
+ right: {
+ backgroundColor: '#595959',
+ },
+})
+
+const NowPlayingBar = () => {
+ const navigation = useNavigation()
+ const track = useAtomValue(currentTrackAtom)
+ const playerState = useAtomValue(playerStateAtom)
+ const play = usePlay()
+ const pause = usePause()
+
+ let playPauseIcon: number
+ let playPauseAction: () => void
+
+ switch (playerState) {
+ case State.Playing:
+ case State.Buffering:
+ case State.Connecting:
+ playPauseIcon = require('../../res/pause-fill.png')
+ playPauseAction = pause
+ break
+ default:
+ playPauseIcon = require('../../res/play-fill.png')
+ playPauseAction = play
+ break
+ }
+
+ return (
+ navigation.navigate('Now Playing')}
+ style={{ ...styles.container, display: track ? 'flex' : 'none' }}>
+
+
+ hi}
+ height={styles.subContainer.height}
+ width={styles.subContainer.height}
+ coverArtUri={track?.artwork as string}
+ />
+
+
+ {track?.title}
+
+
+ {track?.artist}
+
+
+
+
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ width: '100%',
+ backgroundColor: colors.gradient.high,
+ borderBottomColor: colors.gradient.low,
+ borderBottomWidth: 1,
+ },
+ subContainer: {
+ height: 60,
+ flexDirection: 'row',
+ },
+ detailsContainer: {
+ flex: 1,
+ height: '100%',
+ justifyContent: 'center',
+ marginLeft: 10,
+ // backgroundColor: 'green',
+ },
+ detailsTitle: {
+ fontFamily: Font.semiBold,
+ fontSize: 13,
+ color: colors.text.primary,
+ },
+ detailsAlbum: {
+ fontFamily: Font.regular,
+ fontSize: 13,
+ color: colors.text.secondary,
+ },
+ controls: {
+ flexDirection: 'row',
+ height: '100%',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: 18,
+ marginLeft: 12,
+ },
+ play: {
+ height: 32,
+ width: 32,
+ },
+})
+
+export default NowPlayingBar
diff --git a/src/components/NowPlayingLayout.tsx b/src/components/NowPlayingLayout.tsx
index 121a710..9b45139 100644
--- a/src/components/NowPlayingLayout.tsx
+++ b/src/components/NowPlayingLayout.tsx
@@ -2,12 +2,14 @@ import { useAtomValue } from 'jotai/utils'
import React from 'react'
import { StatusBar, StyleSheet, Text, useWindowDimensions, View } from 'react-native'
import FastImage from 'react-native-fast-image'
-import TrackPlayer, { State } from 'react-native-track-player'
+import { State } from 'react-native-track-player'
import {
- queueNameAtom,
currentTrackAtom,
playerStateAtom,
+ queueNameAtom,
useNext,
+ usePause,
+ usePlay,
usePrevious,
useProgress,
} from '../state/trackplayer'
@@ -171,6 +173,8 @@ const seekStyles = StyleSheet.create({
const PlayerControls = () => {
const state = useAtomValue(playerStateAtom)
+ const play = usePlay()
+ const pause = usePause()
const next = useNext()
const previous = usePrevious()
@@ -184,12 +188,12 @@ const PlayerControls = () => {
case State.Connecting:
disabled = false
playPauseIcon = require('../../res/pause_circle-fill.png')
- playPauseAction = () => TrackPlayer.pause()
+ playPauseAction = pause
break
case State.Paused:
disabled = false
playPauseIcon = require('../../res/play_circle-fill.png')
- playPauseAction = () => TrackPlayer.play()
+ playPauseAction = play
break
default:
disabled = true
diff --git a/src/components/TrackPlayerState.tsx b/src/components/TrackPlayerState.tsx
index 630fdfc..fc5b725 100644
--- a/src/components/TrackPlayerState.tsx
+++ b/src/components/TrackPlayerState.tsx
@@ -127,7 +127,7 @@ const ProgressState = () => {
return (
<>
-
+
>
)
}
diff --git a/src/components/common/BottomTabBar.tsx b/src/components/common/BottomTabBar.tsx
index e187ea6..edf89b8 100644
--- a/src/components/common/BottomTabBar.tsx
+++ b/src/components/common/BottomTabBar.tsx
@@ -4,6 +4,7 @@ import { BottomTabBarProps } from '@react-navigation/bottom-tabs'
import textStyles from '../../styles/text'
import colors from '../../styles/colors'
import FastImage from 'react-native-fast-image'
+import NowPlayingBar from '../NowPlayingBar'
const icons: { [key: string]: any } = {
home: {
@@ -77,36 +78,39 @@ const BottomTabButton: React.FC<{
const BottomTabBar: React.FC = ({ state, descriptors, navigation }) => {
return (
-
- {state.routes.map((route, index) => {
- const { options } = descriptors[route.key] as any
- const label =
- options.tabBarLabel !== undefined
- ? (options.tabBarLabel as string)
- : options.title !== undefined
- ? options.title
- : route.name
+
+
+
+ {state.routes.map((route, index) => {
+ const { options } = descriptors[route.key] as any
+ const label =
+ options.tabBarLabel !== undefined
+ ? (options.tabBarLabel as string)
+ : options.title !== undefined
+ ? options.title
+ : route.name
- return (
-
- )
- })}
+ return (
+
+ )
+ })}
+
)
}
diff --git a/src/components/common/CoverArt.tsx b/src/components/common/CoverArt.tsx
index acbe84b..f1757db 100644
--- a/src/components/common/CoverArt.tsx
+++ b/src/components/common/CoverArt.tsx
@@ -1,58 +1,63 @@
-import React, { useState } from 'react'
-import { ActivityIndicator, View } from 'react-native'
+import React, { useEffect, useState } from 'react'
+import { ActivityIndicator, StyleSheet, View } from 'react-native'
import FastImage from 'react-native-fast-image'
import colors from '../../styles/colors'
const CoverArt: React.FC<{
PlaceholderComponent: () => JSX.Element
- height: number
- width: number
+ height?: string | number
+ width?: string | number
coverArtUri?: string
}> = ({ PlaceholderComponent, height, width, coverArtUri }) => {
const [placeholderVisible, setPlaceholderVisible] = useState(false)
const [loading, setLoading] = useState(true)
- const indicatorSize = height > 130 ? 'large' : 'small'
- const halfIndicatorHeight = indicatorSize === 'large' ? 18 : 10
+ useEffect(() => {
+ if (!coverArtUri) {
+ setLoading(false)
+ }
+ }, [coverArtUri, setLoading])
- const Placeholder: React.FC<{ visible: boolean }> = ({ visible }) => (
-
-
+ const Image = () => (
+ {
+ setLoading(false)
+ setPlaceholderVisible(true)
+ }}
+ onLoadEnd={() => setLoading(false)}
+ />
+ )
+
+ return (
+
+ {coverArtUri ? : <>>}
+
+
+
+
)
-
- const Art = () => (
- <>
-
-
- {
- setLoading(false)
- setPlaceholderVisible(true)
- }}
- onLoadEnd={() => setLoading(false)}
- />
- >
- )
-
- return {!coverArtUri ? : }
}
+const styles = StyleSheet.create({
+ container: {},
+ image: {
+ height: '100%',
+ width: '100%',
+ },
+ placeholderContainer: {
+ height: '100%',
+ width: '100%',
+ position: 'absolute',
+ },
+ indicator: {
+ height: '100%',
+ width: '100%',
+ position: 'absolute',
+ },
+})
+
export default React.memo(CoverArt)
diff --git a/src/components/common/PressableImage.tsx b/src/components/common/PressableImage.tsx
index e32bfc5..d347f1b 100644
--- a/src/components/common/PressableImage.tsx
+++ b/src/components/common/PressableImage.tsx
@@ -8,7 +8,8 @@ const PressableImage: React.FC<{
style?: ViewStyle
tintColor?: string
disabled?: boolean
-}> = ({ source, onPress, style, tintColor, disabled }) => {
+ hitSlop?: number
+}> = ({ source, onPress, style, tintColor, disabled, hitSlop }) => {
const [opacity, setOpacity] = useState(1)
const [dimensions, setDimensions] = useState(undefined)
@@ -27,6 +28,7 @@ const PressableImage: React.FC<{
style={style}
onPress={onPress}
disabled={disabled}
+ hitSlop={hitSlop}
onPressIn={() => {
if (!disabled) {
setOpacity(0.4)
diff --git a/src/hooks/trackplayer.ts b/src/hooks/trackplayer.ts
deleted file mode 100644
index 74d7d72..0000000
--- a/src/hooks/trackplayer.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { useUpdateAtom } from 'jotai/utils'
-import TrackPlayer, { Track } from 'react-native-track-player'
-import { Song } from '../models/music'
-import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer'
-
-function mapSongToTrack(song: Song, queueName: string): Track {
- return {
- id: song.id,
- queueName,
- title: song.title,
- artist: song.artist || 'Unknown Artist',
- url: song.streamUri,
- artwork: song.coverArtUri,
- artworkThumb: song.coverArtThumbUri,
- duration: song.duration,
- }
-}
-
-export const useSetQueue = () => {
- const setCurrentTrack = useUpdateAtom(currentTrackAtom)
- const setCurrentQueueName = useUpdateAtom(currentQueueNameAtom)
-
- return async (songs: Song[], name: string, playId?: string) => {
- await TrackPlayer.reset()
- const tracks = songs.map(s => mapSongToTrack(s, name))
-
- setCurrentQueueName(name)
- if (playId) {
- setCurrentTrack(tracks.find(t => t.id === playId))
- }
-
- if (!playId) {
- await TrackPlayer.add(tracks)
- } else if (playId === tracks[0].id) {
- await TrackPlayer.add(tracks)
- await TrackPlayer.play()
- } else {
- const playIndex = tracks.findIndex(t => t.id === playId)
- const tracks1 = tracks.slice(0, playIndex)
- const tracks2 = tracks.slice(playIndex)
-
- await TrackPlayer.add(tracks2)
- await TrackPlayer.play()
-
- await TrackPlayer.add(tracks1, 0)
-
- // const queue = await TrackPlayer.getQueue();
- // console.log(`queue: ${JSON.stringify(queue.map(x => x.title))}`);
- }
- }
-}
diff --git a/src/state/trackplayer.ts b/src/state/trackplayer.ts
index 9dcdcaf..5459846 100644
--- a/src/state/trackplayer.ts
+++ b/src/state/trackplayer.ts
@@ -145,6 +145,14 @@ export const useRefreshProgress = () => {
})
}
+export const usePlay = () => {
+ return () => trackPlayerCommands.enqueue(() => TrackPlayer.play())
+}
+
+export const usePause = () => {
+ return () => trackPlayerCommands.enqueue(() => TrackPlayer.pause())
+}
+
export const usePrevious = () => {
const setCurrentTrack = useUpdateAtom(currentTrackAtom)
@@ -259,6 +267,7 @@ function mapSongToTrack(song: Song, queueName: string): TrackExt {
queueName,
title: song.title,
artist: song.artist || 'Unknown Artist',
+ album: song.album || 'Unknown Album',
url: song.streamUri,
artwork: song.coverArtUri,
artworkThumb: song.coverArtThumbUri,
diff --git a/src/styles/colors.ts b/src/styles/colors.ts
index 50bd00d..6de496d 100644
--- a/src/styles/colors.ts
+++ b/src/styles/colors.ts
@@ -5,7 +5,6 @@ export default {
},
gradient: {
high: '#2d2d2d',
- mid: '#191919',
low: '#000000',
},
accent: '#b134db',