feat: hashtags

- search results are now ActionRows and display information about usage
- you can now (un)follow hashtags
This commit is contained in:
Evangelos Paterakis 2023-02-16 00:44:34 +02:00
parent 17eb47ff4e
commit 9e3851858f
No known key found for this signature in database
GPG Key ID: FE5185F095BFC8C9
8 changed files with 110 additions and 11 deletions

View File

@ -77,6 +77,7 @@ sources = files(
'src/API/SearchResults.vala',
'src/API/Status.vala',
'src/API/Tag.vala',
'src/API/TagHistory.vala',
'src/API/Poll.vala',
'src/API/PollOption.vala',
'src/Application.vala',

View File

@ -100,6 +100,9 @@ public class Tooth.Entity : GLib.Object, Widgetizable, Json.Serializable {
case "hashtags":
contains = typeof (API.Tag);
break;
case "history":
contains = typeof (API.TagHistory);
break;
default:
contains = typeof (Entity);
break;

View File

@ -7,7 +7,6 @@ public class Tooth.API.Poll : GLib.Object, Json.Serializable{
public bool expired { get; set; }
public bool multiple { get; set; }
public int64 votes_count { get; set; }
public int64 voters_count { get; set; }
public bool voted { get; set; default = true;}
public ArrayList<int> own_votes { get; set; }
public ArrayList<PollOption>? options{ get; set; default = null; }

View File

@ -4,17 +4,41 @@ public class Tooth.API.Tag : Entity, Widgetizable {
public string name { get; set; }
public string url { get; set; }
public Gee.ArrayList<API.TagHistory>? history { get; set; default = null; }
public bool following { get; set; default = false; }
public static Tag from (Json.Node node) throws Error {
return Entity.from_json (typeof (API.Tag), node) as API.Tag;
}
public override void open () {
}
public override Widget to_widget () {
var encoded = Soup.URI.encode (name, null);
var w = new Widgets.RichLabel (@"<a href=\"$(accounts.active.instance)/tags/$encoded\">#$name</a>");
w.halign = Align.START;
w.show ();
var w = new Adw.ActionRow () {
title = @"#$name",
activatable = true
};
if (history != null && history.size > 0) {
var last_history_entry = history.get(0);
var total_uses = int.parse (last_history_entry.uses);
var total_accounts = int.parse (last_history_entry.accounts);
var suffix = _("yesterday");
if (history.size > 1) {
last_history_entry = history.get(1);
total_uses += int.parse (last_history_entry.uses);
total_accounts += int.parse (last_history_entry.accounts);
suffix = _("in the past 2 days");
}
w.subtitle = _("Used %d times by %d people %s").printf (total_uses, total_accounts, suffix);
}
w.activated.connect(on_activated);
return w;
}
protected void on_activated () {
app.main_window.open_view (new Views.Hashtag (name, following));
}
}

5
src/API/TagHistory.vala Normal file
View File

@ -0,0 +1,5 @@
public class Tooth.API.TagHistory : Entity {
public string day { get; set; default = ""; }
public string accounts { get; set; default = "0"; }
public string uses { get; set; default = "0"; }
}

View File

@ -1,14 +1,81 @@
public class Tooth.Views.Hashtag : Views.Timeline {
public Hashtag (string tag) {
bool t_following = false;
string t_tag = "";
public Hashtag (string tag, bool? following = null) {
Object (
url: @"/api/v1/timelines/tag/$tag",
label: "#"+tag
url: @"/api/v1/timelines/tag/$tag",
label: "#"+tag
);
t_tag = tag;
if (following != null) {
t_following = following;
create_follow_button();
} else {
init_tag();
}
}
Gtk.Button follow_tag_btn = new Gtk.Button.with_label(_("Follow"));
private void create_follow_button() {
if (t_following) {
follow_tag_btn.label = _("Unfollow");
follow_tag_btn.add_css_class("destructive-action");
} else {
follow_tag_btn.add_css_class("suggested-action");
}
follow_tag_btn.clicked.connect(follow);
header.pack_end(follow_tag_btn);
}
private void update_button() {
if (t_following) {
follow_tag_btn.label = _("Follow");
follow_tag_btn.remove_css_class("destructive-action");
follow_tag_btn.add_css_class("suggested-action");
} else {
follow_tag_btn.label = _("Unfollow");
follow_tag_btn.remove_css_class("suggested-action");
follow_tag_btn.add_css_class("destructive-action");
}
t_following = !t_following;
}
private void follow() {
var action = "follow";
if (t_following) {
action = "unfollow";
}
update_button();
new Request.POST (@"/api/v1/tags/$t_tag/$action")
.with_account (accounts.active)
.then ((sess, msg) => {
var root = network.parse (msg);
if (!root.has_member("following")) {
update_button();
};
})
.exec ();
}
private void init_tag() {
new Request.GET (@"/api/v1/tags/$t_tag")
.with_account (accounts.active)
.then ((sess, msg) => {
var node = network.parse_node (msg);
var tag_info = API.Tag.from (node);
t_following = tag_info.following;
create_follow_button();
})
.exec ();
}
public override string? get_stream_url () {
var tag = url.substring (4);
var split_url = url.split ("/");
var tag = split_url[split_url.length - 1];
return account != null ? @"$(account.instance)/api/v1/streaming/?stream=hashtag&tag=$tag&access_token=$(account.access_token)" : null;
}

View File

@ -88,7 +88,7 @@ public class Tooth.Widgets.RichLabel : Adw.Bin {
if ("/tags/" in url) {
var encoded = url.split ("/tags/")[1];
var tag = Soup.URI.decode (encoded);
app.main_window.open_view (new Views.Hashtag (tag));
app.main_window.open_view (new Views.Hashtag (tag, null));
return true;
}

View File

@ -49,7 +49,7 @@ public class Tooth.Widgets.RichLabelContainer : Adw.Bin {
if ("/tags/" in on_click_url) {
var encoded = on_click_url.split ("/tags/")[1];
var tag = Soup.URI.decode (encoded);
app.main_window.open_view (new Views.Hashtag (tag));
app.main_window.open_view (new Views.Hashtag (tag, null));
return;
}