Skip to content

Commit 304350f

Browse files
pixlwavebnjbvr
andauthored
ffi: Expose room heroes (adding support for avatars). (#3503)
This PR makes 2 changes: - Updates the storage of room heroes to be a single array containing the user's complete profile. - Exposes these to the FFI so that client apps can use these for avatar colours/clusters. Closes #2702 (again, now with avatars 🖼️) --- * rooms: Store heroes as a complete user profile. * ffi: Expose room heroes on Room and RoomInfo. * chore: Remove TODO comment. * Update crates/matrix-sdk-base/src/rooms/normal.rs Signed-off-by: Benjamin Bouvier <[email protected]> --------- Signed-off-by: Benjamin Bouvier <[email protected]> Co-authored-by: Benjamin Bouvier <[email protected]>
1 parent 8988094 commit 304350f

File tree

7 files changed

+146
-49
lines changed

7 files changed

+146
-49
lines changed

bindings/matrix-sdk-ffi/src/room.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
44
use matrix_sdk::{
55
event_cache::paginator::PaginatorError,
66
room::{power_levels::RoomPowerLevelChanges, Room as SdkRoom, RoomMemberRole},
7-
ComposerDraft, RoomMemberships, RoomState,
7+
ComposerDraft, RoomHero as SdkRoomHero, RoomMemberships, RoomState,
88
};
99
use matrix_sdk_ui::timeline::{PaginationError, RoomExt, TimelineFocus};
1010
use mime::Mime;
@@ -126,6 +126,11 @@ impl Room {
126126
self.inner.state().into()
127127
}
128128

129+
/// Returns the room heroes for this room.
130+
pub fn heroes(&self) -> Vec<RoomHero> {
131+
self.inner.heroes().into_iter().map(Into::into).collect()
132+
}
133+
129134
/// Is there a non expired membership with application "m.call" and scope
130135
/// "m.room" in this room.
131136
pub fn has_active_room_call(&self) -> bool {
@@ -776,6 +781,27 @@ impl RoomMembersIterator {
776781
}
777782
}
778783

784+
/// Information about a member considered to be a room hero.
785+
#[derive(uniffi::Record)]
786+
pub struct RoomHero {
787+
/// The user ID of the hero.
788+
user_id: String,
789+
/// The display name of the hero.
790+
display_name: Option<String>,
791+
/// The avatar URL of the hero.
792+
avatar_url: Option<String>,
793+
}
794+
795+
impl From<SdkRoomHero> for RoomHero {
796+
fn from(value: SdkRoomHero) -> Self {
797+
Self {
798+
user_id: value.user_id.to_string(),
799+
display_name: value.display_name.clone(),
800+
avatar_url: value.avatar_url.as_ref().map(ToString::to_string),
801+
}
802+
}
803+
}
804+
779805
/// An update for a particular user's power level within the room.
780806
#[derive(uniffi::Record)]
781807
pub struct UserPowerLevelUpdate {

bindings/matrix-sdk-ffi/src/room_info.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::collections::HashMap;
33
use matrix_sdk::RoomState;
44

55
use crate::{
6-
notification_settings::RoomNotificationMode, room::Membership, room_member::RoomMember,
6+
notification_settings::RoomNotificationMode,
7+
room::{Membership, RoomHero},
8+
room_member::RoomMember,
79
};
810

911
#[derive(uniffi::Record)]
@@ -30,6 +32,7 @@ pub struct RoomInfo {
3032
/// Can be missing if the room membership invite event is missing from the
3133
/// store.
3234
inviter: Option<RoomMember>,
35+
heroes: Vec<RoomHero>,
3336
active_members_count: u64,
3437
invited_members_count: u64,
3538
joined_members_count: u64,
@@ -85,6 +88,7 @@ impl RoomInfo {
8588
.map(Into::into),
8689
_ => None,
8790
},
91+
heroes: room.heroes().into_iter().map(Into::into).collect(),
8892
active_members_count: room.active_members_count(),
8993
invited_members_count: room.invited_members_count(),
9094
joined_members_count: room.joined_members_count(),

crates/matrix-sdk-base/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ pub use http;
5252
pub use matrix_sdk_crypto as crypto;
5353
pub use once_cell;
5454
pub use rooms::{
55-
DisplayName, Room, RoomCreateWithCreatorEventContent, RoomInfo, RoomInfoUpdate, RoomMember,
56-
RoomMemberships, RoomState, RoomStateFilter,
55+
DisplayName, Room, RoomCreateWithCreatorEventContent, RoomHero, RoomInfo, RoomInfoUpdate,
56+
RoomMember, RoomMemberships, RoomState, RoomStateFilter,
5757
};
5858
pub use store::{
5959
ComposerDraft, StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError,

crates/matrix-sdk-base/src/rooms/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{
1111

1212
use bitflags::bitflags;
1313
pub use members::RoomMember;
14-
pub use normal::{Room, RoomInfo, RoomInfoUpdate, RoomState, RoomStateFilter};
14+
pub use normal::{Room, RoomHero, RoomInfo, RoomInfoUpdate, RoomState, RoomStateFilter};
1515
use ruma::{
1616
assign,
1717
events::{

crates/matrix-sdk-base/src/rooms/normal.rs

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -110,26 +110,36 @@ pub struct Room {
110110
/// calculate the room display name.
111111
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
112112
pub struct RoomSummary {
113-
/// The heroes of the room, members that should be used for the room display
114-
/// name.
113+
/// The heroes of the room, members that can be used as a fallback for the
114+
/// room's display name or avatar if these haven't been set.
115115
///
116116
/// This was called `heroes` and contained raw `String`s of the `UserId`
117-
/// before; changing the field's name helped with avoiding a migration.
117+
/// before. Following this it was called `heroes_user_ids` and a
118+
/// complimentary `heroes_names` existed too; changing the field's name
119+
/// helped with avoiding a migration.
118120
#[serde(default, skip_serializing_if = "Vec::is_empty")]
119-
pub(crate) heroes_user_ids: Vec<OwnedUserId>,
120-
/// The heroes names, as returned by a server, if available.
121-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
122-
pub(crate) heroes_names: Vec<String>,
121+
pub(crate) room_heroes: Vec<RoomHero>,
123122
/// The number of members that are considered to be joined to the room.
124123
pub(crate) joined_member_count: u64,
125124
/// The number of members that are considered to be invited to the room.
126125
pub(crate) invited_member_count: u64,
127126
}
128127

128+
/// Information about a member considered to be a room hero.
129+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
130+
pub struct RoomHero {
131+
/// The user id of the hero.
132+
pub user_id: OwnedUserId,
133+
/// The display name of the hero.
134+
pub display_name: Option<String>,
135+
/// The avatar url of the hero.
136+
pub avatar_url: Option<OwnedMxcUri>,
137+
}
138+
129139
#[cfg(test)]
130140
impl RoomSummary {
131-
pub(crate) fn heroes(&self) -> &[OwnedUserId] {
132-
&self.heroes_user_ids
141+
pub(crate) fn heroes(&self) -> &[RoomHero] {
142+
&self.room_heroes
133143
}
134144
}
135145

@@ -517,21 +527,26 @@ impl Room {
517527
// From here, use some heroes to compute the room's name.
518528
let own_user_id = self.own_user_id().as_str();
519529

520-
let (heroes, num_joined_guess): (Vec<String>, _) = if !summary.heroes_names.is_empty() {
521-
// Straightforward path: pass through the heroes names, don't give a guess of
522-
// the number of members.
523-
(summary.heroes_names, None)
524-
} else if !summary.heroes_user_ids.is_empty() {
525-
// Use the heroes, if available.
526-
let heroes = summary.heroes_user_ids;
527-
528-
let mut names = Vec::with_capacity(heroes.len());
529-
for user_id in heroes {
530-
if user_id == own_user_id {
530+
let (heroes, num_joined_guess): (Vec<String>, _) = if !summary.room_heroes.is_empty() {
531+
let mut names = Vec::with_capacity(summary.room_heroes.len());
532+
for hero in &summary.room_heroes {
533+
if hero.user_id == own_user_id {
534+
continue;
535+
}
536+
if let Some(display_name) = &hero.display_name {
537+
names.push(display_name.clone());
531538
continue;
532539
}
533-
if let Some(member) = self.get_member(&user_id).await? {
534-
names.push(member.name().to_owned());
540+
match self.get_member(&hero.user_id).await {
541+
Ok(Some(member)) => {
542+
names.push(member.name().to_owned());
543+
}
544+
Ok(None) => {
545+
warn!("Ignoring hero, no member info for {}", hero.user_id);
546+
}
547+
Err(error) => {
548+
warn!("Ignoring hero, error getting member: {}", error);
549+
}
535550
}
536551
}
537552

@@ -694,6 +709,11 @@ impl Room {
694709
Ok(members)
695710
}
696711

712+
/// Get the heroes for this room.
713+
pub fn heroes(&self) -> Vec<RoomHero> {
714+
self.inner.read().heroes().to_vec()
715+
}
716+
697717
/// Get the list of `RoomMember`s that are considered to be joined members
698718
/// of this room.
699719
#[deprecated = "Use members with RoomMemberships::JOIN instead"]
@@ -1128,7 +1148,16 @@ impl RoomInfo {
11281148

11291149
if !summary.is_empty() {
11301150
if !summary.heroes.is_empty() {
1131-
self.summary.heroes_user_ids = summary.heroes.clone();
1151+
self.summary.room_heroes = summary
1152+
.heroes
1153+
.iter()
1154+
.map(|hero_id| RoomHero {
1155+
user_id: hero_id.to_owned(),
1156+
display_name: None,
1157+
avatar_url: None,
1158+
})
1159+
.collect();
1160+
11321161
changed = true;
11331162
}
11341163

@@ -1158,11 +1187,15 @@ impl RoomInfo {
11581187
self.summary.invited_member_count = count;
11591188
}
11601189

1161-
/// Updates the heroes user ids.
1190+
/// Updates the room heroes.
11621191
#[cfg(feature = "experimental-sliding-sync")]
1163-
pub(crate) fn update_heroes(&mut self, heroes: Vec<OwnedUserId>, names: Vec<String>) {
1164-
self.summary.heroes_user_ids = heroes;
1165-
self.summary.heroes_names = names;
1192+
pub(crate) fn update_heroes(&mut self, heroes: Vec<RoomHero>) {
1193+
self.summary.room_heroes = heroes;
1194+
}
1195+
1196+
/// The heroes for this room.
1197+
pub fn heroes(&self) -> &[RoomHero] {
1198+
&self.summary.room_heroes
11661199
}
11671200

11681201
/// The number of active members (invited + joined) in the room.
@@ -1509,7 +1542,7 @@ mod tests {
15091542

15101543
#[cfg(feature = "experimental-sliding-sync")]
15111544
use super::SyncInfo;
1512-
use super::{compute_display_name_from_heroes, Room, RoomInfo, RoomState};
1545+
use super::{compute_display_name_from_heroes, Room, RoomHero, RoomInfo, RoomState};
15131546
#[cfg(any(feature = "experimental-sliding-sync", feature = "e2e-encryption"))]
15141547
use crate::latest_event::LatestEvent;
15151548
use crate::{
@@ -1536,8 +1569,11 @@ mod tests {
15361569
notification_count: 2,
15371570
},
15381571
summary: RoomSummary {
1539-
heroes_user_ids: vec![owned_user_id!("@somebody:example.org")],
1540-
heroes_names: vec![],
1572+
room_heroes: vec![RoomHero {
1573+
user_id: owned_user_id!("@somebody:example.org"),
1574+
display_name: None,
1575+
avatar_url: None,
1576+
}],
15411577
joined_member_count: 5,
15421578
invited_member_count: 0,
15431579
},
@@ -1562,7 +1598,11 @@ mod tests {
15621598
"notification_count": 2,
15631599
},
15641600
"summary": {
1565-
"heroes_user_ids": ["@somebody:example.org"],
1601+
"room_heroes": [{
1602+
"user_id": "@somebody:example.org",
1603+
"display_name": null,
1604+
"avatar_url": null
1605+
}],
15661606
"joined_member_count": 5,
15671607
"invited_member_count": 0,
15681608
},
@@ -1614,7 +1654,7 @@ mod tests {
16141654
// The following JSON should never change if we want to be able to read in old
16151655
// cached state
16161656

1617-
use ruma::owned_user_id;
1657+
use ruma::{owned_mxc_uri, owned_user_id};
16181658
let info_json = json!({
16191659
"room_id": "!gda78o:server.tld",
16201660
"room_state": "Invited",
@@ -1623,8 +1663,11 @@ mod tests {
16231663
"notification_count": 2,
16241664
},
16251665
"summary": {
1626-
"heroes_user_ids": ["@somebody:example.org"],
1627-
"heroes_names": ["Somebody"],
1666+
"room_heroes": [{
1667+
"user_id": "@somebody:example.org",
1668+
"display_name": "Somebody",
1669+
"avatar_url": "mxc://example.org/abc"
1670+
}],
16281671
"joined_member_count": 5,
16291672
"invited_member_count": 0,
16301673
},
@@ -1655,8 +1698,14 @@ mod tests {
16551698
assert_eq!(info.room_state, RoomState::Invited);
16561699
assert_eq!(info.notification_counts.highlight_count, 1);
16571700
assert_eq!(info.notification_counts.notification_count, 2);
1658-
assert_eq!(info.summary.heroes_user_ids, vec![owned_user_id!("@somebody:example.org")]);
1659-
assert_eq!(info.summary.heroes_names, vec!["Somebody".to_owned()]);
1701+
assert_eq!(
1702+
info.summary.room_heroes,
1703+
vec![RoomHero {
1704+
user_id: owned_user_id!("@somebody:example.org"),
1705+
display_name: Some("Somebody".to_owned()),
1706+
avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
1707+
}]
1708+
);
16601709
assert_eq!(info.summary.joined_member_count, 5);
16611710
assert_eq!(info.summary.invited_member_count, 0);
16621711
assert!(info.members_synced);

crates/matrix-sdk-base/src/sliding_sync.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use crate::RoomMemberships;
3838
use crate::{
3939
error::Result,
4040
read_receipts::{compute_unread_counts, PreviousEventsProvider},
41-
rooms::RoomState,
41+
rooms::{normal::RoomHero, RoomState},
4242
store::{ambiguity_map::AmbiguityCache, StateChanges, Store},
4343
sync::{JoinedRoomUpdate, LeftRoomUpdate, Notification, RoomUpdates, SyncResponse},
4444
Room, RoomInfo,
@@ -711,10 +711,15 @@ fn process_room_properties(room_data: &v4::SlidingSyncRoom, room_info: &mut Room
711711
}
712712

713713
if let Some(heroes) = &room_data.heroes {
714-
// Filter out all the heroes which don't have a user id or name.
715714
room_info.update_heroes(
716-
heroes.iter().map(|hero| &hero.user_id).cloned().collect(),
717-
heroes.iter().filter_map(|hero| hero.name.as_ref()).cloned().collect(),
715+
heroes
716+
.iter()
717+
.map(|hero| RoomHero {
718+
user_id: hero.user_id.clone(),
719+
display_name: hero.name.clone(),
720+
avatar_url: hero.avatar.clone(),
721+
})
722+
.collect(),
718723
);
719724
}
720725

@@ -750,15 +755,16 @@ mod tests {
750755
AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
751756
StateEventContent,
752757
},
753-
mxc_uri, room_alias_id, room_id,
758+
mxc_uri, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
754759
serde::Raw,
755760
uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
756761
};
757762
use serde_json::json;
758763

759764
use super::cache_latest_events;
760765
use crate::{
761-
store::MemoryStore, test_utils::logged_in_base_client, BaseClient, Room, RoomState,
766+
rooms::normal::RoomHero, store::MemoryStore, test_utils::logged_in_base_client, BaseClient,
767+
Room, RoomState,
762768
};
763769

764770
#[async_test]
@@ -1356,6 +1362,7 @@ mod tests {
13561362
}),
13571363
assign!(v4::SlidingSyncRoomHero::new(alice), {
13581364
name: Some("Alice".to_owned()),
1365+
avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
13591366
}),
13601367
]);
13611368
let response = response_with_room(room_id, room);
@@ -1370,7 +1377,18 @@ mod tests {
13701377
// And heroes are part of the summary.
13711378
assert_eq!(
13721379
client_room.clone_info().summary.heroes(),
1373-
&["@gordon:e.uk".to_owned(), "@alice:e.uk".to_owned()]
1380+
&[
1381+
RoomHero {
1382+
user_id: owned_user_id!("@gordon:e.uk"),
1383+
display_name: Some("Gordon".to_owned()),
1384+
avatar_url: None
1385+
},
1386+
RoomHero {
1387+
user_id: owned_user_id!("@alice:e.uk"),
1388+
display_name: Some("Alice".to_owned()),
1389+
avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1390+
},
1391+
]
13741392
);
13751393
}
13761394

crates/matrix-sdk/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use matrix_sdk_base::crypto;
2323
pub use matrix_sdk_base::{
2424
deserialized_responses,
2525
store::{ComposerDraft, DynStateStore, MemoryStore, StateStoreExt},
26-
DisplayName, Room as BaseRoom, RoomCreateWithCreatorEventContent, RoomInfo,
26+
DisplayName, Room as BaseRoom, RoomCreateWithCreatorEventContent, RoomHero, RoomInfo,
2727
RoomMember as BaseRoomMember, RoomMemberships, RoomState, SessionMeta, StateChanges,
2828
StateStore, StoreError,
2929
};

0 commit comments

Comments
 (0)