[fractal] invite-subpage: Allow inviting users by id to a room



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]