diff --git a/data/gresource.xml b/data/gresource.xml
index 650e28c..1aacf16 100644
--- a/data/gresource.xml
+++ b/data/gresource.xml
@@ -44,6 +44,8 @@
icons/scalable/actions/tooth-contact-new-symbolic.svg
icons/scalable/actions/tooth-dock-left-symbolic.svg
icons/scalable/actions/tooth-about-symbolic.svg
+ icons/scalable/actions/tooth-error-symbolic.svg
+ icons/scalable/actions/tooth-minus-large-symbolic.svg
gtk/dropdown/icon.ui
gtk/dropdown/full.ui
@@ -59,14 +61,12 @@
ui/widgets/profile_field_row.ui
ui/widgets/timeline_menu.ui
ui/widgets/list_item.ui
- ui/widgets/list_editor_item.ui
ui/widgets/compose_attachment.ui
ui/widgets/votebox.ui
ui/dialogs/new_account.ui
ui/dialogs/compose.ui
ui/dialogs/main.ui
ui/dialogs/preferences.ui
- ui/dialogs/list_editor.ui
ui/menus.ui
diff --git a/data/icons/scalable/actions/tooth-error-symbolic.svg b/data/icons/scalable/actions/tooth-error-symbolic.svg
new file mode 100644
index 0000000..10ac209
--- /dev/null
+++ b/data/icons/scalable/actions/tooth-error-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/icons/scalable/actions/tooth-minus-large-symbolic.svg b/data/icons/scalable/actions/tooth-minus-large-symbolic.svg
new file mode 100644
index 0000000..09943ae
--- /dev/null
+++ b/data/icons/scalable/actions/tooth-minus-large-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/style.css b/data/style.css
index a19a50e..d4866de 100644
--- a/data/style.css
+++ b/data/style.css
@@ -144,3 +144,7 @@
.ttl-profile-stat-button:last-child {
border-bottom-right-radius: 12px;
}
+
+.ttl-box-no-shadow > revealer > box {
+ box-shadow: none;
+}
diff --git a/data/ui/dialogs/list_editor.ui b/data/ui/dialogs/list_editor.ui
deleted file mode 100644
index 81093c6..0000000
--- a/data/ui/dialogs/list_editor.ui
+++ /dev/null
@@ -1,241 +0,0 @@
-
-
-
-
-
- False
- True
- 300
- 400
- dialog
-
-
-
-
-
diff --git a/data/ui/menus.ui b/data/ui/menus.ui
index cbb3a7c..3c8c767 100644
--- a/data/ui/menus.ui
+++ b/data/ui/menus.ui
@@ -106,6 +106,11 @@
-->
+ -
+ Lists
+ view.ar_list
+ action-disabled
+
-
Refresh
app.refresh
diff --git a/data/ui/widgets/list_editor_item.ui b/data/ui/widgets/list_editor_item.ui
deleted file mode 100644
index 054959b..0000000
--- a/data/ui/widgets/list_editor_item.ui
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
- 0
-
-
- 0
- 8
- 8
- 8
- 8
- 8
- 8
-
-
- True
- False
- True
- Display Name
- end
- 0
-
-
-
-
- 0
- 0
-
-
-
-
-
- True
- False
- True
- @handle
- 0
-
- 0
- 1
-
-
-
-
-
- 0
- 32
- 32
- 0
- 1
-
-
-
- 0
- tooth-check-round-outline-symbolic
-
-
-
-
- 1
- 0
- 2
-
-
-
-
-
-
-
diff --git a/install.sh b/install.sh
index 0777dc1..9636cc8 100755
--- a/install.sh
+++ b/install.sh
@@ -1,7 +1,7 @@
#! /bin/sh
set -e
-meson build --prefix=/usr
+meson setup build --prefix=/usr
cd build
ninja
sudo ninja install
diff --git a/meson.build b/meson.build
index 6b8084f..a99df01 100644
--- a/meson.build
+++ b/meson.build
@@ -65,7 +65,6 @@ sources = files(
'src/Dialogs/Composer/EditorPage.vala',
'src/Dialogs/Composer/Page.vala',
'src/Dialogs/Composer/PollPage.vala',
- 'src/Dialogs/ListEditor.vala',
'src/Dialogs/MainWindow.vala',
'src/Dialogs/NewAccount.vala',
'src/Dialogs/Preferences.vala',
diff --git a/src/API/List.vala b/src/API/List.vala
index 8d25609..e42caaf 100644
--- a/src/API/List.vala
+++ b/src/API/List.vala
@@ -4,6 +4,7 @@ public class Tooth.API.List : Entity, Widgetizable {
public string id { get; set; }
public string title { get; set; }
+ public string? replies_policy { get; set; default = null; }
public static List from (Json.Node node) throws Error {
return Entity.from_json (typeof (API.List), node) as API.List;
diff --git a/src/Dialogs/ListEditor.vala b/src/Dialogs/ListEditor.vala
deleted file mode 100644
index 4505b31..0000000
--- a/src/Dialogs/ListEditor.vala
+++ /dev/null
@@ -1,280 +0,0 @@
-using Gtk;
-
-[GtkTemplate (ui = "/dev/geopjr/tooth/ui/dialogs/list_editor.ui")]
-public class Tooth.Dialogs.ListEditor: Adw.Window {
-
- [GtkTemplate (ui = "/dev/geopjr/tooth/ui/widgets/list_editor_item.ui")]
- class Item : ListBoxRow {
-
- public ListEditor editor { get; construct set; }
- public API.Account acc { get; construct set; }
- public bool committed { get; construct set; }
-
- [GtkChild] unowned Widgets.RichLabel label;
- [GtkChild] unowned Widgets.RichLabel handle;
- [GtkChild] unowned ToggleButton status;
-
- public Item (ListEditor editor, API.Account acc, bool committed) {
- this.editor = editor;
- this.acc = acc;
- this.committed = committed;
- acc.bind_property ("display-name", label, "text", BindingFlags.SYNC_CREATE);
- acc.bind_property ("handle", handle, "text", BindingFlags.SYNC_CREATE);
- status.active = committed;
- status.sensitive = true;
- }
-
- [GtkCallback]
- void on_toggled () {
- if (!status.sensitive)
- return;
-
- if (status.active) {
- debug (@"To add: $(acc.id)");
- editor.to_add.add (acc.id);
- editor.to_remove.remove (acc.id);
- }
- else {
- debug (@"To remove: $(acc.id)");
- editor.to_add.remove (acc.id);
- editor.to_remove.add (acc.id);
- }
- committed = status.active;
- if (!editor.working)
- editor.dirty = true;
- }
-
- }
-
- public API.List list { get; set; }
- public bool working { get; set; default = false; }
- public bool exists { get; set; default = false; }
- public bool dirty { get; set; default = false; }
-
- Soup.Message? search_req = null;
-
- public Gee.ArrayList to_add = new Gee.ArrayList ();
- public Gee.ArrayList to_remove = new Gee.ArrayList ();
-
- [GtkChild] unowned Button save_btn;
- [GtkChild] unowned Stack save_btn_stack;
- [GtkChild] unowned Entry name_entry;
- [GtkChild] unowned SearchEntry search_entry;
- [GtkChild] unowned ListBox listbox;
-
- [GtkChild] unowned InfoBar infobar;
- [GtkChild] unowned Label infobar_label;
-
- public signal void done ();
-
- construct {
- transient_for = app.main_window;
- show ();
- }
-
- public ListEditor.empty () {
- var obj = new API.List () {
- title = _("Untitled")
- };
- Object (list: obj);
- init ();
- }
-
- public ListEditor (API.List list) {
- Object (list: list, working: true, exists: true);
- init ();
-
- new Request.GET (@"/api/v1/lists/$(list.id)/accounts")
- .with_account (accounts.active)
- .with_ctx (this)
- .on_error (on_error)
- .then ((sess, msg) => {
- Network.parse_array (msg, node => {
- var acc = API.Account.from (node);
- add_account (acc, true);
- });
- working = false;
- })
- .exec ();
- }
-
- void init () {
- notify["working"].connect (on_state_changed);
- list.bind_property ("title", name_entry, "text", BindingFlags.SYNC_CREATE);
-
- ulong dirty_sigid = 0;
- dirty_sigid = name_entry.changed.connect (() => {
- dirty = true;
- name_entry.disconnect (dirty_sigid);
- });
-
- on_state_changed (null);
- }
-
- void on_state_changed (ParamSpec? p) {
- save_btn_stack.visible_child_name = working ? "working" : "done";
- save_btn.sensitive = search_entry.sensitive = name_entry.sensitive = !working;
- }
-
- void on_error (int32 code, string msg) {
- warning (msg);
- infobar_label.label = msg;
- infobar.revealed = true;
- }
-
- [GtkCallback]
- void infobar_response (int i) {
- infobar.revealed = false;
- }
-
- void request_search (string q) {
- debug (@"Searching for: \"$q\"...");
- if (search_req != null) {
- network.cancel (search_req);
- search_req = null;
- }
-
- search_req = new Request.GET ("/api/v1/accounts/search")
- .with_account (accounts.active)
- .with_ctx (this)
- .with_param ("resolve", "false")
- .with_param ("limit", "8")
- .with_param ("following", "true")
- .with_param ("q", q)
- .then ((sess, msg) => {
- Network.parse_array (msg, node => {
- var acc = API.Account.from (node);
- add_account (acc, false, 0);
- });
- })
- .on_error (on_error)
- .exec ();
- }
-
- void add_account (API.Account acc, bool added, int order = -1) {
- var exists = false;
- // listbox.@foreach (w => {
- // var i = w as Item;
- // if (i != null) {
- // if (i.acc.id == acc.id)
- // exists = true;
- // }
- // });
-
- if (!exists) {
- var item = new Item (this, acc, added);
- listbox.insert (item, order);
- }
- }
-
- void invalidate () {
- // listbox.@foreach (w => {
- // var i = w as Item;
- // if (i != null) {
- // if (!i.committed)
- // i.destroy ();
- // }
- // });
- }
-
-
- [GtkCallback]
- void validate () {
- var has_title = name_entry.text.replace (" ", "") != "";
- save_btn.sensitive = has_title;
- }
-
- [GtkCallback]
- void on_cancel_clicked () {
- if (dirty) {
- var dlg = app.question (
- _("Discard changes?"),
- _("You need to save the list if you want to keep them."),
- this,
- _("Discard"),
- Adw.ResponseAppearance.DESTRUCTIVE
- );
-
- dlg.response.connect(res => {
- if (res == "yes") {
- destroy ();
- }
- dlg.destroy();
- });
-
- dlg.present ();
- }
- else
- destroy ();
- }
-
- [GtkCallback]
- void on_search_changed () {
- var q = search_entry.text.chug ().chomp ();
-
- if (q.char_count () < 3)
- invalidate ();
- else if (q != "") {
- invalidate ();
- request_search (q);
- }
- }
-
- [GtkCallback]
- void on_save_clicked () {
- working = true;
- transaction.begin ((obj, res) => {
- try {
- transaction.end (res);
-
- done ();
- destroy ();
- }
- catch (Error e) {
- working = false;
- on_error (0, e.message);
- }
- });
- }
-
- async void transaction () throws Error {
- if (!exists) {
- message ("Creating list...");
- var req = new Request.POST ("/api/v1/lists")
- .with_account (accounts.active)
- .with_param ("title", name_entry.text);
- yield req.await ();
-
- message ("Received new List entity");
- var node = network.parse_node (req);
- list = API.List.from (node);
- }
- else {
- message ("Updating list title...");
- yield new Request.PUT (@"/api/v1/lists/$(list.id)")
- .with_account (accounts.active)
- .with_param ("title", name_entry.text)
- .await ();
- }
-
- if (!to_add.is_empty) {
- message ("Adding accounts to list...");
- var id_array = Request.array2string (to_add, "account_ids");
- yield new Request.POST (@"/api/v1/lists/$(list.id)/accounts/?$id_array")
- .with_account (accounts.active)
- .await ();
- }
-
- if (!to_remove.is_empty) {
- message ("Removing accounts from list...");
- var id_array = Request.array2string (to_remove, "account_ids");
- yield new Request.DELETE (@"/api/v1/lists/$(list.id)/accounts/?$id_array")
- .with_account (accounts.active)
- .await ();
- }
-
- message ("OK: List updated");
- list.title = name_entry.text;
- }
-
-}
diff --git a/src/Views/Lists.vala b/src/Views/Lists.vala
index 24bf144..692bb3c 100644
--- a/src/Views/Lists.vala
+++ b/src/Views/Lists.vala
@@ -3,32 +3,58 @@ using Gtk;
// TODO: Lists is borken
public class Tooth.Views.Lists : Views.Timeline {
- [GtkTemplate (ui = "/dev/geopjr/tooth/ui/widgets/list_item.ui")]
- public class Row : ListBoxRow {
+ public class Row : Adw.ActionRow {
+ public API.List? list;
+ Button delete_button;
+ Button edit_button;
- API.List? list;
+ construct {
+ var action_box = new Box(Orientation.HORIZONTAL, 6);
- [GtkChild] unowned Stack stack;
- [GtkChild] unowned Label title;
+ edit_button = new Button() {
+ icon_name = "tooth-edit-symbolic",
+ valign = Align.CENTER,
+ halign = Align.CENTER
+ };
+ edit_button.add_css_class("flat");
+ edit_button.add_css_class("circular");
+
+ delete_button = new Button() {
+ icon_name = "tooth-trash-symbolic",
+ valign = Align.CENTER,
+ halign = Align.CENTER
+ };
+ delete_button.add_css_class("flat");
+ delete_button.add_css_class("circular");
+ delete_button.add_css_class("error");
+ delete_button.clicked.connect(on_remove_clicked);
+
+ // this.apply.connect(on_apply);
+ action_box.append(edit_button);
+ action_box.append(delete_button);
+
+ this.activated.connect(() => open());
+ this.activatable = true;
+
+ this.add_suffix(action_box);
+ }
public Row (API.List? list) {
this.list = list;
- if (list == null)
- stack.visible_child_name = "add";
- else
- list.bind_property ("title", title, "label", BindingFlags.SYNC_CREATE);
+ if (list != null) {
+ this.list.bind_property ("title", this, "title", BindingFlags.SYNC_CREATE);
+ edit_button.clicked.connect(() => {
+ create_edit_preferences_window(this.list).show();
+ });
+ }
}
- [GtkCallback]
- void on_edit_clicked () {
- new Dialogs.ListEditor (this.list);
- }
+ public virtual signal void remove_from_model () {}
- [GtkCallback]
void on_remove_clicked () {
var remove = app.question (
- _("Delete \"%s\"?").printf (list.title),
+ _("Delete \"%s\"?").printf (this.list.title),
_("This action cannot be reverted."),
app.main_window,
_("Delete"),
@@ -38,9 +64,12 @@ public class Tooth.Views.Lists : Views.Timeline {
remove.response.connect(res => {
if (res == "yes") {
new Request.DELETE (@"/api/v1/lists/$(list.id)")
- .with_account (accounts.active)
- .then (() => { this.destroy (); })
- .exec ();
+ .with_account (accounts.active)
+ .then (() => {
+ remove_from_model();
+ this.destroy ();
+ })
+ .exec ();
}
remove.destroy();
});
@@ -48,6 +77,171 @@ public class Tooth.Views.Lists : Views.Timeline {
remove.present ();
}
+ public Adw.PreferencesWindow create_edit_preferences_window(API.List t_list) {
+ var edit_preferences_window = new Adw.PreferencesWindow() {
+ modal = true,
+ title = _("Edit \"%s\"").printf (t_list.title),
+ transient_for = app.main_window
+ };
+ var list_settings_page_general = new Adw.PreferencesPage() {
+ icon_name = "tooth-gear-symbolic",
+ title = _("General")
+ };
+ var info_group = new Adw.PreferencesGroup() {
+ title = _("Info")
+ };
+ var title_row = new Adw.EntryRow() {
+ input_purpose = InputPurpose.FREE_FORM,
+ title = _("List Name"),
+ text = t_list.title
+ };
+ info_group.add(title_row);
+ list_settings_page_general.add(info_group);
+
+ string? replies_policy_active = null;
+ if (t_list.replies_policy != null) {
+ var replies_group = new Adw.PreferencesGroup() {
+ title = _("Replies Policy"),
+ description = _("Show member replies to")
+ };
+ var none_radio = new CheckButton();
+ var none_row = new Adw.ActionRow() {
+ title = _("Nobody"),
+ activatable_widget = none_radio
+ };
+ none_row.add_prefix(none_radio);
+ none_radio.toggled.connect(() => {
+ if (none_radio.active)
+ replies_policy_active = "none";
+ });
+
+ var list_radio = new CheckButton();
+ list_radio.group = none_radio;
+ var list_row = new Adw.ActionRow() {
+ title = _("Other members of the list"),
+ activatable_widget = list_radio
+ };
+ list_row.add_prefix(list_radio);
+ list_radio.toggled.connect(() => {
+ if (list_radio.active)
+ replies_policy_active = "list";
+ });
+
+ var followed_radio = new CheckButton();
+ followed_radio.group = none_radio;
+ var followed_row = new Adw.ActionRow() {
+ title = _("Any followed user"),
+ activatable_widget = followed_radio
+ };
+ followed_row.add_prefix(followed_radio);
+ followed_radio.toggled.connect(() => {
+ if (followed_radio.active)
+ replies_policy_active = "followed";
+ });
+
+ switch (t_list.replies_policy) {
+ case "none":
+ none_radio.active = true;
+ break;
+ case "followed":
+ followed_radio.active = true;
+ break;
+ default:
+ list_radio.active = true;
+ break;
+ }
+
+ replies_group.add(none_row);
+ replies_group.add(list_row);
+ replies_group.add(followed_row);
+
+ list_settings_page_general.add(replies_group);
+ }
+
+ var to_remove = new Gee.ArrayList();
+ new Request.GET (@"/api/v1/lists/$(t_list.id)/accounts")
+ .with_account (accounts.active)
+ .then ((sess, msg) => {
+ if (Network.get_array_size(msg) > 0) {
+ var list_settings_page_members = new Adw.PreferencesPage() {
+ icon_name = "tooth-people-symbolic",
+ title = _("Members")
+ };
+
+ var rm_group = new Adw.PreferencesGroup() {
+ title = _("Remove Members")
+ };
+
+ Network.parse_array (msg, node => {
+ var member = API.Account.from (node);
+ var avi = new Widgets.Avatar() {
+ account = member,
+ size = 32
+ };
+ var m_switch = new Switch() {
+ active = true,
+ state = true,
+ valign = Align.CENTER,
+ halign = Align.CENTER
+ };
+ m_switch.state_set.connect((x) => {
+ if (!x) {
+ to_remove.add(member.id);
+ } else if (to_remove.contains(member.id)) {
+ to_remove.remove(member.id);
+ }
+
+ return x;
+ });
+
+ var member_row = new Adw.ActionRow() {
+ title = member.full_handle
+ };
+ member_row.add_prefix(avi);
+ member_row.add_suffix(m_switch);
+
+ rm_group.add(member_row);
+ });
+
+ list_settings_page_members.add(rm_group);
+ edit_preferences_window.add(list_settings_page_members);
+ }
+ })
+ .exec();
+
+ edit_preferences_window.add(list_settings_page_general);
+
+ edit_preferences_window.close_request.connect(() => {
+ on_apply(t_list, title_row.text, replies_policy_active, to_remove);
+ edit_preferences_window.hide();
+ edit_preferences_window.destroy();
+ return false;
+ });
+
+ return edit_preferences_window;
+ }
+
+ public void on_apply(API.List t_list, string title, string? replies_policy, Gee.ArrayList to_remove) {
+ if (t_list.title != title || t_list.replies_policy != replies_policy) {
+ this.list.title = title;
+ this.list.replies_policy = replies_policy;
+ new Request.PUT (@"/api/v1/lists/$(t_list.id)")
+ .with_account (accounts.active)
+ .with_param ("title", title)
+ .with_param ("replies_policy", replies_policy)
+ .then(() => {})
+ .exec ();
+ }
+
+ if (to_remove.size > 0) {
+ var id_array = Request.array2string (to_remove, "account_ids");
+ new Request.DELETE (@"/api/v1/lists/$(t_list.id)/accounts/?$id_array")
+ .with_account (accounts.active)
+ .then(() => {})
+ .exec ();
+ }
+ }
+
public virtual signal void open () {
if (this.list == null)
return;
@@ -61,6 +255,25 @@ public class Tooth.Views.Lists : Views.Timeline {
get { return false; }
}
+ public override Widget on_create_model_widget(Object obj) {
+ var widget = base.on_create_model_widget(obj);
+ var widget_row = widget as Row;
+
+ if (widget_row != null)
+ widget_row.remove_from_model.connect(() => remove_list(widget_row.list));
+
+ return widget;
+ }
+
+ public void remove_list(API.List? list) {
+ if (list == null) return;
+
+ uint indx;
+ var found = model.find (list, out indx);
+ if (found)
+ model.remove(indx);
+ }
+
public Lists () {
Object (
url: @"/api/v1/lists",
@@ -70,14 +283,58 @@ public class Tooth.Views.Lists : Views.Timeline {
accepts = typeof (API.List);
}
+ public void create_list(string list_name) {
+ new Request.POST ("/api/v1/lists")
+ .with_account (accounts.active)
+ .with_param ("title", list_name)
+ .then ((sess, msg) => {
+ var node = network.parse_node (msg);
+ var list = API.List.from (node);
+ model.insert (0, list);
+ })
+ .exec ();
+ }
+
+ public void on_action_bar_activate(EntryBuffer buffer) {
+ if (buffer.length > 0)
+ create_list(buffer.text);
+ buffer.set_text("".data);
+ }
+
+ construct {
+ var add_action_bar = new ActionBar ();
+ add_action_bar.add_css_class("ttl-box-no-shadow");
+
+ var child_box = new Box(Orientation.HORIZONTAL, 6);
+ var child_entry = new Entry() {
+ input_purpose = InputPurpose.FREE_FORM,
+ placeholder_text = _("New list title")
+ };
+ var add_button = new Button.with_label (_("Add list")) {
+ sensitive = false
+ };
+
+ add_button.clicked.connect(() => {
+ on_action_bar_activate(child_entry.buffer);
+ });
+ child_entry.activate.connect(() => {
+ on_action_bar_activate(child_entry.buffer);
+ });
+
+ child_entry.buffer.bind_property("length", add_button, "sensitive", BindingFlags.SYNC_CREATE, (b, src, ref target) => {
+ target.set_boolean ((uint) src > 0);
+ return true;
+ });
+
+ child_box.append(child_entry);
+ child_box.append(add_button);
+
+ add_action_bar.set_center_widget(child_box);
+ insert_child_after (add_action_bar, header);
+ }
+
public override void on_request_finish () {
- var add_row = new Row (null);
- add_row.open.connect (() => {
- var dlg = new Dialogs.ListEditor.empty ();
- dlg.done.connect (on_refresh);
- });
- append (add_row);
- on_content_changed ();
+ on_content_changed ();
}
}
diff --git a/src/Views/Profile.vala b/src/Views/Profile.vala
index 1598030..a0c98be 100644
--- a/src/Views/Profile.vala
+++ b/src/Views/Profile.vala
@@ -18,6 +18,7 @@ public class Tooth.Views.Profile : Views.Timeline {
protected SimpleAction hiding_reblogs_action;
protected SimpleAction blocking_action;
protected SimpleAction domain_blocking_action;
+ protected SimpleAction ar_list_action;
// protected SimpleAction source_action;
construct {
@@ -35,6 +36,7 @@ public class Tooth.Views.Profile : Views.Timeline {
);
cover.bind (profile);
build_profile_stats(cover.info);
+ rs.invalidated.connect (() => invalidate_actions(false));
}
[GtkTemplate (ui = "/dev/geopjr/tooth/ui/views/profile_header.ui")]
@@ -164,6 +166,11 @@ public class Tooth.Views.Profile : Views.Timeline {
// invalidate_actions (true);
// });
// actions.add_action (source_action);
+ ar_list_action = new SimpleAction ("ar_list", null);
+ ar_list_action.activate.connect (v => {
+ create_ar_list_dialog().show();
+ });
+ actions.add_action (ar_list_action);
var mention_action = new SimpleAction ("mention", VariantType.STRING);
mention_action.activate.connect (v => {
@@ -268,6 +275,7 @@ public class Tooth.Views.Profile : Views.Timeline {
blocking_action.set_state (rs.blocking);
domain_blocking_action.set_state (rs.domain_blocking);
domain_blocking_action.set_enabled (accounts.active.domain != profile.domain);
+ ar_list_action.set_enabled(profile.id != accounts.active.id && rs.following);
if (refresh) {
page_next = null;
@@ -320,4 +328,142 @@ public class Tooth.Views.Profile : Views.Timeline {
network.on_error);
}
+ public class RowButton : Button {
+ public bool remove { get; set; default = false; }
+ }
+
+ public Adw.Window create_ar_list_dialog() {
+ var spinner = new Spinner() {
+ spinning = true,
+ halign = Align.CENTER,
+ valign = Align.CENTER,
+ vexpand = true,
+ hexpand = true,
+ width_request = 32,
+ height_request = 32
+ };
+ var box = new Box(Orientation.VERTICAL, 6);
+ var headerbar = new Adw.HeaderBar();
+ var toast_overlay = new Adw.ToastOverlay() {
+ vexpand = true,
+ valign = Align.CENTER
+ };
+ toast_overlay.child = spinner;
+
+ box.append(headerbar);
+ box.append(toast_overlay);
+ var dialog = new Adw.Window() {
+ title = _("Add or remove \"%s\" to or from a list").printf (profile.handle),
+ modal = true,
+ transient_for = app.main_window,
+ content = box,
+ default_width = 600,
+ default_height = 550
+ };
+ spinner.start();
+
+ var preferences_page = new Adw.PreferencesPage();
+ var preferences_group = new Adw.PreferencesGroup() {
+ title = _("Select the list to add or remove \"%s\" to or from:").printf (profile.handle)
+ };
+
+ var no_lists_page = new Adw.StatusPage() {
+ icon_name = "tooth-error-symbolic",
+ vexpand = true,
+ title = _("You don't have any lists")
+ };
+
+ new Request.GET (@"/api/v1/lists/")
+ .with_account (accounts.active)
+ .with_ctx (this)
+ .on_error (on_error)
+ .then ((sess, msg) => {
+ if (Network.get_array_size(msg) > 0) {
+ new Request.GET (@"/api/v1/accounts/$(profile.id)/lists")
+ .with_account (accounts.active)
+ .with_ctx (this)
+ .on_error (on_error)
+ .then ((sess2, msg2) => {
+ var added = false;
+ var in_list = new Gee.ArrayList();
+
+ Network.parse_array (msg2, node => {
+ var list = API.List.from (node);
+ in_list.add(list.id);
+ });
+ Network.parse_array (msg, node => {
+ var list = API.List.from (node);
+ var is_already = in_list.contains(list.id);
+
+ var add_button = new RowButton() {
+ icon_name = is_already ? "tooth-minus-large-symbolic" : "tooth-plus-large-symbolic",
+ tooltip_text = is_already ? _("Remove \"%s\" from \"%s\"").printf (profile.handle, list.title) : _("Add \"%s\" to \"%s\"").printf (profile.handle, list.title),
+ halign = Align.CENTER,
+ valign = Align.CENTER
+ };
+ add_button.add_css_class("flat");
+ add_button.add_css_class("circular");
+ add_button.remove = is_already;
+
+ var row = new Adw.ActionRow() {
+ title = list.title
+ };
+ row.add_suffix(add_button);
+
+ add_button.clicked.connect(() => {
+ handle_list_edit(list, row, toast_overlay, add_button);
+ });
+
+ preferences_group.add(row);
+ added = true;
+ });
+
+ if (added) {
+ preferences_page.add(preferences_group);
+
+ toast_overlay.child = preferences_page;
+ toast_overlay.valign = Align.FILL;
+ } else {
+ toast_overlay.child = no_lists_page;
+ }
+ })
+ .exec();
+ } else {
+ toast_overlay.child = no_lists_page;
+ }
+ })
+ .exec ();
+
+ return dialog;
+ }
+
+ public void handle_list_edit(API.List list, Adw.ActionRow row, Adw.ToastOverlay toast_overlay, RowButton button) {
+ row.sensitive = false;
+
+ var endpoint = @"/api/v1/lists/$(list.id)/accounts/?account_ids[]=$(profile.id)";
+ var req = button.remove ? new Request.DELETE (endpoint) : new Request.POST (endpoint);
+ req
+ .with_account (accounts.active)
+ .with_ctx (this)
+ .on_error (on_error)
+ .then ((sess, msg) => {
+ var toast_msg = "";
+ if (button.remove) {
+ toast_msg = _("User \"%s\" got removed from \"%s\"").printf (profile.handle, list.title);
+ button.icon_name = "tooth-plus-large-symbolic";
+ button.tooltip_text = _("Add \"%s\" to \"%s\"").printf (profile.handle, list.title);
+ } else {
+ toast_msg = _("User \"%s\" got added to \"%s\"").printf (profile.handle, list.title);
+ button.icon_name = "tooth-minus-large-symbolic";
+ button.tooltip_text = _("Remove \"%s\" from \"%s\"").printf (profile.handle, list.title);
+ }
+
+ button.remove = !button.remove;
+ row.sensitive = true;
+
+ var toast = new Adw.Toast(toast_msg);
+ toast_overlay.add_toast(toast);
+ })
+ .exec();
+ }
}