Initial commit
This commit is contained in:
91
src/screens/Player/components/MediaControls.tsx
Normal file
91
src/screens/Player/components/MediaControls.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import TrackPlayer, { usePlaybackState, STATE_PLAYING, STATE_PAUSED, Track } from 'react-native-track-player';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
|
||||
import { faPlay, faPause, faBackward, faForward } from '@fortawesome/free-solid-svg-icons';
|
||||
import styled from 'styled-components/native';
|
||||
import { useHasQueue } from '../../../utility/useQueue';
|
||||
|
||||
const MAIN_SIZE = 48;
|
||||
const BUTTON_SIZE = 32;
|
||||
|
||||
const pause = () => TrackPlayer.pause();
|
||||
const play = () => TrackPlayer.play();
|
||||
const next = () => TrackPlayer.skipToNext();
|
||||
const previous = () => TrackPlayer.skipToPrevious();
|
||||
|
||||
const Container = styled.View`
|
||||
/* */
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
`;
|
||||
|
||||
const Buttons = styled.View`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Button = styled.View`
|
||||
margin: 20px;
|
||||
`;
|
||||
|
||||
export default function MediaControls() {
|
||||
return (
|
||||
<Container>
|
||||
<Buttons>
|
||||
<Button>
|
||||
<PreviousButton />
|
||||
</Button>
|
||||
<MainButton />
|
||||
<Button>
|
||||
<NextButton />
|
||||
</Button>
|
||||
</Buttons>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export function PreviousButton() {
|
||||
const hasQueue = useHasQueue();
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={previous} disabled={!hasQueue} style={{ opacity: hasQueue ? 1 : 0.5 }}>
|
||||
<FontAwesomeIcon icon={faBackward} size={BUTTON_SIZE} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
export function NextButton() {
|
||||
const hasQueue = useHasQueue();
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={next} disabled={!hasQueue} style={{ opacity: hasQueue ? 1 : 0.5 }}>
|
||||
<FontAwesomeIcon icon={faForward} size={BUTTON_SIZE} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
export function MainButton() {
|
||||
const state = usePlaybackState();
|
||||
|
||||
switch (state) {
|
||||
case STATE_PLAYING:
|
||||
return (
|
||||
<TouchableOpacity onPress={pause}>
|
||||
<FontAwesomeIcon icon={faPause} size={MAIN_SIZE} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
case STATE_PAUSED:
|
||||
return (
|
||||
<TouchableOpacity onPress={play}>
|
||||
<FontAwesomeIcon icon={faPlay} size={MAIN_SIZE} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<TouchableOpacity onPress={pause} disabled>
|
||||
<FontAwesomeIcon icon={faPause} size={MAIN_SIZE} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
33
src/screens/Player/components/NowPlaying.tsx
Normal file
33
src/screens/Player/components/NowPlaying.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Text, Dimensions, Image, View } from 'react-native';
|
||||
import useCurrentTrack from '../../../utility/useCurrentTrack';
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
const Screen = Dimensions.get('screen');
|
||||
|
||||
const Artwork = styled.Image`
|
||||
border-radius: 10px;
|
||||
background-color: #fbfbfb;
|
||||
width: ${Screen.width * 0.8}px;
|
||||
height: ${Screen.width * 0.8}px;
|
||||
margin: 25px auto;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
|
||||
export default function NowPlaying() {
|
||||
const track = useCurrentTrack();
|
||||
|
||||
// GUARD: Don't render anything if nothing is playing
|
||||
if (!track) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<Artwork style={{ flex: 1 }} source={{ uri: track.artwork }} />
|
||||
<Text style={{ fontWeight: 'bold', fontSize: 24, marginBottom: 12 }} >{track.artist}</Text>
|
||||
<Text style={{ fontSize: 18, marginBottom: 12, textAlign: 'center', paddingLeft: 20, paddingRight: 20 }}>{track.title}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
65
src/screens/Player/components/ProgressBar.tsx
Normal file
65
src/screens/Player/components/ProgressBar.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { useTrackPlayerProgress } from 'react-native-track-player';
|
||||
import styled from 'styled-components/native';
|
||||
import { View, Text } from 'react-native';
|
||||
import { padStart } from 'lodash';
|
||||
|
||||
const Container = styled.View`
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
background-color: #eeeeee;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Bar = styled.View<{ progress: number }>`
|
||||
background-color: salmon;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
width: ${props => props.progress * 100}%;
|
||||
`;
|
||||
|
||||
const PositionIndicator = styled.View<{ progress: number }>`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 100px;
|
||||
border: 1px solid #eee;
|
||||
background-color: white;
|
||||
transform: translateX(-10px) translateY(-8.5px);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: ${props => props.progress * 100}%;
|
||||
box-shadow: 0px 4px 8px rgba(0,0,0,0.1);
|
||||
`;
|
||||
|
||||
const NumberBar = styled.View`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 20px 0;
|
||||
`;
|
||||
|
||||
function getSeconds(seconds: number): string {
|
||||
return padStart(String(Math.floor(seconds % 60).toString()), 2, '0');
|
||||
}
|
||||
|
||||
function getMinutes(seconds: number): number {
|
||||
return Math.floor(seconds / 60);
|
||||
}
|
||||
|
||||
export default function ProgressBar() {
|
||||
const { position, duration } = useTrackPlayerProgress(500);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<Bar progress={position / duration} />
|
||||
<PositionIndicator progress={position / duration} />
|
||||
</Container>
|
||||
<NumberBar>
|
||||
<Text>0:00</Text>
|
||||
<Text>{getMinutes(position)}:{getSeconds(position)}</Text>
|
||||
<Text>{getMinutes(duration)}:{getSeconds(duration)}</Text>
|
||||
</NumberBar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
40
src/screens/Player/components/Queue.tsx
Normal file
40
src/screens/Player/components/Queue.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import useQueue from '../../../utility/useQueue';
|
||||
import { View, Text } from 'react-native';
|
||||
import styled, { css } from 'styled-components/native';
|
||||
import useCurrentTrack from '../../../utility/useCurrentTrack';
|
||||
|
||||
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean }>`
|
||||
padding: 10px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #eee;
|
||||
|
||||
${props => props.active && css`
|
||||
font-weight: 900;
|
||||
background-color: #ff8c6922;
|
||||
padding: 20px 35px;
|
||||
margin: 0 -25px;
|
||||
`}
|
||||
|
||||
${props => props.alreadyPlayed && css`
|
||||
opacity: 0.25;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default function Queue() {
|
||||
const queue = useQueue();
|
||||
const currentTrack = useCurrentTrack();
|
||||
const currentIndex = queue?.findIndex(d => d.id === currentTrack?.id);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text style={{ marginTop: 20, marginBottom: 20 }}>Queue</Text>
|
||||
{queue?.map((track, i) => (
|
||||
<QueueItem active={currentTrack?.id === track.id} key={i} alreadyPlayed={i < currentIndex}>
|
||||
<Text style={{marginBottom: 2}}>{track.title}</Text>
|
||||
<Text style={{ opacity: 0.5 }}>{track.artist}</Text>
|
||||
</QueueItem>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user