From 8ef015be91a03c5a1e62d7c52c045b613b174349 Mon Sep 17 00:00:00 2001 From: bleakgrey Date: Wed, 13 Jun 2018 16:13:41 +0300 Subject: [PATCH] Stream all timelines (close #45) --- data/com.github.bleakgrey.tootle.gschema.xml | 11 ++++-- meson.build | 1 + src/Dialogs/SettingsDialog.vala | 38 ++++++++++++-------- src/InstanceAccount.vala | 21 ++--------- src/MainWindow.vala | 19 ++++++---- src/NetManager.vala | 1 - src/Notificator.vala | 6 ++++ src/SettingsManager.vala | 1 + src/Views/FavoritesView.vala | 4 +-- src/Views/FederatedView.vala | 12 +++++-- src/Views/FollowersView.vala | 2 +- src/Views/FollowingView.vala | 4 +-- src/Views/HomeView.vala | 20 +++++++++++ src/Views/LocalView.vala | 14 ++++++-- src/Views/TimelineView.vala | 38 +++++++++++++++----- src/Widgets/RichLabel.vala | 15 ++++++-- 16 files changed, 141 insertions(+), 66 deletions(-) create mode 100644 src/Views/HomeView.vala diff --git a/data/com.github.bleakgrey.tootle.gschema.xml b/data/com.github.bleakgrey.tootle.gschema.xml index 9fec02d..518cfc4 100644 --- a/data/com.github.bleakgrey.tootle.gschema.xml +++ b/data/com.github.bleakgrey.tootle.gschema.xml @@ -28,8 +28,13 @@ true - Update timelines in real-time - + Real-time timelines + Update timelines in real-time + + + false + Real-time public timelines + Update local and federated timelines in real-time. May clog up memory on busy instances. false @@ -39,7 +44,7 @@ 500 Default character limit - Change this if your instance supports more than 500 characters in post + Change this if your instance supports more than 500 characters in posts diff --git a/meson.build b/meson.build index 62bf5fe..68a2069 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,7 @@ executable( 'src/Dialogs/SettingsDialog.vala', 'src/Views/AbstractView.vala', 'src/Views/TimelineView.vala', + 'src/Views/HomeView.vala', 'src/Views/LocalView.vala', 'src/Views/FederatedView.vala', 'src/Views/NotificationsView.vala', diff --git a/src/Dialogs/SettingsDialog.vala b/src/Dialogs/SettingsDialog.vala index 302ffcf..254c2a3 100644 --- a/src/Dialogs/SettingsDialog.vala +++ b/src/Dialogs/SettingsDialog.vala @@ -7,27 +7,42 @@ public class Tootle.SettingsDialog : Gtk.Dialog { private SettingsSwitch switch_notifications; private SettingsSwitch switch_watcher; + private SettingsSwitch switch_stream; + private SettingsSwitch switch_stream_public; private Gtk.Grid grid; public SettingsDialog () { - Object ( - border_width: 6, - deletable: false, - resizable: false, - title: _("Settings"), - transient_for: Tootle.window - ); + border_width = 6; + deletable = false; + resizable = false; + title = _("Settings"); + transient_for = Tootle.window; int i = 0; grid = new Gtk.Grid (); + switch_watcher = new SettingsSwitch ("always-online"); + switch_notifications = new SettingsSwitch ("notifications"); + switch_notifications.state_set.connect (state => { + switch_watcher.sensitive = state; + return false; + }); + switch_stream = new SettingsSwitch ("live-updates"); + switch_stream_public = new SettingsSwitch ("live-updates-public"); + switch_stream.state_set.connect (state => { + switch_stream_public.sensitive = state; + return false; + }); + grid.attach (new Granite.HeaderLabel (_("Appearance")), 0, i++, 2, 1); grid.attach (new SettingsLabel (_("Dark theme:")), 0, i); grid.attach (new SettingsSwitch ("dark-theme"), 1, i++); grid.attach (new Granite.HeaderLabel (_("Timelines")), 0, i++, 2, 1); grid.attach (new SettingsLabel (_("Real-time updates:")), 0, i); - grid.attach (new SettingsSwitch ("live-updates"), 1, i++); + grid.attach (switch_stream, 1, i++); + grid.attach (new SettingsLabel (_("Update public timelines:")), 0, i); + grid.attach (switch_stream_public, 1, i++); // grid.attach (new Granite.HeaderLabel (_("Caching")), 0, i++, 2, 1); // grid.attach (new SettingsLabel (_("Use cache:")), 0, i); @@ -37,13 +52,6 @@ public class Tootle.SettingsDialog : Gtk.Dialog { // settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT); // grid.attach (cache_size, 1, i++); - switch_watcher = new SettingsSwitch ("always-online"); - switch_notifications = new SettingsSwitch ("notifications"); - switch_notifications.state_set.connect (state => { - switch_watcher.sensitive = state; - return false; - }); - grid.attach (new Granite.HeaderLabel (_("Notifications")), 0, i++, 2, 1); grid.attach (new SettingsLabel (_("Display notifications:")), 0, i); grid.attach (switch_notifications, 1, i++); diff --git a/src/InstanceAccount.vala b/src/InstanceAccount.vala index 1b46f2b..b00ade1 100644 --- a/src/InstanceAccount.vala +++ b/src/InstanceAccount.vala @@ -23,16 +23,14 @@ public class Tootle.InstanceAccount : GLib.Object { notificator.close (); notificator = new Notificator (get_stream ()); - notificator.status_added.connect (status_added); notificator.status_removed.connect (status_removed); notificator.notification.connect (notification); notificator.start (); } - private Soup.Message get_stream () { + public Soup.Message get_stream () { var url = "%s/api/v1/streaming/?stream=user&access_token=%s".printf (instance, token); - var msg = new Soup.Message("GET", url); - return msg; + return new Soup.Message ("GET", url); } public void close_notificator () { @@ -87,21 +85,8 @@ public class Tootle.InstanceAccount : GLib.Object { network.notification (ref obj); } - private void status_added (ref Status status) { - if (accounts.formal.token != this.token) - return; - - if (settings.live_updates) - network.status_added (ref status, "home"); - else - app.toast (_("New toot available")); - } - private void status_removed (int64 id) { - if (accounts.formal.token != this.token) - return; - - if (settings.live_updates) + if (accounts.formal.token == this.token) network.status_removed (id); } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 1982f3b..746a200 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -2,18 +2,23 @@ using Gtk; public class Tootle.MainWindow: Gtk.Window { - private Gtk.Overlay overlay; + private Overlay overlay; private Granite.Widgets.Toast toast; - private Gtk.Grid grid; + private Grid grid; private Stack primary_stack; private Stack secondary_stack; - public Gtk.HeaderBar header; + public HeaderBar header; private Granite.Widgets.ModeButton button_mode; private AccountsButton button_accounts; private Spinner spinner; private Button button_toot; private Button button_back; + + public HomeView home = new HomeView (); + public NotificationsView notifications = new NotificationsView (); + public LocalView local = new LocalView (); + public FederatedView federated = new FederatedView (); construct { var provider = new Gtk.CssProvider (); @@ -67,10 +72,10 @@ public class Tootle.MainWindow: Gtk.Window { grid = new Gtk.Grid (); grid.attach (primary_stack, 0, 0, 1, 1); - add_header_view (new TimelineView ("home")); - add_header_view (new NotificationsView ()); - add_header_view (new LocalView ()); - add_header_view (new FederatedView ()); + add_header_view (home); + add_header_view (notifications); + add_header_view (local); + add_header_view (federated); button_mode.set_active (0); toast = new Granite.Widgets.Toast (""); diff --git a/src/NetManager.vala b/src/NetManager.vala index 67e8558..0efc14f 100644 --- a/src/NetManager.vala +++ b/src/NetManager.vala @@ -9,7 +9,6 @@ public class Tootle.NetManager : GLib.Object { public abstract signal void finished (); public abstract signal void notification (ref Notification notification); - public abstract signal void status_added (ref Status status, string timeline); public abstract signal void status_removed (int64 id); private int requests_processing = 0; diff --git a/src/Notificator.vala b/src/Notificator.vala index 4262b6a..0c62e68 100644 --- a/src/Notificator.vala +++ b/src/Notificator.vala @@ -67,10 +67,16 @@ public class Tootle.Notificator : GLib.Object { var type = root.get_string_member ("event"); switch (type) { case "update": + if (!settings.live_updates) + return; + var status = Status.parse (sanitize (root)); status_added (ref status); break; case "delete": + if (!settings.live_updates) + return; + var id = int64.parse (root.get_string_member("payload")); status_removed (id); break; diff --git a/src/SettingsManager.vala b/src/SettingsManager.vala index e01351d..14ce15d 100644 --- a/src/SettingsManager.vala +++ b/src/SettingsManager.vala @@ -7,6 +7,7 @@ public class Tootle.SettingsManager : Granite.Services.Settings { public int cache_size { get; set; } public int char_limit { get; set; } public bool live_updates { get; set; } + public bool live_updates_public { get; set; } public bool dark_theme { get; set; } public SettingsManager () { diff --git a/src/Views/FavoritesView.vala b/src/Views/FavoritesView.vala index 1faf718..8f03e6c 100644 --- a/src/Views/FavoritesView.vala +++ b/src/Views/FavoritesView.vala @@ -1,5 +1,3 @@ -using Gtk; - public class Tootle.FavoritesView : TimelineView { public FavoritesView () { @@ -10,7 +8,7 @@ public class Tootle.FavoritesView : TimelineView { if (page_next != null) return page_next; - var url = "%s/api/v1/favourites/?limit=%i".printf (Tootle.accounts.formal.instance, this.limit); + var url = "%s/api/v1/favourites/?limit=%i".printf (accounts.formal.instance, this.limit); return url; } diff --git a/src/Views/FederatedView.vala b/src/Views/FederatedView.vala index 98dcd6f..73ac0fa 100644 --- a/src/Views/FederatedView.vala +++ b/src/Views/FederatedView.vala @@ -1,9 +1,12 @@ -using Gtk; - public class Tootle.FederatedView : TimelineView { public FederatedView () { base ("public"); + notificator = new Notificator (get_stream ()); + notificator.status_added.connect ((ref status) => { + if (settings.live_updates_public) + on_status_added (ref status); + }); } public override string get_icon () { @@ -13,5 +16,10 @@ public class Tootle.FederatedView : TimelineView { public override string get_name () { return _("Federated Timeline"); } + + protected Soup.Message get_stream () { + var url = "%s/api/v1/streaming/?stream=public&access_token=%s".printf (accounts.formal.instance, accounts.formal.token); + return new Soup.Message("GET", url); + } } diff --git a/src/Views/FollowersView.vala b/src/Views/FollowersView.vala index cb5105a..6d5f98f 100644 --- a/src/Views/FollowersView.vala +++ b/src/Views/FollowersView.vala @@ -23,7 +23,7 @@ public class Tootle.FollowersView : TimelineView { if (page_next != null) return page_next; - var url = "%s/api/v1/accounts/%s/followers".printf (Tootle.accounts.formal.instance, this.timeline); + var url = "%s/api/v1/accounts/%s/followers".printf (accounts.formal.instance, this.timeline); return url; } diff --git a/src/Views/FollowingView.vala b/src/Views/FollowingView.vala index 38503f1..c11f473 100644 --- a/src/Views/FollowingView.vala +++ b/src/Views/FollowingView.vala @@ -1,5 +1,3 @@ -using Gtk; - public class Tootle.FollowingView : FollowersView { public FollowingView (ref Account account) { @@ -11,7 +9,7 @@ public class Tootle.FollowingView : FollowersView { if (page_next != null) return page_next; - var url = "%s/api/v1/accounts/%s/following".printf (Tootle.accounts.formal.instance, this.timeline); + var url = "%s/api/v1/accounts/%s/following".printf (accounts.formal.instance, this.timeline); return url; } diff --git a/src/Views/HomeView.vala b/src/Views/HomeView.vala new file mode 100644 index 0000000..5ca85fa --- /dev/null +++ b/src/Views/HomeView.vala @@ -0,0 +1,20 @@ +public class Tootle.HomeView : TimelineView { + + public HomeView () { + base ("home"); + notificator = new Notificator (accounts.formal.get_stream ()); + notificator.status_added.connect ((ref status) => { + if (settings.live_updates) + on_status_added (ref status); + }); + } + + public override string get_icon () { + return "user-home-symbolic"; + } + + public override string get_name () { + return _("Home"); + } + +} diff --git a/src/Views/LocalView.vala b/src/Views/LocalView.vala index 13ce9fa..6274055 100644 --- a/src/Views/LocalView.vala +++ b/src/Views/LocalView.vala @@ -1,9 +1,12 @@ -using Gtk; - public class Tootle.LocalView : TimelineView { public LocalView () { base ("public"); + notificator = new Notificator (get_stream ()); + notificator.status_added.connect ((ref status) => { + if (settings.live_updates_public) + on_status_added (ref status); + }); } public override string get_icon () { @@ -15,9 +18,14 @@ public class Tootle.LocalView : TimelineView { } public override string get_url (){ - string url = base.get_url (); + var url = base.get_url (); url += "&local=true"; return url; } + + protected Soup.Message get_stream () { + var url = "%s/api/v1/streaming/?stream=public:local&access_token=%s".printf (accounts.formal.instance, accounts.formal.token); + return new Soup.Message("GET", url); + } } diff --git a/src/Views/TimelineView.vala b/src/Views/TimelineView.vala index 19ed20a..16c9cda 100644 --- a/src/Views/TimelineView.vala +++ b/src/Views/TimelineView.vala @@ -5,20 +5,39 @@ public class Tootle.TimelineView : AbstractView { protected string timeline; protected string pars; - protected int limit = 25; protected bool is_last_page = false; protected string? page_next; protected string? page_prev; + + private Notificator? _notificator; + public Notificator? notificator { + get { + return _notificator; + } + + set { + if (_notificator != null) + _notificator.close (); + + _notificator = value; + + if (_notificator != null) + _notificator.start (); + } + } public TimelineView (string timeline, string pars = "") { base (); this.timeline = timeline; this.pars = pars; - Tootle.accounts.switched.connect(on_account_changed); - Tootle.app.refresh.connect(on_refresh); - Tootle.network.status_added.connect (on_status_added); + accounts.switched.connect (on_account_changed); + app.refresh.connect (on_refresh); + destroy.connect (() => { + if (notificator != null) + notificator.close (); + }); request (); } @@ -31,10 +50,7 @@ public class Tootle.TimelineView : AbstractView { return _("Home"); } - private void on_status_added (ref Status status, string timeline) { - if (timeline != this.timeline) - return; - + public virtual void on_status_added (ref Status status) { prepend (ref status); } @@ -138,6 +154,12 @@ public class Tootle.TimelineView : AbstractView { public virtual void on_account_changed (Account? account){ if(account == null) return; + + if (notificator != null) { + notificator.close (); + notificator.start (); + } + on_refresh (); } diff --git a/src/Widgets/RichLabel.vala b/src/Widgets/RichLabel.vala index c418d0f..b5ea55c 100644 --- a/src/Widgets/RichLabel.vala +++ b/src/Widgets/RichLabel.vala @@ -48,8 +48,19 @@ public class Tootle.RichLabel : Gtk.Label { if ("/tags/" in url){ var encoded = url.split("/tags/")[1]; var hashtag = Soup.URI.decode (encoded); - var feed = new TimelineView ("tag/" + hashtag); - Tootle.window.open_view (feed); + + var msg_url = "%s/api/v1/streaming/?stream=hashtag&access_token=%s&tag=%s" + .printf (accounts.formal.instance, accounts.formal.token, encoded); + var msg = new Soup.Message("GET", msg_url); + + var timeline = new TimelineView ("tag/" + hashtag); + timeline.notificator = new Notificator (msg); + timeline.notificator.status_added.connect ((ref status) => { + if (settings.live_updates) + timeline.on_status_added (ref status); + }); + window.open_view (timeline); + return true; }