ncspot 1.3.4

ncurses Spotify client written in Rust using librespot, inspired by ncmpc and the likes.
Documentation
use std::sync::{Arc, RwLock};
use std::thread;

use cursive::Cursive;
use cursive::view::ViewWrapper;
use rspotify::model::AlbumType;

use crate::command::Command;
use crate::commands::CommandResult;
use crate::library::Library;
use crate::model::album::Album;
use crate::model::artist::Artist;
use crate::model::track::Track;
use crate::queue::Queue;
use crate::traits::ViewExt;
use crate::ui::listview::ListView;
use crate::ui::tabbedview::TabbedView;

pub struct ArtistView {
    artist: Artist,
    tabs: TabbedView,
}

impl ArtistView {
    pub fn new(queue: Arc<Queue>, library: Arc<Library>, artist: &Artist) -> Self {
        let spotify = queue.get_spotify();

        let albums_view =
            Self::albums_view(artist, AlbumType::Album, queue.clone(), library.clone());
        let singles_view =
            Self::albums_view(artist, AlbumType::Single, queue.clone(), library.clone());

        let top_tracks: Arc<RwLock<Vec<Track>>> = Arc::new(RwLock::new(Vec::new()));
        {
            let top_tracks = top_tracks.clone();
            let spotify = spotify.clone();
            let id = artist.id.clone();
            let library = library.clone();
            thread::spawn(move || {
                if let Some(id) = id
                    && let Ok(tracks) = spotify.api.artist_top_tracks(&id)
                {
                    top_tracks.write().unwrap().extend(tracks);
                    library.trigger_redraw();
                }
            });
        }

        let related: Arc<RwLock<Vec<Artist>>> = Arc::new(RwLock::new(Vec::new()));
        {
            let related = related.clone();
            let id = artist.id.clone();
            let library = library.clone();
            thread::spawn(move || {
                if let Some(id) = id
                    && let Ok(artists) = spotify.api.artist_related_artists(&id)
                {
                    related.write().unwrap().extend(artists);
                    library.trigger_redraw();
                }
            });
        }

        let mut tabs = TabbedView::new();

        if let Some(tracks) = artist.tracks.as_ref() {
            let tracks = tracks.clone();

            tabs.add_tab(
                "Saved Tracks",
                ListView::new(
                    Arc::new(RwLock::new(tracks)),
                    queue.clone(),
                    library.clone(),
                ),
            );
        }
        tabs.add_tab(
            "Top 10",
            ListView::new(top_tracks, queue.clone(), library.clone()),
        );
        tabs.add_tab("Albums", albums_view);
        tabs.add_tab("Singles", singles_view);
        tabs.add_tab("Related Artists", ListView::new(related, queue, library));

        Self {
            artist: artist.clone(),
            tabs,
        }
    }

    fn albums_view(
        artist: &Artist,
        album_type: AlbumType,
        queue: Arc<Queue>,
        library: Arc<Library>,
    ) -> ListView<Album> {
        if let Some(artist_id) = &artist.id {
            let spotify = queue.get_spotify();
            let albums_page = spotify.api.artist_albums(artist_id, Some(album_type));
            let view = ListView::new(albums_page.items.clone(), queue, library);
            albums_page.apply_pagination(view.get_pagination());

            view
        } else {
            ListView::new(Arc::new(RwLock::new(Vec::new())), queue, library)
        }
    }
}

impl ViewWrapper for ArtistView {
    wrap_impl!(self.tabs: TabbedView);
}

impl ViewExt for ArtistView {
    fn title(&self) -> String {
        self.artist.name.clone()
    }

    fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result<CommandResult, String> {
        self.tabs.on_command(s, cmd)
    }
}