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

Voting support only for response #265

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
202 changes: 119 additions & 83 deletions data/ui/widgets/status.ui

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ executable(
'src/API/AccountField.vala',
'src/API/Relationship.vala',
'src/API/Mention.vala',
'src/API/Poll.vala',
'src/API/PollOption.vala',
'src/API/Tag.vala',
'src/API/Status.vala',
'src/API/Visibility.vala',
Expand Down
4 changes: 4 additions & 0 deletions po/com.github.bleakgrey.tootle.pot
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ msgstr ""
msgid "Delete"
msgstr ""

#: src/Widgets/Status.vala:187
msgid "Vote"
msgstr ""

#: data/ui/widgets/list_item.ui:26 src/Dialogs/ListEditor.vala:87
msgid "Untitled"
msgstr ""
Expand Down
10 changes: 10 additions & 0 deletions src/API/Entity.vala
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,19 @@ public class Tootle.Entity : GLib.Object, Widgetizable, Json.Serializable {
}
return des_list (out val, node, contains);
}
else if (type.is_a (typeof (API.Poll)))
return des_poll_type (out val, node);
else if (type.is_a (typeof (API.NotificationType)))
return des_notification_type (out val, node);

return success;
}
static bool des_poll_type (out Value val, Json.Node node) {
var str = node.get_string ();
val = API.Poll.from_json (node);
return true;
}


static bool des_list (out Value val, Json.Node node, Type type) {
if (!node.is_null ()) {
Expand Down
75 changes: 75 additions & 0 deletions src/API/Poll.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Gee;
public class Tootle.API.Poll {
public string id { get; set; }
public string expires_at{ get; set; }
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; }
public int64[] own_votes { get; set; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me wish there was a way to (de)serialize Vala arrays automatically, just like Entity.vala does with Gee.ArrayList<Object>'

Maybe we could do something here so that we can omit the boilerplate code for parsing.

public PollOption[]? options{ get; set; default = null; }

public Poll (string _id) {
id = _id;
}
public static Poll from_json (Json.Node node){
Json.Object obj=node.get_object();
var id = obj.get_string_member("id");
var poll = new Poll (id);

poll.expires_at = obj.get_string_member ("expires_at");
poll.expired = obj.get_boolean_member ("expired");
poll.multiple = obj.get_boolean_member ("multiple");
poll.votes_count = obj.get_int_member ("votes_count");
poll.voters_count = obj.get_int_member ("voters_count");
poll.voted = obj.get_boolean_member ("voted");

var votes = obj.get_array_member ("own_votes");
int64[] array_votes={};
votes.foreach_element((array, i, node) => {
array_votes+=node.get_int();
});
poll.own_votes=array_votes;

PollOption[]? _options = {};
obj.get_array_member ("options").foreach_element ((array, i, node) => {
var object = node.get_object ();
if (object != null)
_options += API.PollOption.parse (object);
});
if (_options.length > 0)
poll.options = _options;
return poll;
}
/**
*/
public static Request vote (InstanceAccount acc,PollOption[] options,ArrayList<string> selection, string id) {
message (@"Voting poll $(id)...");
//Creating json to send
var builder = new Json.Builder ();
builder.begin_object ();
builder.set_member_name ("choices");
builder.begin_array ();
var row_number=0;
foreach (API.PollOption p in options){
foreach (string select in selection){
if (select == p.title){
builder.add_string_value (row_number.to_string());
}
}
row_number++;
}
builder.end_array ();
builder.end_object ();
var generator = new Json.Generator ();
generator.set_root (builder.get_root ());
var json = generator.to_data (null);
//Send POST MESSAGE
Request voting=new Request.POST (@"/api/v1/polls/$(id)/votes")
.with_account (acc);
voting.set_request("application/json",Soup.MemoryUse.COPY,json.data);
return voting;

}
}
11 changes: 11 additions & 0 deletions src/API/PollOption.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public class Tootle.API.PollOption {
public string title { get; set; }
public int64 votes_count{ get; set; }

public static PollOption parse (Json.Object obj) {
var pollOption = new PollOption ();
pollOption.title = obj.get_string_member ("title");
pollOption.votes_count = obj.get_int_member ("votes_count");
return pollOption;
}
}
1 change: 1 addition & 0 deletions src/API/Status.vala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class Tootle.API.Status : Entity, Widgetizable {
public API.Status? reblog { get; set; default = null; }
public ArrayList<API.Mention>? mentions { get; set; default = null; }
public ArrayList<API.Attachment>? media_attachments { get; set; default = null; }
public API.Poll? poll { get; set; default = null; }

public string? _url { get; set; }
public string url {
Expand Down
119 changes: 119 additions & 0 deletions src/Widgets/Status.vala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Gtk;
using Gdk;
using Gee;
using GLib;

[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/status.ui")]
public class Tootle.Widgets.Status : ListBoxRow {
Expand Down Expand Up @@ -47,6 +49,7 @@ public class Tootle.Widgets.Status : ListBoxRow {
[GtkChild] protected Widgets.RichLabel date_label;
[GtkChild] protected Image pin_indicator;
[GtkChild] protected Image indicator;
[GtkChild] protected Box poll;

[GtkChild] protected Box content_column;
[GtkChild] protected Stack spoiler_stack;
Expand All @@ -65,6 +68,8 @@ public class Tootle.Widgets.Status : ListBoxRow {
[GtkChild] protected ToggleButton bookmark_button;
[GtkChild] protected Button menu_button;

protected Button vote_button;

protected string spoiler_text {
owned get {
var text = status.formal.spoiler_text;
Expand Down Expand Up @@ -99,6 +104,116 @@ public class Tootle.Widgets.Status : ListBoxRow {
return status.formal.account.avatar;
}
}
public void get_poll(){
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Widgets/Status.vala is already bloated as it is. The voting box should be a separate Widgets/VoteBox.vala file that handles the voting logic as well as populating the voting options.

It should also use a Glade ui template, just like any other view component in Tootle.

if (status.poll!=null){
var row_number=0;
Gtk.RadioButton[] radios={};
Gtk.CheckButton[] checks={};
ArrayList<string> selectedIndex=new ArrayList<string>();
if (status.poll.own_votes.length==0 && !status.poll.multiple){
selectedIndex.add(status.poll.options[0].title);
}
foreach (API.PollOption p in status.poll.options){
//if it is own poll
if(status.account.id==accounts.active.username){
// If multiple, Checkbox else radioButton
var option = new Widgets.RichLabel (p.title);
var counts = new Widgets.RichLabel ("Votes: "+p.votes_count.to_string()+" %");
}
else{
// If multiple, Checkbox else radioButton
if (status.poll.multiple){
var button_vote = new Gtk.CheckButton ();
button_vote.set_label(p.title);
button_vote.toggled.connect((radio)=>{
if (selectedIndex.contains(radio.get_label())){
selectedIndex.remove(radio.get_label());
}
else{
selectedIndex.add(radio.get_label());
}
});
foreach (int64 own_vote in status.poll.own_votes){
if (own_vote==row_number){
button_vote.set_active(true);
selectedIndex.add(p.title);
}
}
if(status.poll.expired || status.poll.voted){
button_vote.set_sensitive(false);
}
poll.add(button_vote);
checks+=button_vote;
}else{
Gtk.RadioButton button_vote = null;
if (radios.length==0){
button_vote=new Gtk.RadioButton (null);
}
else{
button_vote=new Gtk.RadioButton (radios[0].get_group());
}
button_vote.set_label(p.title);
button_vote.toggled.connect((radiobutton)=>{
if (selectedIndex.contains(radiobutton.get_label()))
{
selectedIndex.remove(radiobutton.get_label());
}
else{
selectedIndex.add(radiobutton.get_label());
}
});

foreach (int64 own_vote in status.poll.own_votes){
if (own_vote==row_number){
button_vote.set_active(true);
selectedIndex=new ArrayList<string>();
selectedIndex.add(p.title);
}
}
if(status.poll.expired || status.poll.voted){
button_vote.set_sensitive(false);
}
poll.add(button_vote);
radios+=button_vote;
}
}
row_number++;
}
if(row_number>0 && !status.poll.expired && !status.poll.voted &&
status.account.id!=accounts.active.id &&
status.poll.own_votes.length==0){
Gtk.Box buttonsPoll=new Box (Orientation.HORIZONTAL, 6);
vote_button = new Gtk.Button();
vote_button.set_label (_("Vote"));
vote_button.clicked.connect ((button) =>{
Request voting=API.Poll.vote(accounts.active,status.poll.options,selectedIndex,status.poll.id);
voting.then ((sess, mess) => {
var node = network.parse_node (mess);
status.poll=API.Poll.from_json(node);
message ("OK: Voting correctly");
GLib.List<weak Gtk.Widget> children=this.poll.get_children();
foreach (Widget child in children){
this.poll.remove(child);
}
get_poll();
})
.on_error ((code, reason) => {
warning ("Voting invalid!");
app.error (
_("Network Error"),
_("The instance has invalidated this session. Please sign in again.\n\n%s").printf (reason)
);
})
.exec ();

});
buttonsPoll.add(vote_button);
buttonsPoll.show_all();
poll.add(buttonsPoll);
}
}
poll.show_all();
}

public signal void open ();

Expand Down Expand Up @@ -147,6 +262,10 @@ public class Tootle.Widgets.Status : ListBoxRow {
status.formal.bind_property ("pinned", pin_indicator, "visible", BindingFlags.SYNC_CREATE);
status.formal.bind_property ("account", avatar, "account", BindingFlags.SYNC_CREATE);

// is it a poll?
get_poll();


status.formal.bind_property ("has-spoiler", this, "reveal-spoiler", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
target.set_boolean (!src.get_boolean ());
return true;
Expand Down