diff --git a/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml b/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml index 0e59605..206d4f5 100644 --- a/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml +++ b/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml @@ -6,6 +6,16 @@ Api key The api key for the current user + + nothing + Profile picture url + The url for the users profile picture + + + nothing + Profile picture data + The data for the users profile picture + (-1,-1) diff --git a/simplelogin-gui/data/settings_panel.blp b/simplelogin-gui/data/settings_panel.blp index 9abb9c3..ac9ebc6 100644 --- a/simplelogin-gui/data/settings_panel.blp +++ b/simplelogin-gui/data/settings_panel.blp @@ -14,17 +14,13 @@ template $SettingsPanel : Box { row-homogeneous: true; valign: start; - Button profile_image_button { - has-frame: false; - - Image profile_image { - halign: center; - icon-name: "user-info-symbolic"; - icon-size: large; - margin-end: 5; - valign: center; - vexpand: true; - } + Image profile_image { + halign: center; + icon-name: "user-info-symbolic"; + icon-size: large; + margin-end: 5; + valign: center; + vexpand: true; layout { column: "0"; @@ -34,27 +30,17 @@ template $SettingsPanel : Box { } } - Box { - Button remove_picture_button { - halign: start; - valign: center; - icon-name: "window-close-symbolic"; - visible: false; - } - - Entry name_label { - editable: false; - can-focus: false; - has-frame: false; - hexpand: true; - input-hints: no_emoji | no_spellcheck; - text: "Testy McTesterface"; - - styles [ - "login-title-label", - ] - } + Entry name_label { + editable: false; + can-focus: false; + has-frame: false; + hexpand: true; + input-hints: no_emoji | no_spellcheck; + text: "Testy McTesterface"; + styles [ + "login-title-label", + ] layout { column: "1"; column-span: "1"; diff --git a/simplelogin-gui/src/application.rs b/simplelogin-gui/src/application.rs index 0d28eaf..1088721 100644 --- a/simplelogin-gui/src/application.rs +++ b/simplelogin-gui/src/application.rs @@ -17,6 +17,7 @@ mod imp { pub struct Application { #[property(name = "api-key", type = Option, get = Self::api_key, set = Self::set_api_key, nullable)] + #[property(get)] pub settings: RefCell, pub context: RefCell, } diff --git a/simplelogin-gui/src/settings_panel.rs b/simplelogin-gui/src/settings_panel.rs index f8ef46a..7ed4c1d 100644 --- a/simplelogin-gui/src/settings_panel.rs +++ b/simplelogin-gui/src/settings_panel.rs @@ -1,6 +1,6 @@ use crate::application; use gtk4 as gtk; -use gtk::{glib, prelude::*, subclass::prelude::*}; +use gtk::{glib, gio, gdk, prelude::*, subclass::prelude::*}; mod imp { use super::*; @@ -8,6 +8,7 @@ mod imp { use std::cell::RefCell; use glib::subclass::Signal; use once_cell::sync::Lazy; + use simplelogin::reqwest; #[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)] #[template(file = "data/settings_panel.blp")] @@ -20,12 +21,8 @@ mod imp { #[template_child] pub content: TemplateChild, #[template_child] - pub profile_image_button: TemplateChild, - #[template_child] pub profile_image: TemplateChild, #[template_child] - pub remove_picture_button: TemplateChild, - #[template_child] pub name_label: TemplateChild, #[template_child] pub edit_button: TemplateChild, @@ -149,6 +146,79 @@ mod imp { } } + fn did_profile_picture_change( + new_url: &Option, settings: &gio::Settings + ) -> bool { + let old_url: Option = settings.value("profile-url") + .get::>().unwrap(); + *new_url != old_url + } + + fn has_valid_content_type(resp: &reqwest::Response) -> bool { + match resp.headers().get("Content-Type") { + Some(ct) => match ct.to_str() { + Ok("image/png") => true, + Ok("image/jpeg") => true, + _ => false, + }, + None => false, + } + } + + async fn process_profile_picture_response_body( + &self, url: &str, resp: reqwest::Response, + settings: &gio::Settings + ) { + if let Ok(body) = resp.bytes().await { + let tex_res = gdk::Texture::from_bytes(&glib::Bytes::from(body.as_ref())); + if let Ok(tex) = tex_res { + self.profile_image.get().set_from_paintable(Some(&tex)); + _ = settings.set("profile-url", Some(url)); + _ = settings.set("profile-data", Some(body.as_ref())); + } + } + } + + async fn download_and_set_profile_picture( + &self, url: &str, app: &application::Application + ) -> bool { + let ctx = app.context().borrow(); + let client = ctx.client(); + match client.get(url).send().await { + Ok(resp) if Self::has_valid_content_type(&resp) => { + self.process_profile_picture_response_body( + url, resp, &app.settings()).await; + false + }, + Ok(_) => false, + Err(e) => { + self.set_error(Some(&e.to_string())); + true + }, + } + } + + async fn refresh_user_profile_picture( + &self, user_info: &simplelogin::UserInfo, + ) -> bool { + let app = self.application.borrow(); + let settings = &app.settings(); + if Self::did_profile_picture_change(&user_info.profile_picture_url, + &settings) { + self.profile_image.get() + .set_from_icon_name(Some("user-info-symbolic")); + _ = settings.set("profile-url", Option::<&str>::None); + _ = settings.set("profile-data", Option::<&[u8]>::None); + match &user_info.profile_picture_url { + Some(new_url) => self.download_and_set_profile_picture( + &new_url, &app).await, + None => false, + } + } else { + false + } + } + async fn refresh_user_info(&self) -> bool { let app = self.application.borrow(); let mut ctx = app.context().borrow_mut(); @@ -170,6 +240,8 @@ mod imp { } else { "Free" }); + drop(ctx); + self.refresh_user_profile_picture(&user_info).await; false }, Err(e) => { diff --git a/simplelogin/src/lib.rs b/simplelogin/src/lib.rs index 138e4b2..e53c511 100644 --- a/simplelogin/src/lib.rs +++ b/simplelogin/src/lib.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +pub use reqwest; use std::fmt::{self, Display}; use serde::{Serialize, Deserialize}; @@ -63,6 +64,7 @@ struct MfaResponse { struct UserInfoRequest<'a> { api_key: &'a str, profile_picture: Option<&'a str>, + name: Option<&'a str>, } #[derive(Debug, Deserialize)] @@ -148,6 +150,10 @@ impl Context { } } + pub fn client(&self) -> &reqwest::Client { + &self.client + } + pub fn api_key(&self) -> &Option { &self.api_key } @@ -304,12 +310,12 @@ impl Context { } pub async fn update_user_info( - &mut self, profile_picture: Option<&str> + &mut self, profile_picture: Option<&str>, name: Option<&str> ) -> Result { match self.api_key.as_ref() { Some(api_key) => { let req_json = Self::obj_to_json( - &UserInfoRequest {api_key, profile_picture})?; + &UserInfoRequest {api_key, profile_picture, name})?; let res = self.patch_request("api/user_info", Some(&req_json), None).await?; Ok(Self::json_to_obj(&res)?)