diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index bef9f069cc6..7749fc0d4c0 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -51,9 +51,9 @@ use tracing::{debug, error, field::debug, info, instrument, trace, warn}; use super::{ day_dividers::DayDividerAdjuster, event_item::{ - AnyOtherFullStateEventContent, BundledReactions, EventItemIdentifier, EventSendState, - EventTimelineItemKind, LocalEventTimelineItem, Profile, RemoteEventOrigin, - RemoteEventTimelineItem, + AnyOtherFullStateEventContent, BundledReactions, EventSendState, EventTimelineItemKind, + LocalEventTimelineItem, Profile, RemoteEventOrigin, RemoteEventTimelineItem, + TimelineEventItemId, }, inner::{TimelineInnerMetadata, TimelineInnerStateTransaction}, polls::PollState, @@ -535,10 +535,10 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { let event_id: &EventId = &c.relates_to.event_id; let (reaction_id, old_txn_id) = match &self.ctx.flow { Flow::Local { txn_id, .. } => { - (EventItemIdentifier::TransactionId(txn_id.clone()), None) + (TimelineEventItemId::TransactionId(txn_id.clone()), None) } Flow::Remote { event_id, txn_id, .. } => { - (EventItemIdentifier::EventId(event_id.clone()), txn_id.as_ref()) + (TimelineEventItemId::EventId(event_id.clone()), txn_id.as_ref()) } }; @@ -558,7 +558,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { let reaction_group = reactions.entry(c.relates_to.key.clone()).or_default(); if let Some(txn_id) = old_txn_id { - let id = EventItemIdentifier::TransactionId(txn_id.clone()); + let id = TimelineEventItemId::TransactionId(txn_id.clone()); // Remove the local echo from the related event. if reaction_group.0.swap_remove(&id).is_none() { warn!( @@ -584,7 +584,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { } } else { trace!("Timeline item not found, adding reaction to the pending list"); - let EventItemIdentifier::EventId(reaction_event_id) = reaction_id.clone() else { + let TimelineEventItemId::EventId(reaction_event_id) = reaction_id.clone() else { error!("Adding local reaction echo to event absent from the timeline"); return; }; @@ -595,7 +595,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { } if let Flow::Remote { txn_id: Some(txn_id), .. } = &self.ctx.flow { - let id = EventItemIdentifier::TransactionId(txn_id.clone()); + let id = TimelineEventItemId::TransactionId(txn_id.clone()); // Remove the local echo from the reaction map. if self.meta.reactions.map.remove(&id).is_none() { warn!( @@ -723,7 +723,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { // TODO: Apply local redaction of PollResponse and PollEnd events. // https://github.com/matrix-org/matrix-rust-sdk/pull/2381#issuecomment-1689647825 - let id = EventItemIdentifier::EventId(redacts.clone()); + let id = TimelineEventItemId::EventId(redacts.clone()); // If it's a reaction that's being redacted, handle it here. if let Some((_, rel)) = self.meta.reactions.map.remove(&id) { @@ -831,7 +831,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { // Redacted redactions are no-ops (unfortunately) #[instrument(skip_all, fields(redacts_event_id = ?redacts))] fn handle_local_redaction(&mut self, redacts: OwnedTransactionId) { - let id = EventItemIdentifier::TransactionId(redacts); + let id = TimelineEventItemId::TransactionId(redacts); // Redact the reaction, if any. if let Some((_, rel)) = self.meta.reactions.map.remove(&id) { @@ -1069,7 +1069,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { let mut bundled = IndexMap::new(); for reaction_event_id in reactions { - let reaction_id = EventItemIdentifier::EventId(reaction_event_id); + let reaction_id = TimelineEventItemId::EventId(reaction_event_id); let Some((reaction_sender_data, annotation)) = self.meta.reactions.map.get(&reaction_id) else { diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/local.rs b/crates/matrix-sdk-ui/src/timeline/event_item/local.rs index ddf08e3b81c..2890b6eab8e 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/local.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/local.rs @@ -18,6 +18,8 @@ use as_variant::as_variant; use matrix_sdk::{send_queue::SendHandle, Error}; use ruma::{EventId, OwnedEventId, OwnedTransactionId}; +use super::TimelineEventItemId; + /// An item for an event that was created locally and not yet echoed back by /// the homeserver. #[derive(Debug, Clone)] @@ -31,6 +33,20 @@ pub(in crate::timeline) struct LocalEventTimelineItem { } impl LocalEventTimelineItem { + /// Get the unique identifier of this item. + /// + /// Returns the transaction ID for a local echo item that has not been sent + /// and the event ID for a local echo item that has been sent. + pub(crate) fn identifier(&self) -> TimelineEventItemId { + if let Some(event_id) = + as_variant!(&self.send_state, EventSendState::Sent { event_id } => event_id) + { + TimelineEventItemId::EventId(event_id.clone()) + } else { + TimelineEventItemId::TransactionId(self.transaction_id.clone()) + } + } + /// Get the event ID of this item. /// /// Will be `Some` if and only if `send_state` is 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 4781e05a8a6..bc602308265 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -45,10 +45,7 @@ pub(super) use self::{ local::LocalEventTimelineItem, remote::{RemoteEventOrigin, RemoteEventTimelineItem}, }; -use super::{ - EditInfo, RepliedToInfo, ReplyContent, TimelineEventItemId, UnsupportedEditItem, - UnsupportedReplyItem, -}; +use super::{EditInfo, RepliedToInfo, ReplyContent, UnsupportedEditItem, UnsupportedReplyItem}; /// An item in the timeline that represents at least one event. /// @@ -79,7 +76,7 @@ pub(super) enum EventTimelineItemKind { /// A wrapper that can contain either a transaction id, or an event id. #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum EventItemIdentifier { +pub enum TimelineEventItemId { /// The item is local, identified by its transaction id (to be used in /// subsequent requests). TransactionId(OwnedTransactionId), @@ -204,6 +201,20 @@ impl EventTimelineItem { as_variant!(&self.kind, EventTimelineItemKind::Local(local) => &local.send_state) } + /// Get the unique identifier of this item. + /// + /// Returns the transaction ID for a local echo item that has not been sent + /// and the event ID for a local echo item that has been sent or a + /// remote item. + pub(crate) fn identifier(&self) -> TimelineEventItemId { + match &self.kind { + EventTimelineItemKind::Local(local) => local.identifier(), + EventTimelineItemKind::Remote(remote) => { + TimelineEventItemId::EventId(remote.event_id.clone()) + } + } + } + /// Get the transaction ID of a local echo item. /// /// The transaction ID is currently only kept until the remote echo for a @@ -459,23 +470,7 @@ impl EventTimelineItem { return Err(UnsupportedEditItem::NotRoomMessage); }; - let id = match &self.kind { - EventTimelineItemKind::Local(local_event) => { - // Prefer the remote event id if it's already available, as it indicates the - // event has been sent to the server. - if let Some(event_id) = local_event.event_id() { - TimelineEventItemId::Remote(event_id.to_owned()) - } else { - TimelineEventItemId::Local(local_event.transaction_id.clone()) - } - } - - EventTimelineItemKind::Remote(remote_event) => { - TimelineEventItemId::Remote(remote_event.event_id.clone()) - } - }; - - Ok(EditInfo { id, original_message: original_content.clone() }) + Ok(EditInfo { id: self.identifier(), original_message: original_content.clone() }) } } diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/reactions.rs b/crates/matrix-sdk-ui/src/timeline/event_item/reactions.rs index 024682a2d91..d117fb94d83 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/reactions.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/reactions.rs @@ -18,7 +18,7 @@ use indexmap::IndexMap; use itertools::Itertools as _; use ruma::{OwnedEventId, OwnedTransactionId, UserId}; -use super::EventItemIdentifier; +use super::TimelineEventItemId; use crate::timeline::ReactionSenderData; /// The reactions grouped by key. @@ -32,7 +32,7 @@ pub type BundledReactions = IndexMap; /// This is a map of the event ID or transaction ID of the reactions to the ID /// of the sender of the reaction. #[derive(Clone, Debug, Default)] -pub struct ReactionGroup(pub(in crate::timeline) IndexMap); +pub struct ReactionGroup(pub(in crate::timeline) IndexMap); impl ReactionGroup { /// The (deduplicated) senders of the reactions in this group. @@ -51,15 +51,15 @@ impl ReactionGroup { ) -> impl Iterator, Option<&OwnedEventId>)> + 'a { self.iter().filter_map(move |(k, v)| { (v.sender_id == user_id).then_some(match k { - EventItemIdentifier::TransactionId(txn_id) => (Some(txn_id), None), - EventItemIdentifier::EventId(event_id) => (None, Some(event_id)), + TimelineEventItemId::TransactionId(txn_id) => (Some(txn_id), None), + TimelineEventItemId::EventId(event_id) => (None, Some(event_id)), }) }) } } impl Deref for ReactionGroup { - type Target = IndexMap; + type Target = IndexMap; fn deref(&self) -> &Self::Target { &self.0 diff --git a/crates/matrix-sdk-ui/src/timeline/inner/state.rs b/crates/matrix-sdk-ui/src/timeline/inner/state.rs index 383877acbdf..949610a6e04 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/state.rs @@ -38,7 +38,7 @@ use crate::{ Flow, HandleEventResult, TimelineEventContext, TimelineEventHandler, TimelineEventKind, TimelineItemPosition, }, - event_item::{EventItemIdentifier, RemoteEventOrigin}, + event_item::{RemoteEventOrigin, TimelineEventItemId}, polls::PollPendingEvents, reactions::{ReactionToggleResult, Reactions}, read_receipts::ReadReceipts, @@ -294,7 +294,7 @@ impl TimelineInnerState { // Remove the local echo from the related event. if let Some(txn_id) = local_echo_to_remove { - let id = EventItemIdentifier::TransactionId(txn_id.clone()); + let id = TimelineEventItemId::TransactionId(txn_id.clone()); if reaction_group.0.swap_remove(&id).is_none() { warn!( "Tried to remove reaction by transaction ID, but didn't \ @@ -306,7 +306,7 @@ impl TimelineInnerState { // Add the remote echo to the related event if let Some(event_id) = remote_echo_to_add { reaction_group.0.insert( - EventItemIdentifier::EventId(event_id.clone()), + TimelineEventItemId::EventId(event_id.clone()), reaction_sender_data.clone(), ); }; @@ -324,7 +324,7 @@ impl TimelineInnerState { // Remove the local echo from reaction_map // (should the local echo already be up-to-date after event handling?) if let Some(txn_id) = local_echo_to_remove { - let id = EventItemIdentifier::TransactionId(txn_id.clone()); + let id = TimelineEventItemId::TransactionId(txn_id.clone()); if self.meta.reactions.map.remove(&id).is_none() { warn!( "Tried to remove reaction by transaction ID, but didn't \ @@ -335,7 +335,7 @@ impl TimelineInnerState { // Add the remote echo to the reaction_map if let Some(event_id) = remote_echo_to_add { self.meta.reactions.map.insert( - EventItemIdentifier::EventId(event_id.clone()), + TimelineEventItemId::EventId(event_id.clone()), (reaction_sender_data, annotation.clone()), ); } diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index a697ff89b91..f3035b6dc9b 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -18,6 +18,7 @@ use std::{path::PathBuf, pin::Pin, sync::Arc, task::Poll}; +use event_item::TimelineEventItemId; use eyeball_im::VectorDiff; use futures_core::Stream; use imbl::Vector; @@ -107,17 +108,8 @@ use self::{ util::rfind_event_by_id, }; -/// An identifier for a given timeline item. -#[derive(Debug)] -enum TimelineEventItemId { - /// Local echoes are identified by their transaction id. - Local(OwnedTransactionId), - /// Remote echoes are identified by their server-sent event id. - Remote(OwnedEventId), -} - /// Information needed to edit an event. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EditInfo { /// The event ID of the event that needs editing. id: TimelineEventItemId, @@ -125,6 +117,13 @@ pub struct EditInfo { original_message: Message, } +impl EditInfo { + /// The original content of the event that needs editing. + pub fn original_message(&self) -> &Message { + &self.original_message + } +} + /// Information needed to reply to an event. #[derive(Debug)] pub struct RepliedToInfo { @@ -138,12 +137,24 @@ pub struct RepliedToInfo { content: ReplyContent, } +impl RepliedToInfo { + /// The sender of the event to reply to. + pub fn sender(&self) -> &UserId { + &self.sender + } + + /// The content of the event to reply to. + pub fn content(&self) -> &ReplyContent { + &self.content + } +} + /// The content of a reply. -#[derive(Debug)] -enum ReplyContent { +#[derive(Debug, Clone)] +pub enum ReplyContent { /// Content of a message event. Message(Message), - /// Content of any other kind of event stored as raw. + /// Content of any other kind of event stored as raw JSON. Raw(Raw), } @@ -407,7 +418,7 @@ impl Timeline { Ok(()) } - /// Gives the information needed to reply to an event from an event id. + /// Get the information needed to reply to the event with the given ID. pub async fn replied_to_info_from_event_id( &self, event_id: &EventId, @@ -457,17 +468,24 @@ impl Timeline { }) } - /// Send an edit to the given event. + /// Edit an event. /// - /// Currently only supports `m.room.message` events whose event ID is known. - /// Please check [`EventTimelineItem::is_editable`] before calling this. + /// Only supports events for which [`EventTimelineItem::is_editable()`] + /// returns `true`. /// /// # Arguments /// - /// * `new_content` - The content of the reply + /// * `new_content` - The new content of the event. /// /// * `edit_info` - A wrapper that contains the event ID and the content of - /// the event to edit + /// the event to edit. + /// + /// # Returns + /// + /// Returns `Ok(true)` if the edit was added to the send queue. Returns + /// `Ok(false)` if the edit targets a local item but the edit could not be + /// applied, which could mean that the event was already sent. Returns an + /// error if there was an issue adding the edit to the send queue. #[instrument(skip(self, new_content))] pub async fn edit( &self, @@ -475,7 +493,7 @@ impl Timeline { edit_info: EditInfo, ) -> Result { let event_id = match edit_info.id { - TimelineEventItemId::Local(txn_id) => { + TimelineEventItemId::TransactionId(txn_id) => { let Some(item) = self.item_by_transaction_id(&txn_id).await else { warn!("Couldn't find the local echo anymore"); return Ok(false); @@ -491,7 +509,7 @@ impl Timeline { return Ok(false); } - TimelineEventItemId::Remote(event_id) => event_id, + TimelineEventItemId::EventId(event_id) => event_id, }; let original_content = edit_info.original_message; @@ -525,7 +543,7 @@ impl Timeline { Ok(true) } - /// Give the information needed to edit an event from an event id. + /// Get the information needed to edit the event with the given ID. pub async fn edit_info_from_event_id( &self, event_id: &EventId, @@ -564,7 +582,7 @@ impl Timeline { &self.items().await, ); return Ok(EditInfo { - id: TimelineEventItemId::Remote(event_id.to_owned()), + id: TimelineEventItemId::EventId(event_id.to_owned()), original_message: message, }); } diff --git a/crates/matrix-sdk-ui/src/timeline/reactions.rs b/crates/matrix-sdk-ui/src/timeline/reactions.rs index 37ff0e8d96c..028eb6bc441 100644 --- a/crates/matrix-sdk-ui/src/timeline/reactions.rs +++ b/crates/matrix-sdk-ui/src/timeline/reactions.rs @@ -20,7 +20,7 @@ use ruma::{ OwnedUserId, }; -use super::event_item::EventItemIdentifier; +use super::event_item::TimelineEventItemId; /// Data associated with a reaction sender. It can be used to display /// a details UI component for a reaction with both sender @@ -36,7 +36,7 @@ pub struct ReactionSenderData { #[derive(Clone, Debug, Default)] pub(super) struct Reactions { /// Reaction event / txn ID => sender and reaction data. - pub(super) map: HashMap, + pub(super) map: HashMap, /// ID of event that is not in the timeline yet => List of reaction event /// IDs. pub(super) pending: HashMap>, diff --git a/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs b/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs index 7d11db6b01e..5a684f9886a 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/reaction_group.rs @@ -17,7 +17,7 @@ use itertools::Itertools; use matrix_sdk_test::{ALICE, BOB}; use ruma::{server_name, uint, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId}; -use crate::timeline::{event_item::EventItemIdentifier, ReactionGroup, ReactionSenderData}; +use crate::timeline::{event_item::TimelineEventItemId, ReactionGroup, ReactionSenderData}; #[test] fn test_by_sender() { @@ -35,7 +35,7 @@ fn test_by_sender() { let reaction = alice_reactions[0]; - assert_let!(EventItemIdentifier::EventId(event_id) = reaction_1); + assert_let!(TimelineEventItemId::EventId(event_id) = reaction_1); assert_eq!(reaction.1.unwrap(), &event_id); } @@ -121,9 +121,9 @@ fn insert(group: &mut ReactionGroup, sender: &UserId, count: u64) { } } -fn new_reaction() -> EventItemIdentifier { +fn new_reaction() -> TimelineEventItemId { let event_id = EventId::new(server_name!("example.org")); - EventItemIdentifier::EventId(event_id) + TimelineEventItemId::EventId(event_id) } fn new_sender_data(sender: OwnedUserId) -> ReactionSenderData {