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 pub fn display_name(&self) -> Option<String> {
97 Some(self.inner.cached_display_name()?.to_string())
98 }
99
100 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 pub fn heroes(&self) -> Vec<RoomHero> {
143 self.inner.heroes().into_iter().map(Into::into).collect()
144 }
145
146 pub fn has_active_room_call(&self) -> bool {
149 self.inner.has_active_room_call()
150 }
151
152 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 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 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 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 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 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 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 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 pub async fn leave(&self) -> Result<(), ClientError> {
407 self.inner.leave().await?;
408 Ok(())
409 }
410
411 pub async fn join(&self) -> Result<(), ClientError> {
415 self.inner.join().await?;
416 Ok(())
417 }
418
419 pub async fn set_name(&self, name: String) -> Result<(), ClientError> {
421 self.inner.set_name(name).await?;
422 Ok(())
423 }
424
425 pub async fn set_topic(&self, topic: String) -> Result<(), ClientError> {
427 self.inner.set_room_topic(&topic).await?;
428 Ok(())
429 }
430
431 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 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 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 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 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 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 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 pub fn is_send_queue_enabled(&self) -> bool {
725 self.inner.send_queue().is_enabled()
726 }
727
728 pub fn enable_send_queue(&self, enable: bool) {
730 self.inner.send_queue().set_enabled(enable);
731 }
732
733 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 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 pub async fn clear_composer_draft(&self) -> Result<(), ClientError> {
746 Ok(self.inner.clear_composer_draft().await?)
747 }
748
749 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 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 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 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 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 seen_ids_cleanup_handle.abort();
863 })));
864
865 Ok(handle)
866 }
867
868 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 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 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 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 pub async fn enable_encryption(&self) -> Result<(), ClientError> {
931 self.inner.enable_encryption().await.map_err(Into::into)
932 }
933
934 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 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 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 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 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 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 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 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 pub async fn forget(&self) -> Result<(), ClientError> {
1043 self.inner.forget().await?;
1044 Ok(())
1045 }
1046}
1047
1048#[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#[matrix_sdk_ffi_macros::export(callback_interface)]
1072pub trait KnockRequestsListener: Send + Sync {
1073 fn call(&self, join_requests: Vec<KnockRequest>);
1074}
1075
1076#[derive(Debug, Clone, uniffi::Record)]
1078pub struct KnockRequest {
1079 pub event_id: String,
1081 pub user_id: String,
1083 pub room_id: String,
1085 pub display_name: Option<String>,
1087 pub avatar_url: Option<String>,
1089 pub reason: Option<String>,
1091 pub timestamp: Option<u64>,
1093 pub is_seen: bool,
1096 pub actions: Arc<KnockRequestActions>,
1098}
1099
1100#[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 pub async fn accept(&self) -> Result<(), ClientError> {
1110 self.inner.accept().await.map_err(Into::into)
1111 }
1112
1113 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 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 pub async fn mark_as_seen(&self) -> Result<(), ClientError> {
1130 self.inner.mark_as_seen().await.map_err(Into::into)
1131 }
1132}
1133
1134#[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 pub ban: i64,
1147 pub invite: i64,
1149 pub kick: i64,
1151 pub redact: i64,
1153 pub events_default: i64,
1155 pub state_default: i64,
1157 pub users_default: i64,
1159 pub room_name: i64,
1161 pub room_avatar: i64,
1163 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#[derive(uniffi::Record)]
1232pub struct RoomHero {
1233 user_id: String,
1235 display_name: Option<String>,
1237 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#[derive(uniffi::Record)]
1253pub struct UserPowerLevelUpdate {
1254 user_id: String,
1256 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#[derive(uniffi::Record)]
1299pub struct ComposerDraft {
1300 pub plain_text: String,
1302 pub html_text: Option<String>,
1305 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#[derive(uniffi::Enum)]
1327pub enum ComposerDraftType {
1328 NewMessage,
1330 Reply {
1332 event_id: String,
1334 },
1335 Edit {
1337 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 Invited,
1374
1375 Joined,
1380
1381 Shared,
1386
1387 WorldReadable,
1391
1392 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}