From ef21a53cf949592b4b33b67a0e82eaafe3120156 Mon Sep 17 00:00:00 2001 From: bleakgrey Date: Sat, 19 May 2018 16:55:41 +0300 Subject: [PATCH] Add empty state --- .../com.github.bleakgrey.tootle.gresource.xml | 3 +- data/empty_state.png | Bin 0 -> 5769 bytes src/Views/AbstractView.vala | 29 +++++++++++++++++- src/Views/AccountView.vala | 4 +++ src/Views/AddAccountView.vala | 2 +- src/Views/FollowersView.vala | 6 ++-- src/Views/NotificationsView.vala | 16 ++++++++-- src/Views/SearchView.vala | 12 ++------ src/Views/TimelineView.vala | 19 +++++------- 9 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 data/empty_state.png diff --git a/data/com.github.bleakgrey.tootle.gresource.xml b/data/com.github.bleakgrey.tootle.gresource.xml index 0dd6a38..11eb9e2 100644 --- a/data/com.github.bleakgrey.tootle.gresource.xml +++ b/data/com.github.bleakgrey.tootle.gresource.xml @@ -2,6 +2,7 @@ Application.css - logo128.png + logo128.png + empty_state.png diff --git a/data/empty_state.png b/data/empty_state.png new file mode 100644 index 0000000000000000000000000000000000000000..4a0cfccd60d80f2638eec2fde501d586b808ff7e GIT binary patch literal 5769 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 literal 0 HcmV?d00001 diff --git a/src/Views/AbstractView.vala b/src/Views/AbstractView.vala index b7d053c..f9d8a12 100644 --- a/src/Views/AbstractView.vala +++ b/src/Views/AbstractView.vala @@ -2,8 +2,9 @@ using Gtk; public abstract class Tootle.AbstractView : Gtk.ScrolledWindow { - public Gtk.Image image; + public Gtk.Image? image; public Gtk.Box view; + protected Gtk.Box? empty; construct { view = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); @@ -40,4 +41,30 @@ public abstract class Tootle.AbstractView : Gtk.ScrolledWindow { public virtual void bottom_reached (){} + public virtual bool is_empty () { + return view.get_children ().length () <= 1; + } + + public virtual bool empty_state () { + if (empty != null) + empty.destroy (); + if (!is_empty ()) + return false; + + empty = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + empty.margin = 64; + var image = new Image.from_resource ("/com/github/bleakgrey/tootle/empty_state"); + var text = new Gtk.Label (_("Nothing to see here")); + text.get_style_context ().add_class ("h2"); + text.opacity = 0.5; + empty.vexpand = true; + empty.valign = Gtk.Align.FILL; + empty.pack_start (image, false, false, 0); + empty.pack_start (text, false, false, 12); + empty.show_all (); + view.pack_start (empty, false, false, 0); + + return true; + } + } diff --git a/src/Views/AccountView.vala b/src/Views/AccountView.vala index 9731a2c..b8ab96a 100644 --- a/src/Views/AccountView.vala +++ b/src/Views/AccountView.vala @@ -197,6 +197,10 @@ public class Tootle.AccountView : TimelineView { return btn; } + public override bool is_empty () { + return view.get_children ().length () <= 2; + } + public override bool is_status_owned (Status status){ return status.get_formal ().account.id == account.id; } diff --git a/src/Views/AddAccountView.vala b/src/Views/AddAccountView.vala index 23557ae..1ec348f 100644 --- a/src/Views/AddAccountView.vala +++ b/src/Views/AddAccountView.vala @@ -18,7 +18,7 @@ public class Tootle.AddAccountView : Tootle.AbstractView { hexpand = true; halign = Gtk.Align.CENTER; - image = new Image.from_resource ("/com/github/bleakgrey/tootle/logo128.png"); + image = new Image.from_resource ("/com/github/bleakgrey/tootle/logo128"); image.halign = Gtk.Align.CENTER; image.hexpand = true; image.margin_bottom = 24; diff --git a/src/Views/FollowersView.vala b/src/Views/FollowersView.vala index 7f2f13f..37c2b54 100644 --- a/src/Views/FollowersView.vala +++ b/src/Views/FollowersView.vala @@ -4,10 +4,12 @@ public class Tootle.FollowersView : TimelineView { public FollowersView (ref Account account) { base (account.id.to_string ()); - } public new void prepend (ref Account account){ + if (empty != null) + empty.destroy (); + var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); separator.show (); @@ -27,7 +29,7 @@ public class Tootle.FollowersView : TimelineView { public override void request (){ var msg = new Soup.Message("GET", get_url ()); - debug (get_url ()); + msg.finished.connect (() => empty_state ()); Tootle.network.queue(msg, (sess, mess) => { try{ Tootle.network.parse_array (mess).foreach_element ((array, i, node) => { diff --git a/src/Views/NotificationsView.vala b/src/Views/NotificationsView.vala index aebeb57..c668850 100644 --- a/src/Views/NotificationsView.vala +++ b/src/Views/NotificationsView.vala @@ -22,7 +22,10 @@ public class Tootle.NotificationsView : AbstractView { return _("Notifications"); } - public void prepend (Notification notification, bool invert_order = false) { + public void prepend (Notification notification) { + if (empty != null) + empty.destroy (); + var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); separator.show (); @@ -37,8 +40,15 @@ public class Tootle.NotificationsView : AbstractView { if (!(widget is NotificationWidget)) return; - if (view.get_children ().length () <= 1) + empty_state (); + } + + public override bool empty_state () { + var is_empty = base.empty_state (); + if (image != null && is_empty) image.icon_name = get_icon (); + + return is_empty; } public virtual void on_refresh () { @@ -89,6 +99,8 @@ public class Tootle.NotificationsView : AbstractView { warning (e.message); } }); + + empty_state (); } } diff --git a/src/Views/SearchView.vala b/src/Views/SearchView.vala index 48013f6..8751cd1 100644 --- a/src/Views/SearchView.vala +++ b/src/Views/SearchView.vala @@ -44,12 +44,6 @@ public class Tootle.SearchView : AbstractView { view.pack_start (widget, false, false, 0); } - private void append_empty_state (){ - var empty_state = new Gtk.Label (_("No Results")); - empty_state.get_style_context ().add_class ("h2"); - view.pack_start (empty_state, false, false, 6); - } - private void append_hashtag (string name) { var text = "#%s".printf (Tootle.settings.instance_url, Soup.URI.encode (name, null), name); var widget = new RichLabel (text); @@ -79,10 +73,6 @@ public class Tootle.SearchView : AbstractView { var hashtags = root.get_array_member ("hashtags"); clear (); - if (accounts.get_length () == 0 && statuses.get_length () == 0 && hashtags.get_length () == 0) { - append_empty_state (); - return; - } if (accounts.get_length () > 0) { append_header (_("Accounts")); @@ -108,6 +98,8 @@ public class Tootle.SearchView : AbstractView { append_hashtag (node.get_string ()); }); } + + empty_state (); } catch (GLib.Error e) { diff --git a/src/Views/TimelineView.vala b/src/Views/TimelineView.vala index 4c5891f..ee4a59e 100644 --- a/src/Views/TimelineView.vala +++ b/src/Views/TimelineView.vala @@ -36,6 +36,9 @@ public class Tootle.TimelineView : AbstractView { } public void prepend (ref Status status){ + if (empty != null) + empty.destroy (); + var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); separator.show (); @@ -58,16 +61,12 @@ public class Tootle.TimelineView : AbstractView { public virtual void on_remove (Widget widget){ if (!(widget is StatusWidget)) return; - - //TODO: empty state } public void get_pages (string? header) { page_next = page_prev = null; - if (header == null) { - debug ("No pagingation links"); + if (header == null) return; - } var pages = header.split (","); foreach (var page in pages) { @@ -76,14 +75,10 @@ public class Tootle.TimelineView : AbstractView { .replace (">", "") .split (";")[0]; - if ("rel=\"prev\"" in page) { - debug ("found prev page %s", sanitized); + if ("rel=\"prev\"" in page) page_prev = sanitized; - } - else { - debug ("found next page %s", sanitized); + else page_next = sanitized; - } } is_last_page = page_prev != null & page_next == null; @@ -100,6 +95,7 @@ public class Tootle.TimelineView : AbstractView { public virtual void request (){ var msg = new Soup.Message("GET", get_url ()); + msg.finished.connect (() => empty_state ()); Tootle.network.queue(msg, (sess, mess) => { try{ Tootle.network.parse_array (mess).foreach_element ((array, i, node) => { @@ -126,7 +122,6 @@ public class Tootle.TimelineView : AbstractView { public virtual void on_account_changed (Account? account){ if(account == null) return; - on_refresh (); }