diff --git a/App.tsx b/App.tsx index 571b2ce..fc046c1 100644 --- a/App.tsx +++ b/App.tsx @@ -3,7 +3,7 @@ import { DarkTheme, NavigationContainer } from '@react-navigation/native'; import SplashPage from './src/components/SplashPage'; import RootNavigator from './src/components/navigation/RootNavigator'; import { Provider } from 'jotai'; -import { StatusBar } from 'react-native'; +import { StatusBar, View } from 'react-native'; import colors from './src/styles/colors'; import TrackPlayerState from './src/components/TrackPlayerState'; @@ -14,11 +14,13 @@ const App = () => ( - - - - - + + + + + + + ); diff --git a/index.js b/index.js index 6e47173..a3ee640 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,22 @@ enableScreens(); import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; -import TrackPlayer from 'react-native-track-player'; +import TrackPlayer, { Capability } from 'react-native-track-player'; AppRegistry.registerComponent(appName, () => App); TrackPlayer.registerPlaybackService(() => require('./src/playback/service')); + +async function start() { + await TrackPlayer.setupPlayer(); + await TrackPlayer.updateOptions({ + capabilities: [ + Capability.Play, + Capability.Pause, + Capability.Stop, + Capability.SkipToNext, + Capability.SkipToPrevious, + ], + compactCapabilities: [Capability.Play, Capability.Pause, Capability.SkipToNext, Capability.SkipToPrevious], + }); +} +start(); diff --git a/package.json b/package.json index 6d813f3..ccbe08a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react-native-fs": "^2.18.0", "react-native-gesture-handler": "^1.10.3", "react-native-get-random-values": "^1.7.0", + "react-native-image-colors": "^1.3.0", "react-native-linear-gradient": "^2.5.6", "react-native-reanimated": "^2.2.0", "react-native-safe-area-context": "^3.2.0", diff --git a/src/components/NowPlayingLayout.tsx b/src/components/NowPlayingLayout.tsx index a03086f..51cf6ad 100644 --- a/src/components/NowPlayingLayout.tsx +++ b/src/components/NowPlayingLayout.tsx @@ -1,134 +1,182 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import { useAtomValue } from 'jotai/utils'; +import React, { useEffect, useState } from 'react'; +import { StyleSheet, Text, View, StatusBar, useWindowDimensions } from 'react-native'; +import FastImage from 'react-native-fast-image'; +import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer'; +import colors from '../styles/colors'; +import text from '../styles/text'; +import CoverArt from './common/CoverArt'; +import GradientBackground from './common/GradientBackground'; +import ImageColors from 'react-native-image-colors'; + +const NowPlayingHeader = () => { + const queueName = useAtomValue(currentQueueNameAtom); -const NowPlayingLayout = () => { return ( - - {/* top bar */} - - - - - Playing from Your Library - Songs - - - - - - {/* album art */} - - - - - - - {/* song/album/artist title */} - - Name of the Song - Cool Artist - - - {/* seek bar */} - - - - - - - 00:00 - - 00:00 - - - - - - - {/* main player controls */} - - - - - - - - - - - {/* extra controls */} - - - - - - - + + + + {queueName} + + ); }; -const styles = StyleSheet.create({ - text: { - color: 'white', - fontWeight: 'bold', +const headerStyles = StyleSheet.create({ + container: { + height: 60, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + // backgroundColor: 'green', + }, + backArrow: { + height: 24, + width: 24, + margin: 20, + }, + queueName: { + ...text.paragraph, + }, + more: { + height: 24, + width: 24, + margin: 20, }, }); +const SongCoverArt = () => { + const track = useAtomValue(currentTrackAtom); + const layout = useWindowDimensions(); + + const size = layout.width - layout.width / 6; + + return ( + + ( + + Failed + + )} + height={size} + width={size} + coverArtUri={track?.artwork as string} + /> + + ); +}; + +const coverArtStyles = StyleSheet.create({ + container: { + width: '100%', + alignItems: 'center', + marginTop: 20, + }, +}); + +const SongInfo = () => { + const track = useAtomValue(currentTrackAtom); + + return ( + + {track?.title} + {track?.artist} + + ); +}; + +const infoStyles = StyleSheet.create({ + container: { + width: '100%', + alignItems: 'center', + marginTop: 20, + paddingHorizontal: 20, + }, + title: { + ...text.songListTitle, + fontSize: 22, + textAlign: 'center', + }, + artist: { + ...text.songListSubtitle, + fontSize: 14, + textAlign: 'center', + }, +}); + +interface AndroidImageColors { + dominant?: string; + average?: string; + vibrant?: string; + darkVibrant?: string; + lightVibrant?: string; + darkMuted?: string; + lightMuted?: string; + muted?: string; + platform: 'android'; +} + +interface IOSImageColors { + background: string; + primary: string; + secondary: string; + detail: string; + quality: Config['quality']; + platform: 'ios'; +} + +interface Config { + fallback?: string; + pixelSpacing?: number; + quality?: 'lowest' | 'low' | 'high' | 'highest'; + cache?: boolean; + key?: string; +} + +declare type ImageColorsResult = AndroidImageColors | IOSImageColors; + +const NowPlayingLayout = () => { + const track = useAtomValue(currentTrackAtom); + const [imageColors, setImageColors] = useState(undefined); + const ica = imageColors as AndroidImageColors; + + useEffect(() => { + async function getColors() { + if (track?.artwork === undefined) { + return; + } + + const cachedResult = ImageColors.cache.getItem(track.artwork as string); + if (cachedResult) { + setImageColors(cachedResult); + return; + } + + const result = await ImageColors.getColors(track.artwork as string, { + cache: true, + }); + setImageColors(result); + } + getColors(); + }, [track]); + + return ( + + + + + + + ); +}; + export default NowPlayingLayout; diff --git a/src/components/SplashPage.tsx b/src/components/SplashPage.tsx index 841facf..ec28220 100644 --- a/src/components/SplashPage.tsx +++ b/src/components/SplashPage.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Text, View } from 'react-native'; import RNFS from 'react-native-fs'; -import TrackPlayer, { Capability, Track } from 'react-native-track-player'; import paths from '../paths'; async function mkdir(path: string): Promise { @@ -27,32 +26,6 @@ const SplashPage: React.FC<{}> = ({ children }) => { await mkdir(paths.imageCache); await mkdir(paths.songCache); await mkdir(paths.songs); - - await TrackPlayer.setupPlayer(); - TrackPlayer.updateOptions({ - capabilities: [ - Capability.Play, - Capability.Pause, - Capability.Stop, - Capability.SkipToNext, - Capability.SkipToPrevious, - ], - compactCapabilities: [Capability.Play, Capability.Pause, Capability.SkipToNext, Capability.SkipToPrevious], - }); - - const castlevania: Track = { - id: 'castlevania', - url: 'http://www.vgmuseum.com/mrp/cv1/music/03.mp3', - title: 'Stage 1: Castle Entrance', - artist: 'Kinuyo Yamashita and S.Terishima', - duration: 110, - artwork: 'https://webgames.host/uploads/2017/03/castlevania-3-draculas-curse.jpg', - genre: 'BGM', - date: new Date(1989, 1).toISOString(), - }; - - await TrackPlayer.add([castlevania]); - // TrackPlayer.play(); }; const promise = Promise.all([prepare(), minSplashTime]); diff --git a/src/components/TrackPlayerState.tsx b/src/components/TrackPlayerState.tsx index ab732dd..cce06af 100644 --- a/src/components/TrackPlayerState.tsx +++ b/src/components/TrackPlayerState.tsx @@ -1,15 +1,15 @@ import React, { useCallback, useEffect } from 'react'; -import TrackPlayer, { Event, State, Track, useTrackPlayerEvents } from 'react-native-track-player'; +import TrackPlayer, { Event, useTrackPlayerEvents } from 'react-native-track-player'; import { useAppState } from '@react-native-community/hooks'; import { useUpdateAtom, useAtomValue } from 'jotai/utils'; -import { currentTrackAtom } from '../state/trackplayer'; +import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer'; import { View } from 'react-native'; -const TrackPlayerState = () => { +const CurrentTrackState = () => { const setCurrentTrack = useUpdateAtom(currentTrackAtom); const appState = useAppState(); - const updateCurrentTrack = useCallback(async () => { + const update = useCallback(async () => { const index = await TrackPlayer.getCurrentTrack(); if (index !== null && index >= 0) { @@ -39,34 +39,59 @@ const TrackPlayerState = () => { setCurrentTrack(undefined); return; } - updateCurrentTrack(); + update(); }, ); useEffect(() => { if (appState === 'active') { - updateCurrentTrack(); + update(); } - }, [appState, updateCurrentTrack]); + }, [appState, update]); return <>; }; -const CurrentTrack = () => { - const currentTrack = useAtomValue(currentTrackAtom); +const CurrentQueueName = () => { + const setCurrentQueueName = useUpdateAtom(currentQueueNameAtom); + const appState = useAppState(); + + const update = useCallback(async () => { + const queue = await TrackPlayer.getQueue(); + + if (queue !== null && queue.length > 0) { + setCurrentQueueName(queue[0].queueName); + return; + } + + setCurrentQueueName(undefined); + }, [setCurrentQueueName]); useEffect(() => { - console.log(currentTrack?.title); - }, [currentTrack]); + if (appState === 'active') { + update(); + } + }, [appState, update]); return <>; }; -const ASDFSADFSAF = () => ( +const Debug = () => { + const value = useAtomValue(currentQueueNameAtom); + + useEffect(() => { + console.log(value); + }, [value]); + + return <>; +}; + +const TrackPlayerState = () => ( - - + + + ); -export default ASDFSADFSAF; +export default TrackPlayerState; diff --git a/src/components/common/AlbumView.tsx b/src/components/common/AlbumView.tsx index 7ad91ae..53c04ef 100644 --- a/src/components/common/AlbumView.tsx +++ b/src/components/common/AlbumView.tsx @@ -10,7 +10,7 @@ import { useWindowDimensions, View, } from 'react-native'; -import { useSetQueue } from '../../hooks/player'; +import { useSetQueue } from '../../hooks/trackplayer'; import { albumAtomFamily } from '../../state/music'; import { currentTrackAtom } from '../../state/trackplayer'; import colors from '../../styles/colors'; @@ -138,7 +138,7 @@ const AlbumDetails: React.FC<{ style={{ flexDirection: 'row', }}> -