Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Port to GTK4 #310

Merged
merged 52 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
77a37d4
Let's do this
Apr 6, 2021
42eafdf
Almost made it compile
Apr 6, 2021
8fb55a8
Now it compiles
Apr 7, 2021
0618b18
Fix base.ui
Apr 8, 2021
e3f4c8a
Fix TabbedBase
Apr 8, 2021
04a1033
Add sidebar view
Apr 9, 2021
138c6b8
Clean ui files
Apr 9, 2021
2e80689
Sidebar: add account switching mode
Apr 9, 2021
514ee97
Populate sidebar with account actions
Apr 11, 2021
3392547
Fix preferences
Apr 11, 2021
d6bdbd7
Fix view title
Apr 12, 2021
48c1d01
Fix thread view
Apr 13, 2021
d17bd2d
Refactor Streams. Again.
Apr 14, 2021
192b32d
Initial Profile view redesign
Apr 17, 2021
161a5c5
New cache system idea
Apr 20, 2021
808fb95
Save window size
Apr 20, 2021
b80a4ec
Account switching
Apr 21, 2021
b7f5559
Fix NewAccount dialog
Apr 21, 2021
b91cb3d
Sidebar: implement account manager mode
Apr 22, 2021
e501716
Add sidebar toggle button
May 15, 2021
4f51c19
Change some timeline icons
May 15, 2021
3d58e44
Rework image cache
May 16, 2021
5c1a9fe
Leaks. Leaks everywhere.
May 16, 2021
5d49e98
Fix Adw.Avatars not being destroyed. Kinda.
May 18, 2021
59c6013
Fix some leaks for GTK 4.3
Jun 15, 2021
a2d5cbf
Doing something controversial
Jun 18, 2021
cdfe8da
Clean up
Jun 18, 2021
6e10056
Reimplement status actions
Jul 2, 2021
1d25465
Cosmetic tweaks
Jul 5, 2021
9e13b5f
Add desktop notifications
Jul 10, 2021
c454e9c
Profile View: move note field up
Jul 10, 2021
04a062f
Work on notifications
Jul 11, 2021
190eb49
Notifications...
Jul 12, 2021
4344d43
Stuff
Jul 14, 2021
12838b3
Add Background widget
Jul 14, 2021
e7a8d54
Remove old Compose dialog
Jul 16, 2021
2b07c00
Add cw field to Composer
Jul 16, 2021
22f42de
Figure out DropDowns
Jul 16, 2021
960da9f
Make composer actually work
Jul 17, 2021
041fcba
Fix symbolic icon recoloring
Jul 17, 2021
81eb396
Initial attachment box
Jul 18, 2021
f059607
Attachment styling
Jul 19, 2021
b2f589a
Open attachments on click
Jul 19, 2021
414825f
Rename Desktop to Host
Jul 19, 2021
e2c6088
Display attachment descriptions
Jul 19, 2021
01f8a82
ImageCache: Store paintables instead of pixbufs
Jul 19, 2021
1709d41
Cosmetics
Jul 19, 2021
eae389f
Composer: Support replies
Jul 23, 2021
179e721
Remove NYI stuff
Jul 23, 2021
f8458ba
Make sidebar useful
Jul 23, 2021
1b60716
Lists are broken
Jul 23, 2021
bb7c1a8
Bump version
Jul 23, 2021
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
Prev Previous commit
Next Next commit
Work on notifications
  • Loading branch information
Bleak Grey committed Jul 11, 2021
commit 04a062fd832a21d64b4bea145ff60b2622388028
13 changes: 1 addition & 12 deletions src/API/Notification.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,13 @@ public class Tootle.API.Notification : Entity, Widgetizable {
public string id { get; set; }
public API.Account account { get; set; }
public string? kind { get; set; default = null; }
public string created_at { get; set; }
// public string created_at { get; set; }
public API.Status? status { get; set; default = null; }

public override Gtk.Widget to_widget () {
return new Widgets.Notification (this);
}

// public Soup.Message? dismiss () {
// if (kind == NotificationType.FOLLOW_REQUEST)
// return reject_follow_request ();

// var req = new Request.POST ("/api/v1/notifications/dismiss")
// .with_account (accounts.active)
// .with_param ("id", id)
// .exec ();
// return req;
// }

public Soup.Message accept_follow_request () {
var req = new Request.POST (@"/api/v1/follow_requests/$(account.id)/authorize")
.with_account (accounts.active)
Expand Down
5 changes: 5 additions & 0 deletions src/Services/Accounts/AccountStore.vala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public abstract class Tootle.AccountStore : GLib.Object {

public virtual void remove (InstanceAccount account) throws GLib.Error {
message (@"Removing account: $(account.handle)");
account.removed ();
saved.remove (account);
changed (saved);
save ();
Expand All @@ -73,6 +74,9 @@ public abstract class Tootle.AccountStore : GLib.Object {
}

public void activate (InstanceAccount? account) {
if (active != null)
active.deactivated ();

if (account == null) {
message ("Reset active account");
return;
Expand All @@ -94,6 +98,7 @@ public abstract class Tootle.AccountStore : GLib.Object {
}

accounts.active = account;
active.activated ();
switched (active);
}

Expand Down
75 changes: 63 additions & 12 deletions src/Services/Accounts/InstanceAccount.vala
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,42 @@ public class Tootle.InstanceAccount : API.Account, Streamable {
public string? client_id { get; set; }
public string? client_secret { get; set; }
public string? access_token { get; set; }
public Error? error { get; set; }

public ArrayList<GLib.Notification> desktop_inbox { get; set; default = new ArrayList<GLib.Notification> (); }
public int64 last_read_notification { get; set; default = 0; }
public uint unread_notifications { get; set; default = 0; }
public Error? error { get; set; } //TODO: use this field when server invalidates the auth token

public new string handle {
owned get { return @"@$username@$domain"; }
}

public virtual signal void added () {
subscribed = true;
check_notifications ();
}
public virtual signal void removed () {
subscribed = false;
}

public virtual signal void activated () {}
public virtual signal void deactivated () {}


construct {
construct_streamable ();
stream_event[Mastodon.Account.EVENT_NOTIFICATION].connect (on_notification);
}
~InstanceAccount () {
destruct_streamable ();
}

public InstanceAccount.empty (string instance){
Object (
id: "",
instance: instance
);
}
~InstanceAccount () {
destruct_streamable ();
}



// Core functions

public bool is_current () {
return accounts.active.access_token == access_token;
Expand Down Expand Up @@ -65,6 +77,41 @@ public class Tootle.InstanceAccount : API.Account, Streamable {



// Notifications

public int unread_count { get; set; default = 0; }
public int last_read_id { get; set; default = 0; }
public ArrayList<GLib.Notification> unread_toasts { get; set; default = new ArrayList<GLib.Notification> (); }
public ArrayList<Object> toast_inhibitors { get; set; default = new ArrayList<Object> (); }

public virtual void check_notifications () {
new Request.GET ("/api/v1/markers?timeline[]=notifications")
.with_account (this)
.then ((sess, msg) => {
var root = network.parse (msg);
var notifications = root.get_object_member ("notifications");
last_read_id = int.parse (notifications.get_string_member ("last_read_id") );

if (notifications.has_member ("pleroma")) {
var pleroma = notifications.get_object_member ("pleroma");
unread_count = (int)pleroma.get_int_member ("unread_count");
}
})
.exec ();
}

public virtual void read_notifications () {
message ("Read notifications");
unread_count = 0;
unread_toasts.@foreach (toast => {
var id = toast.get_data<string> ("id");
app.withdraw_notification (id);
return true;
});
}



// Streamable

public string? _connection_url { get; set; }
Expand All @@ -76,12 +123,13 @@ public class Tootle.InstanceAccount : API.Account, Streamable {

public virtual void on_notification (Streamable.Event ev) {
var obj = Entity.from_json (typeof (API.Notification), ev.get_node ()) as API.Notification;
var toast = create_desktop_toast (obj);
app.send_notification (obj.id.to_string (), toast);
send_toast (obj);
}

// TODO: notification actions
public virtual GLib.Notification create_desktop_toast (API.Notification obj) {
public void send_toast (API.Notification obj) {
if (!toast_inhibitors.is_empty) return;

string descr;
describe_kind (obj.kind, null, out descr, obj.account);

Expand All @@ -96,7 +144,10 @@ public class Tootle.InstanceAccount : API.Account, Streamable {
var icon = new FileIcon (file);
toast.set_icon (icon);

return toast;
var id = obj.id.to_string ();
toast.set_data<string> ("id", id);
app.send_notification (id, toast);
unread_toasts.add (toast);
}

}
21 changes: 14 additions & 7 deletions src/Services/Accounts/Mastodon/Account.vala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ public class Tootle.Mastodon.Account : InstanceAccount {
public const string KIND_FOLLOW_REQUEST = "__follow-request";
public const string KIND_REMOTE_REBLOG = "__remote-reblog";

public Views.Sidebar.Item notifications_item;

construct {
notifications_item = new Views.Sidebar.Item () {
label = "Notifications",
icon = "bell-symbolic",
on_activated = () => {
app.main_window.open_view (new Views.Notifications ());
}
};
bind_property ("unread_count", notifications_item, "badge", BindingFlags.SYNC_CREATE);
}

class Test : AccountStore.BackendTest {

public override string? get_backend (Json.Object obj) {
Expand All @@ -36,13 +49,7 @@ public class Tootle.Mastodon.Account : InstanceAccount {
label = "Timelines",
icon = "user-home-symbolic"
});
model.append (new Views.Sidebar.Item () {
label = "Notifications",
icon = "bell-symbolic",
on_activated = () => {
app.main_window.open_view (new Views.Notifications ());
}
});
model.append (notifications_item);
model.append (new Views.Sidebar.Item () {
label = "Direct Messages",
icon = API.Visibility.DIRECT.get_icon ()
Expand Down
2 changes: 1 addition & 1 deletion src/Services/Accounts/SecretAccountStore.vala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class Tootle.SecretAccountStore : AccountStore {
var account = secret_to_account (item);
if (account != null) {
saved.add (account);
account.subscribed = true;
account.added ();
}
});
changed (saved);
Expand Down
38 changes: 26 additions & 12 deletions src/Services/Cache/EntityCache.vala
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
public class Tootle.EntityCache : AbstractCache {

public Entity lookup_or_insert (owned Json.Node node, owned Type type) {
// Must return unique id for each JSON entity node
protected string? get_node_cache_id (owned Json.Node node) {
var obj = node.get_object ();
var id = obj.get_member ("uri").get_string ();
var key = get_key (id);

Entity entity;
if (contains (key)) {
entity = lookup (key) as Entity;
message ("serving cached: "+id);
if (obj.has_member ("uri")) {
return obj.get_string_member ("uri");
}
else {

return null;
}

public Entity lookup_or_insert (owned Json.Node node, owned Type type) {
Entity entity = null;
var id = get_node_cache_id (node);

// Entity can't be cached
if (id == null) {
entity = Entity.from_json (type, node);
insert (id, entity);
}
else {

if (entity == null)
warning ("lookup_or_insert() returned null. This should not happen.");
// Entity can be reused from cache
if (contains (id)) {
entity = lookup (get_key (id)) as Entity;
message ("Reused: "+id);
}
// It's a new instance and we need to store it
else {
entity = Entity.from_json (type, node);
insert (id, entity);
}
}

return entity;
}
Expand Down
101 changes: 38 additions & 63 deletions src/Views/Notifications.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,45 @@ using Gdk;

public class Tootle.Views.Notifications : Views.Timeline, AccountHolder, Streamable {

public Notifications () {
Object (
url: "/api/v1/notifications",
label: _("Notifications"),
icon: "bell-symbolic"
);
accepts = typeof (API.Notification);
}

public override string? get_stream_url () {
return account.get_stream_url ();
}

// public override void on_shown () {
// if (has_unread ()) {
// needs_attention = false;
// account.has_unread_notifications = false;
// account.last_seen_notification = last_id;
// accounts.safe_save ();
// }
// }
protected InstanceAccount? last_account = null;

//FIXME: Display unread dot

// public override void on_account_changed (InstanceAccount? acc) {
// base.on_account_changed (acc);
// if (account == null) {
// last_id = 0;
// needs_attention = false;
// }
// else {
// last_id = account.last_seen_notification;
// needs_attention = account.has_unread_notifications;
// }
// }

// public override bool request () {
// if (account != null) {
// account.cached_notifications.@foreach (n => {
// model.append (n);
// return true;
// });
// }
// return base.request ();
// }

// bool has_unread () {
// if (account == null)
// return false;
// return last_id > account.last_seen_notification || needs_attention;
// }

// public override void on_stream_event (Streamable.Event ev) {
// try {
// switch (ev.type) {
// case Mastodon.Account.EVENT_NOTIFICATION:
// var entity = Entity.from_json (accepts, ev.get_node ());
// model.insert (0, entity);
// return;
// }
// }
// catch (Error e) {
// warning ("Couldn't process stream event: " + e.message);
// }
// }
public Notifications () {
Object (
url: "/api/v1/notifications",
label: _("Notifications"),
icon: "bell-symbolic"
);
accepts = typeof (API.Notification);
}

public override void on_account_changed (InstanceAccount? acc) {
base.on_account_changed (acc);

if (last_account != null) {
last_account.toast_inhibitors.remove (this);
acc.stream_event[Mastodon.Account.EVENT_NOTIFICATION].disconnect (on_new_post);
}

last_account = acc;
acc.stream_event[Mastodon.Account.EVENT_NOTIFICATION].connect (on_new_post);
}

public override void on_shown () {
base.on_shown ();
if (account != null) {
if (!account.toast_inhibitors.contains (this))
account.toast_inhibitors.add (this);

account.read_notifications ();
}
}
public override void on_hidden () {
base.on_hidden ();
if (account != null) {
if (account.toast_inhibitors.contains (this))
account.toast_inhibitors.remove (this);
}
}

}