carousel thing

This commit is contained in:
Erica Z 2024-11-24 19:53:57 +01:00
parent c43adb66eb
commit a33fe4ba63
6 changed files with 106 additions and 31 deletions

View file

@ -80,7 +80,7 @@ template $AudreyUiAlbumCarousel: Adw.Bin {
homogeneous: true; homogeneous: true;
Label { Label {
label: bind template.item as <StringObject>.string; label: bind template.item as <$AudreyModelAlbum>.name;
ellipsize: end; ellipsize: end;
xalign: 0; xalign: 0;
margin-start: 6; margin-start: 6;
@ -91,7 +91,7 @@ template $AudreyUiAlbumCarousel: Adw.Bin {
} }
Label { Label {
label: bind template.item as <StringObject>.string; label: bind template.item as <$AudreyModelAlbum>.artist;
ellipsize: end; ellipsize: end;
xalign: 0; xalign: 0;
margin-start: 6; margin-start: 6;

View file

@ -41,22 +41,22 @@ template $AudreyUiWindow: Adw.ApplicationWindow {
child: Box { child: Box {
orientation: vertical; orientation: vertical;
$AudreyUiAlbumCarousel { $AudreyUiAlbumCarousel carousel1 {
title: _("Explore from your library"); title: _("Explore from your library");
type: random; type: random;
} }
$AudreyUiAlbumCarousel { $AudreyUiAlbumCarousel carousel2 {
title: _("Newly added releases"); title: _("Newly added releases");
type: newest; type: newest;
} }
$AudreyUiAlbumCarousel { $AudreyUiAlbumCarousel carousel3 {
title: _("Recently played"); title: _("Recently played");
type: recent; type: recent;
} }
$AudreyUiAlbumCarousel { $AudreyUiAlbumCarousel carousel4 {
title: _("Most played"); title: _("Most played");
type: frequent; type: frequent;
} }

View file

@ -30,3 +30,15 @@ mod imp {
glib::wrapper! { glib::wrapper! {
pub struct Album(ObjectSubclass<imp::Album>); pub struct Album(ObjectSubclass<imp::Album>);
} }
impl Album {
pub fn from_api(album: &crate::subsonic::schema::AlbumID3) -> Self {
let album: Self = glib::Object::builder()
.property("id", &album.id)
.property("name", &album.name)
.property("artist", &album.artist)
.build();
album
}
}

View file

@ -22,21 +22,24 @@ impl Client {
size: u32, size: u32,
offset: u32, offset: u32,
) -> Result<Vec<schema::AlbumID3>, Error> { ) -> Result<Vec<schema::AlbumID3>, Error> {
match type_ { let type_ = match type_ {
AlbumListType::Newest => { AlbumListType::Newest => "newest",
self.get_subsonic::<schema::AlbumList2Outer>(self.url( AlbumListType::Random => "random",
&["rest", "getAlbumList2"], AlbumListType::Recent => "recent",
&[ AlbumListType::Frequent => "frequent",
("type", "newest"),
("size", &size.to_string()),
("offset", &offset.to_string()),
],
))
.await
}
_ => todo!("{type_:?}"), _ => todo!("{type_:?}"),
} };
self.get_subsonic::<schema::AlbumList2Outer>(self.url(
&["rest", "getAlbumList2"],
&[
("type", type_),
("size", &size.to_string()),
("offset", &offset.to_string()),
],
))
.await
.map(|response| response.album_list2.album.unwrap_or_default()) .map(|response| response.album_list2.album.unwrap_or_default())
} }

View file

@ -1,9 +1,12 @@
use crate::subsonic::AlbumListType;
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use adw::{gio, glib}; use adw::{gio, glib};
use glib::subclass::InitializingObject; use glib::subclass::InitializingObject;
use std::cell::{Cell, OnceCell, RefCell}; use std::cell::{Cell, OnceCell, RefCell};
use std::rc::Rc;
use std::sync::OnceLock; use std::sync::OnceLock;
use tracing::{event, Level};
#[derive(Debug, Copy, Clone, PartialEq, Eq, glib::Enum)] #[derive(Debug, Copy, Clone, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "AudreyUiAlbumCarouselType")] #[enum_type(name = "AudreyUiAlbumCarouselType")]
@ -26,6 +29,7 @@ mod imp {
#[derive(gtk::CompositeTemplate, Default)] #[derive(gtk::CompositeTemplate, Default)]
#[template(resource = "/eu/callcc/audrey/album_carousel.ui")] #[template(resource = "/eu/callcc/audrey/album_carousel.ui")]
pub struct AlbumCarousel { pub struct AlbumCarousel {
pub api: RefCell<Option<Rc<crate::subsonic::Client>>>,
title: RefCell<String>, title: RefCell<String>,
model: OnceCell<gio::ListStore>, model: OnceCell<gio::ListStore>,
r#type: Cell<Type>, r#type: Cell<Type>,
@ -95,16 +99,43 @@ mod imp {
.get_or_init(gio::ListStore::new::<crate::model::Album>) .get_or_init(gio::ListStore::new::<crate::model::Album>)
} }
fn update_model(&self) { pub fn update_model(&self) {
let window: crate::ui::Window = match self.obj().root() { self.model().remove_all();
None => return,
Some(root) => root.dynamic_cast().unwrap(),
};
let _api = window.api();
match self.r#type.get() { let api = self.api.borrow();
_ => todo!(), let api = match api.as_ref() {
} Some(api) => api,
None => return,
};
let api = Rc::clone(&api);
let carousel = self.obj().downgrade();
let type_ = match self.r#type.get() {
Type::Random => AlbumListType::Random,
Type::Newest => AlbumListType::Newest,
Type::Recent => AlbumListType::Recent,
Type::Frequent => AlbumListType::Frequent,
};
glib::spawn_future_local(async move {
let album_list = match api.album_list(&type_, 50, 0).await {
Ok(album_list) => album_list,
Err(err) => {
event!(Level::ERROR, "could not fetch albums for carousel: {}", err);
return;
}
};
// TODO: handle to cancel on drop etc etc
let carousel = carousel.upgrade().unwrap();
for album in album_list {
carousel
.imp()
.model()
.append(&crate::model::Album::from_api(&album));
}
});
} }
} }
} }
@ -114,3 +145,10 @@ glib::wrapper! {
@extends adw::Bin, gtk::Widget, @extends adw::Bin, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
} }
impl AlbumCarousel {
pub fn set_api(&self, api: Option<&Rc<crate::subsonic::Client>>) {
self.imp().api.replace(api.map(Rc::clone));
self.imp().update_model();
}
}

View file

@ -1,4 +1,5 @@
use crate::model::Song; use crate::model::Song;
use crate::ui::AlbumCarousel;
use crate::util::buffering::BufferingPulseController; use crate::util::buffering::BufferingPulseController;
use crate::{mpris, mpv}; use crate::{mpris, mpv};
use adw::prelude::*; use adw::prelude::*;
@ -90,6 +91,15 @@ mod imp {
refreshing_albums: Cell<bool>, refreshing_albums: Cell<bool>,
css_provider: gtk::CssProvider, css_provider: gtk::CssProvider,
#[template_child]
pub carousel1: TemplateChild<AlbumCarousel>,
#[template_child]
pub carousel2: TemplateChild<AlbumCarousel>,
#[template_child]
pub carousel3: TemplateChild<AlbumCarousel>,
#[template_child]
pub carousel4: TemplateChild<AlbumCarousel>,
} }
impl Default for Window { impl Default for Window {
@ -152,6 +162,11 @@ mod imp {
refreshing_albums: Cell::new(true), refreshing_albums: Cell::new(true),
css_provider: gtk::CssProvider::new(), css_provider: gtk::CssProvider::new(),
carousel1: Default::default(),
carousel2: Default::default(),
carousel3: Default::default(),
carousel4: Default::default(),
} }
} }
} }
@ -1011,11 +1026,18 @@ impl Window {
} }
pub fn setup_connected(&self, api: crate::subsonic::Client) { pub fn setup_connected(&self, api: crate::subsonic::Client) {
if self.imp().api.replace(Some(Rc::new(api))).is_some() { let api = Rc::new(api);
unimplemented!("changing the api object");
}
self.set_can_click_shuffle_all(true); self.set_can_click_shuffle_all(true);
self.imp().carousel1.set_api(Some(&api));
self.imp().carousel2.set_api(Some(&api));
self.imp().carousel3.set_api(Some(&api));
self.imp().carousel4.set_api(Some(&api));
if self.imp().api.replace(Some(api)).is_some() {
unimplemented!("changing the api object");
}
} }
pub fn playlist_next(&self) { pub fn playlist_next(&self) {