[fractal] invite-subpage: Allow inviting users by id to a room
- From: Kévin Commaille <kcommaille src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] invite-subpage: Allow inviting users by id to a room
- Date: Sun, 14 Aug 2022 07:43:19 +0000 (UTC)
commit ac03df8db818382220495dd6bd32eb7616fd893c
Author: Kirill Schmidt <kirill schmidt teckids org>
Date: Sat Jul 23 18:03:33 2022 +0200
invite-subpage: Allow inviting users by id to a room
Fixes #879 #1062
data/resources/ui/content-invitee-row.ui | 34 +++++-
po/POTFILES.in | 1 +
.../content/room_details/invite_subpage/invitee.rs | 23 ++++
.../room_details/invite_subpage/invitee_list.rs | 132 ++++++++++++++-------
.../room_details/invite_subpage/invitee_row.rs | 2 +
src/session/room/member_list.rs | 14 ++-
src/session/room/mod.rs | 7 +-
src/utils.rs | 5 +
8 files changed, 169 insertions(+), 49 deletions(-)
---
diff --git a/data/resources/ui/content-invitee-row.ui b/data/resources/ui/content-invitee-row.ui
index b721bfa5b..ec132e44a 100644
--- a/data/resources/ui/content-invitee-row.ui
+++ b/data/resources/ui/content-invitee-row.ui
@@ -59,7 +59,39 @@
</object>
</child>
<child>
- <object class="GtkCheckButton" id="check_button" />
+ <object class="GtkCheckButton" id="check_button">
+ <binding name="visible">
+ <closure type="gboolean" function="invert_boolean">
+ <closure type="gboolean" function="string_not_empty">
+ <lookup name="invite-exception" type="Invitee">
+ <lookup name="user">ContentInviteInviteeRow</lookup>
+ </lookup>
+ </closure>
+ </closure>
+ </binding>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <binding name="visible">
+ <closure type="gboolean" function="string_not_empty">
+ <lookup name="invite-exception" type="Invitee">
+ <lookup name="user">ContentInviteInviteeRow</lookup>
+ </lookup>
+ </closure>
+ </binding>
+ <property name="hexpand">True</property>
+ <property name="halign">end</property>
+ <property name="ellipsize">end</property>
+ <binding name="label">
+ <lookup name="invite-exception" type="Invitee">
+ <lookup name="user">ContentInviteInviteeRow</lookup>
+ </lookup>
+ </binding>
+ <style>
+ <class name="subtitle"/>
+ </style>
+ </object>
</child>
</object>
</property>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8fac1dbeb..65c52c28c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -54,6 +54,7 @@ src/session/account_settings/user_page/deactivate_account_subpage.rs
src/session/account_settings/user_page/mod.rs
src/session/content/explore/public_room_row.rs
src/session/content/invite.rs
+src/session/content/room_details/invite_subpage/invitee_list.rs
src/session/content/room_details/member_page/mod.rs
src/session/content/room_details/mod.rs
src/session/content/room_history/item_row.rs
diff --git a/src/session/content/room_details/invite_subpage/invitee.rs
b/src/session/content/room_details/invite_subpage/invitee.rs
index 05226f6f8..7f7543366 100644
--- a/src/session/content/room_details/invite_subpage/invitee.rs
+++ b/src/session/content/room_details/invite_subpage/invitee.rs
@@ -14,6 +14,7 @@ mod imp {
pub struct Invitee {
pub invited: Cell<bool>,
pub anchor: RefCell<Option<gtk::TextChildAnchor>>,
+ pub invite_exception: RefCell<Option<String>>,
}
#[glib::object_subclass]
@@ -41,6 +42,13 @@ mod imp {
gtk::TextChildAnchor::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
),
+ glib::ParamSpecString::new(
+ "invite-exception",
+ "Invite Exception",
+ "The reason the user can't be invited",
+ None,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
]
});
@@ -57,6 +65,7 @@ mod imp {
match pspec.name() {
"invited" => obj.set_invited(value.get().unwrap()),
"anchor" => obj.set_anchor(value.get().unwrap()),
+ "invite-exception" => obj.set_invite_exception(value.get().unwrap()),
_ => unimplemented!(),
}
}
@@ -65,6 +74,7 @@ mod imp {
match pspec.name() {
"invited" => obj.is_invited().to_value(),
"anchor" => obj.anchor().to_value(),
+ "invite-exception" => obj.invite_exception().to_value(),
_ => unimplemented!(),
}
}
@@ -125,4 +135,17 @@ impl Invitee {
self.imp().anchor.replace(anchor);
self.notify("anchor");
}
+
+ pub fn invite_exception(&self) -> Option<String> {
+ self.imp().invite_exception.borrow().clone()
+ }
+
+ pub fn set_invite_exception(&self, exception: Option<String>) {
+ if exception == self.invite_exception() {
+ return;
+ }
+
+ self.imp().invite_exception.replace(exception);
+ self.notify("invite-exception");
+ }
}
diff --git a/src/session/content/room_details/invite_subpage/invitee_list.rs
b/src/session/content/room_details/invite_subpage/invitee_list.rs
index a71e2ac9b..fc45b3fd5 100644
--- a/src/session/content/room_details/invite_subpage/invitee_list.rs
+++ b/src/session/content/room_details/invite_subpage/invitee_list.rs
@@ -1,13 +1,17 @@
+use gettextrs::gettext;
use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
use log::error;
use matrix_sdk::{
- ruma::{api::client::user_directory::search_users, OwnedUserId, UserId},
+ ruma::{
+ api::client::{profile::get_profile, user_directory::search_users},
+ OwnedUserId, UserId,
+ },
HttpError,
};
use super::Invitee;
use crate::{
- session::{user::UserExt, Room},
+ session::{room::Membership, user::UserExt, Room},
spawn, spawn_tokio,
};
@@ -229,55 +233,99 @@ impl InviteeList {
let session = self.room().session();
let member_list = self.room().members();
- if Some(search_term) != self.search_term() {
+ if Some(&search_term) != self.search_term().as_ref() {
return;
}
match response {
- Ok(response) if response.results.is_empty() => {
- self.set_state(InviteeListState::NoMatching);
- self.clear_list();
- }
- Ok(response) => {
+ Ok(mut response) => {
+ // If the search term looks like an UserId and is not already in the response,
+ // insert it.
+ if let Ok(user_id) = UserId::parse(&search_term) {
+ if !response.results.iter().any(|item| item.user_id == user_id) {
+ let user = search_users::v3::User::new(user_id);
+ response.results.insert(0, user);
+ }
+ }
+
let users: Vec<Invitee> = response
.results
.into_iter()
- .filter_map(|item| {
- // Skip over users that are already in the room
- if member_list.contains(&item.user_id) {
- self.remove_invitee(item.user_id);
- None
- } else if let Some(user) = self.get_invitee(&item.user_id) {
- // The avatar or the display name may have changed in the mean time
- user.set_avatar_url(item.avatar_url);
- user.set_display_name(item.display_name);
- Some(user)
- } else {
- let user = Invitee::new(
- &session,
- &item.user_id,
- item.display_name.as_deref(),
- item.avatar_url.as_deref(),
- );
-
- user.connect_notify_local(
- Some("invited"),
- clone!(@weak self as obj => move |user, _| {
- if user.is_invited() {
- obj.add_invitee(user.clone());
- } else {
- obj.remove_invitee(user.user_id())
- }
- }),
- );
-
- Some(user)
- }
+ .map(|item| {
+ let user = match self.get_invitee(&item.user_id) {
+ Some(user) => {
+ // The avatar or the display name may have changed in the meantime
+ user.set_avatar_url(item.avatar_url);
+ user.set_display_name(item.display_name);
+
+ user
+ }
+ None => {
+ let user = Invitee::new(
+ &session,
+ &item.user_id,
+ item.display_name.as_deref(),
+ item.avatar_url.as_deref(),
+ );
+ user.connect_notify_local(
+ Some("invited"),
+ clone!(@weak self as obj => move |user, _| {
+ if user.is_invited() && user.invite_exception().is_none() {
+ obj.add_invitee(user.clone());
+ } else {
+ obj.remove_invitee(user.user_id())
+ }
+ }),
+ );
+ // If it is the "custom user" from the search term, fetch the avatar
+ // and display name
+ let user_id = user.user_id();
+ if user_id == search_term {
+ let client = session.client();
+ let handle = spawn_tokio!(async move {
+ let request = get_profile::v3::Request::new(&user_id);
+ client.send(request, None).await
+ });
+ spawn!(clone!(@weak user => async move {
+ let response = handle.await.unwrap();
+ let (display_name, avatar_url) = match response {
+ Ok(response) => {
+ (response.displayname, response.avatar_url)
+ },
+ Err(_) => {
+ return;
+ }
+ };
+ // If the display name and or the avatar were returned, the Invitee
gets updated.
+ if display_name.is_some() {
+ user.set_display_name(display_name);
+ }
+ if avatar_url.is_some() {
+ user.set_avatar_url(avatar_url);
+ }
+ }));
+ }
+
+ user
+ }
+ };
+ // 'Disable' users that can't be invited
+ match member_list.get_membership(&item.user_id) {
+ Membership::Join => user.set_invite_exception(Some(gettext("Member"))),
+ Membership::Ban => user.set_invite_exception(Some(gettext("Banned"))),
+ Membership::Invite => {
+ user.set_invite_exception(Some(gettext("Invited")))
+ }
+ _ => {}
+ };
+ user
})
.collect();
-
+ match users.is_empty() {
+ true => self.set_state(InviteeListState::NoMatching),
+ false => self.set_state(InviteeListState::Matching),
+ }
self.set_list(users);
- self.set_state(InviteeListState::Matching);
}
Err(error) => {
error!("Couldn’t load matching users: {}", error);
@@ -292,7 +340,7 @@ impl InviteeList {
let search_term = if let Some(search_term) = self.search_term() {
search_term
} else {
- // Do nothing for no search term execpt when currently loading
+ // Do nothing for no search term except when currently loading
if self.state() == InviteeListState::Loading {
self.set_state(InviteeListState::Initial);
}
diff --git a/src/session/content/room_details/invite_subpage/invitee_row.rs
b/src/session/content/room_details/invite_subpage/invitee_row.rs
index acaa4900b..d88122a12 100644
--- a/src/session/content/room_details/invite_subpage/invitee_row.rs
+++ b/src/session/content/room_details/invite_subpage/invitee_row.rs
@@ -10,6 +10,7 @@ mod imp {
use once_cell::sync::Lazy;
use super::*;
+ use crate::utils::TemplateCallbacks;
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/content-invitee-row.ui")]
@@ -28,6 +29,7 @@ mod imp {
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
+ TemplateCallbacks::bind_template_callbacks(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
diff --git a/src/session/room/member_list.rs b/src/session/room/member_list.rs
index 8580e81d2..52d2b20f2 100644
--- a/src/session/room/member_list.rs
+++ b/src/session/room/member_list.rs
@@ -5,7 +5,7 @@ use matrix_sdk::ruma::{
OwnedUserId, UserId,
};
-use crate::session::room::{Member, Room};
+use crate::session::room::{Member, Membership, Room};
mod imp {
use std::cell::RefCell;
@@ -163,8 +163,14 @@ impl MemberList {
.update_from_member_event(event);
}
- /// Returns whether the given user id is present in `MemberList`
- pub fn contains(&self, user_id: &UserId) -> bool {
- self.imp().members.borrow().contains_key(user_id)
+ /// Returns the Membership of a given UserId.
+ ///
+ /// If the user has no Membership, Membership::Leave will be returned
+ pub fn get_membership(&self, user_id: &UserId) -> Membership {
+ self.imp()
+ .members
+ .borrow()
+ .get(user_id)
+ .map_or_else(|| Membership::Leave, |member| member.membership())
}
}
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 878cb806b..dc9eb0fd3 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -41,7 +41,7 @@ use matrix_sdk::{
serde::Raw,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
},
- DisplayName, Result as MatrixResult,
+ DisplayName, Result as MatrixResult, RoomMember,
};
use ruma::events::SyncEphemeralRoomEvent;
@@ -1232,7 +1232,7 @@ impl Room {
priv_.members_loaded.set(true);
let matrix_room = self.matrix_room();
- let handle = spawn_tokio!(async move { matrix_room.active_members().await });
+ let handle = spawn_tokio!(async move { matrix_room.members().await });
spawn!(
glib::PRIORITY_LOW,
clone!(@weak self as obj => async move {
@@ -1241,6 +1241,9 @@ impl Room {
match handle.await.unwrap() {
Ok(members) => {
// Add all members needed to display room events.
+ let members: Vec<RoomMember> = members.into_iter().filter(|member| {
+ &MembershipState::Leave != member.membership()
+ }).collect();
obj.members().update_from_room_members(&members);
},
Err(error) => {
diff --git a/src/utils.rs b/src/utils.rs
index 8d5002615..dcb190f0f 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -357,6 +357,11 @@ impl TemplateCallbacks {
fn object_is_some(obj: Option<glib::Object>) -> bool {
obj.is_some()
}
+
+ #[template_callback]
+ fn invert_boolean(boolean: bool) -> bool {
+ !boolean
+ }
}
/// The result of a password validation.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]