1use std::cmp::Ordering;
16
17use matrix_sdk::{Room, RoomHero, RoomState};
18use ruma::{
19 MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedServerName,
20 OwnedSpaceChildOrder,
21 events::{
22 room::{guest_access::GuestAccess, history_visibility::HistoryVisibility},
23 space::child::HierarchySpaceChildEvent,
24 },
25 room::{JoinRuleSummary, RoomSummary, RoomType},
26};
27
28#[derive(Debug, Clone, PartialEq)]
31pub struct SpaceRoom {
32 pub room_id: OwnedRoomId,
34 pub canonical_alias: Option<OwnedRoomAliasId>,
36 pub name: Option<String>,
38 pub display_name: String,
40 pub topic: Option<String>,
42 pub avatar_url: Option<OwnedMxcUri>,
44 pub room_type: Option<RoomType>,
46 pub num_joined_members: u64,
48 pub join_rule: Option<JoinRuleSummary>,
50 pub world_readable: Option<bool>,
52 pub guest_can_join: bool,
54
55 pub is_direct: Option<bool>,
60 pub children_count: u64,
62 pub state: Option<RoomState>,
64 pub heroes: Option<Vec<RoomHero>>,
66 pub via: Vec<OwnedServerName>,
68}
69
70impl SpaceRoom {
71 pub(crate) fn new_from_summary(
74 summary: &RoomSummary,
75 known_room: Option<Room>,
76 children_count: u64,
77 via: Vec<OwnedServerName>,
78 ) -> Self {
79 let display_name = matrix_sdk_base::Room::compute_display_name_with_fields(
80 summary.name.clone(),
81 summary.canonical_alias.as_deref(),
82 known_room.as_ref().map(|r| r.heroes().to_vec()).unwrap_or_default(),
83 summary.num_joined_members.into(),
84 )
85 .to_string();
86
87 Self {
88 room_id: summary.room_id.clone(),
89 canonical_alias: summary.canonical_alias.clone(),
90 name: summary.name.clone(),
91 display_name,
92 topic: summary.topic.clone(),
93 avatar_url: summary.avatar_url.clone(),
94 room_type: summary.room_type.clone(),
95 num_joined_members: summary.num_joined_members.into(),
96 join_rule: Some(summary.join_rule.clone()),
97 world_readable: Some(summary.world_readable),
98 guest_can_join: summary.guest_can_join,
99 is_direct: known_room.as_ref().map(|r| r.direct_targets_length() != 0),
100 children_count,
101 state: known_room.as_ref().map(|r| r.state()),
102 heroes: known_room.map(|r| r.heroes()),
103 via,
104 }
105 }
106
107 pub(crate) fn new_from_known(known_room: &Room, children_count: u64) -> Self {
109 let room_info = known_room.clone_info();
110
111 let name = room_info.name().map(ToOwned::to_owned);
112 let display_name = matrix_sdk_base::Room::compute_display_name_with_fields(
113 name.clone(),
114 room_info.canonical_alias(),
115 room_info.heroes().to_vec(),
116 known_room.joined_members_count(),
117 )
118 .to_string();
119
120 Self {
121 room_id: room_info.room_id().to_owned(),
122 canonical_alias: room_info.canonical_alias().map(ToOwned::to_owned),
123 name,
124 display_name,
125 topic: room_info.topic().map(ToOwned::to_owned),
126 avatar_url: room_info.avatar_url().map(ToOwned::to_owned),
127 room_type: room_info.room_type().cloned(),
128 num_joined_members: known_room.joined_members_count(),
129 join_rule: room_info.join_rule().cloned().map(Into::into),
130 world_readable: room_info
131 .history_visibility()
132 .map(|vis| *vis == HistoryVisibility::WorldReadable),
133 guest_can_join: known_room.guest_access() == GuestAccess::CanJoin,
134 is_direct: Some(known_room.direct_targets_length() != 0),
135 children_count,
136 state: Some(known_room.state()),
137 heroes: Some(room_info.heroes().to_vec()),
138 via: vec![],
139 }
140 }
141
142 pub(crate) fn compare_rooms(
145 a: &SpaceRoom,
146 b: &SpaceRoom,
147 a_state: Option<SpaceRoomChildState>,
148 b_state: Option<SpaceRoomChildState>,
149 ) -> Ordering {
150 match (a_state, b_state) {
151 (Some(a_state), Some(b_state)) => match (&a_state.order, &b_state.order) {
152 (Some(a_order), Some(b_order)) => a_order
153 .cmp(b_order)
154 .then(a_state.origin_server_ts.cmp(&b_state.origin_server_ts))
155 .then(a.room_id.cmp(&b.room_id)),
156 (Some(_), None) => Ordering::Less,
157 (None, Some(_)) => Ordering::Greater,
158 (None, None) => a_state
159 .origin_server_ts
160 .cmp(&b_state.origin_server_ts)
161 .then(a.room_id.to_string().cmp(&b.room_id.to_string())),
162 },
163 _ => a.room_id.to_string().cmp(&b.room_id.to_string()),
164 }
165 }
166}
167
168#[derive(Clone, Debug)]
169pub(crate) struct SpaceRoomChildState {
170 pub(crate) order: Option<OwnedSpaceChildOrder>,
171 pub(crate) origin_server_ts: MilliSecondsSinceUnixEpoch,
172}
173
174impl From<&HierarchySpaceChildEvent> for SpaceRoomChildState {
175 fn from(event: &HierarchySpaceChildEvent) -> Self {
176 SpaceRoomChildState {
177 order: event.content.order.clone(),
178 origin_server_ts: event.origin_server_ts,
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use std::cmp::Ordering;
186
187 use matrix_sdk_test::async_test;
188 use ruma::{MilliSecondsSinceUnixEpoch, OwnedRoomId, SpaceChildOrder, owned_room_id, uint};
189
190 use crate::spaces::{SpaceRoom, room::SpaceRoomChildState};
191
192 #[async_test]
193 async fn test_room_list_sorting() {
194 assert_eq!(
197 SpaceRoom::compare_rooms(
198 &make_space_room(owned_room_id!("!A:a.b")),
199 &make_space_room(owned_room_id!("!B:a.b")),
200 None,
201 None
202 ),
203 Ordering::Less
204 );
205
206 assert_eq!(
207 SpaceRoom::compare_rooms(
208 &make_space_room(owned_room_id!("!Marțolea:a.b")),
209 &make_space_room(owned_room_id!("!Luana:a.b")),
210 None,
211 None,
212 ),
213 Ordering::Greater
214 );
215
216 assert_eq!(
219 SpaceRoom::compare_rooms(
220 &make_space_room(owned_room_id!("!Luana:a.b")),
221 &make_space_room(owned_room_id!("!Marțolea:a.b")),
222 Some(SpaceRoomChildState {
223 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
224 order: None
225 }),
226 Some(SpaceRoomChildState {
227 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(0)),
228 order: None
229 })
230 ),
231 Ordering::Greater
232 );
233
234 assert_eq!(
236 SpaceRoom::compare_rooms(
237 &make_space_room(owned_room_id!("!Joiana:a.b"),),
238 &make_space_room(owned_room_id!("!Mioara:a.b"),),
239 Some(SpaceRoomChildState {
240 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(123)),
241 order: Some(SpaceChildOrder::parse("second").unwrap())
242 }),
243 Some(SpaceRoomChildState {
244 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(234)),
245 order: Some(SpaceChildOrder::parse("first").unwrap())
246 }),
247 ),
248 Ordering::Greater
249 );
250
251 assert_eq!(
253 SpaceRoom::compare_rooms(
254 &make_space_room(owned_room_id!("!Joiana:a.b")),
255 &make_space_room(owned_room_id!("!Mioara:a.b")),
256 Some(SpaceRoomChildState {
257 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
258 order: Some(SpaceChildOrder::parse("Same pasture").unwrap())
259 }),
260 Some(SpaceRoomChildState {
261 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(0)),
262 order: Some(SpaceChildOrder::parse("Same pasture").unwrap())
263 }),
264 ),
265 Ordering::Greater
266 );
267
268 assert_eq!(
271 SpaceRoom::compare_rooms(
272 &make_space_room(owned_room_id!("!Joiana:a.b")),
273 &make_space_room(owned_room_id!("!Mioara:a.b")),
274 Some(SpaceRoomChildState {
275 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(0)),
276 order: Some(SpaceChildOrder::parse("Same pasture").unwrap())
277 }),
278 Some(SpaceRoomChildState {
279 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(0)),
280 order: Some(SpaceChildOrder::parse("Same pasture").unwrap())
281 }),
282 ),
283 Ordering::Less
284 );
285
286 assert_eq!(
289 SpaceRoom::compare_rooms(
290 &make_space_room(owned_room_id!("!Viola:a.b")),
291 &make_space_room(owned_room_id!("!Sâmbotina:a.b")),
292 None,
293 Some(SpaceRoomChildState {
294 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(0)),
295 order: None
296 }),
297 ),
298 Ordering::Greater
299 );
300
301 assert_eq!(
304 SpaceRoom::compare_rooms(
305 &make_space_room(owned_room_id!("!Sâmbotina:a.b")),
306 &make_space_room(owned_room_id!("!Dumana:a.b")),
307 Some(SpaceRoomChildState {
308 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
309 order: None
310 }),
311 Some(SpaceRoomChildState {
312 origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1)),
313 order: Some(SpaceChildOrder::parse("Some pasture").unwrap())
314 }),
315 ),
316 Ordering::Greater
317 );
318 }
319
320 fn make_space_room(room_id: OwnedRoomId) -> SpaceRoom {
321 SpaceRoom {
322 room_id,
323 canonical_alias: None,
324 name: Some("New room name".to_owned()),
325 display_name: "Empty room".to_owned(),
326 topic: None,
327 avatar_url: None,
328 room_type: None,
329 num_joined_members: 0,
330 join_rule: None,
331 world_readable: None,
332 guest_can_join: false,
333 is_direct: None,
334 children_count: 0,
335 state: None,
336 heroes: None,
337 via: vec![],
338 }
339 }
340}