matrix_sdk_ffi/
room_preview.rs

1use anyhow::Context as _;
2use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
3use ruma::{room::RoomType as RumaRoomType, space::SpaceRoomJoinRule};
4use tracing::warn;
5
6use crate::{
7    client::JoinRule,
8    error::ClientError,
9    room::{Membership, RoomHero},
10    room_member::RoomMember,
11    utils::AsyncRuntimeDropped,
12};
13
14/// A room preview for a room. It's intended to be used to represent rooms that
15/// aren't joined yet.
16#[derive(uniffi::Object)]
17pub struct RoomPreview {
18    inner: SdkRoomPreview,
19    client: AsyncRuntimeDropped<Client>,
20}
21
22#[matrix_sdk_ffi_macros::export]
23impl RoomPreview {
24    /// Returns the room info the preview contains.
25    pub fn info(&self) -> Result<RoomPreviewInfo, ClientError> {
26        let info = &self.inner;
27        Ok(RoomPreviewInfo {
28            room_id: info.room_id.to_string(),
29            canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
30            name: info.name.clone(),
31            topic: info.topic.clone(),
32            avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
33            num_joined_members: info.num_joined_members,
34            num_active_members: info.num_active_members,
35            room_type: info.room_type.as_ref().into(),
36            is_history_world_readable: info.is_world_readable,
37            membership: info.state.map(|state| state.into()),
38            join_rule: info
39                .join_rule
40                .clone()
41                .try_into()
42                .map_err(|_| anyhow::anyhow!("unhandled SpaceRoomJoinRule kind"))?,
43            is_direct: info.is_direct,
44            heroes: info
45                .heroes
46                .as_ref()
47                .map(|heroes| heroes.iter().map(|h| h.to_owned().into()).collect()),
48        })
49    }
50
51    /// Leave the room if the room preview state is either joined, invited or
52    /// knocked.
53    ///
54    /// Will return an error otherwise.
55    pub async fn leave(&self) -> Result<(), ClientError> {
56        let room =
57            self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
58        room.leave().await.map_err(Into::into)
59    }
60
61    /// Get the user who created the invite, if any.
62    pub async fn inviter(&self) -> Option<RoomMember> {
63        let room = self.client.get_room(&self.inner.room_id)?;
64        let invite_details = room.invite_details().await.ok()?;
65        invite_details.inviter.and_then(|m| m.try_into().ok())
66    }
67
68    /// Forget the room if we had access to it, and it was left or banned.
69    pub async fn forget(&self) -> Result<(), ClientError> {
70        let room =
71            self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
72        room.forget().await?;
73        Ok(())
74    }
75
76    /// Get the membership details for the current user.
77    pub async fn own_membership_details(&self) -> Option<RoomMembershipDetails> {
78        let room = self.client.get_room(&self.inner.room_id)?;
79
80        let (own_member, sender_member) = match room.own_membership_details().await {
81            Ok(memberships) => memberships,
82            Err(error) => {
83                warn!("Couldn't get membership info: {error}");
84                return None;
85            }
86        };
87
88        Some(RoomMembershipDetails {
89            own_room_member: own_member.try_into().ok()?,
90            sender_room_member: sender_member.and_then(|member| member.try_into().ok()),
91        })
92    }
93}
94
95/// Contains the current user's room member info and the optional room member
96/// info of the sender of the `m.room.member` event that this info represents.
97#[derive(uniffi::Record)]
98pub struct RoomMembershipDetails {
99    pub own_room_member: RoomMember,
100    pub sender_room_member: Option<RoomMember>,
101}
102
103impl RoomPreview {
104    pub(crate) fn new(client: AsyncRuntimeDropped<Client>, inner: SdkRoomPreview) -> Self {
105        Self { client, inner }
106    }
107}
108
109/// The preview of a room, be it invited/joined/left, or not.
110#[derive(uniffi::Record)]
111pub struct RoomPreviewInfo {
112    /// The room id for this room.
113    pub room_id: String,
114    /// The canonical alias for the room.
115    pub canonical_alias: Option<String>,
116    /// The room's name, if set.
117    pub name: Option<String>,
118    /// The room's topic, if set.
119    pub topic: Option<String>,
120    /// The MXC URI to the room's avatar, if set.
121    pub avatar_url: Option<String>,
122    /// The number of joined members.
123    pub num_joined_members: u64,
124    /// The number of active members, if known (joined + invited).
125    pub num_active_members: Option<u64>,
126    /// The room type (space, custom) or nothing, if it's a regular room.
127    pub room_type: RoomType,
128    /// Is the history world-readable for this room?
129    pub is_history_world_readable: Option<bool>,
130    /// The membership state for the current user, if known.
131    pub membership: Option<Membership>,
132    /// The join rule for this room (private, public, knock, etc.).
133    pub join_rule: JoinRule,
134    /// Whether the room is direct or not, if known.
135    pub is_direct: Option<bool>,
136    /// Room heroes.
137    pub heroes: Option<Vec<RoomHero>>,
138}
139
140impl TryFrom<SpaceRoomJoinRule> for JoinRule {
141    type Error = ();
142
143    fn try_from(join_rule: SpaceRoomJoinRule) -> Result<Self, ()> {
144        Ok(match join_rule {
145            SpaceRoomJoinRule::Invite => JoinRule::Invite,
146            SpaceRoomJoinRule::Knock => JoinRule::Knock,
147            SpaceRoomJoinRule::Private => JoinRule::Private,
148            SpaceRoomJoinRule::Restricted => JoinRule::Restricted { rules: Vec::new() },
149            SpaceRoomJoinRule::KnockRestricted => JoinRule::KnockRestricted { rules: Vec::new() },
150            SpaceRoomJoinRule::Public => JoinRule::Public,
151            SpaceRoomJoinRule::_Custom(_) => JoinRule::Custom { repr: join_rule.to_string() },
152            _ => {
153                warn!("unhandled SpaceRoomJoinRule: {join_rule}");
154                return Err(());
155            }
156        })
157    }
158}
159
160/// The type of room for a [`RoomPreviewInfo`].
161#[derive(Debug, Clone, uniffi::Enum)]
162pub enum RoomType {
163    /// It's a plain chat room.
164    Room,
165    /// It's a space that can group several rooms.
166    Space,
167    /// It's a custom implementation.
168    Custom { value: String },
169}
170
171impl From<Option<&RumaRoomType>> for RoomType {
172    fn from(value: Option<&RumaRoomType>) -> Self {
173        match value {
174            Some(RumaRoomType::Space) => RoomType::Space,
175            Some(RumaRoomType::_Custom(_)) => RoomType::Custom {
176                // SAFETY: this was checked in the match branch above
177                value: value.unwrap().to_string(),
178            },
179            _ => RoomType::Room,
180        }
181    }
182}