Introduce Widgetizable interface

This commit is contained in:
Bleak Grey 2020-06-03 18:06:11 +03:00
parent d24e10ace2
commit 82e2f93bc8
12 changed files with 71 additions and 87 deletions

View File

@ -58,6 +58,8 @@ executable(
'src/API/Notification.vala', 'src/API/Notification.vala',
'src/API/NotificationType.vala', 'src/API/NotificationType.vala',
'src/API/Attachment.vala', 'src/API/Attachment.vala',
'src/API/Conversation.vala',
'src/Widgets/Widgetizable.vala',
'src/Widgets/Avatar.vala', 'src/Widgets/Avatar.vala',
'src/Widgets/AccountsButton.vala', 'src/Widgets/AccountsButton.vala',
'src/Widgets/RichLabel.vala', 'src/Widgets/RichLabel.vala',

View File

@ -0,0 +1,10 @@
public class Tootle.API.Conversation : GLib.Object, Json.Serializable, Widgetizable {
public string id { get; construct set; }
public bool unread { get; set; default = false; }
public Conversation () {
GLib.Object ();
}
}

View File

@ -1,4 +1,4 @@
public class Tootle.API.Notification : GLib.Object { public class Tootle.API.Notification : GLib.Object, Widgetizable {
public int64 id { get; construct set; } public int64 id { get; construct set; }
public Account account { get; construct set; } public Account account { get; construct set; }
@ -27,6 +27,10 @@ public class Tootle.API.Notification : GLib.Object {
); );
} }
public override Gtk.Widget to_widget () {
return new Widgets.Notification (this);
}
public Json.Node? serialize () { public Json.Node? serialize () {
var builder = new Json.Builder (); var builder = new Json.Builder ();
builder.begin_object (); builder.begin_object ();

View File

@ -1,7 +1,6 @@
using Gee; using Gee;
public class Tootle.API.Status : GLib.Object { public class Tootle.API.Status : GLib.Object, Widgetizable {
public int64 id { get; construct set; } //TODO: IDs are no longer guaranteed to be numbers. Replace with strings. public int64 id { get; construct set; } //TODO: IDs are no longer guaranteed to be numbers. Replace with strings.
public API.Account account { get; construct set; } public API.Account account { get; construct set; }
@ -115,6 +114,13 @@ public class Tootle.API.Status : GLib.Object {
content = Html.remove_tags (account.note); content = Html.remove_tags (account.note);
} }
public override Gtk.Widget to_widget () {
var w = new Widgets.Status (this);
w.button_press_event.connect (w.open);
return w;
}
public Json.Node? serialize () { public Json.Node? serialize () {
var builder = new Json.Builder (); var builder = new Json.Builder ();
builder.begin_object (); builder.begin_object ();

View File

@ -6,7 +6,8 @@ namespace Tootle {
public errordomain Oopsie { public errordomain Oopsie {
USER, USER,
PARSING, PARSING,
INSTANCE INSTANCE,
INTERNAL
} }
public static Application app; public static Application app;

View File

@ -4,7 +4,8 @@ public class Tootle.Views.Conversations : Views.Timeline {
Object ( Object (
url: "/api/v1/conversations", url: "/api/v1/conversations",
label: _("Conversations"), label: _("Conversations"),
icon: "mail-send-symbolic" icon: API.Visibility.DIRECT.get_icon (),
accepts: typeof (API.Conversation)
); );
} }

View File

@ -11,6 +11,7 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
label: _("Notifications"), label: _("Notifications"),
icon: Desktop.fallback_icon ("notification-symbolic", "preferences-system-notifications-symbolic", "user-invisible-symbolic") icon: Desktop.fallback_icon ("notification-symbolic", "preferences-system-notifications-symbolic", "user-invisible-symbolic")
); );
accepts = typeof (API.Notification);
on_notification.connect (add_notification); on_notification.connect (add_notification);
on_status_added.disconnect (add_status); on_status_added.disconnect (add_status);
} }
@ -41,20 +42,15 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
accounts.save (); accounts.save ();
} }
public override GLib.Object? to_entity (Json.Object? json) { public override GLib.Object to_entity (Json.Node node) throws Oopsie {
if (json != null) if (node == null)
return new API.Notification (json); throw new Oopsie.PARSING ("Received null Json.Node");
else
return null;
}
public override Widget? widgetize (GLib.Object? entity) { var obj = node.get_object ();
var n = entity as API.Notification; if (obj == null)
if (n == null) throw new Oopsie.PARSING ("Received Json.Node is not a Json.Object!");
return null;
var w = new Widgets.Notification (n); return new API.Notification (obj);
return w;
} }
public override void on_account_changed (InstanceAccount? acc) { public override void on_account_changed (InstanceAccount? acc) {
@ -72,7 +68,7 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
public override bool request () { public override bool request () {
if (account != null) { if (account != null) {
account.cached_notifications.@foreach (n => { account.cached_notifications.@foreach (n => {
append (widgetize (n)); append (n.to_widget ());
return true; return true;
}); });
} }
@ -86,7 +82,7 @@ public class Tootle.Views.Notifications : Views.Timeline, IAccountListener, IStr
} }
void add_notification (API.Notification n) { void add_notification (API.Notification n) {
prepend (widgetize (n)); prepend (n.to_widget ());
} }
} }

View File

@ -156,11 +156,12 @@ public class Tootle.Views.Profile : Views.Timeline {
return base.append_params (req); return base.append_params (req);
} }
public override GLib.Object? to_entity (Json.Object? json) { public override GLib.Object to_entity (Json.Node node) {
var obj = node.get_object ();
if (posts_tab.active) if (posts_tab.active)
return new API.Status (json); return new API.Status (obj);
else { else {
var account = new API.Account (json); var account = new API.Account (obj);
return new API.Status.from_account (account); return new API.Status.from_account (account);
} }
} }

View File

@ -5,6 +5,7 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
public string url { get; construct set; } public string url { get; construct set; }
public bool is_public { get; construct set; default = false; } public bool is_public { get; construct set; default = false; }
public Type accepts { get; set; default = typeof (API.Status); }
protected InstanceAccount? account = null; protected InstanceAccount? account = null;
@ -29,21 +30,15 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
return status.is_owned (); return status.is_owned ();
} }
public virtual GLib.Object? to_entity (Json.Object? json) { public virtual GLib.Object to_entity (Json.Node node) throws Oopsie {
return new API.Status (json); if (node == null)
} throw new Oopsie.PARSING ("Received null Json.Node");
public virtual Widget? widgetize (GLib.Object? entity) { var obj = node.get_object ();
var status = entity as API.Status; if (obj == null)
if (status == null) throw new Oopsie.PARSING ("Received Json.Node is not a Json.Object!");
return null;
var w = new Widgets.Status (status); return new API.Status (obj);
w.button_press_event.connect (w.open);
if (!is_status_owned (status))
w.avatar.button_press_event.connect (w.on_avatar_clicked);
return w;
} }
public void prepend (Widget? w) { public void prepend (Widget? w) {
@ -51,8 +46,10 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
} }
public virtual void append (Widget? w, bool first = false) { public virtual void append (Widget? w, bool first = false) {
if (w == null) if (w == null) {
warning ("Attempted to add an empty widget");
return; return;
}
if (first) if (first)
content_list.prepend (w); content_list.prepend (w);
@ -105,15 +102,13 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
append_params (new Request.GET (get_req_url ())) append_params (new Request.GET (get_req_url ()))
.with_account (account) .with_account (account)
.then_parse_array ((node, msg) => { .then_parse_array ((node, msg) => {
var obj = node.get_object (); try {
if (obj == null) var e = to_entity (node);
warning ("Received invalid Json.Object"); var w = e as Widgetizable;
else { append (w.to_widget ());
var entity = to_entity (obj); }
if (entity == null) catch (Error e) {
warning ("Can't convert Json.Object to required entity"); warning (@"Timeline item parse error: $(e.message)");
else
append (widgetize (entity));
} }
get_pages (msg.response_headers.get_one ("Link")); get_pages (msg.response_headers.get_one ("Link"));
}) })
@ -154,7 +149,7 @@ public class Tootle.Views.Timeline : IAccountListener, IStreamListener, Views.Ba
allow_update = settings.public_live_updates; allow_update = settings.public_live_updates;
if (settings.live_updates && allow_update) if (settings.live_updates && allow_update)
prepend (widgetize (status)); prepend (status.to_widget ());
} }
protected virtual void remove_status (int64 id) { protected virtual void remove_status (int64 id) {

View File

@ -1,40 +0,0 @@
using Gdk;
public class Tootle.Widgets.Account : Widgets.Status {
public Account (API.Account account) {
var status = new API.Status (-1);
status.account = account;
//status.url = account.url;
//status.content = "<a href=\"%s\">@%s</a>".printf (account.url, account.acct);
//status.created_at = account.created_at;
base (status);
//counters.visible = false;
//title_acct.visible = false;
//content_label.margin_bottom = 12;
}
protected override bool on_clicked (EventButton ev) {
if (ev.button == 1)
return on_avatar_clicked (ev);
return false;
}
public override bool open_menu (uint button, uint32 time) {
var menu = new Gtk.Menu ();
var item_open_link = new Gtk.MenuItem.with_label (_("Open in Browser"));
item_open_link.activate.connect (() => Desktop.open_uri (status.url));
var item_copy_link = new Gtk.MenuItem.with_label (_("Copy Link"));
item_copy_link.activate.connect (() => Desktop.copy (status.url));
menu.add (item_open_link);
menu.add (item_copy_link);
menu.show_all ();
menu.popup_at_pointer ();
return true;
}
}

View File

@ -135,6 +135,7 @@ public class Tootle.Widgets.Status : EventBox {
} }
menu_button.clicked.connect (open_menu); menu_button.clicked.connect (open_menu);
avatar.button_press_event.connect (on_avatar_clicked);
} }
public Status (API.Status status, API.NotificationType? _kind = null) { public Status (API.Status status, API.NotificationType? _kind = null) {
@ -166,7 +167,7 @@ public class Tootle.Widgets.Status : EventBox {
var view = new Views.Profile (status.formal.account); var view = new Views.Profile (status.formal.account);
return window.open_view (view); return window.open_view (view);
} }
return false; return true;
} }
public bool open (EventButton ev) { public bool open (EventButton ev) {

View File

@ -0,0 +1,7 @@
public interface Tootle.Widgetizable : GLib.Object {
public virtual Gtk.Widget to_widget () throws Oopsie {
throw new Tootle.Oopsie.INTERNAL ("Widgetizable didn't provide a Widget!");
}
}