fractal/session/model/room/
member.rs1use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
2use matrix_sdk::room::RoomMember;
3use ruma::{
4 events::room::{
5 member::MembershipState,
6 power_levels::{NotificationPowerLevelType, PowerLevelAction},
7 },
8 OwnedEventId, OwnedUserId,
9};
10use tracing::{debug, error};
11
12use super::{
13 permissions::{PowerLevel, POWER_LEVEL_MAX, POWER_LEVEL_MIN},
14 MemberRole, Room,
15};
16use crate::{components::PillSource, prelude::*, session::model::User, spawn, spawn_tokio};
17
18#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum, glib::Variant)]
20#[enum_type(name = "Membership")]
21pub enum Membership {
22 #[default]
24 Leave,
25 Join,
27 Invite,
29 Ban,
31 Knock,
33 Unsupported,
35}
36
37impl Membership {
38 pub(crate) fn icon_name(self) -> &'static str {
40 match self {
41 Self::Invite => "user-add-symbolic",
42 Self::Ban => "blocked-symbolic",
43 _ => "users-symbolic",
44 }
45 }
46}
47
48impl From<&MembershipState> for Membership {
49 fn from(state: &MembershipState) -> Self {
50 match state {
51 MembershipState::Leave => Membership::Leave,
52 MembershipState::Join => Membership::Join,
53 MembershipState::Invite => Membership::Invite,
54 MembershipState::Ban => Membership::Ban,
55 MembershipState::Knock => Membership::Knock,
56 _ => Membership::Unsupported,
57 }
58 }
59}
60
61impl From<MembershipState> for Membership {
62 fn from(state: MembershipState) -> Self {
63 Membership::from(&state)
64 }
65}
66
67mod imp {
68 use std::cell::{Cell, OnceCell, RefCell};
69
70 use super::*;
71
72 #[derive(Debug, Default, glib::Properties)]
73 #[properties(wrapper_type = super::Member)]
74 pub struct Member {
75 #[property(get, set = Self::set_room, construct_only)]
77 room: OnceCell<Room>,
78 #[property(get, minimum = POWER_LEVEL_MIN, maximum = POWER_LEVEL_MAX)]
80 power_level: Cell<PowerLevel>,
81 #[property(get, builder(MemberRole::default()))]
83 role: Cell<MemberRole>,
84 #[property(get, builder(Membership::default()))]
86 membership: Cell<Membership>,
87 #[property(get, set = Self::set_latest_activity, explicit_notify)]
89 latest_activity: Cell<u64>,
90 power_level_handlers: RefCell<Vec<glib::SignalHandlerId>>,
91 }
92
93 #[glib::object_subclass]
94 impl ObjectSubclass for Member {
95 const NAME: &'static str = "Member";
96 type Type = super::Member;
97 type ParentType = User;
98 }
99
100 #[glib::derived_properties]
101 impl ObjectImpl for Member {
102 fn dispose(&self) {
103 if let Some(room) = self.room.get() {
104 for handler in self.power_level_handlers.take() {
105 room.permissions().disconnect(handler);
106 }
107 }
108 }
109 }
110
111 impl PillSourceImpl for Member {
112 fn identifier(&self) -> String {
113 self.obj().upcast_ref::<User>().user_id_string()
114 }
115 }
116
117 impl Member {
118 fn set_room(&self, room: Room) {
120 let room = self.room.get_or_init(|| room);
121
122 let default_pl_handler = room
123 .permissions()
124 .connect_default_power_level_notify(clone!(
125 #[weak(rename_to = imp)]
126 self,
127 move |_| {
128 imp.update_role();
129 }
130 ));
131 let mute_pl_handler = room.permissions().connect_mute_power_level_notify(clone!(
132 #[weak(rename_to = imp)]
133 self,
134 move |_| {
135 imp.update_role();
136 }
137 ));
138 self.power_level_handlers
139 .replace(vec![default_pl_handler, mute_pl_handler]);
140 }
141
142 pub(super) fn set_power_level(&self, power_level: PowerLevel) {
144 if self.power_level.get() == power_level {
145 return;
146 }
147
148 self.power_level.set(power_level);
149 self.update_role();
150 self.obj().notify_power_level();
151 }
152
153 fn update_role(&self) {
155 let role = self
156 .room
157 .get()
158 .expect("room is initialized")
159 .permissions()
160 .role(self.power_level.get());
161
162 if self.role.get() == role {
163 return;
164 }
165
166 self.role.set(role);
167 self.obj().notify_role();
168 }
169
170 pub(super) fn set_membership(&self, membership: Membership) {
172 if self.membership.get() == membership {
173 return;
174 }
175
176 self.membership.replace(membership);
177 self.obj().notify_membership();
178 }
179
180 fn set_latest_activity(&self, activity: u64) {
182 if self.latest_activity.get() >= activity {
183 return;
184 }
185
186 self.latest_activity.set(activity);
187 self.obj().notify_latest_activity();
188 }
189 }
190}
191
192glib::wrapper! {
193 pub struct Member(ObjectSubclass<imp::Member>) @extends PillSource, User;
195}
196
197impl Member {
198 pub fn new(room: &Room, user_id: OwnedUserId) -> Self {
199 let session = room.session();
200 let obj = glib::Object::builder::<Self>()
201 .property("session", &session)
202 .property("room", room)
203 .build();
204
205 obj.upcast_ref::<User>().imp().set_user_id(user_id);
206 obj
207 }
208
209 pub(super) fn set_power_level(&self, power_level: PowerLevel) {
211 self.imp().set_power_level(power_level);
212 }
213
214 pub(crate) fn update_from_room_member(&self, member: &RoomMember) {
216 if member.user_id() != self.user_id() {
217 error!("Tried Member update from RoomMember with wrong user ID.");
218 return;
219 }
220
221 self.set_name(member.display_name().map(ToOwned::to_owned));
222 self.set_is_name_ambiguous(member.name_ambiguous());
223 self.avatar_data()
224 .image()
225 .expect("image is set")
226 .set_uri_and_info(member.avatar_url().map(ToOwned::to_owned), None);
227 self.set_power_level(member.power_level());
228 self.imp().set_membership(member.membership().into());
229 }
230
231 pub(crate) fn update(&self) {
233 spawn!(clone!(
234 #[weak(rename_to = obj)]
235 self,
236 async move {
237 obj.update_inner().await;
238 }
239 ));
240 }
241
242 async fn update_inner(&self) {
243 let room = self.room();
244
245 let matrix_room = room.matrix_room().clone();
246 let user_id = self.user_id().clone();
247 let handle = spawn_tokio!(async move { matrix_room.get_member_no_sync(&user_id).await });
248
249 match handle.await.expect("task was not aborted") {
250 Ok(Some(member)) => self.update_from_room_member(&member),
251 Ok(None) => {
252 debug!("Room member {} not found", self.user_id());
253 }
254 Err(error) => {
255 error!("Could not load room member {}: {error}", self.user_id());
256 }
257 }
258 }
259
260 pub(crate) fn redactable_events(&self) -> Vec<OwnedEventId> {
262 self.room()
263 .live_timeline()
264 .redactable_events_for(self.user_id())
265 }
266
267 pub(crate) fn can_notify_room(&self) -> bool {
269 self.room().permissions().user_is_allowed_to(
270 self.user_id(),
271 PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room),
272 )
273 }
274
275 pub(crate) fn search_string(&self) -> String {
277 format!(
278 "{} {} {} {}",
279 self.display_name(),
280 self.user_id(),
281 self.role(),
282 self.power_level(),
283 )
284 }
285}