From 183a835e22e116d5da557cb3e86a317d99d43950 Mon Sep 17 00:00:00 2001 From: abegert <91668665+abegert@users.noreply.github.com> Date: Wed, 17 Nov 2021 14:12:38 +0100 Subject: [PATCH] 232 show album name in playlists and queue (#333) * Pass worker to SongWidget so it can be later used to load song album covers * Show cover art for each song in playlist * Show cover art for songs per default, but not in Albums Albums instead show the songs' index. --- .../artist_details/artist_details.rs | 4 ++- src/app/components/details/details.rs | 2 ++ src/app/components/navigation/factory.rs | 10 ++++-- src/app/components/now_playing/now_playing.rs | 11 ++++-- src/app/components/playlist/playlist.rs | 11 ++++-- src/app/components/playlist/song.rs | 36 ++++++++++++++++--- src/app/components/playlist/song.ui | 8 +++++ .../playlist_details/playlist_details.rs | 2 ++ .../components/saved_tracks/saved_tracks.rs | 11 ++++-- src/app/models/gtypes/song_model.rs | 30 ++++++++++++++-- src/app/models/main.rs | 1 + 11 files changed, 108 insertions(+), 18 deletions(-) diff --git a/src/app/components/artist_details/artist_details.rs b/src/app/components/artist_details/artist_details.rs index d917603c..0c180848 100644 --- a/src/app/components/artist_details/artist_details.rs +++ b/src/app/components/artist_details/artist_details.rs @@ -132,7 +132,7 @@ impl ArtistDetails { if let Some(store) = model.get_list_store() { widget.bind_artist_releases( - worker, + worker.clone(), &*store, clone!(@weak model => move |id| { model.open_album(id); @@ -143,6 +143,8 @@ impl ArtistDetails { let playlist = Box::new(Playlist::new( widget.top_tracks_widget().clone(), Rc::clone(&model), + worker, + true, )); Self { diff --git a/src/app/components/details/details.rs b/src/app/components/details/details.rs index 82b55ec4..84a18b9f 100644 --- a/src/app/components/details/details.rs +++ b/src/app/components/details/details.rs @@ -185,6 +185,8 @@ impl Details { let playlist = Box::new(Playlist::new( widget.album_tracks_widget().clone(), model.clone(), + worker.clone(), + false, )); let modal = ReleaseDetailsWindow::new(); diff --git a/src/app/components/navigation/factory.rs b/src/app/components/navigation/factory.rs index 0fe3cfa5..fb88e1ec 100644 --- a/src/app/components/navigation/factory.rs +++ b/src/app/components/navigation/factory.rs @@ -38,7 +38,10 @@ impl ScreenFactory { Rc::clone(&self.app_model), self.dispatcher.box_clone(), )); - SelectionTools::new(NowPlaying::new(Rc::clone(&model)), model) + SelectionTools::new( + NowPlaying::new(Rc::clone(&model), self.worker.clone()), + model, + ) } pub fn make_saved_tracks(&self) -> impl ListenerComponent { @@ -46,7 +49,10 @@ impl ScreenFactory { Rc::clone(&self.app_model), self.dispatcher.box_clone(), )); - SelectionTools::new(SavedTracks::new(Rc::clone(&model)), model) + SelectionTools::new( + SavedTracks::new(Rc::clone(&model), self.worker.clone()), + model, + ) } pub fn make_album_details(&self, id: String) -> impl ListenerComponent { diff --git a/src/app/components/now_playing/now_playing.rs b/src/app/components/now_playing/now_playing.rs index 89a72844..a6c68bcb 100644 --- a/src/app/components/now_playing/now_playing.rs +++ b/src/app/components/now_playing/now_playing.rs @@ -4,7 +4,7 @@ use gtk::CompositeTemplate; use std::rc::Rc; use crate::app::components::{display_add_css_provider, Component, EventListener, Playlist}; -use crate::app::{state::PlaybackEvent, AppEvent}; +use crate::app::{state::PlaybackEvent, AppEvent, Worker}; use super::NowPlayingModel; @@ -79,14 +79,19 @@ pub struct NowPlaying { } impl NowPlaying { - pub fn new(model: Rc) -> Self { + pub fn new(model: Rc, worker: Worker) -> Self { let widget = NowPlayingWidget::new(); widget.connect_bottom_edge(clone!(@weak model => move || { model.load_more(); })); - let playlist = Playlist::new(widget.song_list_widget().clone(), model.clone()); + let playlist = Playlist::new( + widget.song_list_widget().clone(), + model.clone(), + worker, + true, + ); Self { widget, diff --git a/src/app/components/playlist/playlist.rs b/src/app/components/playlist/playlist.rs index d63be6c7..ca44074d 100644 --- a/src/app/components/playlist/playlist.rs +++ b/src/app/components/playlist/playlist.rs @@ -8,7 +8,7 @@ use crate::app::components::{Component, EventListener, SongWidget}; use crate::app::models::SongModel; use crate::app::{ state::{PlaybackEvent, SelectionEvent, SelectionState}, - AppEvent, ListDiff, ListStore, + AppEvent, ListDiff, ListStore, Worker, }; #[derive(Clone, Copy, Debug)] @@ -64,7 +64,12 @@ impl Playlist where Model: PlaylistModel + 'static, { - pub fn new(listview: gtk::ListView, model: Rc) -> Self { + pub fn new( + listview: gtk::ListView, + model: Rc, + worker: Worker, + show_song_covers: bool, + ) -> Self { let list_model = ListStore::new(); let selection_model = gtk::NoSelection::new(Some(list_model.unsafe_store())); let factory = gtk::SignalListItemFactory::new(); @@ -84,7 +89,7 @@ where song_model.set_state(Self::get_item_state(&*model, &song_model)); let widget = item.child().unwrap().downcast::().unwrap(); - widget.bind(&song_model); + widget.bind(&song_model, worker.clone(), show_song_covers); let id = &song_model.get_id(); widget.set_actions(model.actions_for(id).as_ref()); diff --git a/src/app/components/playlist/song.rs b/src/app/components/playlist/song.rs index 3629af25..b4b45da7 100644 --- a/src/app/components/playlist/song.rs +++ b/src/app/components/playlist/song.rs @@ -1,6 +1,8 @@ use crate::app::components::display_add_css_provider; +use crate::app::loader::ImageLoader; use crate::app::models::SongModel; +use crate::app::Worker; use gio::MenuModel; use gtk::prelude::*; use gtk::subclass::prelude::*; @@ -32,6 +34,9 @@ mod imp { #[template_child] pub menu_btn: TemplateChild, + + #[template_child] + pub song_cover: TemplateChild, } #[glib::object_subclass] @@ -70,9 +75,9 @@ impl SongWidget { glib::Object::new(&[]).expect("Failed to create an instance of SongWidget") } - pub fn for_model(model: SongModel) -> Self { + pub fn for_model(model: SongModel, worker: Worker) -> Self { let _self = Self::new(); - _self.bind(&model); + _self.bind(&model, worker, true); _self } @@ -114,13 +119,36 @@ impl SongWidget { } } - pub fn bind(&self, model: &SongModel) { + fn set_image(&self, pixbuf: Option<&gdk_pixbuf::Pixbuf>) { + imp::SongWidget::from_instance(self) + .song_cover + .set_from_pixbuf(pixbuf); + } + + pub fn bind_art(&self, model: &SongModel, worker: Worker) { + if let Some(url) = model.cover_url() { + let _self = self.downgrade(); + worker.send_local_task(async move { + if let Some(_self) = _self.upgrade() { + let loader = ImageLoader::new(); + let result = loader.load_remote(&url, "jpg", 100, 100).await; + _self.set_image(result.as_ref()); + } + }); + } + } + + pub fn bind(&self, model: &SongModel, worker: Worker, show_cover: bool) { let widget = imp::SongWidget::from_instance(self); - model.bind_index(&*widget.song_index, "label"); model.bind_title(&*widget.song_title, "label"); model.bind_artist(&*widget.song_artist, "label"); model.bind_duration(&*widget.song_length, "label"); + if show_cover { + self.bind_art(model, worker); + } else { + model.bind_index(&*widget.song_index, "label"); + } self.set_playing(model.get_playing()); model.connect_playing_local(clone!(@weak self as _self => move |song| { diff --git a/src/app/components/playlist/song.ui b/src/app/components/playlist/song.ui index 717c030d..72ee808f 100644 --- a/src/app/components/playlist/song.ui +++ b/src/app/components/playlist/song.ui @@ -24,6 +24,14 @@ + + + 30 + + + media-playback-start-symbolic diff --git a/src/app/components/playlist_details/playlist_details.rs b/src/app/components/playlist_details/playlist_details.rs index 6f8b16f6..e61c61a4 100644 --- a/src/app/components/playlist_details/playlist_details.rs +++ b/src/app/components/playlist_details/playlist_details.rs @@ -174,6 +174,8 @@ impl PlaylistDetails { let playlist = Box::new(Playlist::new( widget.playlist_tracks_widget().clone(), model.clone(), + worker.clone(), + true, )); widget.connect_header(); diff --git a/src/app/components/saved_tracks/saved_tracks.rs b/src/app/components/saved_tracks/saved_tracks.rs index c9079a4f..4f25fe67 100644 --- a/src/app/components/saved_tracks/saved_tracks.rs +++ b/src/app/components/saved_tracks/saved_tracks.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use crate::app::components::{display_add_css_provider, Component, EventListener, Playlist}; use crate::app::state::LoginEvent; -use crate::app::AppEvent; +use crate::app::{AppEvent, Worker}; use super::SavedTracksModel; @@ -80,14 +80,19 @@ pub struct SavedTracks { } impl SavedTracks { - pub fn new(model: Rc) -> Self { + pub fn new(model: Rc, worker: Worker) -> Self { let widget = SavedTracksWidget::new(); widget.connect_bottom_edge(clone!(@weak model => move || { model.load_more(); })); - let playlist = Playlist::new(widget.song_list_widget().clone(), model.clone()); + let playlist = Playlist::new( + widget.song_list_widget().clone(), + model.clone(), + worker, + true, + ); Self { widget, diff --git a/src/app/models/gtypes/song_model.rs b/src/app/models/gtypes/song_model.rs index 5d69fe27..3fa29288 100644 --- a/src/app/models/gtypes/song_model.rs +++ b/src/app/models/gtypes/song_model.rs @@ -10,17 +10,33 @@ glib::wrapper! { // Constructor for new instances. This simply calls glib::Object::new() with // initial values for our two properties and then returns the new instance impl SongModel { - pub fn new(id: &str, index: u32, title: &str, artist: &str, duration: &str) -> SongModel { + pub fn new( + id: &str, + index: u32, + title: &str, + artist: &str, + duration: &str, + art: &Option, + ) -> SongModel { glib::Object::new::(&[ ("index", &index), ("title", &title), ("artist", &artist), ("id", &id), ("duration", &duration), + ("art", &art), ]) .expect("Failed to create") } + pub fn cover_url(&self) -> Option { + self.property("art") + .unwrap() + .get::<&str>() + .ok() + .map(|s| s.to_string()) + } + pub fn set_playing(&self, is_playing: bool) { self.set_property("playing", is_playing) .expect("set 'playing' failed"); @@ -128,6 +144,7 @@ mod imp { title: RefCell>, artist: RefCell>, duration: RefCell>, + art: RefCell>, playing: RefCell, selected: RefCell, bindings: RefCell, @@ -176,6 +193,7 @@ mod imp { title: RefCell::new(None), artist: RefCell::new(None), duration: RefCell::new(None), + art: RefCell::new(None), playing: RefCell::new(false), selected: RefCell::new(false), bindings: RefCell::new(Default::default()), @@ -185,7 +203,7 @@ mod imp { // Static array for defining the properties of the new type. lazy_static! { - static ref PROPERTIES: [glib::ParamSpec; 7] = [ + static ref PROPERTIES: [glib::ParamSpec; 8] = [ glib::ParamSpec::new_uint( "index", "Index", @@ -219,6 +237,7 @@ mod imp { false, glib::ParamFlags::READWRITE, ), + glib::ParamSpec::new_string("art", "Art", "", None, glib::ParamFlags::READWRITE,), ]; } @@ -268,6 +287,12 @@ mod imp { .expect("type conformity checked by `Object::set_property`"); self.duration.replace(dur); } + "art" => { + let art = value + .get() + .expect("type conformity checked by `Object::set_property`"); + self.art.replace(art); + } "playing" => { let playing = value .get() @@ -293,6 +318,7 @@ mod imp { "artist" => self.artist.borrow().to_value(), "id" => self.id.borrow().to_value(), "duration" => self.duration.borrow().to_value(), + "art" => self.art.borrow().to_value(), "playing" => self.playing.borrow().to_value(), "selected" => self.selected.borrow().to_value(), _ => unimplemented!(), diff --git a/src/app/models/main.rs b/src/app/models/main.rs index b183bc2d..ce39d377 100644 --- a/src/app/models/main.rs +++ b/src/app/models/main.rs @@ -42,6 +42,7 @@ impl From<&SongDescription> for SongModel { &song.title, &song.artists_name(), &format_duration(song.duration.into()), + &song.art, ) } }