feat(timeline): forward the TimelineEvent::thread_summary to timeline items

Right now, we're not passing the latest event yet, but we could improve
that later!
This commit is contained in:
Benjamin Bouvier
2025-05-28 19:40:45 +02:00
parent 433209ca9b
commit efda26ef6f
6 changed files with 105 additions and 18 deletions

View File

@@ -392,6 +392,15 @@ impl ThreadSummaryStatus {
fn is_unknown(&self) -> bool {
matches!(self, ThreadSummaryStatus::Unknown)
}
/// Transforms the [`ThreadSummaryStatus`] into an optional thread summary,
/// for cases where we don't care about distinguishing unknown and none.
pub fn summary(&self) -> Option<&ThreadSummary> {
match self {
ThreadSummaryStatus::Unknown | ThreadSummaryStatus::None => None,
ThreadSummaryStatus::Some(thread_summary) => Some(thread_summary),
}
}
}
/// Represents a Matrix room event that has been returned from `/sync`,

View File

@@ -37,7 +37,7 @@ use super::{
};
use crate::timeline::{
event_handler::{FailedToParseEvent, RemovedItem, TimelineAction},
VirtualTimelineItem,
ThreadSummary, TimelineDetails, VirtualTimelineItem,
};
pub(in crate::timeline) struct TimelineStateTransaction<'a> {
@@ -205,6 +205,7 @@ impl<'a> TimelineStateTransaction<'a> {
room_data_provider,
None,
None,
None,
&self.items,
&mut self.meta,
)
@@ -555,7 +556,12 @@ impl<'a> TimelineStateTransaction<'a> {
date_divider_adjuster: &mut DateDividerAdjuster,
) -> RemovedItem {
// TODO: do something with the thread summary!
let TimelineEvent { push_actions, kind, thread_summary: _thread_summary } = event;
let TimelineEvent { push_actions, kind, thread_summary } = event;
let thread_summary = thread_summary.summary().map(|_common_summary| {
// TODO: later, fill the latest event in the thread summary!
ThreadSummary { latest_event: TimelineDetails::Unavailable }
});
let encryption_info = kind.encryption_info().cloned();
@@ -584,6 +590,7 @@ impl<'a> TimelineStateTransaction<'a> {
event,
&raw,
room_data_provider,
thread_summary,
utd_info,
bundled_edit_encryption_info,
&self.items,

View File

@@ -57,7 +57,8 @@ use super::{
},
traits::RoomDataProvider,
EncryptedMessage, EventTimelineItem, InReplyToDetails, MsgLikeContent, MsgLikeKind, OtherState,
ReactionStatus, RepliedToEvent, Sticker, TimelineDetails, TimelineItem, TimelineItemContent,
ReactionStatus, RepliedToEvent, Sticker, ThreadSummary, TimelineDetails, TimelineItem,
TimelineItemContent,
};
use crate::timeline::controller::aggregations::PendingEdit;
@@ -151,6 +152,7 @@ pub(super) struct RemoteEventContext<'a> {
raw_event: &'a Raw<AnySyncTimelineEvent>,
relations: BundledMessageLikeRelations<AnySyncMessageLikeEvent>,
bundled_edit_encryption_info: Option<Arc<EncryptionInfo>>,
thread_summary: Option<ThreadSummary>,
}
/// An action that we want to cause on the timeline.
@@ -194,6 +196,7 @@ impl TimelineAction {
event: AnySyncTimelineEvent,
raw_event: &Raw<AnySyncTimelineEvent>,
room_data_provider: &P,
thread_summary: Option<ThreadSummary>,
unable_to_decrypt_info: Option<UnableToDecryptInfo>,
bundled_edit_encryption_info: Option<Arc<EncryptionInfo>>,
timeline_items: &Vector<Arc<TimelineItem>>,
@@ -257,6 +260,7 @@ impl TimelineAction {
raw_event,
relations: ev.relations(),
bundled_edit_encryption_info,
thread_summary,
}),
timeline_items,
meta,
@@ -272,6 +276,7 @@ impl TimelineAction {
raw_event,
relations: ev.relations(),
bundled_edit_encryption_info,
thread_summary,
}),
timeline_items,
meta,
@@ -371,7 +376,7 @@ impl TimelineAction {
timeline_items,
);
if let Some(event_id) = remote_ctx.map(|ctx| ctx.event_id) {
if let Some(event_id) = remote_ctx.as_ref().map(|ctx| ctx.event_id) {
Self::mark_response(meta, event_id, in_reply_to.as_ref());
}
@@ -380,7 +385,7 @@ impl TimelineAction {
reactions: Default::default(),
thread_root,
in_reply_to,
thread_summary: None,
thread_summary: remote_ctx.and_then(|ctx| ctx.thread_summary),
}))
}
@@ -391,7 +396,7 @@ impl TimelineAction {
Self::extract_reply_and_thread_root(c.relates_to.clone(), timeline_items);
// Record the bundled edit in the aggregations set, if any.
if let Some(ctx) = remote_ctx {
let thread_summary = if let Some(ctx) = remote_ctx {
if let Some(new_content) = extract_poll_edit_content(ctx.relations) {
// It is replacing the current event.
if let Some(edit_event_id) =
@@ -417,7 +422,11 @@ impl TimelineAction {
}
Self::mark_response(meta, ctx.event_id, in_reply_to.as_ref());
}
ctx.thread_summary
} else {
None
};
let poll_state = PollState::new(c);
@@ -427,7 +436,7 @@ impl TimelineAction {
reactions: Default::default(),
thread_root,
in_reply_to,
thread_summary: None,
thread_summary,
}),
}
}
@@ -439,7 +448,7 @@ impl TimelineAction {
);
// Record the bundled edit in the aggregations set, if any.
if let Some(ctx) = remote_ctx {
let thread_summary = if let Some(ctx) = remote_ctx {
if let Some(new_content) = extract_room_msg_edit_content(ctx.relations) {
// It is replacing the current event.
if let Some(edit_event_id) =
@@ -465,7 +474,11 @@ impl TimelineAction {
}
Self::mark_response(meta, ctx.event_id, in_reply_to.as_ref());
}
ctx.thread_summary
} else {
None
};
Self::AddItem {
content: TimelineItemContent::message(
@@ -474,7 +487,7 @@ impl TimelineAction {
Default::default(),
thread_root,
in_reply_to,
None,
thread_summary,
),
}
}

View File

@@ -121,11 +121,15 @@ impl RepliedToEvent {
debug!(event_type = %event.event_type(), "got deserialized event");
// We don't need to fill the thread information of an embedded reply.
let thread_summary = None;
let sender = event.sender().to_owned();
let action = TimelineAction::from_event(
event,
&raw_event,
room_data_provider,
thread_summary,
unable_to_decrypt_info,
bundled_edit_encryption_info,
timeline_items,

View File

@@ -693,7 +693,7 @@ impl<T> TimelineDetails<T> {
}
}
pub(crate) fn is_unavailable(&self) -> bool {
pub fn is_unavailable(&self) -> bool {
matches!(self, Self::Unavailable)
}

View File

@@ -15,10 +15,19 @@
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use futures_util::StreamExt as _;
use matrix_sdk::test_utils::mocks::{MatrixMockServer, RoomRelationsResponseTemplate};
use matrix_sdk_test::{async_test, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
use ruma::{event_id, events::AnyTimelineEvent, owned_event_id, room_id, serde::Raw, user_id};
use matrix_sdk::{
assert_let_timeout,
test_utils::mocks::{MatrixMockServer, RoomRelationsResponseTemplate},
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder, ALICE};
use matrix_sdk_ui::timeline::{RoomExt as _, TimelineBuilder, TimelineFocus};
use ruma::{
event_id,
events::{relation::BundledThread, AnyTimelineEvent, BundledMessageLikeRelations},
owned_event_id, room_id,
serde::Raw,
uint, user_id,
};
use stream_assert::assert_pending;
#[async_test]
@@ -151,8 +160,6 @@ async fn test_thread_backpagination() {
// events and the thread root
assert_eq!(timeline_updates.len(), 5);
println!("Stefan: {timeline_updates:?}");
// Check the timeline diffs
assert_let!(VectorDiff::PushFront { value } = &timeline_updates[0]);
assert_eq!(value.as_event().unwrap().event_id().unwrap(), event_id!("$2"));
@@ -193,3 +200,50 @@ async fn test_thread_backpagination() {
"Threaded event 4"
);
}
#[async_test]
async fn test_thread_summary() {
// A sync event that includes a bundled thread summary receives a
// `ThreadSummary` in the associated timeline content.
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let room_id = room_id!("!a:b.c");
let room = server.sync_joined_room(&client, room_id).await;
let timeline = room.timeline().await.unwrap();
let (initial_items, mut stream) = timeline.subscribe().await;
assert!(initial_items.is_empty());
let f = EventFactory::new().room(room_id).sender(&ALICE);
let thread_event_id = event_id!("$thread_root");
let latest_event_id = event_id!("$latest_event");
let latest_thread_event = f.text_msg("the last one!").event_id(latest_event_id).into_raw();
let mut relations = BundledMessageLikeRelations::new();
relations.thread = Some(Box::new(BundledThread::new(latest_thread_event, uint!(42), false)));
let event = f
.text_msg("thready thread mcthreadface")
.bundled_relations(relations)
.event_id(thread_event_id);
server.sync_room(&client, JoinedRoomBuilder::new(room_id).add_timeline_event(event)).await;
assert_let_timeout!(Some(timeline_updates) = stream.next());
// Message+day divider.
assert_eq!(timeline_updates.len(), 2);
// Check the timeline diffs.
assert_let!(VectorDiff::PushBack { value } = &timeline_updates[0]);
let event_item = value.as_event().unwrap();
assert_eq!(event_item.event_id().unwrap(), thread_event_id);
assert_let!(Some(summary) = event_item.content().thread_summary());
// Soon™, Stefan, soon™.
assert!(summary.latest_event.is_unavailable());
assert_let!(VectorDiff::PushFront { value } = &timeline_updates[1]);
assert!(value.is_date_divider());
}