diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index 13bf6bf40..02b6777b0 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -62,7 +62,8 @@ use super::{ TimelineDetails, TimelineItem, TimelineItemContent, }; use crate::{ - events::SyncTimelineEventWithoutContent, timeline::event_item::ReactionInfo, + events::SyncTimelineEventWithoutContent, + timeline::{event_item::ReactionInfo, reactions::PendingReaction}, DEFAULT_SANITIZER_MODE, }; @@ -547,6 +548,11 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { } }; + let reaction_sender_data = ReactionSenderData { + sender_id: self.ctx.sender.clone(), + timestamp: self.ctx.timestamp, + }; + if let Some((idx, event_item)) = rfind_event_by_id(self.items, reacted_to_event_id) { let Some(remote_event_item) = event_item.as_remote() else { error!("received reaction to a local echo"); @@ -580,12 +586,13 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { return; }; - self.meta - .reactions - .pending - .entry(reacted_to_event_id.to_owned()) - .or_default() - .insert(reaction_event_id); + self.meta.reactions.pending.entry(reacted_to_event_id.to_owned()).or_default().insert( + reaction_event_id, + PendingReaction { + key: c.relates_to.key.clone(), + sender_data: reaction_sender_data.clone(), + }, + ); } if let Some(txn_id) = old_txn_id { @@ -595,16 +602,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { self.meta.reactions.map.remove(&TimelineEventItemId::TransactionId(txn_id.clone())); } - self.meta.reactions.map.insert( - reaction_id, - ( - ReactionSenderData { - sender_id: self.ctx.sender.clone(), - timestamp: self.ctx.timestamp, - }, - c.relates_to, - ), - ); + self.meta.reactions.map.insert(reaction_id, (reaction_sender_data, c.relates_to)); } #[instrument(skip_all, fields(replacement_event_id = ?replacement.event_id))] @@ -1015,23 +1013,16 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> { let reactions = self.meta.reactions.pending.remove(event_id)?; let mut bundled = IndexMap::new(); - for reaction_event_id in reactions { - let reaction_id = TimelineEventItemId::EventId(reaction_event_id); - let Some((reaction_sender_data, annotation)) = - self.meta.reactions.map.get(&reaction_id) - else { - error!( - "inconsistent state: reaction from pending_reactions not in reaction_map" - ); - continue; - }; - + for (reaction_event_id, reaction) in reactions { let group: &mut IndexMap = - bundled.entry(annotation.key.clone()).or_default(); + bundled.entry(reaction.key).or_default(); group.insert( - reaction_sender_data.sender_id.clone(), - ReactionInfo { timestamp: reaction_sender_data.timestamp, id: reaction_id }, + reaction.sender_data.sender_id, + ReactionInfo { + timestamp: reaction.sender_data.timestamp, + id: TimelineEventItemId::EventId(reaction_event_id), + }, ); } diff --git a/crates/matrix-sdk-ui/src/timeline/reactions.rs b/crates/matrix-sdk-ui/src/timeline/reactions.rs index 4ecebd75c..c58b29e39 100644 --- a/crates/matrix-sdk-ui/src/timeline/reactions.rs +++ b/crates/matrix-sdk-ui/src/timeline/reactions.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; -use indexmap::{IndexMap, IndexSet}; +use indexmap::IndexMap; use ruma::{ events::relation::Annotation, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, @@ -62,7 +62,7 @@ pub(super) enum ReactionState { /// Data associated with a reaction sender. It can be used to display /// a details UI component for a reaction with both sender /// names and the date at which they sent a reaction. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ReactionSenderData { /// Sender identifier. pub sender_id: OwnedUserId, @@ -70,13 +70,19 @@ pub struct ReactionSenderData { pub timestamp: MilliSecondsSinceUnixEpoch, } +#[derive(Clone, Debug)] +pub(crate) struct PendingReaction { + pub key: String, + pub sender_data: ReactionSenderData, +} + #[derive(Clone, Debug, Default)] pub(super) struct Reactions { /// Reaction event / txn ID => sender and reaction data. pub map: HashMap, - /// ID of event that is not in the timeline yet => List of reaction event - /// IDs. - pub pending: HashMap>, + /// Mapping of events that are not in the timeline => reaction event id => + /// pending reaction. + pub pending: HashMap>, /// The local reaction request state that is queued next. pub reaction_state: IndexMap, /// The in-flight reaction request state that is ongoing.