matrix_sdk_ffi/
room_list.rs

1#![allow(deprecated)]
2
3use std::{fmt::Debug, mem::MaybeUninit, ptr::addr_of_mut, sync::Arc, time::Duration};
4
5use async_compat::get_runtime_handle;
6use eyeball_im::VectorDiff;
7use futures_util::{pin_mut, StreamExt, TryFutureExt};
8use matrix_sdk::ruma::{
9    api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
10    RoomId,
11};
12use matrix_sdk_ui::{
13    room_list_service::filters::{
14        new_filter_all, new_filter_any, new_filter_category, new_filter_favourite,
15        new_filter_fuzzy_match_room_name, new_filter_invite, new_filter_joined,
16        new_filter_non_left, new_filter_none, new_filter_normalized_match_room_name,
17        new_filter_unread, BoxedFilterFn, RoomCategory,
18    },
19    timeline::default_event_filter,
20    unable_to_decrypt_hook::UtdHookManager,
21};
22use ruma::{OwnedRoomOrAliasId, OwnedServerName, ServerName};
23use tokio::sync::RwLock;
24
25use crate::{
26    error::ClientError,
27    room::{Membership, Room},
28    room_info::RoomInfo,
29    room_preview::RoomPreview,
30    timeline::{configuration::TimelineEventTypeFilter, EventTimelineItem, Timeline},
31    utils::AsyncRuntimeDropped,
32    TaskHandle,
33};
34
35#[derive(Debug, thiserror::Error, uniffi::Error)]
36pub enum RoomListError {
37    #[error("sliding sync error: {error}")]
38    SlidingSync { error: String },
39    #[error("unknown list `{list_name}`")]
40    UnknownList { list_name: String },
41    #[error("input cannot be applied")]
42    InputCannotBeApplied,
43    #[error("room `{room_name}` not found")]
44    RoomNotFound { room_name: String },
45    #[error("invalid room ID: {error}")]
46    InvalidRoomId { error: String },
47    #[error("A timeline instance already exists for room {room_name}")]
48    TimelineAlreadyExists { room_name: String },
49    #[error("A timeline instance hasn't been initialized for room {room_name}")]
50    TimelineNotInitialized { room_name: String },
51    #[error("Timeline couldn't be initialized: {error}")]
52    InitializingTimeline { error: String },
53    #[error("Event cache ran into an error: {error}")]
54    EventCache { error: String },
55    #[error("The requested room doesn't match the membership requirements {expected:?}, observed {actual:?}")]
56    IncorrectRoomMembership { expected: Vec<Membership>, actual: Membership },
57}
58
59impl From<matrix_sdk_ui::room_list_service::Error> for RoomListError {
60    fn from(value: matrix_sdk_ui::room_list_service::Error) -> Self {
61        use matrix_sdk_ui::room_list_service::Error::*;
62
63        match value {
64            SlidingSync(error) => Self::SlidingSync { error: error.to_string() },
65            UnknownList(list_name) => Self::UnknownList { list_name },
66            RoomNotFound(room_id) => Self::RoomNotFound { room_name: room_id.to_string() },
67            TimelineAlreadyExists(room_id) => {
68                Self::TimelineAlreadyExists { room_name: room_id.to_string() }
69            }
70            InitializingTimeline(source) => {
71                Self::InitializingTimeline { error: source.to_string() }
72            }
73            EventCache(error) => Self::EventCache { error: error.to_string() },
74        }
75    }
76}
77
78impl From<ruma::IdParseError> for RoomListError {
79    fn from(value: ruma::IdParseError) -> Self {
80        Self::InvalidRoomId { error: value.to_string() }
81    }
82}
83
84#[derive(uniffi::Object)]
85pub struct RoomListService {
86    pub(crate) inner: Arc<matrix_sdk_ui::RoomListService>,
87    pub(crate) utd_hook: Option<Arc<UtdHookManager>>,
88}
89
90#[matrix_sdk_ffi_macros::export]
91impl RoomListService {
92    fn state(&self, listener: Box<dyn RoomListServiceStateListener>) -> Arc<TaskHandle> {
93        let state_stream = self.inner.state();
94
95        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
96            pin_mut!(state_stream);
97
98            while let Some(state) = state_stream.next().await {
99                listener.on_update(state.into());
100            }
101        })))
102    }
103
104    fn room(&self, room_id: String) -> Result<Arc<RoomListItem>, RoomListError> {
105        let room_id = <&RoomId>::try_from(room_id.as_str()).map_err(RoomListError::from)?;
106
107        Ok(Arc::new(RoomListItem {
108            inner: Arc::new(self.inner.room(room_id)?),
109            utd_hook: self.utd_hook.clone(),
110        }))
111    }
112
113    async fn all_rooms(self: Arc<Self>) -> Result<Arc<RoomList>, RoomListError> {
114        Ok(Arc::new(RoomList {
115            room_list_service: self.clone(),
116            inner: Arc::new(self.inner.all_rooms().await.map_err(RoomListError::from)?),
117        }))
118    }
119
120    fn sync_indicator(
121        &self,
122        delay_before_showing_in_ms: u32,
123        delay_before_hiding_in_ms: u32,
124        listener: Box<dyn RoomListServiceSyncIndicatorListener>,
125    ) -> Arc<TaskHandle> {
126        let sync_indicator_stream = self.inner.sync_indicator(
127            Duration::from_millis(delay_before_showing_in_ms.into()),
128            Duration::from_millis(delay_before_hiding_in_ms.into()),
129        );
130
131        Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
132            pin_mut!(sync_indicator_stream);
133
134            while let Some(sync_indicator) = sync_indicator_stream.next().await {
135                listener.on_update(sync_indicator.into());
136            }
137        })))
138    }
139
140    fn subscribe_to_rooms(&self, room_ids: Vec<String>) -> Result<(), RoomListError> {
141        let room_ids = room_ids
142            .into_iter()
143            .map(|room_id| {
144                RoomId::parse(&room_id).map_err(|_| RoomListError::InvalidRoomId { error: room_id })
145            })
146            .collect::<Result<Vec<_>, _>>()?;
147
148        self.inner.subscribe_to_rooms(&room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>());
149
150        Ok(())
151    }
152}
153
154#[derive(uniffi::Object)]
155pub struct RoomList {
156    room_list_service: Arc<RoomListService>,
157    inner: Arc<matrix_sdk_ui::room_list_service::RoomList>,
158}
159
160#[matrix_sdk_ffi_macros::export]
161impl RoomList {
162    fn loading_state(
163        &self,
164        listener: Box<dyn RoomListLoadingStateListener>,
165    ) -> Result<RoomListLoadingStateResult, RoomListError> {
166        let loading_state = self.inner.loading_state();
167
168        Ok(RoomListLoadingStateResult {
169            state: loading_state.get().into(),
170            state_stream: Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
171                pin_mut!(loading_state);
172
173                while let Some(loading_state) = loading_state.next().await {
174                    listener.on_update(loading_state.into());
175                }
176            }))),
177        })
178    }
179
180    fn entries_with_dynamic_adapters(
181        self: Arc<Self>,
182        page_size: u32,
183        listener: Box<dyn RoomListEntriesListener>,
184    ) -> Arc<RoomListEntriesWithDynamicAdaptersResult> {
185        let this = self.clone();
186        let utd_hook = self.room_list_service.utd_hook.clone();
187
188        // The following code deserves a bit of explanation.
189        // `matrix_sdk_ui::room_list_service::RoomList::entries_with_dynamic_adapters`
190        // returns a `Stream` with a lifetime bounds to its `self` (`RoomList`). This is
191        // problematic here as this `Stream` is returned as part of
192        // `RoomListEntriesWithDynamicAdaptersResult` but it is not possible to store
193        // `RoomList` with it inside the `Future` that is run inside the `TaskHandle`
194        // that consumes this `Stream`. We have a lifetime issue: `RoomList` doesn't
195        // live long enough!
196        //
197        // To solve this issue, the trick is to store the `RoomList` inside the
198        // `RoomListEntriesWithDynamicAdaptersResult`. Alright, but then we have another
199        // lifetime issue! `RoomList` cannot move inside this struct because it is
200        // borrowed by `entries_with_dynamic_adapters`. Indeed, the struct is built
201        // after the `Stream` is obtained.
202        //
203        // To solve this issue, we need to build the struct field by field, starting
204        // with `this`, and use a reference to `this` to call
205        // `entries_with_dynamic_adapters`. This is unsafe because a couple of
206        // invariants must hold, but all this is legal and correct if the invariants are
207        // properly fulfilled.
208
209        // Create the struct result with uninitialized fields.
210        let mut result = MaybeUninit::<RoomListEntriesWithDynamicAdaptersResult>::uninit();
211        let ptr = result.as_mut_ptr();
212
213        // Initialize the first field `this`.
214        //
215        // SAFETY: `ptr` is correctly aligned, this is guaranteed by `MaybeUninit`.
216        unsafe {
217            addr_of_mut!((*ptr).this).write(this);
218        }
219
220        // Get a reference to `this`. It is only borrowed, it's not moved.
221        let this =
222            // SAFETY: `ptr` is correct aligned, the `this` field is correctly aligned,
223            // is dereferenceable and points to a correctly initialized value as done
224            // in the previous line.
225            unsafe { addr_of_mut!((*ptr).this).as_ref() }
226                // SAFETY: `this` contains a non null value.
227                .unwrap();
228
229        // Now we can create `entries_stream` and `dynamic_entries_controller` by
230        // borrowing `this`, which is going to live long enough since it will live as
231        // long as `entries_stream` and `dynamic_entries_controller`.
232        let (entries_stream, dynamic_entries_controller) =
233            this.inner.entries_with_dynamic_adapters(page_size.try_into().unwrap());
234
235        // FFI dance to make those values consumable by foreign language, nothing fancy
236        // here, that's the real code for this method.
237        let dynamic_entries_controller =
238            Arc::new(RoomListDynamicEntriesController::new(dynamic_entries_controller));
239
240        let entries_stream = Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
241            pin_mut!(entries_stream);
242
243            while let Some(diffs) = entries_stream.next().await {
244                listener.on_update(
245                    diffs
246                        .into_iter()
247                        .map(|diff| RoomListEntriesUpdate::from(diff, utd_hook.clone()))
248                        .collect(),
249                );
250            }
251        })));
252
253        // Initialize the second field `controller`.
254        //
255        // SAFETY: `ptr` is correctly aligned.
256        unsafe {
257            addr_of_mut!((*ptr).controller).write(dynamic_entries_controller);
258        }
259
260        // Initialize the third and last field `entries_stream`.
261        //
262        // SAFETY: `ptr` is correctly aligned.
263        unsafe {
264            addr_of_mut!((*ptr).entries_stream).write(entries_stream);
265        }
266
267        // The result is complete, let's return it!
268        //
269        // SAFETY: `result` is fully initialized, all its fields have received a valid
270        // value.
271        Arc::new(unsafe { result.assume_init() })
272    }
273
274    fn room(&self, room_id: String) -> Result<Arc<RoomListItem>, RoomListError> {
275        self.room_list_service.room(room_id)
276    }
277}
278
279#[derive(uniffi::Object)]
280pub struct RoomListEntriesWithDynamicAdaptersResult {
281    this: Arc<RoomList>,
282    controller: Arc<RoomListDynamicEntriesController>,
283    entries_stream: Arc<TaskHandle>,
284}
285
286#[matrix_sdk_ffi_macros::export]
287impl RoomListEntriesWithDynamicAdaptersResult {
288    fn controller(&self) -> Arc<RoomListDynamicEntriesController> {
289        self.controller.clone()
290    }
291
292    fn entries_stream(&self) -> Arc<TaskHandle> {
293        self.entries_stream.clone()
294    }
295}
296
297#[derive(uniffi::Record)]
298pub struct RoomListLoadingStateResult {
299    pub state: RoomListLoadingState,
300    pub state_stream: Arc<TaskHandle>,
301}
302
303#[derive(uniffi::Enum)]
304pub enum RoomListServiceState {
305    // Name it `Initial` instead of `Init`, otherwise it creates a keyword conflict in Swift
306    // as of 2023-08-21.
307    Initial,
308    SettingUp,
309    Recovering,
310    Running,
311    Error,
312    Terminated,
313}
314
315impl From<matrix_sdk_ui::room_list_service::State> for RoomListServiceState {
316    fn from(value: matrix_sdk_ui::room_list_service::State) -> Self {
317        use matrix_sdk_ui::room_list_service::State as S;
318
319        match value {
320            S::Init => Self::Initial,
321            S::SettingUp => Self::SettingUp,
322            S::Recovering => Self::Recovering,
323            S::Running => Self::Running,
324            S::Error { .. } => Self::Error,
325            S::Terminated { .. } => Self::Terminated,
326        }
327    }
328}
329
330#[derive(uniffi::Enum)]
331pub enum RoomListServiceSyncIndicator {
332    Show,
333    Hide,
334}
335
336impl From<matrix_sdk_ui::room_list_service::SyncIndicator> for RoomListServiceSyncIndicator {
337    fn from(value: matrix_sdk_ui::room_list_service::SyncIndicator) -> Self {
338        use matrix_sdk_ui::room_list_service::SyncIndicator as SI;
339
340        match value {
341            SI::Show => Self::Show,
342            SI::Hide => Self::Hide,
343        }
344    }
345}
346
347#[derive(uniffi::Enum)]
348pub enum RoomListLoadingState {
349    NotLoaded,
350    Loaded { maximum_number_of_rooms: Option<u32> },
351}
352
353impl From<matrix_sdk_ui::room_list_service::RoomListLoadingState> for RoomListLoadingState {
354    fn from(value: matrix_sdk_ui::room_list_service::RoomListLoadingState) -> Self {
355        use matrix_sdk_ui::room_list_service::RoomListLoadingState as LS;
356
357        match value {
358            LS::NotLoaded => Self::NotLoaded,
359            LS::Loaded { maximum_number_of_rooms } => Self::Loaded { maximum_number_of_rooms },
360        }
361    }
362}
363
364#[matrix_sdk_ffi_macros::export(callback_interface)]
365pub trait RoomListServiceStateListener: Send + Sync + Debug {
366    fn on_update(&self, state: RoomListServiceState);
367}
368
369#[matrix_sdk_ffi_macros::export(callback_interface)]
370pub trait RoomListLoadingStateListener: Send + Sync + Debug {
371    fn on_update(&self, state: RoomListLoadingState);
372}
373
374#[matrix_sdk_ffi_macros::export(callback_interface)]
375pub trait RoomListServiceSyncIndicatorListener: Send + Sync + Debug {
376    fn on_update(&self, sync_indicator: RoomListServiceSyncIndicator);
377}
378
379#[derive(uniffi::Enum)]
380pub enum RoomListEntriesUpdate {
381    Append { values: Vec<Arc<RoomListItem>> },
382    Clear,
383    PushFront { value: Arc<RoomListItem> },
384    PushBack { value: Arc<RoomListItem> },
385    PopFront,
386    PopBack,
387    Insert { index: u32, value: Arc<RoomListItem> },
388    Set { index: u32, value: Arc<RoomListItem> },
389    Remove { index: u32 },
390    Truncate { length: u32 },
391    Reset { values: Vec<Arc<RoomListItem>> },
392}
393
394impl RoomListEntriesUpdate {
395    fn from(
396        vector_diff: VectorDiff<matrix_sdk_ui::room_list_service::Room>,
397        utd_hook: Option<Arc<UtdHookManager>>,
398    ) -> Self {
399        match vector_diff {
400            VectorDiff::Append { values } => Self::Append {
401                values: values
402                    .into_iter()
403                    .map(|value| Arc::new(RoomListItem::from(value, utd_hook.clone())))
404                    .collect(),
405            },
406            VectorDiff::Clear => Self::Clear,
407            VectorDiff::PushFront { value } => {
408                Self::PushFront { value: Arc::new(RoomListItem::from(value, utd_hook)) }
409            }
410            VectorDiff::PushBack { value } => {
411                Self::PushBack { value: Arc::new(RoomListItem::from(value, utd_hook)) }
412            }
413            VectorDiff::PopFront => Self::PopFront,
414            VectorDiff::PopBack => Self::PopBack,
415            VectorDiff::Insert { index, value } => Self::Insert {
416                index: u32::try_from(index).unwrap(),
417                value: Arc::new(RoomListItem::from(value, utd_hook)),
418            },
419            VectorDiff::Set { index, value } => Self::Set {
420                index: u32::try_from(index).unwrap(),
421                value: Arc::new(RoomListItem::from(value, utd_hook)),
422            },
423            VectorDiff::Remove { index } => Self::Remove { index: u32::try_from(index).unwrap() },
424            VectorDiff::Truncate { length } => {
425                Self::Truncate { length: u32::try_from(length).unwrap() }
426            }
427            VectorDiff::Reset { values } => Self::Reset {
428                values: values
429                    .into_iter()
430                    .map(|value| Arc::new(RoomListItem::from(value, utd_hook.clone())))
431                    .collect(),
432            },
433        }
434    }
435}
436
437#[matrix_sdk_ffi_macros::export(callback_interface)]
438pub trait RoomListEntriesListener: Send + Sync + Debug {
439    fn on_update(&self, room_entries_update: Vec<RoomListEntriesUpdate>);
440}
441
442#[derive(uniffi::Object)]
443pub struct RoomListDynamicEntriesController {
444    inner: matrix_sdk_ui::room_list_service::RoomListDynamicEntriesController,
445}
446
447impl RoomListDynamicEntriesController {
448    fn new(
449        dynamic_entries_controller: matrix_sdk_ui::room_list_service::RoomListDynamicEntriesController,
450    ) -> Self {
451        Self { inner: dynamic_entries_controller }
452    }
453}
454
455#[matrix_sdk_ffi_macros::export]
456impl RoomListDynamicEntriesController {
457    fn set_filter(&self, kind: RoomListEntriesDynamicFilterKind) -> bool {
458        self.inner.set_filter(kind.into())
459    }
460
461    fn add_one_page(&self) {
462        self.inner.add_one_page();
463    }
464
465    fn reset_to_one_page(&self) {
466        self.inner.reset_to_one_page();
467    }
468}
469
470#[derive(uniffi::Enum)]
471pub enum RoomListEntriesDynamicFilterKind {
472    All { filters: Vec<RoomListEntriesDynamicFilterKind> },
473    Any { filters: Vec<RoomListEntriesDynamicFilterKind> },
474    NonLeft,
475    Joined,
476    Unread,
477    Favourite,
478    Invite,
479    Category { expect: RoomListFilterCategory },
480    None,
481    NormalizedMatchRoomName { pattern: String },
482    FuzzyMatchRoomName { pattern: String },
483}
484
485#[derive(uniffi::Enum)]
486pub enum RoomListFilterCategory {
487    Group,
488    People,
489}
490
491impl From<RoomListFilterCategory> for RoomCategory {
492    fn from(value: RoomListFilterCategory) -> Self {
493        match value {
494            RoomListFilterCategory::Group => Self::Group,
495            RoomListFilterCategory::People => Self::People,
496        }
497    }
498}
499
500impl From<RoomListEntriesDynamicFilterKind> for BoxedFilterFn {
501    fn from(value: RoomListEntriesDynamicFilterKind) -> Self {
502        use RoomListEntriesDynamicFilterKind as Kind;
503
504        match value {
505            Kind::All { filters } => Box::new(new_filter_all(
506                filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
507            )),
508            Kind::Any { filters } => Box::new(new_filter_any(
509                filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
510            )),
511            Kind::NonLeft => Box::new(new_filter_non_left()),
512            Kind::Joined => Box::new(new_filter_joined()),
513            Kind::Unread => Box::new(new_filter_unread()),
514            Kind::Favourite => Box::new(new_filter_favourite()),
515            Kind::Invite => Box::new(new_filter_invite()),
516            Kind::Category { expect } => Box::new(new_filter_category(expect.into())),
517            Kind::None => Box::new(new_filter_none()),
518            Kind::NormalizedMatchRoomName { pattern } => {
519                Box::new(new_filter_normalized_match_room_name(&pattern))
520            }
521            Kind::FuzzyMatchRoomName { pattern } => {
522                Box::new(new_filter_fuzzy_match_room_name(&pattern))
523            }
524        }
525    }
526}
527
528#[derive(uniffi::Object)]
529pub struct RoomListItem {
530    inner: Arc<matrix_sdk_ui::room_list_service::Room>,
531    utd_hook: Option<Arc<UtdHookManager>>,
532}
533
534impl RoomListItem {
535    fn from(
536        value: matrix_sdk_ui::room_list_service::Room,
537        utd_hook: Option<Arc<UtdHookManager>>,
538    ) -> Self {
539        Self { inner: Arc::new(value), utd_hook }
540    }
541}
542
543#[matrix_sdk_ffi_macros::export]
544impl RoomListItem {
545    fn id(&self) -> String {
546        self.inner.id().to_string()
547    }
548
549    /// Returns the room's name from the state event if available, otherwise
550    /// compute a room name based on the room's nature (DM or not) and number of
551    /// members.
552    fn display_name(&self) -> Option<String> {
553        self.inner.cached_display_name()
554    }
555
556    fn avatar_url(&self) -> Option<String> {
557        self.inner.avatar_url().map(|uri| uri.to_string())
558    }
559
560    async fn is_direct(&self) -> bool {
561        self.inner.inner_room().is_direct().await.unwrap_or(false)
562    }
563
564    fn canonical_alias(&self) -> Option<String> {
565        self.inner.inner_room().canonical_alias().map(|alias| alias.to_string())
566    }
567
568    async fn room_info(&self) -> Result<RoomInfo, ClientError> {
569        RoomInfo::new(self.inner.inner_room()).await
570    }
571
572    /// The room's current membership state.
573    fn membership(&self) -> Membership {
574        self.inner.inner_room().state().into()
575    }
576
577    /// Builds a `RoomPreview` from a room list item. This is intended for
578    /// invited, knocked or banned rooms.
579    async fn preview_room(&self, via: Vec<String>) -> Result<Arc<RoomPreview>, ClientError> {
580        // Validate parameters first.
581        let server_names: Vec<OwnedServerName> = via
582            .into_iter()
583            .map(|server| ServerName::parse(server).map_err(ClientError::from))
584            .collect::<Result<_, ClientError>>()?;
585
586        // Do the thing.
587        let client = self.inner.client();
588        let (room_or_alias_id, mut server_names) = if let Some(alias) = self.inner.canonical_alias()
589        {
590            let room_or_alias_id: OwnedRoomOrAliasId = alias.into();
591            (room_or_alias_id, Vec::new())
592        } else {
593            let room_or_alias_id: OwnedRoomOrAliasId = self.inner.id().to_owned().into();
594            (room_or_alias_id, server_names)
595        };
596
597        // If no server names are provided and the room's membership is invited,
598        // add the server name from the sender's user id as a fallback value
599        if server_names.is_empty() {
600            if let Ok(invite_details) = self.inner.invite_details().await {
601                if let Some(inviter) = invite_details.inviter {
602                    server_names.push(inviter.user_id().server_name().to_owned());
603                }
604            }
605        }
606
607        let room_preview = client.get_room_preview(&room_or_alias_id, server_names).await?;
608
609        Ok(Arc::new(RoomPreview::new(AsyncRuntimeDropped::new(client), room_preview)))
610    }
611
612    /// Build a full `Room` FFI object, filling its associated timeline.
613    ///
614    /// An error will be returned if the room is a state different than joined
615    /// or if its internal timeline hasn't been initialized.
616    fn full_room(&self) -> Result<Arc<Room>, RoomListError> {
617        if !matches!(self.membership(), Membership::Joined) {
618            return Err(RoomListError::IncorrectRoomMembership {
619                expected: vec![Membership::Joined],
620                actual: self.membership(),
621            });
622        }
623
624        if let Some(timeline) = self.inner.timeline() {
625            Ok(Arc::new(Room::with_timeline(
626                self.inner.inner_room().clone(),
627                Arc::new(RwLock::new(Some(Timeline::from_arc(timeline)))),
628            )))
629        } else {
630            Err(RoomListError::TimelineNotInitialized {
631                room_name: self.inner.inner_room().room_id().to_string(),
632            })
633        }
634    }
635
636    /// Checks whether the Room's timeline has been initialized before.
637    fn is_timeline_initialized(&self) -> bool {
638        self.inner.is_timeline_initialized()
639    }
640
641    /// Initializes the timeline for this room using the provided parameters.
642    ///
643    /// * `event_type_filter` - An optional [`TimelineEventTypeFilter`] to be
644    ///   used to filter timeline events besides the default timeline filter. If
645    ///   `None` is passed, only the default timeline filter will be used.
646    /// * `internal_id_prefix` - An optional String that will be prepended to
647    ///   all the timeline item's internal IDs, making it possible to
648    ///   distinguish different timeline instances from each other.
649    async fn init_timeline(
650        &self,
651        event_type_filter: Option<Arc<TimelineEventTypeFilter>>,
652        internal_id_prefix: Option<String>,
653    ) -> Result<(), RoomListError> {
654        let mut timeline_builder = self
655            .inner
656            .default_room_timeline_builder()
657            .await
658            .map_err(|err| RoomListError::InitializingTimeline { error: err.to_string() })?;
659
660        if let Some(event_type_filter) = event_type_filter {
661            timeline_builder = timeline_builder.event_filter(move |event, room_version_id| {
662                // Always perform the default filter first
663                default_event_filter(event, room_version_id) && event_type_filter.filter(event)
664            });
665        }
666
667        if let Some(internal_id_prefix) = internal_id_prefix {
668            timeline_builder = timeline_builder.with_internal_id_prefix(internal_id_prefix);
669        }
670
671        if let Some(utd_hook) = self.utd_hook.clone() {
672            timeline_builder = timeline_builder.with_unable_to_decrypt_hook(utd_hook);
673        }
674
675        self.inner.init_timeline_with_builder(timeline_builder).map_err(RoomListError::from).await
676    }
677
678    /// Checks whether the room is encrypted or not.
679    ///
680    /// **Note**: this info may not be reliable if you don't set up
681    /// `m.room.encryption` as required state.
682    async fn is_encrypted(&self) -> bool {
683        self.inner
684            .latest_encryption_state()
685            .await
686            .map(|state| state.is_encrypted())
687            .unwrap_or(false)
688    }
689
690    async fn latest_event(&self) -> Option<EventTimelineItem> {
691        self.inner.latest_event().await.map(Into::into)
692    }
693}
694
695#[derive(uniffi::Object)]
696pub struct UnreadNotificationsCount {
697    highlight_count: u32,
698    notification_count: u32,
699}
700
701#[matrix_sdk_ffi_macros::export]
702impl UnreadNotificationsCount {
703    fn highlight_count(&self) -> u32 {
704        self.highlight_count
705    }
706
707    fn notification_count(&self) -> u32 {
708        self.notification_count
709    }
710
711    fn has_notifications(&self) -> bool {
712        self.notification_count != 0 || self.highlight_count != 0
713    }
714}
715
716impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
717    fn from(inner: RumaUnreadNotificationsCount) -> Self {
718        UnreadNotificationsCount {
719            highlight_count: inner
720                .highlight_count
721                .and_then(|x| x.try_into().ok())
722                .unwrap_or_default(),
723            notification_count: inner
724                .notification_count
725                .and_then(|x| x.try_into().ok())
726                .unwrap_or_default(),
727        }
728    }
729}