Merge branch 'alatiera/move-update-notif' into 'master'
Move the update indication into an In-app Notification Closes #72 See merge request World/podcasts!60
This commit is contained in:
commit
646439d86a
@ -271,41 +271,5 @@ Tobias Bernard
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="update_notification">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="update_spinner">
|
||||
<property name="can_focus">False</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">6</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="update_label">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes">Fetching new episodes</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@ -53,20 +53,12 @@ Tobias Bernard
|
||||
<property name="margin_end">3</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="text">
|
||||
<property name="width_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_start">12</property>
|
||||
<property name="margin_end">12</property>
|
||||
<property name="label" translatable="yes">An in-app action notification</property>
|
||||
<property name="ellipsize">start</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
@ -97,10 +89,27 @@ Tobias Bernard
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="text">
|
||||
<property name="width_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_start">12</property>
|
||||
<property name="margin_end">12</property>
|
||||
<property name="label" translatable="yes">An in-app action notification</property>
|
||||
<property name="ellipsize">start</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="undo">
|
||||
<property name="label" translatable="yes">Undo</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="halign">center</property>
|
||||
@ -113,7 +122,7 @@ Tobias Bernard
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@ -17,10 +17,11 @@ use settings::{self, WindowGeometry};
|
||||
use stacks::{Content, PopulatedState};
|
||||
use utils;
|
||||
use widgets::about_dialog;
|
||||
use widgets::appnotif::{InAppNotification, UndoState};
|
||||
use widgets::appnotif::{InAppNotification, SpinnerState, State};
|
||||
use widgets::player;
|
||||
use widgets::show_menu::{mark_all_notif, remove_show_notif, ShowMenu};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@ -57,9 +58,8 @@ pub(crate) enum Action {
|
||||
ShowShowsAnimated,
|
||||
HeaderBarShowTile(String),
|
||||
HeaderBarNormal,
|
||||
HeaderBarShowUpdateIndicator,
|
||||
HeaderBarHideUpdateIndicator,
|
||||
MarkAllPlayerNotification(Arc<Show>),
|
||||
ShowUpdateNotif(Receiver<bool>),
|
||||
RemoveShow(Arc<Show>),
|
||||
ErrorNotification(String),
|
||||
InitEpisode(i32),
|
||||
@ -75,6 +75,7 @@ pub(crate) struct App {
|
||||
content: Rc<Content>,
|
||||
headerbar: Rc<Header>,
|
||||
player: Rc<player::PlayerWidget>,
|
||||
updater: RefCell<Option<InAppNotification>>,
|
||||
sender: Sender<Action>,
|
||||
receiver: Receiver<Action>,
|
||||
}
|
||||
@ -130,6 +131,8 @@ impl App {
|
||||
// Add the player to the main Box
|
||||
wrap.add(&player.action_bar);
|
||||
|
||||
let updater = RefCell::new(None);
|
||||
|
||||
window.add(&wrap);
|
||||
|
||||
let app = App {
|
||||
@ -140,6 +143,7 @@ impl App {
|
||||
headerbar: header,
|
||||
content,
|
||||
player,
|
||||
updater,
|
||||
sender,
|
||||
receiver,
|
||||
};
|
||||
@ -293,8 +297,6 @@ impl App {
|
||||
}
|
||||
Action::HeaderBarShowTile(title) => self.headerbar.switch_to_back(&title),
|
||||
Action::HeaderBarNormal => self.headerbar.switch_to_normal(),
|
||||
Action::HeaderBarShowUpdateIndicator => self.headerbar.show_update_notification(),
|
||||
Action::HeaderBarHideUpdateIndicator => self.headerbar.hide_update_notification(),
|
||||
Action::MarkAllPlayerNotification(pd) => {
|
||||
let notif = mark_all_notif(pd, &self.sender);
|
||||
notif.show(&self.overlay);
|
||||
@ -305,10 +307,38 @@ impl App {
|
||||
}
|
||||
Action::ErrorNotification(err) => {
|
||||
error!("An error notification was triggered: {}", err);
|
||||
let callback = || glib::Continue(false);
|
||||
let notif = InAppNotification::new(&err, callback, || {}, UndoState::Hidden);
|
||||
let callback = |revealer: gtk::Revealer| {
|
||||
revealer.set_reveal_child(false);
|
||||
glib::Continue(false)
|
||||
};
|
||||
let undo_cb: Option<fn()> = None;
|
||||
let notif = InAppNotification::new(&err, 6000, callback, undo_cb);
|
||||
notif.show(&self.overlay);
|
||||
}
|
||||
Action::ShowUpdateNotif(receiver) => {
|
||||
let sender = self.sender.clone();
|
||||
let callback = move |revealer: gtk::Revealer| {
|
||||
if let Some(_) = receiver.try_recv() {
|
||||
revealer.set_reveal_child(false);
|
||||
sender.send(Action::RefreshAllViews);
|
||||
return glib::Continue(false);
|
||||
}
|
||||
|
||||
glib::Continue(true)
|
||||
};
|
||||
let txt = i18n("Fetching new episodes");
|
||||
let undo_cb: Option<fn()> = None;
|
||||
let updater = InAppNotification::new(&txt, 250, callback, undo_cb);
|
||||
updater.set_close_state(State::Hidden);
|
||||
updater.set_spinner_state(SpinnerState::Active);
|
||||
|
||||
let old = self.updater.replace(Some(updater));
|
||||
old.map(|i| i.destroy());
|
||||
self.updater
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|i| i.show(&self.overlay));
|
||||
}
|
||||
Action::InitEpisode(rowid) => {
|
||||
let res = self.player.initialize_episode(rowid);
|
||||
debug_assert!(res.is_ok());
|
||||
|
||||
@ -26,34 +26,10 @@ pub(crate) struct Header {
|
||||
back: gtk::Button,
|
||||
show_title: gtk::Label,
|
||||
hamburger: gtk::MenuButton,
|
||||
updater: UpdateIndicator,
|
||||
add: AddPopover,
|
||||
dots: gtk::MenuButton,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct UpdateIndicator {
|
||||
container: gtk::Box,
|
||||
text: gtk::Label,
|
||||
spinner: gtk::Spinner,
|
||||
}
|
||||
|
||||
impl UpdateIndicator {
|
||||
fn show(&self) {
|
||||
self.spinner.start();
|
||||
self.spinner.show();
|
||||
self.container.show();
|
||||
self.text.show();
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
self.spinner.stop();
|
||||
self.spinner.hide();
|
||||
self.container.hide();
|
||||
self.text.hide();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AddPopover {
|
||||
container: gtk::Popover,
|
||||
@ -158,16 +134,6 @@ impl Default for Header {
|
||||
// The 3 dots secondary menu
|
||||
let dots = builder.get_object("secondary_menu").unwrap();
|
||||
|
||||
let update_box = builder.get_object("update_notification").unwrap();
|
||||
let update_label = builder.get_object("update_label").unwrap();
|
||||
let update_spinner = builder.get_object("update_spinner").unwrap();
|
||||
|
||||
let updater = UpdateIndicator {
|
||||
container: update_box,
|
||||
text: update_label,
|
||||
spinner: update_spinner,
|
||||
};
|
||||
|
||||
let add_toggle = builder.get_object("add_toggle").unwrap();
|
||||
let add_popover = builder.get_object("add_popover").unwrap();
|
||||
let new_url = builder.get_object("new_url").unwrap();
|
||||
@ -187,7 +153,6 @@ impl Default for Header {
|
||||
back,
|
||||
show_title,
|
||||
hamburger,
|
||||
updater,
|
||||
add,
|
||||
dots,
|
||||
}
|
||||
@ -248,14 +213,6 @@ impl Header {
|
||||
self.show_title.set_text(title)
|
||||
}
|
||||
|
||||
pub(crate) fn show_update_notification(&self) {
|
||||
self.updater.show();
|
||||
}
|
||||
|
||||
pub(crate) fn hide_update_notification(&self) {
|
||||
self.updater.hide();
|
||||
}
|
||||
|
||||
pub(crate) fn open_menu(&self) {
|
||||
self.hamburger.clicked();
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use gtk::prelude::*;
|
||||
use gtk::{IsA, Widget};
|
||||
|
||||
use chrono::prelude::*;
|
||||
use crossbeam_channel::{unbounded, Sender};
|
||||
use crossbeam_channel::{bounded, unbounded, Sender};
|
||||
use failure::Error;
|
||||
use fragile::Fragile;
|
||||
use rayon;
|
||||
@ -200,7 +200,8 @@ fn refresh_feed<S>(source: Option<S>, sender: Sender<Action>) -> Result<(), Erro
|
||||
where
|
||||
S: IntoIterator<Item = Source> + Send + 'static,
|
||||
{
|
||||
sender.send(Action::HeaderBarShowUpdateIndicator);
|
||||
let (up_sender, up_receiver) = bounded(1);
|
||||
sender.send(Action::ShowUpdateNotif(up_receiver));
|
||||
|
||||
rayon::spawn(move || {
|
||||
if let Some(s) = source {
|
||||
@ -218,8 +219,7 @@ where
|
||||
.ok();
|
||||
};
|
||||
|
||||
sender.send(Action::HeaderBarHideUpdateIndicator);
|
||||
sender.send(Action::RefreshAllViews);
|
||||
up_sender.send(true);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -6,17 +6,26 @@ use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum UndoState {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum State {
|
||||
Shown,
|
||||
Hidden,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum SpinnerState {
|
||||
Active,
|
||||
Stopped,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InAppNotification {
|
||||
revealer: gtk::Revealer,
|
||||
text: gtk::Label,
|
||||
undo: gtk::Button,
|
||||
close: gtk::Button,
|
||||
spinner: gtk::Spinner,
|
||||
}
|
||||
|
||||
impl Default for InAppNotification {
|
||||
@ -27,37 +36,54 @@ impl Default for InAppNotification {
|
||||
let text: gtk::Label = builder.get_object("text").unwrap();
|
||||
let undo: gtk::Button = builder.get_object("undo").unwrap();
|
||||
let close: gtk::Button = builder.get_object("close").unwrap();
|
||||
let spinner = builder.get_object("spinner").unwrap();
|
||||
|
||||
InAppNotification {
|
||||
revealer,
|
||||
text,
|
||||
undo,
|
||||
close,
|
||||
spinner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Timer should be in milliseconds
|
||||
impl InAppNotification {
|
||||
pub(crate) fn new<F, U>(
|
||||
text: &str,
|
||||
timer: u32,
|
||||
mut callback: F,
|
||||
undo_callback: U,
|
||||
show_undo: UndoState,
|
||||
undo_callback: Option<U>,
|
||||
) -> Self
|
||||
where
|
||||
F: FnMut() -> glib::Continue + 'static,
|
||||
F: FnMut(gtk::Revealer) -> glib::Continue + 'static,
|
||||
U: Fn() + 'static,
|
||||
{
|
||||
let notif = InAppNotification::default();
|
||||
notif.text.set_text(&text);
|
||||
|
||||
let revealer = notif.revealer.clone();
|
||||
let id = timeout_add_seconds(6, move || {
|
||||
revealer.set_reveal_child(false);
|
||||
callback()
|
||||
let revealer_weak = notif.revealer.downgrade();
|
||||
let mut time = 0;
|
||||
let id = timeout_add(250, move || {
|
||||
if time < timer {
|
||||
time += 250;
|
||||
return glib::Continue(true);
|
||||
};
|
||||
|
||||
let revealer = match revealer_weak.upgrade() {
|
||||
Some(r) => r,
|
||||
None => return glib::Continue(false),
|
||||
};
|
||||
|
||||
callback(revealer)
|
||||
});
|
||||
let id = Rc::new(RefCell::new(Some(id)));
|
||||
|
||||
if undo_callback.is_some() {
|
||||
notif.set_undo_state(State::Shown)
|
||||
};
|
||||
|
||||
// Cancel the callback
|
||||
let revealer = notif.revealer.clone();
|
||||
notif.undo.connect_clicked(move |_| {
|
||||
@ -66,23 +92,25 @@ impl InAppNotification {
|
||||
glib::source::source_remove(id);
|
||||
}
|
||||
|
||||
undo_callback();
|
||||
if let Some(ref f) = undo_callback {
|
||||
f();
|
||||
}
|
||||
|
||||
// Hide the notification
|
||||
revealer.set_reveal_child(false);
|
||||
});
|
||||
|
||||
// Hide the revealer when the close button is clicked
|
||||
let revealer = notif.revealer.clone();
|
||||
let revealer_weak = notif.revealer.downgrade();
|
||||
notif.close.connect_clicked(move |_| {
|
||||
let revealer = match revealer_weak.upgrade() {
|
||||
Some(r) => r,
|
||||
None => return,
|
||||
};
|
||||
|
||||
revealer.set_reveal_child(false);
|
||||
});
|
||||
|
||||
match show_undo {
|
||||
UndoState::Shown => (),
|
||||
UndoState::Hidden => notif.undo.hide(),
|
||||
}
|
||||
|
||||
notif
|
||||
}
|
||||
|
||||
@ -96,4 +124,35 @@ impl InAppNotification {
|
||||
// so there will be a nice animation.
|
||||
self.revealer.set_reveal_child(true);
|
||||
}
|
||||
|
||||
pub(crate) fn set_undo_state(&self, state: State) {
|
||||
match state {
|
||||
State::Shown => self.undo.show(),
|
||||
State::Hidden => self.undo.hide(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_close_state(&self, state: State) {
|
||||
match state {
|
||||
State::Shown => self.close.show(),
|
||||
State::Hidden => self.close.hide(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_spinner_state(&self, state: SpinnerState) {
|
||||
match state {
|
||||
SpinnerState::Active => {
|
||||
self.spinner.start();
|
||||
self.spinner.show();
|
||||
}
|
||||
SpinnerState::Stopped => {
|
||||
self.spinner.stop();
|
||||
self.spinner.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn destroy(self) {
|
||||
self.revealer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ use podcasts_data::Show;
|
||||
|
||||
use app::Action;
|
||||
use utils;
|
||||
use widgets::appnotif::{InAppNotification, UndoState};
|
||||
use widgets::appnotif::InAppNotification;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -137,15 +137,18 @@ fn mark_all_watched(pd: &Show, sender: &Sender<Action>) -> Result<(), Error> {
|
||||
|
||||
pub(crate) fn mark_all_notif(pd: Arc<Show>, sender: &Sender<Action>) -> InAppNotification {
|
||||
let id = pd.id();
|
||||
let callback = clone!(sender => move || {
|
||||
let res = mark_all_watched(&pd, &sender);
|
||||
let sender_ = sender.clone();
|
||||
let callback = move |revealer: gtk::Revealer| {
|
||||
let res = mark_all_watched(&pd, &sender_);
|
||||
debug_assert!(res.is_ok());
|
||||
|
||||
revealer.set_reveal_child(false);
|
||||
glib::Continue(false)
|
||||
});
|
||||
};
|
||||
|
||||
let undo_callback = clone!(sender => move || sender.send(Action::RefreshWidgetIfSame(id)));
|
||||
let text = i18n("Marked all episodes as listened");
|
||||
InAppNotification::new(&text, callback, undo_callback, UndoState::Shown)
|
||||
InAppNotification::new(&text, 6000, callback, Some(undo_callback))
|
||||
}
|
||||
|
||||
pub(crate) fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppNotification {
|
||||
@ -154,21 +157,25 @@ pub(crate) fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppN
|
||||
let res = utils::ignore_show(pd.id());
|
||||
debug_assert!(res.is_ok());
|
||||
|
||||
let callback = clone!(pd, sender => move || {
|
||||
let res = utils::uningore_show(pd.id());
|
||||
let sender_ = sender.clone();
|
||||
let pd_ = pd.clone();
|
||||
let callback = move |revealer: gtk::Revealer| {
|
||||
let res = utils::uningore_show(pd_.id());
|
||||
debug_assert!(res.is_ok());
|
||||
|
||||
// Spawn a thread so it won't block the ui.
|
||||
rayon::spawn(clone!(pd, sender => move || {
|
||||
delete_show(&pd)
|
||||
rayon::spawn(clone!(pd_, sender_ => move || {
|
||||
delete_show(&pd_)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Failed to delete {}", pd.title()))
|
||||
.map_err(|_| error!("Failed to delete {}", pd_.title()))
|
||||
.ok();
|
||||
|
||||
sender.send(Action::RefreshEpisodesView);
|
||||
sender_.send(Action::RefreshEpisodesView);
|
||||
}));
|
||||
|
||||
revealer.set_reveal_child(false);
|
||||
glib::Continue(false)
|
||||
});
|
||||
};
|
||||
|
||||
let undo_callback = move || {
|
||||
let res = utils::uningore_show(pd.id());
|
||||
@ -177,5 +184,5 @@ pub(crate) fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppN
|
||||
sender.send(Action::RefreshEpisodesView);
|
||||
};
|
||||
|
||||
InAppNotification::new(&text, callback, undo_callback, UndoState::Shown)
|
||||
InAppNotification::new(&text, 6000, callback, Some(undo_callback))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user