- restart the app to complete login
- no volume caching
- cached image memory leak
This commit is contained in:
KRTirtho 2021-03-30 22:14:15 +06:00
parent b0c6781df4
commit 4b513f91d8
5 changed files with 64 additions and 52 deletions

View File

@ -22,6 +22,7 @@ export enum LocalStorageKeys {
credentials = "credentials",
refresh_token = "refresh_token",
preferences = "user-preferences",
volume = "volume"
}
export interface Credentials {
@ -47,6 +48,7 @@ const queryClient = new QueryClient({
const initialPreferences: PreferencesContextProperties = {
playlistImages: false,
};
const initialCredentials: Credentials = { clientId: "", clientSecret: "" };
//* Application start
function RootApp() {
@ -87,7 +89,13 @@ function RootApp() {
const cachedCredentials = localStorage.getItem(LocalStorageKeys.credentials);
// state
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [credentials, setCredentials] = useState<Credentials>({ clientId: "", clientSecret: "" });
const [credentials, setCredentials] = useState<Credentials>(() => {
if (cachedCredentials) {
return JSON.parse(cachedCredentials);
}
return initialCredentials;
});
const [preferences, setPreferences] = useState<PreferencesContextProperties>(() => {
if (cachedPreferences) {
return JSON.parse(cachedPreferences);
@ -98,28 +106,15 @@ function RootApp() {
const [currentPlaylist, setCurrentPlaylist] = useState<CurrentPlaylist>();
useEffect(() => {
setIsLoggedIn(!!cachedCredentials);
const parsedCredentials: Credentials = JSON.parse(cachedCredentials ?? "{}");
setIsLoggedIn(!!(parsedCredentials.clientId && parsedCredentials.clientSecret));
}, []);
// just saves the preferences
useEffect(() => {
localStorage.setItem(LocalStorageKeys.preferences, JSON.stringify(preferences));
}, [preferences]);
useEffect(() => {
const onWindowClose = () => {
if (audioPlayer.isRunning()) {
audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e));
}
};
windowRef.current?.addEventListener(WidgetEventTypes.Close, onWindowClose);
return () => {
windowRef.current?.removeEventListener(WidgetEventTypes.Close, onWindowClose);
};
});
// for user code login
useEffect(() => {
if (isLoggedIn && credentials && !localStorage.getItem(LocalStorageKeys.refresh_token)) {
// saving changed credentials to storage
localStorage.setItem(LocalStorageKeys.credentials, JSON.stringify(credentials));
if (credentials.clientId && credentials.clientSecret && !localStorage.getItem(LocalStorageKeys.refresh_token)) {
const app = express();
app.use(express.json());
@ -130,6 +125,7 @@ function RootApp() {
const { body: authRes } = await spotifyApi.authorizationCodeGrant(req.query.code);
setAccess_token(authRes.access_token);
localStorage.setItem(LocalStorageKeys.refresh_token, authRes.refresh_token);
setIsLoggedIn(true);
return res.end();
} catch (error) {
console.error("Failed to fullfil code grant flow: ", error);
@ -148,18 +144,31 @@ function RootApp() {
server.close(() => console.log("Closed server"));
};
}
}, [isLoggedIn, credentials]);
}, [credentials]);
// just saves the preferences
useEffect(() => {
if (cachedCredentials) {
setCredentials(JSON.parse(cachedCredentials));
localStorage.setItem(LocalStorageKeys.preferences, JSON.stringify(preferences));
}, [preferences]);
// window event listeners
useEffect(() => {
const onWindowClose = () => {
if (audioPlayer.isRunning()) {
audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e));
}
}, [isLoggedIn]);
};
windowRef.current?.addEventListener(WidgetEventTypes.Close, onWindowClose);
return () => {
windowRef.current?.removeEventListener(WidgetEventTypes.Close, onWindowClose);
};
});
return (
<Window ref={windowRef} on={windowEvents} windowState={WindowState.WindowMaximized} windowIcon={winIcon} windowTitle="Spotube" minSize={minSize}>
<MemoryRouter>
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token, setAccess_token, ...credentials }}>
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token, setAccess_token, ...credentials, setCredentials }}>
<preferencesContext.Provider value={{ ...preferences, setPreferences }}>
<playerContext.Provider value={{ currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}>
<QueryClientProvider client={queryClient}>

View File

@ -1,10 +1,9 @@
import React, { useContext, useState } from "react";
import { LineEdit, Text, Button, View } from "@nodegui/react-nodegui";
import authContext from "../context/authContext";
import { LocalStorageKeys, Credentials } from "../app";
function Login() {
const { setIsLoggedIn } = useContext(authContext);
const { setCredentials: setGlobalCredentials } = useContext(authContext);
const [credentials, setCredentials] = useState({
clientId: "",
clientSecret: "",
@ -54,14 +53,7 @@ function Login() {
<Button
on={{
clicked: () => {
localStorage.setItem(
LocalStorageKeys.credentials,
JSON.stringify({
clientId: credentials.clientId,
clientSecret: credentials.clientSecret,
} as Credentials)
);
setIsLoggedIn(true);
setGlobalCredentials(credentials);
},
}}
text="Add"

View File

@ -11,6 +11,7 @@ import IconButton from "./shared/IconButton";
import showError from "../helpers/showError";
import useTrackReaction from "../hooks/useTrackReaction";
import ManualLyricDialog from "./ManualLyricDialog";
import { LocalStorageKeys } from "../app";
export const audioPlayer = new NodeMpv(
{
@ -26,8 +27,9 @@ export const audioPlayer = new NodeMpv(
function Player(): ReactElement {
const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist } = useContext(playerContext);
const { reactToTrack, isFavorite } = useTrackReaction();
const cachedVolume = localStorage.getItem(LocalStorageKeys.volume);
const [isPaused, setIsPaused] = useState(true);
const [volume, setVolume] = useState<number>(55);
const [volume, setVolume] = useState<number>(() => (cachedVolume ? parseFloat(cachedVolume) : 55));
const [totalDuration, setTotalDuration] = useState<number>(0);
const [shuffle, setShuffle] = useState<boolean>(false);
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
@ -40,10 +42,10 @@ function Player(): ReactElement {
setVolume(value);
},
sliderReleased: () => {
localStorage.setItem("volume", volume.toString());
localStorage.setItem(LocalStorageKeys.volume, volume.toString());
},
},
[]
[volume]
);
const playerRunning = audioPlayer.isRunning();
const titleRef = useRef<QLabel>();
@ -223,11 +225,7 @@ function Player(): ReactElement {
}}
icon={new QIcon(isFavorite(currentTrack?.id ?? "") ? heart : heartRegular)}
/>
<IconButton
style={openLyrics ? "background-color: green;": ""}
icon={new QIcon(musicNode)}
on={{ clicked: () => currentTrack && setOpenLyrics(!openLyrics) }}
/>
<IconButton style={openLyrics ? "background-color: green;" : ""} icon={new QIcon(musicNode)} on={{ clicked: () => currentTrack && setOpenLyrics(!openLyrics) }} />
<Slider minSize={{ height: 20, width: 80 }} maxSize={{ height: 20, width: 100 }} hasTracking sliderPosition={volume} on={volumeHandler} orientation={Orientation.Horizontal} />
</BoxView>
</GridColumn>

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { Image, Text, View } from "@nodegui/react-nodegui";
import { QLabel } from "@nodegui/nodegui";
import { Text, View } from "@nodegui/react-nodegui";
import { QLabel, QPixmap } from "@nodegui/nodegui";
import { ImageProps } from "@nodegui/react-nodegui/dist/components/Image/RNImage";
import { getCachedImageBuffer } from "../../helpers/getCachedImageBuffer";
import showError from "../../helpers/showError";
@ -10,14 +10,15 @@ interface CachedImageProps extends Omit<ImageProps, "buffer"> {
alt?: string;
}
function CachedImage({ src, alt, ...props }: CachedImageProps) {
const imgRef = useRef<QLabel>();
function CachedImage({ src, alt, size, maxSize, ...props }: CachedImageProps) {
const labelRef = useRef<QLabel>();
const [imageBuffer, setImageBuffer] = useState<Buffer>();
const [imageProcessError, setImageProcessError] = useState<boolean>(false);
const pixmap = new QPixmap();
useEffect(() => {
if (imageBuffer === undefined) {
getCachedImageBuffer(src, props.maxSize ?? props.size)
getCachedImageBuffer(src, maxSize ?? size)
.then((buffer) => setImageBuffer(buffer))
.catch((error) => {
setImageProcessError(false);
@ -26,13 +27,22 @@ function CachedImage({ src, alt, ...props }: CachedImageProps) {
}
return () => {
imgRef.current?.close();
labelRef.current?.close();
};
}, []);
useEffect(() => {
if (imageBuffer) {
pixmap.loadFromData(imageBuffer);
pixmap.scaled((size ?? maxSize)?.height ?? 100, (size ?? maxSize)?.width ?? 100);
labelRef.current?.setPixmap(pixmap);
}
}, [imageBuffer]);
return !imageProcessError && imageBuffer ? (
<Image ref={imgRef} buffer={imageBuffer} {...props} />
<Text ref={labelRef} {...props}/>
) : alt ? (
<View style={`padding: ${((props.maxSize ?? props.size)?.height || 10) / 2.5}px ${((props.maxSize ?? props.size)?.width || 10) / 2.5}px;`}>
<View style={`padding: ${((maxSize ?? size)?.height || 10) / 2.5}px ${((maxSize ?? size)?.width || 10) / 2.5}px;`}>
<Text>{alt}</Text>
</View>
) : (

View File

@ -1,4 +1,5 @@
import React, { Dispatch, SetStateAction } from "react";
import { Credentials } from "../app";
export interface AuthContext {
isLoggedIn: boolean;
@ -6,6 +7,7 @@ export interface AuthContext {
clientId: string;
clientSecret: string;
access_token: string;
setCredentials: Dispatch<SetStateAction<Credentials>>
setAccess_token: Dispatch<SetStateAction<string>>;
}
@ -15,6 +17,7 @@ const authContext = React.createContext<AuthContext>({
access_token: "",
clientId: "",
clientSecret: "",
setCredentials(){},
setAccess_token() {},
});