diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf5e9b..c3caaa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +* Ability to mark all episodes of a Show as watched. (#47)[https://gitlab.gnome.org/alatiera/Hammond/issues/47] + ## [0.3.0] - 2018-02-11 * Tobias Bernard Redesigned the whole Gtk+ client. diff --git a/hammond-data/src/dbqueries.rs b/hammond-data/src/dbqueries.rs index 27c46f7..04bdcdc 100644 --- a/hammond-data/src/dbqueries.rs +++ b/hammond-data/src/dbqueries.rs @@ -348,3 +348,29 @@ pub fn update_none_to_played_now(parent: &Podcast) -> Result { .map_err(From::from) }) } + +#[cfg(test)] +mod tests { + use super::*; + use database::*; + use pipeline::*; + + #[test] + fn test_update_none_to_played_now() { + truncate_db().unwrap(); + + let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\ + com/InterceptedWithJeremyScahill"; + let source = Source::from_url(url).unwrap(); + let id = source.id(); + index_single_source(source, true).unwrap(); + let pd = get_podcast_from_source_id(id).unwrap(); + + let eps_num = get_pd_unplayed_episodes(&pd).unwrap().len(); + assert_ne!(eps_num, 0); + + update_none_to_played_now(&pd).unwrap(); + let eps_num2 = get_pd_unplayed_episodes(&pd).unwrap().len(); + assert_eq!(eps_num2, 0); + } +} diff --git a/hammond-gtk/resources/gtk/show_widget.ui b/hammond-gtk/resources/gtk/show_widget.ui index fca068c..5bd1c86 100644 --- a/hammond-gtk/resources/gtk/show_widget.ui +++ b/hammond-gtk/resources/gtk/show_widget.ui @@ -68,167 +68,250 @@ Tobias Bernard - - 600 + True False - 32 - 32 - 32 - 32 - False - vertical - 24 + 600 True False - center - 12 - - - True - False - 128 - image-x-generic-symbolic - - - False - False - 0 - - + 32 + 32 + 32 + 32 + False + vertical + 24 True False - end - True - vertical - 6 - - + center + 12 + + True False - start - end - Show description - True - word-char - 100 - - - + 128 + image-x-generic-symbolic False False - 1 + 0 True False + end + True + vertical 6 - - + + True - True - True - - - True - False - center - center - emblem-system-symbolic - - + False + start + end + Show description + True + word-char + 100 + + + False - True - 0 - - - - - Website - True - True - True - center - center - - - False - True - 5 + False 1 - - Unsubscribe + True - True - True - center - center - + False + 6 + + + True + True + True + + + True + False + center + center + emblem-system-symbolic + + + + + False + True + 0 + + + + + Website + True + True + True + center + center + + + False + True + 5 + 1 + + + + + Unsubscribe + True + True + True + center + center + + + + False + True + 5 + end + 2 + + False - True - 5 + False end - 2 + 0 False - False - end - 0 + True + 1 False - True + False 1 + + + True + False + True + 0 + in + + + + + + + + + False + False + 2 + + - False - False - 0 + -1 - - + + True False - True - 0 - in + center + start - - - - + + True + False + + + True + False + center + center + 0 + none + + + True + False + center + center + 6 + 6 + 12 + + + True + False + center + center + An in-app action notification + + + False + False + 0 + + + + + Undo + True + True + True + center + center + + + False + False + end + 1 + + + + + + + + + + + + + -1 + - - False - False - 1 - @@ -266,4 +349,29 @@ Tobias Bernard + + False + bottom + + + True + False + vertical + + + True + True + True + Mark all epiodes as Watched + True + + + False + True + 0 + + + + + diff --git a/hammond-gtk/src/widgets/show.rs b/hammond-gtk/src/widgets/show.rs index 0a5808d..b2791f5 100644 --- a/hammond-gtk/src/widgets/show.rs +++ b/hammond-gtk/src/widgets/show.rs @@ -1,5 +1,6 @@ use dissolve; use failure::Error; +use glib; use gtk; use gtk::prelude::*; use open; @@ -12,6 +13,8 @@ use app::Action; use utils::get_pixbuf_from_path; use widgets::episode::episodes_listbox; +use std::cell::RefCell; +use std::rc::Rc; use std::sync::Arc; use std::sync::mpsc::Sender; use std::thread; @@ -26,6 +29,9 @@ pub struct ShowWidget { settings: gtk::MenuButton, unsub: gtk::Button, episodes: gtk::Frame, + notif: gtk::Revealer, + notif_label: gtk::Label, + notif_undo: gtk::Button, } impl Default for ShowWidget { @@ -41,6 +47,10 @@ impl Default for ShowWidget { let link: gtk::Button = builder.get_object("link_button").unwrap(); let settings: gtk::MenuButton = builder.get_object("settings_button").unwrap(); + let notif: gtk::Revealer = builder.get_object("notif_revealer").unwrap(); + let notif_label: gtk::Label = builder.get_object("notif_label").unwrap(); + let notif_undo: gtk::Button = builder.get_object("undo_button").unwrap(); + ShowWidget { container, scrolled_window, @@ -50,6 +60,9 @@ impl Default for ShowWidget { link, settings, episodes, + notif, + notif_label, + notif_undo, } } } @@ -62,6 +75,8 @@ impl ShowWidget { } pub fn init(&self, pd: Arc, sender: Sender) { + let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/show_widget.ui"); + // Hacky workaround so the pd.id() can be retrieved from the `ShowStack`. WidgetExt::set_name(&self.container, &pd.id().to_string()); @@ -88,6 +103,25 @@ impl ShowWidget { error!("Error: {}", err); } }); + + let show_menu: gtk::Popover = builder.get_object("show_menu").unwrap(); + let mark_all: gtk::ModelButton = builder.get_object("mark_all_watched").unwrap(); + + let notif = self.notif.clone(); + let notif_label = self.notif_label.clone(); + let notif_undo = self.notif_undo.clone(); + let episodes = self.episodes.clone(); + mark_all.connect_clicked(clone!(pd, sender => move |_| { + on_played_button_clicked( + pd.clone(), + ¬if, + ¬if_label, + ¬if_undo, + &episodes, + sender.clone() + ) + })); + self.settings.set_popover(&show_menu); } /// Populate the listbox with the shows episodes. @@ -142,9 +176,83 @@ fn on_unsub_button_clicked( Ok(()) } -#[allow(dead_code)] -fn on_played_button_clicked(pd: &Podcast, sender: Sender) -> Result<(), Error> { +fn on_played_button_clicked( + pd: Arc, + notif: >k::Revealer, + label: >k::Label, + undo: >k::Button, + episodes: >k::Frame, + sender: Sender, +) { + if dim_titles(episodes).is_none() { + error!("Something went horribly wrong when dimming the titles."); + warn!("RUN WHILE YOU STILL CAN!"); + } + + label.set_text("All episodes where marked as watched."); + notif.set_reveal_child(true); + + // Set up the callback + let id = timeout_add_seconds( + 10, + clone!(sender => move || { + if let Err(err) = wrap(&pd, sender.clone()) { + error!( + "Something went horribly wrong with the notif callback: {}", + err + ); + } + glib::Continue(false) + }), + ); + + let id = Rc::new(RefCell::new(Some(id))); + + // Cancel the callback + undo.connect_clicked(clone!(id, notif, sender => move |_| { + let foo = id.borrow_mut().take(); + if let Some(id) = foo { + glib::source::source_remove(id); + notif.set_reveal_child(false); + if let Err(err) = sender.send(Action::RefreshWidgetIfVis) { + error!("Something went horribly wrong with the Action channel: {}", err) + } + } + })); +} + +fn wrap(pd: &Podcast, sender: Sender) -> Result<(), Error> { dbqueries::update_none_to_played_now(pd)?; - sender.send(Action::RefreshWidget)?; + sender.send(Action::RefreshWidgetIfVis)?; + sender.send(Action::RefreshEpisodesView)?; Ok(()) } + +// Ideally if we had a custom widget this would have been as simple as: +// `for row in listbox { ep = row.get_episode(); ep.dim_title(); }` +// But now I can't think of a better way to do it than hardcoding the title +// position relative to the EpisodeWidget container gtk::Box. +fn dim_titles(episodes: >k::Frame) -> Option<()> { + let listbox = episodes + .get_children() + .remove(0) + .downcast::() + .ok()?; + let children = listbox.get_children(); + + for row in children { + let row = row.downcast::().ok()?; + let container = row.get_children().remove(0).downcast::().ok()?; + let foo = container + .get_children() + .remove(0) + .downcast::() + .ok()?; + let bar = foo.get_children().remove(0).downcast::().ok()?; + let baz = bar.get_children().remove(0).downcast::().ok()?; + let title = baz.get_children().remove(0).downcast::().ok()?; + + title.get_style_context().map(|c| c.add_class("dim-label")); + } + Some(()) +}