diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md index 159fe3f2584..eaa72bd52e8 100644 --- a/crates/matrix-sdk-ui/CHANGELOG.md +++ b/crates/matrix-sdk-ui/CHANGELOG.md @@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file. ### Features +- Utilize the cache and include common relations when focusing a timeline on an event without + requestion context. + ([#5858](https://github.com/matrix-org/matrix-rust-sdk/pull/5858)) - [**breaking**] The `LatestEventValue::Local` type gains 2 new fields: `sender` and `profile`. ([#5885](https://github.com/matrix-org/matrix-rust-sdk/pull/5885)) diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index 9eafcce7f24..6e323c24af1 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -22,6 +22,7 @@ use imbl::Vector; #[cfg(test)] use matrix_sdk::Result; use matrix_sdk::{ + config::RequestConfig, deserialized_responses::TimelineEvent, event_cache::{DecryptionRetryRequest, RoomEventCache, RoomPaginationStatus}, paginators::{PaginationResult, PaginationToken, Paginator}, @@ -40,7 +41,7 @@ use ruma::{ poll::unstable_start::UnstablePollStartEventContent, reaction::ReactionEventContent, receipt::{Receipt, ReceiptThread, ReceiptType}, - relation::Annotation, + relation::{Annotation, RelationType}, room::message::{MessageType, Relation}, }, room_version_rules::RoomVersionRules, @@ -467,16 +468,46 @@ impl TimelineController

{ let event_paginator = Paginator::new(self.room_data_provider.clone()); - // Start a /context request so we can know if the event is in a thread or not, - // and know which kind of pagination we'll be using then. - let start_from_result = event_paginator - .start_from(event_id, (*num_context_events).into()) - .await - .map_err(PaginationError::Paginator)?; + let load_events_with_context = || async { + // Start a /context request to load the focussed event and surrounding events. + event_paginator + .start_from(event_id, (*num_context_events).into()) + .await + .map(|r| r.events) + .map_err(PaginationError::Paginator) + }; + + let events = if *num_context_events == 0 { + // If no context is requested, try to load the event from the cache first and + // include common relations such as reactions and edits. + let request_config = Some(RequestConfig::default().retry_limit(3)); + let relations_filter = + Some(vec![RelationType::Annotation, RelationType::Replacement]); + + // Load the event from the cache or, failing that, the server. + match self + .room_data_provider + .load_event_with_relations(event_id, request_config, relations_filter) + .await + { + Ok((event, related_events)) => { + let mut events = vec![event]; + events.extend(related_events); + events + } + Err(err) => { + error!("error when loading focussed event: {err}"); + // Fall back to load the focussed event using /context. + load_events_with_context().await? + } + } + } else { + // Start a /context request to load the focussed event and surrounding events. + load_events_with_context().await? + }; // Find the target event, and see if it's part of a thread. - let thread_root_event_id = start_from_result - .events + let thread_root_event_id = events .iter() .find( |event| { @@ -492,7 +523,7 @@ impl TimelineController

{ // Look if the thread root event is part of the /context response. This // allows us to spare some backwards pagination with // /relations. - let includes_root_event = start_from_result.events.iter().any(|event| { + let includes_root_event = events.iter().any(|event| { if let Some(id) = event.event_id() { id == root_id } else { false } }); @@ -515,8 +546,7 @@ impl TimelineController

{ }, }); - let has_events = !start_from_result.events.is_empty(); - let events = start_from_result.events; + let has_events = !events.is_empty(); match paginator.get().expect("Paginator was not instantiated") { AnyPaginator::Unthreaded { .. } => { diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs index 19d18604b0f..ddb9a37bbeb 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/mod.rs @@ -97,6 +97,57 @@ async fn test_timeline_is_threaded() { assert!(timeline.is_threaded()); } + { + // An event-focused timeline, focused on a non-thread event, isn't threaded when + // no context is requested. + let f = EventFactory::new(); + let event = f + .text_msg("hello world") + .event_id(event_id!("$target")) + .room(room_id) + .sender(&ALICE) + .into_event(); + server.mock_room_event().match_event_id().ok(event).mock_once().mount().await; + + let timeline = TimelineBuilder::new(&room) + .with_focus(TimelineFocus::Event { + target: owned_event_id!("$target"), + num_context_events: 0, + hide_threaded_events: true, + }) + .build() + .await + .unwrap(); + assert!(timeline.is_threaded().not()); + } + + { + // But an event-focused timeline, focused on an in-thread event, is threaded + // when no context is requested \o/ + let f = EventFactory::new(); + let thread_root = event_id!("$thread_root"); + let event = f + .text_msg("hey to you too") + .event_id(event_id!("$thetarget")) + .in_thread(thread_root, thread_root) + .room(room_id) + .sender(&ALICE) + .into_event(); + + server.mock_room_event().match_event_id().ok(event).mock_once().mount().await; + + let timeline = TimelineBuilder::new(&room) + .with_focus(TimelineFocus::Event { + target: owned_event_id!("$thetarget"), + num_context_events: 0, + hide_threaded_events: true, + }) + .build() + .await + .unwrap(); + assert!(timeline.is_threaded()); + } + { // An event-focused timeline, focused on a non-thread event, isn't threaded. let f = EventFactory::new(); @@ -116,7 +167,7 @@ async fn test_timeline_is_threaded() { let timeline = TimelineBuilder::new(&room) .with_focus(TimelineFocus::Event { target: owned_event_id!("$target"), - num_context_events: 0, + num_context_events: 2, hide_threaded_events: true, }) .build() @@ -147,7 +198,7 @@ async fn test_timeline_is_threaded() { let timeline = TimelineBuilder::new(&room) .with_focus(TimelineFocus::Event { target: owned_event_id!("$target"), - num_context_events: 0, + num_context_events: 2, hide_threaded_events: true, }) .build() diff --git a/crates/matrix-sdk-ui/tests/integration/timeline/thread.rs b/crates/matrix-sdk-ui/tests/integration/timeline/thread.rs index 8a1c38f79f0..8fb2c5a7ffb 100644 --- a/crates/matrix-sdk-ui/tests/integration/timeline/thread.rs +++ b/crates/matrix-sdk-ui/tests/integration/timeline/thread.rs @@ -1848,7 +1848,7 @@ async fn test_permalink_doesnt_listen_to_thread_sync() { let timeline = TimelineBuilder::new(&room) .with_focus(TimelineFocus::Event { target: owned_event_id!("$target"), - num_context_events: 0, + num_context_events: 2, hide_threaded_events: true, }) .build()