mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-28 17:19:27 +01:00
some progress on now playing
image colors gradient working for now playing fixed track player init losing handle to notification
This commit is contained in:
parent
28ab092402
commit
0a31597111
14
App.tsx
14
App.tsx
@ -3,7 +3,7 @@ import { DarkTheme, NavigationContainer } from '@react-navigation/native';
|
|||||||
import SplashPage from './src/components/SplashPage';
|
import SplashPage from './src/components/SplashPage';
|
||||||
import RootNavigator from './src/components/navigation/RootNavigator';
|
import RootNavigator from './src/components/navigation/RootNavigator';
|
||||||
import { Provider } from 'jotai';
|
import { Provider } from 'jotai';
|
||||||
import { StatusBar } from 'react-native';
|
import { StatusBar, View } from 'react-native';
|
||||||
import colors from './src/styles/colors';
|
import colors from './src/styles/colors';
|
||||||
import TrackPlayerState from './src/components/TrackPlayerState';
|
import TrackPlayerState from './src/components/TrackPlayerState';
|
||||||
|
|
||||||
@ -14,11 +14,13 @@ const App = () => (
|
|||||||
<Provider>
|
<Provider>
|
||||||
<StatusBar animated={true} backgroundColor={'rgba(0, 0, 0, 0.4)'} barStyle={'light-content'} translucent={true} />
|
<StatusBar animated={true} backgroundColor={'rgba(0, 0, 0, 0.4)'} barStyle={'light-content'} translucent={true} />
|
||||||
<TrackPlayerState />
|
<TrackPlayerState />
|
||||||
<SplashPage>
|
<View style={{ flex: 1, backgroundColor: colors.gradient.high }}>
|
||||||
<NavigationContainer theme={theme}>
|
<SplashPage>
|
||||||
<RootNavigator />
|
<NavigationContainer theme={theme}>
|
||||||
</NavigationContainer>
|
<RootNavigator />
|
||||||
</SplashPage>
|
</NavigationContainer>
|
||||||
|
</SplashPage>
|
||||||
|
</View>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
17
index.js
17
index.js
@ -7,7 +7,22 @@ enableScreens();
|
|||||||
import { AppRegistry } from 'react-native';
|
import { AppRegistry } from 'react-native';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { name as appName } from './app.json';
|
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);
|
AppRegistry.registerComponent(appName, () => App);
|
||||||
TrackPlayer.registerPlaybackService(() => require('./src/playback/service'));
|
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();
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
"react-native-fs": "^2.18.0",
|
"react-native-fs": "^2.18.0",
|
||||||
"react-native-gesture-handler": "^1.10.3",
|
"react-native-gesture-handler": "^1.10.3",
|
||||||
"react-native-get-random-values": "^1.7.0",
|
"react-native-get-random-values": "^1.7.0",
|
||||||
|
"react-native-image-colors": "^1.3.0",
|
||||||
"react-native-linear-gradient": "^2.5.6",
|
"react-native-linear-gradient": "^2.5.6",
|
||||||
"react-native-reanimated": "^2.2.0",
|
"react-native-reanimated": "^2.2.0",
|
||||||
"react-native-safe-area-context": "^3.2.0",
|
"react-native-safe-area-context": "^3.2.0",
|
||||||
|
|||||||
@ -1,134 +1,182 @@
|
|||||||
import React from 'react';
|
import { useAtomValue } from 'jotai/utils';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
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 (
|
return (
|
||||||
<View
|
<View style={headerStyles.container}>
|
||||||
style={{
|
<FastImage source={require('../../res/arrow_left-fill.png')} style={headerStyles.backArrow} tintColor="white" />
|
||||||
// background
|
<Text numberOfLines={2} style={headerStyles.queueName}>
|
||||||
backgroundColor: 'darkblue',
|
{queueName}
|
||||||
flex: 1,
|
</Text>
|
||||||
}}>
|
<FastImage source={require('../../res/more_vertical.png')} style={headerStyles.more} tintColor="white" />
|
||||||
{/* top bar */}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 70,
|
|
||||||
flexDirection: 'row',
|
|
||||||
}}>
|
|
||||||
<View style={{ width: 70, height: 70, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ flex: 1, alignItems: 'center', height: 70 }}>
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
<Text style={styles.text}>Playing from Your Library</Text>
|
|
||||||
<Text style={styles.text}>Songs</Text>
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
</View>
|
|
||||||
<View style={{ width: 70, height: 70, backgroundColor: 'grey' }} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* album art */}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 5,
|
|
||||||
// backgroundColor: 'darkorange',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: 320,
|
|
||||||
height: 320,
|
|
||||||
backgroundColor: 'grey',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* song/album/artist title */}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
// backgroundColor: 'green',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<Text style={{ ...styles.text, fontSize: 26 }}>Name of the Song</Text>
|
|
||||||
<Text style={{ ...styles.text, fontSize: 20, fontWeight: 'normal' }}>Cool Artist</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* seek bar */}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 0.7,
|
|
||||||
// backgroundColor: 'red',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<View style={{ width: 20 }} />
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
}}>
|
|
||||||
<View>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'grey',
|
|
||||||
height: 3,
|
|
||||||
marginBottom: 3,
|
|
||||||
// flex: 1,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
}}>
|
|
||||||
<Text style={{ ...styles.text, fontWeight: 'normal' }}>00:00</Text>
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
<Text style={{ ...styles.text, fontWeight: 'normal' }}>00:00</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={{ width: 20 }} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* main player controls */}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
height: 90,
|
|
||||||
// backgroundColor: 'darkorange',
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
}}>
|
|
||||||
<View style={{ width: 14 }} />
|
|
||||||
<View style={{ width: 60, height: 60, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ width: 60, height: 60, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ width: 90, height: 90, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ width: 60, height: 60, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ width: 60, height: 60, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ width: 14 }} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* extra controls */}
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
// backgroundColor: 'green',
|
|
||||||
flexDirection: 'row',
|
|
||||||
}}>
|
|
||||||
<View style={{ width: 14 }} />
|
|
||||||
<View style={{ width: 60, height: 60, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ flex: 1 }} />
|
|
||||||
<View style={{ width: 60, height: 60, backgroundColor: 'grey' }} />
|
|
||||||
<View style={{ width: 14 }} />
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const headerStyles = StyleSheet.create({
|
||||||
text: {
|
container: {
|
||||||
color: 'white',
|
height: 60,
|
||||||
fontWeight: 'bold',
|
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 (
|
||||||
|
<View style={coverArtStyles.container}>
|
||||||
|
<CoverArt
|
||||||
|
PlaceholderComponent={() => (
|
||||||
|
<View style={{ height: size, width: size }}>
|
||||||
|
<Text>Failed</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
height={size}
|
||||||
|
width={size}
|
||||||
|
coverArtUri={track?.artwork as string}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const coverArtStyles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const SongInfo = () => {
|
||||||
|
const track = useAtomValue(currentTrackAtom);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={infoStyles.container}>
|
||||||
|
<Text style={infoStyles.title}>{track?.title}</Text>
|
||||||
|
<Text style={infoStyles.artist}>{track?.artist}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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<ImageColorsResult | undefined>(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 (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: StatusBar.currentHeight,
|
||||||
|
}}>
|
||||||
|
<GradientBackground
|
||||||
|
colors={[ica ? (ica.muted as string) : colors.gradient.high, colors.gradient.low]}
|
||||||
|
locations={[0.1, 1.0]}
|
||||||
|
/>
|
||||||
|
<NowPlayingHeader />
|
||||||
|
<SongCoverArt />
|
||||||
|
<SongInfo />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default NowPlayingLayout;
|
export default NowPlayingLayout;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
import TrackPlayer, { Capability, Track } from 'react-native-track-player';
|
|
||||||
import paths from '../paths';
|
import paths from '../paths';
|
||||||
|
|
||||||
async function mkdir(path: string): Promise<void> {
|
async function mkdir(path: string): Promise<void> {
|
||||||
@ -27,32 +26,6 @@ const SplashPage: React.FC<{}> = ({ children }) => {
|
|||||||
await mkdir(paths.imageCache);
|
await mkdir(paths.imageCache);
|
||||||
await mkdir(paths.songCache);
|
await mkdir(paths.songCache);
|
||||||
await mkdir(paths.songs);
|
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]);
|
const promise = Promise.all([prepare(), minSplashTime]);
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
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 { useAppState } from '@react-native-community/hooks';
|
||||||
import { useUpdateAtom, useAtomValue } from 'jotai/utils';
|
import { useUpdateAtom, useAtomValue } from 'jotai/utils';
|
||||||
import { currentTrackAtom } from '../state/trackplayer';
|
import { currentQueueNameAtom, currentTrackAtom } from '../state/trackplayer';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
|
|
||||||
const TrackPlayerState = () => {
|
const CurrentTrackState = () => {
|
||||||
const setCurrentTrack = useUpdateAtom(currentTrackAtom);
|
const setCurrentTrack = useUpdateAtom(currentTrackAtom);
|
||||||
const appState = useAppState();
|
const appState = useAppState();
|
||||||
|
|
||||||
const updateCurrentTrack = useCallback(async () => {
|
const update = useCallback(async () => {
|
||||||
const index = await TrackPlayer.getCurrentTrack();
|
const index = await TrackPlayer.getCurrentTrack();
|
||||||
|
|
||||||
if (index !== null && index >= 0) {
|
if (index !== null && index >= 0) {
|
||||||
@ -39,34 +39,59 @@ const TrackPlayerState = () => {
|
|||||||
setCurrentTrack(undefined);
|
setCurrentTrack(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateCurrentTrack();
|
update();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appState === 'active') {
|
if (appState === 'active') {
|
||||||
updateCurrentTrack();
|
update();
|
||||||
}
|
}
|
||||||
}, [appState, updateCurrentTrack]);
|
}, [appState, update]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CurrentTrack = () => {
|
const CurrentQueueName = () => {
|
||||||
const currentTrack = useAtomValue(currentTrackAtom);
|
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(() => {
|
useEffect(() => {
|
||||||
console.log(currentTrack?.title);
|
if (appState === 'active') {
|
||||||
}, [currentTrack]);
|
update();
|
||||||
|
}
|
||||||
|
}, [appState, update]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ASDFSADFSAF = () => (
|
const Debug = () => {
|
||||||
|
const value = useAtomValue(currentQueueNameAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TrackPlayerState = () => (
|
||||||
<View>
|
<View>
|
||||||
<TrackPlayerState />
|
<CurrentTrackState />
|
||||||
<CurrentTrack />
|
<CurrentQueueName />
|
||||||
|
<Debug />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ASDFSADFSAF;
|
export default TrackPlayerState;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
useWindowDimensions,
|
useWindowDimensions,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useSetQueue } from '../../hooks/player';
|
import { useSetQueue } from '../../hooks/trackplayer';
|
||||||
import { albumAtomFamily } from '../../state/music';
|
import { albumAtomFamily } from '../../state/music';
|
||||||
import { currentTrackAtom } from '../../state/trackplayer';
|
import { currentTrackAtom } from '../../state/trackplayer';
|
||||||
import colors from '../../styles/colors';
|
import colors from '../../styles/colors';
|
||||||
@ -138,7 +138,7 @@ const AlbumDetails: React.FC<{
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
}}>
|
}}>
|
||||||
<Button title="Play Album" onPress={() => setQueue(album.songs, album.songs[0].id)} />
|
<Button title="Play Album" onPress={() => setQueue(album.songs, album.name, album.songs[0].id)} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@ -162,7 +162,7 @@ const AlbumDetails: React.FC<{
|
|||||||
title={s.title}
|
title={s.title}
|
||||||
artist={s.artist}
|
artist={s.artist}
|
||||||
track={s.track}
|
track={s.track}
|
||||||
onPress={() => setQueue(album.songs, s.id)}
|
onPress={() => setQueue(album.songs, album.name, s.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useWindowDimensions, ViewStyle } from 'react-native';
|
import { useWindowDimensions, ViewStyle } from 'react-native';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import colors from '../../styles/colors';
|
import colorStyles from '../../styles/colors';
|
||||||
|
|
||||||
const GradientBackground: React.FC<{
|
const GradientBackground: React.FC<{
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
position?: 'relative' | 'absolute';
|
position?: 'relative' | 'absolute';
|
||||||
style?: ViewStyle;
|
style?: ViewStyle;
|
||||||
}> = ({ height, width, position, style, children }) => {
|
colors?: string[];
|
||||||
|
locations?: number[];
|
||||||
|
}> = ({ height, width, position, style, colors, locations, children }) => {
|
||||||
const layout = useWindowDimensions();
|
const layout = useWindowDimensions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[colors.gradient.high, colors.gradient.low]}
|
colors={colors || [colorStyles.gradient.high, colorStyles.gradient.low]}
|
||||||
locations={[0.01, 0.7]}
|
locations={locations || [0.01, 0.7]}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
width: width || '100%',
|
width: width || '100%',
|
||||||
|
|||||||
@ -6,9 +6,12 @@ import BottomTabNavigator from './BottomTabNavigator';
|
|||||||
const RootStack = createNativeStackNavigator();
|
const RootStack = createNativeStackNavigator();
|
||||||
|
|
||||||
const RootNavigator = () => (
|
const RootNavigator = () => (
|
||||||
<RootStack.Navigator>
|
<RootStack.Navigator
|
||||||
<RootStack.Screen name="Main" component={BottomTabNavigator} options={{ headerShown: false }} />
|
screenOptions={{
|
||||||
<RootStack.Screen name="Now Playing" component={NowPlayingLayout} options={{ headerShown: false }} />
|
headerShown: false,
|
||||||
|
}}>
|
||||||
|
<RootStack.Screen name="Main" component={BottomTabNavigator} />
|
||||||
|
<RootStack.Screen name="Now Playing" component={NowPlayingLayout} />
|
||||||
</RootStack.Navigator>
|
</RootStack.Navigator>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { useUpdateAtom } from 'jotai/utils';
|
import { useUpdateAtom } from 'jotai/utils';
|
||||||
import TrackPlayer, { Track } from 'react-native-track-player';
|
import TrackPlayer, { Track } from 'react-native-track-player';
|
||||||
import { Song } from '../models/music';
|
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 {
|
return {
|
||||||
id: song.id,
|
id: song.id,
|
||||||
|
queueName,
|
||||||
title: song.title,
|
title: song.title,
|
||||||
artist: song.artist || 'Unknown Artist',
|
artist: song.artist || 'Unknown Artist',
|
||||||
url: song.streamUri,
|
url: song.streamUri,
|
||||||
@ -16,11 +17,13 @@ function mapSongToTrack(song: Song): Track {
|
|||||||
|
|
||||||
export const useSetQueue = () => {
|
export const useSetQueue = () => {
|
||||||
const setCurrentTrack = useUpdateAtom(currentTrackAtom);
|
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();
|
await TrackPlayer.reset();
|
||||||
const tracks = songs.map(mapSongToTrack);
|
const tracks = songs.map(s => mapSongToTrack(s, name));
|
||||||
|
|
||||||
|
setCurrentQueueName(name);
|
||||||
if (playId) {
|
if (playId) {
|
||||||
setCurrentTrack(tracks.find(t => t.id === playId));
|
setCurrentTrack(tracks.find(t => t.id === playId));
|
||||||
}
|
}
|
||||||
@ -8,9 +8,20 @@ const currentTrack = atom<OptionalTrack>(undefined);
|
|||||||
export const currentTrackAtom = atom<OptionalTrack, OptionalTrack>(
|
export const currentTrackAtom = atom<OptionalTrack, OptionalTrack>(
|
||||||
get => get(currentTrack),
|
get => get(currentTrack),
|
||||||
(get, set, value) => {
|
(get, set, value) => {
|
||||||
if (equal(get(currentTrack), value)) {
|
if (!equal(get(currentTrack), value)) {
|
||||||
return;
|
set(currentTrack, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
type OptionalString = string | undefined;
|
||||||
|
|
||||||
|
const currentQueueName = atom<OptionalString>(undefined);
|
||||||
|
export const currentQueueNameAtom = atom<OptionalString, OptionalString>(
|
||||||
|
get => get(currentQueueName),
|
||||||
|
(get, set, value) => {
|
||||||
|
if (get(currentQueueName) !== value) {
|
||||||
|
set(currentQueueName, value);
|
||||||
}
|
}
|
||||||
set(currentTrack, value);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5429,6 +5429,11 @@ react-native-get-random-values@^1.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-base64-decode "^1.0.0"
|
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:
|
react-native-iphone-x-helper@^1.3.0:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user