diff --git a/Cargo.lock b/Cargo.lock index eaddc9957cd..9d1337b9400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4897,8 +4897,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a555b3e7ca84892ef6e81eadb42c783b76ffb939d6ad9781a9da7023a501521" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "assign", "js_int", @@ -4915,8 +4914,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7ec9d6b5fe002fc018b1daa36f888e80a670ffa7f7778d025e90478c0838ad" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "as_variant", "assign", @@ -4932,14 +4930,14 @@ dependencies = [ "serde_html_form", "serde_json", "thiserror", + "url", "web-time", ] [[package]] name = "ruma-common" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ba97203cc4cab8dc10e62fe8156ae5c61d2553f37c3037759fbae601982fb7b" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "as_variant", "base64 0.22.1", @@ -4971,8 +4969,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fddef15eca0faee500016850a8473fb8c07b17b056b2ae5352ef67cf917102e3" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "as_variant", "indexmap 2.2.6", @@ -4996,8 +4993,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea43deab72b76655e36305127894249e33f48696a5af3c8aa048dc42d772af45" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "js_int", "ruma-common", @@ -5009,8 +5005,7 @@ dependencies = [ [[package]] name = "ruma-html" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6d948779da5fb4d1fc2d4c7a3f0cab21453dd14765a72fbee38ef758613d7f" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "as_variant", "html5ever", @@ -5022,8 +5017,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa38974f5901ed4e00e10aec57b9ad3b4d6d6c1a1ae683c51b88700b9f4ffba" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "js_int", "thiserror", @@ -5032,8 +5026,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36857a3350ea611ecc9968dcc4f3d5a03227a6c3fcbb446e8530e3be8852282" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "once_cell", "proc-macro-crate", @@ -5048,8 +5041,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42ef562eddab426c4402d004ea577f9034c74cd1714e087e9e0d47e88662ec2" +source = "git+https://github.com/Hywan/ruma?branch=feat-sliding-sync-list-include-heroes#8ff12161c2e598f3bfff28e543b304dbdad2af27" dependencies = [ "js_int", "ruma-common", diff --git a/Cargo.toml b/Cargo.toml index 229e8a80e97..9e231b936f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ once_cell = "1.16.0" pin-project-lite = "0.2.9" rand = "0.8.5" reqwest = { version = "0.12.4", default-features = false } -ruma = { version = "0.10.1", features = [ +ruma = { git = "https://github.com/Hywan/ruma", branch = "feat-sliding-sync-list-include-heroes", features = [ "client-api-c", "compat-upload-signatures", "compat-user-id", @@ -53,7 +53,7 @@ ruma = { version = "0.10.1", features = [ "unstable-msc3266", "unstable-msc4075" ] } -ruma-common = { version = "0.13.0" } +ruma-common = { git = "https://github.com/Hywan/ruma", branch = "feat-sliding-sync-list-include-heroes" } serde = "1.0.151" serde_html_form = "0.2.0" serde_json = "1.0.91" diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs index 42697385a9c..48c603e83f8 100644 --- a/bindings/matrix-sdk-ffi/src/room_list.rs +++ b/bindings/matrix-sdk-ffi/src/room_list.rs @@ -615,6 +615,7 @@ pub struct RequiredState { pub struct RoomSubscription { pub required_state: Option>, pub timeline_limit: Option, + pub include_heroes: Option, } impl From for RumaRoomSubscription { @@ -623,7 +624,8 @@ impl From for RumaRoomSubscription { required_state: val.required_state.map(|r| r.into_iter().map(|s| (s.key.into(), s.value)).collect() ).unwrap_or_default(), - timeline_limit: val.timeline_limit.map(|u| u.into()) + timeline_limit: val.timeline_limit.map(|u| u.into()), + include_heroes: val.include_heroes, }) } } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 223195b6f9e..b8398770613 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -834,7 +834,7 @@ impl BaseClient { let mut room_info = room.clone_info(); room_info.mark_as_joined(); - room_info.update_summary(&new_info.summary); + room_info.update_from_ruma_summary(&new_info.summary); room_info.set_prev_batch(new_info.timeline.prev_batch.as_deref()); room_info.mark_state_fully_synced(); diff --git a/crates/matrix-sdk-base/src/rooms/mod.rs b/crates/matrix-sdk-base/src/rooms/mod.rs index 6b4054eed96..48ef48c12b5 100644 --- a/crates/matrix-sdk-base/src/rooms/mod.rs +++ b/crates/matrix-sdk-base/src/rooms/mod.rs @@ -126,13 +126,9 @@ impl BaseRoomInfo { &self, joined_member_count: u64, invited_member_count: u64, - heroes: Vec, + heroes: Vec<&str>, ) -> DisplayName { - calculate_room_name( - joined_member_count, - invited_member_count, - heroes.iter().map(|mem| mem.name()).collect::>(), - ) + calculate_room_name(joined_member_count, invited_member_count, heroes) } /// Get the room version of this room. @@ -371,19 +367,21 @@ fn calculate_room_name( invited_member_count: u64, mut heroes: Vec<&str>, ) -> DisplayName { - let heroes_count = heroes.len() as u64; + let num_heroes = heroes.len() as u64; let invited_joined = invited_member_count + joined_member_count; let invited_joined_minus_one = invited_joined.saturating_sub(1); // Stabilize ordering. heroes.sort_unstable(); - let names = if heroes_count >= invited_joined_minus_one { + let names = if num_heroes == 0 && invited_joined > 1 { + format!("{} people", invited_joined) + } else if num_heroes >= invited_joined_minus_one { heroes.join(", ") - } else if heroes_count < invited_joined_minus_one && invited_joined > 1 { + } else if num_heroes < invited_joined_minus_one && invited_joined > 1 { // TODO: What length does the spec want us to use here and in // the `else`? - format!("{}, and {} others", heroes.join(", "), (invited_joined - heroes_count)) + format!("{}, and {} others", heroes.join(", "), (invited_joined - num_heroes)) } else { "".to_owned() }; @@ -581,6 +579,9 @@ mod tests { actual = calculate_room_name(5, 0, vec!["a", "b", "c"]); assert_eq!(DisplayName::Calculated("a, b, c, and 2 others".to_owned()), actual); + actual = calculate_room_name(5, 0, vec![]); + assert_eq!(DisplayName::Calculated("5 people".to_owned()), actual); + actual = calculate_room_name(0, 0, vec![]); assert_eq!(DisplayName::Empty, actual); diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 1870bcc0261..7c54c212c4d 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -22,7 +22,6 @@ use std::{ use bitflags::bitflags; use eyeball::{SharedObservable, Subscriber}; -use futures_util::stream::{self, StreamExt}; #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] use matrix_sdk_common::ring_buffer::RingBuffer; #[cfg(feature = "experimental-sliding-sync")] @@ -113,11 +112,25 @@ pub struct Room { pub struct RoomSummary { /// The heroes of the room, members that should be used for the room display /// name. - heroes: Vec, + /// + /// This was called `heroes` and contained raw `String`s of the `UserId` + /// before; changing the field's name helped with avoiding a migration. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) heroes_user_ids: Vec, + /// The heroes names, as returned by a server, if available. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub(crate) heroes_names: Vec, /// The number of members that are considered to be joined to the room. - joined_member_count: u64, + pub(crate) joined_member_count: u64, /// The number of members that are considered to be invited to the room. - invited_member_count: u64, + pub(crate) invited_member_count: u64, +} + +#[cfg(test)] +impl RoomSummary { + pub(crate) fn heroes(&self) -> &[OwnedUserId] { + &self.heroes_user_ids + } } /// Enum keeping track in which state the room is, e.g. if our own user is @@ -614,7 +627,26 @@ impl Room { let own_user_id = self.own_user_id().as_str(); - let (heroes, guessed_num_members): (Vec, _) = if summary.heroes.is_empty() { + let (heroes, guessed_num_members): (Vec, _) = if !summary.heroes_names.is_empty() { + // Straightforward path: pass through the heroes names, don't give a guess of + // the number of members. + (summary.heroes_names, None) + } else if !summary.heroes_user_ids.is_empty() { + // Use the heroes, if available. + let heroes = summary.heroes_user_ids; + + let mut names = Vec::with_capacity(heroes.len()); + for user_id in heroes { + if user_id == own_user_id { + continue; + } + if let Some(member) = self.get_member(&user_id).await? { + names.push(member.name().to_owned()); + } + } + + (names, None) + } else { let mut members = self.members(RoomMemberships::ACTIVE).await?; // Make the ordering deterministic. @@ -628,30 +660,10 @@ impl Room { members .into_iter() .take(NUM_HEROES) - .filter(|u| u.user_id() != own_user_id) + .filter_map(|u| (u.user_id() != own_user_id).then(|| u.name().to_owned())) .collect(), guessed_num_members, ) - } else { - let mut heroes = summary.heroes; - - // Make the ordering deterministic. - heroes.sort_unstable(); - - let members: Vec<_> = stream::iter(heroes.iter()) - .filter_map(|u| async move { - let user_id = UserId::parse(u.as_str()).ok()?; - if user_id == own_user_id { - return None; - } - self.get_member(&user_id).await.transpose() - }) - .collect() - .await; - - let members: StoreResult> = members.into_iter().collect(); - - (members?, None) }; let (num_joined, num_invited) = match self.state() { @@ -683,7 +695,11 @@ impl Room { "Calculating name for a room", ); - Ok(self.inner.read().base_info.calculate_room_name(num_joined, num_invited, heroes)) + Ok(self.inner.read().base_info.calculate_room_name( + num_joined, + num_invited, + heroes.iter().map(|hero| hero.as_str()).collect(), + )) } /// Subscribe to the inner `RoomInfo`. @@ -1074,15 +1090,27 @@ impl RoomInfo { self.notification_counts = notification_counts; } - /// Update the RoomSummary. + /// Update the RoomSummary from a Ruma `RoomSummary`. /// - /// Returns true if the Summary modified the info, false otherwise. - pub fn update_summary(&mut self, summary: &RumaSummary) -> bool { + /// Returns true if any field has been updated, false otherwise. + pub fn update_from_ruma_summary(&mut self, summary: &RumaSummary) -> bool { let mut changed = false; if !summary.is_empty() { if !summary.heroes.is_empty() { - self.summary.heroes = summary.heroes.clone(); + // Parse the user IDs from Ruma. + self.summary.heroes_user_ids = summary + .heroes + .iter() + .filter_map(|hero| match UserId::parse(hero.as_str()) { + Ok(user_id) => Some(user_id), + Err(err) => { + warn!("error when parsing user id from hero '{hero}': {err}"); + None + } + }) + .collect(); + changed = true; } @@ -1100,6 +1128,25 @@ impl RoomInfo { changed } + /// Updates the joined member count. + #[cfg(feature = "experimental-sliding-sync")] + pub(crate) fn update_joined_member_count(&mut self, count: u64) { + self.summary.joined_member_count = count; + } + + /// Updates the invited member count. + #[cfg(feature = "experimental-sliding-sync")] + pub(crate) fn update_invited_member_count(&mut self, count: u64) { + self.summary.invited_member_count = count; + } + + /// Updates the heroes user ids. + #[cfg(feature = "experimental-sliding-sync")] + pub(crate) fn update_heroes(&mut self, heroes: Vec, names: Vec) { + self.summary.heroes_user_ids = heroes; + self.summary.heroes_names = names; + } + /// The number of active members (invited + joined) in the room. /// /// The return value is saturated at `u64::MAX`. @@ -1418,6 +1465,8 @@ mod tests { // This test exists to make sure we don't accidentally change the // serialized format for `RoomInfo`. + use ruma::owned_user_id; + use super::RoomSummary; use crate::{rooms::BaseRoomInfo, sync::UnreadNotificationsCount}; @@ -1429,7 +1478,8 @@ mod tests { notification_count: 2, }, summary: RoomSummary { - heroes: vec!["Somebody".to_owned()], + heroes_user_ids: vec![owned_user_id!("@somebody:example.org")], + heroes_names: vec![], joined_member_count: 5, invited_member_count: 0, }, @@ -1453,7 +1503,7 @@ mod tests { "notification_count": 2, }, "summary": { - "heroes": ["Somebody"], + "heroes_user_ids": ["@somebody:example.org"], "joined_member_count": 5, "invited_member_count": 0, }, @@ -1504,6 +1554,8 @@ mod tests { // The following JSON should never change if we want to be able to read in old // cached state + + use ruma::owned_user_id; let info_json = json!({ "room_id": "!gda78o:server.tld", "room_state": "Invited", @@ -1512,7 +1564,8 @@ mod tests { "notification_count": 2, }, "summary": { - "heroes": ["Somebody"], + "heroes_user_ids": ["@somebody:example.org"], + "heroes_names": ["Somebody"], "joined_member_count": 5, "invited_member_count": 0, }, @@ -1542,7 +1595,8 @@ mod tests { assert_eq!(info.room_state, RoomState::Invited); assert_eq!(info.notification_counts.highlight_count, 1); assert_eq!(info.notification_counts.notification_count, 2); - assert_eq!(info.summary.heroes, vec!["Somebody".to_owned()]); + assert_eq!(info.summary.heroes_user_ids, vec![owned_user_id!("@somebody:example.org")]); + assert_eq!(info.summary.heroes_names, vec!["Somebody".to_owned()]); assert_eq!(info.summary.joined_member_count, 5); assert_eq!(info.summary.invited_member_count, 0); assert!(info.members_synced); @@ -1857,7 +1911,7 @@ mod tests { changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me")); store.save_changes(&changes).await.unwrap(); - room.inner.update_if(|info| info.update_summary(&summary)); + room.inner.update_if(|info| info.update_from_ruma_summary(&summary)); assert_eq!( room.computed_display_name().await.unwrap(), DisplayName::Calculated("Matthew".to_owned()) @@ -1909,7 +1963,7 @@ mod tests { store.save_changes(&changes).await.unwrap(); - room.inner.update_if(|info| info.update_summary(&summary)); + room.inner.update_if(|info| info.update_from_ruma_summary(&summary)); assert_eq!( room.computed_display_name().await.unwrap(), DisplayName::Calculated("Matthew".to_owned()) @@ -1988,7 +2042,7 @@ mod tests { joined_member_count: Some(7u32.into()), heroes: vec![denis.to_string(), carol.to_string(), bob.to_string(), erica.to_string()], }); - room.inner.update_if(|info| info.update_summary(&summary)); + room.inner.update_if(|info| info.update_from_ruma_summary(&summary)); assert_eq!( room.computed_display_name().await.unwrap(), @@ -2068,7 +2122,7 @@ mod tests { store.save_changes(&changes).await.unwrap(); - room.inner.update_if(|info| info.update_summary(&summary)); + room.inner.update_if(|info| info.update_from_ruma_summary(&summary)); assert_eq!( room.computed_display_name().await.unwrap(), DisplayName::EmptyWas("Matthew".to_owned()) diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index a1b965cf490..6fec35e6a9c 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -21,7 +21,7 @@ use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; use ruma::events::AnyToDeviceEvent; use ruma::{ api::client::sync::sync_events::{ - v3::{self, InvitedRoom, RoomSummary}, + v3::{self, InvitedRoom}, v4, }, events::{AnyRoomAccountDataEvent, AnySyncStateEvent, AnySyncTimelineEvent}, @@ -693,14 +693,22 @@ fn process_room_properties(room_data: &v4::SlidingSyncRoom, room_info: &mut Room } // Sliding sync doesn't have a room summary, nevertheless it contains the joined - // and invited member counts. It likely will never have a heroes concept since - // it calculates the room display name for us. - // - // Let's at least fetch the member counts, since they might be useful. - let mut room_summary = RoomSummary::new(); - room_summary.invited_member_count = room_data.invited_count; - room_summary.joined_member_count = room_data.joined_count; - room_info.update_summary(&room_summary); + // and invited member counts, in addition to the heroes if it's been configured + // to return them (see the [`v4::RoomSubscription::include_heroes`]). + if let Some(count) = room_data.joined_count { + room_info.update_joined_member_count(count.into()); + } + if let Some(count) = room_data.invited_count { + room_info.update_invited_member_count(count.into()); + } + + if let Some(heroes) = &room_data.heroes { + // Filter out all the heroes which don't have a user id or name. + room_info.update_heroes( + heroes.iter().filter_map(|hero| hero.user_id.as_ref()).cloned().collect(), + heroes.iter().filter_map(|hero| hero.name.as_ref()).cloned().collect(), + ); + } room_info.set_prev_batch(room_data.prev_batch.as_deref()); @@ -1273,6 +1281,43 @@ mod tests { assert!(client_room.name().is_none()); } + #[async_test] + async fn test_compute_heroes_from_sliding_sync() { + // Given a logged-in client + let client = logged_in_base_client(None).await; + let room_id = room_id!("!r:e.uk"); + let gordon = user_id!("@gordon:e.uk").to_owned(); + let alice = user_id!("@alice:e.uk").to_owned(); + + // When I send sliding sync response containing a room (with identifiable data + // in `heroes`) + let mut room = v4::SlidingSyncRoom::new(); + room.heroes = Some(vec![ + assign!(v4::SlidingSyncRoomHero::default(), { + user_id: Some(gordon), + name: Some("Gordon".to_string()), + }), + assign!(v4::SlidingSyncRoomHero::default(), { + user_id: Some(alice), + name: Some("Alice".to_string()), + }), + ]); + let response = response_with_room(room_id, room).await; + let _sync_resp = + client.process_sliding_sync(&response, &()).await.expect("Failed to process sync"); + + // Then the room appears in the client. + let client_room = client.get_room(room_id).expect("No room found"); + assert_eq!(client_room.room_id(), room_id); + assert_eq!(client_room.state(), RoomState::Joined); + + // And heroes are part of the summary. + assert_eq!( + client_room.clone_info().summary.heroes(), + &["@gordon:e.uk".to_string(), "@alice:e.uk".to_string()] + ); + } + #[async_test] async fn test_last_event_from_sliding_sync_is_cached() { // Given a logged-in client diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index 208cd5e1c4d..7c6a35b155b 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -459,6 +459,7 @@ fn configure_all_or_visible_rooms_list( ) -> SlidingSyncListBuilder { list_builder .sort(vec!["by_recency".to_owned(), "by_name".to_owned()]) + .include_heroes(Some(true)) .filters(Some(assign!(SyncRequestListFilters::default(), { // As defined in the [SlidingSync MSC](https://github.com/matrix-org/matrix-spec-proposals/blob/9450ced7fb9cf5ea9077d029b3adf36aebfa8709/proposals/3575-sync.md?plain=1#L444) // If unset, both invited and joined rooms are returned. If false, no invited rooms are diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 0b3b91b4f16..1d10ddb6c37 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -275,6 +275,7 @@ async fn test_sync_all_states() -> Result<(), Error> { ["m.room.name", ""], ["m.room.power_levels", ""], ], + "include_heroes": true, "filters": { "is_tombstoned": false, "not_room_types": ["m.space"], @@ -333,6 +334,7 @@ async fn test_sync_all_states() -> Result<(), Error> { ["m.room.encryption", ""], ["m.room.member", "$LAZY"], ], + "include_heroes": true, "filters": { "is_tombstoned": false, "not_room_types": ["m.space"], diff --git a/crates/matrix-sdk/src/sliding_sync/list/builder.rs b/crates/matrix-sdk/src/sliding_sync/list/builder.rs index b41011d08c7..8608a816886 100644 --- a/crates/matrix-sdk/src/sliding_sync/list/builder.rs +++ b/crates/matrix-sdk/src/sliding_sync/list/builder.rs @@ -49,6 +49,7 @@ pub struct SlidingSyncListBuilder { sync_mode: SlidingSyncMode, sort: Vec, required_state: Vec<(StateEventType, String)>, + include_heroes: Option, filters: Option, timeline_limit: Option, pub(crate) name: String, @@ -74,6 +75,7 @@ impl fmt::Debug for SlidingSyncListBuilder { .field("sync_mode", &self.sync_mode) .field("sort", &self.sort) .field("required_state", &self.required_state) + .field("include_heroes", &self.include_heroes) .field("filters", &self.filters) .field("timeline_limit", &self.timeline_limit) .field("name", &self.name) @@ -91,6 +93,7 @@ impl SlidingSyncListBuilder { (StateEventType::RoomEncryption, "".to_owned()), (StateEventType::RoomTombstone, "".to_owned()), ], + include_heroes: None, filters: None, timeline_limit: None, name: name.into(), @@ -132,6 +135,12 @@ impl SlidingSyncListBuilder { self } + /// Include heroes. + pub fn include_heroes(mut self, value: Option) -> Self { + self.include_heroes = value; + self + } + /// Any filters to apply to the query. pub fn filters(mut self, value: Option) -> Self { self.filters = value; @@ -207,6 +216,7 @@ impl SlidingSyncListBuilder { SlidingSyncListStickyParameters::new( self.sort, self.required_state, + self.include_heroes, self.filters, self.timeline_limit, self.bump_event_types, diff --git a/crates/matrix-sdk/src/sliding_sync/list/sticky.rs b/crates/matrix-sdk/src/sliding_sync/list/sticky.rs index cc8566a5539..cd214f2f0a6 100644 --- a/crates/matrix-sdk/src/sliding_sync/list/sticky.rs +++ b/crates/matrix-sdk/src/sliding_sync/list/sticky.rs @@ -16,6 +16,10 @@ pub(super) struct SlidingSyncListStickyParameters { /// Required states to return per room. required_state: Vec<(StateEventType, String)>, + /// Return a stripped variant of membership events for the users used to + /// calculate the room name. + include_heroes: Option, + /// Any filters to apply to the query. filters: Option, @@ -31,13 +35,14 @@ impl SlidingSyncListStickyParameters { pub fn new( sort: Vec, required_state: Vec<(StateEventType, String)>, + include_heroes: Option, filters: Option, timeline_limit: Option, bump_event_types: Vec, ) -> Self { // Consider that each list will have at least one parameter set, so invalidate // it by default. - Self { sort, required_state, filters, timeline_limit, bump_event_types } + Self { sort, required_state, include_heroes, filters, timeline_limit, bump_event_types } } } @@ -58,6 +63,7 @@ impl StickyData for SlidingSyncListStickyParameters { request.sort = self.sort.to_vec(); request.room_details.required_state = self.required_state.to_vec(); request.room_details.timeline_limit = self.timeline_limit.map(Into::into); + request.include_heroes = self.include_heroes; request.filters = self.filters.clone(); request.bump_event_types = self.bump_event_types.clone(); } diff --git a/labs/multiverse/src/main.rs b/labs/multiverse/src/main.rs index 640f87fbdf9..e7908de6d56 100644 --- a/labs/multiverse/src/main.rs +++ b/labs/multiverse/src/main.rs @@ -120,6 +120,16 @@ struct Timeline { task: JoinHandle<()>, } +/// Extra room information, like its display name, etc. +#[derive(Clone)] +struct ExtraRoomInfo { + /// Content of the raw m.room.name event, if available. + raw_name: Option, + + /// Calculated display name for the room. + display_name: Option, +} + struct App { /// Reference to the main SDK client. client: Client, @@ -136,6 +146,9 @@ struct App { /// Ratatui's list of room list entries. room_list_entries: StatefulList, + /// Extra information about rooms. + room_info: Arc>>, + /// Task listening to room list service changes, and spawning timelines. listen_task: JoinHandle<()>, @@ -164,11 +177,14 @@ impl App { let (rooms, stream) = all_rooms.entries(); let rooms = Arc::new(Mutex::new(rooms)); + let room_infos: Arc>> = + Arc::new(Mutex::new(Default::default())); let ui_rooms: Arc>> = Default::default(); let timelines = Arc::new(Mutex::new(HashMap::new())); let r = rooms.clone(); + let ri = room_infos.clone(); let ur = ui_rooms.clone(); let s = sync_service.clone(); let t = timelines.clone(); @@ -176,6 +192,7 @@ impl App { let listen_task = spawn(async move { pin_mut!(stream); let rooms = r; + let room_infos = ri; let ui_rooms = ur; let sync_service = s; let timelines = t; @@ -252,6 +269,15 @@ impl App { new_ui_rooms.insert(room_id, ui_room); } + for (room_id, room) in &new_ui_rooms { + let raw_name = room.name(); + let display_name = room.computed_display_name().await; + room_infos + .lock() + .unwrap() + .insert(room_id.clone(), ExtraRoomInfo { raw_name, display_name }); + } + ui_rooms.lock().unwrap().extend(new_ui_rooms); timelines.lock().unwrap().extend(new_timelines); } @@ -264,6 +290,7 @@ impl App { Ok(Self { sync_service, room_list_entries: StatefulList { state: Default::default(), items: rooms }, + room_info: room_infos, client, listen_task, last_status_message: Default::default(), @@ -502,6 +529,10 @@ impl App { // We can render the header in outer_area. outer_block.render(outer_area, buf); + // Don't keep this lock too long by cloning the content. RAM's free these days, + // right? + let mut room_info = self.room_info.lock().unwrap().clone(); + // Iterate through all elements in the `items` and stylize them. let items: Vec> = self .room_list_entries @@ -519,7 +550,24 @@ impl App { let line = if let Some(room) = item.as_room_id().and_then(|room_id| self.client.get_room(room_id)) { - format!("#{i} {}", room.room_id()) + let room_id = room.room_id(); + let room_info = room_info.remove(room_id); + + let (raw, display) = if let Some(info) = room_info { + (info.raw_name, info.display_name) + } else { + (None, None) + }; + + let room_name = if let Some(n) = display { + format!("{n} ({room_id})") + } else if let Some(n) = raw { + format!("m.room.name:{n} ({room_id})") + } else { + room_id.to_string() + }; + + format!("#{i} {}", room_name) } else { "non-filled room".to_owned() };