1#![allow(deprecated)]
2
3use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration};
4
5use eyeball_im::VectorDiff;
6use futures_util::{pin_mut, StreamExt};
7use matrix_sdk::{
8 ruma::{
9 api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
10 RoomId,
11 },
12 Room as SdkRoom,
13};
14use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
15use matrix_sdk_ui::{
16 room_list_service::filters::{
17 new_filter_all, new_filter_any, new_filter_category, new_filter_deduplicate_versions,
18 new_filter_favourite, new_filter_fuzzy_match_room_name, new_filter_invite,
19 new_filter_joined, new_filter_low_priority, new_filter_non_left, new_filter_none,
20 new_filter_normalized_match_room_name, new_filter_not, new_filter_space, new_filter_unread,
21 BoxedFilterFn, RoomCategory,
22 },
23 unable_to_decrypt_hook::UtdHookManager,
24};
25
26use crate::{
27 room::{Membership, Room},
28 runtime::get_runtime_handle,
29 TaskHandle,
30};
31
32#[derive(Debug, thiserror::Error, uniffi::Error)]
33pub enum RoomListError {
34 #[error("sliding sync error: {error}")]
35 SlidingSync { error: String },
36 #[error("unknown list `{list_name}`")]
37 UnknownList { list_name: String },
38 #[error("input cannot be applied")]
39 InputCannotBeApplied,
40 #[error("room `{room_name}` not found")]
41 RoomNotFound { room_name: String },
42 #[error("invalid room ID: {error}")]
43 InvalidRoomId { error: String },
44 #[error("Event cache ran into an error: {error}")]
45 EventCache { error: String },
46 #[error(
47 "The requested room doesn't match the membership requirements {expected:?}, \
48 observed {actual:?}"
49 )]
50 IncorrectRoomMembership { expected: Vec<Membership>, actual: Membership },
51}
52
53impl From<matrix_sdk_ui::room_list_service::Error> for RoomListError {
54 fn from(value: matrix_sdk_ui::room_list_service::Error) -> Self {
55 use matrix_sdk_ui::room_list_service::Error::*;
56
57 match value {
58 SlidingSync(error) => Self::SlidingSync { error: error.to_string() },
59 UnknownList(list_name) => Self::UnknownList { list_name },
60 RoomNotFound(room_id) => Self::RoomNotFound { room_name: room_id.to_string() },
61 EventCache(error) => Self::EventCache { error: error.to_string() },
62 }
63 }
64}
65
66impl From<ruma::IdParseError> for RoomListError {
67 fn from(value: ruma::IdParseError) -> Self {
68 Self::InvalidRoomId { error: value.to_string() }
69 }
70}
71
72#[derive(uniffi::Object)]
73pub struct RoomListService {
74 pub(crate) inner: Arc<matrix_sdk_ui::RoomListService>,
75 pub(crate) utd_hook: Option<Arc<UtdHookManager>>,
76}
77
78#[matrix_sdk_ffi_macros::export]
79impl RoomListService {
80 fn state(&self, listener: Box<dyn RoomListServiceStateListener>) -> Arc<TaskHandle> {
81 let state_stream = self.inner.state();
82
83 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
84 pin_mut!(state_stream);
85
86 while let Some(state) = state_stream.next().await {
87 listener.on_update(state.into());
88 }
89 })))
90 }
91
92 fn room(&self, room_id: String) -> Result<Arc<Room>, RoomListError> {
93 let room_id = <&RoomId>::try_from(room_id.as_str()).map_err(RoomListError::from)?;
94
95 Ok(Arc::new(Room::new(self.inner.room(room_id)?, self.utd_hook.clone())))
96 }
97
98 async fn all_rooms(self: Arc<Self>) -> Result<Arc<RoomList>, RoomListError> {
99 Ok(Arc::new(RoomList {
100 room_list_service: self.clone(),
101 inner: Arc::new(self.inner.all_rooms().await.map_err(RoomListError::from)?),
102 }))
103 }
104
105 fn sync_indicator(
106 &self,
107 delay_before_showing_in_ms: u32,
108 delay_before_hiding_in_ms: u32,
109 listener: Box<dyn RoomListServiceSyncIndicatorListener>,
110 ) -> Arc<TaskHandle> {
111 let sync_indicator_stream = self.inner.sync_indicator(
112 Duration::from_millis(delay_before_showing_in_ms.into()),
113 Duration::from_millis(delay_before_hiding_in_ms.into()),
114 );
115
116 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
117 pin_mut!(sync_indicator_stream);
118
119 while let Some(sync_indicator) = sync_indicator_stream.next().await {
120 listener.on_update(sync_indicator.into());
121 }
122 })))
123 }
124
125 async fn subscribe_to_rooms(&self, room_ids: Vec<String>) -> Result<(), RoomListError> {
126 let room_ids = room_ids
127 .into_iter()
128 .map(|room_id| {
129 RoomId::parse(&room_id).map_err(|_| RoomListError::InvalidRoomId { error: room_id })
130 })
131 .collect::<Result<Vec<_>, _>>()?;
132
133 self.inner
134 .subscribe_to_rooms(&room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>())
135 .await;
136
137 Ok(())
138 }
139}
140
141#[derive(uniffi::Object)]
142pub struct RoomList {
143 room_list_service: Arc<RoomListService>,
144 inner: Arc<matrix_sdk_ui::room_list_service::RoomList>,
145}
146
147#[matrix_sdk_ffi_macros::export]
148impl RoomList {
149 fn loading_state(
150 &self,
151 listener: Box<dyn RoomListLoadingStateListener>,
152 ) -> Result<RoomListLoadingStateResult, RoomListError> {
153 let loading_state = self.inner.loading_state();
154
155 Ok(RoomListLoadingStateResult {
156 state: loading_state.get().into(),
157 state_stream: Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
158 pin_mut!(loading_state);
159
160 while let Some(loading_state) = loading_state.next().await {
161 listener.on_update(loading_state.into());
162 }
163 }))),
164 })
165 }
166
167 fn entries_with_dynamic_adapters(
168 self: Arc<Self>,
169 page_size: u32,
170 listener: Box<dyn RoomListEntriesListener>,
171 ) -> Arc<RoomListEntriesWithDynamicAdaptersResult> {
172 self.entries_with_dynamic_adapters_with(page_size, false, listener)
173 }
174
175 fn entries_with_dynamic_adapters_with(
176 self: Arc<Self>,
177 page_size: u32,
178 enable_latest_event_sorter: bool,
179 listener: Box<dyn RoomListEntriesListener>,
180 ) -> Arc<RoomListEntriesWithDynamicAdaptersResult> {
181 let this = self;
182
183 let mut result = MaybeUninit::<RoomListEntriesWithDynamicAdaptersResult>::uninit();
206 let ptr = result.as_mut_ptr();
207
208 unsafe {
212 addr_of_mut!((*ptr).this).write(this);
213 }
214
215 let this =
217 unsafe { addr_of_mut!((*ptr).this).as_ref() }
221 .unwrap();
223
224 let (entries_stream, dynamic_entries_controller) =
228 this.inner.entries_with_dynamic_adapters_with(
229 page_size.try_into().unwrap(),
230 enable_latest_event_sorter,
231 );
232
233 let dynamic_entries_controller =
236 Arc::new(RoomListDynamicEntriesController::new(dynamic_entries_controller));
237
238 let utd_hook = this.room_list_service.utd_hook.clone();
239 let entries_stream = Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
240 pin_mut!(entries_stream);
241
242 while let Some(diffs) = entries_stream.next().await {
243 listener.on_update(
244 diffs
245 .into_iter()
246 .map(|diff| {
247 RoomListEntriesUpdate::from(
248 utd_hook.clone(),
249 diff.map(|room| room.into_inner()),
250 )
251 })
252 .collect(),
253 );
254 }
255 })));
256
257 unsafe {
261 addr_of_mut!((*ptr).controller).write(dynamic_entries_controller);
262 }
263
264 unsafe {
268 addr_of_mut!((*ptr).entries_stream).write(entries_stream);
269 }
270
271 Arc::new(unsafe { result.assume_init() })
276 }
277
278 fn room(&self, room_id: String) -> Result<Arc<Room>, RoomListError> {
279 self.room_list_service.room(room_id)
280 }
281}
282
283#[derive(uniffi::Object)]
284pub struct RoomListEntriesWithDynamicAdaptersResult {
285 this: Arc<RoomList>,
286 controller: Arc<RoomListDynamicEntriesController>,
287 entries_stream: Arc<TaskHandle>,
288}
289
290#[matrix_sdk_ffi_macros::export]
291impl RoomListEntriesWithDynamicAdaptersResult {
292 fn controller(&self) -> Arc<RoomListDynamicEntriesController> {
293 self.controller.clone()
294 }
295
296 fn entries_stream(&self) -> Arc<TaskHandle> {
297 self.entries_stream.clone()
298 }
299}
300
301#[derive(uniffi::Record)]
302pub struct RoomListLoadingStateResult {
303 pub state: RoomListLoadingState,
304 pub state_stream: Arc<TaskHandle>,
305}
306
307#[derive(uniffi::Enum)]
308pub enum RoomListServiceState {
309 Initial,
312 SettingUp,
313 Recovering,
314 Running,
315 Error,
316 Terminated,
317}
318
319impl From<matrix_sdk_ui::room_list_service::State> for RoomListServiceState {
320 fn from(value: matrix_sdk_ui::room_list_service::State) -> Self {
321 use matrix_sdk_ui::room_list_service::State as S;
322
323 match value {
324 S::Init => Self::Initial,
325 S::SettingUp => Self::SettingUp,
326 S::Recovering => Self::Recovering,
327 S::Running => Self::Running,
328 S::Error { .. } => Self::Error,
329 S::Terminated { .. } => Self::Terminated,
330 }
331 }
332}
333
334#[derive(uniffi::Enum)]
335pub enum RoomListServiceSyncIndicator {
336 Show,
337 Hide,
338}
339
340impl From<matrix_sdk_ui::room_list_service::SyncIndicator> for RoomListServiceSyncIndicator {
341 fn from(value: matrix_sdk_ui::room_list_service::SyncIndicator) -> Self {
342 use matrix_sdk_ui::room_list_service::SyncIndicator as SI;
343
344 match value {
345 SI::Show => Self::Show,
346 SI::Hide => Self::Hide,
347 }
348 }
349}
350
351#[derive(uniffi::Enum)]
352pub enum RoomListLoadingState {
353 NotLoaded,
354 Loaded { maximum_number_of_rooms: Option<u32> },
355}
356
357impl From<matrix_sdk_ui::room_list_service::RoomListLoadingState> for RoomListLoadingState {
358 fn from(value: matrix_sdk_ui::room_list_service::RoomListLoadingState) -> Self {
359 use matrix_sdk_ui::room_list_service::RoomListLoadingState as LS;
360
361 match value {
362 LS::NotLoaded => Self::NotLoaded,
363 LS::Loaded { maximum_number_of_rooms } => Self::Loaded { maximum_number_of_rooms },
364 }
365 }
366}
367
368#[matrix_sdk_ffi_macros::export(callback_interface)]
369pub trait RoomListServiceStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
370 fn on_update(&self, state: RoomListServiceState);
371}
372
373#[matrix_sdk_ffi_macros::export(callback_interface)]
374pub trait RoomListLoadingStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
375 fn on_update(&self, state: RoomListLoadingState);
376}
377
378#[matrix_sdk_ffi_macros::export(callback_interface)]
379pub trait RoomListServiceSyncIndicatorListener: SendOutsideWasm + SyncOutsideWasm + Debug {
380 fn on_update(&self, sync_indicator: RoomListServiceSyncIndicator);
381}
382
383#[derive(uniffi::Enum)]
384pub enum RoomListEntriesUpdate {
385 Append { values: Vec<Arc<Room>> },
386 Clear,
387 PushFront { value: Arc<Room> },
388 PushBack { value: Arc<Room> },
389 PopFront,
390 PopBack,
391 Insert { index: u32, value: Arc<Room> },
392 Set { index: u32, value: Arc<Room> },
393 Remove { index: u32 },
394 Truncate { length: u32 },
395 Reset { values: Vec<Arc<Room>> },
396}
397
398impl RoomListEntriesUpdate {
399 fn from(utd_hook: Option<Arc<UtdHookManager>>, vector_diff: VectorDiff<SdkRoom>) -> Self {
400 match vector_diff {
401 VectorDiff::Append { values } => Self::Append {
402 values: values
403 .into_iter()
404 .map(|value| Arc::new(Room::new(value, utd_hook.clone())))
405 .collect(),
406 },
407 VectorDiff::Clear => Self::Clear,
408 VectorDiff::PushFront { value } => {
409 Self::PushFront { value: Arc::new(Room::new(value, utd_hook)) }
410 }
411 VectorDiff::PushBack { value } => {
412 Self::PushBack { value: Arc::new(Room::new(value, utd_hook)) }
413 }
414 VectorDiff::PopFront => Self::PopFront,
415 VectorDiff::PopBack => Self::PopBack,
416 VectorDiff::Insert { index, value } => Self::Insert {
417 index: u32::try_from(index).unwrap(),
418 value: Arc::new(Room::new(value, utd_hook)),
419 },
420 VectorDiff::Set { index, value } => Self::Set {
421 index: u32::try_from(index).unwrap(),
422 value: Arc::new(Room::new(value, utd_hook)),
423 },
424 VectorDiff::Remove { index } => Self::Remove { index: u32::try_from(index).unwrap() },
425 VectorDiff::Truncate { length } => {
426 Self::Truncate { length: u32::try_from(length).unwrap() }
427 }
428 VectorDiff::Reset { values } => Self::Reset {
429 values: values
430 .into_iter()
431 .map(|value| Arc::new(Room::new(value, utd_hook.clone())))
432 .collect(),
433 },
434 }
435 }
436}
437
438#[matrix_sdk_ffi_macros::export(callback_interface)]
439pub trait RoomListEntriesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
440 fn on_update(&self, room_entries_update: Vec<RoomListEntriesUpdate>);
441}
442
443#[derive(uniffi::Object)]
444pub struct RoomListDynamicEntriesController {
445 inner: matrix_sdk_ui::room_list_service::RoomListDynamicEntriesController,
446}
447
448impl RoomListDynamicEntriesController {
449 fn new(
450 dynamic_entries_controller: matrix_sdk_ui::room_list_service::RoomListDynamicEntriesController,
451 ) -> Self {
452 Self { inner: dynamic_entries_controller }
453 }
454}
455
456#[matrix_sdk_ffi_macros::export]
457impl RoomListDynamicEntriesController {
458 fn set_filter(&self, kind: RoomListEntriesDynamicFilterKind) -> bool {
459 self.inner.set_filter(kind.into())
460 }
461
462 fn add_one_page(&self) {
463 self.inner.add_one_page();
464 }
465
466 fn reset_to_one_page(&self) {
467 self.inner.reset_to_one_page();
468 }
469}
470
471#[derive(uniffi::Enum)]
472pub enum RoomListEntriesDynamicFilterKind {
473 All { filters: Vec<RoomListEntriesDynamicFilterKind> },
474 Any { filters: Vec<RoomListEntriesDynamicFilterKind> },
475 NonSpace,
476 Space,
477 NonLeft,
478 Joined,
481 Unread,
482 Favourite,
483 LowPriority,
484 NonLowPriority,
485 Invite,
486 Category { expect: RoomListFilterCategory },
487 None,
488 NormalizedMatchRoomName { pattern: String },
489 FuzzyMatchRoomName { pattern: String },
490 DeduplicateVersions,
491}
492
493#[derive(uniffi::Enum)]
494pub enum RoomListFilterCategory {
495 Group,
496 People,
497}
498
499impl From<RoomListFilterCategory> for RoomCategory {
500 fn from(value: RoomListFilterCategory) -> Self {
501 match value {
502 RoomListFilterCategory::Group => Self::Group,
503 RoomListFilterCategory::People => Self::People,
504 }
505 }
506}
507
508impl From<RoomListEntriesDynamicFilterKind> for BoxedFilterFn {
509 fn from(value: RoomListEntriesDynamicFilterKind) -> Self {
510 use RoomListEntriesDynamicFilterKind as Kind;
511
512 match value {
513 Kind::All { filters } => Box::new(new_filter_all(
514 filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
515 )),
516 Kind::Any { filters } => Box::new(new_filter_any(
517 filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
518 )),
519 Kind::NonSpace => Box::new(new_filter_not(Box::new(new_filter_space()))),
520 Kind::Space => Box::new(new_filter_space()),
521 Kind::NonLeft => Box::new(new_filter_non_left()),
522 Kind::Joined => Box::new(new_filter_joined()),
523 Kind::Unread => Box::new(new_filter_unread()),
524 Kind::Favourite => Box::new(new_filter_favourite()),
525 Kind::LowPriority => Box::new(new_filter_low_priority()),
526 Kind::NonLowPriority => Box::new(new_filter_not(Box::new(new_filter_low_priority()))),
527 Kind::Invite => Box::new(new_filter_invite()),
528 Kind::Category { expect } => Box::new(new_filter_category(expect.into())),
529 Kind::None => Box::new(new_filter_none()),
530 Kind::NormalizedMatchRoomName { pattern } => {
531 Box::new(new_filter_normalized_match_room_name(&pattern))
532 }
533 Kind::FuzzyMatchRoomName { pattern } => {
534 Box::new(new_filter_fuzzy_match_room_name(&pattern))
535 }
536 Kind::DeduplicateVersions => Box::new(new_filter_deduplicate_versions()),
537 }
538 }
539}
540
541#[derive(uniffi::Object)]
542pub struct UnreadNotificationsCount {
543 highlight_count: u32,
544 notification_count: u32,
545}
546
547#[matrix_sdk_ffi_macros::export]
548impl UnreadNotificationsCount {
549 fn highlight_count(&self) -> u32 {
550 self.highlight_count
551 }
552
553 fn notification_count(&self) -> u32 {
554 self.notification_count
555 }
556
557 fn has_notifications(&self) -> bool {
558 self.notification_count != 0 || self.highlight_count != 0
559 }
560}
561
562impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
563 fn from(inner: RumaUnreadNotificationsCount) -> Self {
564 UnreadNotificationsCount {
565 highlight_count: inner
566 .highlight_count
567 .and_then(|x| x.try_into().ok())
568 .unwrap_or_default(),
569 notification_count: inner
570 .notification_count
571 .and_then(|x| x.try_into().ok())
572 .unwrap_or_default(),
573 }
574 }
575}