diff --git a/resources/style.css b/resources/style.css index ac7a237..cbbd0da 100644 --- a/resources/style.css +++ b/resources/style.css @@ -27,3 +27,11 @@ #play-queue .playing label.title { font-weight: bold; } + +gridview.albums child { + margin: 10px; +} + +gridview.albums child image { + margin: 10px; +} diff --git a/resources/window.blp b/resources/window.blp index f1f519f..a708f66 100644 --- a/resources/window.blp +++ b/resources/window.blp @@ -83,7 +83,52 @@ template $AudreyUiWindow: Adw.ApplicationWindow { ScrolledWindow { vexpand: true; - GridView {} + GridView { + styles [ "albums" ] + single-click-activate: true; + + model: NoSelection { + model: bind template.albums-model; + }; + + factory: BuilderListItemFactory { + template ListItem { + child: Box { + orientation: vertical; + margin-bottom: 6; + + Image { + icon-name: "media-optical-cd"; + pixel-size: 160; + halign: center; + hexpand: false; + + styles [ + "frame" + ] + } + + Label { + label: bind template.item as <$AudreyModelAlbum>.name; + ellipsize: end; + + styles [ + "heading" + ] + } + + Label { + label: bind template.item as <$AudreyModelAlbum>.artist; + ellipsize: end; + + styles [ + "caption" + ] + } + }; + } + }; + } } }; } diff --git a/src/model.rs b/src/model.rs index e3ebdf1..8358c26 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,2 +1,5 @@ mod song; pub use song::Song; + +mod album; +pub use album::Album; diff --git a/src/model/album.rs b/src/model/album.rs index 1f05598..4366104 100644 --- a/src/model/album.rs +++ b/src/model/album.rs @@ -1,8 +1,8 @@ mod imp { use adw::prelude::*; + use gtk::glib; use gtk::subclass::prelude::*; - use gtk::{gdk, glib}; - use std::cell::{Cell, RefCell}; + use std::cell::RefCell; #[derive(glib::Properties, Default)] #[properties(wrapper_type = super::Album)] @@ -12,7 +12,7 @@ mod imp { #[property(get, set)] name: RefCell, #[property(get, set)] - artist: RefCell>, + artist: RefCell, } #[glib::object_subclass] @@ -20,55 +20,13 @@ mod imp { const NAME: &'static str = "AudreyModelAlbum"; type Type = super::Album; } + + #[glib::derived_properties] + impl ObjectImpl for Album {} } -use crate::subsonic; -use adw::{prelude::*, subclass::prelude::*}; -use glib::Object; -use gtk::{gdk, glib}; -use std::rc::Rc; +use gtk::glib; glib::wrapper! { pub struct Album(ObjectSubclass); } - -impl Album { - pub fn from_api( - album: &subsonic::schema::Child, - load_thumbnail: bool, - ) -> Self { - let album: Album = Object::builder() - .property("id", &album.id) - .property("title", &album.title) - .property("artist", &album.artist) - .property("album", &album.album) - .property("genre", &album.genre) - .property("duration", album.duration as i64) - .property("track", album.track.map(i64::from).unwrap_or(-1)) - .property("stream-url", api.stream_url(&album.id).as_str()) - .build(); - - if load_thumbnail { - let api = Rc::clone(&api); - let id = album.id(); - let album_weak = album.downgrade(); - album.imp() - .thumbnail_loading - .replace(Some(glib::spawn_future_local(async move { - let bytes = api - .cover_art(&id, Some(50)) // TODO: WidgetExt::scale_factor - .await - .unwrap(); - let album = match album_weak.upgrade() { - None => return, - Some(album) => album, - }; - album.set_thumbnail( - gdk::Texture::from_bytes(&glib::Bytes::from_owned(bytes)).unwrap(), - ); - }))); - } - - album - } -} diff --git a/src/ui/window.rs b/src/ui/window.rs index ab6e45f..cd5e837 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -1,5 +1,5 @@ mod imp { - use crate::model::Song; + use crate::model::{Album, Song}; use crate::{mpris, mpv}; use adw::prelude::*; use adw::subclass::prelude::*; @@ -38,6 +38,9 @@ mod imp { #[property(get)] playlist_model: gio::ListStore, + #[property(get)] + albums_model: gio::ListStore, + #[property(type = i64, get = Self::volume, set = Self::set_volume, minimum = 0, maximum = 100)] _volume: (), #[property(type = bool, get = Self::mute, set = Self::set_mute)] @@ -98,7 +101,9 @@ mod imp { setup: Default::default(), api: Default::default(), mpv, + playlist_model: gio::ListStore::new::(), + albums_model: gio::ListStore::new::(), _volume: (), _mute: (), @@ -653,20 +658,28 @@ impl Window { } pub fn setup_connected(&self, api: crate::subsonic::Client) { - self.imp().api.replace(Some(Rc::new(api))); + if self.imp().api.replace(Some(Rc::new(api))).is_some() { + unimplemented!("changing the api object"); + } + self.set_can_click_shuffle_all(true); let api = Rc::clone(self.imp().api.borrow().as_ref().unwrap()); + let albums_model = self.albums_model().clone(); glib::spawn_future_local(async move { use futures::TryStreamExt; - let mut count = 0; let mut albums = std::pin::pin!(api.album_list_full(crate::subsonic::AlbumListType::Newest)); - while albums.try_next().await.unwrap().is_some() { - count += 1; + while let Some(album) = albums.try_next().await.unwrap() { + albums_model.append( + &glib::Object::builder::() + .property("id", &album.id) + .property("name", &album.name) + .property("artist", &album.artist) + .build(), + ); } - println!("gathered {count} albums"); }); } @@ -732,4 +745,13 @@ impl Window { Ordering::Equal => {} } } + + pub fn stop(&self) { + self.imp().mpv.command(["stop"]).unwrap(); + self.playlist_model().remove_all(); + } + + pub fn api(&self) -> Rc { + Rc::clone(self.imp().api.borrow().as_ref().unwrap()) + } }