diff --git a/src/API/Account.vala b/src/API/Account.vala index afc6a6e..982a825 100644 --- a/src/API/Account.vala +++ b/src/API/Account.vala @@ -54,6 +54,38 @@ public class Tootle.Account{ return account; } + public Json.Node? serialize () { + var builder = new Json.Builder (); + builder.begin_object (); + builder.set_member_name ("id"); + builder.add_string_value (id.to_string ()); + builder.set_member_name ("created_at"); + builder.add_string_value (created_at); + builder.set_member_name ("following_count"); + builder.add_int_value (following_count); + builder.set_member_name ("followers_count"); + builder.add_int_value (followers_count); + builder.set_member_name ("statuses_count"); + builder.add_int_value (statuses_count); + builder.set_member_name ("display_name"); + builder.add_string_value (display_name); + builder.set_member_name ("username"); + builder.add_string_value (username); + builder.set_member_name ("acct"); + builder.add_string_value (acct); + builder.set_member_name ("note"); + builder.add_string_value (note); + builder.set_member_name ("header"); + builder.add_string_value (header); + builder.set_member_name ("avatar"); + builder.add_string_value (avatar); + builder.set_member_name ("url"); + builder.add_string_value (url); + + builder.end_object (); + return builder.get_root (); + } + public bool is_self (){ return id == Tootle.accounts.current.id; } diff --git a/src/API/Attachment.vala b/src/API/Attachment.vala index 1b6ad7b..b141a93 100644 --- a/src/API/Attachment.vala +++ b/src/API/Attachment.vala @@ -23,5 +23,26 @@ public class Tootle.Attachment{ return attachment; } + + public Json.Node? serialize () { + var builder = new Json.Builder (); + builder.begin_object (); + builder.set_member_name ("id"); + builder.add_string_value (id.to_string ()); + builder.set_member_name ("type"); + builder.add_string_value (type); + builder.set_member_name ("url"); + builder.add_string_value (url); + builder.set_member_name ("preview_url"); + builder.add_string_value (preview_url); + + if (description != null) { + builder.set_member_name ("description"); + builder.add_string_value (description); + } + + builder.end_object (); + return builder.get_root (); + } } diff --git a/src/API/Mention.vala b/src/API/Mention.vala index 6105e97..98cbd33 100644 --- a/src/API/Mention.vala +++ b/src/API/Mention.vala @@ -1,4 +1,4 @@ -public class Tootle.Mention{ +public class Tootle.Mention : GLib.Object { public int64 id; public string username; @@ -26,5 +26,20 @@ public class Tootle.Mention{ return mention; } + + public Json.Node? serialize () { + var builder = new Json.Builder (); + builder.begin_object (); + builder.set_member_name ("id"); + builder.add_string_value (id.to_string ()); + builder.set_member_name ("username"); + builder.add_string_value (username); + builder.set_member_name ("acct"); + builder.add_string_value (acct); + builder.set_member_name ("url"); + builder.add_string_value (url); + builder.end_object (); + return builder.get_root (); + } } diff --git a/src/API/Notification.vala b/src/API/Notification.vala index ed20b19..ed149bd 100644 --- a/src/API/Notification.vala +++ b/src/API/Notification.vala @@ -1,4 +1,4 @@ -public class Tootle.Notification{ +public class Tootle.Notification { public int64 id; public NotificationType type; @@ -11,7 +11,7 @@ public class Tootle.Notification{ id = _id; } - public static Notification parse(Json.Object obj) { + public static Notification parse (Json.Object obj) { var id = int64.parse (obj.get_string_member ("id")); var notification = new Notification (id); @@ -26,6 +26,29 @@ public class Tootle.Notification{ return notification; } + public Json.Node? serialize () { + var builder = new Json.Builder (); + builder.begin_object (); + builder.set_member_name ("id"); + builder.add_string_value (id.to_string ()); + builder.set_member_name ("type"); + builder.add_string_value (type.to_string ()); + builder.set_member_name ("created_at"); + builder.add_string_value (created_at); + + if (status != null) { + builder.set_member_name ("status"); + builder.add_value (status.serialize ()); + } + if (account != null) { + builder.set_member_name ("account"); + builder.add_value (account.serialize ()); + } + + builder.end_object (); + return builder.get_root (); + } + public static Notification parse_follow_request (Json.Object obj) { var notification = new Notification (-1); var account = Account.parse (obj); @@ -37,29 +60,32 @@ public class Tootle.Notification{ } public Soup.Message? dismiss () { - if (type == NotificationType.WATCHLIST) + if (type == NotificationType.WATCHLIST) { + if (accounts.formal.cached_notifications.remove (this)); + accounts.save (); return null; + } if (type == NotificationType.FOLLOW_REQUEST) return reject_follow_request (); var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (accounts.formal.instance, id); - var msg = new Soup.Message("POST", url); - network.queue(msg); + var msg = new Soup.Message ("POST", url); + network.queue (msg); return msg; } public Soup.Message accept_follow_request () { var url = "%s/api/v1/follow_requests/%lld/authorize".printf (accounts.formal.instance, account.id); - var msg = new Soup.Message("POST", url); - network.queue(msg); + var msg = new Soup.Message ("POST", url); + network.queue (msg); return msg; } public Soup.Message reject_follow_request () { var url = "%s/api/v1/follow_requests/%lld/reject".printf (accounts.formal.instance, account.id); - var msg = new Soup.Message("POST", url); - network.queue(msg); + var msg = new Soup.Message ("POST", url); + network.queue (msg); return msg; } diff --git a/src/API/Status.vala b/src/API/Status.vala index 2d32342..154dff0 100644 --- a/src/API/Status.vala +++ b/src/API/Status.vala @@ -86,6 +86,61 @@ public class Tootle.Status { return status; } + public Json.Node? serialize () { + var builder = new Json.Builder (); + builder.begin_object (); + builder.set_member_name ("id"); + builder.add_string_value (id.to_string ()); + builder.set_member_name ("uri"); + builder.add_string_value (uri); + builder.set_member_name ("url"); + builder.add_string_value (url); + builder.set_member_name ("content"); + builder.add_string_value (content); + builder.set_member_name ("created_at"); + builder.add_string_value (created_at); + builder.set_member_name ("visibility"); + builder.add_string_value (visibility.to_string ()); + builder.set_member_name ("sensitive"); + builder.add_boolean_value (sensitive); + builder.set_member_name ("sensitive"); + builder.add_boolean_value (sensitive); + builder.set_member_name ("replies_count"); + builder.add_int_value (replies_count); + builder.set_member_name ("favourites_count"); + builder.add_int_value (favourites_count); + builder.set_member_name ("reblogs_count"); + builder.add_int_value (reblogs_count); + builder.set_member_name ("account"); + builder.add_value (account.serialize ()); + + if (spoiler_text != null) { + builder.set_member_name ("spoiler_text"); + builder.add_string_value (spoiler_text); + } + if (reblog != null) { + builder.set_member_name ("reblog"); + builder.add_value (reblog.serialize ()); + } + if (attachments != null) { + builder.set_member_name ("media_attachments"); + builder.begin_array (); + foreach (Attachment attachment in attachments) + builder.add_value (attachment.serialize ()); + builder.end_array (); + } + if (mentions != null) { + builder.set_member_name ("mentions"); + builder.begin_array (); + foreach (Mention mention in mentions) + builder.add_value (mention.serialize ()); + builder.end_array (); + } + + builder.end_object (); + return builder.get_root (); + } + public bool is_owned (){ return get_formal ().account.id == accounts.current.id; } diff --git a/src/Dialogs/WatchlistDialog.vala b/src/Dialogs/WatchlistDialog.vala index a3e0ca3..55f781e 100644 --- a/src/Dialogs/WatchlistDialog.vala +++ b/src/Dialogs/WatchlistDialog.vala @@ -17,8 +17,8 @@ public class Tootle.WatchlistDialog : Gtk.Dialog { private Entry popover_entry; private Button popover_button; - private const string TIP_USERS = _("You'll be notified when toots from specific users appear in your Home timeline."); - private const string TIP_HASHTAGS = _("You'll be notified when toots with specific hashtags are posted in any public timelines."); + private const string TIP_USERS = _("You'll be notified when toots from this user appear in your Home timeline."); + private const string TIP_HASHTAGS = _("You'll be notified when toots with this hashtag appear in any public timelines."); private class ModelItem : GLib.Object { public string name; diff --git a/src/InstanceAccount.vala b/src/InstanceAccount.vala index 7b406b4..dbce9a2 100644 --- a/src/InstanceAccount.vala +++ b/src/InstanceAccount.vala @@ -1,4 +1,5 @@ using GLib; +using Gee; public class Tootle.InstanceAccount : Object { @@ -7,12 +8,16 @@ public class Tootle.InstanceAccount : Object { public string client_id {get; set;} public string client_secret {get; set;} public string token {get; set;} + public int64 last_seen_notification {get; set; default = 0;} public bool has_unread_notifications {get; set; default = false;} + public ArrayList cached_notifications {get; set;} private Notificator? notificator; - public InstanceAccount () {} + public InstanceAccount () { + cached_notifications = new ArrayList (); + } public string get_pretty_instance () { return instance @@ -64,6 +69,17 @@ public class Tootle.InstanceAccount : Object { builder.add_int_value (last_seen_notification); builder.set_member_name ("has_unread_notifications"); builder.add_boolean_value (has_unread_notifications); + + builder.set_member_name ("cached_notifications"); + builder.begin_array (); + cached_notifications.@foreach (notification => { + var node = notification.serialize (); + if (node != null) + builder.add_value (node); + return true; + }); + builder.end_array (); + builder.end_object (); return builder.get_root (); } @@ -77,6 +93,13 @@ public class Tootle.InstanceAccount : Object { acc.token = obj.get_string_member ("token"); acc.last_seen_notification = obj.get_int_member ("last_seen_notification"); acc.has_unread_notifications = obj.get_boolean_member ("has_unread_notifications"); + + var notifications = obj.get_array_member ("cached_notifications"); + notifications.foreach_element ((arr, i, node) => { + var notification = Notification.parse (node.get_object ()); + acc.cached_notifications.add (notification); + }); + return acc; } @@ -96,6 +119,11 @@ public class Tootle.InstanceAccount : Object { if (is_current ()) network.notification (obj); + + if (obj.type == NotificationType.WATCHLIST) { + cached_notifications.add (obj); + accounts.save (); + } } private void status_removed (int64 id) { diff --git a/src/Views/NotificationsView.vala b/src/Views/NotificationsView.vala index f3121aa..dfd59a0 100644 --- a/src/Views/NotificationsView.vala +++ b/src/Views/NotificationsView.vala @@ -53,8 +53,11 @@ public class Tootle.NotificationsView : AbstractView { if (reverse) { view.reorder_child (widget, 0); view.reorder_child (separator, 0); - force_dot = true; - accounts.formal.has_unread_notifications = force_dot; + + if (!current) { + force_dot = true; + accounts.formal.has_unread_notifications = force_dot; + } } if (notification.id > last_id) @@ -94,6 +97,10 @@ public class Tootle.NotificationsView : AbstractView { public virtual void on_refresh () { clear (); + accounts.formal.cached_notifications.@foreach (notification => { + append (notification); + return true; + }); request (); } diff --git a/src/Widgets/NotificationWidget.vala b/src/Widgets/NotificationWidget.vala index 73157cf..56213f8 100644 --- a/src/Widgets/NotificationWidget.vala +++ b/src/Widgets/NotificationWidget.vala @@ -1,7 +1,7 @@ using Gtk; using Granite; -public class Tootle.NotificationWidget : Gtk.Grid { +public class Tootle.NotificationWidget : Grid { private Notification notification; @@ -14,13 +14,13 @@ public class Tootle.NotificationWidget : Gtk.Grid { construct { margin = 6; - image = new Gtk.Image.from_icon_name ("notification-symbolic", Gtk.IconSize.BUTTON); + image = new Image.from_icon_name ("notification-symbolic", IconSize.BUTTON); image.margin_start = 32; image.margin_end = 6; label = new RichLabel (_("Unknown Notification")); label.hexpand = true; - label.halign = Gtk.Align.START; - dismiss = new Gtk.Button.from_icon_name ("close-symbolic", Gtk.IconSize.BUTTON); + label.halign = Align.START; + dismiss = new Button.from_icon_name ("close-symbolic", IconSize.BUTTON); dismiss.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); dismiss.tooltip_text = _("Dismiss"); dismiss.clicked.connect (() => { @@ -28,24 +28,20 @@ public class Tootle.NotificationWidget : Gtk.Grid { destroy (); }); - attach(image, 1, 2); - attach(label, 2, 2); - attach(dismiss, 3, 2); - show_all(); + attach (image, 1, 2); + attach (label, 2, 2); + attach (dismiss, 3, 2); + show_all (); } - public NotificationWidget (Notification notification) { - this.notification = notification; + public NotificationWidget (Notification _notification) { + notification = _notification; image.icon_name = notification.type.get_icon (); label.set_label (notification.type.get_desc (notification.account)); get_style_context ().add_class ("notification"); - if (notification.status != null) { - network.status_removed.connect (id => { - if (id == notification.status.id) - destroy (); - }); - } + if (notification.status != null) + network.status_removed.connect (on_status_removed); destroy.connect (() => { if (separator != null) @@ -57,20 +53,20 @@ public class Tootle.NotificationWidget : Gtk.Grid { if (notification.status != null){ status_widget = new StatusWidget (notification.status); status_widget.is_notification = true; - status_widget.button_press_event.connect(status_widget.open); - status_widget.avatar.button_press_event.connect(status_widget.open_account); - attach(status_widget, 1, 3, 3, 1); + status_widget.button_press_event.connect (status_widget.open); + status_widget.avatar.button_press_event.connect (status_widget.open_account); + attach (status_widget, 1, 3, 3, 1); } if (notification.type == NotificationType.FOLLOW_REQUEST) { - var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); + var box = new Box (Orientation.HORIZONTAL, 6); box.margin_start = 32 + 16 + 8; - var accept = new Gtk.Button.with_label (_("Accept")); + var accept = new Button.with_label (_("Accept")); box.pack_start (accept, false, false, 0); - var reject = new Gtk.Button.with_label (_("Reject")); + var reject = new Button.with_label (_("Reject")); box.pack_start (reject, false, false, 0); - attach(box, 1, 3, 3, 1); + attach (box, 1, 3, 3, 1); box.show_all (); accept.clicked.connect (() => { @@ -83,5 +79,14 @@ public class Tootle.NotificationWidget : Gtk.Grid { }); } } + + private void on_status_removed (int64 id) { + if (id == notification.status.id) { + if (notification.type == NotificationType.WATCHLIST) + notification.dismiss (); + + destroy (); + } + } }