Skip to content

Commit 89aa291

Browse files
authored
ui/ffi: Add timestamp to reactions
1 parent 2225e8a commit 89aa291

File tree

10 files changed

+304
-88
lines changed

10 files changed

+304
-88
lines changed

bindings/matrix-sdk-ffi/src/timeline.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,13 @@ impl EventTimelineItem {
324324
.map(|(k, v)| Reaction {
325325
key: k.to_owned(),
326326
count: v.len().try_into().unwrap(),
327-
senders: v.senders().map(ToString::to_string).collect(),
327+
senders: v
328+
.senders()
329+
.map(|v| ReactionSenderData {
330+
sender_id: v.sender_id.to_string(),
331+
timestamp: v.timestamp.0.into(),
332+
})
333+
.collect(),
328334
})
329335
.collect()
330336
}
@@ -1082,13 +1088,13 @@ impl EncryptedMessage {
10821088
pub struct Reaction {
10831089
pub key: String,
10841090
pub count: u64,
1085-
pub senders: Vec<String>,
1091+
pub senders: Vec<ReactionSenderData>,
10861092
}
10871093

1088-
#[derive(Clone)]
1089-
pub struct ReactionDetails {
1090-
pub id: String,
1091-
pub sender: String,
1094+
#[derive(Clone, uniffi::Record)]
1095+
pub struct ReactionSenderData {
1096+
pub sender_id: String,
1097+
pub timestamp: u64,
10921098
}
10931099

10941100
/// A [`TimelineItem`](super::TimelineItem) that doesn't correspond to an event.

crates/matrix-sdk-ui/src/timeline/event_handler.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,17 @@ use tracing::{debug, error, field::debug, info, instrument, trace, warn};
4242

4343
use super::{
4444
event_item::{
45-
AnyOtherFullStateEventContent, BundledReactions, EventSendState, EventTimelineItemKind,
46-
LocalEventTimelineItem, Profile, RemoteEventOrigin, RemoteEventTimelineItem,
45+
AnyOtherFullStateEventContent, BundledReactions, EventItemIdentifier, EventSendState,
46+
EventTimelineItemKind, LocalEventTimelineItem, Profile, RemoteEventOrigin,
47+
RemoteEventTimelineItem,
4748
},
4849
find_read_marker,
4950
read_receipts::maybe_add_implicit_read_receipt,
5051
rfind_event_by_id, rfind_event_item, EventTimelineItem, Message, OtherState, ReactionGroup,
5152
Sticker, TimelineDetails, TimelineInnerState, TimelineItem, TimelineItemContent,
5253
VirtualTimelineItem, DEFAULT_SANITIZER_MODE,
5354
};
54-
use crate::events::SyncTimelineEventWithoutContent;
55+
use crate::{events::SyncTimelineEventWithoutContent, timeline::event_item::ReactionSenderData};
5556

5657
#[derive(Clone)]
5758
pub(super) enum Flow {
@@ -208,11 +209,7 @@ pub(super) struct TimelineEventHandler<'a> {
208209
meta: TimelineEventMetadata,
209210
flow: Flow,
210211
items: &'a mut ObservableVector<Arc<TimelineItem>>,
211-
#[allow(clippy::type_complexity)]
212-
reaction_map: &'a mut HashMap<
213-
(Option<OwnedTransactionId>, Option<OwnedEventId>),
214-
(OwnedUserId, Annotation),
215-
>,
212+
reaction_map: &'a mut HashMap<EventItemIdentifier, (ReactionSenderData, Annotation)>,
216213
pending_reactions: &'a mut HashMap<OwnedEventId, IndexSet<OwnedEventId>>,
217214
fully_read_event: &'a mut Option<OwnedEventId>,
218215
event_should_update_fully_read_marker: &'a mut bool,
@@ -433,9 +430,11 @@ impl<'a> TimelineEventHandler<'a> {
433430
fn handle_reaction(&mut self, c: ReactionEventContent) {
434431
let event_id: &EventId = &c.relates_to.event_id;
435432
let (reaction_id, old_txn_id) = match &self.flow {
436-
Flow::Local { txn_id, .. } => ((Some(txn_id.clone()), None), None),
433+
Flow::Local { txn_id, .. } => {
434+
(EventItemIdentifier::TransactionId(txn_id.clone()), None)
435+
}
437436
Flow::Remote { event_id, txn_id, .. } => {
438-
((None, Some(event_id.clone())), txn_id.as_ref())
437+
(EventItemIdentifier::EventId(event_id.clone()), txn_id.as_ref())
439438
}
440439
};
441440

@@ -455,15 +454,22 @@ impl<'a> TimelineEventHandler<'a> {
455454
let reaction_group = reactions.entry(c.relates_to.key.clone()).or_default();
456455

457456
if let Some(txn_id) = old_txn_id {
457+
let id = EventItemIdentifier::TransactionId(txn_id.clone());
458458
// Remove the local echo from the related event.
459-
if reaction_group.0.remove(&(Some(txn_id.clone()), None)).is_none() {
459+
if reaction_group.0.remove(&id).is_none() {
460460
warn!(
461461
"Received reaction with transaction ID, but didn't \
462462
find matching reaction in the related event's reactions"
463463
);
464464
}
465465
}
466-
reaction_group.0.insert(reaction_id.clone(), self.meta.sender.clone());
466+
reaction_group.0.insert(
467+
reaction_id.clone(),
468+
ReactionSenderData {
469+
sender_id: self.meta.sender.clone(),
470+
timestamp: self.meta.timestamp,
471+
},
472+
);
467473

468474
trace!("Adding reaction");
469475
self.items.set(
@@ -476,26 +482,31 @@ impl<'a> TimelineEventHandler<'a> {
476482
}
477483
} else {
478484
trace!("Timeline item not found, adding reaction to the pending list");
479-
let (None, Some(reaction_event_id)) = &reaction_id else {
485+
let EventItemIdentifier::EventId(reaction_event_id) = reaction_id.clone() else {
480486
error!("Adding local reaction echo to event absent from the timeline");
481487
return;
482488
};
483489

484490
let pending = self.pending_reactions.entry(event_id.to_owned()).or_default();
485491

486-
pending.insert(reaction_event_id.clone());
492+
pending.insert(reaction_event_id);
487493
}
488494

489495
if let Flow::Remote { txn_id: Some(txn_id), .. } = &self.flow {
496+
let id = EventItemIdentifier::TransactionId(txn_id.clone());
490497
// Remove the local echo from the reaction map.
491-
if self.reaction_map.remove(&(Some(txn_id.clone()), None)).is_none() {
498+
if self.reaction_map.remove(&id).is_none() {
492499
warn!(
493500
"Received reaction with transaction ID, but didn't \
494501
find matching reaction in reaction_map"
495502
);
496503
}
497504
}
498-
self.reaction_map.insert(reaction_id, (self.meta.sender.clone(), c.relates_to));
505+
let reaction_sender_data = ReactionSenderData {
506+
sender_id: self.meta.sender.clone(),
507+
timestamp: self.meta.timestamp,
508+
};
509+
self.reaction_map.insert(reaction_id, (reaction_sender_data, c.relates_to));
499510
}
500511

501512
#[instrument(skip_all)]
@@ -507,7 +518,8 @@ impl<'a> TimelineEventHandler<'a> {
507518
// Redacted redactions are no-ops (unfortunately)
508519
#[instrument(skip_all, fields(redacts_event_id = ?redacts))]
509520
fn handle_redaction(&mut self, redacts: OwnedEventId, _content: RoomRedactionEventContent) {
510-
if let Some((_, rel)) = self.reaction_map.remove(&(None, Some(redacts.clone()))) {
521+
let id = EventItemIdentifier::EventId(redacts.clone());
522+
if let Some((_, rel)) = self.reaction_map.remove(&id) {
511523
update_timeline_item!(self, &rel.event_id, "redaction", |event_item| {
512524
let Some(remote_event_item) = event_item.as_remote() else {
513525
error!("inconsistent state: redaction received on a non-remote event item");
@@ -522,7 +534,7 @@ impl<'a> TimelineEventHandler<'a> {
522534
};
523535
let group = group_entry.get_mut();
524536

525-
if group.0.remove(&(None, Some(redacts.clone()))).is_none() {
537+
if group.0.remove(&id).is_none() {
526538
error!(
527539
"inconsistent state: reaction from reaction_map not in reaction list \
528540
of timeline item"
@@ -543,7 +555,7 @@ impl<'a> TimelineEventHandler<'a> {
543555

544556
if self.result.items_updated == 0 {
545557
if let Some(reactions) = self.pending_reactions.get_mut(&rel.event_id) {
546-
if !reactions.remove(&redacts) {
558+
if !reactions.remove(&redacts.clone()) {
547559
error!(
548560
"inconsistent state: reaction from reaction_map not in reaction list \
549561
of pending_reactions"
@@ -589,7 +601,8 @@ impl<'a> TimelineEventHandler<'a> {
589601
redacts: OwnedTransactionId,
590602
_content: RoomRedactionEventContent,
591603
) {
592-
if let Some((_, rel)) = self.reaction_map.remove(&(Some(redacts.clone()), None)) {
604+
let id = EventItemIdentifier::TransactionId(redacts);
605+
if let Some((_, rel)) = self.reaction_map.remove(&id) {
593606
update_timeline_item!(self, &rel.event_id, "redaction", |event_item| {
594607
let Some(remote_event_item) = event_item.as_remote() else {
595608
error!("inconsistent state: redaction received on a non-remote event item");
@@ -602,7 +615,7 @@ impl<'a> TimelineEventHandler<'a> {
602615
};
603616
let group = group_entry.get_mut();
604617

605-
if group.0.remove(&(Some(redacts.clone()), None)).is_none() {
618+
if group.0.remove(&id).is_none() {
606619
error!(
607620
"inconsistent state: reaction from reaction_map not in reaction list \
608621
of timeline item"
@@ -941,8 +954,10 @@ impl<'a> TimelineEventHandler<'a> {
941954
let mut bundled = IndexMap::new();
942955

943956
for reaction_event_id in reactions {
944-
let reaction_id = (None, Some(reaction_event_id));
945-
let Some((sender, annotation)) = self.reaction_map.get(&reaction_id) else {
957+
let reaction_id = EventItemIdentifier::EventId(reaction_event_id);
958+
let Some((reaction_sender_data, annotation)) =
959+
self.reaction_map.get(&reaction_id)
960+
else {
946961
error!(
947962
"inconsistent state: reaction from pending_reactions not in reaction_map"
948963
);
@@ -951,7 +966,7 @@ impl<'a> TimelineEventHandler<'a> {
951966

952967
let group: &mut ReactionGroup =
953968
bundled.entry(annotation.key.clone()).or_default();
954-
group.0.insert(reaction_id, sender.clone());
969+
group.0.insert(reaction_id, reaction_sender_data.clone());
955970
}
956971

957972
Some(bundled)

crates/matrix-sdk-ui/src/timeline/event_item/content.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use ruma::{
6060
};
6161
use tracing::{debug, error, warn};
6262

63-
use super::{EventTimelineItem, Profile, TimelineDetails};
63+
use super::{EventItemIdentifier, EventTimelineItem, Profile, ReactionSenderData, TimelineDetails};
6464
use crate::timeline::{
6565
traits::RoomDataProvider, Error as TimelineError, TimelineItem, DEFAULT_SANITIZER_MODE,
6666
};
@@ -538,22 +538,17 @@ impl From<RoomEncryptedEventContent> for EncryptedMessage {
538538
/// Key: The reaction, usually an emoji.\
539539
/// Value: The group of reactions.
540540
pub type BundledReactions = IndexMap<String, ReactionGroup>;
541-
542-
// The long type after a long visibility specified trips up rustfmt currently.
543-
// This works around. Report: https://github.com/rust-lang/rustfmt/issues/5703
544-
type ReactionGroupInner = IndexMap<(Option<OwnedTransactionId>, Option<OwnedEventId>), OwnedUserId>;
545-
546541
/// A group of reaction events on the same event with the same key.
547542
///
548543
/// This is a map of the event ID or transaction ID of the reactions to the ID
549544
/// of the sender of the reaction.
550545
#[derive(Clone, Debug, Default)]
551-
pub struct ReactionGroup(pub(in crate::timeline) ReactionGroupInner);
546+
pub struct ReactionGroup(pub(in crate::timeline) IndexMap<EventItemIdentifier, ReactionSenderData>);
552547

553548
impl ReactionGroup {
554549
/// The (deduplicated) senders of the reactions in this group.
555-
pub fn senders(&self) -> impl Iterator<Item = &UserId> {
556-
self.values().unique().map(AsRef::as_ref)
550+
pub fn senders(&self) -> impl Iterator<Item = &ReactionSenderData> {
551+
self.values().unique_by(|v| &v.sender_id)
557552
}
558553

559554
/// All reactions within this reaction group that were sent by the given
@@ -565,13 +560,17 @@ impl ReactionGroup {
565560
&'a self,
566561
user_id: &'a UserId,
567562
) -> impl Iterator<Item = (Option<&OwnedTransactionId>, Option<&OwnedEventId>)> + 'a {
568-
self.iter()
569-
.filter_map(move |(k, v)| (*v == user_id).then_some((k.0.as_ref(), k.1.as_ref())))
563+
self.iter().filter_map(move |(k, v)| {
564+
(v.sender_id == user_id).then_some(match k {
565+
EventItemIdentifier::TransactionId(txn_id) => (Some(txn_id), None),
566+
EventItemIdentifier::EventId(event_id) => (None, Some(event_id)),
567+
})
568+
})
570569
}
571570
}
572571

573572
impl Deref for ReactionGroup {
574-
type Target = IndexMap<(Option<OwnedTransactionId>, Option<OwnedEventId>), OwnedUserId>;
573+
type Target = IndexMap<EventItemIdentifier, ReactionSenderData>;
575574

576575
fn deref(&self) -> &Self::Target {
577576
&self.0

crates/matrix-sdk-ui/src/timeline/event_item/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ use once_cell::sync::Lazy;
2121
use ruma::{
2222
events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent},
2323
serde::Raw,
24-
EventId, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedUserId, TransactionId, UserId,
24+
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri, OwnedTransactionId,
25+
OwnedUserId, TransactionId, UserId,
2526
};
2627
use tracing::warn;
2728

@@ -69,6 +70,24 @@ pub(super) enum EventTimelineItemKind {
6970
Remote(RemoteEventTimelineItem),
7071
}
7172

73+
/// A wrapper that can contain either a transaction id, or an event id.
74+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
75+
pub enum EventItemIdentifier {
76+
TransactionId(OwnedTransactionId),
77+
EventId(OwnedEventId),
78+
}
79+
80+
/// Data associated with a reaction sender. It can be used to display
81+
/// a details UI component for a reaction with both sender
82+
/// names and the date at which they sent a reaction.
83+
#[derive(Clone, Debug, Eq, PartialEq)]
84+
pub struct ReactionSenderData {
85+
/// Sender identifier.
86+
pub sender_id: OwnedUserId,
87+
/// Date at which the sender reacted.
88+
pub timestamp: MilliSecondsSinceUnixEpoch,
89+
}
90+
7291
impl EventTimelineItem {
7392
pub(super) fn new(
7493
sender: OwnedUserId,

0 commit comments

Comments
 (0)