From 4074ee830e73d8c94e509e3354187b045e867ced Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 May 2026 16:49:32 +0200 Subject: [PATCH] fix: `can_be_replied_to()` returns `false` for `LiveLocation` events. --- crates/matrix-sdk-ui/CHANGELOG.md | 5 + .../src/timeline/event_item/mod.rs | 145 ++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/crates/matrix-sdk-ui/CHANGELOG.md b/crates/matrix-sdk-ui/CHANGELOG.md index 6b8e8471302..9df278a5739 100644 --- a/crates/matrix-sdk-ui/CHANGELOG.md +++ b/crates/matrix-sdk-ui/CHANGELOG.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - ReleaseDate +### Bug Fixes + +- Remove the ability to reply to live location events. + ([#6563](https://github.com/matrix-org/matrix-rust-sdk/pull/6563)) + ### Refactor - [**breaking**] `SpaceRoom::new_from_known` and `SpaceRoom::new_from_summary` are now asynchronous so we can diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 2f855b5b864..f2877a47fc9 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -431,6 +431,10 @@ impl EventTimelineItem { false } else if self.content.is_message() { true + } else if self.content().as_live_location_state().is_some() { + // Live location sharing session (MSC3489) events are state events, not always + // displayed in a timeline, so can't be replied to. + false } else { self.latest_json().is_some() } @@ -909,3 +913,144 @@ impl From for TimelineEventShieldStateCode { } } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use ruma::{ + MilliSecondsSinceUnixEpoch, + events::{ + AnySyncTimelineEvent, + beacon_info::BeaconInfoEventContent, + room::message::{MessageType, RoomMessageEventContent, TextMessageEventContent}, + }, + owned_event_id, owned_user_id, + serde::Raw, + uint, + }; + use serde_json::json; + + use super::{ + EventSendState, EventTimelineItem, EventTimelineItemKind, LiveLocationState, + LocalEventTimelineItem, Message, MsgLikeContent, MsgLikeKind, RemoteEventOrigin, + RemoteEventTimelineItem, TimelineDetails, TimelineItemContent, + }; + + fn message_content() -> TimelineItemContent { + TimelineItemContent::MsgLike(MsgLikeContent { + kind: MsgLikeKind::Message(Message { + msgtype: MessageType::Text(TextMessageEventContent::plain("hello")), + edited: false, + mentions: None, + }), + reactions: Default::default(), + thread_root: None, + in_reply_to: None, + thread_summary: None, + }) + } + + fn live_location_content() -> TimelineItemContent { + TimelineItemContent::MsgLike(MsgLikeContent { + kind: MsgLikeKind::LiveLocation(LiveLocationState::new(BeaconInfoEventContent::new( + None, + Duration::from_secs(300), + true, + Some(MilliSecondsSinceUnixEpoch(uint!(1))), + ))), + reactions: Default::default(), + thread_root: None, + in_reply_to: None, + thread_summary: None, + }) + } + + fn remote_item( + content: TimelineItemContent, + original_json: Option>, + ) -> EventTimelineItem { + EventTimelineItem::new( + owned_user_id!("@alice:example.org"), + TimelineDetails::Unavailable, + None, + None, + MilliSecondsSinceUnixEpoch(uint!(1)), + content, + EventTimelineItemKind::Remote(RemoteEventTimelineItem { + event_id: owned_event_id!("$event"), + transaction_id: None, + read_receipts: Default::default(), + is_own: false, + is_highlighted: false, + encryption_info: None, + original_json, + latest_edit_json: None, + origin: RemoteEventOrigin::Sync, + }), + false, + ) + } + + fn local_unsent_item(content: TimelineItemContent) -> EventTimelineItem { + EventTimelineItem::new( + owned_user_id!("@alice:example.org"), + TimelineDetails::Unavailable, + None, + None, + MilliSecondsSinceUnixEpoch(uint!(1)), + content, + EventTimelineItemKind::Local(LocalEventTimelineItem { + send_state: EventSendState::NotSentYet { progress: None }, + transaction_id: "t0".into(), + send_handle: None, + }), + false, + ) + } + + fn sample_raw_event() -> Raw { + Raw::from_json_string( + json!({ + "content": RoomMessageEventContent::text_plain("hi"), + "type": "m.room.message", + "event_id": "$event", + "room_id": "!room:example.org", + "origin_server_ts": 1, + "sender": "@alice:example.org", + }) + .to_string(), + ) + .unwrap() + } + + #[test] + fn cannot_reply_to_local_unsent_events() { + let item = local_unsent_item(message_content()); + assert!(!item.can_be_replied_to()); + } + + #[test] + fn can_reply_to_messages() { + let item = remote_item(message_content(), None); + assert!(item.can_be_replied_to()); + } + + #[test] + fn cannot_reply_to_live_location_events() { + let item = remote_item(live_location_content(), Some(sample_raw_event())); + assert!(!item.can_be_replied_to()); + } + + #[test] + fn cannot_reply_to_non_messages_with_no_json() { + let item = remote_item(TimelineItemContent::CallInvite, None); + assert!(!item.can_be_replied_to()); + } + + #[test] + fn can_reply_to_non_messages_with_json() { + let item = remote_item(TimelineItemContent::CallInvite, Some(sample_raw_event())); + assert!(item.can_be_replied_to()); + } +}