matrix_sdk_ffi/
room.rs

1use std::{collections::HashMap, pin::pin, sync::Arc};
2
3use anyhow::{Context, Result};
4use futures_util::{pin_mut, StreamExt};
5use matrix_sdk::{
6    crypto::LocalTrust,
7    room::{
8        edit::EditedContent, power_levels::RoomPowerLevelChanges, Room as SdkRoom, RoomMemberRole,
9    },
10    ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType,
11    RoomHero as SdkRoomHero, RoomMemberships, RoomState,
12};
13use matrix_sdk_ui::timeline::{default_event_filter, RoomExt};
14use mime::Mime;
15use ruma::{
16    api::client::room::report_content,
17    assign,
18    events::{
19        call::notify,
20        room::{
21            avatar::ImageInfo as RumaAvatarImageInfo,
22            history_visibility::HistoryVisibility as RumaHistoryVisibility,
23            join_rules::JoinRule as RumaJoinRule, message::RoomMessageEventContentWithoutRelation,
24            power_levels::RoomPowerLevels as RumaPowerLevels, MediaSource,
25        },
26        AnyMessageLikeEventContent, AnySyncTimelineEvent, TimelineEventType,
27    },
28    EventId, Int, OwnedDeviceId, OwnedUserId, RoomAliasId, UserId,
29};
30use tokio::sync::RwLock;
31use tracing::{error, warn};
32
33use super::RUNTIME;
34use crate::{
35    chunk_iterator::ChunkIterator,
36    client::{JoinRule, RoomVisibility},
37    error::{ClientError, MediaInfoError, NotYetImplemented, RoomError},
38    event::{MessageLikeEventType, StateEventType},
39    identity_status_change::IdentityStatusChange,
40    live_location_share::{LastLocation, LiveLocationShare},
41    room_info::RoomInfo,
42    room_member::RoomMember,
43    ruma::{ImageInfo, LocationContent, Mentions, NotifyType},
44    timeline::{
45        configuration::{AllowedMessageTypes, TimelineConfiguration},
46        ReceiptType, SendHandle, Timeline,
47    },
48    utils::u64_to_uint,
49    TaskHandle,
50};
51
52#[derive(Debug, Clone, uniffi::Enum)]
53pub enum Membership {
54    Invited,
55    Joined,
56    Left,
57    Knocked,
58    Banned,
59}
60
61impl From<RoomState> for Membership {
62    fn from(value: RoomState) -> Self {
63        match value {
64            RoomState::Invited => Membership::Invited,
65            RoomState::Joined => Membership::Joined,
66            RoomState::Left => Membership::Left,
67            RoomState::Knocked => Membership::Knocked,
68            RoomState::Banned => Membership::Banned,
69        }
70    }
71}
72
73pub(crate) type TimelineLock = Arc<RwLock<Option<Arc<Timeline>>>>;
74
75#[derive(uniffi::Object)]
76pub struct Room {
77    pub(super) inner: SdkRoom,
78    timeline: TimelineLock,
79}
80
81impl Room {
82    pub(crate) fn new(inner: SdkRoom) -> Self {
83        Room { inner, timeline: Default::default() }
84    }
85
86    pub(crate) fn with_timeline(inner: SdkRoom, timeline: TimelineLock) -> Self {
87        Room { inner, timeline }
88    }
89}
90
91#[matrix_sdk_ffi_macros::export]
92impl Room {
93    /// Returns the room's name from the state event if available, otherwise
94    /// compute a room name based on the room's nature (DM or not) and number of
95    /// members.
96    pub fn display_name(&self) -> Option<String> {
97        Some(self.inner.cached_display_name()?.to_string())
98    }
99
100    /// The raw name as present in the room state event.
101    pub fn raw_name(&self) -> Option<String> {
102        self.inner.name()
103    }
104
105    pub fn topic(&self) -> Option<String> {
106        self.inner.topic()
107    }
108
109    pub fn avatar_url(&self) -> Option<String> {
110        self.inner.avatar_url().map(|m| m.to_string())
111    }
112
113    pub fn is_direct(&self) -> bool {
114        RUNTIME.block_on(self.inner.is_direct()).unwrap_or(false)
115    }
116
117    pub fn is_public(&self) -> bool {
118        self.inner.is_public()
119    }
120
121    pub fn is_space(&self) -> bool {
122        self.inner.is_space()
123    }
124
125    pub fn is_tombstoned(&self) -> bool {
126        self.inner.is_tombstoned()
127    }
128
129    pub fn canonical_alias(&self) -> Option<String> {
130        self.inner.canonical_alias().map(|a| a.to_string())
131    }
132
133    pub fn alternative_aliases(&self) -> Vec<String> {
134        self.inner.alt_aliases().iter().map(|a| a.to_string()).collect()
135    }
136
137    pub fn membership(&self) -> Membership {
138        self.inner.state().into()
139    }
140
141    /// Returns the room heroes for this room.
142    pub fn heroes(&self) -> Vec<RoomHero> {
143        self.inner.heroes().into_iter().map(Into::into).collect()
144    }
145
146    /// Is there a non expired membership with application "m.call" and scope
147    /// "m.room" in this room.
148    pub fn has_active_room_call(&self) -> bool {
149        self.inner.has_active_room_call()
150    }
151
152    /// Returns a Vec of userId's that participate in the room call.
153    ///
154    /// MatrixRTC memberships with application "m.call" and scope "m.room" are
155    /// considered. A user can occur twice if they join with two devices.
156    /// convert to a set depending if the different users are required or the
157    /// amount of sessions.
158    ///
159    /// The vector is ordered by oldest membership user to newest.
160    pub fn active_room_call_participants(&self) -> Vec<String> {
161        self.inner.active_room_call_participants().iter().map(|u| u.to_string()).collect()
162    }
163
164    /// For rooms one is invited to, retrieves the room member information for
165    /// the user who invited the logged-in user to a room.
166    pub async fn inviter(&self) -> Option<RoomMember> {
167        if self.inner.state() == RoomState::Invited {
168            self.inner
169                .invite_details()
170                .await
171                .ok()
172                .and_then(|a| a.inviter)
173                .and_then(|m| m.try_into().ok())
174        } else {
175            None
176        }
177    }
178
179    /// Forces the currently active room key, which is used to encrypt messages,
180    /// to be rotated.
181    ///
182    /// A new room key will be crated and shared with all the room members the
183    /// next time a message will be sent. You don't have to call this method,
184    /// room keys will be rotated automatically when necessary. This method is
185    /// still useful for debugging purposes.
186    pub async fn discard_room_key(&self) -> Result<(), ClientError> {
187        self.inner.discard_room_key().await?;
188        Ok(())
189    }
190
191    pub async fn timeline(&self) -> Result<Arc<Timeline>, ClientError> {
192        let mut write_guard = self.timeline.write().await;
193        if let Some(timeline) = &*write_guard {
194            Ok(timeline.clone())
195        } else {
196            let timeline = Timeline::new(self.inner.timeline().await?);
197            *write_guard = Some(timeline.clone());
198            Ok(timeline)
199        }
200    }
201
202    /// Build a new timeline instance with the given configuration.
203    pub async fn timeline_with_configuration(
204        &self,
205        configuration: TimelineConfiguration,
206    ) -> Result<Arc<Timeline>, ClientError> {
207        let mut builder = matrix_sdk_ui::timeline::Timeline::builder(&self.inner);
208
209        builder = builder.with_focus(configuration.focus.try_into()?);
210
211        if let AllowedMessageTypes::Only { types } = configuration.allowed_message_types {
212            builder = builder.event_filter(move |event, room_version_id| {
213                default_event_filter(event, room_version_id)
214                    && match event {
215                        AnySyncTimelineEvent::MessageLike(msg) => match msg.original_content() {
216                            Some(AnyMessageLikeEventContent::RoomMessage(content)) => {
217                                types.contains(&content.msgtype.into())
218                            }
219                            _ => false,
220                        },
221                        _ => false,
222                    }
223            });
224        }
225
226        if let Some(internal_id_prefix) = configuration.internal_id_prefix {
227            builder = builder.with_internal_id_prefix(internal_id_prefix);
228        }
229
230        builder = builder.with_date_divider_mode(configuration.date_divider_mode.into());
231
232        let timeline = builder.build().await?;
233        Ok(Timeline::new(timeline))
234    }
235
236    pub fn id(&self) -> String {
237        self.inner.room_id().to_string()
238    }
239
240    pub fn is_encrypted(&self) -> Result<bool, ClientError> {
241        Ok(RUNTIME.block_on(self.inner.is_encrypted())?)
242    }
243
244    pub async fn members(&self) -> Result<Arc<RoomMembersIterator>, ClientError> {
245        Ok(Arc::new(RoomMembersIterator::new(self.inner.members(RoomMemberships::empty()).await?)))
246    }
247
248    pub async fn members_no_sync(&self) -> Result<Arc<RoomMembersIterator>, ClientError> {
249        Ok(Arc::new(RoomMembersIterator::new(
250            self.inner.members_no_sync(RoomMemberships::empty()).await?,
251        )))
252    }
253
254    pub async fn member(&self, user_id: String) -> Result<RoomMember, ClientError> {
255        let user_id = UserId::parse(&*user_id)?;
256        let member = self.inner.get_member(&user_id).await?.context("User not found")?;
257        Ok(member.try_into().context("Unknown state membership")?)
258    }
259
260    pub async fn member_avatar_url(&self, user_id: String) -> Result<Option<String>, ClientError> {
261        let user_id = UserId::parse(&*user_id)?;
262        let member = self.inner.get_member(&user_id).await?.context("User not found")?;
263        let avatar_url_string = member.avatar_url().map(|m| m.to_string());
264        Ok(avatar_url_string)
265    }
266
267    pub async fn member_display_name(
268        &self,
269        user_id: String,
270    ) -> Result<Option<String>, ClientError> {
271        let user_id = UserId::parse(&*user_id)?;
272        let member = self.inner.get_member(&user_id).await?.context("User not found")?;
273        let avatar_url_string = member.display_name().map(|m| m.to_owned());
274        Ok(avatar_url_string)
275    }
276
277    pub async fn room_info(&self) -> Result<RoomInfo, ClientError> {
278        RoomInfo::new(&self.inner).await
279    }
280
281    pub fn subscribe_to_room_info_updates(
282        self: Arc<Self>,
283        listener: Box<dyn RoomInfoListener>,
284    ) -> Arc<TaskHandle> {
285        let mut subscriber = self.inner.subscribe_info();
286        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
287            while subscriber.next().await.is_some() {
288                match self.room_info().await {
289                    Ok(room_info) => listener.call(room_info),
290                    Err(e) => {
291                        error!("Failed to compute new RoomInfo: {e}");
292                    }
293                }
294            }
295        })))
296    }
297
298    pub async fn set_is_favourite(
299        &self,
300        is_favourite: bool,
301        tag_order: Option<f64>,
302    ) -> Result<(), ClientError> {
303        self.inner.set_is_favourite(is_favourite, tag_order).await?;
304        Ok(())
305    }
306
307    pub async fn set_is_low_priority(
308        &self,
309        is_low_priority: bool,
310        tag_order: Option<f64>,
311    ) -> Result<(), ClientError> {
312        self.inner.set_is_low_priority(is_low_priority, tag_order).await?;
313        Ok(())
314    }
315
316    /// Send a raw event to the room.
317    ///
318    /// # Arguments
319    ///
320    /// * `event_type` - The type of the event to send.
321    ///
322    /// * `content` - The content of the event to send encoded as JSON string.
323    pub async fn send_raw(&self, event_type: String, content: String) -> Result<(), ClientError> {
324        let content_json: serde_json::Value = serde_json::from_str(&content)
325            .map_err(|e| ClientError::Generic { msg: format!("Failed to parse JSON: {e}") })?;
326
327        self.inner.send_raw(&event_type, content_json).await?;
328
329        Ok(())
330    }
331
332    /// Redacts an event from the room.
333    ///
334    /// # Arguments
335    ///
336    /// * `event_id` - The ID of the event to redact
337    ///
338    /// * `reason` - The reason for the event being redacted (optional). its
339    ///   transaction ID (optional). If not given one is created.
340    pub async fn redact(
341        &self,
342        event_id: String,
343        reason: Option<String>,
344    ) -> Result<(), ClientError> {
345        let event_id = EventId::parse(event_id)?;
346        self.inner.redact(&event_id, reason.as_deref(), None).await?;
347        Ok(())
348    }
349
350    pub fn active_members_count(&self) -> u64 {
351        self.inner.active_members_count()
352    }
353
354    pub fn invited_members_count(&self) -> u64 {
355        self.inner.invited_members_count()
356    }
357
358    pub fn joined_members_count(&self) -> u64 {
359        self.inner.joined_members_count()
360    }
361
362    /// Reports an event from the room.
363    ///
364    /// # Arguments
365    ///
366    /// * `event_id` - The ID of the event to report
367    ///
368    /// * `reason` - The reason for the event being reported (optional).
369    ///
370    /// * `score` - The score to rate this content as where -100 is most
371    ///   offensive and 0 is inoffensive (optional).
372    pub async fn report_content(
373        &self,
374        event_id: String,
375        score: Option<i32>,
376        reason: Option<String>,
377    ) -> Result<(), ClientError> {
378        let event_id = EventId::parse(event_id)?;
379        let int_score = score.map(|value| value.into());
380        self.inner
381            .client()
382            .send(report_content::v3::Request::new(
383                self.inner.room_id().into(),
384                event_id,
385                int_score,
386                reason,
387            ))
388            .await?;
389        Ok(())
390    }
391
392    /// Ignores a user.
393    ///
394    /// # Arguments
395    ///
396    /// * `user_id` - The ID of the user to ignore.
397    pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
398        let user_id = UserId::parse(user_id)?;
399        self.inner.client().account().ignore_user(&user_id).await?;
400        Ok(())
401    }
402
403    /// Leave this room.
404    ///
405    /// Only invited and joined rooms can be left.
406    pub async fn leave(&self) -> Result<(), ClientError> {
407        self.inner.leave().await?;
408        Ok(())
409    }
410
411    /// Join this room.
412    ///
413    /// Only invited and left rooms can be joined via this method.
414    pub async fn join(&self) -> Result<(), ClientError> {
415        self.inner.join().await?;
416        Ok(())
417    }
418
419    /// Sets a new name to the room.
420    pub async fn set_name(&self, name: String) -> Result<(), ClientError> {
421        self.inner.set_name(name).await?;
422        Ok(())
423    }
424
425    /// Sets a new topic in the room.
426    pub async fn set_topic(&self, topic: String) -> Result<(), ClientError> {
427        self.inner.set_room_topic(&topic).await?;
428        Ok(())
429    }
430
431    /// Upload and set the room's avatar.
432    ///
433    /// This will upload the data produced by the reader to the homeserver's
434    /// content repository, and set the room's avatar to the MXC URI for the
435    /// uploaded file.
436    ///
437    /// # Arguments
438    ///
439    /// * `mime_type` - The mime description of the avatar, for example
440    ///   image/jpeg
441    /// * `data` - The raw data that will be uploaded to the homeserver's
442    ///   content repository
443    /// * `media_info` - The media info used as avatar image info.
444    pub async fn upload_avatar(
445        &self,
446        mime_type: String,
447        data: Vec<u8>,
448        media_info: Option<ImageInfo>,
449    ) -> Result<(), ClientError> {
450        let mime: Mime = mime_type.parse()?;
451        self.inner
452            .upload_avatar(
453                &mime,
454                data,
455                media_info
456                    .map(TryInto::try_into)
457                    .transpose()
458                    .map_err(|_| RoomError::InvalidMediaInfo)?,
459            )
460            .await?;
461        Ok(())
462    }
463
464    /// Removes the current room avatar
465    pub async fn remove_avatar(&self) -> Result<(), ClientError> {
466        self.inner.remove_avatar().await?;
467        Ok(())
468    }
469
470    pub async fn invite_user_by_id(&self, user_id: String) -> Result<(), ClientError> {
471        let user =
472            <&UserId>::try_from(user_id.as_str()).context("Could not create user from string")?;
473        self.inner.invite_user_by_id(user).await?;
474        Ok(())
475    }
476
477    pub async fn can_user_redact_own(&self, user_id: String) -> Result<bool, ClientError> {
478        let user_id = UserId::parse(&user_id)?;
479        Ok(self.inner.can_user_redact_own(&user_id).await?)
480    }
481
482    pub async fn can_user_redact_other(&self, user_id: String) -> Result<bool, ClientError> {
483        let user_id = UserId::parse(&user_id)?;
484        Ok(self.inner.can_user_redact_other(&user_id).await?)
485    }
486
487    pub async fn can_user_ban(&self, user_id: String) -> Result<bool, ClientError> {
488        let user_id = UserId::parse(&user_id)?;
489        Ok(self.inner.can_user_ban(&user_id).await?)
490    }
491
492    pub async fn ban_user(
493        &self,
494        user_id: String,
495        reason: Option<String>,
496    ) -> Result<(), ClientError> {
497        let user_id = UserId::parse(&user_id)?;
498        Ok(self.inner.ban_user(&user_id, reason.as_deref()).await?)
499    }
500
501    pub async fn unban_user(
502        &self,
503        user_id: String,
504        reason: Option<String>,
505    ) -> Result<(), ClientError> {
506        let user_id = UserId::parse(&user_id)?;
507        Ok(self.inner.unban_user(&user_id, reason.as_deref()).await?)
508    }
509
510    pub async fn can_user_invite(&self, user_id: String) -> Result<bool, ClientError> {
511        let user_id = UserId::parse(&user_id)?;
512        Ok(self.inner.can_user_invite(&user_id).await?)
513    }
514
515    pub async fn can_user_kick(&self, user_id: String) -> Result<bool, ClientError> {
516        let user_id = UserId::parse(&user_id)?;
517        Ok(self.inner.can_user_kick(&user_id).await?)
518    }
519
520    pub async fn kick_user(
521        &self,
522        user_id: String,
523        reason: Option<String>,
524    ) -> Result<(), ClientError> {
525        let user_id = UserId::parse(&user_id)?;
526        Ok(self.inner.kick_user(&user_id, reason.as_deref()).await?)
527    }
528
529    pub async fn can_user_send_state(
530        &self,
531        user_id: String,
532        state_event: StateEventType,
533    ) -> Result<bool, ClientError> {
534        let user_id = UserId::parse(&user_id)?;
535        Ok(self.inner.can_user_send_state(&user_id, state_event.into()).await?)
536    }
537
538    pub async fn can_user_send_message(
539        &self,
540        user_id: String,
541        message: MessageLikeEventType,
542    ) -> Result<bool, ClientError> {
543        let user_id = UserId::parse(&user_id)?;
544        Ok(self.inner.can_user_send_message(&user_id, message.into()).await?)
545    }
546
547    pub async fn can_user_pin_unpin(&self, user_id: String) -> Result<bool, ClientError> {
548        let user_id = UserId::parse(&user_id)?;
549        Ok(self.inner.can_user_pin_unpin(&user_id).await?)
550    }
551
552    pub async fn can_user_trigger_room_notification(
553        &self,
554        user_id: String,
555    ) -> Result<bool, ClientError> {
556        let user_id = UserId::parse(&user_id)?;
557        Ok(self.inner.can_user_trigger_room_notification(&user_id).await?)
558    }
559
560    pub fn own_user_id(&self) -> String {
561        self.inner.own_user_id().to_string()
562    }
563
564    pub async fn typing_notice(&self, is_typing: bool) -> Result<(), ClientError> {
565        Ok(self.inner.typing_notice(is_typing).await?)
566    }
567
568    pub fn subscribe_to_typing_notifications(
569        self: Arc<Self>,
570        listener: Box<dyn TypingNotificationsListener>,
571    ) -> Arc<TaskHandle> {
572        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
573            let (_event_handler_drop_guard, mut subscriber) =
574                self.inner.subscribe_to_typing_notifications();
575            while let Ok(typing_user_ids) = subscriber.recv().await {
576                let typing_user_ids =
577                    typing_user_ids.into_iter().map(|user_id| user_id.to_string()).collect();
578                listener.call(typing_user_ids);
579            }
580        })))
581    }
582
583    pub fn subscribe_to_identity_status_changes(
584        &self,
585        listener: Box<dyn IdentityStatusChangeListener>,
586    ) -> Arc<TaskHandle> {
587        let room = self.inner.clone();
588        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
589            let status_changes = room.subscribe_to_identity_status_changes().await;
590            if let Ok(status_changes) = status_changes {
591                // TODO: what to do with failures?
592                let mut status_changes = pin!(status_changes);
593                while let Some(identity_status_changes) = status_changes.next().await {
594                    listener.call(
595                        identity_status_changes
596                            .into_iter()
597                            .map(|change| {
598                                let user_id = change.user_id.to_string();
599                                IdentityStatusChange { user_id, changed_to: change.changed_to }
600                            })
601                            .collect(),
602                    );
603                }
604            }
605        })))
606    }
607
608    /// Set (or unset) a flag on the room to indicate that the user has
609    /// explicitly marked it as unread.
610    pub async fn set_unread_flag(&self, new_value: bool) -> Result<(), ClientError> {
611        Ok(self.inner.set_unread_flag(new_value).await?)
612    }
613
614    /// Mark a room as read, by attaching a read receipt on the latest event.
615    ///
616    /// Note: this does NOT unset the unread flag; it's the caller's
617    /// responsibility to do so, if needs be.
618    pub async fn mark_as_read(&self, receipt_type: ReceiptType) -> Result<(), ClientError> {
619        let timeline = self.timeline().await?;
620
621        timeline.mark_as_read(receipt_type).await?;
622        Ok(())
623    }
624
625    pub async fn get_power_levels(&self) -> Result<RoomPowerLevels, ClientError> {
626        let power_levels = self.inner.power_levels().await.map_err(matrix_sdk::Error::from)?;
627        Ok(RoomPowerLevels::from(power_levels))
628    }
629
630    pub async fn apply_power_level_changes(
631        &self,
632        changes: RoomPowerLevelChanges,
633    ) -> Result<(), ClientError> {
634        self.inner.apply_power_level_changes(changes).await?;
635        Ok(())
636    }
637
638    pub async fn update_power_levels_for_users(
639        &self,
640        updates: Vec<UserPowerLevelUpdate>,
641    ) -> Result<(), ClientError> {
642        let updates = updates
643            .iter()
644            .map(|update| {
645                let user_id: &UserId = update.user_id.as_str().try_into()?;
646                let power_level = Int::new(update.power_level).context("Invalid power level")?;
647                Ok((user_id, power_level))
648            })
649            .collect::<Result<Vec<_>>>()?;
650
651        self.inner
652            .update_power_levels(updates)
653            .await
654            .map_err(|e| ClientError::Generic { msg: e.to_string() })?;
655        Ok(())
656    }
657
658    pub async fn suggested_role_for_user(
659        &self,
660        user_id: String,
661    ) -> Result<RoomMemberRole, ClientError> {
662        let user_id = UserId::parse(&user_id)?;
663        Ok(self.inner.get_suggested_user_role(&user_id).await?)
664    }
665
666    pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels, ClientError> {
667        Ok(RoomPowerLevels::from(self.inner.reset_power_levels().await?))
668    }
669
670    pub async fn matrix_to_permalink(&self) -> Result<String, ClientError> {
671        Ok(self.inner.matrix_to_permalink().await?.to_string())
672    }
673
674    pub async fn matrix_to_event_permalink(&self, event_id: String) -> Result<String, ClientError> {
675        let event_id = EventId::parse(event_id)?;
676        Ok(self.inner.matrix_to_event_permalink(event_id).await?.to_string())
677    }
678
679    /// This will only send a call notification event if appropriate.
680    ///
681    /// This function is supposed to be called whenever the user creates a room
682    /// call. It will send a `m.call.notify` event if:
683    ///  - there is not yet a running call.
684    ///
685    /// It will configure the notify type: ring or notify based on:
686    ///  - is this a DM room -> ring
687    ///  - is this a group with more than one other member -> notify
688    pub async fn send_call_notification_if_needed(&self) -> Result<(), ClientError> {
689        self.inner.send_call_notification_if_needed().await?;
690        Ok(())
691    }
692
693    /// Send a call notification event in the current room.
694    ///
695    /// This is only supposed to be used in **custom** situations where the user
696    /// explicitly chooses to send a `m.call.notify` event to invite/notify
697    /// someone explicitly in unusual conditions. The default should be to
698    /// use `send_call_notification_if_necessary` just before a new room call is
699    /// created/joined.
700    ///
701    /// One example could be that the UI allows to start a call with a subset of
702    /// users of the room members first. And then later on the user can
703    /// invite more users to the call.
704    pub async fn send_call_notification(
705        &self,
706        call_id: String,
707        application: RtcApplicationType,
708        notify_type: NotifyType,
709        mentions: Mentions,
710    ) -> Result<(), ClientError> {
711        self.inner
712            .send_call_notification(
713                call_id,
714                application.into(),
715                notify_type.into(),
716                mentions.into(),
717            )
718            .await?;
719        Ok(())
720    }
721
722    /// Returns whether the send queue for that particular room is enabled or
723    /// not.
724    pub fn is_send_queue_enabled(&self) -> bool {
725        self.inner.send_queue().is_enabled()
726    }
727
728    /// Enable or disable the send queue for that particular room.
729    pub fn enable_send_queue(&self, enable: bool) {
730        self.inner.send_queue().set_enabled(enable);
731    }
732
733    /// Store the given `ComposerDraft` in the state store using the current
734    /// room id, as identifier.
735    pub async fn save_composer_draft(&self, draft: ComposerDraft) -> Result<(), ClientError> {
736        Ok(self.inner.save_composer_draft(draft.try_into()?).await?)
737    }
738
739    /// Retrieve the `ComposerDraft` stored in the state store for this room.
740    pub async fn load_composer_draft(&self) -> Result<Option<ComposerDraft>, ClientError> {
741        Ok(self.inner.load_composer_draft().await?.map(Into::into))
742    }
743
744    /// Remove the `ComposerDraft` stored in the state store for this room.
745    pub async fn clear_composer_draft(&self) -> Result<(), ClientError> {
746        Ok(self.inner.clear_composer_draft().await?)
747    }
748
749    /// Edit an event given its event id.
750    ///
751    /// Useful outside the context of a timeline, or when a timeline doesn't
752    /// have the full content of an event.
753    pub async fn edit(
754        &self,
755        event_id: String,
756        new_content: Arc<RoomMessageEventContentWithoutRelation>,
757    ) -> Result<(), ClientError> {
758        let event_id = EventId::parse(event_id)?;
759
760        let replacement_event = self
761            .inner
762            .make_edit_event(&event_id, EditedContent::RoomMessage((*new_content).clone()))
763            .await?;
764
765        self.inner.send_queue().send(replacement_event).await?;
766        Ok(())
767    }
768
769    /// Remove verification requirements for the given users and
770    /// resend messages that failed to send because their identities were no
771    /// longer verified (in response to
772    /// `SessionRecipientCollectionError::VerifiedUserChangedIdentity`)
773    ///
774    /// # Arguments
775    ///
776    /// * `user_ids` - The list of users identifiers received in the error
777    /// * `transaction_id` - The send queue transaction identifier of the local
778    ///   echo the send error applies to
779    pub async fn withdraw_verification_and_resend(
780        &self,
781        user_ids: Vec<String>,
782        send_handle: Arc<SendHandle>,
783    ) -> Result<(), ClientError> {
784        let user_ids: Vec<OwnedUserId> =
785            user_ids.iter().map(UserId::parse).collect::<Result<_, _>>()?;
786
787        let encryption = self.inner.client().encryption();
788
789        for user_id in user_ids {
790            if let Some(user_identity) = encryption.get_user_identity(&user_id).await? {
791                user_identity.withdraw_verification().await?;
792            }
793        }
794
795        send_handle.try_resend().await?;
796
797        Ok(())
798    }
799
800    /// Set the local trust for the given devices to `LocalTrust::Ignored`
801    /// and resend messages that failed to send because said devices are
802    /// unverified (in response to
803    /// `SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice`).
804    /// # Arguments
805    ///
806    /// * `devices` - The map of users identifiers to device identifiers
807    ///   received in the error
808    /// * `transaction_id` - The send queue transaction identifier of the local
809    ///   echo the send error applies to
810    pub async fn ignore_device_trust_and_resend(
811        &self,
812        devices: HashMap<String, Vec<String>>,
813        send_handle: Arc<SendHandle>,
814    ) -> Result<(), ClientError> {
815        let encryption = self.inner.client().encryption();
816
817        for (user_id, device_ids) in devices.iter() {
818            let user_id = UserId::parse(user_id)?;
819
820            for device_id in device_ids {
821                let device_id: OwnedDeviceId = device_id.as_str().into();
822
823                if let Some(device) = encryption.get_device(&user_id, &device_id).await? {
824                    device.set_local_trust(LocalTrust::Ignored).await?;
825                }
826            }
827        }
828
829        send_handle.try_resend().await?;
830
831        Ok(())
832    }
833
834    /// Clear the event cache storage for the current room.
835    ///
836    /// This will remove all the information related to the event cache, in
837    /// memory and in the persisted storage, if enabled.
838    pub async fn clear_event_cache_storage(&self) -> Result<(), ClientError> {
839        let (room_event_cache, _drop_handles) = self.inner.event_cache().await?;
840        room_event_cache.clear().await?;
841        Ok(())
842    }
843
844    /// Subscribes to requests to join this room (knock member events), using a
845    /// `listener` to be notified of the changes.
846    ///
847    /// The current requests to join the room will be emitted immediately
848    /// when subscribing, along with a [`TaskHandle`] to cancel the
849    /// subscription.
850    pub async fn subscribe_to_knock_requests(
851        self: Arc<Self>,
852        listener: Box<dyn KnockRequestsListener>,
853    ) -> Result<Arc<TaskHandle>, ClientError> {
854        let (stream, seen_ids_cleanup_handle) = self.inner.subscribe_to_knock_requests().await?;
855
856        let handle = Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
857            pin_mut!(stream);
858            while let Some(requests) = stream.next().await {
859                listener.call(requests.into_iter().map(Into::into).collect());
860            }
861            // Cancel the seen ids cleanup task
862            seen_ids_cleanup_handle.abort();
863        })));
864
865        Ok(handle)
866    }
867
868    /// Return a debug representation for the internal room events data
869    /// structure, one line per entry in the resulting vector.
870    pub async fn room_events_debug_string(&self) -> Result<Vec<String>, ClientError> {
871        let (cache, _drop_guards) = self.inner.event_cache().await?;
872        Ok(cache.debug_string().await)
873    }
874
875    /// Update the canonical alias of the room.
876    ///
877    /// Note that publishing the alias in the room directory is done separately.
878    pub async fn update_canonical_alias(
879        &self,
880        alias: Option<String>,
881        alt_aliases: Vec<String>,
882    ) -> Result<(), ClientError> {
883        let new_alias = alias.map(TryInto::try_into).transpose()?;
884        let new_alt_aliases =
885            alt_aliases.into_iter().map(RoomAliasId::parse).collect::<Result<_, _>>()?;
886        self.inner
887            .privacy_settings()
888            .update_canonical_alias(new_alias, new_alt_aliases)
889            .await
890            .map_err(Into::into)
891    }
892
893    /// Publish a new room alias for this room in the room directory.
894    ///
895    /// Returns:
896    /// - `true` if the room alias didn't exist and it's now published.
897    /// - `false` if the room alias was already present so it couldn't be
898    ///   published.
899    pub async fn publish_room_alias_in_room_directory(
900        &self,
901        alias: String,
902    ) -> Result<bool, ClientError> {
903        let new_alias = RoomAliasId::parse(alias)?;
904        self.inner
905            .privacy_settings()
906            .publish_room_alias_in_room_directory(&new_alias)
907            .await
908            .map_err(Into::into)
909    }
910
911    /// Remove an existing room alias for this room in the room directory.
912    ///
913    /// Returns:
914    /// - `true` if the room alias was present and it's now removed from the
915    ///   room directory.
916    /// - `false` if the room alias didn't exist so it couldn't be removed.
917    pub async fn remove_room_alias_from_room_directory(
918        &self,
919        alias: String,
920    ) -> Result<bool, ClientError> {
921        let alias = RoomAliasId::parse(alias)?;
922        self.inner
923            .privacy_settings()
924            .remove_room_alias_from_room_directory(&alias)
925            .await
926            .map_err(Into::into)
927    }
928
929    /// Enable End-to-end encryption in this room.
930    pub async fn enable_encryption(&self) -> Result<(), ClientError> {
931        self.inner.enable_encryption().await.map_err(Into::into)
932    }
933
934    /// Update room history visibility for this room.
935    pub async fn update_history_visibility(
936        &self,
937        visibility: RoomHistoryVisibility,
938    ) -> Result<(), ClientError> {
939        let visibility: RumaHistoryVisibility = visibility.try_into()?;
940        self.inner
941            .privacy_settings()
942            .update_room_history_visibility(visibility)
943            .await
944            .map_err(Into::into)
945    }
946
947    /// Update the join rule for this room.
948    pub async fn update_join_rules(&self, new_rule: JoinRule) -> Result<(), ClientError> {
949        let new_rule: RumaJoinRule = new_rule.try_into()?;
950        self.inner.privacy_settings().update_join_rule(new_rule).await.map_err(Into::into)
951    }
952
953    /// Update the room's visibility in the room directory.
954    pub async fn update_room_visibility(
955        &self,
956        visibility: RoomVisibility,
957    ) -> Result<(), ClientError> {
958        self.inner
959            .privacy_settings()
960            .update_room_visibility(visibility.into())
961            .await
962            .map_err(Into::into)
963    }
964
965    /// Returns the visibility for this room in the room directory.
966    ///
967    /// [Public](`RoomVisibility::Public`) rooms are listed in the room
968    /// directory and can be found using it.
969    pub async fn get_room_visibility(&self) -> Result<RoomVisibility, ClientError> {
970        let visibility = self.inner.privacy_settings().get_room_visibility().await?;
971        Ok(visibility.into())
972    }
973
974    /// Start the current users live location share in the room.
975    pub async fn start_live_location_share(&self, duration_millis: u64) -> Result<(), ClientError> {
976        self.inner.start_live_location_share(duration_millis, None).await?;
977        Ok(())
978    }
979
980    /// Stop the current users live location share in the room.
981    pub async fn stop_live_location_share(&self) -> Result<(), ClientError> {
982        self.inner.stop_live_location_share().await.expect("Unable to stop live location share");
983        Ok(())
984    }
985
986    /// Send the current users live location beacon in the room.
987    pub async fn send_live_location(&self, geo_uri: String) -> Result<(), ClientError> {
988        self.inner
989            .send_location_beacon(geo_uri)
990            .await
991            .expect("Unable to send live location beacon");
992        Ok(())
993    }
994
995    /// Subscribes to live location shares in this room, using a `listener` to
996    /// be notified of the changes.
997    ///
998    /// The current live location shares will be emitted immediately when
999    /// subscribing, along with a [`TaskHandle`] to cancel the subscription.
1000    pub fn subscribe_to_live_location_shares(
1001        self: Arc<Self>,
1002        listener: Box<dyn LiveLocationShareListener>,
1003    ) -> Arc<TaskHandle> {
1004        let room = self.inner.clone();
1005
1006        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
1007            let subscription = room.observe_live_location_shares();
1008            let mut stream = subscription.subscribe();
1009            let mut pinned_stream = pin!(stream);
1010
1011            while let Some(event) = pinned_stream.next().await {
1012                let last_location = LocationContent {
1013                    body: "".to_owned(),
1014                    geo_uri: event.last_location.location.uri.clone().to_string(),
1015                    description: None,
1016                    zoom_level: None,
1017                    asset: None,
1018                };
1019
1020                let Some(beacon_info) = event.beacon_info else {
1021                    warn!("Live location share is missing the associated beacon_info state, skipping event.");
1022                    continue;
1023                };
1024
1025                listener.call(vec![LiveLocationShare {
1026                    last_location: LastLocation {
1027                        location: last_location,
1028                        ts: event.last_location.ts.0.into(),
1029                    },
1030                    is_live: beacon_info.is_live(),
1031                    user_id: event.user_id.to_string(),
1032                }])
1033            }
1034        })))
1035    }
1036
1037    /// Forget this room.
1038    ///
1039    /// This communicates to the homeserver that it should forget the room.
1040    ///
1041    /// Only left or banned-from rooms can be forgotten.
1042    pub async fn forget(&self) -> Result<(), ClientError> {
1043        self.inner.forget().await?;
1044        Ok(())
1045    }
1046}
1047
1048/// A listener for receiving new live location shares in a room.
1049#[matrix_sdk_ffi_macros::export(callback_interface)]
1050pub trait LiveLocationShareListener: Sync + Send {
1051    fn call(&self, live_location_shares: Vec<LiveLocationShare>);
1052}
1053
1054impl From<matrix_sdk::room::knock_requests::KnockRequest> for KnockRequest {
1055    fn from(request: matrix_sdk::room::knock_requests::KnockRequest) -> Self {
1056        Self {
1057            event_id: request.event_id.to_string(),
1058            user_id: request.member_info.user_id.to_string(),
1059            room_id: request.room_id().to_string(),
1060            display_name: request.member_info.display_name.clone(),
1061            avatar_url: request.member_info.avatar_url.as_ref().map(|url| url.to_string()),
1062            reason: request.member_info.reason.clone(),
1063            timestamp: request.timestamp.map(|ts| ts.into()),
1064            is_seen: request.is_seen,
1065            actions: Arc::new(KnockRequestActions { inner: request }),
1066        }
1067    }
1068}
1069
1070/// A listener for receiving new requests to a join a room.
1071#[matrix_sdk_ffi_macros::export(callback_interface)]
1072pub trait KnockRequestsListener: Send + Sync {
1073    fn call(&self, join_requests: Vec<KnockRequest>);
1074}
1075
1076/// An FFI representation of a request to join a room.
1077#[derive(Debug, Clone, uniffi::Record)]
1078pub struct KnockRequest {
1079    /// The event id of the event that contains the `knock` membership change.
1080    pub event_id: String,
1081    /// The user id of the user who's requesting to join the room.
1082    pub user_id: String,
1083    /// The room id of the room whose access was requested.
1084    pub room_id: String,
1085    /// The optional display name of the user who's requesting to join the room.
1086    pub display_name: Option<String>,
1087    /// The optional avatar url of the user who's requesting to join the room.
1088    pub avatar_url: Option<String>,
1089    /// An optional reason why the user wants join the room.
1090    pub reason: Option<String>,
1091    /// The timestamp when this request was created.
1092    pub timestamp: Option<u64>,
1093    /// Whether the knock request has been marked as `seen` so it can be
1094    /// filtered by the client.
1095    pub is_seen: bool,
1096    /// A set of actions to perform for this knock request.
1097    pub actions: Arc<KnockRequestActions>,
1098}
1099
1100/// A set of actions to perform for a knock request.
1101#[derive(Debug, Clone, uniffi::Object)]
1102pub struct KnockRequestActions {
1103    inner: matrix_sdk::room::knock_requests::KnockRequest,
1104}
1105
1106#[matrix_sdk_ffi_macros::export]
1107impl KnockRequestActions {
1108    /// Accepts the knock request by inviting the user to the room.
1109    pub async fn accept(&self) -> Result<(), ClientError> {
1110        self.inner.accept().await.map_err(Into::into)
1111    }
1112
1113    /// Declines the knock request by kicking the user from the room with an
1114    /// optional reason.
1115    pub async fn decline(&self, reason: Option<String>) -> Result<(), ClientError> {
1116        self.inner.decline(reason.as_deref()).await.map_err(Into::into)
1117    }
1118
1119    /// Declines the knock request by banning the user from the room with an
1120    /// optional reason.
1121    pub async fn decline_and_ban(&self, reason: Option<String>) -> Result<(), ClientError> {
1122        self.inner.decline_and_ban(reason.as_deref()).await.map_err(Into::into)
1123    }
1124
1125    /// Marks the knock request as 'seen'.
1126    ///
1127    /// **IMPORTANT**: this won't update the current reference to this request,
1128    /// a new one with the updated value should be emitted instead.
1129    pub async fn mark_as_seen(&self) -> Result<(), ClientError> {
1130        self.inner.mark_as_seen().await.map_err(Into::into)
1131    }
1132}
1133
1134/// Generates a `matrix.to` permalink to the given room alias.
1135#[matrix_sdk_ffi_macros::export]
1136pub fn matrix_to_room_alias_permalink(
1137    room_alias: String,
1138) -> std::result::Result<String, ClientError> {
1139    let room_alias = RoomAliasId::parse(room_alias)?;
1140    Ok(room_alias.matrix_to_uri().to_string())
1141}
1142
1143#[derive(uniffi::Record)]
1144pub struct RoomPowerLevels {
1145    /// The level required to ban a user.
1146    pub ban: i64,
1147    /// The level required to invite a user.
1148    pub invite: i64,
1149    /// The level required to kick a user.
1150    pub kick: i64,
1151    /// The level required to redact an event.
1152    pub redact: i64,
1153    /// The default level required to send message events.
1154    pub events_default: i64,
1155    /// The default level required to send state events.
1156    pub state_default: i64,
1157    /// The default power level for every user in the room.
1158    pub users_default: i64,
1159    /// The level required to change the room's name.
1160    pub room_name: i64,
1161    /// The level required to change the room's avatar.
1162    pub room_avatar: i64,
1163    /// The level required to change the room's topic.
1164    pub room_topic: i64,
1165}
1166
1167impl From<RumaPowerLevels> for RoomPowerLevels {
1168    fn from(value: RumaPowerLevels) -> Self {
1169        fn state_event_level_for(
1170            power_levels: &RumaPowerLevels,
1171            event_type: &TimelineEventType,
1172        ) -> i64 {
1173            let default_state: i64 = power_levels.state_default.into();
1174            power_levels.events.get(event_type).map_or(default_state, |&level| level.into())
1175        }
1176        Self {
1177            ban: value.ban.into(),
1178            invite: value.invite.into(),
1179            kick: value.kick.into(),
1180            redact: value.redact.into(),
1181            events_default: value.events_default.into(),
1182            state_default: value.state_default.into(),
1183            users_default: value.users_default.into(),
1184            room_name: state_event_level_for(&value, &TimelineEventType::RoomName),
1185            room_avatar: state_event_level_for(&value, &TimelineEventType::RoomAvatar),
1186            room_topic: state_event_level_for(&value, &TimelineEventType::RoomTopic),
1187        }
1188    }
1189}
1190
1191#[matrix_sdk_ffi_macros::export(callback_interface)]
1192pub trait RoomInfoListener: Sync + Send {
1193    fn call(&self, room_info: RoomInfo);
1194}
1195
1196#[matrix_sdk_ffi_macros::export(callback_interface)]
1197pub trait TypingNotificationsListener: Sync + Send {
1198    fn call(&self, typing_user_ids: Vec<String>);
1199}
1200
1201#[matrix_sdk_ffi_macros::export(callback_interface)]
1202pub trait IdentityStatusChangeListener: Sync + Send {
1203    fn call(&self, identity_status_change: Vec<IdentityStatusChange>);
1204}
1205
1206#[derive(uniffi::Object)]
1207pub struct RoomMembersIterator {
1208    chunk_iterator: ChunkIterator<matrix_sdk::room::RoomMember>,
1209}
1210
1211impl RoomMembersIterator {
1212    fn new(members: Vec<matrix_sdk::room::RoomMember>) -> Self {
1213        Self { chunk_iterator: ChunkIterator::new(members) }
1214    }
1215}
1216
1217#[matrix_sdk_ffi_macros::export]
1218impl RoomMembersIterator {
1219    fn len(&self) -> u32 {
1220        self.chunk_iterator.len()
1221    }
1222
1223    fn next_chunk(&self, chunk_size: u32) -> Option<Vec<RoomMember>> {
1224        self.chunk_iterator
1225            .next(chunk_size)
1226            .map(|members| members.into_iter().filter_map(|m| m.try_into().ok()).collect())
1227    }
1228}
1229
1230/// Information about a member considered to be a room hero.
1231#[derive(uniffi::Record)]
1232pub struct RoomHero {
1233    /// The user ID of the hero.
1234    user_id: String,
1235    /// The display name of the hero.
1236    display_name: Option<String>,
1237    /// The avatar URL of the hero.
1238    avatar_url: Option<String>,
1239}
1240
1241impl From<SdkRoomHero> for RoomHero {
1242    fn from(value: SdkRoomHero) -> Self {
1243        Self {
1244            user_id: value.user_id.to_string(),
1245            display_name: value.display_name.clone(),
1246            avatar_url: value.avatar_url.as_ref().map(ToString::to_string),
1247        }
1248    }
1249}
1250
1251/// An update for a particular user's power level within the room.
1252#[derive(uniffi::Record)]
1253pub struct UserPowerLevelUpdate {
1254    /// The user ID of the user to update.
1255    user_id: String,
1256    /// The power level to assign to the user.
1257    power_level: i64,
1258}
1259
1260impl TryFrom<ImageInfo> for RumaAvatarImageInfo {
1261    type Error = MediaInfoError;
1262
1263    fn try_from(value: ImageInfo) -> Result<Self, MediaInfoError> {
1264        let thumbnail_url = if let Some(media_source) = value.thumbnail_source {
1265            match &media_source.as_ref().media_source {
1266                MediaSource::Plain(mxc_uri) => Some(mxc_uri.clone()),
1267                MediaSource::Encrypted(_) => return Err(MediaInfoError::InvalidField),
1268            }
1269        } else {
1270            None
1271        };
1272
1273        Ok(assign!(RumaAvatarImageInfo::new(), {
1274            height: value.height.map(u64_to_uint),
1275            width: value.width.map(u64_to_uint),
1276            mimetype: value.mimetype,
1277            size: value.size.map(u64_to_uint),
1278            thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
1279            thumbnail_url: thumbnail_url,
1280            blurhash: value.blurhash,
1281        }))
1282    }
1283}
1284
1285#[derive(uniffi::Enum)]
1286pub enum RtcApplicationType {
1287    Call,
1288}
1289impl From<RtcApplicationType> for notify::ApplicationType {
1290    fn from(value: RtcApplicationType) -> Self {
1291        match value {
1292            RtcApplicationType::Call => notify::ApplicationType::Call,
1293        }
1294    }
1295}
1296
1297/// Current draft of the composer for the room.
1298#[derive(uniffi::Record)]
1299pub struct ComposerDraft {
1300    /// The draft content in plain text.
1301    pub plain_text: String,
1302    /// If the message is formatted in HTML, the HTML representation of the
1303    /// message.
1304    pub html_text: Option<String>,
1305    /// The type of draft.
1306    pub draft_type: ComposerDraftType,
1307}
1308
1309impl From<SdkComposerDraft> for ComposerDraft {
1310    fn from(value: SdkComposerDraft) -> Self {
1311        let SdkComposerDraft { plain_text, html_text, draft_type } = value;
1312        Self { plain_text, html_text, draft_type: draft_type.into() }
1313    }
1314}
1315
1316impl TryFrom<ComposerDraft> for SdkComposerDraft {
1317    type Error = ruma::IdParseError;
1318
1319    fn try_from(value: ComposerDraft) -> std::result::Result<Self, Self::Error> {
1320        let ComposerDraft { plain_text, html_text, draft_type } = value;
1321        Ok(Self { plain_text, html_text, draft_type: draft_type.try_into()? })
1322    }
1323}
1324
1325/// The type of draft of the composer.
1326#[derive(uniffi::Enum)]
1327pub enum ComposerDraftType {
1328    /// The draft is a new message.
1329    NewMessage,
1330    /// The draft is a reply to an event.
1331    Reply {
1332        /// The ID of the event being replied to.
1333        event_id: String,
1334    },
1335    /// The draft is an edit of an event.
1336    Edit {
1337        /// The ID of the event being edited.
1338        event_id: String,
1339    },
1340}
1341
1342impl From<SdkComposerDraftType> for ComposerDraftType {
1343    fn from(value: SdkComposerDraftType) -> Self {
1344        match value {
1345            SdkComposerDraftType::NewMessage => Self::NewMessage,
1346            SdkComposerDraftType::Reply { event_id } => Self::Reply { event_id: event_id.into() },
1347            SdkComposerDraftType::Edit { event_id } => Self::Edit { event_id: event_id.into() },
1348        }
1349    }
1350}
1351
1352impl TryFrom<ComposerDraftType> for SdkComposerDraftType {
1353    type Error = ruma::IdParseError;
1354
1355    fn try_from(value: ComposerDraftType) -> std::result::Result<Self, Self::Error> {
1356        let draft_type = match value {
1357            ComposerDraftType::NewMessage => Self::NewMessage,
1358            ComposerDraftType::Reply { event_id } => Self::Reply { event_id: event_id.try_into()? },
1359            ComposerDraftType::Edit { event_id } => Self::Edit { event_id: event_id.try_into()? },
1360        };
1361
1362        Ok(draft_type)
1363    }
1364}
1365
1366#[derive(Debug, Clone, uniffi::Enum)]
1367pub enum RoomHistoryVisibility {
1368    /// Previous events are accessible to newly joined members from the point
1369    /// they were invited onwards.
1370    ///
1371    /// Events stop being accessible when the member's state changes to
1372    /// something other than *invite* or *join*.
1373    Invited,
1374
1375    /// Previous events are accessible to newly joined members from the point
1376    /// they joined the room onwards.
1377    /// Events stop being accessible when the member's state changes to
1378    /// something other than *join*.
1379    Joined,
1380
1381    /// Previous events are always accessible to newly joined members.
1382    ///
1383    /// All events in the room are accessible, even those sent when the member
1384    /// was not a part of the room.
1385    Shared,
1386
1387    /// All events while this is the `HistoryVisibility` value may be shared by
1388    /// any participating homeserver with anyone, regardless of whether they
1389    /// have ever joined the room.
1390    WorldReadable,
1391
1392    /// A custom visibility value.
1393    Custom { value: String },
1394}
1395
1396impl TryFrom<RumaHistoryVisibility> for RoomHistoryVisibility {
1397    type Error = NotYetImplemented;
1398    fn try_from(value: RumaHistoryVisibility) -> Result<Self, Self::Error> {
1399        match value {
1400            RumaHistoryVisibility::Invited => Ok(RoomHistoryVisibility::Invited),
1401            RumaHistoryVisibility::Shared => Ok(RoomHistoryVisibility::Shared),
1402            RumaHistoryVisibility::WorldReadable => Ok(RoomHistoryVisibility::WorldReadable),
1403            RumaHistoryVisibility::Joined => Ok(RoomHistoryVisibility::Joined),
1404            RumaHistoryVisibility::_Custom(_) => {
1405                Ok(RoomHistoryVisibility::Custom { value: value.to_string() })
1406            }
1407            _ => Err(NotYetImplemented),
1408        }
1409    }
1410}
1411
1412impl TryFrom<RoomHistoryVisibility> for RumaHistoryVisibility {
1413    type Error = NotYetImplemented;
1414    fn try_from(value: RoomHistoryVisibility) -> Result<Self, Self::Error> {
1415        match value {
1416            RoomHistoryVisibility::Invited => Ok(RumaHistoryVisibility::Invited),
1417            RoomHistoryVisibility::Shared => Ok(RumaHistoryVisibility::Shared),
1418            RoomHistoryVisibility::Joined => Ok(RumaHistoryVisibility::Joined),
1419            RoomHistoryVisibility::WorldReadable => Ok(RumaHistoryVisibility::WorldReadable),
1420            RoomHistoryVisibility::Custom { .. } => Err(NotYetImplemented),
1421        }
1422    }
1423}