From 44111fc4e1eb0e0278d86215107a4d1492cc4a17 Mon Sep 17 00:00:00 2001 From: Thang Pham Date: Wed, 18 Feb 2026 16:44:43 +0700 Subject: [PATCH 1/2] feat: remember Tracks context in Currently Playing page Add support for preserving Tracks context (Top Tracks, Recently Played, Liked Tracks) when navigating to the Currently Playing page. Since Spotify's native API only recognizes Playlist, Album, Artist, and Show as playback contexts, custom Tracks contexts were previously lost when viewing the Currently Playing page. This change tracks the currently playing Tracks context in UIState and displays it appropriately. Changes: - Add `currently_playing_tracks_id` field to `UIState` to store the active Tracks context - Modify `ContextPageType::CurrentPlaying` to accept optional `TracksId`, indicating the currently playing Tracks context - Update playback handlers to set/clear `currently_playing_tracks_id`: - Set when playing from Tracks contexts (Top/Recent/Liked Tracks) - Clear when playing from other contexts (Playlist, Album, Show, etc.) This allows users to see and navigate the Tracks context list when viewing the Currently Playing page after playing from these contexts. --- spotify_player/src/client/handlers.rs | 9 ++++++++- spotify_player/src/event/mod.rs | 9 ++++++++- spotify_player/src/event/window.rs | 21 +++++++++++++++++++++ spotify_player/src/state/ui/mod.rs | 5 +++++ spotify_player/src/state/ui/page.rs | 6 +++--- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/spotify_player/src/client/handlers.rs b/spotify_player/src/client/handlers.rs index 3229b887..4051f9f1 100644 --- a/spotify_player/src/client/handlers.rs +++ b/spotify_player/src/client/handlers.rs @@ -101,7 +101,14 @@ fn handle_page_change_event( } => { let expected_id = match context_page_type { ContextPageType::Browsing(context_id) => Some(context_id.clone()), - ContextPageType::CurrentPlaying => state.player.read().playing_context_id(), + ContextPageType::CurrentPlaying { tracks_id } => { + // If we have a stored tracks_id, use it; otherwise get from player + if let Some(tracks_id) = tracks_id { + Some(ContextId::Tracks(tracks_id.clone())) + } else { + state.player.read().playing_context_id() + } + } }; let new_id = if *id == expected_id { diff --git a/spotify_player/src/event/mod.rs b/spotify_player/src/event/mod.rs index b2727fb0..6ffd79ba 100644 --- a/spotify_player/src/event/mod.rs +++ b/spotify_player/src/event/mod.rs @@ -640,9 +640,12 @@ fn handle_global_command( } } Command::CurrentlyPlayingContextPage => { + // Use the currently playing Tracks context if available + let tracks_id = ui.currently_playing_tracks_id.clone(); + ui.new_page(PageState::Context { id: None, - context_page_type: ContextPageType::CurrentPlaying, + context_page_type: ContextPageType::CurrentPlaying { tracks_id }, state: None, }); } @@ -732,6 +735,10 @@ fn handle_global_command( // for track link, play the song "track" => { let id = TrackId::from_id(id)?.into_static(); + + // Clear Tracks context when playing from clipboard link + ui.currently_playing_tracks_id = None; + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::URIs(vec![id.into()], None), None, diff --git a/spotify_player/src/event/window.rs b/spotify_player/src/event/window.rs index 3046feac..eb7656bf 100644 --- a/spotify_player/src/event/window.rs +++ b/spotify_player/src/event/window.rs @@ -309,6 +309,16 @@ fn handle_command_for_track_table_window( filtered_tracks[id].id.uri() }; + // Update currently_playing_tracks_id based on the context + match context_id { + Some(ContextId::Tracks(ref tracks_id)) => { + ui.currently_playing_tracks_id = Some(tracks_id.clone()); + } + _ => { + ui.currently_playing_tracks_id = None; + } + } + let base_playback = match context_id { None | Some(ContextId::Tracks(_)) => { Playback::URIs(tracks.iter().map(|t| t.id.clone().into()).collect(), None) @@ -381,6 +391,10 @@ pub fn handle_command_for_track_list_window( // This is different from the track table, which handles // `ChooseSelected` by starting a `URIs` playback // containing all the tracks in the table. + + // Track lists are used for search results, so clear the Tracks context + ui.currently_playing_tracks_id = None; + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::URIs(vec![tracks[id].id.clone().into()], None), None, @@ -587,6 +601,9 @@ pub fn handle_command_for_episode_list_window( } match command { Command::ChooseSelected => { + // Episodes don't have a Tracks context, so clear it + ui.currently_playing_tracks_id = None; + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::URIs(vec![episodes[id].id.clone().into()], None), None, @@ -629,6 +646,10 @@ fn handle_command_for_episode_table_window( match command { Command::ChooseSelected => { let uri = episodes[id].id.uri(); + + // Show context doesn't have a Tracks context, so clear it + ui.currently_playing_tracks_id = None; + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::Context( ContextId::Show(show_id.clone_static()), diff --git a/spotify_player/src/state/ui/mod.rs b/spotify_player/src/state/ui/mod.rs index f44a6840..a510471d 100644 --- a/spotify_player/src/state/ui/mod.rs +++ b/spotify_player/src/state/ui/mod.rs @@ -42,6 +42,9 @@ pub struct UIState { /// Count prefix for vim-style navigation (e.g., 5j, 10k) pub count_prefix: Option, + /// The currently playing Tracks context + pub currently_playing_tracks_id: Option, + #[cfg(feature = "image")] pub last_cover_image_render_info: ImageRenderInfo, } @@ -122,6 +125,8 @@ impl Default for UIState { count_prefix: None, + currently_playing_tracks_id: None, + #[cfg(feature = "image")] last_cover_image_render_info: ImageRenderInfo::default(), } diff --git a/spotify_player/src/state/ui/page.rs b/spotify_player/src/state/ui/page.rs index cd8a401c..78cdfc8f 100644 --- a/spotify_player/src/state/ui/page.rs +++ b/spotify_player/src/state/ui/page.rs @@ -1,5 +1,5 @@ use crate::{ - state::model::{Category, ContextId}, + state::model::{Category, ContextId, TracksId}, ui::single_line_input::LineInput, }; use ratatui::widgets::{ListState, TableState}; @@ -68,7 +68,7 @@ pub struct SearchPageUIState { #[derive(Clone, Debug)] pub enum ContextPageType { - CurrentPlaying, + CurrentPlaying { tracks_id: Option }, Browsing(ContextId), } @@ -268,7 +268,7 @@ impl SearchPageUIState { impl ContextPageType { pub fn title(&self) -> String { match self { - ContextPageType::CurrentPlaying => String::from("Current Playing"), + ContextPageType::CurrentPlaying { .. } => String::from("Current Playing"), ContextPageType::Browsing(id) => match id { ContextId::Playlist(_) => String::from("Playlist"), ContextId::Album(_) => String::from("Album"), From 4f50078160cee9e2f6ad4521ae102fdecf0e0bad Mon Sep 17 00:00:00 2001 From: Thang Pham Date: Wed, 18 Feb 2026 16:49:18 +0700 Subject: [PATCH 2/2] fmt --- spotify_player/src/event/mod.rs | 4 ++-- spotify_player/src/event/window.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spotify_player/src/event/mod.rs b/spotify_player/src/event/mod.rs index 6ffd79ba..9e37bbc7 100644 --- a/spotify_player/src/event/mod.rs +++ b/spotify_player/src/event/mod.rs @@ -735,10 +735,10 @@ fn handle_global_command( // for track link, play the song "track" => { let id = TrackId::from_id(id)?.into_static(); - + // Clear Tracks context when playing from clipboard link ui.currently_playing_tracks_id = None; - + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::URIs(vec![id.into()], None), None, diff --git a/spotify_player/src/event/window.rs b/spotify_player/src/event/window.rs index eb7656bf..0285a85b 100644 --- a/spotify_player/src/event/window.rs +++ b/spotify_player/src/event/window.rs @@ -391,10 +391,10 @@ pub fn handle_command_for_track_list_window( // This is different from the track table, which handles // `ChooseSelected` by starting a `URIs` playback // containing all the tracks in the table. - + // Track lists are used for search results, so clear the Tracks context ui.currently_playing_tracks_id = None; - + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::URIs(vec![tracks[id].id.clone().into()], None), None, @@ -603,7 +603,7 @@ pub fn handle_command_for_episode_list_window( Command::ChooseSelected => { // Episodes don't have a Tracks context, so clear it ui.currently_playing_tracks_id = None; - + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::URIs(vec![episodes[id].id.clone().into()], None), None, @@ -646,10 +646,10 @@ fn handle_command_for_episode_table_window( match command { Command::ChooseSelected => { let uri = episodes[id].id.uri(); - + // Show context doesn't have a Tracks context, so clear it ui.currently_playing_tracks_id = None; - + client_pub.send(ClientRequest::Player(PlayerRequest::StartPlayback( Playback::Context( ContextId::Show(show_id.clone_static()),