matrix_sdk_ffi/
room.rs

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