import CoverArt from '@app/components/CoverArt' import ImageGradientBackground from '@app/components/ImageGradientBackground' import PressableOpacity from '@app/components/PressableOpacity' import Star from '@app/components/Star' import { useStarred } from '@app/hooks/music' import { useNext, usePause, usePlay, usePrevious, useSeekTo, useToggleRepeat, useToggleShuffle, } from '@app/hooks/trackplayer' import { selectMusic } from '@app/state/music' import { useStore } from '@app/state/store' import { QueueContextType, selectTrackPlayer } from '@app/state/trackplayer' import colors from '@app/styles/colors' import dimensions from '@app/styles/dimensions' import font from '@app/styles/font' import formatDuration from '@app/util/formatDuration' import { useNavigation } from '@react-navigation/native' import React, { useCallback, useEffect, useState } from 'react' import { StatusBar, StyleSheet, Text, View } from 'react-native' import { NativeStackScreenProps } from 'react-native-screens/native-stack' import { RepeatMode, State } from 'react-native-track-player' import IconFA from 'react-native-vector-icons/FontAwesome' import IconFA5 from 'react-native-vector-icons/FontAwesome5' import Icon from 'react-native-vector-icons/Ionicons' import IconMatCom from 'react-native-vector-icons/MaterialCommunityIcons' import IconMat from 'react-native-vector-icons/MaterialIcons' import Slider from '@react-native-community/slider' function getContextName(type?: QueueContextType) { switch (type) { case 'album': return 'Album' case 'artist': return 'Top Songs' case 'playlist': return 'Playlist' case 'song': return 'Search Results' default: return undefined } } const NowPlayingHeader = React.memo(() => { const navigation = useNavigation() const queueName = useStore(selectTrackPlayer.name) const queueContextType = useStore(selectTrackPlayer.queueContextType) const queueContextId = useStore(selectTrackPlayer.queueContextId) let contextName = getContextName(queueContextType) const back = useCallback(() => { navigation.navigate('top') }, [navigation]) const goToContext = useCallback(() => { if (!queueContextType || !queueContextId || queueContextType === 'song') { return } navigation.navigate('library') navigation.navigate(queueContextType, { id: queueContextId, title: queueName }) }, [navigation, queueContextId, queueContextType, queueName]) return ( {contextName ? ( {contextName} ) : ( <> )} {queueName || 'Nothing playing...'} ) }) const headerStyles = StyleSheet.create({ container: { height: dimensions.header, width: '100%', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, icons: { height: 42, width: 42, marginHorizontal: 8, }, center: { flex: 1, }, queueType: { fontFamily: font.regular, fontSize: 14, color: colors.text.primary, // flex: 1, textAlign: 'center', }, queueName: { fontFamily: font.bold, fontSize: 16, color: colors.text.primary, // flex: 1, textAlign: 'center', }, }) const SongCoverArt = () => { const track = useStore(selectTrackPlayer.currentTrack) return ( ) } const coverArtStyles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', paddingBottom: 10, paddingHorizontal: 10, }, image: { height: '100%', width: '100%', }, }) const SongInfo = () => { const track = useStore(selectTrackPlayer.currentTrack) const id = track?.id || '-1' const type = 'song' const starred = useStarred(id, type) const setStarred = useStore(selectMusic.starItem) return ( {track?.title} {track?.artist} setStarred(id, type, starred)}> ) } const infoStyles = StyleSheet.create({ container: { width: '100%', flexDirection: 'row', paddingHorizontal: 10, }, details: { flex: 1, marginRight: 20, }, controls: { justifyContent: 'center', }, title: { height: 28, fontFamily: font.bold, fontSize: 22, color: colors.text.primary, }, artist: { height: 20, fontFamily: font.regular, fontSize: 16, color: colors.text.secondary, }, }) const SeekBar = () => { const { position, duration } = useStore(selectTrackPlayer.progress) const seekTo = useSeekTo() const [value, setValue] = useState(0) const [sliding, setSliding] = useState(false) useEffect(() => { if (sliding) { return } setValue(position) }, [position, sliding]) const onSlidingStart = useCallback(() => { setSliding(true) }, []) const onSlidingComplete = useCallback( async (val: number) => { await seekTo(val) setSliding(false) }, [seekTo], ) return ( {formatDuration(value)} {formatDuration(duration)} ) } const seekStyles = StyleSheet.create({ container: { width: '100%', marginTop: 16, }, barContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 0, }, bars: { backgroundColor: colors.text.primary, height: 4, }, slider: { flex: 1, height: 40, }, barLeft: { marginRight: -6, }, barRight: { opacity: 0.3, marginLeft: -6, }, indicator: { height: 12, width: 12, borderRadius: 6, backgroundColor: colors.text.primary, elevation: 1, }, textContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 10, }, text: { fontFamily: font.regular, fontSize: 15, color: colors.text.primary, }, }) const PlayerControls = () => { const state = useStore(selectTrackPlayer.playerState) const play = usePlay() const pause = usePause() const next = useNext() const previous = usePrevious() const shuffled = useStore(selectTrackPlayer.shuffled) const toggleShuffle = useToggleShuffle() const repeatMode = useStore(selectTrackPlayer.repeatMode) const toggleRepeat = useToggleRepeat() const navigation = useNavigation() let playPauseIcon: string let playPauseAction: undefined | (() => void) let disabled: boolean switch (state) { case State.Playing: disabled = false playPauseIcon = 'pause-circle' playPauseAction = pause break default: disabled = false playPauseIcon = 'play-circle' playPauseAction = play break } return ( toggleRepeat()} disabled={disabled} hitSlop={16}> 1 toggleShuffle()} disabled={disabled} hitSlop={16}> navigation.navigate('queue')} disabled={disabled} hitSlop={16}> ) } const controlsStyles = StyleSheet.create({ container: { width: '100%', paddingHorizontal: 10, }, top: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingBottom: 8, }, bottom: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingTop: 10, paddingBottom: 54, }, play: { marginHorizontal: 30, }, center: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, repeatExt: { color: colors.accent, fontFamily: font.bold, fontSize: 14, position: 'absolute', top: 26, opacity: 0, }, }) type RootStackParamList = { top: undefined main: undefined } type NowPlayingProps = NativeStackScreenProps const NowPlayingView: React.FC = ({ navigation }) => { const track = useStore(selectTrackPlayer.currentTrack) useEffect(() => { if (!track) { navigation.navigate('top') } }) return ( ) } const styles = StyleSheet.create({ container: { flex: 1, paddingTop: StatusBar.currentHeight, }, content: { flex: 1, paddingHorizontal: 20, }, }) export default NowPlayingView