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',
}}>
-
setQueue(album.songs, s.id)}
+ onPress={() => setQueue(album.songs, album.name, s.id)}
/>
))}
diff --git a/src/components/common/GradientBackground.tsx b/src/components/common/GradientBackground.tsx
index 1ec479e..00f2ebe 100644
--- a/src/components/common/GradientBackground.tsx
+++ b/src/components/common/GradientBackground.tsx
@@ -1,20 +1,22 @@
import React from 'react';
import { useWindowDimensions, ViewStyle } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
-import colors from '../../styles/colors';
+import colorStyles from '../../styles/colors';
const GradientBackground: React.FC<{
height?: number | string;
width?: number | string;
position?: 'relative' | 'absolute';
style?: ViewStyle;
-}> = ({ height, width, position, style, children }) => {
+ colors?: string[];
+ locations?: number[];
+}> = ({ height, width, position, style, colors, locations, children }) => {
const layout = useWindowDimensions();
return (
(
-
-
-
+
+
+
);
diff --git a/src/hooks/player.ts b/src/hooks/trackplayer.ts
similarity index 74%
rename from src/hooks/player.ts
rename to src/hooks/trackplayer.ts
index 281dd06..b710edf 100644
--- a/src/hooks/player.ts
+++ b/src/hooks/trackplayer.ts
@@ -1,11 +1,12 @@
import { useUpdateAtom } from 'jotai/utils';
import TrackPlayer, { Track } from 'react-native-track-player';
import { Song } from '../models/music';
-import { currentTrackAtom } from '../state/trackplayer';
+import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer';
-function mapSongToTrack(song: Song): Track {
+function mapSongToTrack(song: Song, queueName: string): Track {
return {
id: song.id,
+ queueName,
title: song.title,
artist: song.artist || 'Unknown Artist',
url: song.streamUri,
@@ -16,11 +17,13 @@ function mapSongToTrack(song: Song): Track {
export const useSetQueue = () => {
const setCurrentTrack = useUpdateAtom(currentTrackAtom);
+ const setCurrentQueueName = useUpdateAtom(currentQueueNameAtom);
- return async (songs: Song[], playId?: string) => {
+ return async (songs: Song[], name: string, playId?: string) => {
await TrackPlayer.reset();
- const tracks = songs.map(mapSongToTrack);
+ const tracks = songs.map(s => mapSongToTrack(s, name));
+ setCurrentQueueName(name);
if (playId) {
setCurrentTrack(tracks.find(t => t.id === playId));
}
diff --git a/src/state/trackplayer.ts b/src/state/trackplayer.ts
index 16003e6..46256d8 100644
--- a/src/state/trackplayer.ts
+++ b/src/state/trackplayer.ts
@@ -8,9 +8,20 @@ const currentTrack = atom(undefined);
export const currentTrackAtom = atom(
get => get(currentTrack),
(get, set, value) => {
- if (equal(get(currentTrack), value)) {
- return;
+ if (!equal(get(currentTrack), value)) {
+ set(currentTrack, value);
+ }
+ },
+);
+
+type OptionalString = string | undefined;
+
+const currentQueueName = atom(undefined);
+export const currentQueueNameAtom = atom(
+ get => get(currentQueueName),
+ (get, set, value) => {
+ if (get(currentQueueName) !== value) {
+ set(currentQueueName, value);
}
- set(currentTrack, value);
},
);
diff --git a/yarn.lock b/yarn.lock
index 5649fb0..26665e9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5429,6 +5429,11 @@ react-native-get-random-values@^1.7.0:
dependencies:
fast-base64-decode "^1.0.0"
+react-native-image-colors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/react-native-image-colors/-/react-native-image-colors-1.3.0.tgz#3e499730618540fddb779b3f73defe81d2d91801"
+ integrity sha512-1k+4wXWqm+sFA3H01Xri9HQ4UCWi0dvuUID4rrnzu6VlR/Oa525Scot0li36IOTOl0x/Ar883bh0bcfHOhi+vg==
+
react-native-iphone-x-helper@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"