diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index e6b073790..75abe1718 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -16,13 +16,13 @@ use std::{collections::HashMap, sync::Arc}; use chrono::{Datelike, Local, TimeZone}; use eyeball_im::ObservableVector; -use indexmap::{map::Entry, IndexMap, IndexSet}; +use indexmap::{map::Entry, IndexMap}; use matrix_sdk::deserialized_responses::EncryptionInfo; use ruma::{ events::{ reaction::ReactionEventContent, receipt::{Receipt, ReceiptType}, - relation::{Annotation, Replacement}, + relation::Replacement, room::{ encrypted::RoomEncryptedEventContent, member::RoomMemberEventContent, @@ -51,6 +51,7 @@ use super::{ }, find_read_marker, item::{new_timeline_item, timeline_item}, + reactions::Reactions, read_receipts::maybe_add_implicit_read_receipt, rfind_event_by_id, rfind_event_item, EventTimelineItem, Message, OtherState, ReactionGroup, ReactionSenderData, Sticker, TimelineDetails, TimelineInnerState, TimelineItem, @@ -214,8 +215,7 @@ pub(super) struct TimelineEventHandler<'a> { flow: Flow, items: &'a mut ObservableVector>, next_internal_id: &'a mut u64, - reaction_map: &'a mut HashMap, - pending_reactions: &'a mut HashMap>, + reactions: &'a mut Reactions, fully_read_event: &'a mut Option, event_should_update_fully_read_marker: &'a mut bool, track_read_receipts: bool, @@ -251,8 +251,7 @@ impl<'a> TimelineEventHandler<'a> { flow, items: &mut state.items, next_internal_id: &mut state.next_internal_id, - reaction_map: &mut state.reaction_map, - pending_reactions: &mut state.pending_reactions, + reactions: &mut state.reactions, fully_read_event: &mut state.fully_read_event, event_should_update_fully_read_marker: &mut state.event_should_update_fully_read_marker, track_read_receipts, @@ -494,7 +493,7 @@ impl<'a> TimelineEventHandler<'a> { return; }; - let pending = self.pending_reactions.entry(event_id.to_owned()).or_default(); + let pending = self.reactions.pending.entry(event_id.to_owned()).or_default(); pending.insert(reaction_event_id); } @@ -502,7 +501,7 @@ impl<'a> TimelineEventHandler<'a> { if let Flow::Remote { txn_id: Some(txn_id), .. } = &self.flow { let id = EventItemIdentifier::TransactionId(txn_id.clone()); // Remove the local echo from the reaction map. - if self.reaction_map.remove(&id).is_none() { + if self.reactions.map.remove(&id).is_none() { warn!( "Received reaction with transaction ID, but didn't \ find matching reaction in reaction_map" @@ -513,7 +512,7 @@ impl<'a> TimelineEventHandler<'a> { sender_id: self.meta.sender.clone(), timestamp: self.meta.timestamp, }; - self.reaction_map.insert(reaction_id, (reaction_sender_data, c.relates_to)); + self.reactions.map.insert(reaction_id, (reaction_sender_data, c.relates_to)); } #[instrument(skip_all)] @@ -526,7 +525,7 @@ impl<'a> TimelineEventHandler<'a> { #[instrument(skip_all, fields(redacts_event_id = ?redacts))] fn handle_redaction(&mut self, redacts: OwnedEventId, _content: RoomRedactionEventContent) { let id = EventItemIdentifier::EventId(redacts.clone()); - if let Some((_, rel)) = self.reaction_map.remove(&id) { + if let Some((_, rel)) = self.reactions.map.remove(&id) { update_timeline_item!(self, &rel.event_id, "redaction", |event_item| { let Some(remote_event_item) = event_item.as_remote() else { error!("inconsistent state: redaction received on a non-remote event item"); @@ -561,7 +560,7 @@ impl<'a> TimelineEventHandler<'a> { }); if self.result.items_updated == 0 { - if let Some(reactions) = self.pending_reactions.get_mut(&rel.event_id) { + if let Some(reactions) = self.reactions.pending.get_mut(&rel.event_id) { if !reactions.remove(&redacts.clone()) { error!( "inconsistent state: reaction from reaction_map not in reaction list \ @@ -609,7 +608,7 @@ impl<'a> TimelineEventHandler<'a> { _content: RoomRedactionEventContent, ) { let id = EventItemIdentifier::TransactionId(redacts); - if let Some((_, rel)) = self.reaction_map.remove(&id) { + if let Some((_, rel)) = self.reactions.map.remove(&id) { update_timeline_item!(self, &rel.event_id, "redaction", |event_item| { let Some(remote_event_item) = event_item.as_remote() else { error!("inconsistent state: redaction received on a non-remote event item"); @@ -1000,13 +999,13 @@ impl<'a> TimelineEventHandler<'a> { match &self.flow { Flow::Local { .. } => None, Flow::Remote { event_id, .. } => { - let reactions = self.pending_reactions.remove(event_id)?; + let reactions = self.reactions.pending.remove(event_id)?; let mut bundled = IndexMap::new(); for reaction_event_id in reactions { let reaction_id = EventItemIdentifier::EventId(reaction_event_id); let Some((reaction_sender_data, annotation)) = - self.reaction_map.get(&reaction_id) + self.reactions.map.get(&reaction_id) else { error!( "inconsistent state: reaction from pending_reactions not in reaction_map" diff --git a/crates/matrix-sdk-ui/src/timeline/inner.rs b/crates/matrix-sdk-ui/src/timeline/inner.rs index 09b157475..d1a66f24c 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner.rs @@ -20,7 +20,7 @@ use eyeball_im::{ObservableVector, ObservableVectorEntry, VectorSubscriber}; #[cfg(any(test, feature = "testing"))] use eyeball_im_util::{FilterMapVectorSubscriber, VectorExt}; use imbl::Vector; -use indexmap::{IndexMap, IndexSet}; +use indexmap::IndexMap; use itertools::Itertools; #[cfg(all(test, feature = "e2e-encryption"))] use matrix_sdk::crypto::OlmMachine; @@ -63,7 +63,7 @@ use super::{ }, event_item::EventItemIdentifier, item::{new_timeline_item, timeline_item}, - reactions::ReactionToggleResult, + reactions::{ReactionToggleResult, Reactions}, rfind_event_by_id, rfind_event_item, traits::RoomDataProvider, AnnotationKey, EventSendState, EventTimelineItem, InReplyToDetails, Message, Profile, @@ -83,11 +83,7 @@ pub(super) struct TimelineInner { pub(super) struct TimelineInnerState { pub(super) items: ObservableVector>, pub(super) next_internal_id: u64, - /// Reaction event / txn ID => sender and reaction data. - pub(super) reaction_map: HashMap, - /// ID of event that is not in the timeline yet => List of reaction event - /// IDs. - pub(super) pending_reactions: HashMap>, + pub(super) reactions: Reactions, pub(super) fully_read_event: Option, /// Whether the fully-read marker item should try to be updated when an /// event is added. @@ -1225,7 +1221,7 @@ impl TimelineInnerState { pub(super) fn clear(&mut self) { self.items.clear(); - self.reaction_map.clear(); + self.reactions.clear(); self.fully_read_event = None; self.event_should_update_fully_read_marker = false; } @@ -1368,7 +1364,7 @@ fn update_timeline_reaction( // (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()); - if state.reaction_map.remove(&id).is_none() { + if state.reactions.map.remove(&id).is_none() { warn!( "Tried to remove reaction by transaction ID, but didn't \ find matching reaction in the reaction map" @@ -1377,7 +1373,7 @@ fn update_timeline_reaction( } // Add the remote echo to the reaction_map if let Some(event_id) = remote_echo_to_add { - state.reaction_map.insert( + state.reactions.map.insert( EventItemIdentifier::EventId(event_id.clone()), (reaction_sender_data, annotation.clone()), ); diff --git a/crates/matrix-sdk-ui/src/timeline/reactions.rs b/crates/matrix-sdk-ui/src/timeline/reactions.rs index cf3dfe01e..a0b3c893c 100644 --- a/crates/matrix-sdk-ui/src/timeline/reactions.rs +++ b/crates/matrix-sdk-ui/src/timeline/reactions.rs @@ -12,7 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId}; +use std::collections::HashMap; + +use indexmap::IndexSet; +use ruma::{ + events::relation::Annotation, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, + OwnedUserId, +}; + +use super::event_item::EventItemIdentifier; /// Data associated with a reaction sender. It can be used to display /// a details UI component for a reaction with both sender @@ -25,6 +33,22 @@ pub struct ReactionSenderData { pub timestamp: MilliSecondsSinceUnixEpoch, } +#[derive(Debug, Default)] +pub(super) struct Reactions { + /// Reaction event / txn ID => sender and reaction data. + pub(super) map: HashMap, + /// ID of event that is not in the timeline yet => List of reaction event + /// IDs. + pub(super) pending: HashMap>, +} + +impl Reactions { + pub(super) fn clear(&mut self) { + self.map.clear(); + self.pending.clear(); + } +} + /// The result of toggling a reaction /// /// Holds the data required to update the state of the reaction in the timeline