Commit 646439d8 authored by Tobias Bernard's avatar Tobias Bernard 🌱

Merge branch 'alatiera/move-update-notif' into 'master'

Move the update indication into an In-app Notification

Closes #72

See merge request !60
parents 866fa6a7 ae7f65e9
Pipeline #24968 passed with stages
in 12 minutes and 34 seconds
......@@ -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))
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment