diff --git a/res/next-fill.png b/res/next-fill.png new file mode 100644 index 0000000..46bc713 Binary files /dev/null and b/res/next-fill.png differ diff --git a/res/pause_circle-fill.png b/res/pause_circle-fill.png new file mode 100644 index 0000000..e31b55b Binary files /dev/null and b/res/pause_circle-fill.png differ diff --git a/res/play_circle-fill.png b/res/play_circle-fill.png new file mode 100644 index 0000000..db9d664 Binary files /dev/null and b/res/play_circle-fill.png differ diff --git a/res/previous-fill.png b/res/previous-fill.png new file mode 100644 index 0000000..328112b Binary files /dev/null and b/res/previous-fill.png differ diff --git a/src/components/NowPlayingLayout.tsx b/src/components/NowPlayingLayout.tsx index 8987dc8..0e907ac 100644 --- a/src/components/NowPlayingLayout.tsx +++ b/src/components/NowPlayingLayout.tsx @@ -1,8 +1,9 @@ import { useAtomValue } from 'jotai/utils'; import React from 'react'; -import { StatusBar, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; +import { Pressable, StatusBar, StyleSheet, Text, useWindowDimensions, View } from 'react-native'; import FastImage from 'react-native-fast-image'; -import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer'; +import TrackPlayer, { State } from 'react-native-track-player'; +import { currentQueueNameAtom, currentTrackAtom, playerStateAtom } from '../state/trackplayer'; import text from '../styles/text'; import CoverArt from './common/CoverArt'; import ImageGradientBackground from './common/ImageGradientBackground'; @@ -14,7 +15,7 @@ const NowPlayingHeader = () => { - {queueName} + {queueName || 'Nothing playing...'} @@ -48,16 +49,12 @@ const SongCoverArt = () => { const track = useAtomValue(currentTrackAtom); const layout = useWindowDimensions(); - const size = layout.width - layout.width / 6; + const size = layout.width - layout.width / 7; return ( ( - - Failed - - )} + PlaceholderComponent={() => } height={size} width={size} coverArtUri={track?.artwork as string} @@ -70,7 +67,7 @@ const coverArtStyles = StyleSheet.create({ container: { width: '100%', alignItems: 'center', - marginTop: 20, + marginTop: 10, }, }); @@ -104,6 +101,81 @@ const infoStyles = StyleSheet.create({ }, }); +const PlayerControls = () => { + const state = useAtomValue(playerStateAtom); + + let playPauseIcon: number; + let playPauseStyle: any; + let playPauseAction: () => void; + + switch (state) { + case State.Playing: + playPauseIcon = require('../../res/pause_circle-fill.png'); + playPauseStyle = controlsStyles.enabled; + playPauseAction = () => TrackPlayer.pause(); + break; + case State.Paused: + playPauseIcon = require('../../res/play_circle-fill.png'); + playPauseStyle = controlsStyles.enabled; + playPauseAction = () => TrackPlayer.play(); + break; + case State.Buffering: + case State.Connecting: + playPauseIcon = require('../../res/pause_circle-fill.png'); + playPauseStyle = controlsStyles.disabled; + playPauseAction = () => {}; + break; + default: + playPauseIcon = require('../../res/play_circle-fill.png'); + playPauseStyle = controlsStyles.disabled; + playPauseAction = () => {}; + break; + } + + return ( + + + + + + + + ); +}; + +const controlsStyles = StyleSheet.create({ + container: { + width: '100%', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginTop: 40, + }, + skip: { + height: 40, + width: 40, + marginHorizontal: 18, + }, + play: { + height: 90, + width: 90, + }, + enabled: { + opacity: 1, + }, + disabled: { + opacity: 0.35, + }, +}); + const NowPlayingLayout = () => { const track = useAtomValue(currentTrackAtom); @@ -117,6 +189,7 @@ const NowPlayingLayout = () => { + ); }; diff --git a/src/components/TrackPlayerState.tsx b/src/components/TrackPlayerState.tsx index cce06af..cfc0999 100644 --- a/src/components/TrackPlayerState.tsx +++ b/src/components/TrackPlayerState.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useEffect } from 'react'; -import TrackPlayer, { Event, useTrackPlayerEvents } from 'react-native-track-player'; +import TrackPlayer, { Event, State, useTrackPlayerEvents } from 'react-native-track-player'; import { useAppState } from '@react-native-community/hooks'; import { useUpdateAtom, useAtomValue } from 'jotai/utils'; -import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer'; +import { currentQueueNameAtom, currentTrackAtom, playerStateAtom } from '../state/trackplayer'; import { View } from 'react-native'; const CurrentTrackState = () => { @@ -67,6 +67,42 @@ const CurrentQueueName = () => { setCurrentQueueName(undefined); }, [setCurrentQueueName]); + useTrackPlayerEvents( + [Event.PlaybackState, Event.PlaybackQueueEnded, Event.PlaybackMetadataReceived, Event.RemoteDuck, Event.RemoteStop], + event => { + if (event.type === Event.PlaybackState) { + if (event.state === State.Stopped || event.state === State.None) { + return; + } + } + update(); + }, + ); + + useEffect(() => { + if (appState === 'active') { + update(); + } + }, [appState, update]); + + return <>; +}; + +const PlayerState = () => { + const setPlayerState = useUpdateAtom(playerStateAtom); + const appState = useAppState(); + + const update = useCallback( + async (state?: State) => { + setPlayerState(state || (await TrackPlayer.getState())); + }, + [setPlayerState], + ); + + useTrackPlayerEvents([Event.PlaybackState], event => { + update(event.state); + }); + useEffect(() => { if (appState === 'active') { update(); @@ -90,6 +126,7 @@ const TrackPlayerState = () => ( + ); diff --git a/src/state/trackplayer.ts b/src/state/trackplayer.ts index 46256d8..236c4cd 100644 --- a/src/state/trackplayer.ts +++ b/src/state/trackplayer.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { Track } from 'react-native-track-player'; +import { State, Track } from 'react-native-track-player'; import equal from 'fast-deep-equal'; type OptionalTrack = Track | undefined; @@ -25,3 +25,13 @@ export const currentQueueNameAtom = atom( } }, ); + +const playerState = atom(State.None); +export const playerStateAtom = atom( + get => get(playerState), + (get, set, value) => { + if (get(playerState) !== value) { + set(playerState, value); + } + }, +);