Implement Watchlist (close #33)
This commit is contained in:
parent
56a70160d9
commit
cadc7166a6
|
@ -46,5 +46,15 @@
|
||||||
<summary>Default character limit</summary>
|
<summary>Default character limit</summary>
|
||||||
<description>Change this if your instance supports more than 500 characters in posts</description>
|
<description>Change this if your instance supports more than 500 characters in posts</description>
|
||||||
</key>
|
</key>
|
||||||
|
<key name="watched-users" type="s">
|
||||||
|
<default>''</default>
|
||||||
|
<summary>Watched Users</summary>
|
||||||
|
<description>Comma separated list of usernames to notify you about</description>
|
||||||
|
</key>
|
||||||
|
<key name="watched-hashtags" type="s">
|
||||||
|
<default>''</default>
|
||||||
|
<summary>Watched Hashtags</summary>
|
||||||
|
<description>Comma separated list of hashtags to notify you about</description>
|
||||||
|
</key>
|
||||||
</schema>
|
</schema>
|
||||||
</schemalist>
|
</schemalist>
|
||||||
|
|
|
@ -26,6 +26,7 @@ executable(
|
||||||
'src/Accounts.vala',
|
'src/Accounts.vala',
|
||||||
'src/ImageCache.vala',
|
'src/ImageCache.vala',
|
||||||
'src/Network.vala',
|
'src/Network.vala',
|
||||||
|
'src/Watchlist.vala',
|
||||||
'src/Notificator.vala',
|
'src/Notificator.vala',
|
||||||
'src/InstanceAccount.vala',
|
'src/InstanceAccount.vala',
|
||||||
'src/API/Account.vala',
|
'src/API/Account.vala',
|
||||||
|
@ -49,6 +50,7 @@ executable(
|
||||||
'src/Dialogs/NewAccountDialog.vala',
|
'src/Dialogs/NewAccountDialog.vala',
|
||||||
'src/Dialogs/PostDialog.vala',
|
'src/Dialogs/PostDialog.vala',
|
||||||
'src/Dialogs/SettingsDialog.vala',
|
'src/Dialogs/SettingsDialog.vala',
|
||||||
|
'src/Dialogs/WatchlistDialog.vala',
|
||||||
'src/Views/AbstractView.vala',
|
'src/Views/AbstractView.vala',
|
||||||
'src/Views/TimelineView.vala',
|
'src/Views/TimelineView.vala',
|
||||||
'src/Views/HomeView.vala',
|
'src/Views/HomeView.vala',
|
||||||
|
|
|
@ -36,27 +36,30 @@ public class Tootle.Notification{
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message dismiss () {
|
public Soup.Message? dismiss () {
|
||||||
|
if (type == NotificationType.WATCHLIST)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (type == NotificationType.FOLLOW_REQUEST)
|
if (type == NotificationType.FOLLOW_REQUEST)
|
||||||
return reject_follow_request ();
|
return reject_follow_request ();
|
||||||
|
|
||||||
var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (Tootle.accounts.formal.instance, id);
|
var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (accounts.formal.instance, id);
|
||||||
var msg = new Soup.Message("POST", url);
|
var msg = new Soup.Message("POST", url);
|
||||||
Tootle.network.queue(msg);
|
network.queue(msg);
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message accept_follow_request () {
|
public Soup.Message accept_follow_request () {
|
||||||
var url = "%s/api/v1/follow_requests/%lld/authorize".printf (Tootle.accounts.formal.instance, account.id);
|
var url = "%s/api/v1/follow_requests/%lld/authorize".printf (accounts.formal.instance, account.id);
|
||||||
var msg = new Soup.Message("POST", url);
|
var msg = new Soup.Message("POST", url);
|
||||||
Tootle.network.queue(msg);
|
network.queue(msg);
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Soup.Message reject_follow_request () {
|
public Soup.Message reject_follow_request () {
|
||||||
var url = "%s/api/v1/follow_requests/%lld/reject".printf (Tootle.accounts.formal.instance, account.id);
|
var url = "%s/api/v1/follow_requests/%lld/reject".printf (accounts.formal.instance, account.id);
|
||||||
var msg = new Soup.Message("POST", url);
|
var msg = new Soup.Message("POST", url);
|
||||||
Tootle.network.queue(msg);
|
network.queue(msg);
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ public enum Tootle.NotificationType {
|
||||||
REBLOG,
|
REBLOG,
|
||||||
FAVORITE,
|
FAVORITE,
|
||||||
FOLLOW,
|
FOLLOW,
|
||||||
FOLLOW_REQUEST;
|
FOLLOW_REQUEST,
|
||||||
|
WATCHLIST; // Internal
|
||||||
|
|
||||||
public string to_string() {
|
public string to_string() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -17,6 +18,8 @@ public enum Tootle.NotificationType {
|
||||||
return "follow";
|
return "follow";
|
||||||
case FOLLOW_REQUEST:
|
case FOLLOW_REQUEST:
|
||||||
return "follow_request";
|
return "follow_request";
|
||||||
|
case WATCHLIST:
|
||||||
|
return "watchlist";
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
@ -34,6 +37,8 @@ public enum Tootle.NotificationType {
|
||||||
return FOLLOW;
|
return FOLLOW;
|
||||||
case "follow_request":
|
case "follow_request":
|
||||||
return FOLLOW_REQUEST;
|
return FOLLOW_REQUEST;
|
||||||
|
case "watchlist":
|
||||||
|
return WATCHLIST;
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
@ -51,6 +56,8 @@ public enum Tootle.NotificationType {
|
||||||
return _("<a href=\"%s\"><b>%s</b></a> now follows you").printf (account.url, account.display_name);
|
return _("<a href=\"%s\"><b>%s</b></a> now follows you").printf (account.url, account.display_name);
|
||||||
case FOLLOW_REQUEST:
|
case FOLLOW_REQUEST:
|
||||||
return _("<a href=\"%s\"><b>%s</b></a> wants to follow you").printf (account.url, account.display_name);
|
return _("<a href=\"%s\"><b>%s</b></a> wants to follow you").printf (account.url, account.display_name);
|
||||||
|
case WATCHLIST:
|
||||||
|
return _("<a href=\"%s\"><b>%s</b></a> posted a toot").printf (account.url, account.display_name);
|
||||||
default:
|
default:
|
||||||
assert_not_reached();
|
assert_not_reached();
|
||||||
}
|
}
|
||||||
|
@ -59,6 +66,7 @@ public enum Tootle.NotificationType {
|
||||||
public string get_icon () {
|
public string get_icon () {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case MENTION:
|
case MENTION:
|
||||||
|
case WATCHLIST:
|
||||||
return "user-available-symbolic";
|
return "user-available-symbolic";
|
||||||
case REBLOG:
|
case REBLOG:
|
||||||
return "media-playlist-repeat-symbolic";
|
return "media-playlist-repeat-symbolic";
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Tootle{
|
||||||
public static Accounts accounts;
|
public static Accounts accounts;
|
||||||
public static Network network;
|
public static Network network;
|
||||||
public static ImageCache image_cache;
|
public static ImageCache image_cache;
|
||||||
|
public static Watchlist watchlist;
|
||||||
|
|
||||||
public class Application : Granite.Application {
|
public class Application : Granite.Application {
|
||||||
|
|
||||||
|
@ -39,8 +40,9 @@ namespace Tootle{
|
||||||
accounts = new Accounts ();
|
accounts = new Accounts ();
|
||||||
network = new Network ();
|
network = new Network ();
|
||||||
image_cache = new ImageCache ();
|
image_cache = new ImageCache ();
|
||||||
|
watchlist = new Watchlist ();
|
||||||
accounts.init ();
|
accounts.init ();
|
||||||
|
|
||||||
app.error.connect (app.on_error);
|
app.error.connect (app.on_error);
|
||||||
|
|
||||||
window_dummy = new Window ();
|
window_dummy = new Window ();
|
||||||
|
|
|
@ -89,7 +89,7 @@ public class Tootle.SettingsDialog : Gtk.Dialog {
|
||||||
halign = Gtk.Align.START;
|
halign = Gtk.Align.START;
|
||||||
valign = Gtk.Align.CENTER;
|
valign = Gtk.Align.CENTER;
|
||||||
margin_bottom = 6;
|
margin_bottom = 6;
|
||||||
Tootle.settings.schema.bind (setting, this, "active", SettingsBindFlags.DEFAULT);
|
settings.schema.bind (setting, this, "active", SettingsBindFlags.DEFAULT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
using Gtk;
|
||||||
|
using Tootle;
|
||||||
|
|
||||||
|
public class Tootle.WatchlistDialog : Gtk.Window {
|
||||||
|
|
||||||
|
private static WatchlistDialog dialog;
|
||||||
|
|
||||||
|
private Gtk.HeaderBar header;
|
||||||
|
private Gtk.StackSwitcher switcher;
|
||||||
|
private Gtk.MenuButton button_add;
|
||||||
|
private Gtk.Stack stack;
|
||||||
|
private ListStack users;
|
||||||
|
private ListStack hashtags;
|
||||||
|
|
||||||
|
private Gtk.Popover popover;
|
||||||
|
private Gtk.Grid popover_grid;
|
||||||
|
private Gtk.Entry popover_entry;
|
||||||
|
private Gtk.Button popover_button;
|
||||||
|
|
||||||
|
private const string TIP_USERS = _("Youl'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 class ModelItem : GLib.Object {
|
||||||
|
public string name;
|
||||||
|
public bool is_hashtag;
|
||||||
|
|
||||||
|
public ModelItem (string name, bool is_hashtag) {
|
||||||
|
this.name = name;
|
||||||
|
this.is_hashtag = is_hashtag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelView : Gtk.ListBoxRow {
|
||||||
|
private Gtk.Box box;
|
||||||
|
private Gtk.Button button_remove;
|
||||||
|
private Gtk.Label label;
|
||||||
|
private bool is_hashtag;
|
||||||
|
|
||||||
|
public ModelView (ModelItem item) {
|
||||||
|
is_hashtag = item.is_hashtag;
|
||||||
|
box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
|
||||||
|
box.margin = 6;
|
||||||
|
label = new Gtk.Label (item.name);
|
||||||
|
label.vexpand = true;
|
||||||
|
label.valign = Gtk.Align.CENTER;
|
||||||
|
label.justify = Gtk.Justification.LEFT;
|
||||||
|
button_remove = new Gtk.Button.from_icon_name ("list-remove-symbolic", Gtk.IconSize.BUTTON);
|
||||||
|
button_remove.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||||
|
button_remove.clicked.connect (() => {
|
||||||
|
watchlist.remove (label.label, is_hashtag);
|
||||||
|
watchlist.save ();
|
||||||
|
destroy ();
|
||||||
|
});
|
||||||
|
|
||||||
|
box.pack_start (label, false, false, 0);
|
||||||
|
box.pack_end (button_remove, false, false, 0);
|
||||||
|
add (box);
|
||||||
|
show_all ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Model : GLib.ListModel, GLib.Object {
|
||||||
|
private GenericArray<ModelItem> items = new GenericArray<ModelItem> ();
|
||||||
|
|
||||||
|
public GLib.Type get_item_type () {
|
||||||
|
return typeof (ModelItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint get_n_items () {
|
||||||
|
return items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GLib.Object? get_item (uint position) {
|
||||||
|
return items.@get ((int)position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append (ModelItem item) {
|
||||||
|
this.items.add (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Widget create_row (GLib.Object obj) {
|
||||||
|
var item = (ModelItem) obj;
|
||||||
|
return new ModelView (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListStack : Gtk.ScrolledWindow {
|
||||||
|
public Model model;
|
||||||
|
public Gtk.ListBox list;
|
||||||
|
private bool is_hashtags;
|
||||||
|
|
||||||
|
public void update () {
|
||||||
|
if (is_hashtags)
|
||||||
|
watchlist.hashtags.@foreach (item => {
|
||||||
|
model.append (new ModelItem (item, true));
|
||||||
|
});
|
||||||
|
else
|
||||||
|
watchlist.users.@foreach (item => {
|
||||||
|
model.append (new ModelItem (item, false));
|
||||||
|
});
|
||||||
|
|
||||||
|
list.bind_model (model, create_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListStack (bool is_hashtags) {
|
||||||
|
this.is_hashtags = is_hashtags;
|
||||||
|
model = new Model ();
|
||||||
|
list = new Gtk.ListBox ();
|
||||||
|
add (list);
|
||||||
|
update ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set_tip () {
|
||||||
|
var is_user = stack.visible_child_name == "users";
|
||||||
|
popover_entry.secondary_icon_tooltip_text = is_user ? TIP_USERS : TIP_HASHTAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WatchlistDialog () {
|
||||||
|
deletable = true;
|
||||||
|
resizable = true;
|
||||||
|
transient_for = window;
|
||||||
|
|
||||||
|
stack = new Gtk.Stack ();
|
||||||
|
stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
|
||||||
|
stack.hexpand = true;
|
||||||
|
stack.vexpand = true;
|
||||||
|
|
||||||
|
users = new ListStack (false);
|
||||||
|
hashtags = new ListStack (true);
|
||||||
|
|
||||||
|
stack.add_titled (users, "users", _("Users"));
|
||||||
|
stack.add_titled (hashtags, "hashtags", _("Hashtags"));
|
||||||
|
stack.set_size_request (400, 300);
|
||||||
|
|
||||||
|
popover_entry = new Gtk.Entry ();
|
||||||
|
popover_entry.hexpand = true;
|
||||||
|
popover_entry.secondary_icon_name = "dialog-information-symbolic";
|
||||||
|
popover_entry.secondary_icon_activatable = false;
|
||||||
|
popover_entry.activate.connect (() => submit ());
|
||||||
|
|
||||||
|
popover_button = new Gtk.Button.with_label (_("Add"));
|
||||||
|
popover_button.halign = Gtk.Align.END;
|
||||||
|
popover_button.margin_left = 8;
|
||||||
|
popover_button.clicked.connect (() => submit ());
|
||||||
|
|
||||||
|
popover_grid = new Gtk.Grid ();
|
||||||
|
popover_grid.margin = 8;
|
||||||
|
popover_grid.attach (popover_entry, 0, 0);
|
||||||
|
popover_grid.attach (popover_button, 1, 0);
|
||||||
|
popover_grid.show_all ();
|
||||||
|
|
||||||
|
popover = new Gtk.Popover (null);
|
||||||
|
popover.add (popover_grid);
|
||||||
|
|
||||||
|
button_add = new Gtk.MenuButton ();
|
||||||
|
button_add.image = new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.BUTTON);
|
||||||
|
button_add.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
|
||||||
|
button_add.popover = popover;
|
||||||
|
button_add.clicked.connect (() => set_tip ());
|
||||||
|
|
||||||
|
switcher = new StackSwitcher ();
|
||||||
|
switcher.stack = stack;
|
||||||
|
switcher.halign = Gtk.Align.CENTER;
|
||||||
|
|
||||||
|
header = new Gtk.HeaderBar ();
|
||||||
|
header.show_close_button = true;
|
||||||
|
header.pack_start (button_add);
|
||||||
|
header.set_custom_title (switcher);
|
||||||
|
set_titlebar (header);
|
||||||
|
|
||||||
|
add (stack);
|
||||||
|
show_all ();
|
||||||
|
|
||||||
|
destroy.connect (() => {
|
||||||
|
dialog = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submit () {
|
||||||
|
if (popover_entry.text_length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var is_hashtag = stack.visible_child_name == "hashtags";
|
||||||
|
var entity = popover_entry.text
|
||||||
|
.replace ("#", "")
|
||||||
|
.replace (" ", "");
|
||||||
|
|
||||||
|
watchlist.add (entity, is_hashtag);
|
||||||
|
watchlist.save ();
|
||||||
|
button_add.active = false;
|
||||||
|
|
||||||
|
if (is_hashtag)
|
||||||
|
hashtags.list.insert (create_row (new ModelItem (entity, true)), 0);
|
||||||
|
else
|
||||||
|
users.list.insert (create_row (new ModelItem (entity, false)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void open () {
|
||||||
|
if (dialog == null)
|
||||||
|
dialog = new WatchlistDialog ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ public class Tootle.InstanceAccount : GLib.Object {
|
||||||
notificator.close ();
|
notificator.close ();
|
||||||
|
|
||||||
notificator = new Notificator (get_stream ());
|
notificator = new Notificator (get_stream ());
|
||||||
|
notificator.status_added.connect (status_added);
|
||||||
notificator.status_removed.connect (status_removed);
|
notificator.status_removed.connect (status_removed);
|
||||||
notificator.notification.connect (notification);
|
notificator.notification.connect (notification);
|
||||||
notificator.start ();
|
notificator.start ();
|
||||||
|
@ -67,7 +68,7 @@ public class Tootle.InstanceAccount : GLib.Object {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notification (ref Notification obj) {
|
public void notification (ref Notification obj) {
|
||||||
var title = Html.remove_tags (obj.type.get_desc (obj.account));
|
var title = Html.remove_tags (obj.type.get_desc (obj.account));
|
||||||
var notification = new GLib.Notification (title);
|
var notification = new GLib.Notification (title);
|
||||||
if (obj.status != null) {
|
if (obj.status != null) {
|
||||||
|
@ -89,5 +90,20 @@ public class Tootle.InstanceAccount : GLib.Object {
|
||||||
if (accounts.formal.token == this.token)
|
if (accounts.formal.token == this.token)
|
||||||
network.status_removed (id);
|
network.status_removed (id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void status_added (ref Status status) {
|
||||||
|
if (accounts.formal.token != this.token)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var acct = status.account.acct;
|
||||||
|
var obj = new Notification (-1);
|
||||||
|
obj.type = NotificationType.WATCHLIST;
|
||||||
|
obj.account = status.account;
|
||||||
|
obj.status = status;
|
||||||
|
watchlist.users.@foreach (item => {
|
||||||
|
if (item == acct || item == "@" + acct)
|
||||||
|
notification (ref obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ public class Tootle.Notificator : GLib.Object {
|
||||||
Object ();
|
Object ();
|
||||||
this.msg = msg;
|
this.msg = msg;
|
||||||
this.msg.priority = Soup.MessagePriority.VERY_HIGH;
|
this.msg.priority = Soup.MessagePriority.VERY_HIGH;
|
||||||
|
this.msg.set_flags (Soup.MessageFlags.IGNORE_CONNECTION_LIMITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string get_url () {
|
public string get_url () {
|
||||||
|
@ -37,7 +38,7 @@ public class Tootle.Notificator : GLib.Object {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
info ("Starting notificator: %s", get_name ());
|
info ("Starting: %s", get_name ());
|
||||||
connection = yield network.stream (msg);
|
connection = yield network.stream (msg);
|
||||||
connection.error.connect (on_error);
|
connection.error.connect (on_error);
|
||||||
connection.message.connect (on_message);
|
connection.message.connect (on_message);
|
||||||
|
@ -54,7 +55,7 @@ public class Tootle.Notificator : GLib.Object {
|
||||||
if (connection == null)
|
if (connection == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
info ("Stopping notificator: %s", get_name ());
|
info ("Closing: %s", get_name ());
|
||||||
closing = true;
|
closing = true;
|
||||||
connection.close (0, null);
|
connection.close (0, null);
|
||||||
}
|
}
|
||||||
|
@ -68,13 +69,13 @@ public class Tootle.Notificator : GLib.Object {
|
||||||
if (closing)
|
if (closing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
warning ("Notificator %s aborted. Reconnecting in %i seconds.", get_name (), timeout);
|
warning ("Aborted: %s. Reconnecting in %i seconds.", get_name (), timeout);
|
||||||
GLib.Timeout.add_seconds (timeout, reconnect);
|
GLib.Timeout.add_seconds (timeout, reconnect);
|
||||||
timeout = int.min (timeout*2, 60);
|
timeout = int.min (timeout*2, 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_error (Error e) {
|
private void on_error (Error e) {
|
||||||
warning ("Error in notificator %s: %s", get_name (), e.message);
|
warning ("Error in %s: %s", get_name (), e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_message (int i, Bytes bytes) {
|
private void on_message (int i, Bytes bytes) {
|
||||||
|
|
|
@ -9,6 +9,8 @@ public class Tootle.Settings : Granite.Services.Settings {
|
||||||
public bool live_updates { get; set; }
|
public bool live_updates { get; set; }
|
||||||
public bool live_updates_public { get; set; }
|
public bool live_updates_public { get; set; }
|
||||||
public bool dark_theme { get; set; }
|
public bool dark_theme { get; set; }
|
||||||
|
public string watched_users { get; set; }
|
||||||
|
public string watched_hashtags { get; set; }
|
||||||
|
|
||||||
public Settings () {
|
public Settings () {
|
||||||
base ("com.github.bleakgrey.tootle");
|
base ("com.github.bleakgrey.tootle");
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
using Soup;
|
||||||
|
using GLib;
|
||||||
|
using Gdk;
|
||||||
|
using Json;
|
||||||
|
|
||||||
|
public class Tootle.Watchlist : GLib.Object {
|
||||||
|
|
||||||
|
public GenericArray<string> users = new GenericArray<string> ();
|
||||||
|
public GenericArray<string> hashtags = new GenericArray<string> ();
|
||||||
|
public GenericArray<Notificator> notificators = new GenericArray<Notificator> ();
|
||||||
|
|
||||||
|
construct {
|
||||||
|
accounts.switched.connect (on_account_changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Watchlist () {
|
||||||
|
GLib.Object();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void on_account_changed (Account? account){
|
||||||
|
if(account != null)
|
||||||
|
reload ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reload () {
|
||||||
|
info ("Reloading");
|
||||||
|
|
||||||
|
notificators.@foreach (notificator => notificator.close ());
|
||||||
|
notificators.remove_range (0, notificators.length);
|
||||||
|
users.remove_range (0, users.length);
|
||||||
|
hashtags.remove_range (0, hashtags.length);
|
||||||
|
|
||||||
|
load ();
|
||||||
|
|
||||||
|
info ("Watching for %i users and %i hashtags", users.length, hashtags.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load () {
|
||||||
|
var users_array = settings.watched_users.split (",");
|
||||||
|
foreach (string item in users_array)
|
||||||
|
add (item, false);
|
||||||
|
|
||||||
|
var hashtags_array = settings.watched_hashtags.split (",");
|
||||||
|
foreach (string item in hashtags_array)
|
||||||
|
add (item, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save () {
|
||||||
|
var serialized_users = "";
|
||||||
|
users.@foreach (item => serialized_users += item + ",");
|
||||||
|
serialized_users = remove_last_delimiter (serialized_users);
|
||||||
|
settings.watched_users = serialized_users;
|
||||||
|
|
||||||
|
var serialized_hashtags = "";
|
||||||
|
hashtags.@foreach (item => serialized_hashtags += item + ",");
|
||||||
|
serialized_hashtags = remove_last_delimiter (serialized_hashtags);
|
||||||
|
settings.watched_hashtags = serialized_hashtags;
|
||||||
|
|
||||||
|
info ("Saved");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string remove_last_delimiter (string str) {
|
||||||
|
var i = str.last_index_of (",");
|
||||||
|
if (i > -1)
|
||||||
|
return str.substring (0, i);
|
||||||
|
else
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notificator get_notificator (string hashtag) {
|
||||||
|
var url = "%s/api/v1/streaming/?stream=hashtag&tag=%s&access_token=%s".printf (accounts.formal.instance, hashtag, accounts.formal.token);
|
||||||
|
var msg = new Soup.Message ("GET", url);
|
||||||
|
var notificator = new Notificator (msg);
|
||||||
|
notificator.status_added.connect (on_status_added);
|
||||||
|
return notificator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void on_status_added (ref Status status) {
|
||||||
|
var obj = new Notification (-1);
|
||||||
|
obj.type = NotificationType.WATCHLIST;
|
||||||
|
obj.account = status.account;
|
||||||
|
obj.status = status;
|
||||||
|
accounts.formal.notification (ref obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add (string entity, bool is_hashtag) {
|
||||||
|
if (entity == "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (is_hashtag) {
|
||||||
|
hashtags.add (entity);
|
||||||
|
var notificator = get_notificator (entity);
|
||||||
|
notificator.start ();
|
||||||
|
notificators.add (notificator);
|
||||||
|
info ("Added #%s", entity);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
users.add (entity);
|
||||||
|
info ("Added @%s", entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove (string entity, bool is_hashtag) {
|
||||||
|
int i = -1;
|
||||||
|
if (is_hashtag)
|
||||||
|
hashtags.@foreach (item => {
|
||||||
|
i++;
|
||||||
|
if (item == entity) {
|
||||||
|
var notificator = notificators.@get(i);
|
||||||
|
notificator.close ();
|
||||||
|
notificators.remove_index (i);
|
||||||
|
hashtags.remove_index (i);
|
||||||
|
info ("Removed #%s", entity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
else
|
||||||
|
users.@foreach (item => {
|
||||||
|
i++;
|
||||||
|
if (item == entity) {
|
||||||
|
users.remove_index (i);
|
||||||
|
info ("Removed @%s", entity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
Gtk.ModelButton item_search;
|
Gtk.ModelButton item_search;
|
||||||
Gtk.ModelButton item_favs;
|
Gtk.ModelButton item_favs;
|
||||||
Gtk.ModelButton item_direct;
|
Gtk.ModelButton item_direct;
|
||||||
|
Gtk.ModelButton item_watchlist;
|
||||||
|
|
||||||
private class AccountView : Gtk.ListBoxRow{
|
private class AccountView : Gtk.ListBoxRow{
|
||||||
|
|
||||||
|
@ -78,6 +79,10 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
item_search.text = _("Search");
|
item_search.text = _("Search");
|
||||||
item_search.clicked.connect (() => window.open_view (new SearchView ()));
|
item_search.clicked.connect (() => window.open_view (new SearchView ()));
|
||||||
|
|
||||||
|
item_watchlist = new Gtk.ModelButton ();
|
||||||
|
item_watchlist.text = _("Watchlist");
|
||||||
|
item_watchlist.clicked.connect (() => WatchlistDialog.open ());
|
||||||
|
|
||||||
item_settings = new Gtk.ModelButton ();
|
item_settings = new Gtk.ModelButton ();
|
||||||
item_settings.text = _("Settings");
|
item_settings.text = _("Settings");
|
||||||
item_settings.clicked.connect (() => SettingsDialog.open ());
|
item_settings.clicked.connect (() => SettingsDialog.open ());
|
||||||
|
@ -92,7 +97,8 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||||
grid.attach(new Gtk.Separator (Gtk.Orientation.HORIZONTAL), 0, 6, 1, 1);
|
grid.attach(new Gtk.Separator (Gtk.Orientation.HORIZONTAL), 0, 6, 1, 1);
|
||||||
grid.attach(item_refresh, 0, 7, 1, 1);
|
grid.attach(item_refresh, 0, 7, 1, 1);
|
||||||
grid.attach(item_search, 0, 8, 1, 1);
|
grid.attach(item_search, 0, 8, 1, 1);
|
||||||
grid.attach(item_settings, 0, 9, 1, 1);
|
grid.attach(item_watchlist, 0, 9, 1, 1);
|
||||||
|
grid.attach(item_settings, 0, 10, 1, 1);
|
||||||
grid.show_all ();
|
grid.show_all ();
|
||||||
|
|
||||||
menu = new Gtk.Popover (null);
|
menu = new Gtk.Popover (null);
|
||||||
|
|
|
@ -41,13 +41,15 @@ public class Tootle.NotificationWidget : Gtk.Grid {
|
||||||
get_style_context ().add_class ("notification");
|
get_style_context ().add_class ("notification");
|
||||||
|
|
||||||
if (notification.status != null) {
|
if (notification.status != null) {
|
||||||
Tootle.network.status_removed.connect (id => {
|
network.status_removed.connect (id => {
|
||||||
if (id == notification.status.id)
|
if (id == notification.status.id)
|
||||||
destroy ();
|
destroy ();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy.connect (() => {
|
destroy.connect (() => {
|
||||||
|
if (separator != null)
|
||||||
|
separator.destroy ();
|
||||||
separator = null;
|
separator = null;
|
||||||
status_widget = null;
|
status_widget = null;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue