From 5b0a7124a8948d93ad4bba3c4a72fc3fa1a7d319 Mon Sep 17 00:00:00 2001 From: Erica Z Date: Sun, 1 Dec 2024 01:09:57 +0100 Subject: [PATCH] make star button work --- resources/play_queue_song.blp | 4 ++-- resources/playbar.blp | 3 ++- resources/window.blp | 1 + src/model/song.rs | 2 ++ src/subsonic.rs | 10 ++++++++++ src/ui/play_queue/song.rs | 36 +++++++++++++++++++++++++++++++++++ src/ui/playbar.rs | 31 ++++++++++++++++++++++++++++++ 7 files changed, 84 insertions(+), 3 deletions(-) diff --git a/resources/play_queue_song.blp b/resources/play_queue_song.blp index 1c4e71d..0b7b3fa 100644 --- a/resources/play_queue_song.blp +++ b/resources/play_queue_song.blp @@ -110,8 +110,8 @@ template $AudreyUiPlayQueueSong: Box { Button { focusable: true; - // TODO icon-name: bind $star_button_icon_name (template.song as <$AudreyModelSong>.starred) as ; - icon-name: bind $star_button_icon_name() as ; + icon-name: bind $heart_button_icon_name(template.song as <$AudreyModelSong>.starred) as ; + clicked => $on_heart_button_clicked() swapped; styles [ "flat" diff --git a/resources/playbar.blp b/resources/playbar.blp index a38c2fb..dc10a53 100644 --- a/resources/playbar.blp +++ b/resources/playbar.blp @@ -176,10 +176,11 @@ template $AudreyUiPlaybar: Adw.Bin { } Button { - icon-name: "heart-empty-symbolic"; // placeholder + icon-name: bind $heart_button_icon_name(template.song as <$AudreyModelSong>.starred) as ; halign: end; valign: center; sensitive: bind template.idle-active inverted; + clicked => $on_heart_button_clicked() swapped; } Button { diff --git a/resources/window.blp b/resources/window.blp index a3d7f49..2be0c2a 100644 --- a/resources/window.blp +++ b/resources/window.blp @@ -94,6 +94,7 @@ template $AudreyUiWindow: Adw.ApplicationWindow { duration: bind template.duration; idle-active: bind template.idle-active; playlist-count: bind template.playlist-count; + client: bind template.client; } } } diff --git a/src/model/song.rs b/src/model/song.rs index fa83a8c..d336528 100644 --- a/src/model/song.rs +++ b/src/model/song.rs @@ -32,6 +32,8 @@ mod imp { track: Cell, #[property(get, set)] cover_art: RefCell, + #[property(get, set)] + starred: Cell, #[property(get, set)] stream_url: RefCell, diff --git a/src/subsonic.rs b/src/subsonic.rs index 5c2318e..84e969d 100644 --- a/src/subsonic.rs +++ b/src/subsonic.rs @@ -299,6 +299,16 @@ impl Client { pub fn stream_url(&self, id: &str) -> url::Url { self.url(&["rest", "stream"], &[("id", id)]) } + + pub async fn star(&self, id: &str) -> Result<(), Error> { + let url = self.url(&["rest", "star"], &[("id", id)]); + self.get_bytes(url).await.map(|_| ()) + } + + pub async fn unstar(&self, id: &str) -> Result<(), Error> { + let url = self.url(&["rest", "unstar"], &[("id", id)]); + self.get_bytes(url).await.map(|_| ()) + } } impl Drop for Client { diff --git a/src/ui/play_queue/song.rs b/src/ui/play_queue/song.rs index 5a47508..3f2b0eb 100644 --- a/src/ui/play_queue/song.rs +++ b/src/ui/play_queue/song.rs @@ -3,6 +3,7 @@ use adw::prelude::*; use glib::subclass::InitializingObject; use gtk::{gdk, gio, glib, subclass::prelude::*}; use std::cell::{Cell, RefCell}; +use tracing::{event, Level}; mod imp { use super::*; @@ -35,6 +36,9 @@ mod imp { drag_pos: Cell<(i32, i32)>, drag_widget: Cell>, + + #[property(get, set)] + client: RefCell>, } #[glib::object_subclass] @@ -174,6 +178,37 @@ mod imp { self.obj() .set_current(playlist_pos == self.position.get() as i64); } + + // FIXME: duplicated in playbar.... + #[template_callback] + fn heart_button_icon_name(&self, starred: bool) -> &'static str { + if starred { + "heart-full-symbolic" + } else { + "heart-empty-symbolic" + } + } + + #[template_callback] + async fn on_heart_button_clicked(&self) { + // TODO: disable button while await ongoing?? consider making star/unstar methods in + // the model class and add a semaphore there for serialization + // FIXME: doesn't update all if track is duplicated in the play queue + + let song = self.obj().song().unwrap(); + let client = self.obj().client().unwrap(); + if song.starred() { + match client.unstar(&song.id()).await { + Ok(()) => song.set_starred(false), + Err(err) => event!(Level::ERROR, "could not unstar song {}: {err}", song.id()), + } + } else { + match client.star(&song.id()).await { + Ok(()) => song.set_starred(true), + Err(err) => event!(Level::ERROR, "could not star song {}: {err}", song.id()), + } + } + } } } @@ -199,6 +234,7 @@ impl Song { self.set_song(&song); self.set_position(position); self.set_playlist_pos(window.playlist_pos()); + self.set_client(window.client().unwrap()); song.need_thumbnail( window.client().as_ref().unwrap(), diff --git a/src/ui/playbar.rs b/src/ui/playbar.rs index e57db49..2838424 100644 --- a/src/ui/playbar.rs +++ b/src/ui/playbar.rs @@ -43,6 +43,9 @@ mod imp { #[property(get, set)] _show_pulse_bar: Cell, + + #[property(get, set)] + client: RefCell>, } #[glib::object_subclass] @@ -162,6 +165,34 @@ mod imp { fn song_album(&self, song: Option<&Song>) -> String { song.map(Song::album).unwrap_or_default() } + + #[template_callback] + fn heart_button_icon_name(&self, starred: bool) -> &'static str { + if starred { + "heart-full-symbolic" + } else { + "heart-empty-symbolic" + } + } + + #[template_callback] + async fn on_heart_button_clicked(&self) { + // TODO: disable button while await ongoing?? + + let song = self.obj().song().unwrap(); + let client = self.obj().client().unwrap(); + if song.starred() { + match client.unstar(&song.id()).await { + Ok(()) => song.set_starred(false), + Err(err) => event!(Level::ERROR, "could not unstar song {}: {err}", song.id()), + } + } else { + match client.star(&song.id()).await { + Ok(()) => song.set_starred(true), + Err(err) => event!(Level::ERROR, "could not star song {}: {err}", song.id()), + } + } + } } impl Drop for Playbar {