Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(notifications): groups #1193

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/API/GroupedNotificationsResults.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
public class Tuba.API.GroupedNotificationsResults : Entity {
public class NotificationGroup : API.Notification, Widgetizable {
public string most_recent_notification_id { get; set; }
public Gee.ArrayList<string> sample_account_ids { get; set; }
public Gee.ArrayList<API.Account> accounts { get; set; }
public string? status_id { get; set; default = null; }

public override Type deserialize_array_type (string prop) {
switch (prop) {
case "sample-account-ids":
return Type.STRING;
}

return base.deserialize_array_type (prop);
}

public override Gtk.Widget to_widget () {
var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
var avi_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
box.append (avi_box);
box.append (base.to_widget ());

foreach (var account in accounts) {
avi_box.append (new Widgets.Avatar () {
account = account,
size = 30,
overflow = Gtk.Overflow.HIDDEN,
allow_mini_profile = true
});
}

return box;
}
}

public Gee.ArrayList<API.Account> accounts { get; set; }
public Gee.ArrayList<API.Status> statuses { get; set; }
public Gee.ArrayList<NotificationGroup> notification_groups { get; set; }

public override Type deserialize_array_type (string prop) {
switch (prop) {
case "accounts":
return typeof (API.Account);
case "statuses":
return typeof (API.Status);
case "notification-groups":
return typeof (NotificationGroup);
}

return base.deserialize_array_type (prop);
}

public static GroupedNotificationsResults from (Json.Node node) throws Error {
return Entity.from_json (typeof (API.GroupedNotificationsResults), node) as API.GroupedNotificationsResults;
}
}
1 change: 0 additions & 1 deletion src/API/Instance.vala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public class Tuba.API.Instance : Entity {
public Gee.ArrayList<Rule>? rules { get; set; }

public bool tuba_can_translate { get; set; default=false; }
public int8 tuba_mastodon_version { get; set; default=0; }

public override Type deserialize_array_type (string prop) {
switch (prop) {
Expand Down
1 change: 1 addition & 0 deletions src/API/Notification.vala
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class Tuba.API.Notification : Entity, Widgetizable {
public string? emoji { get; set; default = null; }
public string? emoji_url { get; set; default = null; }
public API.Admin.Report? report { get; set; default = null; }
public string? group_key { get; set; default = null; }

// the docs claim that 'relationship_severance_event'
// is the one used but that is not true
Expand Down
1 change: 1 addition & 0 deletions src/API/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ sources += files(
'EmojiReaction.vala',
'Entity.vala',
'Funkwhale.vala',
'GroupedNotificationsResults.vala',
'Instance.vala',
'InstanceV2.vala',
'List.vala',
Expand Down
4 changes: 2 additions & 2 deletions src/Dialogs/Preferences.vala
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public class Tuba.Dialogs.Preferences : Adw.PreferencesDialog {

private Gee.HashMap<Adw.SwitchRow, bool>? notification_filter_policy_status = null;
void setup_notification_filters () {
if (!accounts.active.probably_has_notification_filters) return;
if (!accounts.active.tuba_probably_has_notification_filters) return;

new Request.GET ("/api/v1/notifications/policy")
.with_account (accounts.active)
Expand All @@ -239,7 +239,7 @@ public class Tuba.Dialogs.Preferences : Adw.PreferencesDialog {
})
.on_error ((code, message) => {
if (code == 404) {
accounts.active.probably_has_notification_filters = false;
accounts.active.tuba_probably_has_notification_filters = false;
} else {
warning (@"Error while trying to get notification policy: $code $message");
}
Expand Down
8 changes: 8 additions & 0 deletions src/Services/Accounts/AccountStore.vala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract class Tuba.AccountStore : GLib.Object {

public abstract void load () throws GLib.Error;
public abstract void save () throws GLib.Error;
public abstract void update_account (InstanceAccount account) throws GLib.Error;
public void safe_save () {
try {
save ();
Expand Down Expand Up @@ -122,6 +123,13 @@ public abstract class Tuba.AccountStore : GLib.Object {
if (account == null)
throw new Oopsie.INTERNAL (@"Account $handle has unknown backend: $backend");

if (obj.has_member ("api-versions")) {
var api_versions = obj.get_object_member ("api-versions");
if (api_versions != null) {
account.tuba_mastodon_version = (int8) api_versions.get_int_member ("mastodon");
}
}

if (account.uuid == null || !GLib.Uuid.string_is_valid (account.uuid)) account.uuid = GLib.Uuid.string_random ();
return account;
}
Expand Down
11 changes: 7 additions & 4 deletions src/Services/Accounts/InstanceAccount.vala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public class Tuba.InstanceAccount : API.Account, Streamable {
public string? access_token { get; set; }
public bool needs_update { get; set; default=false; }
public Error? error { get; set; } //TODO: use this field when server invalidates the auth token
public bool probably_has_notification_filters { get; set; default=false; }
public bool tuba_probably_has_notification_filters { get; set; default=false; }
public int8 tuba_mastodon_version { get; set; default=0; }

public GLib.ListStore known_places = new GLib.ListStore (typeof (Place));

Expand Down Expand Up @@ -488,15 +489,17 @@ public class Tuba.InstanceAccount : API.Account, Streamable {
var node = network.parse_node (parser);
if (node == null) return;

this.probably_has_notification_filters = true;
this.tuba_probably_has_notification_filters = true;
var instance_v2 = API.InstanceV2.from (node);

if (instance_v2 != null) {
if (instance_v2.configuration != null && instance_v2.configuration.translation != null)
this.instance_info.tuba_can_translate = instance_v2.configuration.translation.enabled;

if (instance_v2.api_versions != null && instance_v2.api_versions.mastodon > 0)
this.instance_info.tuba_mastodon_version = instance_v2.api_versions.mastodon;
if (instance_v2.api_versions != null && instance_v2.api_versions.mastodon > 0 && this.tuba_mastodon_version != instance_v2.api_versions.mastodon) {
this.tuba_mastodon_version = instance_v2.api_versions.mastodon;
accounts.update_account (this);
}
}
})
.exec ();
Expand Down
13 changes: 13 additions & 0 deletions src/Services/Accounts/SecretAccountStore.vala
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ public class Tuba.SecretAccountStore : AccountStore {
debug (@"Saved $(saved.size) accounts");
}

public override void update_account (InstanceAccount account) throws GLib.Error {
account_to_secret (account);
debug (@"Updated $(account.full_handle)");
}

public override void remove (InstanceAccount account) throws GLib.Error {
base.remove (account);

Expand Down Expand Up @@ -181,6 +186,14 @@ public class Tuba.SecretAccountStore : AccountStore {
builder.set_member_name ("admin-mode");
builder.add_boolean_value (account.admin_mode);

builder.set_member_name ("api-versions");
builder.begin_object ();

builder.set_member_name ("mastodon");
builder.add_int_value (account.tuba_mastodon_version);

builder.end_object ();

// If display name has emojis it's
// better to save and load them
// so users don't see their shortcode
Expand Down
10 changes: 6 additions & 4 deletions src/Services/Network/Request.vala
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,15 @@ public class Tuba.Request : GLib.Object {
public Request with_ctx (Gtk.Widget ctx) {
this.has_ctx = true;
this.ctx = ctx;
this.ctx.destroy.connect (() => {
this.cancellable.cancel ();
this.ctx = null;
});
this.ctx.destroy.connect (on_ctx_destroy);
return this;
}

private void on_ctx_destroy () {
this.cancellable.cancel ();
this.ctx = null;
}

public Request on_error (owned Network.ErrorCallback cb) {
this.error_cb = (owned) cb;
return this;
Expand Down
61 changes: 57 additions & 4 deletions src/Views/Notifications.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
private Binding badge_number_binding;
private Binding filtered_notifications_count_binding;
private Adw.Banner notifications_filter_banner;
private bool enabled_group_notifications = false;

public int32 filtered_notifications {
set {
Expand Down Expand Up @@ -32,7 +33,8 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
}

construct {
url = "/api/v1/notifications";
enabled_group_notifications = accounts.active.tuba_mastodon_version >= 2;
url = @"/api/v$(enabled_group_notifications ? 2 : 1)/notifications";
label = _("Notifications");
icon = "tuba-bell-outline-symbolic";
accepts = typeof (API.Notification);
Expand Down Expand Up @@ -96,6 +98,7 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
}

public override void on_account_changed (InstanceAccount? acc) {
enabled_group_notifications = acc.tuba_mastodon_version >= 2;
filters_changed (false);
base.on_account_changed (acc);

Expand Down Expand Up @@ -143,7 +146,7 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
: account.last_read_id
);

if (account.probably_has_notification_filters)
if (account.tuba_probably_has_notification_filters)
update_filtered_notifications ();
}
}
Expand All @@ -155,6 +158,56 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
}
}

public override bool request () {
if (!enabled_group_notifications) return base.request ();

append_params (new Request.GET (get_req_url ()))
.with_account (account)
.with_ctx (this)
.with_extra_data (Tuba.Network.ExtraData.RESPONSE_HEADERS)
.then ((in_stream, headers) => {
var parser = Network.get_parser_from_inputstream (in_stream);
var node = network.parse_node (parser);
if (node == null) return;

Object[] to_add = {};
var group_notifications = API.GroupedNotificationsResults.from (node);
foreach (var group in group_notifications.notification_groups) {
group.kind = null;

Gee.ArrayList<API.Account> group_accounts = new Gee.ArrayList<API.Account> ();
foreach (var account in group_notifications.accounts) {
if (account.id in group.sample_account_ids)
group_accounts.add (account);
}
group.accounts = group_accounts;

if (group.status_id != null) {
foreach (var status in group_notifications.statuses) {
if (status.id == group.status_id) {
group.status = status;
break;
}
}
}

if (!(should_hide (group))) to_add += group;
}
model.splice (model.get_n_items (), 0, to_add);

if (headers != null)
get_pages (headers.get_one ("Link"));

if (to_add.length == 0)
on_content_changed ();
on_request_finish ();
})
.on_error (on_error)
.exec ();

return GLib.Source.REMOVE;
}

public void update_filtered_notifications () {
new Request.GET ("/api/v1/notifications/policy")
.with_account (accounts.active)
Expand All @@ -174,7 +227,7 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
.on_error ((code, message) => {
accounts.active.filtered_notifications_count = 0;
if (code == 404) {
accounts.active.probably_has_notification_filters = false;
accounts.active.tuba_probably_has_notification_filters = false;
} else {
warning (@"Error while trying to get notification policy: $code $message");
}
Expand All @@ -195,7 +248,7 @@ public class Tuba.Views.Notifications : Views.Timeline, AccountHolder, Streamabl
}

public void filters_changed (bool refresh = true) {
string new_url = "/api/v1/notifications";
string new_url = @"/api/v$(enabled_group_notifications ? 2 : 1)/notifications";

if (settings.notification_filters.length == 0) {
this.is_all = true;
Expand Down
2 changes: 1 addition & 1 deletion src/Widgets/PreviewCardExplore.vala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public class Tuba.Widgets.PreviewCardExplore : Gtk.ListBoxRow {
css_classes = { "caption" }
};

if (accounts.active.instance_info.tuba_mastodon_version >= 1) {
if (accounts.active.tuba_mastodon_version >= 1) {
Gtk.Button discussions_button = new Gtk.Button () {
child = used_times_label,
// translators: tooltip text on 'explore' tab button to
Expand Down
Loading