diff --git a/src/app.rs b/src/app.rs index d7af0fc7..57156d8f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -58,6 +58,20 @@ impl ScrollableResultPages { } } + pub fn index(&self) -> usize { + self.index + } + + pub fn next_page(&mut self) { + if self.index + 1 < self.pages.len() { + self.index += 1; + } + } + + pub fn previous_page(&mut self) { + self.index -= 1; + } + pub fn get_results(&self, at_index: Option) -> Option<&T> { self.pages.get(at_index.unwrap_or(self.index)) } @@ -67,10 +81,17 @@ impl ScrollableResultPages { } pub fn add_pages(&mut self, new_pages: T) { - self.pages.push(new_pages); // Whenever a new page is added, set the active index to the end of the vector + self.pages.push(new_pages); self.index = self.pages.len() - 1; } + + pub fn set_page_at_index(&mut self, new_pages: T, at_index: usize) { + self.pages.push(new_pages); + self.pages.swap_remove(at_index); + //When a page is modified, the following pages must be invalidated + self.pages.truncate(at_index + 1); + } } #[derive(Default)] @@ -284,11 +305,12 @@ pub struct App { pub saved_show_ids_set: HashSet, pub large_search_limit: u32, pub library: Library, - pub playlist_offset: u32, + pub playlist_track_offset: u32, pub made_for_you_offset: u32, pub playlist_tracks: Option>, pub made_for_you_tracks: Option>, - pub playlists: Option>, + pub playlists: ScrollableResultPages>, + pub playlist_offset: u32, pub recently_played: SpotifyResultAndSelectedIndex>>, pub recommended_tracks: Vec, pub recommendations_seed: String, @@ -339,6 +361,7 @@ impl Default for App { artists: vec![], artist: None, user_config: UserConfig::new(), + playlist_offset: 0, saved_album_tracks_index: 0, recently_played: Default::default(), size: Rect::default(), @@ -367,11 +390,11 @@ impl Default for App { input: vec![], input_idx: 0, input_cursor_position: 0, - playlist_offset: 0, + playlist_track_offset: 0, made_for_you_offset: 0, playlist_tracks: None, made_for_you_tracks: None, - playlists: None, + playlists: ScrollableResultPages::new(), recommended_tracks: vec![], recommendations_context: None, recommendations_seed: "".to_string(), @@ -1022,10 +1045,12 @@ impl App { } pub fn user_unfollow_playlist(&mut self) { - if let (Some(playlists), Some(selected_index), Some(user)) = - (&self.playlists, self.selected_playlist_index, &self.user) - { - let selected_playlist = &playlists.items[selected_index]; + if let (Some(selected_index), Some(user), Some(playlist_page)) = ( + self.selected_playlist_index, + &self.user, + &self.playlists.get_results(None), + ) { + let selected_playlist = &playlist_page.items[selected_index]; let selected_id = selected_playlist.id.clone(); let user_id = user.id.clone(); self.dispatch(IoEvent::UserUnfollowPlaylist(user_id, selected_id)) diff --git a/src/cli/cli_app.rs b/src/cli/cli_app.rs index bd9867f8..369139d4 100644 --- a/src/cli/cli_app.rs +++ b/src/cli/cli_app.rs @@ -200,9 +200,14 @@ impl<'a> CliApp<'a> { } } Type::Playlist => { - self.net.handle_network_event(IoEvent::GetPlaylists).await; - if let Some(playlists) = &self.net.app.lock().await.playlists { - playlists + //TODO:This will only return the playlists in the first page + self + .net + .handle_network_event(IoEvent::GetPlaylists(None)) + .await; + let playlists = &self.net.app.lock().await.playlists; + match playlists.get_results(Some(0)) { + Some(playlists) => playlists .items .iter() .map(|p| { @@ -212,9 +217,8 @@ impl<'a> CliApp<'a> { ) }) .collect::>() - .join("\n") - } else { - "No playlists found".to_string() + .join("\n"), + None => "No playlist found".to_string(), } } Type::Liked => { diff --git a/src/handlers/made_for_you.rs b/src/handlers/made_for_you.rs index 1645ae26..7f36af92 100644 --- a/src/handlers/made_for_you.rs +++ b/src/handlers/made_for_you.rs @@ -46,7 +46,7 @@ pub fn handler(key: Key, app: &mut App) { &app.made_for_you_index, ) { app.track_table.context = Some(TrackTableContext::MadeForYou); - app.playlist_offset = 0; + app.playlist_track_offset = 0; if let Some(selected_playlist) = playlists.items.get(selected_playlist_index.to_owned()) { app.made_for_you_offset = 0; let playlist_id = selected_playlist.id.to_owned(); diff --git a/src/handlers/playlist.rs b/src/handlers/playlist.rs index 76f43b07..536fd8c7 100644 --- a/src/handlers/playlist.rs +++ b/src/handlers/playlist.rs @@ -9,30 +9,26 @@ use crate::network::IoEvent; pub fn handler(key: Key, app: &mut App) { match key { k if common_key_events::right_event(k) => common_key_events::handle_right_event(app), - k if common_key_events::down_event(k) => { - match &app.playlists { - Some(p) => { - if let Some(selected_playlist_index) = app.selected_playlist_index { - let next_index = - common_key_events::on_down_press_handler(&p.items, Some(selected_playlist_index)); - app.selected_playlist_index = Some(next_index); - } - } - None => {} - }; - } - k if common_key_events::up_event(k) => { - match &app.playlists { - Some(p) => { + k if common_key_events::down_event(k) => match &app.playlists.get_results(None) { + Some(p) => { + if let Some(selected_playlist_index) = app.selected_playlist_index { let next_index = - common_key_events::on_up_press_handler(&p.items, app.selected_playlist_index); + common_key_events::on_down_press_handler(&p.items, Some(selected_playlist_index)); app.selected_playlist_index = Some(next_index); } - None => {} - }; - } + } + None => {} + }, + k if common_key_events::up_event(k) => match &app.playlists.get_results(None) { + Some(playlist_page) => { + let next_index = + common_key_events::on_up_press_handler(&playlist_page.items, app.selected_playlist_index); + app.selected_playlist_index = Some(next_index); + } + None => {} + }, k if common_key_events::high_event(k) => { - match &app.playlists { + match &app.playlists.get_results(None) { Some(_p) => { let next_index = common_key_events::on_high_press_handler(); app.selected_playlist_index = Some(next_index); @@ -41,7 +37,7 @@ pub fn handler(key: Key, app: &mut App) { }; } k if common_key_events::middle_event(k) => { - match &app.playlists { + match &app.playlists.get_results(None) { Some(p) => { let next_index = common_key_events::on_middle_press_handler(&p.items); app.selected_playlist_index = Some(next_index); @@ -50,7 +46,7 @@ pub fn handler(key: Key, app: &mut App) { }; } k if common_key_events::low_event(k) => { - match &app.playlists { + match &app.playlists.get_results(None) { Some(p) => { let next_index = common_key_events::on_low_press_handler(&p.items); app.selected_playlist_index = Some(next_index); @@ -59,25 +55,51 @@ pub fn handler(key: Key, app: &mut App) { }; } Key::Enter => { - if let (Some(playlists), Some(selected_playlist_index)) = - (&app.playlists, &app.selected_playlist_index) - { + if let (Some(playlists), Some(selected_playlist_index)) = ( + &app.playlists.get_results(None), + &app.selected_playlist_index, + ) { app.active_playlist_index = Some(selected_playlist_index.to_owned()); app.track_table.context = Some(TrackTableContext::MyPlaylists); - app.playlist_offset = 0; + app.playlist_track_offset = 0; if let Some(selected_playlist) = playlists.items.get(selected_playlist_index.to_owned()) { let playlist_id = selected_playlist.id.to_owned(); - app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_offset)); + app.dispatch(IoEvent::GetPlaylistTracks( + playlist_id, + app.playlist_track_offset, + )); } }; } + + k if k == app.user_config.keys.previous_page => { + if app.playlists.index() > 0 { + app.playlists.previous_page(); + app.selected_playlist_index = Some(0) + } + } + k if k == app.user_config.keys.next_page => { + match app.playlists.get_results(Some(app.playlists.index() + 1)) { + Some(_) => { + app.playlists.next_page(); + app.selected_playlist_index = Some(0); + } + None => { + if let Some(saved_playlists) = &app.playlists.get_results(None) { + let offset = Some(saved_playlists.offset + saved_playlists.limit); + app.dispatch(IoEvent::GetPlaylists(offset)); + } + } + } + } Key::Char('D') => { - if let (Some(playlists), Some(selected_index)) = (&app.playlists, app.selected_playlist_index) - { - let selected_playlist = &playlists.items[selected_index].name; + if let (Some(playlists), Some(selected_index)) = ( + &app.playlists.get_results(None), + &app.selected_playlist_index, + ) { + let selected_playlist = &playlists.items[selected_index.to_owned()].name; app.dialog = Some(selected_playlist.clone()); app.confirm = false; - app.push_navigation_stack( RouteId::Dialog, ActiveBlock::Dialog(DialogContext::PlaylistWindow), diff --git a/src/handlers/search_results.rs b/src/handlers/search_results.rs index c87dacee..8b831b0f 100644 --- a/src/handlers/search_results.rs +++ b/src/handlers/search_results.rs @@ -327,7 +327,7 @@ fn handle_enter_event_on_selected_block(app: &mut App) { // Go to playlist tracks table app.track_table.context = Some(TrackTableContext::PlaylistSearch); let playlist_id = playlist.id.to_owned(); - app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_offset)); + app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_track_offset)); }; } } @@ -495,7 +495,7 @@ pub fn handler(key: Key, app: &mut App) { Key::Enter => match app.search_results.selected_block { SearchResultBlock::Empty => handle_enter_event_on_hovered_block(app), SearchResultBlock::PlaylistSearch => { - app.playlist_offset = 0; + app.playlist_track_offset = 0; handle_enter_event_on_selected_block(app); } _ => handle_enter_event_on_selected_block(app), diff --git a/src/handlers/track_table.rs b/src/handlers/track_table.rs index 4bffda26..e6511f51 100644 --- a/src/handlers/track_table.rs +++ b/src/handlers/track_table.rs @@ -44,17 +44,21 @@ pub fn handler(key: Key, app: &mut App) { match &app.track_table.context { Some(context) => match context { TrackTableContext::MyPlaylists => { - if let (Some(playlists), Some(selected_playlist_index)) = - (&app.playlists, &app.selected_playlist_index) - { + if let (Some(playlist_page), Some(selected_playlist_index)) = ( + &app.playlists.get_results(None), + &app.selected_playlist_index, + ) { if let Some(selected_playlist) = - playlists.items.get(selected_playlist_index.to_owned()) + playlist_page.items.get(selected_playlist_index.to_owned()) { if let Some(playlist_tracks) = &app.playlist_tracks { - if app.playlist_offset + app.large_search_limit < playlist_tracks.total { - app.playlist_offset += app.large_search_limit; + if app.playlist_track_offset + app.large_search_limit < playlist_tracks.total { + app.playlist_track_offset += app.large_search_limit; let playlist_id = selected_playlist.id.to_owned(); - app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_offset)); + app.dispatch(IoEvent::GetPlaylistTracks( + playlist_id, + app.playlist_track_offset, + )); } } } @@ -97,17 +101,21 @@ pub fn handler(key: Key, app: &mut App) { match &app.track_table.context { Some(context) => match context { TrackTableContext::MyPlaylists => { - if let (Some(playlists), Some(selected_playlist_index)) = - (&app.playlists, &app.selected_playlist_index) - { - if app.playlist_offset >= app.large_search_limit { - app.playlist_offset -= app.large_search_limit; + if let (Some(playlist_page), Some(selected_playlist_index)) = ( + &app.playlists.get_results(None), + &app.selected_playlist_index, + ) { + if app.playlist_track_offset >= app.large_search_limit { + app.playlist_track_offset -= app.large_search_limit; }; if let Some(selected_playlist) = - playlists.items.get(selected_playlist_index.to_owned()) + playlist_page.items.get(selected_playlist_index.to_owned()) { let playlist_id = selected_playlist.id.to_owned(); - app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_offset)); + app.dispatch(IoEvent::GetPlaylistTracks( + playlist_id, + app.playlist_track_offset, + )); } }; } @@ -158,9 +166,13 @@ fn play_random_song(app: &mut App) { if let Some(context) = &app.track_table.context { match context { TrackTableContext::MyPlaylists => { - let (context_uri, track_json) = match (&app.selected_playlist_index, &app.playlists) { - (Some(selected_playlist_index), Some(playlists)) => { - if let Some(selected_playlist) = playlists.items.get(selected_playlist_index.to_owned()) + let (context_uri, track_json) = match ( + &app.selected_playlist_index, + &app.playlists.get_results(None), + ) { + (Some(selected_playlist_index), Some(playlist_page)) => { + if let Some(selected_playlist) = + playlist_page.items.get(selected_playlist_index.to_owned()) { ( Some(selected_playlist.uri.to_owned()), @@ -279,9 +291,10 @@ fn jump_to_end(app: &mut App) { match &app.track_table.context { Some(context) => match context { TrackTableContext::MyPlaylists => { - if let (Some(playlists), Some(selected_playlist_index)) = - (&app.playlists, &app.selected_playlist_index) - { + if let (Some(playlists), Some(selected_playlist_index)) = ( + &app.playlists.get_results(None), + &app.selected_playlist_index, + ) { if let Some(selected_playlist) = playlists.items.get(selected_playlist_index.to_owned()) { let total_tracks = selected_playlist .tracks @@ -291,9 +304,12 @@ fn jump_to_end(app: &mut App) { as u32; if app.large_search_limit < total_tracks { - app.playlist_offset = total_tracks - (total_tracks % app.large_search_limit); + app.playlist_track_offset = total_tracks - (total_tracks % app.large_search_limit); let playlist_id = selected_playlist.id.to_owned(); - app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_offset)); + app.dispatch(IoEvent::GetPlaylistTracks( + playlist_id, + app.playlist_track_offset, + )); } } } @@ -318,8 +334,8 @@ fn on_enter(app: &mut App) { Some(context) => match context { TrackTableContext::MyPlaylists => { if let Some(_track) = tracks.get(*selected_index) { - let context_uri = match (&app.active_playlist_index, &app.playlists) { - (Some(active_playlist_index), Some(playlists)) => playlists + let context_uri = match (&app.active_playlist_index, &app.playlists.get_results(None)) { + (Some(active_playlist_index), Some(playlist_page)) => playlist_page .items .get(active_playlist_index.to_owned()) .map(|selected_playlist| selected_playlist.uri.to_owned()), @@ -329,7 +345,7 @@ fn on_enter(app: &mut App) { app.dispatch(IoEvent::StartPlayback( context_uri, None, - Some(app.track_table.selected_index + app.playlist_offset as usize), + Some(app.track_table.selected_index + app.playlist_track_offset as usize), )); }; } @@ -469,13 +485,19 @@ fn jump_to_start(app: &mut App) { match &app.track_table.context { Some(context) => match context { TrackTableContext::MyPlaylists => { - if let (Some(playlists), Some(selected_playlist_index)) = - (&app.playlists, &app.selected_playlist_index) - { - if let Some(selected_playlist) = playlists.items.get(selected_playlist_index.to_owned()) { - app.playlist_offset = 0; + if let (Some(selected_playlist_index), Some(playlist_page)) = ( + &app.selected_playlist_index, + &app.playlists.get_results(None), + ) { + if let Some(selected_playlist) = + playlist_page.items.get(selected_playlist_index.to_owned()) + { + app.playlist_track_offset = 0; let playlist_id = selected_playlist.id.to_owned(); - app.dispatch(IoEvent::GetPlaylistTracks(playlist_id, app.playlist_offset)); + app.dispatch(IoEvent::GetPlaylistTracks( + playlist_id, + app.playlist_track_offset, + )); } } } diff --git a/src/main.rs b/src/main.rs index a7c85ef1..9b8bd073 100644 --- a/src/main.rs +++ b/src/main.rs @@ -400,7 +400,7 @@ async fn start_ui(user_config: UserConfig, app: &Arc>) -> Result<()> // Delay spotify request until first render, will have the effect of improving // startup speed if is_first_render { - app.dispatch(IoEvent::GetPlaylists); + app.dispatch(IoEvent::GetPlaylists(None)); app.dispatch(IoEvent::GetUser); app.dispatch(IoEvent::GetCurrentPlayback); app.help_docs_size = ui::help::get_help_docs(&app.user_config.keys).len() as u32; diff --git a/src/network.rs b/src/network.rs index 6a58efba..5ebeb53b 100644 --- a/src/network.rs +++ b/src/network.rs @@ -35,7 +35,7 @@ use tokio::try_join; pub enum IoEvent { GetCurrentPlayback, RefreshAuthentication, - GetPlaylists, + GetPlaylists(Option), GetDevices, GetSearchResults(String, Option), SetTracksToTable(Vec), @@ -146,8 +146,8 @@ impl<'a> Network<'a> { IoEvent::RefreshAuthentication => { self.refresh_authentication().await; } - IoEvent::GetPlaylists => { - self.get_current_user_playlists().await; + IoEvent::GetPlaylists(offset) => { + self.get_current_user_playlists(offset).await; } IoEvent::GetUser => { self.get_user().await; @@ -169,8 +169,10 @@ impl<'a> Network<'a> { .get_made_for_you_playlist_tracks(playlist_id, made_for_you_offset) .await; } - IoEvent::GetPlaylistTracks(playlist_id, playlist_offset) => { - self.get_playlist_tracks(playlist_id, playlist_offset).await; + IoEvent::GetPlaylistTracks(playlist_id, playlist_track_offset) => { + self + .get_playlist_tracks(playlist_id, playlist_track_offset) + .await; } IoEvent::GetCurrentSavedTracks(offset) => { self.get_current_user_saved_tracks(offset).await; @@ -400,7 +402,7 @@ impl<'a> Network<'a> { } } - async fn get_playlist_tracks(&mut self, playlist_id: String, playlist_offset: u32) { + async fn get_playlist_tracks(&mut self, playlist_id: String, playlist_track_offset: u32) { if let Ok(playlist_tracks) = self .spotify .user_playlist_tracks( @@ -408,7 +410,7 @@ impl<'a> Network<'a> { &playlist_id, None, Some(self.large_search_limit), - Some(playlist_offset), + Some(playlist_track_offset), None, ) .await @@ -1264,7 +1266,7 @@ impl<'a> Network<'a> { .await { Ok(_) => { - self.get_current_user_playlists().await; + self.get_current_user_playlists(None).await; } Err(e) => { self.handle_error(anyhow!(e)).await; @@ -1279,7 +1281,7 @@ impl<'a> Network<'a> { .await { Ok(_) => { - self.get_current_user_playlists().await; + self.get_current_user_playlists(None).await; } Err(e) => { self.handle_error(anyhow!(e)).await; @@ -1346,18 +1348,52 @@ impl<'a> Network<'a> { } } - async fn get_current_user_playlists(&mut self) { + async fn get_current_user_playlists(&mut self, offset: Option) { + let mut app = self.app.lock().await; + + let call_offset: u32 = match app.playlists.get_results(None) { + Some(x) => match offset { + Some(o) => o, + None => x.offset, + }, + None => 0 as u32, + }; + let playlists = self .spotify - .current_user_playlists(self.large_search_limit, None) + .current_user_playlists(self.large_search_limit, call_offset) .await; match playlists { Ok(p) => { - let mut app = self.app.lock().await; - app.playlists = Some(p); - // Select the first playlist - app.selected_playlist_index = Some(0); + // next not working as expected, so using items.is_empty + match p.items.is_empty() { + false => { + match app.playlists.get_results(None) { + Some(x) => { + let current_offset: u32 = match offset { + Some(o) => o, + None => x.offset, + }; + + let pos = app + .playlists + .pages + .iter() + .position(|x| x.offset == current_offset); + + match pos { + Some(pos) => app.playlists.set_page_at_index(p, pos), + None => app.playlists.add_pages(p), + } + } + None => app.playlists.add_pages(p), + } + + app.selected_playlist_index = Some(0); + } + true => {} + } } Err(e) => { self.handle_error(anyhow!(e)).await; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 83723571..6167c21d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -304,8 +304,8 @@ pub fn draw_playlist_block(f: &mut Frame, app: &App, layout_chunk: Rect) where B: Backend, { - let playlist_items = match &app.playlists { - Some(p) => p.items.iter().map(|item| item.name.to_owned()).collect(), + let playlist_items = match app.playlists.get_results(None) { + Some(a) => a.items.iter().map(|item| item.name.to_owned()).collect(), None => vec![], };