import CoverArt from '@app/components/CoverArt' import HeaderBar from '@app/components/HeaderBar' import GradientImageBackground from '@app/components/GradientImageBackground' import PressableOpacity from '@app/components/PressableOpacity' import { PressableStar } from '@app/components/Star' import { withSuspenseMemo } from '@app/components/withSuspense' import { useNext, usePause, usePlay, usePrevious, useSeekTo } from '@app/hooks/trackplayer' import { mapTrackExtToSong } from '@app/models/map' import { TrackExt } from '@app/models/trackplayer' import { useStore, useStoreDeep } from '@app/state/store' import colors from '@app/styles/colors' import font from '@app/styles/font' import formatDuration from '@app/util/formatDuration' import Slider from '@react-native-community/slider' import { useNavigation } from '@react-navigation/native' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { ActivityIndicator, StyleSheet, Text, TextStyle, 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' const NowPlayingHeader = withSuspenseMemo<{ track?: TrackExt }>(({ track }) => { const queueName = useStore(store => store.queueName) const queueContextType = useStore(store => store.queueContextType) const { t } = useTranslation() console.log(t('resources.album.name', { count: 1 })) if (!track) { return <> } let contextName: string if (queueContextType === 'album') { contextName = t('resources.album.name', { count: 1 }) } else if (queueContextType === 'artist') { contextName = t('resources.song.lists.artistTopSongs') } else if (queueContextType === 'playlist') { contextName = t('resources.playlist.name', { count: 1 }) } else if (queueContextType === 'song') { contextName = t('search.nowPlayingContext') } return ( ( {contextName !== undefined && ( {contextName} )} {queueName || 'Nothing playing...'} )} /> ) }) const headerStyles = StyleSheet.create({ bar: { backgroundColor: 'transparent', }, center: { flex: 1, justifyContent: 'center', }, queueType: { fontFamily: font.regular, fontSize: 14, color: colors.text.primary, textAlign: 'center', }, queueName: { fontFamily: font.bold, fontSize: 16, color: colors.text.primary, textAlign: 'center', }, }) const SongCoverArt = () => { const albumId = useStore(store => store.currentTrack?.albumId) return ( ) } const coverArtStyles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', paddingBottom: 10, paddingHorizontal: 10, }, image: { height: '100%', width: '100%', }, }) const SongInfo = () => { const id = useStore(store => store.currentTrack?.id) const artist = useStore(store => store.currentTrack?.artist) const title = useStore(store => store.currentTrack?.title) return ( {title} {artist} ) } const infoStyles = StyleSheet.create({ container: { width: '100%', flexDirection: 'row', paddingHorizontal: 10, }, details: { flex: 1, marginRight: 20, }, controls: { justifyContent: 'center', }, title: { minHeight: 30, fontFamily: font.bold, fontSize: 22, color: colors.text.primary, }, artist: { minHeight: 21, fontFamily: font.regular, fontSize: 16, color: colors.text.secondary, }, }) const SeekBar = () => { const position = useStore(store => store.progress.position) const duration = useStore(store => store.progress.duration) 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(store => store.playerState) const play = usePlay() const pause = usePause() const next = useNext() const previous = usePrevious() const shuffled = useStore(store => !!store.shuffleOrder) const toggleShuffle = useStore(store => store.toggleShuffle) const repeatMode = useStore(store => store.repeatMode) const toggleRepeat = useStore(store => store.toggleRepeatMode) 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 case State.Buffering: disabled = false playPauseIcon = 'circle' playPauseAction = pause break default: disabled = false playPauseIcon = 'play-circle' playPauseAction = play break } const repeatExtOpacity: TextStyle = { opacity: repeatMode === RepeatMode.Track ? 1 : 0, } return ( toggleRepeat()} disabled={disabled} hitSlop={16}> 1 {state === State.Buffering && ( )} 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: 'flex-end', paddingTop: 10, paddingBottom: 40, }, play: { marginHorizontal: 30, }, center: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, repeatExt: { color: colors.accent, fontFamily: font.bold, fontSize: 14, position: 'absolute', top: 26, }, buffering: { position: 'absolute', }, }) type RootStackParamList = { top: undefined main: undefined } type NowPlayingProps = NativeStackScreenProps const NowPlayingView: React.FC = ({ navigation }) => { const track = useStoreDeep(store => store.currentTrack) useEffect(() => { if (!track) { navigation.navigate('top') } }) const imagePath = typeof track?.artwork === 'string' ? track?.artwork.replace('file://', '') : undefined return ( ) } const styles = StyleSheet.create({ container: { flex: 1, }, content: { flex: 1, paddingHorizontal: 20, }, }) export default NowPlayingView