ShowWidget: Move controls into a headerbar menu.

This fits better the HIG and allows for more flexibility in the way
the ShowWidget is implemented/designed.
This commit is contained in:
Jordan Petridis 2018-07-21 09:47:08 +03:00
parent 5a6c73c4c1
commit 536805791e
No known key found for this signature in database
GPG Key ID: CEABAD9F5683B9A6
10 changed files with 328 additions and 229 deletions

View File

@ -257,6 +257,29 @@ Tobias Bernard
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkMenuButton" id="secondary_menu">
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="valign">center</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">view-more-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
<style>
<class name="image-button"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<child> <child>
<object class="GtkMenuButton" id="menu_button"> <object class="GtkMenuButton" id="menu_button">
<property name="visible">True</property> <property name="visible">True</property>

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<menu id="menu">
<section>
<item>
<attribute name="label" translatable="yes">_Mark all episodes as played</attribute>
<!-- <attribute name="action">win.refresh</attribute> -->
</item>
<item>
<attribute name="label" translatable="yes">_Website</attribute>
<!-- <attribute name="action">win.import</attribute> -->
</item>
<item>
<attribute name="label" translatable="yes">_Unsubscribe</attribute>
<!-- <attribute name="action">win.export</attribute> -->
</item>
</section>
</menu>
</interface>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkPopoverMenu" id="menu">
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_left">6</property>
<property name="margin_right">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkModelButton" id="website">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="text" translatable="yes">Open Website</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="played">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="text" translatable="yes">Mark all as played</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkModelButton" id="unsub">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="text" translatable="yes">Unsubscribe</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="submenu">main</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -79,13 +79,12 @@ Tobias Bernard
<property name="margin_bottom">32</property> <property name="margin_bottom">32</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">24</property>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">6</property> <property name="spacing">12</property>
<child> <child>
<object class="GtkImage" id="cover"> <object class="GtkImage" id="cover">
<property name="visible">True</property> <property name="visible">True</property>
@ -133,75 +132,6 @@ Sorry, we could not find a description for this Show.</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkMenuButton" id="settings_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="icon_name">emblem-system-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="link_button">
<property name="label" translatable="yes">Website</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="unsub_button">
<property name="label" translatable="yes">Unsubscribe</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">center</property>
<property name="valign">center</property>
<style>
<class name="destructive-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -213,6 +143,7 @@ Sorry, we could not find a description for this Show.</property>
<object class="GtkFrame"> <object class="GtkFrame">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_top">12</property>
<property name="label_xalign">0</property> <property name="label_xalign">0</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>

View File

@ -12,6 +12,7 @@
<file compressed="true" preprocess="xml-stripblanks">gtk/headerbar.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/headerbar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/inapp_notif.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/inapp_notif.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/hamburger.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/hamburger.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/show_menu.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/player_toolbar.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/player_toolbar.ui</file>
<file compressed="true">gtk/style.css</file> <file compressed="true">gtk/style.css</file>

View File

@ -11,14 +11,16 @@ use gtk::SettingsExt as GtkSettingsExt;
use crossbeam_channel::{unbounded, Receiver, Sender}; use crossbeam_channel::{unbounded, Receiver, Sender};
use hammond_data::Show; use hammond_data::Show;
use send_cell::SendCell;
use headerbar::Header; use headerbar::Header;
use settings::{self, WindowGeometry}; use settings::{self, WindowGeometry};
use stacks::{Content, PopulatedState}; use stacks::{Content, PopulatedState};
use utils; use utils;
use widgets::about_dialog;
use widgets::appnotif::{InAppNotification, UndoState}; use widgets::appnotif::{InAppNotification, UndoState};
use widgets::player; use widgets::player;
use widgets::{about_dialog, mark_all_notif, remove_show_notif}; use widgets::show_menu::{mark_all_notif, remove_show_notif, ShowMenu};
use std::env; use std::env;
use std::rc::Rc; use std::rc::Rc;
@ -56,6 +58,7 @@ pub enum Action {
RemoveShow(Arc<Show>), RemoveShow(Arc<Show>),
ErrorNotification(String), ErrorNotification(String),
InitEpisode(i32), InitEpisode(i32),
InitShowMenu(SendCell<ShowMenu>),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -265,6 +268,10 @@ impl App {
notif.show(&self.overlay); notif.show(&self.overlay);
} }
Action::InitEpisode(rowid) => self.player.initialize_episode(rowid).unwrap(), Action::InitEpisode(rowid) => self.player.initialize_episode(rowid).unwrap(),
Action::InitShowMenu(s) => {
let menu = s.borrow();
self.headerbar.set_secondary_menu(&menu.container);
}
} }
} }

View File

@ -27,6 +27,7 @@ pub struct Header {
app_menu: MenuModel, app_menu: MenuModel,
updater: UpdateIndicator, updater: UpdateIndicator,
add: AddPopover, add: AddPopover,
dots: gtk::MenuButton,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -141,6 +142,7 @@ impl Default for Header {
let back = builder.get_object("back").unwrap(); let back = builder.get_object("back").unwrap();
let show_title = builder.get_object("show_title").unwrap(); let show_title = builder.get_object("show_title").unwrap();
let menu_button = builder.get_object("menu_button").unwrap(); let menu_button = builder.get_object("menu_button").unwrap();
let dots = builder.get_object("secondary_menu").unwrap();
let app_menu = menus.get_object("menu").unwrap(); let app_menu = menus.get_object("menu").unwrap();
let update_box = builder.get_object("update_notification").unwrap(); let update_box = builder.get_object("update_notification").unwrap();
@ -175,11 +177,11 @@ impl Default for Header {
app_menu, app_menu,
updater, updater,
add, add,
dots,
} }
} }
} }
// TODO: Factor out the hamburger menu
// TODO: Make a proper state machine for the headerbar states // TODO: Make a proper state machine for the headerbar states
impl Header { impl Header {
pub fn new(content: &Content, sender: &Sender<Action>) -> Rc<Self> { pub fn new(content: &Content, sender: &Sender<Action>) -> Rc<Self> {
@ -209,13 +211,15 @@ impl Header {
let add_toggle = &s.add.toggle; let add_toggle = &s.add.toggle;
let show_title = &s.show_title; let show_title = &s.show_title;
let menu = &s.menu_button; let menu = &s.menu_button;
let dots = &s.dots;
s.back.connect_clicked( s.back.connect_clicked(
clone!(switch, add_toggle, show_title, sender, menu => move |back| { clone!(switch, add_toggle, show_title, sender, menu, dots => move |back| {
switch.show(); switch.show();
add_toggle.show(); add_toggle.show();
back.hide(); back.hide();
show_title.hide(); show_title.hide();
menu.show(); menu.show();
dots.hide();
sender.send(Action::ShowShowsAnimated); sender.send(Action::ShowShowsAnimated);
}), }),
); );
@ -230,6 +234,7 @@ impl Header {
self.set_show_title(title); self.set_show_title(title);
self.show_title.show(); self.show_title.show();
self.menu_button.hide(); self.menu_button.hide();
self.dots.show();
} }
pub fn switch_to_normal(&self) { pub fn switch_to_normal(&self) {
@ -238,6 +243,7 @@ impl Header {
self.back.hide(); self.back.hide();
self.show_title.hide(); self.show_title.hide();
self.menu_button.show(); self.menu_button.show();
self.dots.hide();
} }
pub fn set_show_title(&self, title: &str) { pub fn set_show_title(&self, title: &str) {
@ -255,4 +261,8 @@ impl Header {
pub fn open_menu(&self) { pub fn open_menu(&self) {
self.menu_button.clicked(); self.menu_button.clicked();
} }
pub fn set_secondary_menu(&self, pop: &gtk::PopoverMenu) {
self.dots.set_popover(Some(pop));
}
} }

View File

@ -5,6 +5,7 @@ mod episode;
mod home_view; mod home_view;
pub mod player; pub mod player;
mod show; mod show;
pub mod show_menu;
mod shows_view; mod shows_view;
pub use self::aboutdialog::about_dialog; pub use self::aboutdialog::about_dialog;
@ -12,5 +13,5 @@ pub use self::empty::EmptyView;
pub use self::episode::EpisodeWidget; pub use self::episode::EpisodeWidget;
pub use self::home_view::HomeView; pub use self::home_view::HomeView;
pub use self::show::ShowWidget; pub use self::show::ShowWidget;
pub use self::show::{mark_all_notif, remove_show_notif}; pub use self::show_menu::ShowMenu;
pub use self::shows_view::ShowsView; pub use self::shows_view::ShowsView;

View File

@ -5,18 +5,15 @@ use gtk::prelude::*;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use failure::Error; use failure::Error;
use html2text; use html2text;
use open;
use rayon; use rayon;
use send_cell::SendCell; use send_cell::SendCell;
use hammond_data::dbqueries; use hammond_data::dbqueries;
use hammond_data::utils::delete_show;
use hammond_data::Show; use hammond_data::Show;
use app::Action; use app::Action;
use utils::{self, lazy_load}; use utils::{self, lazy_load};
use widgets::appnotif::{InAppNotification, UndoState}; use widgets::{EpisodeWidget, ShowMenu};
use widgets::EpisodeWidget;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -32,9 +29,6 @@ pub struct ShowWidget {
scrolled_window: gtk::ScrolledWindow, scrolled_window: gtk::ScrolledWindow,
cover: gtk::Image, cover: gtk::Image,
description: gtk::Label, description: gtk::Label,
link: gtk::Button,
settings: gtk::MenuButton,
unsub: gtk::Button,
episodes: gtk::ListBox, episodes: gtk::ListBox,
show_id: Option<i32>, show_id: Option<i32>,
} }
@ -48,18 +42,12 @@ impl Default for ShowWidget {
let cover: gtk::Image = builder.get_object("cover").unwrap(); let cover: gtk::Image = builder.get_object("cover").unwrap();
let description: gtk::Label = builder.get_object("description").unwrap(); let description: gtk::Label = builder.get_object("description").unwrap();
let unsub: gtk::Button = builder.get_object("unsub_button").unwrap();
let link: gtk::Button = builder.get_object("link_button").unwrap();
let settings: gtk::MenuButton = builder.get_object("settings_button").unwrap();
ShowWidget { ShowWidget {
container, container,
scrolled_window, scrolled_window,
cover, cover,
description, description,
unsub,
link,
settings,
episodes, episodes,
show_id: None, show_id: None,
} }
@ -69,52 +57,26 @@ impl Default for ShowWidget {
impl ShowWidget { impl ShowWidget {
pub fn new(pd: Arc<Show>, sender: Sender<Action>) -> Rc<ShowWidget> { pub fn new(pd: Arc<Show>, sender: Sender<Action>) -> Rc<ShowWidget> {
let mut pdw = ShowWidget::default(); let mut pdw = ShowWidget::default();
pdw.init(&pd, &sender); pdw.init(&pd);
let menu = ShowMenu::new(&pd, &pdw.episodes, &sender);
sender.send(Action::InitShowMenu(SendCell::new(menu)));
let pdw = Rc::new(pdw); let pdw = Rc::new(pdw);
populate_listbox(&pdw, pd, sender) populate_listbox(&pdw, pd.clone(), sender)
.map_err(|err| error!("Failed to populate the listbox: {}", err)) .map_err(|err| error!("Failed to populate the listbox: {}", err))
.ok(); .ok();
pdw pdw
} }
pub fn init(&mut self, pd: &Arc<Show>, sender: &Sender<Action>) { pub fn init(&mut self, pd: &Arc<Show>) {
let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/show_widget.ui");
self.unsub
.connect_clicked(clone!(pd, sender => move |bttn| {
on_unsub_button_clicked(pd.clone(), bttn, &sender);
}));
self.set_description(pd.description()); self.set_description(pd.description());
self.show_id = Some(pd.id()); self.show_id = Some(pd.id());
self.set_cover(&pd) self.set_cover(&pd)
.map_err(|err| error!("Failed to set a cover: {}", err)) .map_err(|err| error!("Failed to set a cover: {}", err))
.ok(); .ok();
let link = pd.link().to_owned();
self.link.set_tooltip_text(Some(link.as_str()));
self.link.connect_clicked(move |_| {
info!("Opening link: {}", &link);
open::that(&link)
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Failed open link: {}", &link))
.ok();
});
let show_menu: gtk::Popover = builder.get_object("show_menu").unwrap();
let mark_all: gtk::ModelButton = builder.get_object("mark_all_watched").unwrap();
let episodes = self.episodes.clone();
mark_all.connect_clicked(clone!(pd, sender => move |_| {
on_played_button_clicked(
pd.clone(),
&episodes,
&sender
)
}));
self.settings.set_popover(&show_menu);
} }
/// Set the show cover. /// Set the show cover.
@ -173,6 +135,7 @@ impl ShowWidget {
/// Populate the listbox with the shows episodes. /// Populate the listbox with the shows episodes.
fn populate_listbox( fn populate_listbox(
// FIXME: Refference cycle
show: &Rc<ShowWidget>, show: &Rc<ShowWidget>,
pd: Arc<Show>, pd: Arc<Show>,
sender: Sender<Action>, sender: Sender<Action>,
@ -222,111 +185,3 @@ fn populate_listbox(
Ok(()) Ok(())
} }
fn on_unsub_button_clicked(pd: Arc<Show>, unsub_button: &gtk::Button, sender: &Sender<Action>) {
// hack to get away without properly checking for none.
// if pressed twice would panic.
unsub_button.set_sensitive(false);
sender.send(Action::RemoveShow(pd));
sender.send(Action::HeaderBarNormal);
sender.send(Action::ShowShowsAnimated);
// Queue a refresh after the switch to avoid blocking the db.
sender.send(Action::RefreshShowsView);
sender.send(Action::RefreshEpisodesView);
unsub_button.set_sensitive(true);
}
fn on_played_button_clicked(pd: Arc<Show>, episodes: &gtk::ListBox, sender: &Sender<Action>) {
if dim_titles(episodes).is_none() {
error!("Something went horribly wrong when dimming the titles.");
warn!("RUN WHILE YOU STILL CAN!");
}
sender.send(Action::MarkAllPlayerNotification(pd))
}
fn mark_all_watched(pd: &Show, sender: &Sender<Action>) -> Result<(), Error> {
dbqueries::update_none_to_played_now(pd)?;
// Not all widgets migth have been loaded when the mark_all is hit
// So we will need to refresh again after it's done.
sender.send(Action::RefreshWidgetIfSame(pd.id()));
sender.send(Action::RefreshEpisodesView);
Ok(())
}
pub fn mark_all_notif(pd: Arc<Show>, sender: &Sender<Action>) -> InAppNotification {
let id = pd.id();
let callback = clone!(sender => move || {
mark_all_watched(&pd, &sender)
.map_err(|err| error!("Notif Callback Error: {}", err))
.ok();
glib::Continue(false)
});
let undo_callback = clone!(sender => move || sender.send(Action::RefreshWidgetIfSame(id)));
let text = "Marked all episodes as listened";
InAppNotification::new(text, callback, undo_callback, UndoState::Shown)
}
pub fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppNotification {
let text = format!("Unsubscribed from {}", pd.title());
utils::ignore_show(pd.id())
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Could not insert {} to the ignore list.", pd.title()))
.ok();
let callback = clone!(pd, sender => move || {
utils::uningore_show(pd.id())
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Could not remove {} from the ignore list.", pd.title()))
.ok();
// Spawn a thread so it won't block the ui.
rayon::spawn(clone!(pd, sender => move || {
delete_show(&pd)
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Failed to delete {}", pd.title()))
.ok();
sender.send(Action::RefreshEpisodesView);
}));
glib::Continue(false)
});
let undo_callback = move || {
utils::uningore_show(pd.id())
.map_err(|err| error!("{}", err))
.ok();
sender.send(Action::RefreshShowsView);
sender.send(Action::RefreshEpisodesView);
};
InAppNotification::new(&text, callback, undo_callback, UndoState::Shown)
}
// 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: &gtk::ListBox) -> Option<()> {
let children = episodes.get_children();
for row in children {
let row = row.downcast::<gtk::ListBoxRow>().ok()?;
let container = row.get_children().remove(0).downcast::<gtk::Box>().ok()?;
let foo = container
.get_children()
.remove(0)
.downcast::<gtk::Box>()
.ok()?;
let bar = foo.get_children().remove(0).downcast::<gtk::Box>().ok()?;
let title = bar.get_children().remove(0).downcast::<gtk::Label>().ok()?;
title.get_style_context().map(|c| c.add_class("dim-label"));
}
Some(())
}

View File

@ -0,0 +1,181 @@
use glib;
use gtk;
use gtk::prelude::*;
use crossbeam_channel::Sender;
use failure::Error;
use open;
use rayon;
use hammond_data::dbqueries;
use hammond_data::utils::delete_show;
use hammond_data::Show;
use app::Action;
use utils;
use widgets::appnotif::{InAppNotification, UndoState};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ShowMenu {
pub container: gtk::PopoverMenu,
website: gtk::ModelButton,
played: gtk::ModelButton,
unsub: gtk::ModelButton,
}
impl Default for ShowMenu {
fn default() -> Self {
let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/show_menu.ui");
let container = builder.get_object("menu").unwrap();
let website = builder.get_object("website").unwrap();
let played = builder.get_object("played").unwrap();
let unsub = builder.get_object("unsub").unwrap();
ShowMenu {
container,
website,
played,
unsub,
}
}
}
impl ShowMenu {
pub fn new(pd: &Arc<Show>, episodes: &gtk::ListBox, sender: &Sender<Action>) -> Self {
let s = Self::default();
s.init(pd, episodes, sender);
s
}
fn init(&self, pd: &Arc<Show>, episodes: &gtk::ListBox, sender: &Sender<Action>) {
self.connect_website(pd);
self.connect_played(pd, episodes, sender);
self.connect_unsub(pd, sender)
}
fn connect_website(&self, pd: &Arc<Show>) {
self.website.set_tooltip_text(Some(pd.link()));
self.website.connect_clicked(clone!(pd => move |_| {
let link = pd.link();
info!("Opening link: {}", link);
open::that(link)
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Failed open link: {}", link))
.ok();
}));
}
fn connect_played(&self, pd: &Arc<Show>, episodes: &gtk::ListBox, sender: &Sender<Action>) {
self.played
.connect_clicked(clone!(pd, episodes, sender => move |_| {
let res = dim_titles(&episodes);
debug_assert!(res.is_some());
sender.send(Action::MarkAllPlayerNotification(pd.clone()))
}));
}
fn connect_unsub(&self, pd: &Arc<Show>, sender: &Sender<Action>) {
self.unsub
.connect_clicked(clone!(pd, sender => move |unsub| {
// hack to get away without properly checking for none.
// if pressed twice would panic.
unsub.set_sensitive(false);
sender.send(Action::RemoveShow(pd.clone()));
sender.send(Action::HeaderBarNormal);
sender.send(Action::ShowShowsAnimated);
// Queue a refresh after the switch to avoid blocking the db.
sender.send(Action::RefreshShowsView);
sender.send(Action::RefreshEpisodesView);
unsub.set_sensitive(true);
}));
}
}
// 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: &gtk::ListBox) -> Option<()> {
let children = episodes.get_children();
for row in children {
let row = row.downcast::<gtk::ListBoxRow>().ok()?;
let container = row.get_children().remove(0).downcast::<gtk::Box>().ok()?;
let foo = container
.get_children()
.remove(0)
.downcast::<gtk::Box>()
.ok()?;
let bar = foo.get_children().remove(0).downcast::<gtk::Box>().ok()?;
let title = bar.get_children().remove(0).downcast::<gtk::Label>().ok()?;
title.get_style_context().map(|c| c.add_class("dim-label"));
}
Some(())
}
fn mark_all_watched(pd: &Show, sender: &Sender<Action>) -> Result<(), Error> {
dbqueries::update_none_to_played_now(pd)?;
// Not all widgets migth have been loaded when the mark_all is hit
// So we will need to refresh again after it's done.
sender.send(Action::RefreshWidgetIfSame(pd.id()));
sender.send(Action::RefreshEpisodesView);
Ok(())
}
pub fn mark_all_notif(pd: Arc<Show>, sender: &Sender<Action>) -> InAppNotification {
let id = pd.id();
let callback = clone!(sender => move || {
mark_all_watched(&pd, &sender)
.map_err(|err| error!("Notif Callback Error: {}", err))
.ok();
glib::Continue(false)
});
let undo_callback = clone!(sender => move || sender.send(Action::RefreshWidgetIfSame(id)));
let text = "Marked all episodes as listened";
InAppNotification::new(text, callback, undo_callback, UndoState::Shown)
}
pub fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppNotification {
let text = format!("Unsubscribed from {}", pd.title());
utils::ignore_show(pd.id())
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Could not insert {} to the ignore list.", pd.title()))
.ok();
let callback = clone!(pd, sender => move || {
utils::uningore_show(pd.id())
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Could not remove {} from the ignore list.", pd.title()))
.ok();
// Spawn a thread so it won't block the ui.
rayon::spawn(clone!(pd, sender => move || {
delete_show(&pd)
.map_err(|err| error!("Error: {}", err))
.map_err(|_| error!("Failed to delete {}", pd.title()))
.ok();
sender.send(Action::RefreshEpisodesView);
}));
glib::Continue(false)
});
let undo_callback = move || {
utils::uningore_show(pd.id())
.map_err(|err| error!("{}", err))
.ok();
sender.send(Action::RefreshShowsView);
sender.send(Action::RefreshEpisodesView);
};
InAppNotification::new(&text, callback, undo_callback, UndoState::Shown)
}