ncspot 0.12.0

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

use cursive::view::ViewWrapper;
use cursive::Cursive;
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::tabview::TabView;

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

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 {
                    if let Some(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 {
                    if let Some(artists) = spotify.api.artist_related_artists(&id) {
                        related.write().unwrap().extend(artists);
                        library.trigger_redraw();
                    }
                }
            });
        }

        let mut tabs = TabView::new();

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

            tabs.add_tab(
                "tracks",
                ListView::new(
                    Arc::new(RwLock::new(tracks)),
                    queue.clone(),
                    library.clone(),
                )
                .with_title("Saved Tracks"),
            );
        }

        tabs.add_tab(
            "top_tracks",
            ListView::new(top_tracks, queue.clone(), library.clone()).with_title("Top 10"),
        );

        tabs.add_tab("albums", albums_view.with_title("Albums"));
        tabs.add_tab("singles", singles_view.with_title("Singles"));

        tabs.add_tab(
            "related",
            ListView::new(related, queue, library).with_title("Related Artists"),
        );

        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: TabView);
}

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)
    }
}