import React, { useCallback, useEffect, useRef } from 'react'; import { ActivityIndicator, Animated, Dimensions, Platform, Pressable, View } from 'react-native'; import FastImage from 'react-native-fast-image'; import styled, { css } from 'styled-components/native'; import PlayIcon from 'assets/icons/play.svg'; import PauseIcon from 'assets/icons/pause.svg'; import useCurrentTrack from 'utility/useCurrentTrack'; import TrackPlayer, { State, usePlaybackState, useProgress } from 'react-native-track-player'; import { Shadow } from 'react-native-shadow-2'; import usePrevious from 'utility/usePrevious'; import { Text } from 'components/Typography'; import useDefaultStyles, { ColoredBlurView } from 'components/Colors'; import { useNavigation } from '@react-navigation/native'; import { calculateProgressTranslation } from 'components/Progresstrack'; import { THEME_COLOR } from 'CONSTANTS'; import { NavigationProp } from 'screens/types'; import { ShadowWrapper } from 'components/Shadow'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; const NOW_PLAYING_POPOVER_MARGIN = 6; const NOW_PLAYING_POPOVER_WIDTH = Dimensions.get('screen').width - 2 * NOW_PLAYING_POPOVER_MARGIN; const PopoverPosition = css` position: absolute; left: ${NOW_PLAYING_POPOVER_MARGIN}px; right: ${NOW_PLAYING_POPOVER_MARGIN}px; border-radius: 8px; overflow: visible; `; const Container = styled.ScrollView` ${PopoverPosition}; `; const InnerContainer = styled.TouchableOpacity` padding: 12px; overflow: hidden; flex: 1; flex-direction: row; align-items: center; `; const ProgressTrack = styled(Animated.View)<{ stroke?: number; opacity?: number}>` position: absolute; bottom: 0; left: 0; right: 0; height: ${(props) => props.stroke ? props.stroke + 'px' : '100%'}; background-color: ${THEME_COLOR}; opacity: ${(props) => props.opacity || 1}; border-radius: 99px; `; const ShadowOverlay = styled.View` position: absolute; top: 0; left: 0; right: 0; bottom: 0; `; const Cover = styled(FastImage)` height: 32px; width: 32px; border-radius: 4px; margin-right: 12px; `; const TrackNameContainer = styled.View` flex: 1; margin-right: 12px; `; const ActionButton = styled.Pressable` margin-right: 8px; `; function SelectActionButton() { const state = usePlaybackState(); const defaultStyles = useDefaultStyles(); switch(state) { case State.Playing: return ( ); case State.Stopped: case State.Paused: return ( ); case State.Buffering: case State.Connecting: return ( ); default: return null; } } function NowPlaying() { const { index, track } = useCurrentTrack(); const { buffered, position } = useProgress(); const defaultStyles = useDefaultStyles(); const tabBarHeight = useBottomTabBarHeight(); const previousBuffered = usePrevious(buffered); const previousPosition = usePrevious(position); const navigation = useNavigation(); const bufferAnimation = useRef(new Animated.Value(0)); const progressAnimation = useRef(new Animated.Value(0)); const openNowPlayingModal = useCallback(() => { navigation.navigate('Player'); }, [navigation]); useEffect(() => { const duration = (track?.duration || 0) / 10_000_000; // GUARD: Don't update when the duration is 0, cause it will put the // bars in a weird space. if (duration === 0) { return; } // First calculate the new value for the buffer animation. Then, check // whether the buffered state is smaller than the previous one, in which // case we'll just set the value without animation const bufferValue = calculateProgressTranslation(buffered, duration, NOW_PLAYING_POPOVER_WIDTH); if (buffered < (previousBuffered || 0)) { bufferAnimation.current.setValue(bufferValue); } else { Animated.timing(bufferAnimation.current, { toValue: bufferValue, duration: 500, useNativeDriver: true, }).start(); } // Then, do the same for the progress animation const progressValule = calculateProgressTranslation(position, duration, NOW_PLAYING_POPOVER_WIDTH); if (position < (previousPosition || 0)) { progressAnimation.current.setValue(progressValule); } else { Animated.timing(progressAnimation.current, { toValue: progressValule, duration: 500, useNativeDriver: true, }).start(); } }, [buffered, track?.duration, position, index, previousBuffered, previousPosition]); if (!track) { return null; } return ( {/** TODO: Fix shadow overflow on Android */} {Platform.OS === 'ios' ? ( ) : null} {track.title} {(track.artist || track.album) && ( {track.artist}{track.album ? ` — ${track.album}` : ''} )} ); } export default NowPlaying;