diff --git a/data/gresource.xml b/data/gresource.xml index a4d2e5a0c..d3b6ef86d 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -93,6 +93,8 @@ <file>ui/widgets/status.ui</file> <file>ui/widgets/votebox.ui</file> <file>ui/widgets/preview_card.ui</file> + <file>ui/widgets/preview_card_internal.ui</file> + <file>ui/widgets/preview_card_explore.ui</file> <file>ui/widgets/profile.ui</file> <file>ui/dialogs/admin_dashboard.ui</file> <file>ui/dialogs/list_edit.ui</file> diff --git a/data/ui/widgets/preview_card.ui b/data/ui/widgets/preview_card.ui index 901649fbd..43036eabd 100644 --- a/data/ui/widgets/preview_card.ui +++ b/data/ui/widgets/preview_card.ui @@ -12,74 +12,7 @@ <class name="flat" /> </style> <child> - <object class="GtkBox" id="box"> - <property name="orientation">vertical</property> - <child> - <object class="GtkBox"> - <property name="orientation">vertical</property> - <property name="spacing">3</property> - <property name="valign">center</property> - <property name="hexpand">1</property> - <property name="margin-top">12</property> - <property name="margin-start">12</property> - <property name="margin-end">12</property> - <property name="margin-bottom">12</property> - <child> - <object class="GtkLabel" id="author_label"> - <property name="ellipsize">end</property> - <property name="halign">start</property> - <property name="single-line-mode">1</property> - <style> - <class name="dim-label" /> - <class name="caption" /> - </style> - </object> - </child> - <child> - <object class="GtkLabel" id="title_label"> - <property name="visible">0</property> - <property name="ellipsize">end</property> - <property name="halign">fill</property> - <property name="xalign">0</property> - <property name="lines">2</property> - <property name="wrap">1</property> - <property name="wrap-mode">word-char</property> - <style> - <class name="font-bold" /> - </style> - </object> - </child> - <child> - <object class="GtkLabel" id="description_label"> - <property name="visible">0</property> - <property name="ellipsize">end</property> - <property name="halign">fill</property> - <property name="xalign">0</property> - <property name="lines">3</property> - <property name="wrap">1</property> - <property name="wrap-mode">word-char</property> - <property name="single-line-mode">1</property> - <style> - <class name="caption" /> - </style> - </object> - </child> - <child> - <object class="GtkLabel" id="used_times_label"> - <property name="visible">0</property> - <property name="halign">fill</property> - <property name="xalign">0</property> - <property name="wrap">1</property> - <property name="wrap-mode">word-char</property> - <style> - <class name="dim-label" /> - <class name="caption" /> - </style> - </object> - </child> - </object> - </child> - </object> + <object class="TubaWidgetsPreviewCardInternal" id="box" /> </child> </object> </child> diff --git a/data/ui/widgets/preview_card_explore.ui b/data/ui/widgets/preview_card_explore.ui new file mode 100644 index 000000000..74d90fa28 --- /dev/null +++ b/data/ui/widgets/preview_card_explore.ui @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0" /> + <template class="TubaWidgetsPreviewCardExplore" parent="GtkListBoxRow"> + <style> + <class name="preview_card" /> + <class name="explore" /> + </style> + <child> + <object class="TubaWidgetsPreviewCardInternal" id="box"> + <property name="orientation">horizontal</property> + <property name="homogeneous">1</property> + </object> + </child> + </template> +</interface> diff --git a/data/ui/widgets/preview_card_internal.ui b/data/ui/widgets/preview_card_internal.ui new file mode 100644 index 000000000..37d0607cc --- /dev/null +++ b/data/ui/widgets/preview_card_internal.ui @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="4.0" /> + <template class="TubaWidgetsPreviewCardInternal" parent="GtkBox"> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="internal_box"> + <property name="orientation">vertical</property> + <property name="spacing">3</property> + <property name="valign">center</property> + <property name="hexpand">1</property> + <property name="margin-top">12</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-bottom">12</property> + <child> + <object class="GtkLabel" id="author_label"> + <property name="ellipsize">end</property> + <property name="halign">start</property> + <property name="single-line-mode">1</property> + <style> + <class name="dim-label" /> + <class name="caption" /> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="title_label"> + <property name="visible">0</property> + <property name="ellipsize">end</property> + <property name="halign">fill</property> + <property name="xalign">0</property> + <property name="lines">2</property> + <property name="wrap">1</property> + <property name="wrap-mode">word-char</property> + <style> + <class name="font-bold" /> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="description_label"> + <property name="visible">0</property> + <property name="ellipsize">end</property> + <property name="halign">fill</property> + <property name="xalign">0</property> + <property name="lines">3</property> + <property name="wrap">1</property> + <property name="wrap-mode">word-char</property> + <property name="single-line-mode">1</property> + <style> + <class name="caption" /> + </style> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/src/API/Instance.vala b/src/API/Instance.vala index 9950393f6..e1462fd16 100644 --- a/src/API/Instance.vala +++ b/src/API/Instance.vala @@ -17,6 +17,7 @@ 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) { diff --git a/src/API/InstanceV2.vala b/src/API/InstanceV2.vala index ccdc2d718..104602f9f 100644 --- a/src/API/InstanceV2.vala +++ b/src/API/InstanceV2.vala @@ -6,7 +6,12 @@ public class Tuba.API.InstanceV2 : Entity { public Translation translation { get; set; default = null; } } + public class APIVersions : Entity { + public int8 mastodon { get; set; default = 0; } + } + public Configuration configuration { get; set; default = null; } + public APIVersions? api_versions { get; set; default = null; } public static InstanceV2 from (Json.Node node) throws Error { return Entity.from_json (typeof (API.InstanceV2), node) as API.InstanceV2; diff --git a/src/API/Status/PreviewCard.vala b/src/API/Status/PreviewCard.vala index f1ecabba9..6013d2e9f 100644 --- a/src/API/Status/PreviewCard.vala +++ b/src/API/Status/PreviewCard.vala @@ -168,6 +168,8 @@ public class Tuba.API.PreviewCard : Entity, Widgetizable { } public override Gtk.Widget to_widget () { + if (this.kind == "link" && this.history != null) return new Widgets.PreviewCardExplore (this); + return new Widgets.PreviewCard (this); } diff --git a/src/Services/Accounts/InstanceAccount.vala b/src/Services/Accounts/InstanceAccount.vala index 194b7266a..3e414f8b9 100644 --- a/src/Services/Accounts/InstanceAccount.vala +++ b/src/Services/Accounts/InstanceAccount.vala @@ -466,8 +466,12 @@ public class Tuba.InstanceAccount : API.Account, Streamable { this.probably_has_notification_filters = true; var instance_v2 = API.InstanceV2.from (node); - if (instance_v2 != null && instance_v2.configuration != null && instance_v2.configuration.translation != null) { - this.instance_info.tuba_can_translate = instance_v2.configuration.translation.enabled; + 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; } }) .exec (); diff --git a/src/Views/ContentBase.vala b/src/Views/ContentBase.vala index 1506c08b1..5768d21d0 100644 --- a/src/Views/ContentBase.vala +++ b/src/Views/ContentBase.vala @@ -122,22 +122,13 @@ public class Tuba.Views.ContentBase : Views.Base { #if !USE_LISTVIEW Gtk.Widget widget = obj_widgetable.to_widget (); - if (widget as Widgets.PreviewCard == null) { - widget.add_css_class ("card"); - widget.add_css_class ("card-spacing"); - widget.focusable = true; - - // Thread lines overflow slightly - widget.overflow = Gtk.Overflow.HIDDEN; - return widget; - } - - return new Gtk.ListBoxRow () { - css_classes = { "card", "card-spacing" }, - focusable = false, - overflow = Gtk.Overflow.HIDDEN, - child = widget - }; + widget.add_css_class ("card"); + widget.add_css_class ("card-spacing"); + widget.focusable = true; + + // Thread lines overflow slightly + widget.overflow = Gtk.Overflow.HIDDEN; + return widget; #else return obj_widgetable.to_widget (); #endif diff --git a/src/Views/Link.vala b/src/Views/Link.vala new file mode 100644 index 000000000..215facba6 --- /dev/null +++ b/src/Views/Link.vala @@ -0,0 +1,9 @@ +public class Tuba.Views.Link : Views.Timeline { + public Link (string link) { + Object ( + url: @"/api/v1/timelines/link?url=$(Uri.escape_string (link))", + label: link, + icon: "tuba-globe-symbolic" + ); + } +} diff --git a/src/Views/meson.build b/src/Views/meson.build index a1c235948..81d12a65c 100644 --- a/src/Views/meson.build +++ b/src/Views/meson.build @@ -13,6 +13,7 @@ sources += files( 'Hashtag.vala', 'Hashtags.vala', 'Home.vala', + 'Link.vala', 'List.vala', 'Lists.vala', 'Local.vala', diff --git a/src/Widgets/PreviewCard.vala b/src/Widgets/PreviewCard.vala index 393e2384a..cddcb2d21 100644 --- a/src/Widgets/PreviewCard.vala +++ b/src/Widgets/PreviewCard.vala @@ -5,11 +5,7 @@ public class Tuba.Widgets.PreviewCard : Gtk.Box { } [GtkChild] public unowned Gtk.Button button; - [GtkChild] unowned Gtk.Box box; - [GtkChild] unowned Gtk.Label author_label; - [GtkChild] unowned Gtk.Label title_label; - [GtkChild] unowned Gtk.Label description_label; - [GtkChild] unowned Gtk.Label used_times_label; + [GtkChild] unowned Widgets.PreviewCardInternal box; private Gee.ArrayList<API.PreviewCard.AuthorEntity>? verified_authors = null; private string? author_url = null; @@ -73,57 +69,19 @@ public class Tuba.Widgets.PreviewCard : Gtk.Box { if (host != null) author = host; } catch {} } - author_label.label = author; + box.author_label.label = author; if (card_obj.title != "") { - title_label.label = title_label.tooltip_text = card_obj.title.replace ("\n", " ").strip (); - title_label.visible = true; + box.title_label.label = box.title_label.tooltip_text = card_obj.title.replace ("\n", " ").strip (); + box.title_label.visible = true; } if (card_obj.description != "") { - description_label.label = description_label.tooltip_text = card_obj.description; - description_label.visible = true; + box.description_label.label = box.description_label.tooltip_text = card_obj.description; + box.description_label.visible = true; } - if (card_obj.kind == "link" && card_obj.history != null && card_obj.history.size > 0) { - box.orientation = Gtk.Orientation.HORIZONTAL; - box.homogeneous = true; - image_widget.height_request = 70; - image_widget.add_css_class ("preview_card_h"); - image_widget.remove_css_class ("preview_card_v"); - - button.add_css_class ("explore"); - button.remove_css_class ("frame"); - button.clicked.connect (() => Host.open_url (card_obj.url)); - - if (description_label.visible) { - if (description_label.label.length > 109) - description_label.label = description_label.label.replace ("\n", " ").substring (0, 109) + "…"; - description_label.single_line_mode = false; - description_label.ellipsize = Pango.EllipsizeMode.NONE; - description_label.wrap = true; - description_label.wrap_mode = Pango.WrapMode.WORD_CHAR; - } - - var last_history_entry = card_obj.history.get (0); - var total_uses = int.parse (last_history_entry.uses); - var total_accounts = int.parse (last_history_entry.accounts); - // translators: the variables are numbers - var subtitle = _("Discussed %d times by %d people yesterday").printf (total_uses, total_accounts); - - if (card_obj.history.size > 1) { - last_history_entry = card_obj.history.get (1); - total_uses += int.parse (last_history_entry.uses); - total_accounts += int.parse (last_history_entry.accounts); - - // translators: the variables are numbers - subtitle = _("Discussed %d times by %d people in the past 2 days") - .printf (total_uses, total_accounts); - } - - used_times_label.label = subtitle; - used_times_label.visible = true; - } else if (card_obj.authors != null && card_obj.authors.size > 0) { + if (card_obj.authors != null && card_obj.authors.size > 0) { bool should_add = true; Gtk.Widget more_from_button = new Gtk.Button () { diff --git a/src/Widgets/PreviewCardExplore.vala b/src/Widgets/PreviewCardExplore.vala new file mode 100644 index 000000000..3a6bbc772 --- /dev/null +++ b/src/Widgets/PreviewCardExplore.vala @@ -0,0 +1,118 @@ +[GtkTemplate (ui = "/dev/geopjr/Tuba/ui/widgets/preview_card_explore.ui")] +public class Tuba.Widgets.PreviewCardExplore : Gtk.ListBoxRow { + ~PreviewCardExplore () { + debug ("Destroying PreviewCardExplore"); + } + + [GtkChild] unowned Widgets.PreviewCardInternal box; + + private string url; + public signal void open (); + + public PreviewCardExplore (API.PreviewCard card_obj) { + this.url = card_obj.url; + this.activate.connect (on_card_click); + this.open.connect (on_card_click); + + Gtk.Widget image_widget; + if (card_obj.image != null) { + image_widget = new Gtk.Picture () { + width_request = 25, + content_fit = Gtk.ContentFit.COVER, + height_request = 250, + css_classes = {"preview_card_h"} + }; + + Tuba.Helper.Image.request_paintable (card_obj.image, card_obj.blurhash, false, (paintable) => { + ((Gtk.Picture) image_widget).paintable = paintable; + }); + } else { + image_widget = new Gtk.Image.from_icon_name ("tuba-earth-symbolic") { + css_classes = {"preview_card_h"}, + icon_size = Gtk.IconSize.LARGE, + width_request = 70, + }; + } + image_widget.height_request = 70; + box.prepend (image_widget); + + var author = card_obj.provider_name; + if (author == "") { + try { + var uri = GLib.Uri.parse (card_obj.url, GLib.UriFlags.NONE); + var host = uri.get_host (); + if (host != null) author = host; + } catch {} + } + box.author_label.label = author; + + if (card_obj.title != "") { + box.title_label.label = box.title_label.tooltip_text = card_obj.title.replace ("\n", " ").strip (); + box.title_label.visible = true; + } + + if (card_obj.description != "") { + box.description_label.label = box.description_label.tooltip_text = card_obj.description; + box.description_label.visible = true; + box.description_label.single_line_mode = false; + box.description_label.ellipsize = Pango.EllipsizeMode.NONE; + box.description_label.wrap = true; + box.description_label.wrap_mode = Pango.WrapMode.WORD_CHAR; + + if (box.description_label.label.length > 109) + box.description_label.label = box.description_label.label.replace ("\n", " ").substring (0, 109) + "…"; + } + + + var last_history_entry = card_obj.history.get (0); + var total_uses = int.parse (last_history_entry.uses); + var total_accounts = int.parse (last_history_entry.accounts); + // translators: the variables are numbers + var subtitle = _("Discussed %d times by %d people yesterday").printf (total_uses, total_accounts); + + if (card_obj.history.size > 1) { + last_history_entry = card_obj.history.get (1); + total_uses += int.parse (last_history_entry.uses); + total_accounts += int.parse (last_history_entry.accounts); + + // translators: the variables are numbers + subtitle = _("Discussed %d times by %d people in the past 2 days") + .printf (total_uses, total_accounts); + } + + var used_times_label = new Gtk.Label (subtitle) { + xalign = 0.0f, + wrap = true, + wrap_mode = Pango.WrapMode.WORD_CHAR, + css_classes = { "caption" } + }; + + if (accounts.active.instance_info.tuba_mastodon_version >= 1) { + Gtk.Button discussions_button = new Gtk.Button () { + child = used_times_label, + // translators: tooltip text on 'explore' tab button to + // see posts where the selected article is + // being discussed. + tooltip_text = _("See Discussions"), + + // Looks weird flat, as it doesn't indicate + // that it's clickable, plus it's not dimmed and + // has padding. Looks out of place. + // css_classes = { "flat" }, + }; + discussions_button.clicked.connect (on_link_timeline_open); + box.internal_box.append (discussions_button); + } else { + used_times_label.add_css_class ("dim-label"); + box.internal_box.append (used_times_label); + } + } + + private void on_link_timeline_open () { + app.main_window.open_view (new Views.Link (this.url)); + } + + private void on_card_click () { + Host.open_url (this.url); + } +} diff --git a/src/Widgets/PreviewCardInternal.vala b/src/Widgets/PreviewCardInternal.vala new file mode 100644 index 000000000..9e51086b8 --- /dev/null +++ b/src/Widgets/PreviewCardInternal.vala @@ -0,0 +1,7 @@ +[GtkTemplate (ui = "/dev/geopjr/Tuba/ui/widgets/preview_card_internal.ui")] +public class Tuba.Widgets.PreviewCardInternal : Gtk.Box { + [GtkChild] public unowned Gtk.Label author_label; + [GtkChild] public unowned Gtk.Label title_label; + [GtkChild] public unowned Gtk.Label description_label; + [GtkChild] public unowned Gtk.Box internal_box; +} diff --git a/src/Widgets/meson.build b/src/Widgets/meson.build index b011216c0..3d8ad1bc7 100644 --- a/src/Widgets/meson.build +++ b/src/Widgets/meson.build @@ -15,6 +15,8 @@ sources += files( 'Notification.vala', 'NotificationRequest.vala', 'PreviewCard.vala', + 'PreviewCardInternal.vala', + 'PreviewCardExplore.vala', 'ProfileCover.vala', 'RelationshipButton.vala', 'RichLabel.vala',