mirror of
https://gitlab.gnome.org/World/tootle
synced 2025-02-17 03:51:11 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
d4feb6f801
@ -1,30 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
<schema path="/com/github/bleakgrey/tootle/" id="com.github.bleakgrey.tootle" gettext-domain="com.github.bleakgrey.tootle">
|
||||
<key name="client-id" type="s">
|
||||
<default>'null'</default>
|
||||
<summary>Client ID</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="client-secret" type="s">
|
||||
<default>'null'</default>
|
||||
<summary>Client Secret</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="instance-url" type="s">
|
||||
<default>'null'</default>
|
||||
<summary>Instance URL</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="access-token" type="s">
|
||||
<default>'null'</default>
|
||||
<summary>Access Token</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="refresh-token" type="s">
|
||||
<default>'null'</default>
|
||||
<summary>Refresh Token</summary>
|
||||
<description></description>
|
||||
<key name="current-account" type="i">
|
||||
<default>0</default>
|
||||
<summary>Current Account</summary>
|
||||
<description>Do not edit or it shall set your house on fire</description>
|
||||
</key>
|
||||
<key name="always-online" type="b">
|
||||
<default>false</default>
|
||||
|
@ -26,6 +26,7 @@ executable(
|
||||
'src/NetManager.vala',
|
||||
'src/Utils.vala',
|
||||
'src/Notificator.vala',
|
||||
'src/InstanceAccount.vala',
|
||||
'src/API/Account.vala',
|
||||
'src/API/Relationship.vala',
|
||||
'src/API/Mention.vala',
|
||||
@ -35,7 +36,6 @@ executable(
|
||||
'src/API/Notification.vala',
|
||||
'src/API/NotificationType.vala',
|
||||
'src/API/Attachment.vala',
|
||||
'src/Widgets/HeaderBar.vala',
|
||||
'src/Widgets/AlignedLabel.vala',
|
||||
'src/Widgets/RichLabel.vala',
|
||||
'src/Widgets/ImageToggleButton.vala',
|
||||
@ -45,10 +45,10 @@ executable(
|
||||
'src/Widgets/NotificationWidget.vala',
|
||||
'src/Widgets/AttachmentWidget.vala',
|
||||
'src/Widgets/AttachmentBox.vala',
|
||||
'src/Dialogs/NewAccountDialog.vala',
|
||||
'src/Dialogs/PostDialog.vala',
|
||||
'src/Dialogs/SettingsDialog.vala',
|
||||
'src/Views/AbstractView.vala',
|
||||
'src/Views/AddAccountView.vala',
|
||||
'src/Views/TimelineView.vala',
|
||||
'src/Views/LocalView.vala',
|
||||
'src/Views/FederatedView.vala',
|
||||
|
@ -59,7 +59,7 @@ public class Tootle.Account{
|
||||
}
|
||||
|
||||
public Soup.Message get_relationship (){
|
||||
var url = "%s/api/v1/accounts/relationships?id=%lld".printf (Tootle.settings.instance_url, id);
|
||||
var url = "%s/api/v1/accounts/relationships?id=%lld".printf (Tootle.accounts.formal.instance, id);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue (msg, (sess, mess) => {
|
||||
@ -78,7 +78,7 @@ public class Tootle.Account{
|
||||
|
||||
public Soup.Message set_following (bool follow = true){
|
||||
var action = follow ? "follow" : "unfollow";
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (Tootle.settings.instance_url, id, action);
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (Tootle.accounts.formal.instance, id, action);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue (msg, (sess, mess) => {
|
||||
@ -97,7 +97,7 @@ public class Tootle.Account{
|
||||
|
||||
public Soup.Message set_muted (bool mute = true){
|
||||
var action = mute ? "mute" : "unmute";
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (Tootle.settings.instance_url, id, action);
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (Tootle.accounts.formal.instance, id, action);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue (msg, (sess, mess) => {
|
||||
@ -116,7 +116,7 @@ public class Tootle.Account{
|
||||
|
||||
public Soup.Message set_blocked (bool block = true){
|
||||
var action = block ? "block" : "unblock";
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (Tootle.settings.instance_url, id, action);
|
||||
var url = "%s/api/v1/accounts/%lld/%s".printf (Tootle.accounts.formal.instance, id, action);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue (msg, (sess, mess) => {
|
||||
@ -132,12 +132,5 @@ public class Tootle.Account{
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
public Soup.Message get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=user&access_token=%s".printf (Tootle.settings.instance_url, Tootle.settings.access_token);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.priority = Soup.MessagePriority.VERY_HIGH;
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,21 +40,21 @@ public class Tootle.Notification{
|
||||
if (type == NotificationType.FOLLOW_REQUEST)
|
||||
return reject_follow_request ();
|
||||
|
||||
var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (Tootle.settings.instance_url, id);
|
||||
var url = "%s/api/v1/notifications/dismiss?id=%lld".printf (Tootle.accounts.formal.instance, id);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
Tootle.network.queue(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public Soup.Message accept_follow_request () {
|
||||
var url = "%s/api/v1/follow_requests/%lld/authorize".printf (Tootle.settings.instance_url, account.id);
|
||||
var url = "%s/api/v1/follow_requests/%lld/authorize".printf (Tootle.accounts.formal.instance, account.id);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
Tootle.network.queue(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public Soup.Message reject_follow_request () {
|
||||
var url = "%s/api/v1/follow_requests/%lld/reject".printf (Tootle.settings.instance_url, account.id);
|
||||
var url = "%s/api/v1/follow_requests/%lld/reject".printf (Tootle.accounts.formal.instance, account.id);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
Tootle.network.queue(msg);
|
||||
return msg;
|
||||
|
@ -93,7 +93,7 @@ public class Tootle.Status {
|
||||
|
||||
public void set_reblogged (bool rebl = true){
|
||||
var action = rebl ? "reblog" : "unreblog";
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/statuses/%lld/%s".printf (Tootle.settings.instance_url, id, action));
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/statuses/%lld/%s".printf (Tootle.accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
msg.finished.connect (() => {
|
||||
reblogged = rebl;
|
||||
@ -108,7 +108,7 @@ public class Tootle.Status {
|
||||
|
||||
public void set_favorited (bool fav = true){
|
||||
var action = fav ? "favourite" : "unfavourite";
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/statuses/%lld/%s".printf (Tootle.settings.instance_url, id, action));
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/statuses/%lld/%s".printf (Tootle.accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
msg.finished.connect (() => {
|
||||
favorited = fav;
|
||||
@ -123,7 +123,7 @@ public class Tootle.Status {
|
||||
|
||||
public void set_muted (bool mute = true){
|
||||
var action = mute ? "mute" : "unmute";
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/statuses/%lld/%s".printf (Tootle.settings.instance_url, id, action));
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/statuses/%lld/%s".printf (Tootle.accounts.formal.instance, id, action));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
msg.finished.connect (() => {
|
||||
muted = mute;
|
||||
@ -137,7 +137,7 @@ public class Tootle.Status {
|
||||
}
|
||||
|
||||
public void poof (){
|
||||
var msg = new Soup.Message("DELETE", "%s/api/v1/statuses/%lld".printf (Tootle.settings.instance_url, id));
|
||||
var msg = new Soup.Message("DELETE", "%s/api/v1/statuses/%lld".printf (Tootle.accounts.formal.instance, id));
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
msg.finished.connect (() => {
|
||||
Tootle.app.toast (_("Poof!"));
|
||||
|
@ -2,118 +2,155 @@ using GLib;
|
||||
|
||||
public class Tootle.AccountManager : Object{
|
||||
|
||||
public abstract signal void switched(Account? account);
|
||||
public abstract signal void added(Account account);
|
||||
public abstract signal void removed(Account account);
|
||||
private string dir_path;
|
||||
private string file_path;
|
||||
|
||||
public Account? current;
|
||||
public abstract signal void switched (Account? account);
|
||||
public abstract signal void updated (GenericArray<InstanceAccount> accounts);
|
||||
|
||||
private GenericArray<InstanceAccount> saved_accounts = new GenericArray<InstanceAccount> ();
|
||||
public InstanceAccount? formal {get; set;}
|
||||
public Account? current {get; set;}
|
||||
|
||||
public AccountManager(){
|
||||
Object();
|
||||
dir_path = "%s/%s".printf (GLib.Environment.get_user_config_dir (), Tootle.app.application_id);
|
||||
file_path = "%s/%s".printf (dir_path, "accounts.json");
|
||||
}
|
||||
|
||||
public bool has_client_tokens(){
|
||||
var client_id = Tootle.settings.client_id;
|
||||
var client_secret = Tootle.settings.client_secret;
|
||||
|
||||
return !(client_id == "null" || client_secret == "null");
|
||||
|
||||
public void signal_current () {
|
||||
switched (current);
|
||||
updated (saved_accounts);
|
||||
}
|
||||
|
||||
public bool has_access_token (){
|
||||
return Tootle.settings.access_token != "null";
|
||||
|
||||
public void switch_account (int id){
|
||||
debug ("Switching to account #%i", id);
|
||||
Tootle.settings.current_account = id;
|
||||
formal = saved_accounts.@get(id);
|
||||
var msg = new Soup.Message("GET", "%s/api/v1/accounts/verify_credentials".printf (Tootle.accounts.formal.instance));
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
var root = Tootle.network.parse (mess);
|
||||
current = Account.parse (root);
|
||||
switched (current);
|
||||
updated (saved_accounts);
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't login into %s", formal.instance);
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void request_auth_code (string client_id){
|
||||
var pars = "?scope=read%20write%20follow";
|
||||
pars += "&response_type=code";
|
||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&client_id=" +client_id;
|
||||
|
||||
public void add (InstanceAccount account) {
|
||||
debug ("Adding account for %s at %s", account.username, account.instance);
|
||||
saved_accounts.add (account);
|
||||
save ();
|
||||
updated (saved_accounts);
|
||||
switch_account (saved_accounts.length - 1);
|
||||
account.start_notificator ();
|
||||
}
|
||||
|
||||
public void remove (int i) {
|
||||
var account = saved_accounts.@get (i);
|
||||
account.close_notificator ();
|
||||
|
||||
saved_accounts.remove_index (i);
|
||||
if (saved_accounts.length < 1)
|
||||
switched (null);
|
||||
else {
|
||||
var id = Tootle.settings.current_account - 1;
|
||||
if (id > saved_accounts.length - 1)
|
||||
id = saved_accounts.length - 1;
|
||||
else if (id < saved_accounts.length - 1)
|
||||
id = 0;
|
||||
switch_account (id);
|
||||
}
|
||||
save ();
|
||||
updated (saved_accounts);
|
||||
|
||||
if (is_empty ()) {
|
||||
Tootle.window.destroy ();
|
||||
NewAccountDialog.open ();
|
||||
}
|
||||
}
|
||||
|
||||
public bool is_empty () {
|
||||
return saved_accounts.length == 0;
|
||||
}
|
||||
|
||||
public void init (){
|
||||
save (false);
|
||||
load ();
|
||||
|
||||
if (saved_accounts.length < 1) {
|
||||
switched (null);
|
||||
NewAccountDialog.open ();
|
||||
}
|
||||
else
|
||||
switch_account (Tootle.settings.current_account);
|
||||
}
|
||||
|
||||
private void save (bool overwrite = true) {
|
||||
try {
|
||||
AppInfo.launch_default_for_uri ("%s/oauth/authorize%s".printf (Tootle.settings.instance_url, pars), null);
|
||||
var dir = File.new_for_path (dir_path);
|
||||
if (!dir.query_exists ())
|
||||
dir.make_directory ();
|
||||
|
||||
var file = File.new_for_path (file_path);
|
||||
if (file.query_exists () && !overwrite)
|
||||
return;
|
||||
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_array ();
|
||||
saved_accounts.foreach ((acc) => {
|
||||
var node = acc.serialize ();
|
||||
builder.add_value (node);
|
||||
});
|
||||
builder.end_array ();
|
||||
|
||||
var generator = new Json.Generator ();
|
||||
generator.set_root (builder.get_root ());
|
||||
var data = generator.to_data (null);
|
||||
|
||||
if (file.query_exists ())
|
||||
file.@delete ();
|
||||
|
||||
FileOutputStream stream = file.create (FileCreateFlags.PRIVATE);
|
||||
stream.write (data.data);
|
||||
}
|
||||
catch (GLib.Error e){
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void load () {
|
||||
try {
|
||||
uint8[] data;
|
||||
string etag;
|
||||
var file = File.new_for_path (file_path);
|
||||
file.load_contents (null, out data, out etag);
|
||||
var contents = (string) data;
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
parser.load_from_data (contents, -1);
|
||||
var array = parser.get_root ().get_array ();
|
||||
|
||||
saved_accounts = new GenericArray<InstanceAccount> ();
|
||||
array.foreach_element ((_arr, _i, node) => {
|
||||
var obj = node.get_object ();
|
||||
var account = InstanceAccount.parse (obj);
|
||||
if (account != null) {
|
||||
saved_accounts.add (account);
|
||||
account.start_notificator ();
|
||||
}
|
||||
});
|
||||
debug ("Loaded %i saved accounts", saved_accounts.length);
|
||||
updated (saved_accounts);
|
||||
}
|
||||
catch (GLib.Error e){
|
||||
warning (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public Soup.Message request_client_tokens(){
|
||||
var pars = "?client_name=Tootle";
|
||||
pars += "&redirect_uris=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&website=https://github.com/bleakgrey/tootle";
|
||||
pars += "&scopes=read%20write%20follow";
|
||||
|
||||
var msg = new Soup.Message("POST", "%s/api/v1/apps%s".printf (Tootle.settings.instance_url, pars));
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
var root = Tootle.network.parse (mess);
|
||||
var client_id = root.get_string_member ("client_id");
|
||||
var client_secret = root.get_string_member ("client_secret");
|
||||
Tootle.settings.client_id = client_id;
|
||||
Tootle.settings.client_secret = client_secret;
|
||||
debug ("Received tokens");
|
||||
|
||||
request_auth_code (client_id);
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't request client secret.");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
public Soup.Message try_auth (string code){
|
||||
var pars = "?client_id=" + Tootle.settings.client_id;
|
||||
pars += "&client_secret=" + Tootle.settings.client_secret;
|
||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&grant_type=authorization_code";
|
||||
pars += "&code=" + code;
|
||||
|
||||
var msg = new Soup.Message("POST", "%s/oauth/token%s".printf (Tootle.settings.instance_url, pars));
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
var root = Tootle.network.parse (mess);
|
||||
var access_token = root.get_string_member ("access_token");
|
||||
Tootle.settings.access_token = access_token;
|
||||
debug ("Got access token");
|
||||
request_current ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't get access token");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
public Soup.Message request_current (){
|
||||
var msg = new Soup.Message("GET", "%s/api/v1/accounts/verify_credentials".printf (Tootle.settings.instance_url));
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
var root = Tootle.network.parse (mess);
|
||||
current = Account.parse(root);
|
||||
switched (current);
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't get current user");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void logout (){
|
||||
current = null;
|
||||
Tootle.settings.access_token = "null";
|
||||
switched (null);
|
||||
}
|
||||
|
||||
public void init (){
|
||||
if(has_access_token())
|
||||
request_current ();
|
||||
else
|
||||
switched (null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ namespace Tootle{
|
||||
accounts = new AccountManager ();
|
||||
network = new NetManager ();
|
||||
image_cache = new ImageCache ();
|
||||
|
||||
accounts.init ();
|
||||
|
||||
app.error.connect (app.on_error);
|
||||
return app.run (args);
|
||||
}
|
||||
|
||||
@ -47,14 +49,28 @@ namespace Tootle{
|
||||
protected override void activate () {
|
||||
if (window != null) {
|
||||
debug ("Reopening window");
|
||||
window.present ();
|
||||
if (!accounts.is_empty ())
|
||||
window.present ();
|
||||
else
|
||||
NewAccountDialog.open ();
|
||||
}
|
||||
else {
|
||||
debug ("Creating new window");
|
||||
window = new MainWindow (this);
|
||||
window.present ();
|
||||
if (accounts.is_empty ())
|
||||
NewAccountDialog.open ();
|
||||
else {
|
||||
window = new MainWindow (this);
|
||||
window.present ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void on_error (string title, string msg){
|
||||
var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (title, msg, "dialog-warning");
|
||||
message_dialog.transient_for = window;
|
||||
message_dialog.run ();
|
||||
message_dialog.destroy ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
218
src/Dialogs/NewAccountDialog.vala
Normal file
218
src/Dialogs/NewAccountDialog.vala
Normal file
@ -0,0 +1,218 @@
|
||||
using Gtk;
|
||||
using Tootle;
|
||||
|
||||
public class Tootle.NewAccountDialog : Gtk.Dialog {
|
||||
|
||||
private static NewAccountDialog dialog;
|
||||
|
||||
private Gtk.Grid grid;
|
||||
private Gtk.Button button_done;
|
||||
private Gtk.Image logo;
|
||||
private Gtk.Entry instance_entry;
|
||||
private Gtk.Label instance_register;
|
||||
private Gtk.Label code_name;
|
||||
private Gtk.Entry code_entry;
|
||||
|
||||
private string? instance;
|
||||
private string? client_id;
|
||||
private string? client_secret;
|
||||
private string? code;
|
||||
private string? token;
|
||||
private string? username;
|
||||
|
||||
public NewAccountDialog () {
|
||||
Object (
|
||||
border_width: 6,
|
||||
deletable: true,
|
||||
resizable: false,
|
||||
title: _("New Account"),
|
||||
transient_for: Tootle.window
|
||||
);
|
||||
|
||||
logo = new Image.from_resource ("/com/github/bleakgrey/tootle/logo128");
|
||||
logo.halign = Gtk.Align.CENTER;
|
||||
logo.hexpand = true;
|
||||
logo.margin_bottom = 24;
|
||||
|
||||
instance_entry = new Entry ();
|
||||
instance_entry.text = "https://myinstance.com";
|
||||
instance_entry.set_placeholder_text ("https://myinstance.com");
|
||||
instance_entry.width_chars = 30;
|
||||
|
||||
instance_register = new Label ("<a href=\"https://joinmastodon.org/\">%s</a>".printf (_("What's an instance?")));
|
||||
instance_register.halign = Gtk.Align.END;
|
||||
instance_register.set_use_markup (true);
|
||||
|
||||
code_name = new AlignedLabel (_("Code:"));
|
||||
|
||||
code_entry = new Entry ();
|
||||
code_entry.secondary_icon_name = "dialog-question-symbolic";
|
||||
code_entry.secondary_icon_tooltip_text = _("Paste your instance authorization code here");
|
||||
code_entry.secondary_icon_activatable = false;
|
||||
|
||||
button_done = new Gtk.Button.with_label (_("Add Account"));
|
||||
button_done.clicked.connect (on_done_clicked);
|
||||
button_done.halign = Gtk.Align.END;
|
||||
button_done.margin_top = 24;
|
||||
|
||||
grid = new Gtk.Grid ();
|
||||
grid.column_spacing = 12;
|
||||
grid.row_spacing = 6;
|
||||
grid.hexpand = true;
|
||||
grid.halign = Gtk.Align.CENTER;
|
||||
grid.attach (logo, 0, 0, 2, 1);
|
||||
grid.attach (new AlignedLabel (_("Instance:")), 0, 1);
|
||||
grid.attach (instance_entry, 1, 1);
|
||||
grid.attach (code_name, 0, 3);
|
||||
grid.attach (code_entry, 1, 3);
|
||||
grid.attach (instance_register, 1, 5);
|
||||
grid.attach (button_done, 1, 10);
|
||||
|
||||
var content = get_content_area () as Gtk.Box;
|
||||
content.pack_start (grid, false, false, 0);
|
||||
|
||||
destroy.connect (() => {
|
||||
dialog = null;
|
||||
|
||||
if (Tootle.accounts.is_empty ())
|
||||
Tootle.app.remove_window (Tootle.window_dummy);
|
||||
});
|
||||
|
||||
show_all ();
|
||||
clear ();
|
||||
}
|
||||
|
||||
private void clear () {
|
||||
code_name.hide ();
|
||||
code_entry.hide ();
|
||||
code_entry.text = "";
|
||||
client_id = client_secret = code = token = null;
|
||||
}
|
||||
|
||||
private void on_done_clicked () {
|
||||
instance = instance_entry.text;
|
||||
code = code_entry.text;
|
||||
|
||||
if (this.client_id == null || this.client_secret == null) {
|
||||
request_client_tokens ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (code == "")
|
||||
Tootle.app.error (_("Error"), _("Please paste valid instance authorization code"));
|
||||
else
|
||||
try_auth (code);
|
||||
}
|
||||
|
||||
private bool show_error (Soup.Message msg) {
|
||||
if (msg.status_code != Soup.Status.OK) {
|
||||
var phrase = Soup.Status.get_phrase (msg.status_code);
|
||||
Tootle.app.error (_("Network Error"), phrase);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void request_client_tokens (){
|
||||
var pars = "?client_name=Tootle";
|
||||
pars += "&redirect_uris=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&website=https://github.com/bleakgrey/tootle";
|
||||
pars += "&scopes=read%20write%20follow";
|
||||
|
||||
grid.sensitive = false;
|
||||
var msg = new Soup.Message ("POST", "%s/api/v1/apps%s".printf (instance, pars));
|
||||
Tootle.network.queue_custom (msg, (sess, mess) => {
|
||||
grid.sensitive = true;
|
||||
if (show_error (mess)) return;
|
||||
|
||||
var root = Tootle.network.parse (mess);
|
||||
var id = root.get_string_member ("client_id");
|
||||
var secret = root.get_string_member ("client_secret");
|
||||
client_id = id;
|
||||
client_secret = secret;
|
||||
|
||||
info ("Received tokens from %s", instance);
|
||||
request_auth_code ();
|
||||
code_name.show ();
|
||||
code_entry.show ();
|
||||
});
|
||||
}
|
||||
|
||||
private void request_auth_code (){
|
||||
var pars = "?scope=read%20write%20follow";
|
||||
pars += "&response_type=code";
|
||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&client_id=" + client_id;
|
||||
|
||||
try {
|
||||
info ("Requesting auth token");
|
||||
AppInfo.launch_default_for_uri ("%s/oauth/authorize%s".printf (instance, pars), null);
|
||||
}
|
||||
catch (GLib.Error e){
|
||||
warning (e.message);
|
||||
Tootle.app.error (_("Error"), e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private void try_auth (string code){
|
||||
var pars = "?client_id=" + client_id;
|
||||
pars += "&client_secret=" + client_secret;
|
||||
pars += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob";
|
||||
pars += "&grant_type=authorization_code";
|
||||
pars += "&code=" + code;
|
||||
|
||||
var msg = new Soup.Message ("POST", "%s/oauth/token%s".printf (instance, pars));
|
||||
Tootle.network.queue_custom (msg, (sess, mess) => {
|
||||
try{
|
||||
if (show_error (mess)) return;
|
||||
var root = Tootle.network.parse (mess);
|
||||
token = root.get_string_member ("access_token");
|
||||
|
||||
debug ("Got access token");
|
||||
get_username ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't get access token");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void get_username () {
|
||||
var msg = new Soup.Message("GET", "%s/api/v1/accounts/verify_credentials".printf (instance));
|
||||
msg.request_headers.append ("Authorization", "Bearer " + token);
|
||||
Tootle.network.queue_custom (msg, (sess, mess) => {
|
||||
try{
|
||||
if (show_error (mess)) return;
|
||||
var root = Tootle.network.parse (mess);
|
||||
username = root.get_string_member ("username");
|
||||
|
||||
add_account ();
|
||||
Tootle.window.show ();
|
||||
Tootle.window.present ();
|
||||
destroy ();
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't get username");
|
||||
warning (e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void add_account () {
|
||||
var account = new InstanceAccount ();
|
||||
account.username = username;
|
||||
account.instance = instance;
|
||||
account.client_id = client_id;
|
||||
account.client_secret = client_secret;
|
||||
account.token = token;
|
||||
Tootle.accounts.add (account);
|
||||
Tootle.app.activate ();
|
||||
}
|
||||
|
||||
public static void open () {
|
||||
if (dialog == null)
|
||||
dialog = new NewAccountDialog ();
|
||||
}
|
||||
|
||||
}
|
@ -184,7 +184,7 @@ public class Tootle.PostDialog : Gtk.Dialog {
|
||||
pars += "&spoiler_text=" + Soup.URI.encode (spoiler_text.buffer.text, null);
|
||||
}
|
||||
|
||||
var url = "%s/api/v1/statuses%s".printf (Tootle.settings.instance_url, pars);
|
||||
var url = "%s/api/v1/statuses%s".printf (Tootle.accounts.formal.instance, pars);
|
||||
var msg = new Soup.Message("POST", url);
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try {
|
||||
|
@ -27,13 +27,13 @@ public class Tootle.SettingsDialog : Gtk.Dialog {
|
||||
grid.attach (new SettingsLabel (_("Real-time updates:")), 0, i);
|
||||
grid.attach (new SettingsSwitch ("live-updates"), 1, i++);
|
||||
|
||||
grid.attach (new Granite.HeaderLabel (_("Caching")), 0, i++, 2, 1);
|
||||
grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||
grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||
grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||
var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
||||
settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||
grid.attach (cache_size, 1, i++);
|
||||
// grid.attach (new Granite.HeaderLabel (_("Caching")), 0, i++, 2, 1);
|
||||
// grid.attach (new SettingsLabel (_("Use cache:")), 0, i);
|
||||
// grid.attach (new SettingsSwitch ("cache"), 1, i++);
|
||||
// grid.attach (new SettingsLabel (_("Max cache size (MB):")), 0, i);
|
||||
// var cache_size = new Gtk.SpinButton.with_range (16, 256, 1);
|
||||
// settings.schema.bind ("cache-size", cache_size, "value", SettingsBindFlags.DEFAULT);
|
||||
// grid.attach (cache_size, 1, i++);
|
||||
|
||||
grid.attach (new Granite.HeaderLabel (_("Notifications")), 0, i++, 2, 1);
|
||||
grid.attach (new SettingsLabel (_("Always receive notifications:")), 0, i);
|
||||
|
99
src/InstanceAccount.vala
Normal file
99
src/InstanceAccount.vala
Normal file
@ -0,0 +1,99 @@
|
||||
public class Tootle.InstanceAccount : GLib.Object {
|
||||
|
||||
public string username {get; set;}
|
||||
public string instance {get; set;}
|
||||
public string client_id {get; set;}
|
||||
public string client_secret {get; set;}
|
||||
public string token {get; set;}
|
||||
|
||||
private Notificator? notificator;
|
||||
|
||||
public InstanceAccount (){
|
||||
Object ();
|
||||
}
|
||||
|
||||
public string get_pretty_instance () {
|
||||
return instance
|
||||
.replace ("https://", "")
|
||||
.replace ("/","");
|
||||
}
|
||||
|
||||
public void start_notificator () {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
|
||||
notificator = new Notificator (get_stream ());
|
||||
notificator.status_added.connect (status_added);
|
||||
notificator.status_removed.connect (status_removed);
|
||||
notificator.notification.connect (notification);
|
||||
notificator.start ();
|
||||
}
|
||||
|
||||
private Soup.Message get_stream () {
|
||||
var url = "%s/api/v1/streaming/?stream=user&access_token=%s".printf (instance, token);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void close_notificator () {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
}
|
||||
|
||||
public Json.Node serialize () {
|
||||
var builder = new Json.Builder ();
|
||||
builder.begin_object ();
|
||||
builder.set_member_name ("hash");
|
||||
builder.add_string_value ("test");
|
||||
builder.set_member_name ("username");
|
||||
builder.add_string_value (this.username);
|
||||
builder.set_member_name ("instance");
|
||||
builder.add_string_value (this.instance);
|
||||
builder.set_member_name ("id");
|
||||
builder.add_string_value (this.client_id);
|
||||
builder.set_member_name ("secret");
|
||||
builder.add_string_value (this.client_secret);
|
||||
builder.set_member_name ("token");
|
||||
builder.add_string_value (this.token);
|
||||
builder.end_object ();
|
||||
return builder.get_root ();
|
||||
}
|
||||
|
||||
public static InstanceAccount parse (Json.Object obj) {
|
||||
var acc = new InstanceAccount ();
|
||||
acc.username = obj.get_string_member ("username");
|
||||
acc.instance = obj.get_string_member ("instance");
|
||||
acc.client_id = obj.get_string_member ("id");
|
||||
acc.client_secret = obj.get_string_member ("secret");
|
||||
acc.token = obj.get_string_member ("token");
|
||||
return acc;
|
||||
}
|
||||
|
||||
private void notification (ref Notification obj) {
|
||||
var title = Utils.escape_html (obj.type.get_desc (obj.account));
|
||||
var notification = new GLib.Notification (title);
|
||||
if (obj.status != null) {
|
||||
var body = "";
|
||||
body += get_pretty_instance ();
|
||||
body += "\n";
|
||||
body += Utils.escape_html (obj.status.content);
|
||||
notification.set_body (body);
|
||||
}
|
||||
app.send_notification (app.application_id + ":" + obj.id.to_string (), notification);
|
||||
|
||||
network.notification (ref obj);
|
||||
}
|
||||
|
||||
private void status_added (ref Status status) {
|
||||
if (settings.live_updates)
|
||||
network.status_added (ref status, "home");
|
||||
else
|
||||
app.toast (_("New toot available"));
|
||||
}
|
||||
|
||||
private void status_removed (int64 id) {
|
||||
if (settings.live_updates)
|
||||
network.status_removed (id);
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
using Gtk;
|
||||
|
||||
public class Tootle.MainWindow: Gtk.Window {
|
||||
|
||||
private weak SettingsManager settings;
|
||||
|
||||
private Gtk.Overlay overlay;
|
||||
private Granite.Widgets.Toast toast;
|
||||
private Gtk.Grid grid;
|
||||
public Tootle.HeaderBar header;
|
||||
public Stack primary_stack;
|
||||
public Stack secondary_stack;
|
||||
private Stack primary_stack;
|
||||
private Stack secondary_stack;
|
||||
|
||||
public Gtk.HeaderBar header;
|
||||
private Granite.Widgets.ModeButton button_mode;
|
||||
private AccountsButton button_accounts;
|
||||
private Spinner spinner;
|
||||
private Button button_toot;
|
||||
private Button button_back;
|
||||
|
||||
construct {
|
||||
settings = Tootle.settings;
|
||||
|
||||
var provider = new Gtk.CssProvider ();
|
||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/app.css");
|
||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
@ -33,15 +35,54 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
primary_stack.add_named (secondary_stack, "0");
|
||||
primary_stack.hexpand = true;
|
||||
primary_stack.vexpand = true;
|
||||
header = new Tootle.HeaderBar ();
|
||||
|
||||
spinner = new Spinner ();
|
||||
spinner.active = true;
|
||||
|
||||
button_accounts = new AccountsButton ();
|
||||
|
||||
button_back = new Button ();
|
||||
button_back.label = _("Back");
|
||||
button_back.get_style_context ().add_class (Granite.STYLE_CLASS_BACK_BUTTON);
|
||||
button_back.clicked.connect (() => back ());
|
||||
|
||||
button_toot = new Button ();
|
||||
button_toot.tooltip_text = _("Toot");
|
||||
button_toot.image = new Gtk.Image.from_icon_name ("document-edit-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
|
||||
button_toot.clicked.connect (() => PostDialog.open ());
|
||||
|
||||
button_mode = new Granite.Widgets.ModeButton ();
|
||||
button_mode.get_style_context ().add_class ("mode");
|
||||
button_mode.mode_changed.connect (widget => {
|
||||
secondary_stack.set_visible_child_name (widget.tooltip_text);
|
||||
});
|
||||
button_mode.show ();
|
||||
|
||||
header = new Gtk.HeaderBar ();
|
||||
header.show_close_button = true;
|
||||
header.custom_title = button_mode;
|
||||
header.show_all ();
|
||||
header.pack_start (button_back);
|
||||
header.pack_start (button_toot);
|
||||
header.pack_end (button_accounts);
|
||||
header.pack_end (spinner);
|
||||
|
||||
grid = new Gtk.Grid ();
|
||||
grid.set_size_request (400, 500);
|
||||
grid.attach (primary_stack, 0, 0, 1, 1);
|
||||
grid.attach (overlay, 0, 0, 1, 1);
|
||||
|
||||
add_header_view (new TimelineView ("home"));
|
||||
add_header_view (new NotificationsView ());
|
||||
add_header_view (new LocalView ());
|
||||
add_header_view (new FederatedView ());
|
||||
button_mode.set_active (0);
|
||||
update_header ();
|
||||
|
||||
add (grid);
|
||||
show_all ();
|
||||
|
||||
button_mode.valign = Gtk.Align.FILL;
|
||||
}
|
||||
|
||||
public MainWindow (Gtk.Application application) {
|
||||
@ -53,43 +94,16 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
set_titlebar (header);
|
||||
window_position = WindowPosition.CENTER;
|
||||
|
||||
Tootle.accounts.switched.connect(on_account_switched);
|
||||
Tootle.app.error.connect (on_error);
|
||||
Tootle.app.toast.connect (on_toast);
|
||||
Tootle.accounts.init ();
|
||||
}
|
||||
|
||||
private void reset () {
|
||||
header.button_mode.clear_children ();
|
||||
secondary_stack.forall (widget => widget.destroy ());
|
||||
}
|
||||
|
||||
private void on_account_switched(Account? account = Tootle.accounts.current){
|
||||
reset ();
|
||||
if(account == null)
|
||||
build_setup_view ();
|
||||
else
|
||||
build_main_view ();
|
||||
}
|
||||
|
||||
private void build_setup_view (){
|
||||
var add_account = new AddAccountView ();
|
||||
secondary_stack.add_named (add_account, add_account.get_name ());
|
||||
header.update (false, true);
|
||||
}
|
||||
|
||||
private void build_main_view (){
|
||||
add_header_view (new TimelineView ("home"));
|
||||
add_header_view (new NotificationsView ());
|
||||
add_header_view (new LocalView ());
|
||||
add_header_view (new FederatedView ());
|
||||
header.update (true);
|
||||
app.toast.connect (on_toast);
|
||||
network.started.connect (() => spinner.show ());
|
||||
network.finished.connect (() => spinner.hide ());
|
||||
accounts.signal_current ();
|
||||
}
|
||||
|
||||
private void add_header_view (AbstractView view) {
|
||||
var img = new Gtk.Image.from_icon_name(view.get_icon (), Gtk.IconSize.LARGE_TOOLBAR);
|
||||
img.tooltip_text = view.get_name ();
|
||||
header.button_mode.append (img);
|
||||
button_mode.append (img);
|
||||
view.image = img;
|
||||
secondary_stack.add_named(view, view.get_name ());
|
||||
|
||||
@ -97,13 +111,34 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
img.pixel_size = 20; // For some reason Notifications icon is too small without this
|
||||
}
|
||||
|
||||
public void open_view (Widget widget) {
|
||||
widget.show ();
|
||||
var i = int.parse (primary_stack.get_visible_child_name ());
|
||||
public int get_visible_id () {
|
||||
return int.parse (primary_stack.get_visible_child_name ());
|
||||
}
|
||||
|
||||
public void open_view (AbstractView widget) {
|
||||
var i = get_visible_id ();
|
||||
i++;
|
||||
widget.stack_pos = i;
|
||||
widget.show ();
|
||||
primary_stack.add_named (widget, i.to_string ());
|
||||
primary_stack.set_visible_child_name (i.to_string ());
|
||||
header.update (false);
|
||||
update_header ();
|
||||
}
|
||||
|
||||
public void back () {
|
||||
var i = get_visible_id ();
|
||||
var child = primary_stack.get_child_by_name (i.to_string ());
|
||||
primary_stack.set_visible_child_name ((i-1).to_string ());
|
||||
child.destroy ();
|
||||
update_header ();
|
||||
}
|
||||
|
||||
public void reopen_view (int view_id) {
|
||||
var i = get_visible_id ();
|
||||
while (i != view_id && view_id != 0) {
|
||||
back ();
|
||||
i = get_visible_id ();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_toast (string msg){
|
||||
@ -111,16 +146,9 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
toast.send_notification ();
|
||||
}
|
||||
|
||||
private void on_error (string title, string msg){
|
||||
var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (title, msg, "dialog-warning");
|
||||
message_dialog.transient_for = this;
|
||||
message_dialog.run ();
|
||||
message_dialog.destroy ();
|
||||
}
|
||||
|
||||
public override bool delete_event (Gdk.EventAny event) {
|
||||
this.destroy.connect (() => {
|
||||
if (!Tootle.settings.always_online)
|
||||
if (!Tootle.settings.always_online || Tootle.accounts.is_empty ())
|
||||
Tootle.app.remove_window (Tootle.window_dummy);
|
||||
Tootle.window = null;
|
||||
});
|
||||
@ -133,8 +161,15 @@ public class Tootle.MainWindow: Gtk.Window {
|
||||
var theme = is_dark ? "dark" : "light";
|
||||
provider.load_from_resource ("/com/github/bleakgrey/tootle/%s.css".printf (theme));
|
||||
StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = is_dark;
|
||||
}
|
||||
|
||||
private void update_header () {
|
||||
bool primary_mode = get_visible_id () == 0;
|
||||
button_mode.set_visible (primary_mode);
|
||||
button_toot.set_visible (primary_mode);
|
||||
button_back.set_visible (!primary_mode);
|
||||
button_accounts.set_visible (true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ public class Tootle.NetManager : GLib.Object {
|
||||
private Soup.Cache cache;
|
||||
public string cache_path;
|
||||
|
||||
private Notificator? notificator;
|
||||
|
||||
construct {
|
||||
cache_path = "%s/%s".printf (GLib.Environment.get_user_cache_dir (), Tootle.app.application_id);
|
||||
cache = new Soup.Cache (cache_path, Soup.CacheType.SINGLE_USER);
|
||||
@ -45,38 +43,23 @@ public class Tootle.NetManager : GLib.Object {
|
||||
|
||||
public NetManager() {
|
||||
GLib.Object();
|
||||
|
||||
Tootle.accounts.switched.connect (acc => {
|
||||
if (notificator != null)
|
||||
notificator.close ();
|
||||
if (acc == null)
|
||||
return;
|
||||
|
||||
notificator = new Notificator (acc);
|
||||
notificator.start ();
|
||||
});
|
||||
}
|
||||
|
||||
private void on_settings_changed () {
|
||||
cache.set_max_size (1024 * 1024 * Tootle.settings.cache_size);
|
||||
|
||||
var has_cache = session.has_feature (cache.get_type ());
|
||||
if (Tootle.settings.cache) {
|
||||
if (!has_cache) {
|
||||
debug ("Turning on cache");
|
||||
session.add_feature (cache);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (has_cache) {
|
||||
debug ("Turning off cache");
|
||||
session.remove_feature (cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void abort (Soup.Message msg) {
|
||||
session.cancel_message (msg, 0);
|
||||
// cache.set_max_size (1024 * 1024 * Tootle.settings.cache_size);
|
||||
// var has_cache = session.has_feature (cache.get_type ());
|
||||
// if (Tootle.settings.cache) {
|
||||
// if (!has_cache) {
|
||||
// debug ("Turning on cache");
|
||||
// session.add_feature (cache);
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// if (has_cache) {
|
||||
// debug ("Turning off cache");
|
||||
// session.remove_feature (cache);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public async WebsocketConnection stream (Soup.Message msg) {
|
||||
@ -87,9 +70,9 @@ public class Tootle.NetManager : GLib.Object {
|
||||
requests_processing++;
|
||||
started ();
|
||||
|
||||
var token = Tootle.settings.access_token;
|
||||
if(token != "null")
|
||||
msg.request_headers.append ("Authorization", "Bearer " + token);
|
||||
var formal = Tootle.accounts.formal;
|
||||
if(formal != null)
|
||||
msg.request_headers.append ("Authorization", "Bearer " + formal.token);
|
||||
|
||||
session.queue_message (msg, (sess, mess) => {
|
||||
switch (mess.tls_errors){
|
||||
@ -108,6 +91,12 @@ public class Tootle.NetManager : GLib.Object {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mess.status_code != Soup.Status.OK) {
|
||||
var phrase = Soup.Status.get_phrase (mess.status_code);
|
||||
Tootle.app.toast (_("Error: %s").printf(phrase));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cb != null)
|
||||
cb (sess, mess);
|
||||
|
||||
@ -119,6 +108,10 @@ public class Tootle.NetManager : GLib.Object {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void queue_custom (Soup.Message msg, owned Soup.SessionCallback cb) {
|
||||
session.queue_message (msg, cb);
|
||||
}
|
||||
|
||||
public Json.Object parse (Soup.Message msg) throws GLib.Error {
|
||||
// debug ("Status Code: %u", msg.status_code);
|
||||
// debug ("Message length: %lld", msg.response_body.length);
|
||||
|
@ -3,24 +3,28 @@ using Soup;
|
||||
|
||||
public class Tootle.Notificator : GLib.Object {
|
||||
|
||||
weak Account account;
|
||||
WebsocketConnection? connection;
|
||||
Soup.Message msg;
|
||||
|
||||
public Notificator (Account acc){
|
||||
public abstract signal void notification (ref Notification notification);
|
||||
public abstract signal void status_added (ref Status status);
|
||||
public abstract signal void status_removed (int64 id);
|
||||
|
||||
public Notificator (Soup.Message msg){
|
||||
Object ();
|
||||
account = acc;
|
||||
this.msg = msg;
|
||||
this.msg.priority = Soup.MessagePriority.VERY_HIGH;
|
||||
}
|
||||
|
||||
public async void start () {
|
||||
var msg = account.get_stream ();
|
||||
debug ("Starting notificator");
|
||||
connection = yield Tootle.network.stream (msg);
|
||||
connection.error.connect (on_error);
|
||||
connection.message.connect (on_message);
|
||||
debug ("Receiving notifications for %lld", account.id);
|
||||
}
|
||||
|
||||
public void close () {
|
||||
debug ("Closing notifications for %lld", account.id);
|
||||
debug ("Stopping notificator");
|
||||
connection.close (0, null);
|
||||
}
|
||||
|
||||
@ -29,7 +33,6 @@ public class Tootle.Notificator : GLib.Object {
|
||||
}
|
||||
|
||||
private void on_message (int i, Bytes bytes) {
|
||||
var network = Tootle.network;
|
||||
var msg = (string) bytes.get_data ();
|
||||
|
||||
var parser = new Json.Parser ();
|
||||
@ -39,32 +42,21 @@ public class Tootle.Notificator : GLib.Object {
|
||||
var type = root.get_string_member ("event");
|
||||
switch (type) {
|
||||
case "update":
|
||||
if (Tootle.settings.live_updates) {
|
||||
var status = Status.parse (sanitize (root));
|
||||
network.status_added (ref status, "home");
|
||||
}
|
||||
else
|
||||
Tootle.app.toast ("New post available");
|
||||
|
||||
var status = Status.parse (sanitize (root));
|
||||
status_added (ref status);
|
||||
break;
|
||||
case "delete":
|
||||
if (Tootle.settings.live_updates) {
|
||||
var id = int64.parse (root.get_string_member("payload"));
|
||||
network.status_removed (id);
|
||||
}
|
||||
|
||||
var id = int64.parse (root.get_string_member("payload"));
|
||||
status_removed (id);
|
||||
break;
|
||||
case "notification":
|
||||
var notif = Notification.parse (sanitize (root));
|
||||
toast (notif);
|
||||
network.notification (ref notif);
|
||||
|
||||
notification (ref notif);
|
||||
break;
|
||||
default:
|
||||
warning ("Unknown push event: %s", type);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Json.Object sanitize (Json.Object root) {
|
||||
@ -75,12 +67,4 @@ public class Tootle.Notificator : GLib.Object {
|
||||
return parser.get_root ().get_object ();
|
||||
}
|
||||
|
||||
private void toast (Notification obj) {
|
||||
var title = Utils.escape_html (obj.type.get_desc (obj.account));
|
||||
var notification = new GLib.Notification (title);
|
||||
if (obj.status != null)
|
||||
notification.set_body (Utils.escape_html (obj.status.content));
|
||||
Tootle.app.send_notification (Tootle.app.application_id + ":" + obj.id.to_string (), notification);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,25 +1,12 @@
|
||||
public class Tootle.SettingsManager : Granite.Services.Settings {
|
||||
|
||||
public string client_id { get; set; }
|
||||
public string client_secret { get; set; }
|
||||
public string access_token { get; set; }
|
||||
public string refresh_token { get; set; }
|
||||
public string instance_url { get; set; }
|
||||
|
||||
public int current_account { get; set; }
|
||||
public bool always_online { get; set; }
|
||||
public bool cache { get; set; }
|
||||
public int cache_size { get; set; }
|
||||
public bool live_updates { get; set; }
|
||||
public bool dark_theme { get; set; }
|
||||
|
||||
public void clear_account (){
|
||||
access_token = "null";
|
||||
refresh_token = "null";
|
||||
instance_url = "null";
|
||||
client_id = "null";
|
||||
client_secret = "null";
|
||||
debug ("Removed current account");
|
||||
}
|
||||
|
||||
public SettingsManager () {
|
||||
base ("com.github.bleakgrey.tootle");
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ using Gtk;
|
||||
|
||||
public abstract class Tootle.AbstractView : Gtk.ScrolledWindow {
|
||||
|
||||
public int stack_pos = -1;
|
||||
public Gtk.Image? image;
|
||||
public Gtk.Box view;
|
||||
protected Gtk.Box? empty;
|
||||
|
@ -209,7 +209,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/accounts/%lld/statuses?limit=%i".printf (Tootle.settings.instance_url, account.id, this.limit);
|
||||
var url = "%s/api/v1/accounts/%lld/statuses?limit=%i".printf (Tootle.accounts.formal.instance, account.id, this.limit);
|
||||
return url;
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
}
|
||||
|
||||
public static void open_from_id (int64 id){
|
||||
var url = "%s/api/v1/accounts/%lld".printf (Tootle.settings.instance_url, id);
|
||||
var url = "%s/api/v1/accounts/%lld".printf (Tootle.accounts.formal.instance, id);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
@ -249,7 +249,7 @@ public class Tootle.AccountView : TimelineView {
|
||||
}
|
||||
|
||||
public static void open_from_name (string name){
|
||||
var url = "%s/api/v1/accounts/search?limit=1&q=%s".printf (Tootle.settings.instance_url, name);
|
||||
var url = "%s/api/v1/accounts/search?limit=1&q=%s".printf (Tootle.accounts.formal.instance, name);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
msg.priority = Soup.MessagePriority.HIGH;
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
|
@ -1,146 +0,0 @@
|
||||
using Gtk;
|
||||
using Granite;
|
||||
|
||||
public class Tootle.AddAccountView : Tootle.AbstractView {
|
||||
|
||||
public Stack stack;
|
||||
GridInstance grid_instance;
|
||||
GridCode grid_code;
|
||||
|
||||
protected class GridInstance : Grid{
|
||||
Image image;
|
||||
public Button button_next;
|
||||
public Entry entry;
|
||||
|
||||
construct{
|
||||
column_spacing = 12;
|
||||
row_spacing = 6;
|
||||
hexpand = true;
|
||||
halign = Gtk.Align.CENTER;
|
||||
|
||||
image = new Image.from_resource ("/com/github/bleakgrey/tootle/logo128");
|
||||
image.halign = Gtk.Align.CENTER;
|
||||
image.hexpand = true;
|
||||
image.margin_bottom = 24;
|
||||
|
||||
entry = new Entry ();
|
||||
entry.text = "https://myinstance.com";
|
||||
entry.set_placeholder_text ("https://myinstance.com");
|
||||
entry.width_chars = 30;
|
||||
|
||||
button_next = new Button.with_label ("Next");
|
||||
button_next.halign = Gtk.Align.END;
|
||||
|
||||
var register = new Label ("<a href=\"https://joinmastodon.org/\">%s</a>".printf (_("What's an instance?")));
|
||||
register.halign = Gtk.Align.END;
|
||||
register.set_use_markup (true);
|
||||
|
||||
attach (image, 0, 1, 2, 1);
|
||||
attach (new AlignedLabel (_("Instance:")), 0, 2, 1, 1);
|
||||
attach (entry, 1, 2, 1, 1);
|
||||
attach (button_next, 0, 3, 2, 1);
|
||||
attach (register, 0, 4, 2, 1);
|
||||
}
|
||||
|
||||
public GridInstance(){}
|
||||
}
|
||||
|
||||
protected class GridCode : Grid{
|
||||
Granite.Widgets.Avatar image;
|
||||
public Button button_back;
|
||||
public Button button_next;
|
||||
public Entry code;
|
||||
|
||||
construct{
|
||||
column_spacing = 12;
|
||||
row_spacing = 6;
|
||||
hexpand = true;
|
||||
halign = Gtk.Align.CENTER;
|
||||
valign = Gtk.Align.CENTER;
|
||||
|
||||
image = new Granite.Widgets.Avatar.with_default_icon (128);
|
||||
image.halign = Gtk.Align.CENTER;
|
||||
image.hexpand = true;
|
||||
image.margin_bottom = 24;
|
||||
|
||||
code = new Entry ();
|
||||
code.width_chars = 30;
|
||||
|
||||
button_next = new Button.with_label (_("Add Account"));
|
||||
button_next.halign = Gtk.Align.END;
|
||||
|
||||
button_back = new Button.with_label (_("Back"));
|
||||
button_back.halign = Gtk.Align.START;
|
||||
|
||||
attach (image, 0, 1, 2, 1);
|
||||
attach (new AlignedLabel (_("Authorization Code:")), 0, 2, 1, 1);
|
||||
attach (code, 1, 2, 1, 1);
|
||||
attach (button_back, 0, 3, 1, 1);
|
||||
attach (button_next, 1, 3, 1, 1);
|
||||
}
|
||||
|
||||
public GridCode(){}
|
||||
}
|
||||
|
||||
|
||||
|
||||
construct {
|
||||
view.valign = Gtk.Align.CENTER;
|
||||
|
||||
stack = new Stack ();
|
||||
stack.valign = Gtk.Align.CENTER;
|
||||
stack.vexpand = true;
|
||||
stack.transition_type = StackTransitionType.SLIDE_LEFT_RIGHT;
|
||||
|
||||
grid_instance = new GridInstance ();
|
||||
grid_instance.button_next.clicked.connect(on_next_click);
|
||||
|
||||
grid_code = new GridCode ();
|
||||
grid_code.button_back.clicked.connect(() => stack.set_visible_child_name ("instance"));
|
||||
grid_code.button_next.clicked.connect(on_add_click);
|
||||
|
||||
var header1 = new Gtk.Label (_("Enter Your Instance URL:"));
|
||||
header1.get_style_context ().add_class (Granite.STYLE_CLASS_H2_LABEL);
|
||||
header1.halign = Gtk.Align.CENTER;
|
||||
header1.hexpand = true;
|
||||
|
||||
stack.add_named (grid_instance, "instance");
|
||||
stack.add_named (grid_code, "code");
|
||||
|
||||
view.add (stack);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public AddAccountView () {
|
||||
base ();
|
||||
}
|
||||
|
||||
public override string get_name () {
|
||||
return "add_account";
|
||||
}
|
||||
|
||||
private void on_next_click(){
|
||||
Tootle.settings.clear_account ();
|
||||
Tootle.settings.instance_url = grid_instance.entry.text;
|
||||
grid_instance.sensitive = false;
|
||||
|
||||
if(!Tootle.accounts.has_client_tokens ()){
|
||||
var msg = Tootle.accounts.request_client_tokens ();
|
||||
msg.finished.connect(() => {
|
||||
grid_instance.sensitive = true;
|
||||
stack.set_visible_child_name ("code");
|
||||
});
|
||||
}
|
||||
else{
|
||||
grid_instance.sensitive = true;
|
||||
stack.set_visible_child_name ("code");
|
||||
Tootle.accounts.request_auth_code (Tootle.settings.client_id);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_add_click (){
|
||||
var code = grid_code.code.text;
|
||||
Tootle.accounts.try_auth (code);
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,7 @@ public class Tootle.FavoritesView : TimelineView {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/favourites/?limit=%i".printf (Tootle.settings.instance_url, this.limit);
|
||||
var url = "%s/api/v1/favourites/?limit=%i".printf (Tootle.accounts.formal.instance, this.limit);
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ public class Tootle.FollowersView : TimelineView {
|
||||
base (account.id.to_string ());
|
||||
}
|
||||
|
||||
public new void prepend (ref Account account){
|
||||
public new void append (ref Account account){
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
|
||||
@ -23,7 +23,7 @@ public class Tootle.FollowersView : TimelineView {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/accounts/%s/followers".printf (Tootle.settings.instance_url, this.timeline);
|
||||
var url = "%s/api/v1/accounts/%s/followers".printf (Tootle.accounts.formal.instance, this.timeline);
|
||||
return url;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ public class Tootle.FollowersView : TimelineView {
|
||||
var object = node.get_object ();
|
||||
if (object != null){
|
||||
var status = Account.parse (object);
|
||||
prepend (ref status);
|
||||
append (ref status);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,7 @@ public class Tootle.FollowingView : FollowersView {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/accounts/%s/following".printf (Tootle.settings.instance_url, this.timeline);
|
||||
var url = "%s/api/v1/accounts/%s/following".printf (Tootle.accounts.formal.instance, this.timeline);
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ public class Tootle.LocalView : TimelineView {
|
||||
}
|
||||
|
||||
public override string get_icon () {
|
||||
return "folder-recent-symbolic";
|
||||
return "document-open-recent-symbolic";
|
||||
}
|
||||
|
||||
public override string get_name () {
|
||||
|
@ -73,7 +73,7 @@ public class Tootle.NotificationsView : AbstractView {
|
||||
}
|
||||
|
||||
public void request () {
|
||||
var url = "%s/api/v1/follow_requests".printf (Tootle.settings.instance_url);
|
||||
var url = "%s/api/v1/follow_requests".printf (Tootle.accounts.formal.instance);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
@ -91,7 +91,7 @@ public class Tootle.NotificationsView : AbstractView {
|
||||
}
|
||||
});
|
||||
|
||||
var url2 = "%s/api/v1/notifications?limit=30".printf (Tootle.settings.instance_url);
|
||||
var url2 = "%s/api/v1/notifications?limit=30".printf (Tootle.accounts.formal.instance);
|
||||
var msg2 = new Soup.Message("GET", url2);
|
||||
Tootle.network.queue(msg2, (sess, mess) => {
|
||||
try{
|
||||
|
@ -2,7 +2,8 @@ using Gtk;
|
||||
|
||||
public class Tootle.SearchView : AbstractView {
|
||||
|
||||
Gtk.Entry entry;
|
||||
private string query = "";
|
||||
private Gtk.Entry entry;
|
||||
|
||||
construct {
|
||||
view.margin_bottom = 6;
|
||||
@ -11,6 +12,7 @@ public class Tootle.SearchView : AbstractView {
|
||||
entry.placeholder_text = _("Search");
|
||||
entry.secondary_icon_name = "system-search-symbolic";
|
||||
entry.width_chars = 25;
|
||||
entry.text = query;
|
||||
entry.show ();
|
||||
Tootle.window.header.pack_start (entry);
|
||||
|
||||
@ -45,7 +47,7 @@ public class Tootle.SearchView : AbstractView {
|
||||
}
|
||||
|
||||
private void append_hashtag (string name) {
|
||||
var text = "<a href=\"%s/tags/%s\">#%s</a>".printf (Tootle.settings.instance_url, Soup.URI.encode (name, null), name);
|
||||
var text = "<a href=\"%s/tags/%s\">#%s</a>".printf (Tootle.accounts.formal.instance, Soup.URI.encode (name, null), name);
|
||||
var widget = new RichLabel (text);
|
||||
widget.use_markup = true;
|
||||
widget.halign = Gtk.Align.START;
|
||||
@ -56,14 +58,15 @@ public class Tootle.SearchView : AbstractView {
|
||||
}
|
||||
|
||||
private void request () {
|
||||
if (entry.text == "") {
|
||||
query = entry.text;
|
||||
if (query == "") {
|
||||
clear ();
|
||||
return;
|
||||
}
|
||||
Tootle.window.reopen_view (this.stack_pos);
|
||||
|
||||
var query = Soup.URI.encode (entry.text, null);
|
||||
var url = "%s/api/v1/search?q=%s".printf (Tootle.settings.instance_url, query);
|
||||
|
||||
var query_encoded = Soup.URI.encode (query, null);
|
||||
var url = "%s/api/v1/search?q=%s".printf (Tootle.accounts.formal.instance, query_encoded);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
@ -100,7 +103,6 @@ public class Tootle.SearchView : AbstractView {
|
||||
}
|
||||
|
||||
empty_state ();
|
||||
|
||||
}
|
||||
catch (GLib.Error e) {
|
||||
warning ("Can't update feed");
|
||||
|
@ -31,7 +31,7 @@ public class Tootle.StatusView : AbstractView {
|
||||
}
|
||||
|
||||
public Soup.Message request_context (){
|
||||
var url = "%s/api/v1/statuses/%lld/context".printf (Tootle.settings.instance_url, root_status.id);
|
||||
var url = "%s/api/v1/statuses/%lld/context".printf (Tootle.accounts.formal.instance, root_status.id);
|
||||
var msg = new Soup.Message("GET", url);
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
try{
|
||||
|
@ -35,14 +35,18 @@ public class Tootle.TimelineView : AbstractView {
|
||||
if (timeline != this.timeline)
|
||||
return;
|
||||
|
||||
prepend (ref status, true);
|
||||
prepend (ref status);
|
||||
}
|
||||
|
||||
public virtual bool is_status_owned (ref Status status) {
|
||||
return status.is_owned ();
|
||||
}
|
||||
|
||||
public void prepend (ref Status status, bool first = false){
|
||||
public void prepend (ref Status status) {
|
||||
append (ref status, true);
|
||||
}
|
||||
|
||||
public void append (ref Status status, bool first = false){
|
||||
if (empty != null)
|
||||
empty.destroy ();
|
||||
|
||||
@ -95,12 +99,17 @@ public class Tootle.TimelineView : AbstractView {
|
||||
if (page_next != null)
|
||||
return page_next;
|
||||
|
||||
var url = "%s/api/v1/timelines/%s?limit=%i".printf (Tootle.settings.instance_url, this.timeline, this.limit);
|
||||
var url = "%s/api/v1/timelines/%s?limit=%i".printf (Tootle.accounts.formal.instance, this.timeline, this.limit);
|
||||
url += this.pars;
|
||||
return url;
|
||||
}
|
||||
|
||||
public virtual void request (){
|
||||
if (accounts.current == null) {
|
||||
empty_state ();
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = new Soup.Message("GET", get_url ());
|
||||
msg.finished.connect (() => empty_state ());
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
@ -109,7 +118,7 @@ public class Tootle.TimelineView : AbstractView {
|
||||
var object = node.get_object ();
|
||||
if (object != null){
|
||||
var status = Status.parse(object);
|
||||
prepend (ref status);
|
||||
append (ref status);
|
||||
}
|
||||
});
|
||||
get_pages (mess.response_headers.get_one ("Link"));
|
||||
|
@ -5,40 +5,47 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||
Granite.Widgets.Avatar avatar;
|
||||
Gtk.Grid grid;
|
||||
Gtk.Popover menu;
|
||||
AccountView default_account;
|
||||
Gtk.ListBox list;
|
||||
Gtk.ModelButton item_settings;
|
||||
Gtk.ModelButton item_refresh;
|
||||
Gtk.ModelButton item_search;
|
||||
Gtk.ModelButton item_favs;
|
||||
|
||||
private class AccountView : Gtk.Grid{
|
||||
private class AccountView : Gtk.ListBoxRow{
|
||||
|
||||
private Gtk.Grid grid;
|
||||
public Gtk.Label display_name;
|
||||
public Gtk.Label user;
|
||||
public Gtk.Button logout;
|
||||
|
||||
construct {
|
||||
margin = 6;
|
||||
margin_start = 14;
|
||||
public Gtk.Label instance;
|
||||
public Gtk.Button button;
|
||||
public int id = -1;
|
||||
|
||||
display_name = new Gtk.Label ("<b>Anonymous</b>");
|
||||
construct {
|
||||
can_default = false;
|
||||
|
||||
grid = new Gtk.Grid ();
|
||||
grid.margin = 6;
|
||||
grid.margin_start = 14;
|
||||
|
||||
display_name = new Gtk.Label ("");
|
||||
display_name.hexpand = true;
|
||||
display_name.halign = Gtk.Align.START;
|
||||
display_name.use_markup = true;
|
||||
user = new Gtk.Label ("@error");
|
||||
user.halign = Gtk.Align.START;
|
||||
logout = new Gtk.Button.from_icon_name ("pane-hide-symbolic", Gtk.IconSize.SMALL_TOOLBAR);
|
||||
logout.receives_default = false;
|
||||
logout.tooltip_text = _("Log out");
|
||||
logout.clicked.connect (() => Tootle.accounts.logout ());
|
||||
show_all ();
|
||||
instance = new Gtk.Label ("");
|
||||
instance.halign = Gtk.Align.START;
|
||||
button = new Gtk.Button.from_icon_name ("close-symbolic", Gtk.IconSize.SMALL_TOOLBAR);
|
||||
button.receives_default = false;
|
||||
button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT);
|
||||
|
||||
attach(display_name, 1, 0, 1, 1);
|
||||
attach(user, 1, 1, 1, 1);
|
||||
attach(logout, 2, 0, 2, 2);
|
||||
grid.attach(display_name, 1, 0, 1, 1);
|
||||
grid.attach(instance, 1, 1, 1, 1);
|
||||
grid.attach(button, 2, 0, 2, 2);
|
||||
add (grid);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
public AccountView (){}
|
||||
public AccountView (){
|
||||
button.clicked.connect (() => Tootle.accounts.remove (id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -48,7 +55,7 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||
return false;
|
||||
});
|
||||
|
||||
default_account = new AccountView ();
|
||||
list = new Gtk.ListBox ();
|
||||
|
||||
var item_separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL);
|
||||
item_separator.hexpand = true;
|
||||
@ -72,12 +79,12 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||
grid = new Gtk.Grid ();
|
||||
grid.orientation = Gtk.Orientation.VERTICAL;
|
||||
grid.width_request = 200;
|
||||
grid.attach(default_account, 0, 1, 1, 1);
|
||||
grid.attach(item_separator, 0, 2, 1, 1);
|
||||
grid.attach(item_favs, 0, 3, 1, 1);
|
||||
grid.attach(new Gtk.Separator (Gtk.Orientation.HORIZONTAL), 0, 4, 1, 1);
|
||||
grid.attach(item_refresh, 0, 5, 1, 1);
|
||||
grid.attach(item_search, 0, 6, 1, 1);
|
||||
grid.attach(list, 0, 1, 1, 1);
|
||||
grid.attach(item_separator, 0, 3, 1, 1);
|
||||
grid.attach(item_favs, 0, 4, 1, 1);
|
||||
grid.attach(new Gtk.Separator (Gtk.Orientation.HORIZONTAL), 0, 5, 1, 1);
|
||||
grid.attach(item_refresh, 0, 6, 1, 1);
|
||||
grid.attach(item_search, 0, 7, 1, 1);
|
||||
grid.attach(item_settings, 0, 8, 1, 1);
|
||||
grid.show_all ();
|
||||
|
||||
@ -89,17 +96,55 @@ public class Tootle.AccountsButton : Gtk.MenuButton{
|
||||
add(avatar);
|
||||
show_all ();
|
||||
|
||||
Tootle.accounts.switched.connect (account => {
|
||||
if (account != null){
|
||||
Tootle.image_cache.load_avatar (account.avatar, avatar, 24);
|
||||
default_account.display_name.label = "<b>"+account.display_name+"</b>";
|
||||
default_account.user.label = "@"+account.username;
|
||||
accounts.updated.connect (accounts_updated);
|
||||
accounts.switched.connect (account_switched);
|
||||
list.row_activated.connect (row => {
|
||||
var widget = row as AccountView;
|
||||
if (widget.id == -1) {
|
||||
NewAccountDialog.open ();
|
||||
return;
|
||||
}
|
||||
if (widget.id == Tootle.settings.current_account)
|
||||
return;
|
||||
else
|
||||
Tootle.accounts.switch_account (widget.id);
|
||||
});
|
||||
}
|
||||
|
||||
public AccountsButton(){
|
||||
Object();
|
||||
|
||||
private void accounts_updated (GenericArray<InstanceAccount> accounts) {
|
||||
list.forall (widget => widget.destroy ());
|
||||
int i = -1;
|
||||
accounts.foreach (account => {
|
||||
i++;
|
||||
var widget = new AccountView ();
|
||||
widget.id = i;
|
||||
widget.display_name.label = "<b>@"+account.username+"</b>";
|
||||
widget.instance.label = account.instance;
|
||||
list.add (widget);
|
||||
});
|
||||
|
||||
var add_account = new AccountView ();
|
||||
add_account.display_name.label = _("<b>New Account</b>");
|
||||
add_account.instance.label = _("Click to add");
|
||||
add_account.button.hide ();
|
||||
list.add (add_account);
|
||||
update_selection ();
|
||||
}
|
||||
|
||||
private void account_switched (Account? account) {
|
||||
if (account == null)
|
||||
avatar.show_default (24);
|
||||
else
|
||||
image_cache.load_avatar (account.avatar, avatar, 24);
|
||||
}
|
||||
|
||||
private void update_selection () {
|
||||
var id = Tootle.settings.current_account;
|
||||
var row = list.get_row_at_index (id);
|
||||
if (row != null)
|
||||
list.select_row (row);
|
||||
}
|
||||
|
||||
public AccountsButton() {}
|
||||
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ public class Tootle.AttachmentWidget : Gtk.EventBox {
|
||||
var buffer = new Soup.Buffer.take (contents);
|
||||
var multipart = new Soup.Multipart (Soup.FORM_MIME_TYPE_MULTIPART);
|
||||
multipart.append_form_file ("file", mime.replace ("/", "."), mime, buffer);
|
||||
var url = "%s/api/v1/media".printf (Tootle.settings.instance_url);
|
||||
var url = "%s/api/v1/media".printf (Tootle.accounts.formal.instance);
|
||||
var msg = Soup.Form.request_new_from_multipart (url, multipart);
|
||||
|
||||
Tootle.network.queue(msg, (sess, mess) => {
|
||||
|
@ -1,82 +0,0 @@
|
||||
using Gtk;
|
||||
|
||||
public class Tootle.HeaderBar : Gtk.HeaderBar{
|
||||
|
||||
public Granite.Widgets.ModeButton button_mode;
|
||||
AccountsButton button_accounts;
|
||||
Spinner spinner;
|
||||
Button button_toot;
|
||||
Button button_back;
|
||||
|
||||
private int last_tab = 0;
|
||||
|
||||
construct {
|
||||
spinner = new Spinner ();
|
||||
spinner.active = true;
|
||||
|
||||
button_accounts = new AccountsButton ();
|
||||
|
||||
button_back = new Button ();
|
||||
button_back.label = _("Back");
|
||||
button_back.get_style_context ().add_class (Granite.STYLE_CLASS_BACK_BUTTON);
|
||||
button_back.clicked.connect (() => {
|
||||
var primary_stack = Tootle.window.primary_stack;
|
||||
var i = int.parse (primary_stack.get_visible_child_name ());
|
||||
primary_stack.set_visible_child_name ((i-1).to_string ());
|
||||
|
||||
var child = primary_stack.get_child_by_name (i.to_string ());
|
||||
child.destroy ();
|
||||
|
||||
var is_root = primary_stack.get_visible_child_name () == "0";
|
||||
update (is_root);
|
||||
});
|
||||
|
||||
button_toot = new Button ();
|
||||
button_toot.tooltip_text = "Toot";
|
||||
button_toot.image = new Gtk.Image.from_icon_name ("edit-symbolic", Gtk.IconSize.LARGE_TOOLBAR);
|
||||
button_toot.clicked.connect (() => PostDialog.open ());
|
||||
|
||||
button_mode = new Granite.Widgets.ModeButton ();
|
||||
button_mode.get_style_context ().add_class ("mode");
|
||||
button_mode.mode_changed.connect(widget => {
|
||||
last_tab = button_mode.selected;
|
||||
Tootle.window.secondary_stack.set_visible_child_name(widget.tooltip_text);
|
||||
});
|
||||
button_mode.show ();
|
||||
|
||||
Tootle.network.started.connect (() => spinner.show ());
|
||||
Tootle.network.finished.connect (() => spinner.hide ());
|
||||
|
||||
pack_start (button_back);
|
||||
pack_start (button_toot);
|
||||
pack_end (button_accounts);
|
||||
pack_end (spinner);
|
||||
}
|
||||
|
||||
public HeaderBar () {
|
||||
custom_title = button_mode;
|
||||
show_close_button = true;
|
||||
show ();
|
||||
button_mode.valign = Gtk.Align.FILL;
|
||||
}
|
||||
|
||||
public void update (bool primary_mode, bool hide_all = false){
|
||||
button_mode.set_active (last_tab);
|
||||
if (hide_all){
|
||||
//button_mode.opacity = 0;
|
||||
//button_mode.sensitive = false;
|
||||
button_mode.hide ();
|
||||
button_toot.hide ();
|
||||
button_back.hide ();
|
||||
button_accounts.hide ();
|
||||
return;
|
||||
}
|
||||
//button_mode.opacity = primary_mode ? 1 : 0;
|
||||
//button_mode.sensitive = primary_mode ? true : false;
|
||||
button_mode.set_visible (primary_mode);
|
||||
button_toot.set_visible (primary_mode);
|
||||
button_back.set_visible (!primary_mode);
|
||||
button_accounts.set_visible (true);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user