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 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 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 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 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 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 }
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 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 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 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 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 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 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 pub async fn leave(&self) -> Result<(), ClientError> {
434 self.inner.leave().await?;
435 Ok(())
436 }
437
438 pub async fn join(&self) -> Result<(), ClientError> {
442 self.inner.join().await?;
443 Ok(())
444 }
445
446 pub async fn set_name(&self, name: String) -> Result<(), ClientError> {
448 self.inner.set_name(name).await?;
449 Ok(())
450 }
451
452 pub async fn set_topic(&self, topic: String) -> Result<(), ClientError> {
454 self.inner.set_room_topic(&topic).await?;
455 Ok(())
456 }
457
458 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 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 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 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 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 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 pub fn is_send_queue_enabled(&self) -> bool {
751 self.inner.send_queue().is_enabled()
752 }
753
754 pub fn enable_send_queue(&self, enable: bool) {
756 self.inner.send_queue().set_enabled(enable);
757 }
758
759 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 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 pub async fn clear_composer_draft(&self) -> Result<(), ClientError> {
772 Ok(self.inner.clear_composer_draft().await?)
773 }
774
775 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 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 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 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 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 seen_ids_cleanup_handle.abort();
889 })));
890
891 Ok(handle)
892 }
893
894 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 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 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 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 pub async fn enable_encryption(&self) -> Result<(), ClientError> {
957 self.inner.enable_encryption().await.map_err(Into::into)
958 }
959
960 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 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 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 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 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 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 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 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 pub async fn forget(&self) -> Result<(), ClientError> {
1069 self.inner.forget().await?;
1070 Ok(())
1071 }
1072}
1073
1074#[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#[matrix_sdk_ffi_macros::export(callback_interface)]
1098pub trait KnockRequestsListener: Send + Sync {
1099 fn call(&self, join_requests: Vec<KnockRequest>);
1100}
1101
1102#[derive(Debug, Clone, uniffi::Record)]
1104pub struct KnockRequest {
1105 pub event_id: String,
1107 pub user_id: String,
1109 pub room_id: String,
1111 pub display_name: Option<String>,
1113 pub avatar_url: Option<String>,
1115 pub reason: Option<String>,
1117 pub timestamp: Option<u64>,
1119 pub is_seen: bool,
1122 pub actions: Arc<KnockRequestActions>,
1124}
1125
1126#[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 pub async fn accept(&self) -> Result<(), ClientError> {
1136 self.inner.accept().await.map_err(Into::into)
1137 }
1138
1139 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 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 pub async fn mark_as_seen(&self) -> Result<(), ClientError> {
1156 self.inner.mark_as_seen().await.map_err(Into::into)
1157 }
1158}
1159
1160#[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 pub ban: i64,
1173 pub invite: i64,
1175 pub kick: i64,
1177 pub redact: i64,
1179 pub events_default: i64,
1181 pub state_default: i64,
1183 pub users_default: i64,
1185 pub room_name: i64,
1187 pub room_avatar: i64,
1189 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#[derive(uniffi::Record)]
1258pub struct RoomHero {
1259 user_id: String,
1261 display_name: Option<String>,
1263 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#[derive(uniffi::Record)]
1279pub struct UserPowerLevelUpdate {
1280 user_id: String,
1282 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#[derive(uniffi::Record)]
1325pub struct ComposerDraft {
1326 pub plain_text: String,
1328 pub html_text: Option<String>,
1331 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#[derive(uniffi::Enum)]
1353pub enum ComposerDraftType {
1354 NewMessage,
1356 Reply {
1358 event_id: String,
1360 },
1361 Edit {
1363 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 Invited,
1400
1401 Joined,
1406
1407 Shared,
1412
1413 WorldReadable,
1417
1418 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}