Implement in-app notifications. Bug 774442.

Implemented it for the mail sent-notification.

Signed-off-by: Niels De Graef <nielsdegraef@gmail.com>
This commit is contained in:
Niels De Graef 2017-05-27 14:29:45 +02:00 committed by Michael James Gratton
parent 17551d17f6
commit c72d7b28ac
11 changed files with 271 additions and 114 deletions

View file

@ -76,6 +76,7 @@ src/client/folder-list/folder-list-inboxes-branch.vala
src/client/folder-list/folder-list-search-branch.vala
src/client/folder-list/folder-list-special-grouping.vala
src/client/folder-list/folder-list-tree.vala
src/client/notification/in-app-notification.vala
src/client/notification/libmessagingmenu.vala
src/client/notification/libnotify.vala
src/client/notification/new-messages-indicator.vala
@ -421,6 +422,7 @@ ui/find_bar.glade
ui/folder-popover.ui
ui/gtk/help-overlay.ui
ui/gtk/menus.ui
ui/in-app-notification.ui
ui/login.glade
ui/main-toolbar.ui
ui/main-toolbar-menus.ui

View file

@ -403,6 +403,7 @@ client/folder-list/folder-list-inbox-folder-entry.vala
client/folder-list/folder-list-search-branch.vala
client/folder-list/folder-list-special-grouping.vala
client/notification/in-app-notification.vala
client/notification/libmessagingmenu.vala
client/notification/libnotify.vala
client/notification/new-messages-indicator.vala

View file

@ -2713,6 +2713,13 @@ public class GearyController : Geary.BaseObject {
}
private void on_sent(Geary.RFC822.Message rfc822) {
// Translators: The label for an in-app notification. The
// string substitution is a list of recipients of the email.
string message = _(
"Successfully sent mail to %s."
).printf(EmailUtil.to_short_recipient_display(rfc822.to));
InAppNotification notification = new InAppNotification(message);
this.main_window.add_notification(notification);
Libnotify.play_sound("message-sent-email");
}

View file

@ -49,6 +49,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
private Gtk.Box conversation_box;
[GtkChild]
private Gtk.ScrolledWindow conversation_list_scrolled;
[GtkChild]
private Gtk.Overlay overlay;
// This is a frame so users can use F6/Shift-F6 to get to it
[GtkChild]
@ -170,6 +172,11 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
}
}
public void add_notification(InAppNotification notification) {
this.overlay.add_overlay(notification);
notification.show();
}
private void set_styling() {
Gtk.CssProvider provider = new Gtk.CssProvider();
Gtk.StyleContext.add_provider_for_screen(Gdk.Display.get_default().get_default_screen(),

View file

@ -78,6 +78,7 @@ geary_client_vala_sources = files(
'folder-list/folder-list-search-branch.vala',
'folder-list/folder-list-special-grouping.vala',
'notification/in-app-notification.vala',
'notification/libmessagingmenu.vala',
'notification/libnotify.vala',
'notification/new-messages-indicator.vala',

View file

@ -0,0 +1,66 @@
/* Copyright 2017 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Represents an in-app notification.
*
* Following the GNOME HIG, it should only contain a label and maybe a button.
*/
[GtkTemplate (ui = "/org/gnome/Geary/in-app-notification.ui")]
public class InAppNotification : Gtk.Revealer {
/** Length of the default timeout to close the notification. */
public const uint DEFAULT_KEEPALIVE = 5;
[GtkChild]
private Gtk.Label message_label;
[GtkChild]
private Gtk.Button action_button;
/**
* Creates an in-app notification.
*
* @param message The messag that should be displayed.
* @param keepalive The amount of seconds that the notification should stay visible.
*/
public InAppNotification(string message, uint keepalive = DEFAULT_KEEPALIVE) {
this.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
this.message_label.label = message;
// Close after the given amount of time.
Timeout.add_seconds(keepalive, () => { close(); return false; });
}
/**
* Sets a button for the notification.
*/
public void set_button(string label, string action_name) {
this.action_button.visible = true;
this.action_button.label = label;
this.action_button.action_name = action_name;
}
public override void show() {
base.show();
this.reveal_child = true;
}
/**
* Closes the in-app notification.
*/
[GtkCallback]
public void close() {
// Allows for the disappearing transition
this.reveal_child = false;
}
// Make sure the notification gets destroyed after closing.
[GtkCallback]
private void on_child_revealed(Object src, ParamSpec p) {
if (!this.child_revealed)
destroy();
}
}

View file

@ -30,5 +30,40 @@ public string strip_subject_prefixes(Geary.Email email) {
return !Geary.String.is_empty(cleaned) ? cleaned : _("(no subject)");
}
}
/**
* Returns a shortened recipient list suitable for display.
*
* This is useful in case there are a lot of recipients, or there
* is little room for the display.
*
* @return a string containing at least the first mailbox
* serialised by {@link MailboxAddress.to_short_display}, if the
* list contains more mailboxes then an indication of how many
* additional are present.
*/
public string to_short_recipient_display(Geary.RFC822.MailboxAddresses mailboxes) {
if (mailboxes.size == 0) {
// Translators: This is shown for displaying a list of
// email recipients that happens to be empty,
// i.e. contains no email addresses.
return _("(No recipients)");
}
// Always mention the first recipient
string first_recipient = mailboxes.get(0).to_short_display();
if (mailboxes.size == 1)
return first_recipient;
// Translators: This is used for displaying a short list of
// email recipients lists with two or more addresses. The
// first (string) substitution is address of the first, the
// second substitution is the number of n - 1 remaining
// recipients.
return GLib.ngettext(
"%s and %d other",
"%s and %d others",
mailboxes.size - 1
).printf(first_recipient, mailboxes.size - 1);
}
}

View file

@ -26,6 +26,7 @@ set(RESOURCE_LIST
STRIPBLANKS "folder-popover.ui"
STRIPBLANKS "gtk/help-overlay.ui"
STRIPBLANKS "gtk/menus.ui"
STRIPBLANKS "in-app-notification.ui"
STRIPBLANKS "login.glade"
STRIPBLANKS "main-toolbar.ui"
STRIPBLANKS "main-toolbar-menus.ui"

48
ui/in-app-notification.ui Normal file
View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.14"/>
<template class="InAppNotification" parent="GtkRevealer">
<property name="visible">False</property>
<property name="halign">center</property>
<property name="valign">start</property>
<signal name="notify::child-revealed" handler="on_child_revealed" swapped="no"/>
<child>
<object class="GtkBox" id="layout">
<property name="visible">True</property>
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<style>
<class name="app-notification"/>
</style>
<child>
<object class="GtkLabel" id="message_label">
<property name="visible">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="action_button">
<property name="visible">False</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="clicked" handler="close" swapped="no"/>
<style>
<class name="flat"/>
</style>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">window-close-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.22.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="MainWindow" parent="GtkApplicationWindow">
@ -11,175 +11,163 @@
<signal name="focus-in-event" handler="on_focus_event" swapped="no"/>
<signal name="key-release-event" handler="on_key_release_event" swapped="no"/>
<child>
<object class="GtkBox" id="main_layout">
<object class="GtkOverlay" id="overlay">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkPaned" id="conversations_paned">
<object class="GtkBox" id="main_layout">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="search_bar_box">
<object class="GtkPaned" id="conversations_paned">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="can_focus">True</property>
<child>
<object class="GtkPaned" id="folder_paned">
<object class="GtkBox" id="search_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="folder_box">
<object class="GtkPaned" id="folder_paned">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="can_focus">True</property>
<child>
<object class="GtkFrame" id="folder_frame">
<object class="GtkBox" id="folder_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="folder_list_scrolled">
<property name="width_request">100</property>
<object class="GtkFrame" id="folder_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hscrollbar_policy">never</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkScrolledWindow" id="folder_list_scrolled">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
</object>
</child>
<style>
<class name="geary-folder-frame"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="geary-folder-frame"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="conversation_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="conversation_frame">
<object class="GtkBox" id="conversation_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="conversation_list_scrolled">
<property name="width_request">250</property>
<object class="GtkFrame" id="conversation_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkScrolledWindow" id="conversation_list_scrolled">
<property name="width_request">250</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
<style>
<class name="geary-conversation-frame"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="geary-conversation-frame"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child>
<style>
<class name="geary-sidebar-pane-separator"/>
</style>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<style>
<class name="geary-sidebar-pane-separator"/>
<class name="sidebar"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="info_bar_frame">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="info_bar_container">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<signal name="remove" handler="on_info_bar_container_remove" swapped="no"/>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
<style>
<class name="sidebar"/>
<class name="geary-info-bar-frame"/>
</style>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="info_bar_frame">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="info_bar_container">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<signal name="remove" handler="on_info_bar_container_remove" swapped="no"/>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
<style>
<class name="geary-info-bar-frame"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="index">-1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</template>
</interface>

View file

@ -27,6 +27,7 @@
<file compressed="true" preprocess="xml-stripblanks">folder-popover.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/menus.ui</file>
<file compressed="true" preprocess="xml-stripblanks">in-app-notification.ui</file>
<file compressed="true" preprocess="xml-stripblanks">login.glade</file>
<file compressed="true" preprocess="xml-stripblanks">main-toolbar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">main-toolbar-menus.ui</file>