Skip to content

Commit d563ceb

Browse files
pixlwaveHywan
authored andcommitted
feat: Allow Timelines to be configured to hide read receipts on state events.
1 parent 19b7036 commit d563ceb

11 files changed

Lines changed: 151 additions & 69 deletions

File tree

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: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,19 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
460460
}
461461
}
462462

463+
/// Whether this event can show read receipts, or if they should be moved
464+
/// to the previous event.
465+
fn can_show_read_receipts(
466+
&self,
467+
settings: &TimelineSettings,
468+
event: &AnySyncTimelineEvent,
469+
) -> bool {
470+
match event {
471+
AnySyncTimelineEvent::State(_) => settings.state_events_can_show_read_receipts,
472+
AnySyncTimelineEvent::MessageLike(_) => true,
473+
}
474+
}
475+
463476
/// After a deserialization error, adds a failed-to-parse item to the
464477
/// timeline if configured to do so, or logs the error (and optionally
465478
/// save metadata) if not.
@@ -478,6 +491,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
478491
Option<TimelineAction>,
479492
Option<OwnedEventId>,
480493
bool,
494+
bool,
481495
)> {
482496
let state_key: Option<String> = raw.get_field("state_key").ok().flatten();
483497

@@ -537,6 +551,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
537551
Some(TimelineAction::failed_to_parse(event_type, deserialization_error)),
538552
None,
539553
true,
554+
true,
540555
))
541556
}
542557

@@ -552,7 +567,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
552567
// Remember the event before returning prematurely.
553568
// See [`ObservableItems::all_remote_events`].
554569
self.add_or_update_remote_event(
555-
EventMeta::new(event_id, false, None),
570+
EventMeta::new(event_id, false, false, None),
556571
sender.as_deref(),
557572
origin_server_ts,
558573
position,
@@ -630,65 +645,74 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
630645
_ => (event.kind.into_raw(), None),
631646
};
632647

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-
);
648+
let (
649+
event_id,
650+
sender,
651+
timestamp,
652+
txn_id,
653+
timeline_action,
654+
thread_root,
655+
should_add,
656+
can_show_read_receipts,
657+
) = match raw.deserialize() {
658+
// Classical path: the event is valid, can be deserialized, everything is alright.
659+
Ok(event) => {
660+
let (in_reply_to, thread_root) = self.meta.process_event_relations(
661+
&event,
662+
&raw,
663+
bundled_edit_encryption_info,
664+
&self.items,
665+
self.focus.is_thread(),
666+
);
644667

645-
let should_add = self.should_add_event_item(
668+
let should_add = self.should_add_event_item(
669+
room_data_provider,
670+
settings,
671+
&event,
672+
thread_root.as_deref(),
673+
position,
674+
);
675+
676+
let can_show_read_receipts = self.can_show_read_receipts(settings, &event);
677+
678+
(
679+
event.event_id().to_owned(),
680+
event.sender().to_owned(),
681+
event.origin_server_ts(),
682+
event.transaction_id().map(ToOwned::to_owned),
683+
TimelineAction::from_event(
684+
event,
685+
&raw,
646686
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,
687+
utd_info
688+
.map(|utd_info| (utd_info, self.meta.unable_to_decrypt_hook.as_ref())),
689+
in_reply_to,
690+
thread_root.clone(),
691+
thread_summary,
672692
)
673-
}
693+
.await,
694+
thread_root,
695+
should_add,
696+
can_show_read_receipts,
697+
)
698+
}
674699

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-
}
700+
// The event seems invalid…
701+
Err(e) => {
702+
if let Some(tuple) =
703+
self.maybe_add_error_item(position, room_data_provider, &raw, e, settings).await
704+
{
705+
tuple
706+
} else {
707+
return false;
685708
}
686-
};
709+
}
710+
};
687711

688712
// Remember the event.
689713
// See [`ObservableItems::all_remote_events`].
690714
self.add_or_update_remote_event(
691-
EventMeta::new(event_id.clone(), should_add, thread_root),
715+
EventMeta::new(event_id.clone(), should_add, can_show_read_receipts, thread_root),
692716
Some(&sender),
693717
Some(timestamp),
694718
position,
@@ -711,7 +735,10 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
711735
sender,
712736
sender_profile,
713737
timestamp,
714-
read_receipts: if settings.track_read_receipts && should_add {
738+
read_receipts: if settings.track_read_receipts
739+
&& should_add
740+
&& can_show_read_receipts
741+
{
715742
self.meta.read_receipts.compute_event_receipts(
716743
&event_id,
717744
&mut self.items,
@@ -887,9 +914,11 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
887914
TimelineItemPosition::UpdateAt { .. } => {
888915
if let Some(event) =
889916
self.items.get_remote_event_by_event_id_mut(&event_meta.event_id)
890-
&& event.visible != event_meta.visible
917+
&& (event.visible != event_meta.visible
918+
|| event.can_show_read_receipts != event_meta.can_show_read_receipts)
891919
{
892920
event.visible = event_meta.visible;
921+
event.can_show_read_receipts = event_meta.can_show_read_receipts;
893922

894923
if settings.track_read_receipts {
895924
// 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;

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,11 @@ async fn test_no_read_marker_with_local_echo() {
239239

240240
let timeline = TestTimelineBuilder::new()
241241
.provider(TestRoomDataProvider::default().with_fully_read_marker(event_id.to_owned()))
242-
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
242+
.settings(TimelineSettings {
243+
track_read_receipts: true,
244+
state_events_can_show_read_receipts: true,
245+
..Default::default()
246+
})
243247
.build();
244248

245249
let f = &timeline.factory;

0 commit comments

Comments
 (0)