You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
8.7 KiB
283 lines
8.7 KiB
use std::sync::Arc; |
|
|
|
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*}; |
|
use log::error; |
|
use matrix_sdk::{ |
|
encryption::identities::UserIdentity, |
|
ruma::{MxcUri, UserId}, |
|
}; |
|
|
|
use crate::{ |
|
session::{ |
|
verification::{IdentityVerification, VerificationState}, |
|
Avatar, Session, |
|
}, |
|
spawn, spawn_tokio, |
|
}; |
|
|
|
#[glib::flags(name = "UserActions")] |
|
pub enum UserActions { |
|
NONE = 0b00000000, |
|
VERIFY = 0b00000001, |
|
} |
|
|
|
impl Default for UserActions { |
|
fn default() -> Self { |
|
Self::NONE |
|
} |
|
} |
|
|
|
mod imp { |
|
use std::cell::{Cell, RefCell}; |
|
|
|
use glib::object::WeakRef; |
|
use once_cell::{sync::Lazy, unsync::OnceCell}; |
|
|
|
use super::*; |
|
|
|
#[derive(Debug, Default)] |
|
pub struct User { |
|
pub user_id: OnceCell<Arc<UserId>>, |
|
pub display_name: RefCell<Option<String>>, |
|
pub session: OnceCell<WeakRef<Session>>, |
|
pub avatar: OnceCell<Avatar>, |
|
pub is_verified: Cell<bool>, |
|
} |
|
|
|
#[glib::object_subclass] |
|
impl ObjectSubclass for User { |
|
const NAME: &'static str = "User"; |
|
type Type = super::User; |
|
type ParentType = glib::Object; |
|
} |
|
|
|
impl ObjectImpl for User { |
|
fn properties() -> &'static [glib::ParamSpec] { |
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| { |
|
vec![ |
|
glib::ParamSpecString::new( |
|
"user-id", |
|
"User id", |
|
"The user id of this user", |
|
None, |
|
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY, |
|
), |
|
glib::ParamSpecString::new( |
|
"display-name", |
|
"Display Name", |
|
"The display name of the user", |
|
None, |
|
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY, |
|
), |
|
glib::ParamSpecObject::new( |
|
"avatar", |
|
"Avatar", |
|
"The avatar of this user", |
|
Avatar::static_type(), |
|
glib::ParamFlags::READABLE, |
|
), |
|
glib::ParamSpecObject::new( |
|
"session", |
|
"Session", |
|
"The session", |
|
Session::static_type(), |
|
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY, |
|
), |
|
glib::ParamSpecBoolean::new( |
|
"verified", |
|
"Verified", |
|
"Whether this user has been verified", |
|
false, |
|
glib::ParamFlags::READABLE, |
|
), |
|
glib::ParamSpecFlags::new( |
|
"allowed-actions", |
|
"Allowed Actions", |
|
"The actions the currently logged-in user is allowed to perform on this user.", |
|
UserActions::static_type(), |
|
UserActions::default().bits(), |
|
glib::ParamFlags::READABLE, |
|
) |
|
] |
|
}); |
|
|
|
PROPERTIES.as_ref() |
|
} |
|
|
|
fn set_property( |
|
&self, |
|
obj: &Self::Type, |
|
_id: usize, |
|
value: &glib::Value, |
|
pspec: &glib::ParamSpec, |
|
) { |
|
match pspec.name() { |
|
"user-id" => { |
|
self.user_id |
|
.set(UserId::parse_arc(value.get::<&str>().unwrap()).unwrap()) |
|
.unwrap(); |
|
} |
|
"display-name" => { |
|
obj.set_display_name(value.get::<Option<String>>().unwrap()); |
|
} |
|
"session" => self |
|
.session |
|
.set(value.get::<Session>().unwrap().downgrade()) |
|
.unwrap(), |
|
_ => unimplemented!(), |
|
} |
|
} |
|
|
|
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { |
|
match pspec.name() { |
|
"display-name" => obj.display_name().to_value(), |
|
"user-id" => obj.user_id().as_str().to_value(), |
|
"session" => obj.session().to_value(), |
|
"avatar" => obj.avatar().to_value(), |
|
"verified" => obj.is_verified().to_value(), |
|
"allowed-actions" => obj.allowed_actions().to_value(), |
|
_ => unimplemented!(), |
|
} |
|
} |
|
|
|
fn constructed(&self, obj: &Self::Type) { |
|
self.parent_constructed(obj); |
|
|
|
let avatar = Avatar::new(&obj.session(), None); |
|
self.avatar.set(avatar).unwrap(); |
|
|
|
obj.bind_property("display-name", obj.avatar(), "display-name") |
|
.flags(glib::BindingFlags::SYNC_CREATE) |
|
.build(); |
|
|
|
obj.init_is_verified(); |
|
} |
|
} |
|
} |
|
|
|
glib::wrapper! { |
|
/// `glib::Object` representation of a Matrix user. |
|
pub struct User(ObjectSubclass<imp::User>); |
|
} |
|
|
|
impl User { |
|
pub fn new(session: &Session, user_id: &UserId) -> Self { |
|
glib::Object::new(&[("session", session), ("user-id", &user_id.as_str())]) |
|
.expect("Failed to create User") |
|
} |
|
|
|
pub async fn crypto_identity(&self) -> Option<UserIdentity> { |
|
let encryption = self.session().client().encryption(); |
|
let user_id = self.user_id(); |
|
let handle = spawn_tokio!(async move { encryption.get_user_identity(&user_id).await }); |
|
|
|
match handle.await.unwrap() { |
|
Ok(identity) => identity, |
|
Err(error) => { |
|
error!("Failed to find crypto identity: {}", error); |
|
None |
|
} |
|
} |
|
} |
|
|
|
pub async fn verify_identity(&self) -> IdentityVerification { |
|
let request = IdentityVerification::create(&self.session(), Some(self)).await; |
|
self.session().verification_list().add(request.clone()); |
|
// FIXME: actually listen to room events to get updates for verification state |
|
request.connect_notify_local( |
|
Some("state"), |
|
clone!(@weak self as obj => move |request,_| { |
|
if request.state() == VerificationState::Completed { |
|
obj.init_is_verified(); |
|
} |
|
}), |
|
); |
|
request |
|
} |
|
|
|
pub fn is_verified(&self) -> bool { |
|
self.imp().is_verified.get() |
|
} |
|
|
|
fn init_is_verified(&self) { |
|
spawn!(clone!(@weak self as obj => async move { |
|
let is_verified = obj.crypto_identity().await.map_or(false, |i| i.verified()); |
|
|
|
if is_verified == obj.is_verified() { |
|
return; |
|
} |
|
|
|
obj.imp().is_verified.set(is_verified); |
|
obj.notify("verified"); |
|
obj.notify("allowed-actions"); |
|
})); |
|
} |
|
} |
|
|
|
pub trait UserExt: IsA<User> { |
|
fn session(&self) -> Session { |
|
self.upcast_ref() |
|
.imp() |
|
.session |
|
.get() |
|
.unwrap() |
|
.upgrade() |
|
.unwrap() |
|
} |
|
|
|
fn user_id(&self) -> Arc<UserId> { |
|
self.upcast_ref().imp().user_id.get().unwrap().clone() |
|
} |
|
|
|
fn display_name(&self) -> String { |
|
let priv_ = self.upcast_ref().imp(); |
|
|
|
if let Some(display_name) = priv_.display_name.borrow().to_owned() { |
|
display_name |
|
} else { |
|
priv_.user_id.get().unwrap().localpart().to_owned() |
|
} |
|
} |
|
|
|
fn set_display_name(&self, display_name: Option<String>) { |
|
if Some(self.display_name()) == display_name { |
|
return; |
|
} |
|
self.upcast_ref().imp().display_name.replace(display_name); |
|
self.notify("display-name"); |
|
} |
|
|
|
fn avatar(&self) -> &Avatar { |
|
self.upcast_ref().imp().avatar.get().unwrap() |
|
} |
|
|
|
fn set_avatar_url(&self, url: Option<Box<MxcUri>>) { |
|
self.avatar().set_url(url); |
|
} |
|
|
|
fn allowed_actions(&self) -> UserActions { |
|
let user = self.upcast_ref(); |
|
|
|
let is_us = self.session().user().map_or(false, |session_user| { |
|
session_user.user_id() != self.user_id() |
|
}); |
|
|
|
if !user.is_verified() && is_us { |
|
UserActions::VERIFY |
|
} else { |
|
UserActions::NONE |
|
} |
|
} |
|
} |
|
|
|
impl<T: IsA<User>> UserExt for T {} |
|
|
|
unsafe impl<T: ObjectImpl + 'static> IsSubclassable<T> for User { |
|
fn class_init(class: &mut glib::Class<Self>) { |
|
<glib::Object as IsSubclassable<T>>::class_init(class.upcast_ref_mut()); |
|
} |
|
|
|
fn instance_init(instance: &mut glib::subclass::InitializingObject<T>) { |
|
<glib::Object as IsSubclassable<T>>::instance_init(instance); |
|
} |
|
}
|
|
|