Commit ba74610a authored by Julian Sparber's avatar Julian Sparber

accountsettings: managment for phone and email threePIDs

* Allow adding and removing of phone and email PIDs

https://gitlab.gnome.org/World/fractal/issues/21
parent 345cb8e3
......@@ -159,6 +159,26 @@ impl Backend {
let r = user::get_threepid(self);
bkerror!(r, tx, BKResponse::GetThreePIDError);
}
Ok(BKCommand::GetTokenEmail(identity, email, client_secret)) => {
let r = user::get_email_token(self, identity, email, client_secret);
bkerror!(r, tx, BKResponse::GetTokenEmailError);
}
Ok(BKCommand::GetTokenPhone(identity, phone, client_secret)) => {
let r = user::get_phone_token(self, identity, phone, client_secret);
bkerror!(r, tx, BKResponse::GetTokenEmailError);
}
Ok(BKCommand::SubmitPhoneToken(identity, client_secret, sid, token)) => {
let r = user::submit_phone_token(self, identity, client_secret, sid, token);
bkerror!(r, tx, BKResponse::SubmitPhoneTokenError);
}
Ok(BKCommand::AddThreePID(identity, client_secret, sid)) => {
let r = user::add_threepid(self, identity, client_secret, sid);
bkerror!(r, tx, BKResponse::AddThreePIDError);
}
Ok(BKCommand::DeleteThreePID(medium, address)) => {
let r = user::delete_three_pid(self, medium, address);
bkerror!(r, tx, BKResponse::DeleteThreePIDError);
}
Ok(BKCommand::GetAvatar) => {
let r = user::get_avatar(self);
bkerror!(r, tx, BKResponse::AvatarError);
......
......@@ -27,6 +27,11 @@ pub enum BKCommand {
GetUsername,
SetUserName(String),
GetThreePID,
GetTokenEmail(String, String, String),
GetTokenPhone(String, String, String),
SubmitPhoneToken(String, String, String, String),
AddThreePID(String, String, String),
DeleteThreePID(String, String),
GetAvatar,
SetUserAvatar(String),
Sync,
......@@ -75,6 +80,13 @@ pub enum BKResponse {
Name(String),
SetUserName(String),
GetThreePID(Vec<UserInfo>),
GetTokenEmail(String),
GetTokenPhone(String),
SubmitPhoneToken(Option<String>),
AddThreePID(String),
GetTokenPhoneUsed,
GetTokenEmailUsed,
DeleteThreePID,
Avatar(String),
SetUserAvatar(String),
Sync(String),
......@@ -112,6 +124,11 @@ pub enum BKResponse {
UserNameError(Error),
SetUserNameError(Error),
GetThreePIDError(Error),
GetTokenEmailError(Error),
GetTokenPhoneError(Error),
SubmitPhoneTokenError(Error),
AddThreePIDError(Error),
DeleteThreePIDError(Error),
AvatarError(Error),
SetUserAvatarError(Error),
LoginError(Error),
......
extern crate serde_json;
extern crate url;
use std::fs::File;
use std::io::prelude::*;
use self::url::Url;
use globals;
use std::thread;
use std::sync::{Arc, Mutex};
......@@ -60,7 +62,6 @@ pub fn get_threepid(bk: &Backend) -> Result<(), Error> {
get!(&url,
|r: JsonValue| {
let mut result: Vec<UserInfo> = vec![];
println!("{}", r);
if let Some(arr) = r["threepids"].as_array() {
for pid in arr.iter() {
let address = match pid["address"].as_str() {
......@@ -96,6 +97,137 @@ pub fn get_threepid(bk: &Backend) -> Result<(), Error> {
Ok(())
}
pub fn get_email_token(bk: &Backend, identity: String, email: String, client_secret: String) -> Result<(), Error> {
let url = bk.url(&format!("account/3pid/email/requestToken"), vec![])?;
let attrs = json!({
"id_server": identity,
"client_secret": client_secret,
"email": email,
"send_attempt": "1",
});
let tx = bk.tx.clone();
post!(&url, &attrs,
|r: JsonValue| {
let sid = String::from(r["sid"].as_str().unwrap_or(""));
tx.send(BKResponse::GetTokenEmail(sid)).unwrap();
},
|err| {
match err {
Error::MatrixError(ref js) if js["errcode"].as_str().unwrap_or("") == "M_THREEPID_IN_USE" => {
tx.send(BKResponse::GetTokenEmailUsed).unwrap(); },
_ => {
tx.send(BKResponse::GetTokenEmailError(err)).unwrap(); }
}
}
);
Ok(())
}
pub fn get_phone_token(bk: &Backend, identity: String, phone: String, client_secret: String) -> Result<(), Error> {
let url = bk.url(&format!("account/3pid/msisdn/requestToken"), vec![])?;
let attrs = json!({
"id_server": identity,
"client_secret": client_secret,
"phone_number": phone,
"country": "",
"send_attempt": "1",
});
let tx = bk.tx.clone();
post!(&url, &attrs,
|r: JsonValue| {
let sid = String::from(r["sid"].as_str().unwrap_or(""));
tx.send(BKResponse::GetTokenPhone(sid)).unwrap();
},
|err| {
match err {
Error::MatrixError(ref js) if js["errcode"].as_str().unwrap_or("") == "M_THREEPID_IN_USE" => {
tx.send(BKResponse::GetTokenPhoneUsed).unwrap(); },
_ => {
tx.send(BKResponse::GetTokenPhoneError(err)).unwrap(); }
}
}
);
Ok(())
}
pub fn add_threepid(bk: &Backend, identity: String, client_secret: String, sid: String) -> Result<(), Error> {
let url = bk.url(&format!("account/3pid"), vec![])?;
let attrs = json!({
"three_pid_creds": {
"id_server": identity,
"sid": sid,
"client_secret": client_secret
},
"bind": true
});
let tx = bk.tx.clone();
post!(&url, &attrs,
|_r: JsonValue| {
tx.send(BKResponse::AddThreePID(sid)).unwrap();
},
|err| {
tx.send(BKResponse::AddThreePIDError(err)).unwrap(); }
);
Ok(())
}
pub fn submit_phone_token(bk: &Backend, url: String, client_secret: String, sid: String, token: String) -> Result<(), Error> {
let params = vec![
("sid", sid.clone()),
("client_secret", client_secret.clone()),
("token", token),
];
let path = "/_matrix/identity/api/v1/validate/msisdn/submitToken";
let url = build_url(&Url::parse(&url)?, path, params)?;
let tx = bk.tx.clone();
post!(&url,
|r: JsonValue| {
let result = if r["success"] == true {
Some(sid)
}
else {
None
};
tx.send(BKResponse::SubmitPhoneToken(result, client_secret)).unwrap();
},
|err| {
tx.send(BKResponse::SubmitPhoneTokenError(err)).unwrap();
}
);
Ok(())
}
pub fn delete_three_pid(bk: &Backend, medium: String, address: String) -> Result<(), Error> {
let baseu = bk.get_base_url()?;
let tk = bk.data.lock().unwrap().access_token.clone();
let mut url = baseu.join("/_matrix/client/unstable/account/3pid/delete")?;
url.query_pairs_mut().clear().append_pair("access_token", &tk);
let attrs = json!({
"medium": medium,
"address": address,
});
let tx = bk.tx.clone();
post!(&url, &attrs,
|_r: JsonValue| {
tx.send(BKResponse::DeleteThreePID).unwrap();
},
|err| {
tx.send(BKResponse::DeleteThreePIDError(err)).unwrap(); }
);
Ok(())
}
pub fn get_avatar(bk: &Backend) -> Result<(), Error> {
let baseu = bk.get_base_url()?;
......
......@@ -484,4 +484,59 @@
</object>
</child>
</object>
<object class="GtkDialog" id="account_settings_email_dialog">
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="window_position">center</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="deletable">False</property>
<property name="gravity">center</property>
<property name="transient_for">main_window</property>
<property name="attached_to">account_settings_dialog</property>
<property name="width_request">300</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Check your email</property>
<child>
<object class="GtkButton" id="cancel_email_account_settings">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="apply_email_account_settings">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<child>
<object class="GtkLabel" id="account_settings_email_dialog_label">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wrap">True</property>
<property name="max-width-chars">35</property>
<property name="label" translatable="yes"></property>
</object>
</child>
</object>
</child>
</object>
</interface>
......@@ -53,6 +53,32 @@ pub fn backend_loop(rx: Receiver<BKResponse>) {
let l = Some(list);
APPOP!(set_three_pid, (l));
}
Ok(BKResponse::GetTokenEmail(sid)) => {
let sid = Some(sid);
APPOP!(get_token_email, (sid));
}
Ok(BKResponse::GetTokenPhone(sid)) => {
let sid = Some(sid);
APPOP!(get_token_phone, (sid));
}
Ok(BKResponse:: GetTokenEmailUsed) => {
let error = gettext("Email is already in use");
APPOP!(show_three_pid_error_dialog, (error));
}
Ok(BKResponse:: GetTokenPhoneUsed) => {
let error = gettext("Phone number is already in use");
APPOP!(show_three_pid_error_dialog, (error));
}
Ok(BKResponse:: SubmitPhoneToken(sid)) => {
APPOP!(valid_phone_token, (sid));
}
Ok(BKResponse:: AddThreePID(list)) => {
let l = Some(list);
APPOP!(added_three_pid, (l));
}
Ok(BKResponse::DeleteThreePID) => {
APPOP!(get_three_pid);
}
Ok(BKResponse::SetUserName(username)) => {
let u = Some(username);
APPOP!(set_username, (u));
......@@ -163,6 +189,11 @@ pub fn backend_loop(rx: Receiver<BKResponse>) {
}
// errors
Ok(BKResponse::GetTokenEmailError(err)) => {
let error = gettext("Couldn't add the email address.");
println!("ERROR: {:?}", err);
APPOP!(show_three_pid_error_dialog, (error));
},
Ok(BKResponse::NewRoomError(err, internal_id)) => {
println!("ERROR: {:?}", err);
......
......@@ -156,13 +156,13 @@ impl App {
}));
/* Body */
verify_password.connect_property_text_notify(clone!(op, builder => move |_| {
verify_password.connect_property_text_notify(clone!(builder => move |_| {
validate_password_input(&builder.clone());
}));
new_password.connect_property_text_notify(clone!(op, builder => move |_| {
new_password.connect_property_text_notify(clone!(builder => move |_| {
validate_password_input(&builder.clone());
}));
old_password.connect_property_text_notify(clone!(op, builder => move |_| {
old_password.connect_property_text_notify(clone!(builder => move |_| {
validate_password_input(&builder)
}));
......
......@@ -19,6 +19,129 @@ impl AppOp {
self.backend.send(BKCommand::GetThreePID).unwrap();
}
pub fn added_three_pid(&self, _l: Option<String>) {
self.get_three_pid();
}
pub fn valid_phone_token(&self, sid: Option<String>) {
if let Some(sid) = sid {
let _ = self.backend.send(BKCommand::AddThreePID(String::from("vector.im"), String::from("canitworksandia2"), sid.clone()));
}
else {
self.show_three_pid_error_dialog(String::from("The validation code is not correct."));
self.get_three_pid();
}
}
pub fn show_phone_dialog(&self, sid: String) {
let parent = self.ui.builder
.get_object::<gtk::Dialog>("account_settings_dialog")
.expect("Can't find account_settings_dialog in ui file.");
let entry = gtk::Entry::new();
let msg = String::from("Insert the code recived via SMS");
let flags = gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT;
let dialog = gtk::MessageDialog::new(Some(&parent), flags, gtk::MessageType::Error, gtk::ButtonsType::None, &msg);
if let Some(area) = dialog.get_message_area() {
if let Ok(area) = area.downcast::<gtk::Box>() {
area.add(&entry);
}
}
let backend = self.backend.clone();
dialog.add_button("Cancel", gtk::ResponseType::Cancel.into());
let button = dialog.add_button("Continue", gtk::ResponseType::Ok.into());
button.set_sensitive(false);
let ok = button.clone();
entry.connect_activate(move |_| {
if ok.get_sensitive() {
let _ = ok.emit("clicked", &[]);
}
});
entry.connect_property_text_notify(move |w| {
if let Some(text) = w.get_text() {
if text != "" {
button.set_sensitive(true);
return;
}
}
button.set_sensitive(false);
});
let value = entry.clone();
dialog.connect_response(move |w, r| {
match gtk::ResponseType::from(r) {
gtk::ResponseType::Ok => {
if let Some(token) = value.get_text() {
let _ = backend.send(BKCommand::SubmitPhoneToken(String::from("https://vector.im"), String::from("canitworksandia2"), sid.clone(), token));
}
},
_ => {}
}
w.destroy();
});
dialog.show_all();
}
pub fn show_email_dialog(&self, sid: String) {
let parent = self.ui.builder
.get_object::<gtk::Dialog>("account_settings_dialog")
.expect("Can't find account_settings_dialog in ui file.");
let msg = String::from("In order to add this email address, go to your inbox and follow the link you received. Once you've done that, click 'continue'");
let flags = gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT;
let dialog = gtk::MessageDialog::new(Some(&parent), flags, gtk::MessageType::Error, gtk::ButtonsType::None, &msg);
let backend = self.backend.clone();
dialog.add_button("Cancel", gtk::ResponseType::Cancel.into());
dialog.add_button("Continue", gtk::ResponseType::Ok.into());
dialog.connect_response(move |w, r| {
match gtk::ResponseType::from(r) {
gtk::ResponseType::Ok => {
let _ = backend.send(BKCommand::AddThreePID(String::from("vector.im"), String::from("tosecretsecret2"), sid.clone()));
},
_ => {}
}
w.destroy();
});
dialog.show_all();
}
pub fn show_three_pid_error_dialog(&self, error: String) {
let parent = self.ui.builder
.get_object::<gtk::Dialog>("account_settings_dialog")
.expect("Can't find account_settings_dialog in ui file.");
let msg = error;
let flags = gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT;
let dialog = gtk::MessageDialog::new(Some(&parent), flags, gtk::MessageType::Error, gtk::ButtonsType::None, &msg);
dialog.add_button("Ok", gtk::ResponseType::Ok.into());
let backend = self.backend.clone();
dialog.connect_response(move |w, _| {
backend.send(BKCommand::GetThreePID).unwrap();
w.destroy();
});
dialog.show_all();
}
pub fn get_token_email(&mut self, sid: Option<String>) {
self.tmp_sid = sid.clone();
if let Some(sid) = sid {
self.show_email_dialog(sid);
}
}
pub fn get_token_phone(&mut self, sid: Option<String>) {
self.tmp_sid = sid.clone();
if let Some(sid) = sid {
println!("Phone sid: {}", sid);
self.show_phone_dialog(sid);
}
}
pub fn show_account_settings_dialog(&self) {
let dialog = self.ui.builder
.get_object::<gtk::Dialog>("account_settings_dialog")
......@@ -106,38 +229,37 @@ impl AppOp {
}
/* Make sure we have at least one empty entry for email and phone */
let empty_email = widgets::Address::new(widgets::AddressType::Email);
let empty_phone = widgets::Address::new(widgets::AddressType::Phone);
email.pack_start(&empty_email.clone().create(None), true, true, 0);
phone.pack_start(&empty_phone.clone().create(None), true, true, 0);
let mut empty_email = widgets::Address::new(widgets::AddressType::Email, &self);
let mut empty_phone = widgets::Address::new(widgets::AddressType::Phone, &self);
email.pack_start(&empty_email.create(None), true, true, 0);
phone.pack_start(&empty_phone.create(None), true, true, 0);
if let Some(data) = data {
for item in data {
if item.medium == "email" {
if first_email {
empty_email.clone().update(Some(item.address));
let entry = widgets::Address::new(widgets::AddressType::Email).create(None);
empty_email.update(Some(item.address));
let entry = widgets::Address::new(widgets::AddressType::Email, &self).create(None);
grid.insert_next_to(&email, gtk::PositionType::Bottom);
grid.attach_next_to(&entry, &email, gtk::PositionType::Bottom, 1, 1);
first_email = false;
}
else {
let entry = widgets::Address::new(widgets::AddressType::Email).create(Some(item.address));
let entry = widgets::Address::new(widgets::AddressType::Email, &self).create(Some(item.address));
grid.insert_next_to(&email, gtk::PositionType::Bottom);
grid.attach_next_to(&entry, &email, gtk::PositionType::Bottom, 1, 1);
}
}
else if item.medium == "msisdn" {
if first_phone {
let s = String::from("+") + &String::from(item.address);
empty_phone.clone().update(Some(s));
let entry = widgets::Address::new(widgets::AddressType::Phone).create(None);
empty_phone.update(Some(item.address));
let entry = widgets::Address::new(widgets::AddressType::Phone, &self).create(None);
grid.insert_next_to(&phone, gtk::PositionType::Bottom);
grid.attach_next_to(&entry, &phone, gtk::PositionType::Bottom, 1, 1);
first_phone = false;
}
else {
let s = String::from("+") + &String::from(item.address);
let entry = widgets::Address::new(widgets::AddressType::Phone).create(Some(s));
let entry = widgets::Address::new(widgets::AddressType::Phone, &self).create(Some(s));
grid.insert_next_to(&phone, gtk::PositionType::Bottom);
grid.attach_next_to(&entry, &phone, gtk::PositionType::Bottom, 1, 1);
}
......
......@@ -82,6 +82,7 @@ pub struct AppOp {
pub popover_closing: bool,
pub tmp_avatar: Option<String>,
pub tmp_sid: Option<String>,
pub state: AppState,
pub since: Option<String>,
......@@ -134,6 +135,7 @@ impl AppOp {
unsent_messages: HashMap::new(),
tmp_avatar: None,
tmp_sid: None,
highlighted_entry: vec![],
popover_position: None,
......
extern crate gtk;
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
use self::gtk::prelude::*;
use glib::signal;
use widgets;
use backend::BKCommand;
use appop::AppOp;
#[derive(Debug, Clone)]
pub enum AddressType {
......@@ -21,32 +18,39 @@ pub enum AddressAction {
Add,
}
#[derive(Debug, Clone)]
pub struct Address {
pub struct Address<'a> {
op: &'a AppOp,
entry: gtk::Entry,
button: gtk::Button,
action: Option<AddressAction>,
medium: AddressType,
address: Option<String>,
signal_id: Option<signal::SignalHandlerId>,
}
impl Address {
pub fn new(t: AddressType) -> Address {
impl<'a> Address<'a> {
pub fn new(t: AddressType, op: &'a AppOp) -> Address<'a> {
let entry = gtk::Entry::new();
let button = gtk::Button::new();
Address {
op: op,
entry: entry,
button: button,
action: None,
address: None,
signal_id: None,
medium: t,
}
}
pub fn create(mut self, text: Option<String>) -> gtk::Box {
pub fn create(&mut self, text: Option<String>) -> gtk::Box {
let b = gtk::Box::new(gtk::Orientation::Horizontal, 0);
b.pack_start(&self.entry, true, true, 0);
b.pack_end(&self.button, false, false, 0);
if let Some(text) = text {
self.address = Some(text.clone());
self.entry.set_text(&text);
self.entry.set_editable(false);
self.action = Some(AddressAction::Delete);
let label = gtk::Image::new_from_icon_name("user-trash-symbolic", 1);
......@@ -61,6 +65,7 @@ impl Address {
style.add_class("suggested-action");
}
self.button.hide();
self.entry.set_editable(true);
}
if let Some(style) = b.get_style_context() {
style.add_class("linked");
......@@ -71,9 +76,17 @@ impl Address {
b
}
pub fn update(mut self, text: Option<String>) {
pub fn update(&mut self, text: Option<String>) {
if let Some(text) = text {
self.address = Some(text.clone());
/* Add prefix(+) to phone numbers */
let text = match self.medium {
AddressType::Email => text,
AddressType::Phone => String::from("+") + &text,
};
self.entry.set_text(&text);
self.entry.set_editable(false);
self.action = Some(AddressAction::Delete);
let label = gtk::Image::new_from_icon_name("user-trash-symbolic", 1);
......@@ -91,19 +104,31 @@ impl Address {
style.add_class("suggested-action");
}
self.button.hide();
self.entry.set_editable(true);
}
self.remove_handler();
self.connect();
}
fn remove_handler(&mut self) {
let id = self.signal_id.take();
if let Some(id) = id {
signal::signal_handler_disconnect(&self.button, id);
}
}
fn connect(self) {
fn connect(&mut self) {
let button = self.button.clone();
let medium = self.medium.clone();
self.entry.connect_property_text_notify(move |w| {
self.entry.connect_property_text_notify(move |w| {
if let Some(text) = w.get_text() {
if text != "" {
/* FIXME: use better validation */
match medium {
AddressType::Email => button.set_sensitive(text.contains("@") && text.contains(".")),
AddressType::Email => {
button.set_sensitive(text.contains("@") && text.contains("."));
},
AddressType::Phone => {},
};
button.show();
......@@ -115,33 +140,86 @@ impl Address {
});
let button = self.button.clone();
self.entry.connect_activate(move |_| {
let _ = button.emit("clicked", &[]);
self.entry.connect_activate(move |w| {
if w.get_editable() {
let _ = button.emit("clicked", &[]);
}
});
let medium = self.medium.clone();
let action = self.action.clone();
self.button.connect_clicked(move |w| {
let medium = &self.medium;
let action = &self.action;
let entry = &self.entry;
let address = &self.address;
let backend = &self.op.backend;
self.signal_id = Some(self.button.clone().connect_clicked(clone!(medium, action, entry, address, backend => move |w| {
if w.get_sensitive() && w.is_visible() {
match medium {
AddressType::Email => {
if let Some(ref action) = action {
match action {
AddressAction::Delete => println!("Delete email number"),
AddressAction::Add => println!("Add email number"),
/* get address from entry if we don't have one */
let address = if address.is_none() {
entry.get_text()
}
else {
address.clone()
};
if let Some(address) = address.clone() {
match medium {
AddressType::Email => {