matrix_sdk_ui/timeline/controller/
state.rs1use std::{future::Future, sync::Arc};
16
17use eyeball_im::VectorDiff;
18use matrix_sdk::{deserialized_responses::TimelineEvent, send_queue::SendHandle};
19#[cfg(test)]
20use ruma::events::receipt::ReceiptEventContent;
21use ruma::{
22 events::{AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent},
23 serde::Raw,
24 MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId,
25};
26use tracing::{instrument, trace, warn};
27
28use super::{
29 super::{
30 date_dividers::DateDividerAdjuster,
31 event_handler::{
32 Flow, TimelineAction, TimelineEventContext, TimelineEventHandler, TimelineItemPosition,
33 },
34 event_item::RemoteEventOrigin,
35 traits::RoomDataProvider,
36 Profile, TimelineItem,
37 },
38 observable_items::ObservableItems,
39 DateDividerMode, TimelineFocusKind, TimelineMetadata, TimelineSettings,
40 TimelineStateTransaction,
41};
42use crate::unable_to_decrypt_hook::UtdHookManager;
43
44#[derive(Debug)]
45pub(in crate::timeline) struct TimelineState {
46 pub items: ObservableItems,
47 pub meta: TimelineMetadata,
48
49 pub timeline_focus: TimelineFocusKind,
51}
52
53impl TimelineState {
54 pub(super) fn new(
55 timeline_focus: TimelineFocusKind,
56 own_user_id: OwnedUserId,
57 room_version: RoomVersionId,
58 internal_id_prefix: Option<String>,
59 unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
60 is_room_encrypted: bool,
61 ) -> Self {
62 Self {
63 items: ObservableItems::new(),
64 meta: TimelineMetadata::new(
65 own_user_id,
66 room_version,
67 internal_id_prefix,
68 unable_to_decrypt_hook,
69 is_room_encrypted,
70 ),
71 timeline_focus,
72 }
73 }
74
75 pub(super) async fn handle_remote_events_with_diffs<RoomData>(
77 &mut self,
78 diffs: Vec<VectorDiff<TimelineEvent>>,
79 origin: RemoteEventOrigin,
80 room_data: &RoomData,
81 settings: &TimelineSettings,
82 ) where
83 RoomData: RoomDataProvider,
84 {
85 if diffs.is_empty() {
86 return;
87 }
88
89 let mut transaction = self.transaction();
90 transaction.handle_remote_events_with_diffs(diffs, origin, room_data, settings).await;
91 transaction.commit();
92 }
93
94 pub(super) async fn handle_remote_aggregations<RoomData>(
96 &mut self,
97 diffs: Vec<VectorDiff<TimelineEvent>>,
98 origin: RemoteEventOrigin,
99 room_data: &RoomData,
100 settings: &TimelineSettings,
101 ) where
102 RoomData: RoomDataProvider,
103 {
104 if diffs.is_empty() {
105 return;
106 }
107
108 let mut transaction = self.transaction();
109 transaction.handle_remote_aggregations(diffs, origin, room_data, settings).await;
110 transaction.commit();
111 }
112
113 pub(super) fn handle_fully_read_marker(&mut self, fully_read_event_id: OwnedEventId) {
116 let mut txn = self.transaction();
117 txn.set_fully_read_event(fully_read_event_id);
118 txn.commit();
119 }
120
121 #[instrument(skip_all)]
122 pub(super) async fn handle_ephemeral_events<P: RoomDataProvider>(
123 &mut self,
124 events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
125 room_data_provider: &P,
126 ) {
127 if events.is_empty() {
128 return;
129 }
130
131 let mut txn = self.transaction();
132
133 trace!("Handling ephemeral room events");
134 let own_user_id = room_data_provider.own_user_id();
135 for raw_event in events {
136 match raw_event.deserialize() {
137 Ok(AnySyncEphemeralRoomEvent::Receipt(ev)) => {
138 txn.handle_explicit_read_receipts(ev.content, own_user_id);
139 }
140 Ok(_) => {}
141 Err(e) => {
142 let event_type = raw_event.get_field::<String>("type").ok().flatten();
143 warn!(event_type, "Failed to deserialize ephemeral event: {e}");
144 }
145 }
146 }
147
148 txn.commit();
149 }
150
151 #[allow(clippy::too_many_arguments)]
153 #[instrument(skip_all)]
154 pub(super) async fn handle_local_event(
155 &mut self,
156 own_user_id: OwnedUserId,
157 own_profile: Option<Profile>,
158 date_divider_mode: DateDividerMode,
159 txn_id: OwnedTransactionId,
160 send_handle: Option<SendHandle>,
161 content: AnyMessageLikeEventContent,
162 ) {
163 let mut txn = self.transaction();
164
165 let mut date_divider_adjuster = DateDividerAdjuster::new(date_divider_mode);
166
167 let (in_reply_to, thread_root) =
168 txn.meta.process_content_relations(&content, None, &txn.items, &txn.timeline_focus);
169
170 let should_add_new_items = match &txn.timeline_focus {
172 TimelineFocusKind::Live { hide_threaded_events } => {
173 thread_root.is_none() || !hide_threaded_events
174 }
175 TimelineFocusKind::Thread { root_event_id } => {
176 thread_root.as_ref().is_some_and(|r| r == root_event_id)
177 }
178 TimelineFocusKind::Event { .. } | TimelineFocusKind::PinnedEvents => {
179 false
182 }
183 };
184
185 let ctx = TimelineEventContext {
186 sender: own_user_id,
187 sender_profile: own_profile,
188 timestamp: MilliSecondsSinceUnixEpoch::now(),
189 read_receipts: Default::default(),
190 is_highlighted: false,
192 flow: Flow::Local { txn_id, send_handle },
193 should_add_new_items,
194 };
195
196 if let Some(timeline_action) =
197 TimelineAction::from_content(content, in_reply_to, thread_root, None)
198 {
199 TimelineEventHandler::new(&mut txn, ctx)
200 .handle_event(&mut date_divider_adjuster, timeline_action)
201 .await;
202 txn.adjust_date_dividers(date_divider_adjuster);
203 }
204
205 txn.commit();
206 }
207
208 pub(super) async fn retry_event_decryption<P: RoomDataProvider, Fut>(
209 &mut self,
210 retry_one: impl Fn(Arc<TimelineItem>) -> Fut,
211 retry_indices: Vec<usize>,
212 room_data_provider: &P,
213 settings: &TimelineSettings,
214 ) where
215 Fut: Future<Output = Option<TimelineEvent>>,
216 {
217 let mut txn = self.transaction();
218
219 let mut date_divider_adjuster =
220 DateDividerAdjuster::new(settings.date_divider_mode.clone());
221
222 let mut offset = 0;
226 for idx in retry_indices {
227 let idx = idx - offset;
228 let Some(event) = retry_one(txn.items[idx].clone()).await else {
229 continue;
230 };
231
232 let removed_item = txn
233 .handle_remote_event(
234 event,
235 TimelineItemPosition::UpdateAt { timeline_item_index: idx },
236 room_data_provider,
237 settings,
238 &mut date_divider_adjuster,
239 )
240 .await;
241
242 if removed_item {
245 offset += 1;
246 }
247 }
248
249 txn.adjust_date_dividers(date_divider_adjuster);
250
251 txn.commit();
252 }
253
254 #[cfg(test)]
255 pub(super) fn handle_read_receipts(
256 &mut self,
257 receipt_event_content: ReceiptEventContent,
258 own_user_id: &ruma::UserId,
259 ) {
260 let mut txn = self.transaction();
261 txn.handle_explicit_read_receipts(receipt_event_content, own_user_id);
262 txn.commit();
263 }
264
265 pub(super) fn clear(&mut self) {
266 let mut txn = self.transaction();
267 txn.clear();
268 txn.commit();
269 }
270
271 pub(super) async fn replace_with_remote_events<Events, RoomData>(
277 &mut self,
278 events: Events,
279 origin: RemoteEventOrigin,
280 room_data_provider: &RoomData,
281 settings: &TimelineSettings,
282 ) where
283 Events: IntoIterator,
284 Events::Item: Into<TimelineEvent>,
285 RoomData: RoomDataProvider,
286 {
287 let mut txn = self.transaction();
288 txn.clear();
289 txn.handle_remote_events_with_diffs(
290 vec![VectorDiff::Append { values: events.into_iter().map(Into::into).collect() }],
291 origin,
292 room_data_provider,
293 settings,
294 )
295 .await;
296 txn.commit();
297 }
298
299 pub(super) fn mark_all_events_as_encrypted(&mut self) {
300 let mut txn = self.transaction();
303 txn.mark_all_events_as_encrypted();
304 txn.commit();
305 }
306
307 pub(super) fn transaction(&mut self) -> TimelineStateTransaction<'_> {
308 TimelineStateTransaction::new(&mut self.items, &mut self.meta, self.timeline_focus.clone())
309 }
310}