Skip to content

Commit 3470137

Browse files
committed
feat: Allow Timelines to be configured for renderer that can't show read receipts on state events.
1 parent 45a9d96 commit 3470137

File tree

12 files changed

+215
-72
lines changed

12 files changed

+215
-72
lines changed

bindings/matrix-sdk-ffi/src/room/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ impl Room {
233233

234234
builder = builder
235235
.with_focus(configuration.focus.try_into()?)
236-
.with_date_divider_mode(configuration.date_divider_mode.into());
236+
.with_date_divider_mode(configuration.date_divider_mode.into())
237+
.state_events_can_show_read_receipts(configuration.state_events_can_show_read_receipts);
237238

238239
if configuration.track_read_receipts {
239240
builder = builder.track_read_marker_and_receipts();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ pub struct TimelineConfiguration {
164164
/// How should we filter out events from the timeline?
165165
pub filter: TimelineFilter,
166166

167+
/// Can read receipts be shown on state events or only on messages?
168+
pub state_events_can_show_read_receipts: bool,
169+
167170
/// An optional String that will be prepended to
168171
/// all the timeline item's internal IDs, making it possible to
169172
/// distinguish different timeline instances from each other.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ impl TimelineBuilder {
141141
self
142142
}
143143

144+
pub fn state_events_can_show_read_receipts(mut self, show: bool) -> Self {
145+
self.settings.state_events_can_show_read_receipts = show;
146+
self
147+
}
148+
144149
/// Whether to add events that failed to deserialize to the timeline.
145150
///
146151
/// Defaults to `true`.

crates/matrix-sdk-ui/src/timeline/controller/metadata.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ pub(in crate::timeline) struct EventMeta {
519519
/// Whether the event is among the timeline items.
520520
pub visible: bool,
521521

522+
/// Whether the event can show read receipts.
523+
pub can_show_read_receipts: bool,
524+
522525
/// Foundation for the mapping between remote events to timeline items.
523526
///
524527
/// Let's explain it. The events represent the first set and are stored in
@@ -587,8 +590,15 @@ impl EventMeta {
587590
pub fn new(
588591
event_id: OwnedEventId,
589592
visible: bool,
593+
can_show_read_receipts: bool,
590594
thread_root_id: Option<OwnedEventId>,
591595
) -> Self {
592-
Self { event_id, thread_root_id, visible, timeline_item_index: None }
596+
Self {
597+
event_id,
598+
thread_root_id,
599+
visible,
600+
can_show_read_receipts,
601+
timeline_item_index: None,
602+
}
593603
}
594604
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ pub(super) struct TimelineSettings {
268268
/// Should the read receipts and read markers be handled?
269269
pub(super) track_read_receipts: bool,
270270

271+
/// Whether state events can show read receipts.
272+
pub(super) state_events_can_show_read_receipts: bool,
273+
271274
/// Event filter that controls what's rendered as a timeline item (and thus
272275
/// what can carry read receipts).
273276
pub(super) event_filter: Arc<TimelineEventFilterFn>,
@@ -284,6 +287,7 @@ impl fmt::Debug for TimelineSettings {
284287
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285288
f.debug_struct("TimelineSettings")
286289
.field("track_read_receipts", &self.track_read_receipts)
290+
.field("state_events_can_show_read_receipts", &self.state_events_can_show_read_receipts)
287291
.field("add_failed_to_parse", &self.add_failed_to_parse)
288292
.finish_non_exhaustive()
289293
}
@@ -293,6 +297,7 @@ impl Default for TimelineSettings {
293297
fn default() -> Self {
294298
Self {
295299
track_read_receipts: false,
300+
state_events_can_show_read_receipts: true,
296301
event_filter: Arc::new(default_event_filter),
297302
add_failed_to_parse: true,
298303
date_divider_mode: DateDividerMode::Daily,

crates/matrix-sdk-ui/src/timeline/controller/observable_items.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ mod observable_items_tests {
801801
thread_root_id: None,
802802
timeline_item_index: None,
803803
visible: false,
804+
can_show_read_receipts: false,
804805
}
805806
}
806807

@@ -2065,6 +2066,7 @@ mod all_remote_events_tests {
20652066
thread_root_id: None,
20662067
timeline_item_index,
20672068
visible: false,
2069+
can_show_read_receipts: false,
20682070
}
20692071
}
20702072

crates/matrix-sdk-ui/src/timeline/controller/read_receipts.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,13 @@ impl ReadReceipts {
131131
old_receipt_pos = Some(pos);
132132
}
133133

134-
// The receipt should appear on the first event that is visible.
135-
if old_receipt_pos.is_some() && old_item_event_id.is_none() && event.visible {
134+
// The receipt should appear on the first visible event that can show read
135+
// receipts.
136+
if old_receipt_pos.is_some()
137+
&& old_item_event_id.is_none()
138+
&& event.visible
139+
&& event.can_show_read_receipts
140+
{
136141
old_item_pos = event.timeline_item_index;
137142
old_item_event_id = Some(event.event_id.clone());
138143
}
@@ -141,8 +146,13 @@ impl ReadReceipts {
141146
new_receipt_pos = Some(pos);
142147
}
143148

144-
// The receipt should appear on the first event that is visible.
145-
if new_receipt_pos.is_some() && new_item_event_id.is_none() && event.visible {
149+
// The receipt should appear on the first visible event that can show read
150+
// receipts.
151+
if new_receipt_pos.is_some()
152+
&& new_item_event_id.is_none()
153+
&& event.visible
154+
&& event.can_show_read_receipts
155+
{
146156
new_item_pos = event.timeline_item_index;
147157
new_item_event_id = Some(event.event_id.clone());
148158
}
@@ -316,11 +326,16 @@ impl ReadReceipts {
316326
}
317327
}
318328

319-
// Include receipts for all the following non-visible events.
329+
// Include receipts from all the following events that are hidden or can't show
330+
// read receipts.
320331
let mut hidden = Vec::new();
321-
for hidden_event_meta in events_iter.take_while(|meta| !meta.visible) {
322-
if let Some(event_receipts) = self.get_event_receipts(&hidden_event_meta.event_id) {
323-
trace!(%hidden_event_meta.event_id, "found receipts on hidden event");
332+
for hidden_receipt_event_meta in
333+
events_iter.take_while(|meta| !meta.visible || !meta.can_show_read_receipts)
334+
{
335+
if let Some(event_receipts) =
336+
self.get_event_receipts(&hidden_receipt_event_meta.event_id)
337+
{
338+
trace!(%hidden_receipt_event_meta.event_id, "found receipts on hidden event");
324339
hidden.extend(event_receipts.clone());
325340
}
326341
}
@@ -659,8 +674,8 @@ impl<P: RoomDataProvider> TimelineStateTransaction<'_, P> {
659674
.skip_while(|meta| meta.event_id != event_id)
660675
// Go past the event item.
661676
.skip(1)
662-
// Find the first visible item.
663-
.find(|meta| meta.visible)
677+
// Find the first visible item that can show read receipts.
678+
.find(|meta| meta.visible && meta.can_show_read_receipts)
664679
else {
665680
trace!("Couldn't find any previous visible event, exiting");
666681
return;
@@ -806,7 +821,7 @@ impl<P: RoomDataProvider> TimelineState<P> {
806821
.iter()
807822
.rev()
808823
.skip_while(|ev| ev.event_id != *latest_receipt_id)
809-
.find(|ev| ev.visible)
824+
.find(|ev| ev.visible && ev.can_show_read_receipts)
810825
.map(|ev| ev.event_id.clone())
811826
}
812827
}

crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
184184
}
185185

186186
self.adjust_date_dividers(date_divider_adjuster);
187-
self.check_invariants();
187+
self.check_invariants(settings);
188188
}
189189

190190
async fn handle_remote_aggregation(
@@ -339,11 +339,13 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
339339
}
340340

341341
self.adjust_date_dividers(date_divider_adjuster);
342-
self.check_invariants();
342+
self.check_invariants(settings);
343343
}
344344

345-
fn check_invariants(&self) {
346-
self.check_no_duplicate_read_receipts();
345+
fn check_invariants(&self, settings: &TimelineSettings) {
346+
if settings.state_events_can_show_read_receipts {
347+
self.check_no_duplicate_read_receipts();
348+
}
347349
self.check_no_unused_unique_ids();
348350
}
349351

@@ -460,6 +462,19 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
460462
}
461463
}
462464

465+
/// Whether this event can show read receipts, or if they should be moved
466+
/// to the previous event.
467+
fn can_show_read_receipts(
468+
&self,
469+
settings: &TimelineSettings,
470+
event: &AnySyncTimelineEvent,
471+
) -> bool {
472+
match event {
473+
AnySyncTimelineEvent::State(_) => settings.state_events_can_show_read_receipts,
474+
AnySyncTimelineEvent::MessageLike(_) => true,
475+
}
476+
}
477+
463478
/// After a deserialization error, adds a failed-to-parse item to the
464479
/// timeline if configured to do so, or logs the error (and optionally
465480
/// save metadata) if not.
@@ -478,6 +493,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
478493
Option<TimelineAction>,
479494
Option<OwnedEventId>,
480495
bool,
496+
bool,
481497
)> {
482498
let state_key: Option<String> = raw.get_field("state_key").ok().flatten();
483499

@@ -537,6 +553,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
537553
Some(TimelineAction::failed_to_parse(event_type, deserialization_error)),
538554
None,
539555
true,
556+
true,
540557
))
541558
}
542559

@@ -552,7 +569,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
552569
// Remember the event before returning prematurely.
553570
// See [`ObservableItems::all_remote_events`].
554571
self.add_or_update_remote_event(
555-
EventMeta::new(event_id, false, None),
572+
EventMeta::new(event_id, false, false, None),
556573
sender.as_deref(),
557574
origin_server_ts,
558575
position,
@@ -630,65 +647,74 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
630647
_ => (event.kind.into_raw(), None),
631648
};
632649

633-
let (event_id, sender, timestamp, txn_id, timeline_action, thread_root, should_add) =
634-
match raw.deserialize() {
635-
// Classical path: the event is valid, can be deserialized, everything is alright.
636-
Ok(event) => {
637-
let (in_reply_to, thread_root) = self.meta.process_event_relations(
638-
&event,
639-
&raw,
640-
bundled_edit_encryption_info,
641-
&self.items,
642-
self.focus.is_thread(),
643-
);
650+
let (
651+
event_id,
652+
sender,
653+
timestamp,
654+
txn_id,
655+
timeline_action,
656+
thread_root,
657+
should_add,
658+
can_show_read_receipts,
659+
) = match raw.deserialize() {
660+
// Classical path: the event is valid, can be deserialized, everything is alright.
661+
Ok(event) => {
662+
let (in_reply_to, thread_root) = self.meta.process_event_relations(
663+
&event,
664+
&raw,
665+
bundled_edit_encryption_info,
666+
&self.items,
667+
self.focus.is_thread(),
668+
);
669+
670+
let should_add = self.should_add_event_item(
671+
room_data_provider,
672+
settings,
673+
&event,
674+
thread_root.as_deref(),
675+
position,
676+
);
677+
678+
let can_show_read_receipts = self.can_show_read_receipts(settings, &event);
644679

645-
let should_add = self.should_add_event_item(
680+
(
681+
event.event_id().to_owned(),
682+
event.sender().to_owned(),
683+
event.origin_server_ts(),
684+
event.transaction_id().map(ToOwned::to_owned),
685+
TimelineAction::from_event(
686+
event,
687+
&raw,
646688
room_data_provider,
647-
settings,
648-
&event,
649-
thread_root.as_deref(),
650-
position,
651-
);
652-
653-
(
654-
event.event_id().to_owned(),
655-
event.sender().to_owned(),
656-
event.origin_server_ts(),
657-
event.transaction_id().map(ToOwned::to_owned),
658-
TimelineAction::from_event(
659-
event,
660-
&raw,
661-
room_data_provider,
662-
utd_info.map(|utd_info| {
663-
(utd_info, self.meta.unable_to_decrypt_hook.as_ref())
664-
}),
665-
in_reply_to,
666-
thread_root.clone(),
667-
thread_summary,
668-
)
669-
.await,
670-
thread_root,
671-
should_add,
689+
utd_info
690+
.map(|utd_info| (utd_info, self.meta.unable_to_decrypt_hook.as_ref())),
691+
in_reply_to,
692+
thread_root.clone(),
693+
thread_summary,
672694
)
673-
}
695+
.await,
696+
thread_root,
697+
should_add,
698+
can_show_read_receipts,
699+
)
700+
}
674701

675-
// The event seems invalid…
676-
Err(e) => {
677-
if let Some(tuple) = self
678-
.maybe_add_error_item(position, room_data_provider, &raw, e, settings)
679-
.await
680-
{
681-
tuple
682-
} else {
683-
return false;
684-
}
702+
// The event seems invalid…
703+
Err(e) => {
704+
if let Some(tuple) =
705+
self.maybe_add_error_item(position, room_data_provider, &raw, e, settings).await
706+
{
707+
tuple
708+
} else {
709+
return false;
685710
}
686-
};
711+
}
712+
};
687713

688714
// Remember the event.
689715
// See [`ObservableItems::all_remote_events`].
690716
self.add_or_update_remote_event(
691-
EventMeta::new(event_id.clone(), should_add, thread_root),
717+
EventMeta::new(event_id.clone(), should_add, can_show_read_receipts, thread_root),
692718
Some(&sender),
693719
Some(timestamp),
694720
position,
@@ -887,9 +913,11 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
887913
TimelineItemPosition::UpdateAt { .. } => {
888914
if let Some(event) =
889915
self.items.get_remote_event_by_event_id_mut(&event_meta.event_id)
890-
&& event.visible != event_meta.visible
916+
&& (event.visible != event_meta.visible
917+
|| event.can_show_read_receipts != event_meta.can_show_read_receipts)
891918
{
892919
event.visible = event_meta.visible;
920+
event.can_show_read_receipts = event_meta.can_show_read_receipts;
893921

894922
if settings.track_read_receipts {
895923
// Since the event's visibility changed, we need to update the read

crates/matrix-sdk-ui/src/timeline/tests/basic.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ async fn test_replace_with_initial_events_and_read_marker() {
9898
.with_fully_read_marker(event_id)
9999
.with_initial_user_receipts(receipts),
100100
)
101-
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
101+
.settings(TimelineSettings {
102+
track_read_receipts: true,
103+
state_events_can_show_read_receipts: true,
104+
..Default::default()
105+
})
102106
.build();
103107

104108
let f = &timeline.factory;

0 commit comments

Comments
 (0)