playlist play, optimized layout & better player style implemented

This commit is contained in:
KRTirtho 2021-02-15 00:32:06 +06:00
parent 23394e437b
commit b97a8edc5b
5 changed files with 66 additions and 39 deletions

View File

@ -1,3 +0,0 @@
[Dolphin]
Timestamp=2021,2,13,23,13,52
Version=4

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { Window, hot, BoxView, View } from "@nodegui/react-nodegui"; import { Window, hot, View } from "@nodegui/react-nodegui";
import { Direction, QIcon, QMainWindow, WidgetEventTypes, WindowState } from "@nodegui/nodegui"; import { QIcon, QMainWindow, WidgetEventTypes, WindowState } from "@nodegui/nodegui";
import nodeguiIcon from "../assets/nodegui.jpg"; import nodeguiIcon from "../assets/nodegui.jpg";
import { MemoryRouter } from "react-router"; import { MemoryRouter } from "react-router";
import Routes from "./routes"; import Routes from "./routes";
@ -19,7 +19,7 @@ export interface Credentials {
clientSecret: string; clientSecret: string;
} }
const minSize = { width: 700, height: 520 }; const minSize = { width: 700, height: 750 };
const winIcon = new QIcon(nodeguiIcon); const winIcon = new QIcon(nodeguiIcon);
global.localStorage = new LocalStorage("./local"); global.localStorage = new LocalStorage("./local");
@ -35,13 +35,21 @@ function RootApp() {
const credentialStr = localStorage.getItem(CredentialKeys.credentials); const credentialStr = localStorage.getItem(CredentialKeys.credentials);
useEffect(() => { useEffect(() => {
windowRef.current?.addEventListener(WidgetEventTypes.Close, () => { setIsLoggedIn(!!credentialStr);
}, []);
useEffect(() => {
const onWindowClose = () => {
if (audioPlayer.isRunning()) { if (audioPlayer.isRunning()) {
audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e)); audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e));
} }
}); };
setIsLoggedIn(!!credentialStr);
}, []); windowRef.current?.addEventListener(WidgetEventTypes.Close, onWindowClose);
return () => {
windowRef.current?.removeEventListener(WidgetEventTypes.Close, onWindowClose);
};
});
useEffect(() => { useEffect(() => {
if (isLoggedIn) { if (isLoggedIn) {
@ -67,10 +75,10 @@ function RootApp() {
<MemoryRouter> <MemoryRouter>
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token }}> <authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token }}>
<playerContext.Provider value={{ spotifyApi, currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}> <playerContext.Provider value={{ spotifyApi, currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}>
<BoxView direction={Direction.TopToBottom}> <View style={`flex: 1; flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch'; height: '100%';`}>
<Routes /> <Routes />
{isLoggedIn && <Player />} {isLoggedIn && <Player />}
</BoxView> </View>
</playerContext.Provider> </playerContext.Provider>
</authContext.Provider> </authContext.Provider>
</MemoryRouter> </MemoryRouter>

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { Button, Text, View, ScrollArea } from "@nodegui/react-nodegui"; import { Button, View, ScrollArea } from "@nodegui/react-nodegui";
import playerContext from "../context/playerContext"; import playerContext from "../context/playerContext";
import authContext from "../context/authContext"; import authContext from "../context/authContext";
import { useHistory } from "react-router"; import { useHistory } from "react-router";
@ -7,7 +7,7 @@ import CachedImage from "./shared/CachedImage";
import { CursorShape } from "@nodegui/nodegui"; import { CursorShape } from "@nodegui/nodegui";
function Home() { function Home() {
const { spotifyApi, currentPlaylist, currentTrack } = useContext(playerContext); const { spotifyApi } = useContext(playerContext);
const { isLoggedIn, access_token } = useContext(authContext); const { isLoggedIn, access_token } = useContext(authContext);
const [categories, setCategories] = useState<SpotifyApi.CategoryObject[]>([]); const [categories, setCategories] = useState<SpotifyApi.CategoryObject[]>([]);
@ -26,8 +26,8 @@ function Home() {
}, [access_token]); }, [access_token]);
return ( return (
<ScrollArea style={`flex-grow: 1; border: none;`}> <ScrollArea style={`flex-grow: 1; border: none; flex: 1;`}>
<View style={`flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch';`}> <View style={`flex-direction: 'column'; justify-content: 'center'; flex: 1;`}>
<CategoryCard key={((Math.random() * Date.now()) / Math.random()) * 100} id="current" name="Currently Playing" /> <CategoryCard key={((Math.random() * Date.now()) / Math.random()) * 100} id="current" name="Currently Playing" />
{isLoggedIn && {isLoggedIn &&
categories.map(({ id, name }, index) => { categories.map(({ id, name }, index) => {
@ -75,6 +75,7 @@ function CategoryCard({ id, name }: CategoryCardProps) {
const categoryStylesheet = ` const categoryStylesheet = `
#container{ #container{
flex: 1;
flex-direction: column; flex-direction: column;
justify-content: 'center'; justify-content: 'center';
margin-bottom: 20px; margin-bottom: 20px;
@ -94,7 +95,10 @@ function CategoryCard({ id, name }: CategoryCardProps) {
text-decoration: underline; text-decoration: underline;
} }
#child-view{ #child-view{
flex: 1;
justify-content: 'space-evenly'; justify-content: 'space-evenly';
align-items: 'center';
flex-wrap: 'wrap';
} }
`; `;

View File

@ -23,7 +23,7 @@ export const audioPlayer = new NodeMpv(
function Player(): ReactElement { function Player(): ReactElement {
const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist } = useContext(playerContext); const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist } = useContext(playerContext);
const [volume, setVolume] = useState(55); const [volume, setVolume] = useState<number>(parseFloat(localStorage.getItem("volume") ?? "55"));
const [totalDuration, setTotalDuration] = useState(0); const [totalDuration, setTotalDuration] = useState(0);
const [shuffle, setShuffle] = useState<boolean>(false); const [shuffle, setShuffle] = useState<boolean>(false);
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]); const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
@ -34,6 +34,9 @@ function Player(): ReactElement {
sliderMoved: (value) => { sliderMoved: (value) => {
setVolume(value); setVolume(value);
}, },
sliderReleased: () => {
localStorage.setItem("volume", volume.toString());
}
}, },
[] []
); );
@ -47,10 +50,8 @@ function Player(): ReactElement {
if (!playerRunning) { if (!playerRunning) {
await audioPlayer.start(); await audioPlayer.start();
} }
await audioPlayer.volume(55);
} catch (error) { } catch (error) {
console.error("Failed to start audio player"); console.error("Failed to start audio player", error);
console.error(error);
} }
})(); })();
@ -157,9 +158,9 @@ function Player(): ReactElement {
} }
} }
const artistsNames = currentTrack?.artists.map((x) => x.name); const artistsNames = currentTrack?.artists?.map((x) => x.name);
return ( return (
<GridView style="flex: 1; max-height: 100px;"> <GridView enabled={!!currentTrack} style="flex: 1; max-height: 100px;">
<GridRow> <GridRow>
<GridColumn width={2}> <GridColumn width={2}>
<Text ref={titleRef} wordWrap> <Text ref={titleRef} wordWrap>

View File

@ -1,11 +1,14 @@
import React, { FC, useContext, useEffect, useState } from "react"; import React, { FC, useContext, useEffect, useState } from "react";
import { BoxView, Button, ScrollArea, Text, View } from "@nodegui/react-nodegui"; import { BoxView, Button, GridView, ScrollArea, Text, View } from "@nodegui/react-nodegui";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
import { useLocation, useParams } from "react-router"; import { useLocation, useParams } from "react-router";
import { Direction, QAbstractButtonSignals } from "@nodegui/nodegui"; import { Direction, QAbstractButtonSignals, QIcon } from "@nodegui/nodegui";
import { WidgetEventListeners } from "@nodegui/react-nodegui/dist/components/View/RNView"; import { WidgetEventListeners } from "@nodegui/react-nodegui/dist/components/View/RNView";
import authContext from "../context/authContext"; import authContext from "../context/authContext";
import playerContext from "../context/playerContext"; import playerContext from "../context/playerContext";
import IconButton from "./shared/IconButton";
import { heartRegular, play, stop } from "../icons";
import { audioPlayer } from "./Player";
export interface PlaylistTrackRes { export interface PlaylistTrackRes {
name: string; name: string;
@ -21,20 +24,9 @@ const PlaylistView: FC<PlaylistViewProps> = () => {
const { isLoggedIn, access_token } = useContext(authContext); const { isLoggedIn, access_token } = useContext(authContext);
const { spotifyApi, setCurrentTrack, currentPlaylist, currentTrack, setCurrentPlaylist } = useContext(playerContext); const { spotifyApi, setCurrentTrack, currentPlaylist, currentTrack, setCurrentPlaylist } = useContext(playerContext);
const params = useParams<{ id: string }>(); const params = useParams<{ id: string }>();
const location = useLocation<{ name: string, thumbnail: string }>(); const location = useLocation<{ name: string; thumbnail: string }>();
const [tracks, setTracks] = useState<SpotifyApi.PlaylistTrackObject[]>([]); const [tracks, setTracks] = useState<SpotifyApi.PlaylistTrackObject[]>([]);
const trackClickHandler = async (track: SpotifyApi.TrackObjectFull) => {
try {
setCurrentTrack(track);
if (currentPlaylist?.id !== params.id) {
setCurrentPlaylist({...params, ...location.state, tracks});
}
} catch (error) {
console.error("Failed to resolve track's youtube url: ", error);
}
};
useEffect(() => { useEffect(() => {
if (isLoggedIn) { if (isLoggedIn) {
(async () => { (async () => {
@ -49,9 +41,34 @@ const PlaylistView: FC<PlaylistViewProps> = () => {
} }
}, []); }, []);
const handlePlaylistPlayPause = () => {
if (currentPlaylist?.id !== params.id) {
setCurrentPlaylist({ ...params, ...location.state, tracks });
setCurrentTrack(tracks[0].track);
} else {
audioPlayer.stop().catch((error) => console.error("Failed to stop audio player: ", error));
setCurrentTrack(undefined);
setCurrentPlaylist(undefined);
}
};
const trackClickHandler = async (track: SpotifyApi.TrackObjectFull) => {
try {
setCurrentTrack(track);
} catch (error) {
console.error("Failed to resolve track's youtube url: ", error);
}
};
return ( return (
<BoxView direction={Direction.TopToBottom}> <View style={`flex-direction: 'column'; flex-grow: 1;`}>
<BackButton /> <View style={`justify-content: 'space-between'; padding-bottom: 10px; padding-left: 10px;`}>
<BackButton />
<View style={`height: 50px; justify-content: 'space-between'; width: 100px; padding-right: 20px;`}>
<IconButton icon={new QIcon(heartRegular)} />
<IconButton style={`background-color: #00be5f; color: white;`} on={{ clicked: handlePlaylistPlayPause }} icon={new QIcon(currentPlaylist?.id === params.id ? stop : play)} />
</View>
</View>
<Text>{`<center><h2>${location.state.name[0].toUpperCase()}${location.state.name.slice(1)}</h2></center>`}</Text> <Text>{`<center><h2>${location.state.name[0].toUpperCase()}${location.state.name.slice(1)}</h2></center>`}</Text>
<ScrollArea style={`flex-grow: 1; border: none;`}> <ScrollArea style={`flex-grow: 1; border: none;`}>
<View style={`flex-direction:column;`}> <View style={`flex-direction:column;`}>
@ -70,7 +87,7 @@ const PlaylistView: FC<PlaylistViewProps> = () => {
})} })}
</View> </View>
</ScrollArea> </ScrollArea>
</BoxView> </View>
); );
}; };