implement pagination for search results

closes #5

Ideally we should implement some indication in the UI that more search results
can be retrieved by scrolling down further.
This commit is contained in:
Henrik Friedrichsen
2019-04-10 14:30:29 +02:00
parent f9f4c52139
commit ea4ea5a853

View File

@@ -18,14 +18,18 @@ use queue::Queue;
use spotify::{Spotify, URIType}; use spotify::{Spotify, URIType};
use track::Track; use track::Track;
use traits::{ListItem, ViewExt}; use traits::{ListItem, ViewExt};
use ui::listview::ListView; use ui::listview::{ListView, Pagination};
use ui::tabview::TabView; use ui::tabview::TabView;
pub struct SearchView { pub struct SearchView {
results_tracks: Arc<RwLock<Vec<Track>>>, results_tracks: Arc<RwLock<Vec<Track>>>,
pagination_tracks: Pagination<Track>,
results_albums: Arc<RwLock<Vec<Album>>>, results_albums: Arc<RwLock<Vec<Album>>>,
pagination_albums: Pagination<Album>,
results_artists: Arc<RwLock<Vec<Artist>>>, results_artists: Arc<RwLock<Vec<Artist>>>,
pagination_artists: Pagination<Artist>,
results_playlists: Arc<RwLock<Vec<Playlist>>>, results_playlists: Arc<RwLock<Vec<Playlist>>>,
pagination_playlists: Pagination<Playlist>,
edit: IdView<EditView>, edit: IdView<EditView>,
tabs: IdView<TabView>, tabs: IdView<TabView>,
edit_focused: bool, edit_focused: bool,
@@ -33,6 +37,9 @@ pub struct SearchView {
spotify: Arc<Spotify>, spotify: Arc<Spotify>,
} }
type SearchHandler<I> =
Box<Fn(&Arc<Spotify>, &Arc<RwLock<Vec<I>>>, &str, usize, bool) -> u32 + Send + Sync>;
pub const LIST_ID: &str = "search_list"; pub const LIST_ID: &str = "search_list";
pub const EDIT_ID: &str = "search_edit"; pub const EDIT_ID: &str = "search_edit";
impl SearchView { impl SearchView {
@@ -53,33 +60,30 @@ impl SearchView {
}) })
.with_id(EDIT_ID); .with_id(EDIT_ID);
let list_tracks = ListView::new(results_tracks.clone(), queue.clone());
let pagination_tracks = list_tracks.get_pagination().clone();
let list_albums = ListView::new(results_albums.clone(), queue.clone());
let pagination_albums = list_albums.get_pagination().clone();
let list_artists = ListView::new(results_artists.clone(), queue.clone());
let pagination_artists = list_artists.get_pagination().clone();
let list_playlists = ListView::new(results_playlists.clone(), queue.clone());
let pagination_playlists = list_playlists.get_pagination().clone();
let tabs = TabView::new() let tabs = TabView::new()
.tab( .tab("tracks", "Tracks", list_tracks)
"tracks", .tab("albums", "Albums", list_albums)
"Tracks", .tab("artists", "Artists", list_artists)
ListView::new(results_tracks.clone(), queue.clone()), .tab("playlists", "Playlists", list_playlists);
)
.tab(
"albums",
"Albums",
ListView::new(results_albums.clone(), queue.clone()),
)
.tab(
"artists",
"Artists",
ListView::new(results_artists.clone(), queue.clone()),
)
.tab(
"playlists",
"Playlists",
ListView::new(results_playlists.clone(), queue.clone()),
);
SearchView { SearchView {
results_tracks, results_tracks,
pagination_tracks,
results_albums, results_albums,
pagination_albums,
results_artists, results_artists,
pagination_artists,
results_playlists, results_playlists,
pagination_playlists,
edit: searchfield, edit: searchfield,
tabs: tabs.with_id(LIST_ID), tabs: tabs.with_id(LIST_ID),
edit_focused: true, edit_focused: true,
@@ -95,104 +99,190 @@ impl SearchView {
}); });
} }
fn get_track(spotify: Arc<Spotify>, tracks: Arc<RwLock<Vec<Track>>>, query: &str) { fn get_track(
spotify: &Arc<Spotify>,
tracks: &Arc<RwLock<Vec<Track>>>,
query: &str,
_offset: usize,
_append: bool,
) -> u32 {
if let Some(results) = spotify.track(&query) { if let Some(results) = spotify.track(&query) {
let t = vec![(&results).into()]; let mut t = vec![(&results).into()];
let mut r = tracks.write().unwrap(); let mut r = tracks.write().unwrap();
*r = t; *r = t;
return 1;
} }
0
} }
fn search_track(spotify: Arc<Spotify>, tracks: Arc<RwLock<Vec<Track>>>, query: &str) { fn search_track(
if let Some(results) = spotify.search_track(&query, 50, 0) { spotify: &Arc<Spotify>,
let t = results.tracks.items.iter().map(|ft| ft.into()).collect(); tracks: &Arc<RwLock<Vec<Track>>>,
query: &str,
offset: usize,
append: bool,
) -> u32 {
if let Some(results) = spotify.search_track(&query, 50, offset as u32) {
let mut t = results.tracks.items.iter().map(|ft| ft.into()).collect();
let mut r = tracks.write().unwrap(); let mut r = tracks.write().unwrap();
*r = t;
if append {
r.append(&mut t);
} else {
*r = t;
}
return results.tracks.total;
} }
0
} }
fn get_album(spotify: Arc<Spotify>, albums: Arc<RwLock<Vec<Album>>>, query: &str) { fn get_album(
spotify: &Arc<Spotify>,
albums: &Arc<RwLock<Vec<Album>>>,
query: &str,
_offset: usize,
_append: bool,
) -> u32 {
if let Some(results) = spotify.album(&query) { if let Some(results) = spotify.album(&query) {
let a = vec![(&results).into()]; let a = vec![(&results).into()];
let mut r = albums.write().unwrap(); let mut r = albums.write().unwrap();
*r = a; *r = a;
return 1;
} }
0
} }
fn search_album(spotify: Arc<Spotify>, albums: Arc<RwLock<Vec<Album>>>, query: &str) { fn search_album(
if let Some(results) = spotify.search_album(&query, 50, 0) { spotify: &Arc<Spotify>,
let a = results.albums.items.iter().map(|sa| sa.into()).collect(); albums: &Arc<RwLock<Vec<Album>>>,
query: &str,
offset: usize,
append: bool,
) -> u32 {
if let Some(results) = spotify.search_album(&query, 50, offset as u32) {
let mut a = results.albums.items.iter().map(|sa| sa.into()).collect();
let mut r = albums.write().unwrap(); let mut r = albums.write().unwrap();
*r = a;
if append {
r.append(&mut a);
} else {
*r = a;
}
return results.albums.total;
} }
0
} }
fn get_artist(spotify: Arc<Spotify>, artists: Arc<RwLock<Vec<Artist>>>, query: &str) { fn get_artist(
spotify: &Arc<Spotify>,
artists: &Arc<RwLock<Vec<Artist>>>,
query: &str,
_offset: usize,
_append: bool,
) -> u32 {
if let Some(results) = spotify.artist(&query) { if let Some(results) = spotify.artist(&query) {
let a = vec![(&results).into()]; let a = vec![(&results).into()];
let mut r = artists.write().unwrap(); let mut r = artists.write().unwrap();
*r = a; *r = a;
return 1;
} }
0
} }
fn search_artist(spotify: Arc<Spotify>, artists: Arc<RwLock<Vec<Artist>>>, query: &str) { fn search_artist(
if let Some(results) = spotify.search_artist(&query, 50, 0) { spotify: &Arc<Spotify>,
let a = results.artists.items.iter().map(|fa| fa.into()).collect(); artists: &Arc<RwLock<Vec<Artist>>>,
query: &str,
offset: usize,
append: bool,
) -> u32 {
if let Some(results) = spotify.search_artist(&query, 50, offset as u32) {
let mut a = results.artists.items.iter().map(|fa| fa.into()).collect();
let mut r = artists.write().unwrap(); let mut r = artists.write().unwrap();
*r = a;
if append {
r.append(&mut a);
} else {
*r = a;
}
return results.artists.total;
} }
0
} }
fn get_playlist(spotify: Arc<Spotify>, playlists: Arc<RwLock<Vec<Playlist>>>, query: &str) { fn get_playlist(
spotify: &Arc<Spotify>,
playlists: &Arc<RwLock<Vec<Playlist>>>,
query: &str,
_offset: usize,
_append: bool,
) -> u32 {
if let Some(results) = spotify.playlist(&query) { if let Some(results) = spotify.playlist(&query) {
let pls = vec![Playlists::process_full_playlist(&results, &&spotify)]; let pls = vec![Playlists::process_full_playlist(&results, &&spotify)];
let mut r = playlists.write().unwrap(); let mut r = playlists.write().unwrap();
*r = pls; *r = pls;
return 1;
} }
0
} }
fn search_playlist(spotify: Arc<Spotify>, playlists: Arc<RwLock<Vec<Playlist>>>, query: &str) { fn search_playlist(
if let Some(results) = spotify.search_playlist(&query, 50, 0) { spotify: &Arc<Spotify>,
let pls = results playlists: &Arc<RwLock<Vec<Playlist>>>,
query: &str,
offset: usize,
append: bool,
) -> u32 {
if let Some(results) = spotify.search_playlist(&query, 50, offset as u32) {
let mut pls = results
.playlists .playlists
.items .items
.iter() .iter()
.map(|sp| Playlists::process_simplified_playlist(sp, &&spotify)) .map(|sp| Playlists::process_simplified_playlist(sp, &&spotify))
.collect(); .collect();
let mut r = playlists.write().unwrap(); let mut r = playlists.write().unwrap();
*r = pls;
}
}
fn perform_uri_lookup<I: ListItem>( if append {
&self, r.append(&mut pls);
handler: Box<Fn(Arc<Spotify>, Arc<RwLock<Vec<I>>>, &str) + Send>, } else {
results: &Arc<RwLock<Vec<I>>>, *r = pls;
query: &str, }
) { return results.playlists.total;
let spotify = self.spotify.clone(); }
let query = query.to_owned(); 0
let results = results.clone();
let ev = self.events.clone();
std::thread::spawn(move || {
handler(spotify, results, &query);
ev.trigger();
});
} }
fn perform_search<I: ListItem>( fn perform_search<I: ListItem>(
&self, &self,
handler: Box<Fn(Arc<Spotify>, Arc<RwLock<Vec<I>>>, &str) + Send>, handler: SearchHandler<I>,
results: &Arc<RwLock<Vec<I>>>, results: &Arc<RwLock<Vec<I>>>,
query: &str, query: &str,
paginator: Option<&Pagination<I>>,
) { ) {
let spotify = self.spotify.clone(); let spotify = self.spotify.clone();
let query = query.to_owned(); let query = query.to_owned();
let results = results.clone(); let results = results.clone();
let ev = self.events.clone(); let ev = self.events.clone();
let paginator = paginator.cloned();
std::thread::spawn(move || { std::thread::spawn(move || {
handler(spotify, results, &query); let total_items = handler(&spotify, &results, &query, 0, false) as usize;
// register paginator if the API has more than one page of results
if paginator.is_some() && total_items > results.read().unwrap().len() {
let mut paginator = paginator.unwrap();
let ev = ev.clone();
// paginator callback
let cb = move |items: Arc<RwLock<Vec<I>>>| {
let offset = items.read().unwrap().len();
handler(&spotify, &results, &query, offset, true);
ev.trigger();
};
paginator.set(total_items, Box::new(cb));
} else if let Some(mut p) = paginator {
p.clear()
}
ev.trigger(); ev.trigger();
}); });
} }
@@ -210,6 +300,9 @@ impl SearchView {
}); });
} }
// check if API token refresh is necessary before commencing multiple
// requests to avoid deadlock, as the parallel requests might
// simultaneously try to refresh the token
self.spotify.refresh_token(); self.spotify.refresh_token();
// is the query a Spotify URI? // is the query a Spotify URI?
@@ -229,46 +322,66 @@ impl SearchView {
let mut tab_view = self.tabs.get_mut(); let mut tab_view = self.tabs.get_mut();
match uritype { match uritype {
URIType::Track => { URIType::Track => {
self.perform_uri_lookup( self.perform_search(
Box::new(Self::get_track), Box::new(Self::get_track),
&self.results_tracks, &self.results_tracks,
&query, &query,
None,
); );
tab_view.move_focus_to(0); tab_view.move_focus_to(0);
} }
URIType::Album => { URIType::Album => {
self.perform_uri_lookup( self.perform_search(
Box::new(Self::get_album), Box::new(Self::get_album),
&self.results_albums, &self.results_albums,
&query, &query,
None,
); );
tab_view.move_focus_to(1); tab_view.move_focus_to(1);
} }
URIType::Artist => { URIType::Artist => {
self.perform_uri_lookup( self.perform_search(
Box::new(Self::get_artist), Box::new(Self::get_artist),
&self.results_artists, &self.results_artists,
&query, &query,
None,
); );
tab_view.move_focus_to(2); tab_view.move_focus_to(2);
} }
URIType::Playlist => { URIType::Playlist => {
self.perform_uri_lookup( self.perform_search(
Box::new(Self::get_playlist), Box::new(Self::get_playlist),
&self.results_playlists, &self.results_playlists,
&query, &query,
None,
); );
tab_view.move_focus_to(3); tab_view.move_focus_to(3);
} }
} }
} else { } else {
self.perform_search(Box::new(Self::search_track), &self.results_tracks, &query); self.perform_search(
self.perform_search(Box::new(Self::search_album), &self.results_albums, &query); Box::new(Self::search_track),
self.perform_search(Box::new(Self::search_artist), &self.results_artists, &query); &self.results_tracks,
&query,
Some(&self.pagination_tracks),
);
self.perform_search(
Box::new(Self::search_album),
&self.results_albums,
&query,
Some(&self.pagination_albums),
);
self.perform_search(
Box::new(Self::search_artist),
&self.results_artists,
&query,
Some(&self.pagination_artists),
);
self.perform_search( self.perform_search(
Box::new(Self::search_playlist), Box::new(Self::search_playlist),
&self.results_playlists, &self.results_playlists,
&query, &query,
Some(&self.pagination_playlists),
); );
} }
} }