diff --git a/src/API/Account.vala b/src/API/Account.vala index 52e3dfa..a2f0373 100644 --- a/src/API/Account.vala +++ b/src/API/Account.vala @@ -1,13 +1,27 @@ public class Tootle.Account{ - public int id = -1; + public int64 id; public string username; - public string username_acct; + public string acct; public string display_name; public string note; + public string avatar; - public Account(){ + public Account(int64 id){ + this.id = id; + } + + public static Account parse(Json.Object obj) { + var id = int64.parse (obj.get_string_member ("id")); + var account = new Account (id); + account.username = obj.get_string_member ("username"); + account.acct = obj.get_string_member ("acct"); + account.display_name = obj.get_string_member ("display_name"); + account.note = obj.get_string_member ("note"); + account.avatar = obj.get_string_member ("avatar"); + + return account; } } diff --git a/src/API/Account.vala~ b/src/API/Account.vala~ new file mode 100644 index 0000000..52e3dfa --- /dev/null +++ b/src/API/Account.vala~ @@ -0,0 +1,13 @@ +public class Tootle.Account{ + + public int id = -1; + public string username; + public string username_acct; + public string display_name; + public string note; + + public Account(){ + + } + +} diff --git a/src/AccountManager.vala b/src/AccountManager.vala index e18f84c..3ef8208 100644 --- a/src/AccountManager.vala +++ b/src/AccountManager.vala @@ -2,6 +2,11 @@ using GLib; public class Tootle.AccountManager : Object{ + public abstract signal void changed_current(Account account); + public abstract signal void added(Account account); + public abstract signal void removed(Account account); + + private static Account current; private static Settings settings; private static AccountManager _instance; public static AccountManager instance{ @@ -93,5 +98,21 @@ public class Tootle.AccountManager : Object{ }); return msg; } + + public Soup.Message update_current (){ + var msg = new Soup.Message("GET", settings.instance_url + "/api/v1/accounts/verify_credentials"); + NetManager.instance.queue(msg, (sess, mess) => { + try{ + var root = NetManager.parse (mess); + current = Account.parse(root); + changed_current (current); + } + catch (GLib.Error e) { + warning ("Can't get current user"); + warning (e.message); + } + }); + return msg; + } } diff --git a/src/AccountManager.vala~ b/src/AccountManager.vala~ new file mode 100644 index 0000000..e18f84c --- /dev/null +++ b/src/AccountManager.vala~ @@ -0,0 +1,97 @@ +using GLib; + +public class Tootle.AccountManager : Object{ + + private static Settings settings; + private static AccountManager _instance; + public static AccountManager instance{ + get{ + if(_instance == null) + _instance = new AccountManager(); + return _instance; + } + } + + construct{ + settings = Settings.instance; + } + + public AccountManager(){ + Object(); + } + + public bool has_client_tokens(){ + var client_id = settings.client_id; + var client_secret = settings.client_secret; + + return !(client_id == "null" || client_secret == "null"); + } + + public bool has_access_token (){ + return settings.access_token != "null"; + } + + 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; + + try { + AppInfo.launch_default_for_uri (settings.instance_url + "/oauth/authorize" + pars, null); + } + 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 += "&scopes=read%20write%20follow"; + + var msg = new Soup.Message("POST", settings.instance_url + "/api/v1/apps" + pars); + NetManager.instance.queue(msg, (sess, mess) => { + try{ + var root = NetManager.parse (mess); + var client_id = root.get_string_member ("client_id"); + var client_secret = root.get_string_member ("client_secret"); + settings.client_id = client_id; + 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=" + settings.client_id; + pars += "&client_secret=" + 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", settings.instance_url + "/oauth/token" + pars); + NetManager.instance.queue(msg, (sess, mess) => { + try{ + var root = NetManager.parse (mess); + var access_token = root.get_string_member ("access_token"); + settings.access_token = access_token; + debug ("Got access token"); + Tootle.app.state_updated (); + } + catch (GLib.Error e) { + warning ("Can't get access token"); + warning (e.message); + } + }); + return msg; + } + +} diff --git a/src/CacheManager.vala b/src/CacheManager.vala index da78a40..75bb777 100644 --- a/src/CacheManager.vala +++ b/src/CacheManager.vala @@ -22,12 +22,13 @@ public class Tootle.CacheManager : GLib.Object{ Object (); } - public void load_image (string url, Granite.Widgets.Avatar avatar){ + //TODO: actually cache images + public void load_avatar (string url, Granite.Widgets.Avatar avatar, int size = 32){ var msg = new Soup.Message("GET", url); msg.finished.connect(()=>{ uint8[] buf = msg.response_body.data; var loader = new PixbufLoader(); - loader.set_size (32,32); + loader.set_size (size, size); loader.write(buf); loader.close(); var pixbuf = loader.get_pixbuf (); diff --git a/src/CacheManager.vala~ b/src/CacheManager.vala~ new file mode 100644 index 0000000..e7db808 --- /dev/null +++ b/src/CacheManager.vala~ @@ -0,0 +1,41 @@ +using Gdk; +using GLib; + +public class Tootle.CacheManager : GLib.Object{ + + private static CacheManager _instance; + public static CacheManager instance{ + get{ + if(_instance == null) + _instance = new CacheManager(); + return _instance; + } + } + + private static string path_images; + + construct{ + path_images = GLib.Environment.get_user_special_dir (UserDirectory.DOWNLOAD); + } + + public CacheManager(){ + Object (); + } + + //TODO: actually cache images + public void load_image (string url, Granite.Widgets.Avatar avatar){ + var msg = new Soup.Message("GET", url); + msg.finished.connect(()=>{ + uint8[] buf = msg.response_body.data; + var loader = new PixbufLoader(); + loader.set_size (32,32); + loader.write(buf); + loader.close(); + var pixbuf = loader.get_pixbuf (); + + avatar.pixbuf = pixbuf; + }); + NetManager.instance.queue(msg, (sess, mess) => {}); + } + +} diff --git a/src/MainWindow.vala b/src/MainWindow.vala index c6d78c2..7781cce 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -83,6 +83,8 @@ public class Tootle.MainWindow: Gtk.Window { show_setup_views (); else show_main_views (); + + AccountManager.instance.update_current (); } private void show_setup_views (){ diff --git a/src/NetManager.vala b/src/NetManager.vala index 3cc8adb..4e004b5 100644 --- a/src/NetManager.vala +++ b/src/NetManager.vala @@ -46,7 +46,7 @@ public class Tootle.NetManager : GLib.Object{ public static Json.Object parse(Soup.Message msg) throws GLib.Error{ // stdout.printf ("Status Code: %u\n", msg.status_code); - // stdout.printf ("Message length: %lld\n", msg.response_body.length); + // stdout.printf ("Message length: %lld\n", msg.response_body.length); // stdout.printf ("Data: \n%s\n", (string) msg.response_body.data); var parser = new Json.Parser (); diff --git a/src/NetManager.vala~ b/src/NetManager.vala~ new file mode 100644 index 0000000..cc89de1 --- /dev/null +++ b/src/NetManager.vala~ @@ -0,0 +1,67 @@ +using Soup; +using GLib; +using Json; + +public class Tootle.NetManager : GLib.Object{ + + public abstract signal void started(); + public abstract signal void finished(); + + private static NetManager _instance; + public static NetManager instance{ + get{ + if(_instance == null) + _instance = new NetManager(); + return _instance; + } + } + + private int requests_processing = 0; + private Soup.Session session; + + construct{ + session = new Soup.Session (); + session.request_unqueued.connect (() => { + requests_processing--; + if(requests_processing <= 0) + finished (); + }); + } + + public NetManager(){ + GLib.Object(); + } + + public Soup.Message queue(Soup.Message msg, Soup.SessionCallback cb){ + requests_processing++; + started (); + + var token = Settings.instance.access_token; + if(token != "null") + msg.request_headers.append ("Authorization", "Bearer " + token); + + session.queue_message (msg, cb); + return msg; + } + + public static Json.Object parse(Soup.Message msg) throws GLib.Error{ + stdout.printf ("Status Code: %u\n", msg.status_code); + stdout.printf ("Message length: %lld\n", msg.response_body.length); + stdout.printf ("Data: \n%s\n", (string) msg.response_body.data); + + var parser = new Json.Parser (); + parser.load_from_data ((string) msg.response_body.flatten ().data, -1); + return parser.get_root ().get_object (); + } + + public static Json.Array parse_array(Soup.Message msg) throws GLib.Error{ + // stdout.printf ("Status Code: %u\n", msg.status_code); + // stdout.printf ("Message length: %lld\n", msg.response_body.length); + // stdout.printf ("Data: \n%s\n", (string) msg.response_body.data); + + var parser = new Json.Parser (); + parser.load_from_data ((string) msg.response_body.flatten ().data, -1); + return parser.get_root ().get_array (); + } + +} diff --git a/src/Views/HomeView.vala b/src/Views/HomeView.vala index d92d4df..92c7481 100644 --- a/src/Views/HomeView.vala +++ b/src/Views/HomeView.vala @@ -26,9 +26,9 @@ public class Tootle.HomeView : Tootle.AbstractView { base (true); this.timeline = timeline; this.pars = pars; - request_update (); show_all(); + request_update (); // var s = new Status(1); // s.content = "Test content, wow!"; diff --git a/src/Views/HomeView.vala~ b/src/Views/HomeView.vala~ new file mode 100644 index 0000000..d92d4df --- /dev/null +++ b/src/Views/HomeView.vala~ @@ -0,0 +1,77 @@ +using Gtk; +using Gdk; + +public class Tootle.HomeView : Tootle.AbstractView { + + Gtk.Box view; + Gtk.ScrolledWindow scroll; + + private string timeline; + private string pars; + + construct { + view = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + view.hexpand = true; + view.valign = Gtk.Align.START; + + scroll = new Gtk.ScrolledWindow (null, null); + scroll.hexpand = true; + scroll.vexpand = true; + scroll.hscrollbar_policy = Gtk.PolicyType.NEVER; + scroll.add (view); + add (scroll); + } + + public HomeView (string timeline = "home", string pars = "") { + base (true); + this.timeline = timeline; + this.pars = pars; + request_update (); + + show_all(); + + // var s = new Status(1); + // s.content = "Test content, wow!"; + // prepend (s); + } + + public override string get_icon () { + return "user-home-symbolic"; + } + + public override string get_name () { + return "Home Timeline"; + } + + public void prepend(Status status){ + var widget = new StatusWidget(); + widget.rebind (status); + view.pack_end (widget, false, false, 0); + } + + public virtual Soup.Message request_update (){ + var url = Settings.instance.instance_url; + url += "api/v1/timelines/"; + url += this.timeline; + url += this.pars; + + var msg = new Soup.Message("GET", url); + NetManager.instance.queue(msg, (sess, mess) => { + try{ + NetManager.parse_array (mess).foreach_element ((array, i, node) => { + var object = node.get_object (); + if (object != null){ + var status = Status.parse(object); + prepend (status); + } + }); + } + catch (GLib.Error e) { + warning ("Can't update feed"); + warning (e.message); + } + }); + return msg; + } + +} diff --git a/src/Widgets/AccountsButton.vala b/src/Widgets/AccountsButton.vala index cbe584a..758578f 100644 --- a/src/Widgets/AccountsButton.vala +++ b/src/Widgets/AccountsButton.vala @@ -7,9 +7,7 @@ public class Tootle.AccountsButton : Gtk.MenuButton{ Gtk.Popover menu; construct{ - //var iconfile = "/var/lib/AccountsService/icons/blue"; avatar = new Granite.Widgets.Avatar.with_default_icon (24); - avatar.set_tooltip_text (_("Account Options")); avatar.button_press_event.connect(event => { return false; }); @@ -35,6 +33,10 @@ public class Tootle.AccountsButton : Gtk.MenuButton{ popover = menu; add(avatar); show_all (); + + AccountManager.instance.changed_current.connect (account => { + CacheManager.instance.load_avatar (account.avatar, avatar, 24); + }); } public AccountsButton(){ diff --git a/src/Widgets/AccountsButton.vala~ b/src/Widgets/AccountsButton.vala~ new file mode 100644 index 0000000..dcd704a --- /dev/null +++ b/src/Widgets/AccountsButton.vala~ @@ -0,0 +1,43 @@ +using Gtk; + +public class Tootle.AccountsButton : Gtk.MenuButton{ + + Granite.Widgets.Avatar avatar; + Gtk.Grid grid; + Gtk.Popover menu; + + construct{ + avatar = new Granite.Widgets.Avatar.with_default_icon (24); + avatar.set_tooltip_text (_("Account Options")); + avatar.button_press_event.connect(event => { + return false; + }); + + var item_separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); + item_separator.hexpand = true; + item_separator.margin_top = 6; + + var item_settings = new Gtk.ModelButton (); + item_settings.text = _("Settings"); + + grid = new Gtk.Grid (); + grid.orientation = Gtk.Orientation.VERTICAL; + grid.width_request = 200; + grid.attach(item_separator, 0, 1, 1, 1); + grid.attach(item_settings, 0, 2, 1, 1); + grid.show_all (); + + menu = new Gtk.Popover (null); + menu.add (grid); + + get_style_context ().add_class ("button_avatar"); + popover = menu; + add(avatar); + show_all (); + } + + public AccountsButton(){ + Object(); + } + +} diff --git a/src/Widgets/StatusWidget.vala b/src/Widgets/StatusWidget.vala index 64c7b26..6c50e23 100644 --- a/src/Widgets/StatusWidget.vala +++ b/src/Widgets/StatusWidget.vala @@ -13,6 +13,7 @@ public class Tootle.StatusWidget : Gtk.Grid { construct { margin = 6; + avatar = new Granite.Widgets.Avatar.with_default_icon (32); avatar.valign = Gtk.Align.START; avatar.margin_end = 6; @@ -32,7 +33,7 @@ public class Tootle.StatusWidget : Gtk.Grid { reblogs = new Gtk.Label ("0"); favorites = new Gtk.Label ("0"); - counters = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); + counters = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); //TODO: currently useless counters.margin_top = 6; counters.margin_bottom = 12; counters.add(new Gtk.Image.from_icon_name ("media-playlist-repeat-symbolic", Gtk.IconSize.SMALL_TOOLBAR)); @@ -45,7 +46,7 @@ public class Tootle.StatusWidget : Gtk.Grid { attach(user, 1, 1, 1, 1); attach(content, 1, 2, 1, 1); attach(counters, 1, 3, 1, 1); - show_all(); + show_all(); //TODO: display conversations } public StatusWidget () { @@ -59,7 +60,7 @@ public class Tootle.StatusWidget : Gtk.Grid { reblogs.label = status.reblogs_count.to_string (); favorites.label = status.favourites_count.to_string (); - CacheManager.instance.load_image (status.avatar, this.avatar); + CacheManager.instance.load_avatar (status.avatar, this.avatar); } }