chore: Re-use the existing track_read_receipts setting to hide receipts on state events.

# Conflicts:
#	bindings/matrix-sdk-ffi/CHANGELOG.md
This commit is contained in:
Doug
2025-11-26 14:55:05 +00:00
committed by Ivan Enderlin
parent d563cebcfc
commit 59604713e8
16 changed files with 133 additions and 74 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::test_utils::mocks::MatrixMockServer;
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
use matrix_sdk_ui::timeline::TimelineBuilder;
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineReadReceiptTracking};
use ruma::{
EventId, events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id,
owned_user_id,
@@ -103,7 +103,7 @@ pub fn create_timeline_with_initial_events(c: &mut Criterion) {
|b| {
b.to_async(&runtime).iter(|| async {
let timeline = TimelineBuilder::new(&room)
.track_read_marker_and_receipts()
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
.build()
.await
.expect("Could not create timeline");
+3
View File
@@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
### Breaking changes
- `TimelineConfiguration::track_read_receipts`'s type is now an enum to allow tracking to be enabled for all events
(like before) or only for message-like events (which prevents read receipts from being placed on state events).
([#5900](https://github.com/matrix-org/matrix-rust-sdk/pull/5900))
- `Client::reset_server_info()` has been split into `reset_supported_versions()`
and `reset_well_known()`.
([#5910](https://github.com/matrix-org/matrix-rust-sdk/pull/5910))
+1 -5
View File
@@ -234,11 +234,7 @@ impl Room {
builder = builder
.with_focus(configuration.focus.try_into()?)
.with_date_divider_mode(configuration.date_divider_mode.into())
.state_events_can_show_read_receipts(configuration.state_events_can_show_read_receipts);
if configuration.track_read_receipts {
builder = builder.track_read_marker_and_receipts();
}
.track_read_marker_and_receipts(configuration.track_read_receipts);
match configuration.filter {
TimelineFilter::All => {
@@ -1,6 +1,9 @@
use std::sync::Arc;
use matrix_sdk_ui::timeline::event_type_filter::TimelineEventTypeFilter as InnerTimelineEventTypeFilter;
use matrix_sdk_ui::timeline::{
event_type_filter::TimelineEventTypeFilter as InnerTimelineEventTypeFilter,
TimelineReadReceiptTracking,
};
use ruma::{
events::{AnySyncTimelineEvent, TimelineEventType},
EventId,
@@ -164,9 +167,6 @@ pub struct TimelineConfiguration {
/// How should we filter out events from the timeline?
pub filter: TimelineFilter,
/// Can read receipts be shown on state events or only on messages?
pub state_events_can_show_read_receipts: bool,
/// An optional String that will be prepended to
/// all the timeline item's internal IDs, making it possible to
/// distinguish different timeline instances from each other.
@@ -176,11 +176,11 @@ pub struct TimelineConfiguration {
pub date_divider_mode: DateDividerMode,
/// Should the read receipts and read markers be tracked for the timeline
/// items in this instance?
/// items in this instance and on which event types?
///
/// As this has a non negligible performance impact, make sure to enable it
/// only when you need it.
pub track_read_receipts: bool,
pub track_read_receipts: TimelineReadReceiptTracking,
/// Whether this timeline instance should report UTDs through the client's
/// delegate.
+7
View File
@@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - ReleaseDate
### Features
- [**breaking**] `TimelineBuilder::track_read_marker_and_receipts` now takes a parameter to allow tracking to be enabled
for all events (like before) or only for message-like events (which prevents read receipts from being placed on state
events).
([#5900](https://github.com/matrix-org/matrix-rust-sdk/pull/5900))
## [0.15.0] - 2025-11-27
### Features
+7 -11
View File
@@ -25,6 +25,7 @@ use super::{
};
use crate::{
timeline::{
TimelineReadReceiptTracking,
controller::spawn_crypto_tasks,
tasks::{
pinned_events_task, room_event_cache_updates_task, room_send_queue_update_task,
@@ -91,17 +92,17 @@ impl TimelineBuilder {
self
}
/// Chose when to insert the date separators, either in between each day
/// Choose when to insert the date separators, either in between each day
/// or each month.
pub fn with_date_divider_mode(mut self, mode: DateDividerMode) -> Self {
self.settings.date_divider_mode = mode;
self
}
/// Enable tracking of the fully-read marker and the read receipts on the
/// timeline.
pub fn track_read_marker_and_receipts(mut self) -> Self {
self.settings.track_read_receipts = true;
/// Choose whether to enable tracking of the fully-read marker and the read
/// receipts and on which event types.
pub fn track_read_marker_and_receipts(mut self, tracking: TimelineReadReceiptTracking) -> Self {
self.settings.track_read_receipts = tracking;
self
}
@@ -141,11 +142,6 @@ impl TimelineBuilder {
self
}
pub fn state_events_can_show_read_receipts(mut self, show: bool) -> Self {
self.settings.state_events_can_show_read_receipts = show;
self
}
/// Whether to add events that failed to deserialize to the timeline.
///
/// Defaults to `true`.
@@ -159,7 +155,7 @@ impl TimelineBuilder {
skip(self),
fields(
room_id = ?self.room.room_id(),
track_read_receipts = self.settings.track_read_receipts,
track_read_receipts = ?self.settings.track_read_receipts,
)
)]
pub async fn build(self) -> Result<Timeline, Error> {
@@ -61,7 +61,8 @@ pub(super) use self::{
use super::{
DateDividerMode, EmbeddedEvent, Error, EventSendState, EventTimelineItem, InReplyToDetails,
MediaUploadProgress, PaginationError, Profile, TimelineDetails, TimelineEventItemId,
TimelineFocus, TimelineItem, TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
TimelineFocus, TimelineItem, TimelineItemContent, TimelineItemKind,
TimelineReadReceiptTracking, VirtualTimelineItem,
algorithms::{rfind_event_by_id, rfind_event_item},
event_item::{ReactionStatus, RemoteEventOrigin},
item::TimelineUniqueId,
@@ -265,11 +266,9 @@ pub(super) struct TimelineController<P: RoomDataProvider = Room> {
#[derive(Clone)]
pub(super) struct TimelineSettings {
/// Should the read receipts and read markers be handled?
pub(super) track_read_receipts: bool,
/// Whether state events can show read receipts.
pub(super) state_events_can_show_read_receipts: bool,
/// Should the read receipts and read markers be handled and on which event
/// types?
pub(super) track_read_receipts: TimelineReadReceiptTracking,
/// Event filter that controls what's rendered as a timeline item (and thus
/// what can carry read receipts).
@@ -287,7 +286,6 @@ impl fmt::Debug for TimelineSettings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TimelineSettings")
.field("track_read_receipts", &self.track_read_receipts)
.field("state_events_can_show_read_receipts", &self.state_events_can_show_read_receipts)
.field("add_failed_to_parse", &self.add_failed_to_parse)
.finish_non_exhaustive()
}
@@ -296,8 +294,7 @@ impl fmt::Debug for TimelineSettings {
impl Default for TimelineSettings {
fn default() -> Self {
Self {
track_read_receipts: false,
state_events_can_show_read_receipts: true,
track_read_receipts: TimelineReadReceiptTracking::Disabled,
event_filter: Arc::new(default_event_filter),
add_failed_to_parse: true,
date_divider_mode: DateDividerMode::Daily,
@@ -986,8 +983,8 @@ impl<P: RoomDataProvider> TimelineController<P> {
{
let mut state = self.state.write().await;
let track_read_markers = self.settings.track_read_receipts;
if track_read_markers {
let track_read_markers = &self.settings.track_read_receipts;
if track_read_markers.is_enabled() {
state.populate_initial_user_receipt(&self.room_data_provider, ReceiptType::Read).await;
state
.populate_initial_user_receipt(&self.room_data_provider, ReceiptType::ReadPrivate)
@@ -1011,7 +1008,7 @@ impl<P: RoomDataProvider> TimelineController<P> {
.await;
}
if track_read_markers {
if track_read_markers.is_enabled() {
if let Some(fully_read_event_id) =
self.room_data_provider.load_fully_read_marker().await
{
@@ -33,7 +33,8 @@ use super::{
event_item::RemoteEventOrigin,
traits::RoomDataProvider,
},
ObservableItems, ObservableItemsTransaction, TimelineMetadata, TimelineSettings,
ObservableItems, ObservableItemsTransaction, TimelineMetadata, TimelineReadReceiptTracking,
TimelineSettings,
metadata::EventMeta,
};
use crate::timeline::{
@@ -468,8 +469,12 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
event: &AnySyncTimelineEvent,
) -> bool {
match event {
AnySyncTimelineEvent::State(_) => settings.state_events_can_show_read_receipts,
AnySyncTimelineEvent::MessageLike(_) => true,
AnySyncTimelineEvent::State(_) => {
settings.track_read_receipts == TimelineReadReceiptTracking::AllEvents
}
AnySyncTimelineEvent::MessageLike(_) => {
settings.track_read_receipts != TimelineReadReceiptTracking::Disabled
}
}
}
@@ -735,7 +740,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
sender,
sender_profile,
timestamp,
read_receipts: if settings.track_read_receipts
read_receipts: if settings.track_read_receipts.is_enabled()
&& should_add
&& can_show_read_receipts
{
@@ -920,7 +925,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
event.visible = event_meta.visible;
event.can_show_read_receipts = event_meta.can_show_read_receipts;
if settings.track_read_receipts {
if settings.track_read_receipts.is_enabled() {
// Since the event's visibility changed, we need to update the read
// receipts of the previous visible event.
self.maybe_update_read_receipts_of_prev_event(&event_meta.event_id);
@@ -929,7 +934,7 @@ impl<'a, P: RoomDataProvider> TimelineStateTransaction<'a, P> {
}
}
if settings.track_read_receipts
if settings.track_read_receipts.is_enabled()
&& matches!(
position,
TimelineItemPosition::Start { .. }
+23
View File
@@ -1103,3 +1103,26 @@ impl TryFrom<GalleryItemInfo> for matrix_sdk::attachment::GalleryItemInfo {
})
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
/// The level of read receipt tracking for the timeline.
pub enum TimelineReadReceiptTracking {
/// Track read receipts for all events.
AllEvents,
/// Track read receipts only for message-like events.
MessageLikeEvents,
/// Disable read receipt tracking.
Disabled,
}
impl TimelineReadReceiptTracking {
/// Whether or not read receipt tracking is enabled.
pub fn is_enabled(&self) -> bool {
match self {
TimelineReadReceiptTracking::AllEvents => true,
TimelineReadReceiptTracking::MessageLikeEvents => true,
TimelineReadReceiptTracking::Disabled => false,
}
}
}
@@ -42,7 +42,7 @@ use stream_assert::{assert_next_matches, assert_pending};
use super::TestTimeline;
use crate::timeline::{
MembershipChange, MsgLikeContent, MsgLikeKind, RoomExt, TimelineDetails, TimelineFocus,
TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
TimelineItemContent, TimelineItemKind, TimelineReadReceiptTracking, VirtualTimelineItem,
controller::TimelineSettings,
event_item::{AnyOtherFullStateEventContent, RemoteEventOrigin},
tests::{ReadReceiptMap, TestRoomDataProvider, TestTimelineBuilder},
@@ -99,8 +99,7 @@ async fn test_replace_with_initial_events_and_read_marker() {
.with_initial_user_receipts(receipts),
)
.settings(TimelineSettings {
track_read_receipts: true,
state_events_can_show_read_receipts: true,
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
@@ -527,7 +526,7 @@ async fn test_latest_event_id_in_main_timeline() {
let timeline = room
.timeline_builder()
.with_focus(TimelineFocus::Live { hide_threaded_events: true })
.track_read_marker_and_receipts()
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
.build()
.await
.expect("Could not build live timeline");
@@ -28,6 +28,7 @@ use stream_assert::{assert_next_matches, assert_pending};
use super::TestTimeline;
use crate::timeline::{
TimelineReadReceiptTracking,
controller::TimelineSettings,
event_item::{EventSendState, RemoteEventOrigin},
tests::{TestRoomDataProvider, TestTimelineBuilder},
@@ -240,8 +241,7 @@ async fn test_no_read_marker_with_local_echo() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_fully_read_marker(event_id.to_owned()))
.settings(TimelineSettings {
track_read_receipts: true,
state_events_can_show_read_receipts: true,
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
@@ -97,11 +97,7 @@ async fn test_default_filter() {
#[async_test]
async fn test_filter_always_false() {
let timeline = TestTimelineBuilder::new()
.settings(TimelineSettings {
event_filter: Arc::new(|_, _| false),
state_events_can_show_read_receipts: true,
..Default::default()
})
.settings(TimelineSettings { event_filter: Arc::new(|_, _| false), ..Default::default() })
.build();
let f = &timeline.factory;
@@ -35,7 +35,7 @@ use stream_assert::{assert_next_matches, assert_pending};
use super::{ReadReceiptMap, TestRoomDataProvider};
use crate::timeline::{
MsgLikeContent, MsgLikeKind, RoomExt, TimelineFocus,
MsgLikeContent, MsgLikeKind, RoomExt, TimelineFocus, TimelineReadReceiptTracking,
controller::TimelineSettings,
tests::{TestTimelineBuilder, encryption::get_client},
};
@@ -52,7 +52,10 @@ fn filter_notice(ev: &AnySyncTimelineEvent, _rules: &RoomVersionRules) -> bool {
#[async_test]
async fn test_read_receipts_updates_on_live_events() {
let timeline = TestTimelineBuilder::new()
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let mut stream = timeline.subscribe().await;
@@ -114,7 +117,10 @@ async fn test_read_receipts_updates_on_live_events() {
#[async_test]
async fn test_read_receipts_updates_on_back_paginated_events() {
let timeline = TestTimelineBuilder::new()
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let room_id = room_id!("!room:localhost");
@@ -153,7 +159,7 @@ async fn test_read_receipts_updates_on_back_paginated_events() {
async fn test_read_receipts_updates_on_filtered_events() {
let timeline = TestTimelineBuilder::new()
.settings(TimelineSettings {
track_read_receipts: true,
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
event_filter: Arc::new(filter_notice),
..Default::default()
})
@@ -254,7 +260,7 @@ async fn test_read_receipts_updates_on_filtered_events_with_stored() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings {
track_read_receipts: true,
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
event_filter: Arc::new(filter_notice),
..Default::default()
})
@@ -324,7 +330,7 @@ async fn test_read_receipts_updates_on_back_paginated_filtered_events() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings {
track_read_receipts: true,
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
event_filter: Arc::new(filter_notice),
..Default::default()
})
@@ -420,7 +426,7 @@ async fn test_read_receipts_updates_on_message_decryption() {
let timeline = room
.timeline_builder()
.event_filter(filter_text_msg)
.track_read_marker_and_receipts()
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
.build()
.await
.unwrap();
@@ -537,7 +543,10 @@ async fn test_initial_public_unthreaded_receipt() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let (receipt_event_id, _) = timeline.controller.latest_user_read_receipt(*ALICE).await.unwrap();
@@ -562,7 +571,10 @@ async fn test_initial_public_main_thread_receipt() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let (receipt_event_id, _) = timeline.controller.latest_user_read_receipt(*ALICE).await.unwrap();
@@ -587,7 +599,10 @@ async fn test_initial_private_unthreaded_receipt() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let (receipt_event_id, _) = timeline.controller.latest_user_read_receipt(*ALICE).await.unwrap();
@@ -612,7 +627,10 @@ async fn test_initial_private_main_thread_receipt() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let (receipt_event_id, _) = timeline.controller.latest_user_read_receipt(*ALICE).await.unwrap();
@@ -626,7 +644,10 @@ async fn test_clear_read_receipts() {
let event_b_id = event_id!("$event_b");
let timeline = TestTimelineBuilder::new()
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
let f = &timeline.factory;
@@ -706,7 +727,10 @@ async fn test_implicit_read_receipt_before_explicit_read_receipt() {
let timeline = TestTimelineBuilder::new()
.provider(TestRoomDataProvider::default().with_initial_user_receipts(initial_user_receipts))
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
// Check that the receipts are at the correct place.
@@ -768,7 +792,10 @@ async fn test_threaded_latest_user_read_receipt() {
let timeline = TestTimelineBuilder::new()
.focus(TimelineFocus::Thread { root_event_id: thread_root })
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.build();
// Sanity check: no read receipts before any events.
@@ -839,7 +866,10 @@ async fn test_threaded_latest_user_read_receipt() {
#[async_test]
async fn test_unthreaded_client_updates_threaded_read_receipts() {
let timeline = TestTimelineBuilder::new()
.settings(TimelineSettings { track_read_receipts: true, ..Default::default() })
.settings(TimelineSettings {
track_read_receipts: TimelineReadReceiptTracking::AllEvents,
..Default::default()
})
.focus(TimelineFocus::Live { hide_threaded_events: true })
.build();
let mut stream = timeline.subscribe().await;
+4 -2
View File
@@ -37,7 +37,8 @@ use tracing::error;
use super::{EventTimelineItem, Profile, RedactError, TimelineBuilder};
use crate::timeline::{
self, Timeline, latest_event::LatestEventValue, pinned_events_loader::PinnedEventsRoom,
self, Timeline, TimelineReadReceiptTracking, latest_event::LatestEventValue,
pinned_events_loader::PinnedEventsRoom,
};
pub trait RoomExt {
@@ -77,7 +78,8 @@ impl RoomExt for Room {
}
fn timeline_builder(&self) -> TimelineBuilder {
TimelineBuilder::new(self).track_read_marker_and_receipts()
TimelineBuilder::new(self)
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
}
async fn latest_event_item(&self) -> Option<EventTimelineItem> {
@@ -24,7 +24,9 @@ use matrix_sdk::{
test_utils::logged_in_client_with_server,
};
use matrix_sdk_test::{async_test, mocks::mock_encryption_state};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineItem, TimelineItemKind};
use matrix_sdk_ui::timeline::{
TimelineBuilder, TimelineItem, TimelineItemKind, TimelineReadReceiptTracking,
};
use ruma::{RoomId, room_id, user_id};
use serde_json::json;
use wiremock::{Match, Mock, MockServer, Request, ResponseTemplate, http::Method};
@@ -404,7 +406,10 @@ async fn timeline_test_helper(
anyhow::anyhow!("Room {room_id} not found in client. Can't provide a timeline for it")
})?;
let timeline = TimelineBuilder::new(&sdk_room).track_read_marker_and_receipts().build().await?;
let timeline = TimelineBuilder::new(&sdk_room)
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
.build()
.await?;
Ok(timeline.subscribe().await)
}
+2 -2
View File
@@ -16,7 +16,7 @@ use matrix_sdk::{
};
use matrix_sdk_ui::{
Timeline,
timeline::{TimelineBuilder, TimelineFocus, TimelineItem},
timeline::{TimelineBuilder, TimelineFocus, TimelineItem, TimelineReadReceiptTracking},
};
use ratatui::{prelude::*, widgets::*};
use tokio::{spawn, sync::OnceCell, task::JoinHandle};
@@ -152,7 +152,7 @@ impl RoomView {
let task = spawn(async move {
let timeline = TimelineBuilder::new(&r)
.with_focus(TimelineFocus::Thread { root_event_id: cloned_root })
.track_read_marker_and_receipts()
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
.build()
.await
.unwrap();