diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f828ebc3..f672f44e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
needs: [ lint ]
container:
- image: bilelmoussaoui/flatpak-github-actions:gnome-43
+ image: bilelmoussaoui/flatpak-github-actions:gnome-47
options: --privileged
strategy:
matrix:
@@ -39,7 +39,7 @@ jobs:
uses: docker/setup-qemu-action@v2
with:
platforms: arm64
- - uses: flatpak/flatpak-github-actions/flatpak-builder@v6
+ - uses: flathub-infra/flatpak-github-actions/flatpak-builder@4388a4c5fc8bab58e1dfb7fc63267dca0f7b4976
with:
bundle: "dev.geopjr.Tuba.Devel.flatpak"
run-tests: true
diff --git a/build-aux/dev.geopjr.Tuba.Devel.json b/build-aux/dev.geopjr.Tuba.Devel.json
index 26ff18b7..fff35a3a 100644
--- a/build-aux/dev.geopjr.Tuba.Devel.json
+++ b/build-aux/dev.geopjr.Tuba.Devel.json
@@ -3,6 +3,7 @@
"runtime": "org.gnome.Platform",
"runtime-version": "47",
"sdk": "org.gnome.Sdk",
+ "sdk-extensions": [ "org.freedesktop.Sdk.Extension.llvm19" ],
"command": "dev.geopjr.Tuba",
"finish-args": [
"--device=dri",
@@ -26,12 +27,23 @@
],
"modules": [
{
- "name" : "libspelling",
- "buildsystem" : "meson",
- "config-opts" : [
+ "name": "libspelling",
+ "buildsystem": "meson",
+ "config-opts": [
"-Ddocs=false"
],
- "sources" : [
+ "build-options": {
+ "arch": {
+ "aarch64": {
+ "append-path": "/usr/lib/sdk/llvm19/bin",
+ "prepend-ld-library-path": "/usr/lib/sdk/llvm19/lib",
+ "env": {
+ "CC": "clang"
+ }
+ }
+ }
+ },
+ "sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/libspelling.git",
@@ -43,9 +55,20 @@
"name": "tuba",
"builddir": true,
"buildsystem": "meson",
- "config-opts" : [
+ "config-opts": [
"-Ddevel=true"
],
+ "build-options": {
+ "arch": {
+ "aarch64": {
+ "append-path": "/usr/lib/sdk/llvm19/bin",
+ "prepend-ld-library-path": "/usr/lib/sdk/llvm19/lib",
+ "env": {
+ "CC": "clang"
+ }
+ }
+ }
+ },
"sources": [
{
"type": "dir",
diff --git a/data/style.css b/data/style.css
index 5fd22944..56bde378 100644
--- a/data/style.css
+++ b/data/style.css
@@ -61,11 +61,6 @@ headerbar.flat.no-title .title {
margin: 0px;
}
-.ttl-thread-line {
- background: var(--window-fg-color);
- opacity: .1;
-}
-
.ttl-code {
padding: 12px;
background: rgba(150, 150, 150, .1);
@@ -351,15 +346,6 @@ headerbar.flat.no-title .title {
font-weight: initial;
}
-.ttl-thread-line.top {
- margin-top: -18px;
- margin-bottom: -3px;
-}
-
-.ttl-thread-line.bottom {
- margin-bottom: -20px;
-}
-
.attachment-overlay-icon {
border-radius: 100%;
min-height: 64px;
diff --git a/data/ui/widgets/status.ui b/data/ui/widgets/status.ui
index a32ea475..e672b6bd 100644
--- a/data/ui/widgets/status.ui
+++ b/data/ui/widgets/status.ui
@@ -36,18 +36,6 @@
1
-
-
-
-
-
-
diff --git a/src/API/Conversation.vala b/src/API/Conversation.vala
index d4825e8c..f6fca9c0 100644
--- a/src/API/Conversation.vala
+++ b/src/API/Conversation.vala
@@ -1,4 +1,4 @@
-public class Tuba.API.Conversation : Entity, Widgetizable {
+public class Tuba.API.Conversation : Entity, WidgetizableForListView {
public string id { get; set; }
public Gee.ArrayList? accounts { get; set; }
diff --git a/src/API/Status.vala b/src/API/Status.vala
index e4ff9611..65d999fd 100644
--- a/src/API/Status.vala
+++ b/src/API/Status.vala
@@ -1,4 +1,4 @@
-public class Tuba.API.Status : Entity, Widgetizable, SearchResult {
+public class Tuba.API.Status : Entity, WidgetizableForListView, SearchResult {
~Status () {
debug (@"[OBJ] Destroyed $(uri ?? "")");
@@ -219,7 +219,13 @@ public class Tuba.API.Status : Entity, Widgetizable, SearchResult {
}
public override Gtk.Widget to_widget () {
- return new Widgets.Status (this);
+ return new Widgets.Status ();
+ }
+
+ public override void fill_widget_with_content (Gtk.Widget widget) throws Oopsie {
+ ((Widgets.Status) widget).kind_instigator = this.account;
+ ((Widgets.Status) widget).status = this;
+ ((Widgets.Status) widget).init (this);
}
public override void open () {
diff --git a/src/Dialogs/Composer/AttachmentsPage.vala b/src/Dialogs/Composer/AttachmentsPage.vala
index 42f92a0f..589bc171 100644
--- a/src/Dialogs/Composer/AttachmentsPage.vala
+++ b/src/Dialogs/Composer/AttachmentsPage.vala
@@ -356,6 +356,10 @@ public class Tuba.AttachmentsPage : ComposerPage {
&& accounts.active.instance_info.configuration.media_attachments != null
&& accounts.active.instance_info.configuration.media_attachments.supported_mime_types != null
&& accounts.active.instance_info.configuration.media_attachments.supported_mime_types.size > 0
+ && !(
+ accounts.active.instance_info.configuration.media_attachments.supported_mime_types.size == 1
+ && accounts.active.instance_info.configuration.media_attachments.supported_mime_types[0] == "application/octet-stream"
+ )
) {
supported_mimes = accounts.active.instance_info.configuration.media_attachments.supported_mime_types;
}
diff --git a/src/Dialogs/Report.vala b/src/Dialogs/Report.vala
index 8cb9e09f..ee70a417 100644
--- a/src/Dialogs/Report.vala
+++ b/src/Dialogs/Report.vala
@@ -475,11 +475,7 @@ public class Tuba.Dialogs.Report : Adw.Dialog {
widget_status.can_target = false;
widget_status.focusable = false;
widget_status.actions.visible = false;
- #if USE_LISTVIEW
- widget_status.can_be_opened = false;
- #else
- widget_status.activatable = false;
- #endif
+ widget_status.can_be_opened = false;
listbox.append (new StatusRow (checkbutton, widget_status));
});
diff --git a/src/Views/Base.vala b/src/Views/Base.vala
index c25c09c7..64d4aa66 100644
--- a/src/Views/Base.vala
+++ b/src/Views/Base.vala
@@ -68,11 +68,7 @@ public class Tuba.Views.Base : Adw.BreakpointBin {
// [GtkChild] protected unowned Adw.Clamp clamp;
// [GtkChild] protected unowned Gtk.Box column_view;
[GtkChild] protected unowned Gtk.Stack states;
- #if USE_LISTVIEW
- [GtkChild] protected unowned Adw.ClampScrollable content_box;
- #else
- [GtkChild] protected unowned Adw.Clamp content_box;
- #endif
+ [GtkChild] protected unowned Adw.ClampScrollable content_box;
[GtkChild] protected unowned Gtk.Button status_button;
[GtkChild] unowned Gtk.Image status_image;
[GtkChild] unowned Gtk.Stack status_stack;
@@ -186,11 +182,9 @@ public class Tuba.Views.Base : Adw.BreakpointBin {
base.dispose ();
}
- #if !USE_LISTVIEW
- public virtual void unbind_listboxes () {
- this.last_widget = null;
- }
- #endif
+ public virtual void unbind_listboxes () {
+ this.last_widget = null;
+ }
protected virtual void build_actions () {}
diff --git a/src/Views/ContentBase.vala b/src/Views/ContentBase.vala
index 533156c1..ac6ec93d 100644
--- a/src/Views/ContentBase.vala
+++ b/src/Views/ContentBase.vala
@@ -1,10 +1,5 @@
public class Tuba.Views.ContentBase : Views.Base {
-
- #if USE_LISTVIEW
- protected Gtk.ListView content;
- #else
- protected Gtk.ListBox content;
- #endif
+ protected Gtk.ListBox content;
protected signal void reached_close_to_top ();
public GLib.ListStore model;
private bool bottom_reached_locked = false;
@@ -16,32 +11,21 @@ public class Tuba.Views.ContentBase : Views.Base {
construct {
model = new GLib.ListStore (typeof (Widgetizable));
- #if USE_LISTVIEW
- Gtk.SignalListItemFactory signallistitemfactory = new Gtk.SignalListItemFactory ();
- signallistitemfactory.bind.connect (bind_listitem_cb);
-
- content = new Gtk.ListView (new Gtk.NoSelection (model), signallistitemfactory) {
- css_classes = { "contentbase", "content", "background" },
- single_click_activate = true
- };
-
- content.activate.connect (on_content_item_activated);
- model.items_changed.connect (on_content_changed);
- #else
- model.items_changed.connect (on_content_changed);
- content = new Gtk.ListBox () {
- selection_mode = Gtk.SelectionMode.NONE,
- css_classes = { "content", "background" }
- };
-
- content.row_activated.connect (on_content_item_activated);
- content.bind_model (model, on_create_model_widget);
- #endif
+ model.items_changed.connect (on_content_changed);
+
+ content = new Gtk.ListBox () {
+ selection_mode = Gtk.SelectionMode.NONE,
+ css_classes = { "fake-content", "background" }
+ };
+
+ content.row_activated.connect (on_content_item_activated);
+ content.bind_model (model, on_create_model_widget);
content_box.child = content;
scrolled.vadjustment.value_changed.connect (on_scrolled_vadjustment_value_change);
scroll_to_top_rev.bind_property ("child-revealed", scroll_to_top_rev, "visible", GLib.BindingFlags.SYNC_CREATE);
}
+
~ContentBase () {
debug ("Destroying ContentBase");
}
@@ -71,26 +55,9 @@ public class Tuba.Views.ContentBase : Views.Base {
scroll_to_top_rev.reveal_child = reveal;
}
- #if USE_LISTVIEW
- protected virtual void bind_listitem_cb (GLib.Object item) {
- ((Gtk.ListItem) item).child = on_create_model_widget (((Gtk.ListItem) item).item);
-
- var gtklistitemwidget = ((Gtk.ListItem) item).child.get_parent ();
- if (gtklistitemwidget != null) {
- gtklistitemwidget.add_css_class ("card");
- gtklistitemwidget.add_css_class ("card-spacing");
- gtklistitemwidget.focusable = true;
-
- // Thread lines overflow slightly
- gtklistitemwidget.overflow = Gtk.Overflow.HIDDEN;
- }
- }
- #endif
public override void dispose () {
- #if !USE_LISTVIEW
- unbind_listboxes ();
- #endif
+ unbind_listboxes ();
base.dispose ();
}
@@ -114,31 +81,25 @@ public class Tuba.Views.ContentBase : Views.Base {
}
}
- #if !USE_LISTVIEW
- public override void unbind_listboxes () {
- if (content != null)
- content.bind_model (null, null);
- base.unbind_listboxes ();
- }
- #endif
+ public override void unbind_listboxes () {
+ if (content != null)
+ content.bind_model (null, null);
+ base.unbind_listboxes ();
+ }
public virtual Gtk.Widget on_create_model_widget (Object obj) {
var obj_widgetable = obj as Widgetizable;
if (obj_widgetable == null)
Process.exit (0);
try {
- #if !USE_LISTVIEW
- Gtk.Widget widget = obj_widgetable.to_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
+ Gtk.Widget widget = obj_widgetable.to_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;
} catch (Oopsie e) {
warning (@"Error on_create_model_widget: $(e.message)");
Process.exit (0);
@@ -155,13 +116,7 @@ public class Tuba.Views.ContentBase : Views.Base {
}, Priority.LOW);
}
- #if USE_LISTVIEW
- public virtual void on_content_item_activated (uint pos) {
- ((Widgetizable) ((ListModel) content.model).get_item (pos)).open ();
- }
- #else
- public virtual void on_content_item_activated (Gtk.ListBoxRow row) {
- Signal.emit_by_name (row, "open");
- }
- #endif
+ public virtual void on_content_item_activated (Gtk.ListBoxRow row) {
+ Signal.emit_by_name (row, "open");
+ }
}
diff --git a/src/Views/ContentBaseListView.vala b/src/Views/ContentBaseListView.vala
new file mode 100644
index 00000000..3f92b5da
--- /dev/null
+++ b/src/Views/ContentBaseListView.vala
@@ -0,0 +1,136 @@
+public class Tuba.Views.ContentBaseListView : Views.Base {
+
+ protected Gtk.ListView content;
+ protected signal void reached_close_to_top ();
+ public GLib.ListStore model;
+ private bool bottom_reached_locked = false;
+
+ public bool empty {
+ get { return model.get_n_items () <= 0; }
+ }
+
+ construct {
+ model = new GLib.ListStore (typeof (WidgetizableForListView));
+
+ Gtk.SignalListItemFactory signallistitemfactory = new Gtk.SignalListItemFactory ();
+ signallistitemfactory.setup.connect (setup_listitem_cb);
+ signallistitemfactory.bind.connect (bind_listitem_cb);
+
+ content = new Gtk.ListView (new Gtk.NoSelection (model), signallistitemfactory) {
+ css_classes = { "content", "background" },
+ single_click_activate = true
+ };
+
+ content.activate.connect (on_content_item_activated);
+ content_box.child = content;
+
+ scrolled.vadjustment.value_changed.connect (on_scrolled_vadjustment_value_change);
+ scroll_to_top_rev.bind_property ("child-revealed", scroll_to_top_rev, "visible", GLib.BindingFlags.SYNC_CREATE);
+ }
+ ~ContentBaseListView () {
+ debug ("Destroying ContentBaseListView");
+ }
+
+ protected virtual void on_scrolled_vadjustment_value_change () {
+ if (
+ !bottom_reached_locked
+ && scrolled.vadjustment.value > scrolled.vadjustment.upper - scrolled.vadjustment.page_size * 2
+ ) {
+ bottom_reached_locked = true;
+ on_bottom_reached ();
+ }
+
+ var is_close_to_top = scrolled.vadjustment.value <= 100;
+ set_scroll_to_top_reveal_child (
+ !is_close_to_top
+ && scrolled.vadjustment.value + scrolled.vadjustment.page_size + 100 < scrolled.vadjustment.upper
+ );
+ }
+
+ protected void set_scroll_to_top_reveal_child (bool reveal) {
+ if (reveal == scroll_to_top_rev.reveal_child) return;
+ if (reveal) scroll_to_top_rev.visible = true;
+
+ scroll_to_top_rev.reveal_child = reveal;
+ }
+
+ protected void setup_listitem_cb (GLib.Object item) {
+ Gtk.ListItem i = (Gtk.ListItem) item;
+ i.child = on_create_model_widget (i.item);
+
+ var gtklistitemwidget = i.child.get_parent ();
+ if (gtklistitemwidget != null) {
+ gtklistitemwidget.add_css_class ("card");
+ gtklistitemwidget.add_css_class ("card-spacing");
+ gtklistitemwidget.focusable = true;
+
+ // Thread lines overflow slightly
+ gtklistitemwidget.overflow = Gtk.Overflow.HIDDEN;
+ }
+ }
+
+ protected virtual void bind_listitem_cb (GLib.Object item) {
+ var obj_widgetable = ((Gtk.ListItem) item).item as WidgetizableForListView;
+ if (obj_widgetable == null)
+ Process.exit (0);
+
+ try {
+ obj_widgetable.fill_widget_with_content (((Gtk.ListItem) item).child);
+ } catch (Oopsie e) {
+ warning (@"Error bind_listitem_cb: $(e.message)");
+ Process.exit (0);
+ }
+ }
+
+ public override void dispose () {
+ base.dispose ();
+ }
+
+ public override void clear () {
+ base.clear ();
+ this.model.remove_all ();
+ }
+
+ protected virtual void clear_all_but_first (int i = 1) {
+ base.clear ();
+
+ print ("before splice!\n");
+ if (model.n_items > i)
+ model.splice (i, model.n_items - i, {});
+ }
+
+ public override void on_content_changed () {
+ if (empty) {
+ base_status = new StatusMessage ();
+ } else {
+ base_status = null;
+ }
+ }
+
+ public override void unbind_listboxes () {}
+ public virtual Gtk.Widget on_create_model_widget (Object obj) {
+ var obj_widgetable = obj as Widgetizable;
+ if (obj_widgetable == null)
+ Process.exit (0);
+ try {
+ return obj_widgetable.to_widget ();
+ } catch (Oopsie e) {
+ warning (@"Error on_create_model_widget: $(e.message)");
+ Process.exit (0);
+ }
+ }
+
+ public virtual void on_bottom_reached () {
+ uint timeout = 0;
+ timeout = Timeout.add (1000, () => {
+ bottom_reached_locked = false;
+ GLib.Source.remove (timeout);
+
+ return true;
+ }, Priority.LOW);
+ }
+
+ public virtual void on_content_item_activated (uint pos) {
+ ((WidgetizableForListView) ((ListModel) content.model).get_item (pos)).open ();
+ }
+}
diff --git a/src/Views/EditHistory.vala b/src/Views/EditHistory.vala
index a7db20ae..f0511464 100644
--- a/src/Views/EditHistory.vala
+++ b/src/Views/EditHistory.vala
@@ -14,17 +14,11 @@ public class Tuba.Views.EditHistory : Views.Timeline {
widget_status.actions.visible = false;
widget_status.menu_button.visible = false;
- #if USE_LISTVIEW
- widget_status.can_be_opened = false;
- widget_status.content.selectable = true;
- #else
- widget_status.activatable = false;
- #endif
+ widget_status.can_be_opened = false;
+ widget_status.content.selectable = true;
return widget_status;
}
- #if USE_LISTVIEW
- public override void on_content_item_activated (uint pos) {}
- #endif
+ public override void on_content_item_activated (uint pos) {}
}
diff --git a/src/Views/Lists.vala b/src/Views/Lists.vala
index d9d97475..b0efd02b 100644
--- a/src/Views/Lists.vala
+++ b/src/Views/Lists.vala
@@ -30,10 +30,6 @@ public class Tuba.Views.Lists : Views.Timeline {
action_box.append (edit_button);
action_box.append (delete_button);
- #if !USE_LISTVIEW
- this.activated.connect (() => open ());
- #endif
-
this.activatable = true;
this.add_suffix (action_box);
@@ -142,16 +138,6 @@ public class Tuba.Views.Lists : Views.Timeline {
public Adw.PreferencesDialog create_edit_preferences_dialog (API.List t_list) {
return new Dialogs.ListEdit (t_list);
}
-
- #if !USE_LISTVIEW
- public virtual signal void open () {
- if (this.list == null)
- return;
-
- var view = new Views.List (list);
- app.main_window.open_view (view);
- }
- #endif
}
public new bool empty {
diff --git a/src/Views/MutesBlocks.vala b/src/Views/MutesBlocks.vala
index 62fa48ac..d069cc31 100644
--- a/src/Views/MutesBlocks.vala
+++ b/src/Views/MutesBlocks.vala
@@ -1,6 +1,6 @@
public class Tuba.Views.MutesBlocks : Views.TabbedBase {
- Views.ContentBase mutes;
- Views.ContentBase blocks;
+ Views.ContentBaseListView mutes;
+ Views.ContentBaseListView blocks;
construct {
label = _("Mutes & Blocks");
diff --git a/src/Views/NotificationRequests.vala b/src/Views/NotificationRequests.vala
index d8b39c88..2f4361f8 100644
--- a/src/Views/NotificationRequests.vala
+++ b/src/Views/NotificationRequests.vala
@@ -44,13 +44,7 @@ public class Tuba.Views.NotificationRequests : Views.Timeline {
.exec ();
}
- #if USE_LISTVIEW
- public override void on_content_item_activated (uint pos) {
- ((Widgetizable) ((ListModel) content.model).get_item (pos)).open ();
- }
- #else
- public override void on_content_item_activated (Gtk.ListBoxRow row) {
- ((Widgets.NotificationRequest) row).open ();
- }
- #endif
+ public override void on_content_item_activated (uint pos) {
+ ((Widgetizable) ((ListModel) content.model).get_item (pos)).open ();
+ }
}
diff --git a/src/Views/Profile.vala b/src/Views/Profile.vala
index 24aea087..ac28c31d 100644
--- a/src/Views/Profile.vala
+++ b/src/Views/Profile.vala
@@ -52,11 +52,15 @@ public class Tuba.Views.Profile : Views.Accounts {
public class FilterGroup : Widgetizable, GLib.Object {
public bool visible { get; set; default=true; }
- public override Gtk.Widget to_widget () {
+ private Gtk.Widget create_widget () {
var widget = new Widgets.ProfileFilterGroup ();
this.bind_property ("visible", widget, "visible", GLib.BindingFlags.SYNC_CREATE);
return widget;
}
+
+ public override Gtk.Widget to_widget () {
+ return create_widget ();
+ }
}
public ProfileAccount profile { get; construct set; }
@@ -134,27 +138,10 @@ public class Tuba.Views.Profile : Views.Accounts {
widget_cover.rs_invalidated.connect (on_rs_updated);
widget_cover.timeline_change.connect (change_timeline_source);
widget_cover.aria_updated.connect (on_cover_aria_update);
- #if !USE_LISTVIEW
- widget_cover.remove_css_class ("card");
- widget_cover.remove_css_class ("card-spacing");
- #endif
this.cover_profile_update.connect (widget_cover.update_cover_from_profile);
- #if USE_LISTVIEW
- widget_cover.update_aria ();
- return widget_cover;
- #else
- var row = new Gtk.ListBoxRow () {
- focusable = true,
- activatable = false,
- child = widget_cover,
- css_classes = { "card-spacing", "card" },
- overflow = Gtk.Overflow.HIDDEN
- };
- widget_cover.update_aria ();
-
- return row;
- #endif
+ widget_cover.update_aria ();
+ return widget_cover;
}
var widget_status = widget as Widgets.Status;
@@ -165,40 +152,35 @@ public class Tuba.Views.Profile : Views.Accounts {
var widget_filter_group = widget as Widgets.ProfileFilterGroup;
if (widget_filter_group != null) {
- #if !USE_LISTVIEW
- widget_filter_group.remove_css_class ("card");
- #endif
widget_filter_group.filter_change.connect (change_filter);
}
return widget;
}
- #if USE_LISTVIEW
- protected override void bind_listitem_cb (GLib.Object item) {
- ((Gtk.ListItem) item).child = on_create_model_widget (((Gtk.ListItem) item).item);
-
- var gtklistitemwidget = ((Gtk.ListItem) item).child.get_parent ();
- if (gtklistitemwidget != null) {
- gtklistitemwidget.add_css_class ("card-spacing");
-
- if ((((Gtk.ListItem) item).child as Widgets.ProfileFilterGroup) == null) gtklistitemwidget.add_css_class ("keep-margin");
- if ((((Gtk.ListItem) item).child as Widgets.ProfileFilterGroup) == null && (((Gtk.ListItem) item).child as Widgets.Cover) == null) {
- gtklistitemwidget.add_css_class ("card");
- } else {
- ((Gtk.ListItem) item).activatable = false;
- }
+ protected override void bind_listitem_cb (GLib.Object item) {
+ ((Gtk.ListItem) item).child = on_create_model_widget (((Gtk.ListItem) item).item);
- gtklistitemwidget.focusable = true;
+ var gtklistitemwidget = ((Gtk.ListItem) item).child.get_parent ();
+ if (gtklistitemwidget != null) {
+ gtklistitemwidget.add_css_class ("card-spacing");
- // Thread lines overflow slightly
- gtklistitemwidget.overflow = Gtk.Overflow.HIDDEN;
+ if ((((Gtk.ListItem) item).child as Widgets.ProfileFilterGroup) == null) gtklistitemwidget.add_css_class ("keep-margin");
+ if ((((Gtk.ListItem) item).child as Widgets.ProfileFilterGroup) == null && (((Gtk.ListItem) item).child as Widgets.Cover) == null) {
+ gtklistitemwidget.add_css_class ("card");
+ } else {
+ ((Gtk.ListItem) item).activatable = false;
}
- if (((((Gtk.ListItem) item).item) as ProfileAccount) != null)
- ((Gtk.ListItem) item).activatable = false;
+ gtklistitemwidget.focusable = true;
+
+ // Thread lines overflow slightly
+ gtklistitemwidget.overflow = Gtk.Overflow.HIDDEN;
}
- #endif
+
+ if (((((Gtk.ListItem) item).item) as ProfileAccount) != null)
+ ((Gtk.ListItem) item).activatable = false;
+ }
public override void on_refresh () {
base.on_refresh ();
diff --git a/src/Views/StatusStats.vala b/src/Views/StatusStats.vala
index 13f93f7f..44a67861 100644
--- a/src/Views/StatusStats.vala
+++ b/src/Views/StatusStats.vala
@@ -1,7 +1,7 @@
public class Tuba.Views.StatusStats : Views.TabbedBase {
- Views.ContentBase favorited;
- Views.ContentBase boosted;
- Views.ContentBase reacted;
+ Views.ContentBaseListView favorited;
+ Views.ContentBaseListView boosted;
+ Views.ContentBaseListView reacted;
construct {
label = _("Post Stats");
diff --git a/src/Views/TabbedBase.vala b/src/Views/TabbedBase.vala
index 93ad097d..49eac94a 100644
--- a/src/Views/TabbedBase.vala
+++ b/src/Views/TabbedBase.vala
@@ -47,14 +47,12 @@ public class Tuba.Views.TabbedBase : Views.Base {
views = {};
}
- #if !USE_LISTVIEW
- public override void unbind_listboxes () {
- foreach (var tab in views) {
- tab.unbind_listboxes ();
- }
- base.unbind_listboxes ();
+ public override void unbind_listboxes () {
+ foreach (var tab in views) {
+ tab.unbind_listboxes ();
}
- #endif
+ base.unbind_listboxes ();
+ }
protected virtual bool title_stack_page_visible {
get {
@@ -118,7 +116,7 @@ public class Tuba.Views.TabbedBase : Views.Base {
return tab;
}
- public Views.ContentBase add_timeline_tab (string label, string icon, string url, Type accepts, string? empty_state_title = null, string? empty_state_icon = null) {
+ public Views.ContentBaseListView add_timeline_tab (string label, string icon, string url, Type accepts, string? empty_state_title = null, string? empty_state_icon = null) {
var tab = new Views.Accounts () {
url = url,
label = label,
diff --git a/src/Views/Thread.vala b/src/Views/Thread.vala
index 17c21842..99d007b4 100644
--- a/src/Views/Thread.vala
+++ b/src/Views/Thread.vala
@@ -1,4 +1,4 @@
-public class Tuba.Views.Thread : Views.ContentBase, AccountHolder {
+public class Tuba.Views.Thread : Views.ContentBaseListView, AccountHolder {
public enum ThreadRole {
NONE,
START,
@@ -184,17 +184,15 @@ public class Tuba.Views.Thread : Views.ContentBase, AccountHolder {
connect_threads ();
on_content_changed ();
- #if USE_LISTVIEW
- if (to_add_ancestors.length > 0) {
- uint timeout = 0;
- timeout = Timeout.add (1000, () => {
- content.scroll_to (to_add_ancestors.length, Gtk.ListScrollFlags.FOCUS, null);
+ if (to_add_ancestors.length > 0) {
+ uint timeout = 0;
+ timeout = Timeout.add (1000, () => {
+ content.scroll_to (to_add_ancestors.length, Gtk.ListScrollFlags.FOCUS, null);
- GLib.Source.remove (timeout);
- return true;
- }, Priority.LOW);
- }
- #endif
+ GLib.Source.remove (timeout);
+ return true;
+ }, Priority.LOW);
+ }
})
.exec ();
@@ -231,9 +229,6 @@ public class Tuba.Views.Thread : Views.ContentBase, AccountHolder {
widget_status.kind = null;
if (((API.Status) obj).id == root_status.id) {
- #if !USE_LISTVIEW
- widget_status.activatable = false;
- #endif
widget_status.expand_root ();
root_status_widget = widget_status;
}
@@ -241,12 +236,10 @@ public class Tuba.Views.Thread : Views.ContentBase, AccountHolder {
return widget_status;
}
- #if USE_LISTVIEW
protected override void bind_listitem_cb (GLib.Object item) {
- base.bind_listitem_cb (item);
+ base.bind_listitem_cb (item);
- if (((API.Status) ((Gtk.ListItem) item).item).id == root_status.id)
- ((Gtk.ListItem) item).activatable = false;
- }
- #endif
+ if (((API.Status) ((Gtk.ListItem) item).item).id == root_status.id)
+ ((Gtk.ListItem) item).activatable = false;
+ }
}
diff --git a/src/Views/Timeline.vala b/src/Views/Timeline.vala
index 9645931f..f960f9e3 100644
--- a/src/Views/Timeline.vala
+++ b/src/Views/Timeline.vala
@@ -1,11 +1,8 @@
-public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBase {
+public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBaseListView {
public string url { get; construct set; }
public bool is_public { get; construct set; default = false; }
public Type accepts { get; set; default = typeof (API.Status); }
- #if !USE_LISTVIEW
- public bool use_queue { get; set; default = true; }
- #endif
protected InstanceAccount? account { get; set; default = null; }
@@ -14,9 +11,6 @@ public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBase
public string? page_prev { get; set; }
protected int entity_queue_size { get; set; default=0; }
- #if !USE_LISTVIEW
- Entity[] entity_queue = {};
- #endif
private Adw.Spinner pull_to_refresh_spinner;
private bool _is_pulling = false;
@@ -102,10 +96,6 @@ public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBase
settings.notify["show-preview-cards"].connect (on_refresh);
settings.notify["enlarge-custom-emojis"].connect (on_refresh);
- #if !USE_LISTVIEW
- content.bind_model (model, on_create_model_widget);
- #endif
-
var drag = new Gtk.GestureDrag ();
drag.drag_update.connect (on_drag_update);
drag.drag_end.connect (on_drag_end);
@@ -117,21 +107,9 @@ public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBase
destruct_account_holder ();
destruct_streamable ();
- #if !USE_LISTVIEW
- content.bind_model (null, null);
- entity_queue = {};
- #endif
entity_queue_size = 0;
}
- #if !USE_LISTVIEW
- public override void unbind_listboxes () {
- destruct_account_holder ();
- destruct_streamable ();
- base.unbind_listboxes ();
- }
- #endif
-
public override void dispose () {
destruct_streamable ();
base.dispose ();
@@ -236,11 +214,7 @@ public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBase
}
public virtual void on_refresh () {
- #if !USE_LISTVIEW
- entity_queue = {};
- #endif
entity_queue_size = 0;
-
scrolled.vadjustment.value = 0;
status_button.sensitive = false;
clear ();
@@ -299,49 +273,17 @@ public class Tuba.Views.Timeline : AccountHolder, Streamable, Views.ContentBase
if (!has_finished_request) return;
try {
- #if USE_LISTVIEW
- model.insert (0, Entity.from_json (accepts, ev.get_node ()));
- if (scrolled.vadjustment.value > 100) {
- entity_queue_size += 1;
- return;
- }
- #else
- var entity = Entity.from_json (accepts, ev.get_node ());
- if (should_hide (entity)) return;
-
- if (use_queue && scrolled.vadjustment.value > 100) {
- entity_queue += entity;
- entity_queue_size += 1;
- return;
- }
-
- // This can occur on race conditions or multiple calls.
- // The post might already be in the timeline due to a refresh etc.
- // So just if the id exists already in the first page and remove it.
- if (accepts == typeof (API.Status)) {
- string e_id = ((API.Status) entity).id;
- for (uint i = 0; i < uint.min (model.n_items, settings.timeline_page_size); i++) {
- var status_obj = model.get_item (i) as API.Status;
- if (status_obj != null && status_obj.id == e_id) {
- model.remove (i);
- }
- }
- }
-
- model.insert (0, entity);
- #endif
+ model.insert (0, Entity.from_json (accepts, ev.get_node ()));
+ if (scrolled.vadjustment.value > 100) {
+ entity_queue_size += 1;
+ return;
+ }
} catch (Error e) {
warning (@"Error getting Entity from json: $(e.message)");
}
}
private void finish_queue () {
- #if !USE_LISTVIEW
- if (entity_queue.length == 0) return;
- model.splice (0, 0, (Object[])entity_queue);
-
- entity_queue = {};
- #endif
entity_queue_size = 0;
}
diff --git a/src/Views/meson.build b/src/Views/meson.build
index 4872191f..ce1dd899 100644
--- a/src/Views/meson.build
+++ b/src/Views/meson.build
@@ -5,6 +5,7 @@ sources += files(
'Bookmarks.vala',
'Bubble.vala',
'ContentBase.vala',
+ 'ContentBaseListView.vala',
'Conversations.vala',
'DraftStatuses.vala',
'Explore.vala',
diff --git a/src/Widgets/ScheduledStatus.vala b/src/Widgets/ScheduledStatus.vala
index e213a8c5..c3b2721c 100644
--- a/src/Widgets/ScheduledStatus.vala
+++ b/src/Widgets/ScheduledStatus.vala
@@ -116,11 +116,8 @@ public class Tuba.Widgets.ScheduledStatus : Gtk.ListBoxRow {
if (scheduled_status.props.language != null) status.language = scheduled_status.props.language;
- var widg = new Widgets.Status (status);
+ var widg = new Widgets.Status.from_status (status);
widg.can_be_opened = false;
- #if !USE_LISTVIEW
- widg.activatable = false;
- #endif
widg.actions.visible = false;
widg.menu_button.visible = false;
widg.date_label.visible = false;
diff --git a/src/Widgets/Status.vala b/src/Widgets/Status.vala
index 68ff863c..9ec70871 100644
--- a/src/Widgets/Status.vala
+++ b/src/Widgets/Status.vala
@@ -1,9 +1,5 @@
[GtkTemplate (ui = "/dev/geopjr/Tuba/ui/widgets/status.ui")]
-#if USE_LISTVIEW
- public class Tuba.Widgets.Status : Adw.Bin {
-#else
- public class Tuba.Widgets.Status : Gtk.ListBoxRow {
-#endif
+public class Tuba.Widgets.Status : Adw.Bin {
API.Status? _bound_status = null;
public API.Status? status {
@@ -106,8 +102,6 @@
[GtkChild] protected unowned Gtk.Image header_icon;
[GtkChild] protected unowned Widgets.RichLabel header_label;
[GtkChild] protected unowned Gtk.Button header_button;
- [GtkChild] public unowned Gtk.Image thread_line_top;
- [GtkChild] public unowned Gtk.Image thread_line_bottom;
[GtkChild] public unowned Widgets.Avatar avatar;
[GtkChild] public unowned Gtk.Overlay avatar_overlay;
@@ -218,12 +212,20 @@
return res;
}
- public Status (API.Status status) {
+ public Status () {
+ Object ();
+ }
+
+ public Status.from_status (API.Status status) {
Object (
kind_instigator: status.account,
status: status
);
+ init (status);
+ }
+
+ public void init (API.Status status) {
if (kind == null) {
if (status.reblog != null) {
kind = InstanceAccount.KIND_REMOTE_REBLOG;
@@ -243,6 +245,7 @@
}
}
}
+
~Status () {
debug ("Destroying Status widget");
if (context_menu != null) {
@@ -1286,27 +1289,66 @@
}
}
+ const float THREAD_WIDTH = 4f;
+
+ public override void snapshot (Gtk.Snapshot snapshot) {
+ if (!expanded && enable_thread_lines && status.formal.tuba_thread_role != NONE && filter_stack.visible_child_name == "status") {
+ float y;
+ float height;
+
+ // Get the avatar's position
+ Graphene.Point avatar_point;
+ avatar.compute_point (
+ this,
+ Graphene.Point () { x = 0.0f, y=0.0f },
+ out avatar_point
+ );
+
+ // NOTE: we need the thread line to be > status height as
+ // it looks better if it reaches the status' bounds
+ // so the sizes below are either always bigger or
+ // start at a negative point
+ switch (status.formal.tuba_thread_role) {
+ // Thread starter line needs to start from the
+ // center of the avatar and end at the end of
+ // the status
+ case START:
+ y = avatar_point.y + avatar.get_height () / 2f;
+ height = (float) this.get_height ();
+ break;
+ // Thread in-between line needs to start from the
+ // top and end at the end of the status
+ case MIDDLE:
+ y = -4f;
+ height = this.get_height () * 1.2f;
+ break;
+ // Thread end line needs to start from the
+ // status top and end at the center of the
+ // avatar
+ case END:
+ y = -4f;
+ height = (avatar_point.y + avatar.get_height () / 2f) + 4f;
+ break;
+ default:
+ assert_not_reached ();
+ }
+
+ var line_rect = Graphene.Rect () {
+ // we need the center of the avatar for the x point minus half the thread line width
+ origin = Graphene.Point () { x = avatar_point.x + avatar.get_width () / 2f - THREAD_WIDTH / 2f, y = y },
+ size = Graphene.Size () { width = THREAD_WIDTH, height = height }
+ };
+
+ snapshot.push_opacity (0.1);
+ snapshot.append_color (this.get_color (), line_rect);
+ snapshot.pop ();
+ }
+
+ base.snapshot (snapshot);
+ }
+
// Threads
public void install_thread_line () {
- if (expanded || !enable_thread_lines) return;
-
- switch (status.formal.tuba_thread_role) {
- case NONE:
- thread_line_top.visible = false;
- thread_line_bottom.visible = false;
- break;
- case START:
- thread_line_top.visible = false;
- thread_line_bottom.visible = true;
- break;
- case MIDDLE:
- thread_line_top.visible = true;
- thread_line_bottom.visible = true;
- break;
- case END:
- thread_line_top.visible = true;
- thread_line_bottom.visible = false;
- break;
- }
+ this.queue_draw ();
}
}
diff --git a/src/Widgets/WidgetizableForListView.vala b/src/Widgets/WidgetizableForListView.vala
new file mode 100644
index 00000000..60891495
--- /dev/null
+++ b/src/Widgets/WidgetizableForListView.vala
@@ -0,0 +1,17 @@
+public interface Tuba.WidgetizableForListView : GLib.Object {
+ public virtual Gtk.Widget to_widget () throws Oopsie {
+ throw new Tuba.Oopsie.INTERNAL ("Widgetizable didn't provide a Widget!");
+ }
+
+ public virtual void open () {
+ warning ("Widgetizable didn't provide a way to open it!");
+ }
+
+ public virtual void resolve_open (InstanceAccount account) {
+ this.open ();
+ }
+
+ public virtual void fill_widget_with_content (Gtk.Widget widget) throws Oopsie {
+ throw new Tuba.Oopsie.INTERNAL ("Widgetizable didn't fill widget with content!");
+ }
+}
diff --git a/src/Widgets/meson.build b/src/Widgets/meson.build
index 05d28e5a..038f55ab 100644
--- a/src/Widgets/meson.build
+++ b/src/Widgets/meson.build
@@ -33,6 +33,7 @@ sources += files(
'VoteBox.vala',
'VoteCheckButton.vala',
'Widgetizable.vala',
+ 'WidgetizableForListView.vala',
)
subdir('Admin')