From 9571d12eb4adb9c4cecd74b2d11333685fc1164b Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Mon, 23 Dec 2024 21:57:14 +0100 Subject: [PATCH 1/9] commit test --- src/models/settings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/models/settings.ts b/src/models/settings.ts index 891b557..8d9c59f 100644 --- a/src/models/settings.ts +++ b/src/models/settings.ts @@ -23,6 +23,7 @@ export interface Settings { isOnLeft: boolean; isAlwaysShowTrackInfo: boolean; isAlwaysShowSongProgress: boolean; + showTrackInfoTemporarilyInSeconds: number; barThickness: number; barColor: string; size: number; @@ -55,6 +56,7 @@ export const DEFAULT_SETTINGS: Settings = { isAlwaysOnTop: true, isVisibleInTaskbar: true, isAlwaysShowTrackInfo: false, + showTrackInfoTemporarilyInSeconds: 0, isAlwaysShowSongProgress: false, barThickness: 2, barColor: '#74C999', From 20c789807660bd4afaa480c344433973fffced65 Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Mon, 23 Dec 2024 21:58:59 +0100 Subject: [PATCH 2/9] added option to temporarily show track info --- src/renderer/app/cover/index.tsx | 38 ++++++++++++++++--- .../windows/settings/window-settings.tsx | 15 ++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/renderer/app/cover/index.tsx b/src/renderer/app/cover/index.tsx index 8e64821..4449ea5 100644 --- a/src/renderer/app/cover/index.tsx +++ b/src/renderer/app/cover/index.tsx @@ -52,6 +52,7 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza barColor, isAlwaysShowSongProgress, isAlwaysShowTrackInfo, + showTrackInfoTemporarilyInSeconds, isOnLeft, size, skipSongDelay, @@ -64,6 +65,7 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza const [currentSongId, setCurrentSongId] = useState(''); const [shouldShowTrackInfo, setShouldShowTrackInfo] = useState(isAlwaysShowTrackInfo); + const [trackInfoTimer, setTrackInfoTimer] = useState(null); const [errorToDisplay, setErrorToDisplay] = useState(''); useEffect(() => setErrorToDisplay(message), [message]); @@ -108,14 +110,40 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza useEffect(() => { (async () => { - if (state.isPlaying && state.id !== currentSongId) { - setCurrentSongId(state.id); - console.log(`New song '${songTitle}' by '${artist}.`); - await refreshTrackLiked(); + if (state.isPlaying) { + if (state.id !== currentSongId) { + setCurrentSongId(state.id); + console.log(`New song '${songTitle}' by '${artist}.`); + await refreshTrackLiked(); + } + showTrackInfoTemporarily(); } })(); }, [artist, currentSongId, refreshTrackLiked, songTitle, state.id, state.isPlaying]); + const showTrackInfoTemporarily = () => { + if (!isAlwaysShowTrackInfo && showTrackInfoTemporarilyInSeconds) { + setShouldShowTrackInfo(true); + + const timer = setTimeout(() => { + if (!document.getElementById('visible-ui')?.matches(':hover')) { + setShouldShowTrackInfo(false); + } + setTrackInfoTimer(null); + }, showTrackInfoTemporarilyInSeconds * ONE_SECOND_IN_MS); + + setTrackInfoTimer(timer); + } + }; + + useEffect(() => { + return () => { + if (trackInfoTimer) { + clearTimeout(trackInfoTimer); + } + }; + }, [trackInfoTimer]); + const keepAlive = useCallback(async (): Promise => { if (state.isPlaying || state.userProfile?.accountType !== AccountType.Premium) { return; @@ -212,7 +240,7 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza
!isAlwaysShowTrackInfo && setShouldShowTrackInfo(true)} - onMouseLeave={() => !isAlwaysShowTrackInfo && setShouldShowTrackInfo(false)}> + onMouseLeave={() => !isAlwaysShowTrackInfo && !trackInfoTimer && setShouldShowTrackInfo(false)}> { const barThicknessWatch = watch('barThickness'); const cornerRadiusWatch = watch('cornerRadius'); + const tempTrackInfoWatch = watch('showTrackInfoTemporarilyInSeconds'); return ( @@ -45,6 +46,20 @@ export const WindowSettings: FunctionComponent = () => { {...register('isAlwaysShowTrackInfo')} /> + + + Date: Mon, 23 Dec 2024 23:29:29 +0100 Subject: [PATCH 3/9] fix focus when track info appears --- src/renderer/app/cover/index.tsx | 47 ++++++++++++++--------- src/renderer/components/window-portal.tsx | 2 +- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/renderer/app/cover/index.tsx b/src/renderer/app/cover/index.tsx index 4449ea5..4977f67 100644 --- a/src/renderer/app/cover/index.tsx +++ b/src/renderer/app/cover/index.tsx @@ -116,26 +116,32 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza console.log(`New song '${songTitle}' by '${artist}.`); await refreshTrackLiked(); } - showTrackInfoTemporarily(); - } - })(); - }, [artist, currentSongId, refreshTrackLiked, songTitle, state.id, state.isPlaying]); - const showTrackInfoTemporarily = () => { - if (!isAlwaysShowTrackInfo && showTrackInfoTemporarilyInSeconds) { - setShouldShowTrackInfo(true); + if (!isAlwaysShowTrackInfo && showTrackInfoTemporarilyInSeconds) { + setShouldShowTrackInfo(true); + + const timer = setTimeout(() => { + if (!document.getElementById('visible-ui')?.matches(':hover')) { + setShouldShowTrackInfo(false); + } + setTrackInfoTimer(null); + }, showTrackInfoTemporarilyInSeconds * ONE_SECOND_IN_MS); - const timer = setTimeout(() => { - if (!document.getElementById('visible-ui')?.matches(':hover')) { - setShouldShowTrackInfo(false); + setTrackInfoTimer(timer); } - setTrackInfoTimer(null); - }, showTrackInfoTemporarilyInSeconds * ONE_SECOND_IN_MS); - - setTrackInfoTimer(timer); - } - }; - + } + })(); + }, [ + artist, + currentSongId, + refreshTrackLiked, + songTitle, + state.id, + state.isPlaying, + isAlwaysShowTrackInfo, + showTrackInfoTemporarilyInSeconds, + ]); + useEffect(() => { return () => { if (trackInfoTimer) { @@ -217,7 +223,10 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza }, [handlePlaybackChanged, trackInfoRefreshTimeInSeconds]); useEffect(() => { - const refreshTrackLikedIntervalId = setInterval(refreshTrackLiked, 2 * trackInfoRefreshTimeInSeconds * ONE_SECOND_IN_MS); + const refreshTrackLikedIntervalId = setInterval( + refreshTrackLiked, + 2 * trackInfoRefreshTimeInSeconds * ONE_SECOND_IN_MS + ); return () => { if (refreshTrackLikedIntervalId) { clearInterval(refreshTrackLikedIntervalId); @@ -249,7 +258,7 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza {state.id ? ( <> {shouldShowTrackInfo && ( - + )} diff --git a/src/renderer/components/window-portal.tsx b/src/renderer/components/window-portal.tsx index 02b692e..bb901ee 100644 --- a/src/renderer/components/window-portal.tsx +++ b/src/renderer/components/window-portal.tsx @@ -63,7 +63,7 @@ interface Props { url?: string; name: string; title?: string; - features?: { width?: number; height?: number; isFullscreen?: boolean }; + features?: { width?: number; height?: number; isFullscreen?: boolean; focusable?: boolean }; children: ReactNode; onOpen?: (window: Window) => void; onUnload?: () => void; From eb80bad417a4f32c00cb67864e7f1ded14461b77 Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Wed, 3 Dec 2025 19:22:31 +0000 Subject: [PATCH 4/9] hide temporary track info on pause --- src/renderer/app/cover/index.tsx | 17 ++++++++--------- .../windows/settings/window-settings.tsx | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/renderer/app/cover/index.tsx b/src/renderer/app/cover/index.tsx index 4977f67..d54568e 100644 --- a/src/renderer/app/cover/index.tsx +++ b/src/renderer/app/cover/index.tsx @@ -115,10 +115,9 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza setCurrentSongId(state.id); console.log(`New song '${songTitle}' by '${artist}.`); await refreshTrackLiked(); - } - - if (!isAlwaysShowTrackInfo && showTrackInfoTemporarilyInSeconds) { + } else if (!isAlwaysShowTrackInfo && showTrackInfoTemporarilyInSeconds) { setShouldShowTrackInfo(true); + console.log('Showing track info temporarily.'); const timer = setTimeout(() => { if (!document.getElementById('visible-ui')?.matches(':hover')) { @@ -143,12 +142,12 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza ]); useEffect(() => { - return () => { - if (trackInfoTimer) { - clearTimeout(trackInfoTimer); - } - }; - }, [trackInfoTimer]); + if (!state.isPlaying && trackInfoTimer) { + clearTimeout(trackInfoTimer); + setTrackInfoTimer(null); + setShouldShowTrackInfo(false); + } + }, [state.isPlaying, trackInfoTimer]); const keepAlive = useCallback(async (): Promise => { if (state.isPlaying || state.userProfile?.accountType !== AccountType.Premium) { diff --git a/src/renderer/windows/settings/window-settings.tsx b/src/renderer/windows/settings/window-settings.tsx index 0419a74..0693e8f 100644 --- a/src/renderer/windows/settings/window-settings.tsx +++ b/src/renderer/windows/settings/window-settings.tsx @@ -52,7 +52,7 @@ export const WindowSettings: FunctionComponent = () => { Date: Wed, 3 Dec 2025 19:40:12 +0000 Subject: [PATCH 5/9] added local volume to avoid api throttling --- src/renderer/app/cover/index.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/renderer/app/cover/index.tsx b/src/renderer/app/cover/index.tsx index d54568e..6431e80 100644 --- a/src/renderer/app/cover/index.tsx +++ b/src/renderer/app/cover/index.tsx @@ -165,6 +165,11 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza } }, [state.isPlaying, state.progress, state.userProfile?.accountType]); + const [localVolume, setLocalVolume] = useState(state.volume); + useEffect(() => { + setLocalVolume(state.volume); + }, [state.volume]); + const changeVolume = useCallback( (newVolume: number) => { try { @@ -190,18 +195,17 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza const onMouseWheel = useCallback( async ({ deltaY }: WheelEvent): Promise => { - const direction = Math.sign(deltaY); - const newVolume = clamp(state.volume - direction * volumeIncrement, 0, 100); + const direction = Math.sign(-deltaY); + console.log(`Mouse wheel direction: ${direction}`); + const newVolume = clamp(localVolume + volumeIncrement * direction, 0, 100); + setLocalVolume(newVolume); try { - // TODO use a state variable to buffer the volume change - if (newVolume !== state.volume) { - changeVolume(newVolume); - } + changeVolume(newVolume); } catch (error) { throw new Error(`Update volume error: ${error}`); } }, - [changeVolume, state.volume, volumeIncrement] + [localVolume, volumeIncrement, changeVolume] ); useEffect(() => { From d712dda7c63d9798f583c60c5fc59094c3643c0f Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Wed, 3 Dec 2025 19:43:32 +0000 Subject: [PATCH 6/9] Double click functionality template --- src/renderer/app/cover/index.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/renderer/app/cover/index.tsx b/src/renderer/app/cover/index.tsx index 6431e80..56ab04e 100644 --- a/src/renderer/app/cover/index.tsx +++ b/src/renderer/app/cover/index.tsx @@ -208,13 +208,28 @@ export const Cover: FunctionComponent = ({ settings, message, onVisualiza [localVolume, volumeIncrement, changeVolume] ); + const ondblClick = useCallback((): void => { + console.log('Double clicked'); + // Add functionality here + // Tried opening spotify app but couldn't get it to work + }, []); + useEffect(() => { - document.getElementById('visible-ui').addEventListener('mousewheel', onMouseWheel); + const el = document.getElementById('visible-ui'); + el.addEventListener('mousewheel', onMouseWheel); return () => { - document.getElementById('visible-ui').removeEventListener('mousewheel', onMouseWheel); + el.removeEventListener('mousewheel', onMouseWheel); }; }, [onMouseWheel]); + useEffect(() => { + const el = document.getElementById('visible-ui'); + el.addEventListener('dblclick', ondblClick); + return () => { + el.removeEventListener('dblclick', ondblClick); + }; + }, [ondblClick]); + useEffect(() => { const listeningToIntervalId = setInterval(handlePlaybackChanged, trackInfoRefreshTimeInSeconds * ONE_SECOND_IN_MS); From ad9d9ee6e90cf8200bde7bdc37ed56b644f5f7a0 Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Wed, 15 Apr 2026 18:29:07 +0100 Subject: [PATCH 7/9] OAuth 2 fixes and updates to match new library endpoints --- src/main/auth.ts | 8 ++++---- src/renderer/api/spotify-api.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/auth.ts b/src/main/auth.ts index f41f012..b98f423 100644 --- a/src/main/auth.ts +++ b/src/main/auth.ts @@ -49,7 +49,7 @@ export const getAuthUrl = (): string => { const scopes = AUTH_SCOPES.join('%20'); const authUrl = - `${AUTH_URL}?response_type=code&client_id=${AUTH_CLIENT_ID}&redirect_uri=http://localhost:${AUTH_PORT}&` + + `${AUTH_URL}?response_type=code&client_id=${AUTH_CLIENT_ID}&redirect_uri=http://127.0.0.1:${AUTH_PORT}&` + `scope=${scopes}&state=${codeState}&code_challenge=${codeChallenge}&code_challenge_method=S256`; return authUrl; @@ -104,7 +104,7 @@ export const refreshAccessToken = async (refreshToken: string): Promise => data = null; } else if (!scopesMatch(data.scope)) { console.warn( - `Authorization scopes mismatch\n Expected: ${AUTH_SCOPES.join(' ')}\n Token has: '${data.scope}` + `Authorization scopes mismatch\n Expected: ${AUTH_SCOPES.join(' ')}\n Token has: '${data.scope}'` ); data = null; } else { @@ -120,7 +120,7 @@ const retrieveAccessToken = async (verifier: string, code: string): Promise { }; const handleServerResponse = async (request: http.IncomingMessage, response: http.ServerResponse): Promise => { - const urlObj = new URL(`http://localhost:${AUTH_PORT}/${request.url}`); + const urlObj = new URL(`http://127.0.0.1:${AUTH_PORT}/${request.url}`); const queryState = urlObj.searchParams.get('state'); try { diff --git a/src/renderer/api/spotify-api.ts b/src/renderer/api/spotify-api.ts index c32dc50..40f7e8d 100644 --- a/src/renderer/api/spotify-api.ts +++ b/src/renderer/api/spotify-api.ts @@ -119,13 +119,13 @@ class SpotifyApi { async like(isLiked: boolean, trackId: string): Promise { const verb = isLiked ? 'DELETE' : 'PUT'; - await this.fetch(`/me/tracks?ids=${trackId}`, { + await this.fetch(`/me/library?uris=spotify:track:${trackId}`, { method: verb, }); } async isTrackLiked(trackId: string): Promise { - const likedResponse: Array = await this.fetch(`/me/tracks/contains?ids=${trackId}`, { + const likedResponse: Array = await this.fetch(`/me/library/contains?uris=spotify:track:${trackId}`, { method: 'GET', }); From c50be9253bdc7a82d0d3a906e2fa23b1bc4ecf85 Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Wed, 15 Apr 2026 18:29:45 +0100 Subject: [PATCH 8/9] App logout on sleep fix --- src/renderer/api/spotify-api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/api/spotify-api.ts b/src/renderer/api/spotify-api.ts index 40f7e8d..ddb8872 100644 --- a/src/renderer/api/spotify-api.ts +++ b/src/renderer/api/spotify-api.ts @@ -74,8 +74,8 @@ class SpotifyApi { private refreshToken: string; async updateTokens(data: AuthData): Promise { - this.accessToken = data?.access_token; - this.refreshToken = data?.refresh_token; + this.accessToken = data?.access_token ?? this.accessToken; + this.refreshToken = data?.refresh_token ?? this.refreshToken; if (!this.accessToken) { return null; From 26cc1564d013b84e94caf0a097cbe6961777fd0b Mon Sep 17 00:00:00 2001 From: EdwardM222 Date: Wed, 15 Apr 2026 18:30:07 +0100 Subject: [PATCH 9/9] minor padding adjustment for song title box --- src/renderer/windows/track-info/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/renderer/windows/track-info/index.tsx b/src/renderer/windows/track-info/index.tsx index 5e8256f..0e56d72 100644 --- a/src/renderer/windows/track-info/index.tsx +++ b/src/renderer/windows/track-info/index.tsx @@ -27,11 +27,13 @@ const TrackInfoWrapper = styled.div` const Track = styled.div` font-weight: bold; + padding: 0 0.25rem; `; const Artist = styled(Track)` font-weight: normal; font-size: 90%; + padding: 0 0.25rem; `; const MessageWrapper = styled.div` @@ -76,16 +78,12 @@ export const TrackInfo: FunctionComponent = ({ track, artist, me {track} {artist} @@ -93,8 +91,6 @@ export const TrackInfo: FunctionComponent = ({ track, artist, me style={{ display: message ? 'flex' : 'none', justifyContent: isOnLeft ? 'start' : 'end', - paddingLeft: !isOnLeft && '0.5rem', - paddingRight: isOnLeft && '0.5rem', }}> {message}