[geary/wip/774442-in-app-notifications] Implement in-app notifications. Bug 774442.



commit 0f8f98d8878215f4a28d2eb80213c9337e804211
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Sat May 27 14:29:45 2017 +0200

    Implement in-app notifications. Bug 774442.
    
    Implemented it for the mail sent-notification.
    
    Signed-off-by: Niels De Graef <nielsdegraef gmail com>

 po/POTFILES.in                                   |    2 +
 src/CMakeLists.txt                               |    1 +
 src/client/application/geary-controller.vala     |    3 +
 src/client/components/main-window.vala           |    7 +
 src/client/notification/in-app-notification.vala |   66 +++++++++++
 src/engine/rfc822/rfc822-mailbox-addresses.vala  |   20 +++
 ui/CMakeLists.txt                                |    1 +
 ui/in-app-notification.ui                        |   48 ++++++++
 ui/main-window.ui                                |  135 +++++++++++-----------
 9 files changed, 218 insertions(+), 65 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index dea321b..88f4a8e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -69,6 +69,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
@@ -404,6 +405,7 @@ src/mailer/main.vala
 [type: gettext/glade]ui/folder-popover.ui
 [type: gettext/glade]ui/gtk/help-overlay.ui
 [type: gettext/glade]ui/gtk/menus.ui
+[type: gettext/glade]ui/in-app-notification.ui
 [type: gettext/glade]ui/login.glade
 [type: gettext/glade]ui/main-toolbar.ui
 [type: gettext/glade]ui/main-toolbar-menus.ui
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bb56e06..59043d9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -388,6 +388,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
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 040dede..ae37bb7 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2541,6 +2541,9 @@ public class GearyController : Geary.BaseObject {
     }
 
     private void on_sent(Geary.RFC822.Message rfc822) {
+        string message = _("Successfully sent mail to %s.".printf(rfc822.to.to_short_string()));
+        InAppNotification notification = new InAppNotification(message);
+        this.main_window.add_notification(notification);
         Libnotify.play_sound("message-sent-email");
     }
 
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index 13fedf7..d9ac413 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -49,6 +49,8 @@ public class MainWindow : Gtk.ApplicationWindow {
     private Gtk.Box conversation_box;
     [GtkChild]
     private Gtk.ScrolledWindow conversation_list_scrolled;
+    [GtkChild]
+    private Gtk.Overlay overlay;
 
 
     /** Fired when the shift key is pressed or released. */
@@ -139,6 +141,11 @@ public class MainWindow : Gtk.ApplicationWindow {
         }
     }
 
+    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(),
diff --git a/src/client/notification/in-app-notification.vala 
b/src/client/notification/in-app-notification.vala
new file mode 100644
index 0000000..bc2b6e4
--- /dev/null
+++ b/src/client/notification/in-app-notification.vala
@@ -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 {
+
+    /** Close the in-app notification after 3 seconds by default. */
+    public const uint DEFAULT_KEEPALIVE = 3;
+
+    [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();
+    }
+}
diff --git a/src/engine/rfc822/rfc822-mailbox-addresses.vala b/src/engine/rfc822/rfc822-mailbox-addresses.vala
index b5f39b1..379aec5 100644
--- a/src/engine/rfc822/rfc822-mailbox-addresses.vala
+++ b/src/engine/rfc822/rfc822-mailbox-addresses.vala
@@ -124,5 +124,25 @@ public class Geary.RFC822.MailboxAddresses : Geary.MessageData.AbstractMessageDa
     public override string to_string() {
         return MailboxAddress.list_to_string(addrs, "(no addresses)", (a) => a.to_string());
     }
+
+    /**
+     * Returns a human-readable short string.
+     * Useful in case there are a lot of recipients, or there is little room for the display.
+     */
+    public string to_short_string() {
+        if (this.size == 0)
+            return _("(No recipients)");
+
+        // Always mention the first recipient
+        string first_recipient = this.get(0).get_short_address();
+        if (this.size == 1)
+            return first_recipient;
+
+        // If more than one, add the amount of participants
+        if (this.size == 2)
+            return _("%s and 1 other recipient").printf(first_recipient);
+
+        return _("%s and %d others").printf(first_recipient, this.size - 1);
+    }
 }
 
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 05a74e7..5426c65 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -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"
diff --git a/ui/in-app-notification.ui b/ui/in-app-notification.ui
new file mode 100644
index 0000000..087d794
--- /dev/null
+++ b/ui/in-app-notification.ui
@@ -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>
diff --git a/ui/main-window.ui b/ui/main-window.ui
index 1ed5894..eac0386 100644
--- a/ui/main-window.ui
+++ b/ui/main-window.ui
@@ -10,122 +10,127 @@
     <signal name="key_release_event" handler="on_key_release_event"/>
     <signal name="focus_in_event" handler="on_focus_event"/>
     <child>
-      <object class="GtkBox" id="main_layout">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">0</property>
+      <object class="GtkOverlay" id="overlay">
+        <property name="visible">False</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">horizontal</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">0</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="spacing">0</property>
-                <style>
-                  <class name="sidebar"/>
-                </style>
+                <property name="orientation">horizontal</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">horizontal</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">0</property>
                     <style>
-                      <class name="geary-sidebar-pane-separator"/>
+                      <class name="sidebar"/>
                     </style>
                     <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="spacing">0</property>
+                        <property name="orientation">horizontal</property>
+                        <style>
+                          <class name="geary-sidebar-pane-separator"/>
+                        </style>
                         <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="shadow_type">in</property>
-                            <style>
-                              <class name="geary-folder-frame"/>
-                            </style>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">0</property>
                             <child>
-                              <object class="GtkScrolledWindow" id="folder_list_scrolled">
+                              <object class="GtkFrame" id="folder_frame">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
-                                <property name="width_request">100</property>
-                                <property name="hscrollbar_policy">never</property>
-                                <property name="vscrollbar_policy">automatic</property>
+                                <property name="shadow_type">in</property>
+                                <style>
+                                  <class name="geary-folder-frame"/>
+                                </style>
+                                <child>
+                                  <object class="GtkScrolledWindow" id="folder_list_scrolled">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="width_request">100</property>
+                                    <property name="hscrollbar_policy">never</property>
+                                    <property name="vscrollbar_policy">automatic</property>
+                                  </object>
+                                </child>
                               </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                              </packing>
                             </child>
                           </object>
                           <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</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>
-                        <property name="spacing">0</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="shadow_type">in</property>
-                            <style>
-                              <class name="geary-conversation-frame"/>
-                            </style>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">0</property>
                             <child>
-                              <object class="GtkScrolledWindow" id="conversation_list_scrolled">
+                              <object class="GtkFrame" id="conversation_frame">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
-                                <property name="width_request">250</property>
-                                <property name="hscrollbar_policy">automatic</property>
-                                <property name="vscrollbar_policy">automatic</property>
+                                <property name="shadow_type">in</property>
+                                <style>
+                                  <class name="geary-conversation-frame"/>
+                                </style>
+                                <child>
+                                  <object class="GtkScrolledWindow" id="conversation_list_scrolled">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="width_request">250</property>
+                                    <property name="hscrollbar_policy">automatic</property>
+                                    <property name="vscrollbar_policy">automatic</property>
+                                  </object>
+                                </child>
                               </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                              </packing>
                             </child>
                           </object>
                           <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</property>
+                            <property name="resize">True</property>
+                            <property name="shrink">False</property>
                           </packing>
                         </child>
                       </object>
                       <packing>
-                        <property name="resize">True</property>
-                        <property name="shrink">False</property>
+                        <property name="pack_type">end</property>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
                       </packing>
                     </child>
                   </object>
                   <packing>
-                    <property name="pack_type">end</property>
-                    <property name="expand">True</property>
-                    <property name="fill">True</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>
+                <property name="pack_type">end</property>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
               </packing>
             </child>
           </object>
-          <packing>
-            <property name="pack_type">end</property>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-          </packing>
         </child>
       </object>
     </child>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]