diff --git a/Cargo.lock b/Cargo.lock index c38e7ab..1f5a897 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,6 +323,7 @@ dependencies = [ "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "gdk 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "gdk-pixbuf 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "gtk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.86 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 224376e..fa17ad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ cairo-rs = "0.5" dirs = "1.0" gdk = "0.9" gdk-pixbuf = "0.5" +glib = "0.6" gtk = { version = "0.5", features = [ "v3_22" ] } serde = "1.0" serde_derive = "1.0" diff --git a/src/api.rs b/src/api.rs index afb3e0c..0ca2bf6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -57,6 +57,7 @@ pub struct ArtistAlbum { pub title: String, pub tracks_count: i32, pub id: i32, + pub cover: Image, } #[derive(Deserialize, Serialize, Debug, Clone)] diff --git a/src/main.rs b/src/main.rs index 4751d44..64c7dc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,7 +93,9 @@ fn main() { let login_page = ui::login_page::render(state.clone()); state.borrow().stack.add_named(&login_page, "login"); - window.add(&state.borrow().stack); + let scrolled = ScrolledWindow::new(None, None); + scrolled.add(&state.borrow().stack); + window.add(&scrolled); window.show_all(); if state.borrow().instance.is_some() && state.borrow().token.is_some() { diff --git a/src/ui/card.rs b/src/ui/card.rs index 1d100d1..1b64225 100644 --- a/src/ui/card.rs +++ b/src/ui/card.rs @@ -1,6 +1,6 @@ use gtk::*; use std::{fs, thread}; -use crate::{Download, State, api}; +use crate::{Download, State, api, ui::network_image::NetworkImage}; pub struct Card { model: T, @@ -17,8 +17,12 @@ impl Card where T: CardModel { pub fn render(&self) -> Grid { let card = Grid::new(); + card.set_column_spacing(12); + card.set_valign(Align::Start); + if let Some(url) = self.model.image_url() { - // TODO + let img = NetworkImage::new(format!("https://{}{}", self.state.borrow().instance.clone().unwrap(), url)); + card.attach(&*img.img.borrow(), 0, 0, 1, 2); } let main_text = Label::new(self.model.text().as_ref()); @@ -30,11 +34,22 @@ impl Card where T: CardModel { sub_text.set_hexpand(true); sub_text.set_halign(Align::Start); - let dl_bt = Button::new_from_icon_name("go-down", 32); - dl_bt.set_label("Download"); + let dl_bt = Button::new_with_label("Download"); + dl_bt.set_valign(Align::Center); + dl_bt.set_vexpand(true); + dl_bt.get_style_context().map(|c| c.add_class("suggested-action")); + + let dl_list = self.model.downloads(self.state.clone()); + if dl_list.len() > 1 { // Not only one song + let more_bt = Button::new_with_label("Details"); + more_bt.set_valign(Align::Center); + more_bt.set_vexpand(true); + card.attach(&more_bt, 2, 0, 1, 2); + } + let state = self.state.clone(); let model = self.model.clone(); - dl_bt.connect_clicked(move |_| { + dl_bt.connect_clicked(clone!(state, model => move |_| { let downloads = state.borrow().downloads.clone(); for dl in model.downloads(state.clone()) { let token = state.borrow().token.clone().unwrap_or_default(); @@ -53,11 +68,11 @@ impl Card where T: CardModel { println!("saved {:?}", dl.output); }); } - }); + })); - card.attach(&main_text, 0, 0, 1, 1); - card.attach(&sub_text, 0, 1, 1, 1); - card.attach(&dl_bt, 1, 0, 2, 1); + card.attach(&main_text, 1, 0, 1, 1); + card.attach(&sub_text, 1, 1, 1, 1); + card.attach(&dl_bt, 3, 0, 1, 2); card } @@ -84,6 +99,12 @@ impl CardModel for api::Artist { format!("{} albums", self.albums.clone().unwrap().len()) } + fn image_url(&self) -> Option { + self.albums.clone()?.iter() + .next() + .and_then(|album| album.cover.medium_square_crop.clone()) + } + fn downloads(&self, state: State) -> Vec { let mut dls = vec![]; for album in self.albums.clone().unwrap_or_default() { @@ -120,6 +141,10 @@ impl CardModel for api::Album { format!("{} tracks, by {}", self.tracks.clone().map(|t| t.len()).unwrap_or_default(), self.artist.name) } + fn image_url(&self) -> Option { + self.cover.medium_square_crop.clone() + } + fn downloads(&self, state: State) -> Vec { self.tracks.clone().unwrap_or_default().iter().filter_map(|track| api::Upload::get_for_track(track.id, state.borrow().instance.clone().unwrap(), state.borrow().token.clone().unwrap()).map(|u| Download { @@ -140,6 +165,10 @@ impl CardModel for api::Track { format!("By {}, in {}", self.artist.name, self.album.title) } + fn image_url(&self) -> Option { + self.album.cover.medium_square_crop.clone() + } + fn downloads(&self, state: State) -> Vec { println!("yoy"); let upload = match api::Upload::get_for_track(self.id, state.borrow().instance.clone().unwrap(), state.borrow().token.clone().unwrap()) { diff --git a/src/ui/main_page.rs b/src/ui/main_page.rs index e9511f7..00813ab 100644 --- a/src/ui/main_page.rs +++ b/src/ui/main_page.rs @@ -82,6 +82,7 @@ pub fn render(state: State) -> gtk::Box { cont.add(&search); let results = gtk::Box::new(Orientation::Vertical, 12); + results.set_valign(Align::Start); cont.add(&results); let widgets = Rc::new(RefCell::new( diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 5facb01..af5e172 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,6 +3,7 @@ use gtk::prelude::*; pub mod card; pub mod login_page; pub mod main_page; +pub mod network_image; fn title(text: &str) -> gtk::Label { let lbl = gtk::Label::new(text); diff --git a/src/ui/network_image.rs b/src/ui/network_image.rs new file mode 100644 index 0000000..b7a2c59 --- /dev/null +++ b/src/ui/network_image.rs @@ -0,0 +1,45 @@ +use gtk::*; +use std::{ + sync::mpsc, cell::RefCell, + fs, thread, rc::Rc, time::Duration, +}; + +pub struct NetworkImage { + pub img: Rc>, +} + +impl NetworkImage { + pub fn new(url: String) -> NetworkImage { + let image = Rc::new(RefCell::new( + Image::new_from_icon_name("image-loading", 4) + )); + let dest_file = url.split("/media/").last().unwrap().replace('/', "-"); + let dest = dirs::cache_dir().unwrap().join(env!("CARGO_PKG_NAME")).join(dest_file.to_string()); + let (tx, rx) = mpsc::channel(); + thread::spawn(clone!(dest => move || { + fs::create_dir_all(dest.parent().unwrap()).unwrap(); + let mut file = fs::File::create(dest.clone()).unwrap(); // TODO: check if it exists + + reqwest::Client::new() + .get(&url) + .send() + .unwrap() + .copy_to(&mut file) + .unwrap(); + tx.send(dest).unwrap(); + })); + gtk::idle_add(clone!(image => move || { // Check every 0.5s + match rx.recv_timeout(Duration::from_millis(500)) { + Err(_) => glib::Continue(true), + Ok(res) => { + let pb = gdk_pixbuf::Pixbuf::new_from_file_at_scale(res, 64, 64, true).unwrap(); + image.borrow().set_from_pixbuf(&pb); + glib::Continue(false) + } + } + })); + NetworkImage { + img: image.clone(), + } + } +}