diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index 62de5555b..50f6e5459 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -90,10 +90,10 @@ mod state_transaction; pub(super) use aggregations::*; pub(super) use decryption_retry_task::{CryptoDropHandles, spawn_crypto_tasks}; -use matrix_sdk::paginators::{PaginatorError::NotInstantiated, thread::ThreadedEventsLoader}; -use matrix_sdk_common::{ - deserialized_responses::TimelineEventKind, serde_helpers::extract_thread_root, +use matrix_sdk::paginators::{ + PaginatorError, PaginatorError::NotInstantiated, thread::ThreadedEventsLoader, }; +use matrix_sdk_common::serde_helpers::extract_thread_root; /// Data associated to the current timeline focus. /// @@ -113,9 +113,6 @@ pub(in crate::timeline) enum TimelineFocusKind { Event { /// The paginator instance. paginator: OnceCell>, - - /// Whether to hide in-thread events from the timeline. - hide_threaded_events: bool, }, /// A live timeline for a thread. @@ -131,10 +128,74 @@ pub(in crate::timeline) enum TimelineFocusKind { #[derive(Debug)] pub(in crate::timeline) enum AnyPaginator { - Unthreaded(Paginator

), + Unthreaded { paginator: Paginator

, hide_threaded_events: bool }, Threaded(ThreadedEventsLoader

), } +impl AnyPaginator

{ + /// Runs a backward pagination (requesting `num_events` to the server), from + /// the current state of the object. + /// + /// Will return immediately if we have already hit the start of the + /// timeline. + /// + /// May return an error if it's already paginating, or if the call to + /// the homeserver endpoints failed. + pub async fn paginate_backwards( + &self, + num_events: u16, + ) -> Result { + match self { + Self::Unthreaded { paginator, .. } => { + paginator.paginate_backward(num_events.into()).await + } + Self::Threaded(threaded_paginator) => { + threaded_paginator.paginate_backwards(num_events.into()).await + } + } + } + + /// Runs a forward pagination (requesting `num_events` to the server), from + /// the current state of the object. + /// + /// Will return immediately if we have already hit the end of the timeline. + /// + /// May return an error if it's already paginating, or if the call to + /// the homeserver endpoints failed. + pub async fn paginate_forwards( + &self, + num_events: u16, + ) -> Result { + match self { + Self::Unthreaded { paginator, .. } => { + paginator.paginate_forward(num_events.into()).await + } + Self::Threaded(threaded_paginator) => { + threaded_paginator.paginate_forwards(num_events.into()).await + } + } + } + + /// Whether to hide in-thread events from the timeline. + pub fn hide_threaded_events(&self) -> bool { + match self { + Self::Unthreaded { hide_threaded_events, .. } => *hide_threaded_events, + Self::Threaded(_) => false, + } + } + + /// Returns the root event id of the thread, if the paginator is + /// [`AnyPaginator::Threaded`]. + pub fn thread_root(&self) -> Option<&EventId> { + match self { + Self::Unthreaded { .. } => None, + Self::Threaded(thread_events_loader) => { + Some(thread_events_loader.thread_root_event_id()) + } + } + } +} + impl TimelineFocusKind

{ /// Returns the [`ReceiptThread`] that should be used for the current /// timeline focus. @@ -155,8 +216,11 @@ impl TimelineFocusKind

{ /// Whether to hide in-thread events from the timeline. fn hide_threaded_events(&self) -> bool { match self { - TimelineFocusKind::Live { hide_threaded_events } - | TimelineFocusKind::Event { hide_threaded_events, .. } => *hide_threaded_events, + TimelineFocusKind::Live { hide_threaded_events } => *hide_threaded_events, + TimelineFocusKind::Event { paginator } => paginator + .get() + .map(|paginator| paginator.hide_threaded_events()) + .unwrap_or_default(), TimelineFocusKind::Thread { .. } | TimelineFocusKind::PinnedEvents { .. } => false, } } @@ -170,13 +234,8 @@ impl TimelineFocusKind

{ /// If the focus is a thread, returns its root event ID. fn thread_root(&self) -> Option<&EventId> { match self { - TimelineFocusKind::Event { paginator, hide_threaded_events: _ } => { - match paginator.get() { - Some(AnyPaginator::Threaded(threaded_event_loader)) => { - Some(&threaded_event_loader.root_event_id) - } - _ => None, - } + TimelineFocusKind::Event { paginator, .. } => { + paginator.get().and_then(|paginator| paginator.thread_root()) } TimelineFocusKind::Live { .. } | TimelineFocusKind::PinnedEvents { .. } => None, TimelineFocusKind::Thread { root_event_id } => Some(root_event_id), @@ -338,9 +397,7 @@ impl TimelineController

{ TimelineFocusKind::Live { hide_threaded_events } } - TimelineFocus::Event { hide_threaded_events, .. } => { - TimelineFocusKind::Event { paginator: OnceCell::new(), hide_threaded_events } - } + TimelineFocus::Event { .. } => TimelineFocusKind::Event { paginator: OnceCell::new() }, TimelineFocus::Thread { root_event_id, .. } => { TimelineFocusKind::Thread { root_event_id } @@ -407,7 +464,7 @@ impl TimelineController

{ Ok(has_events) } - TimelineFocus::Event { target: event_id, num_context_events, .. } => { + TimelineFocus::Event { target: event_id, num_context_events, hide_threaded_events } => { let TimelineFocusKind::Event { paginator, .. } = &*self.focus else { // Note: this is sync'd with code in the ctor. unreachable!(); @@ -429,23 +486,22 @@ impl TimelineController

{ if let Some(id) = event.event_id() { id == *event_id } else { false } }, ) - .and_then(|raw_event| match &raw_event.kind { - TimelineEventKind::Decrypted(event) => { - let raw_event = event.event.clone().cast_unchecked(); - extract_thread_root(&raw_event) - } - TimelineEventKind::PlainText { event } => extract_thread_root(event), - TimelineEventKind::UnableToDecrypt { .. } => None, - }); + .and_then(|event| extract_thread_root(event.raw())); let _ = paginator.set(match thread_root_event_id { - Some(root_id) => AnyPaginator::Threaded(ThreadedEventsLoader::new( - self.room_data_provider.clone(), - root_id, - event_paginator.prev_token(), - event_paginator.next_token(), - )), - None => AnyPaginator::Unthreaded(event_paginator), + Some(root_id) => { + let tokens = event_paginator.tokens(); + AnyPaginator::Threaded(ThreadedEventsLoader::new( + self.room_data_provider.clone(), + root_id, + tokens.previous, + tokens.next, + )) + } + None => AnyPaginator::Unthreaded { + paginator: event_paginator, + hide_threaded_events: *hide_threaded_events, + }, }); let has_events = !start_from_result.events.is_empty(); @@ -594,17 +650,16 @@ impl TimelineController

{ | TimelineFocusKind::Thread { .. } => { return Err(PaginationError::NotSupported); } - TimelineFocusKind::Event { paginator, .. } => match paginator.get() { - Some(AnyPaginator::Unthreaded(event_paginator)) => event_paginator - .paginate_backward(num_events.into()) - .await - .map_err(PaginationError::Paginator)?, - Some(AnyPaginator::Threaded(threaded_paginator)) => threaded_paginator - .paginate_backwards(num_events.into()) - .await - .map_err(PaginationError::Paginator)?, - None => return Err(PaginationError::Paginator(NotInstantiated)), - }, + TimelineFocusKind::Event { paginator, .. } => { + if let Some(paginator) = paginator.get() { + paginator + .paginate_backwards(num_events) + .await + .map_err(PaginationError::Paginator)? + } else { + return Err(PaginationError::Paginator(NotInstantiated)); + } + } }; // Events are in reverse topological order. @@ -631,17 +686,16 @@ impl TimelineController

{ | TimelineFocusKind::PinnedEvents { .. } | TimelineFocusKind::Thread { .. } => return Err(PaginationError::NotSupported), - TimelineFocusKind::Event { paginator, .. } => match paginator.get() { - Some(AnyPaginator::Unthreaded(event_paginator)) => event_paginator - .paginate_forward(num_events.into()) - .await - .map_err(PaginationError::Paginator)?, - Some(AnyPaginator::Threaded(threaded_paginator)) => threaded_paginator - .paginate_forwards(num_events.into()) - .await - .map_err(PaginationError::Paginator)?, - None => return Err(PaginationError::Paginator(NotInstantiated)), - }, + TimelineFocusKind::Event { paginator, .. } => { + if let Some(paginator) = paginator.get() { + paginator + .paginate_forwards(num_events) + .await + .map_err(PaginationError::Paginator)? + } else { + return Err(PaginationError::Paginator(NotInstantiated)); + } + } }; // Events are in topological order. diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs b/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs index 32e262a95..93b235b8a 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs @@ -410,10 +410,14 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> { room_data_provider.is_pinned_event(event.event_id()) } - TimelineFocusKind::Event { hide_threaded_events, paginator: _ } => { + TimelineFocusKind::Event { paginator } => { // If the timeline's filtering out in-thread events, don't add items for // threaded events. - if thread_root.is_some() && *hide_threaded_events { + let hide_threaded_events = paginator + .get() + .map(|paginator| paginator.hide_threaded_events()) + .unwrap_or_default(); + if thread_root.is_some() && hide_threaded_events { return false; } diff --git a/crates/matrix-sdk/src/paginators/room.rs b/crates/matrix-sdk/src/paginators/room.rs index 369956b71..7146dc374 100644 --- a/crates/matrix-sdk/src/paginators/room.rs +++ b/crates/matrix-sdk/src/paginators/room.rs @@ -48,12 +48,12 @@ pub enum PaginatorState { } /// Paginations tokens used for backward and forward pagination. -#[derive(Debug)] -pub(crate) struct PaginationTokens { +#[derive(Debug, Clone)] +pub struct PaginationTokens { /// Pagination token used for backward pagination. - pub(crate) previous: PaginationToken, + pub previous: PaginationToken, /// Pagination token used for forward pagination. - pub(crate) next: PaginationToken, + pub next: PaginationToken, } /// A stateful object to reach to an event, and then paginate backward and @@ -340,14 +340,9 @@ impl Paginator { Ok(PaginationResult { events: response.chunk, hit_end_of_timeline }) } - /// Returns the current `prev_token` used for pagination. - pub fn prev_token(&self) -> PaginationToken { - self.tokens.lock().unwrap().previous.clone() - } - - /// Returns the current `next_token` used for pagination. - pub fn next_token(&self) -> PaginationToken { - self.tokens.lock().unwrap().next.clone() + /// Returns the current pagination tokens. + pub fn tokens(&self) -> PaginationTokens { + self.tokens.lock().unwrap().clone() } } diff --git a/crates/matrix-sdk/src/paginators/thread.rs b/crates/matrix-sdk/src/paginators/thread.rs index dec7687de..c0a2610a2 100644 --- a/crates/matrix-sdk/src/paginators/thread.rs +++ b/crates/matrix-sdk/src/paginators/thread.rs @@ -19,7 +19,7 @@ use std::{fmt::Formatter, future::Future, sync::Mutex}; use matrix_sdk_base::{SendOutsideWasm, SyncOutsideWasm, deserialized_responses::TimelineEvent}; -use ruma::{OwnedEventId, UInt, api::Direction}; +use ruma::{EventId, OwnedEventId, UInt, api::Direction}; use crate::{ Error, Room, @@ -63,9 +63,9 @@ pub struct ThreadedEventsLoader { room: P, /// The thread root event ID (the event that started the thread). - pub root_event_id: OwnedEventId, + root_event_id: OwnedEventId, - /// The current pagination token, which is used to keep track of the + /// The current pagination tokens, which are used to keep track of the /// pagination state. tokens: Mutex, } @@ -192,6 +192,11 @@ impl ThreadedEventsLoader

{ Ok(PaginationResult { events: result.chunk, hit_end_of_timeline }) } + + /// Returns the root [`EventId`] for the thread. + pub fn thread_root_event_id(&self) -> &EventId { + &self.root_event_id + } } #[cfg(not(tarpaulin_include))]