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