diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index 89c49db90..f68b2758c 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -31,7 +31,8 @@ use ruma::{ receipt::Receipt, relation::Replacement, room::message::{ - Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation, + Relation, RelationWithoutReplacement, RoomMessageEventContent, + RoomMessageEventContentWithoutRelation, }, AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, BundledMessageLikeRelations, EventContent, FullStateEventContent, @@ -354,11 +355,44 @@ impl TimelineAction { } AnyMessageLikeEventContent::Sticker(content) => { + let mut replied_to_event_id = None; + let mut thread_root = None; + let in_reply_to_details = + content.relates_to.as_ref().and_then(|relation| match relation { + Relation::Reply { in_reply_to } => { + replied_to_event_id = Some(in_reply_to.event_id.clone()); + Some(InReplyToDetails::new( + in_reply_to.event_id.clone(), + timeline_items, + )) + } + Relation::Thread(thread) => { + thread_root = Some(thread.event_id.clone()); + thread.in_reply_to.as_ref().map(|in_reply_to| { + replied_to_event_id = Some(in_reply_to.event_id.clone()); + InReplyToDetails::new(in_reply_to.event_id.clone(), timeline_items) + }) + } + _ => None, + }); + + // If this message is a reply to another message, add an entry in the + // inverted mapping. + if let Some(event_id) = event_id { + if let Some(replied_to_event_id) = replied_to_event_id { + // This is a reply! Add an entry. + meta.replies + .entry(replied_to_event_id) + .or_default() + .insert(event_id.to_owned()); + } + } + Self::add_item(TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(Sticker { content }), reactions: Default::default(), - thread_root: None, - in_reply_to: None, + thread_root, + in_reply_to: in_reply_to_details, thread_summary: None, })) } @@ -393,6 +427,39 @@ impl TimelineAction { .or(pending_edit) .unzip(); + let mut replied_to_event_id = None; + let mut thread_root = None; + let in_reply_to_details = + c.relates_to.as_ref().and_then(|relation| match relation { + RelationWithoutReplacement::Reply { in_reply_to } => { + replied_to_event_id = Some(in_reply_to.event_id.clone()); + Some(InReplyToDetails::new( + in_reply_to.event_id.clone(), + timeline_items, + )) + } + RelationWithoutReplacement::Thread(thread) => { + thread_root = Some(thread.event_id.clone()); + thread.in_reply_to.as_ref().map(|in_reply_to| { + replied_to_event_id = Some(in_reply_to.event_id.clone()); + InReplyToDetails::new(in_reply_to.event_id.clone(), timeline_items) + }) + } + _ => None, + }); + + // If this message is a reply to another message, add an entry in the + // inverted mapping. + if let Some(event_id) = event_id { + if let Some(replied_to_event_id) = replied_to_event_id { + // This is a reply! Add an entry. + meta.replies + .entry(replied_to_event_id) + .or_default() + .insert(event_id.to_owned()); + } + } + let poll_state = PollState::new(c, edit_content); let edit_json = edit_json.flatten(); @@ -401,8 +468,8 @@ impl TimelineAction { content: TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(poll_state), reactions: Default::default(), - thread_root: None, - in_reply_to: None, + thread_root, + in_reply_to: in_reply_to_details, thread_summary: None, }), edit_json, diff --git a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs index 2b4078f84..7379083fd 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/basic.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs @@ -22,6 +22,7 @@ use matrix_sdk_test::{ async_test, event_factory::PreviousMembership, sync_timeline_event, ALICE, BOB, CAROL, }; use ruma::{ + event_id, events::{ receipt::{Receipt, ReceiptThread, ReceiptType}, room::{ @@ -141,6 +142,13 @@ async fn test_sticker() { "w": 394 }, "url": "mxc://server.name/JWEIFJgwEIhweiWJE", + "m.relates_to": { + "rel_type": "m.thread", + "event_id": "$thread_root", + "m.in_reply_to": { + "event_id": "$in_reply_to" + } + } }, "event_id": "$143273582443PhrSn", "origin_server_ts": 143273582, @@ -151,6 +159,11 @@ async fn test_sticker() { let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value); assert!(item.content().is_sticker()); + + assert_eq!(item.content().thread_root(), Some(event_id!("$thread_root").to_owned())); + + assert_let!(Some(details) = item.content().in_reply_to()); + assert_eq!(details.event_id, event_id!("$in_reply_to").to_owned()) } #[async_test] diff --git a/crates/matrix-sdk-ui/src/timeline/tests/polls.rs b/crates/matrix-sdk-ui/src/timeline/tests/polls.rs index bd1fd5989..850b4d2e8 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/polls.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/polls.rs @@ -1,6 +1,8 @@ +use assert_matches2::assert_let; use fakes::poll_a2; use matrix_sdk_test::{async_test, ALICE, BOB}; use ruma::{ + event_id, events::{ poll::{ unstable_end::UnstablePollEndEventContent, @@ -230,6 +232,28 @@ async fn test_ending_poll_doesnt_clear_latest_json_edit() { assert!(timeline.event_items().await[0].latest_edit_json().is_some()); } +#[async_test] +async fn test_poll_contains_relations() { + let timeline = TestTimeline::new(); + + let thread_root_id = event_id!("$thread_root"); + let in_reply_to_id = event_id!("$in_reply_to"); + + let poll_start = poll_a2(&timeline.factory) + .in_thread(thread_root_id, in_reply_to_id) + .sender(&ALICE) + .event_id(event_id!("$poll")); + + timeline.handle_live_event(poll_start).await; + + let poll = timeline.event_items().await[0].clone(); + + assert_eq!(poll.content().thread_root(), Some(thread_root_id.to_owned())); + + assert_let!(Some(details) = poll.content().in_reply_to()); + assert_eq!(details.event_id, in_reply_to_id); +} + impl TestTimeline { async fn event_items(&self) -> Vec { self.controller.items().await.iter().filter_map(|item| item.as_event().cloned()).collect() diff --git a/testing/matrix-sdk-test/src/event_factory.rs b/testing/matrix-sdk-test/src/event_factory.rs index 98f380473..339ba0d66 100644 --- a/testing/matrix-sdk-test/src/event_factory.rs +++ b/testing/matrix-sdk-test/src/event_factory.rs @@ -45,7 +45,8 @@ use ruma::{ member::{MembershipState, RoomMemberEventContent}, message::{ FormattedBody, ImageMessageEventContent, MessageType, Relation, - RoomMessageEventContent, RoomMessageEventContentWithoutRelation, + RelationWithoutReplacement, RoomMessageEventContent, + RoomMessageEventContentWithoutRelation, }, name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent, @@ -360,6 +361,29 @@ impl EventBuilder { } } +impl EventBuilder { + /// Adds a reply relation to the current event. + pub fn reply_to(mut self, event_id: &EventId) -> Self { + if let UnstablePollStartEventContent::New(content) = &mut self.content { + content.relates_to = Some(RelationWithoutReplacement::Reply { + in_reply_to: InReplyTo::new(event_id.to_owned()), + }); + }; + self + } + + /// Adds a thread relation to the root event, setting the reply to + /// event id as well. + pub fn in_thread(mut self, root: &EventId, reply_to_event_id: &EventId) -> Self { + let thread = Thread::reply(root.to_owned(), reply_to_event_id.to_owned()); + + if let UnstablePollStartEventContent::New(content) = &mut self.content { + content.relates_to = Some(RelationWithoutReplacement::Thread(thread)); + }; + self + } +} + impl From> for Raw where E::EventType: Serialize,