tauri_plugin_matrix_svelte/matrix/timeline.rs
1use std::{
2 cmp::{max, min},
3 collections::BTreeMap,
4 ops::Range,
5 sync::{Arc, Mutex},
6};
7
8use futures::StreamExt;
9use matrix_sdk::{
10 room::RoomMember,
11 ruma::{
12 events::{receipt::Receipt, FullStateEventContent},
13 OwnedEventId, OwnedRoomId,
14 },
15 Room,
16};
17use matrix_sdk_ui::{
18 eyeball_im::{Vector, VectorDiff},
19 timeline::{
20 self, AnyOtherFullStateEventContent, EventTimelineItem, MembershipChange,
21 TimelineEventItemId, TimelineItem, TimelineItemContent,
22 },
23 Timeline,
24};
25use rangemap::RangeSet;
26use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
27use tokio::sync::watch;
28
29use crate::matrix::singletons::{broadcast_event, UIUpdateMessage, LOG_TIMELINE_DIFFS};
30
31use super::{
32 events::get_latest_event_details,
33 requests::{submit_async_request, MatrixRequest},
34 room::{
35 frontend_events::events_dto::{to_frontend_timeline_item, FrontendTimelineItem},
36 room_screen::SavedState,
37 rooms_list::{enqueue_rooms_list_update, RoomsListUpdate},
38 },
39 rooms::UnreadMessageCount,
40 singletons::ALL_JOINED_ROOMS,
41 user_power_level::UserPowerLevels,
42 utils::current_user_id,
43};
44
45/// Which direction to paginate in.
46///
47/// * `Forwards` will retrieve later events (towards the end of the timeline),
48/// which only works if the timeline is *focused* on a specific event.
49/// * `Backwards`: the more typical choice, in which earlier events are retrieved
50/// (towards the start of the timeline), which works in both live mode and focused mode.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
52#[serde(rename_all = "camelCase", rename_all_fields = "camelCase")]
53pub enum PaginationDirection {
54 Forwards,
55 Backwards,
56}
57impl std::fmt::Display for PaginationDirection {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::Forwards => write!(f, "forwards"),
61 Self::Backwards => write!(f, "backwards"),
62 }
63 }
64}
65
66/// A tokio::watch channel sender for sending requests from the RoomScreen UI widget
67/// to the corresponding background async task for that room (its `timeline_subscriber_handler`).
68pub type TimelineRequestSender = watch::Sender<Vec<BackwardsPaginateUntilEventRequest>>;
69
70/// A request to search backwards for a specific event in a room's timeline.
71#[derive(Debug)]
72pub struct BackwardsPaginateUntilEventRequest {
73 pub room_id: OwnedRoomId,
74 pub target_event_id: OwnedEventId,
75 /// The index in the timeline where a backwards search should begin.
76 pub starting_index: usize,
77 /// The number of items in the timeline at the time of the request,
78 /// which is used to detect if the timeline has changed since the request was made,
79 /// meaning that the `starting_index` can no longer be relied upon.
80 pub current_tl_len: usize,
81}
82
83/// A message that is sent from a background async task to a room's timeline view
84/// for the purpose of update the Timeline UI contents or metadata.
85pub enum TimelineUpdate {
86 /// The very first update a given room's timeline receives.
87 FirstUpdate {
88 /// The initial list of timeline items (events) for a room.
89 initial_items: Vector<Arc<TimelineItem>>,
90 },
91 /// The content of a room's timeline was updated in the background.
92 NewItems {
93 /// The entire list of timeline items (events) for a room.
94 new_items: Vector<Arc<TimelineItem>>,
95 /// The range of indices in the `items` list that have been changed in this update
96 /// and thus must be removed from any caches of drawn items in the timeline.
97 /// Any items outside of this range are assumed to be unchanged and need not be redrawn.
98 changed_indices: Range<usize>,
99 /// An optimization that informs the UI whether the changes to the timeline
100 /// resulted in new items being *appended to the end* of the timeline.
101 is_append: bool,
102 /// Whether to clear the entire cache of drawn items in the timeline.
103 /// This supersedes `index_of_first_change` and is used when the entire timeline is being redrawn.
104 clear_cache: bool,
105 },
106 /// The updated number of unread messages in the room.
107 NewUnreadMessagesCount(UnreadMessageCount),
108 /// The target event ID was found at the given `index` in the timeline items vector.
109 ///
110 /// This means that the RoomScreen widget can scroll the timeline up to this event,
111 /// and the background `timeline_subscriber_handler` async task can stop looking for this event.
112 TargetEventFound {
113 target_event_id: OwnedEventId,
114 index: usize,
115 },
116 /// A notice that the background task doing pagination for this room is currently running
117 /// a pagination request in the given direction, and is waiting for that request to complete.
118 PaginationRunning(PaginationDirection),
119 /// An error occurred while paginating the timeline for this room.
120 PaginationError {
121 error: timeline::Error,
122 direction: PaginationDirection,
123 },
124 /// A notice that the background task doing pagination for this room has become idle,
125 /// meaning that it has completed its recent pagination request(s).
126 PaginationIdle {
127 /// If `true`, the start of the timeline has been reached, meaning that
128 /// there is no need to send further pagination requests.
129 fully_paginated: bool,
130 direction: PaginationDirection,
131 },
132 /// A notice that event details have been fetched from the server,
133 /// including a `result` that indicates whether the request was successful.
134 EventDetailsFetched {
135 event_id: OwnedEventId,
136 result: Result<(), matrix_sdk_ui::timeline::Error>,
137 },
138 /// The result of a request to edit a message in this timeline.
139 MessageEdited {
140 timeline_event_id: TimelineEventItemId,
141 result: Result<(), matrix_sdk_ui::timeline::Error>,
142 },
143 /// A notice that the room's members have been fetched from the server,
144 /// though the success or failure of the request is not yet known until the client
145 /// requests the member info via a timeline event's `sender_profile()` method.
146 RoomMembersSynced,
147 /// A notice that the room's full member list has been fetched from the server,
148 /// includes a complete list of room members that can be shared across components.
149 /// This is different from RoomMembersSynced which only indicates members were fetched
150 /// but doesn't provide the actual data.
151 RoomMembersListFetched { members: Vec<RoomMember> },
152 /// A notice that one or more requested media items (images, videos, etc.)
153 /// that should be displayed in this timeline have now been fetched and are available.
154 MediaFetched,
155 /// A notice that one or more members of a this room are currently typing.
156 TypingUsers {
157 /// The list of users (their displayable name) who are currently typing in this room.
158 users: Vec<String>,
159 },
160 /// An update containing the currently logged-in user's power levels for this room.
161 UserPowerLevels(UserPowerLevels),
162 /// An update to the currently logged-in user's own read receipt for this room.
163 OwnUserReadReceipt(Receipt),
164}
165
166/// The global set of all timeline states, one entry per room.
167pub static TIMELINE_STATES: Mutex<BTreeMap<OwnedRoomId, TimelineUiState>> =
168 Mutex::new(BTreeMap::new());
169
170/// The UI-side state of a single room's timeline, which is only accessed/updated by the UI thread.
171///
172/// This struct should only include states that need to be persisted for a given room
173/// across multiple `Hide`/`Show` cycles of that room's timeline within a RoomScreen.
174/// If a state is more temporary and shouldn't be persisted when the timeline is hidden,
175/// then it should be stored in the RoomScreen widget itself, not in this struct.
176#[derive(Debug)]
177pub struct TimelineUiState {
178 /// The ID of the room that this timeline is for.
179 pub(crate) room_id: OwnedRoomId,
180
181 /// The power levels of the currently logged-in user in this room.
182 pub(crate) user_power: UserPowerLevels,
183
184 /// Whether this room's timeline has been fully paginated, which means
185 /// that the oldest (first) event in the timeline is locally synced and available.
186 /// When `true`, further backwards pagination requests will not be sent.
187 ///
188 /// This must be reset to `false` whenever the timeline is fully cleared.
189 pub(crate) fully_paginated: bool,
190
191 /// The list of items (events) in this room's timeline that our client currently knows about.
192 pub(crate) items: Vector<Arc<TimelineItem>>,
193
194 /// The range of items (indices in the above `items` list) whose event **contents** have been drawn
195 /// since the last update and thus do not need to be re-populated on future draw events.
196 ///
197 /// This range is partially cleared on each background update (see below) to ensure that
198 /// items modified during the update are properly redrawn. Thus, it is a conservative
199 /// "cache tracker" that may not include all items that have already been drawn,
200 /// but that's okay because big updates that clear out large parts of the rangeset
201 /// only occur during back pagination, which is both rare and slow in and of itself.
202 /// During typical usage, new events are appended to the end of the timeline,
203 /// meaning that the range of already-drawn items doesn't need to be cleared.
204 ///
205 /// Upon a background update, only item indices greater than or equal to the
206 /// `index_of_first_change` are removed from this set.
207 /// Not included in frontend serialization
208 pub(crate) content_drawn_since_last_update: RangeSet<usize>,
209
210 /// Same as `content_drawn_since_last_update`, but for the event **profiles** (avatar, username).
211 /// Not included in frontend serialization
212 pub(crate) profile_drawn_since_last_update: RangeSet<usize>,
213
214 /// The channel receiver for timeline updates for this room.
215 ///
216 /// Here we use a synchronous (non-async) channel because the receiver runs
217 /// in a sync context and the sender runs in an async context,
218 /// which is okay because a sender on an unbounded channel never needs to block.
219 /// Not included in frontend serialization
220 pub(crate) update_receiver: crossbeam_channel::Receiver<TimelineUpdate>,
221
222 /// The sender for timeline requests from a RoomScreen showing this room
223 /// to the background async task that handles this room's timeline updates.
224 /// Not included in frontend serialization
225 pub(crate) request_sender: TimelineRequestSender,
226
227 /// The cache of media items (images, videos, etc.) that appear in this timeline.
228 ///
229 /// Currently this excludes avatars, as those are shared across multiple rooms.
230 // media_cache: MediaCache,
231
232 /// Info about the event currently being replied to, if any.
233 // TODO: replace repliedtoinfo struct with the latest one from the SDK (this one is broken)
234 // replying_to: Option<(EventTimelineItem, RepliedToInfo)>,
235
236 /// The states relevant to the UI display of this timeline that are saved upon
237 /// a `Hide` action and restored upon a `Show` action.
238 /// Not included in frontend serialization
239 pub(crate) saved_state: SavedState,
240
241 /// The state of the message highlight animation.
242 ///
243 /// We need to run the animation once the scrolling, triggered by the click of of a
244 /// a reply preview, ends. so we keep a small state for it.
245 /// By default, it starts in Off.
246 /// Once the scrolling is started, the state becomes Pending.
247 /// If the animation was triggered, the state goes back to Off.
248 // TODO: remove this if I'm sure I don't need it
249 // message_highlight_animation_state: MessageHighlightAnimationState,
250
251 /// The index of the timeline item that was most recently scrolled up past it.
252 /// This is used to detect when the user has scrolled up past the second visible item (index 1)
253 /// upwards to the first visible item (index 0), which is the top of the timeline,
254 /// at which point we submit a backwards pagination request to fetch more events.
255 pub(crate) last_scrolled_index: usize,
256
257 /// The index of the first item shown in the timeline's PortalList from *before* the last "jump".
258 ///
259 /// This index is saved before the timeline undergoes any jumps, e.g.,
260 /// receiving new items, major scroll changes, or other timeline view jumps.
261 pub(crate) prev_first_index: Option<usize>,
262
263 /// Whether the user has scrolled past their latest read marker.
264 ///
265 /// This is used to determine whether we should send a fully-read receipt
266 /// after the user scrolls past their "read marker", i.e., their latest fully-read receipt.
267 /// Its value is determined by comparing the fully-read event's timestamp with the
268 /// first and last timestamp of displayed events in the timeline.
269 /// When scrolling down, if the value is true, we send a fully-read receipt
270 /// for the last visible event in the timeline.
271 ///
272 /// When new message come in, this value is reset to `false`.
273 pub(crate) scrolled_past_read_marker: bool,
274 pub(crate) latest_own_user_receipt: Option<Receipt>,
275}
276
277impl Serialize for TimelineUiState {
278 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
279 where
280 S: Serializer,
281 {
282 let mut state = serializer.serialize_struct("TimelineUiState", 8)?;
283
284 state.serialize_field("roomId", &self.room_id)?;
285 state.serialize_field("userPower", &self.user_power)?;
286 state.serialize_field("fullyPaginated", &self.fully_paginated)?;
287 state.serialize_field(
288 "items",
289 &serialize_timeline_items(&self.items, &self.room_id),
290 )?;
291 state.serialize_field("lastScrolledIndex", &self.last_scrolled_index)?;
292 state.serialize_field("prevFirstIndex", &self.prev_first_index)?;
293 state.serialize_field("scrolledPastReadMarker", &self.scrolled_past_read_marker)?;
294 state.serialize_field("latestOwnUserReceipt", &self.latest_own_user_receipt)?;
295
296 state.end()
297 }
298}
299fn serialize_timeline_items(
300 items: &Vector<Arc<TimelineItem>>,
301 room_id: &OwnedRoomId,
302) -> Vec<FrontendTimelineItem> {
303 items
304 .iter()
305 .map(|item| to_frontend_timeline_item(item, Some(room_id)))
306 .collect()
307}
308
309/// Returns three channel endpoints related to the timeline for the given joined room.
310///
311/// 1. A timeline update sender.
312/// 2. The timeline update receiver, which is a singleton, and can only be taken once.
313/// 3. A `tokio::watch` sender that can be used to send requests to the timeline subscriber handler.
314///
315/// This will only succeed once per room, as only a single channel receiver can exist.
316pub fn take_timeline_endpoints(
317 room_id: &OwnedRoomId,
318) -> Option<(
319 crossbeam_channel::Sender<TimelineUpdate>,
320 crossbeam_channel::Receiver<TimelineUpdate>,
321 TimelineRequestSender,
322)> {
323 ALL_JOINED_ROOMS
324 .lock()
325 .unwrap()
326 .get_mut(room_id)
327 .and_then(|ri| {
328 ri.timeline_singleton_endpoints
329 .take()
330 .map(|(receiver, request_sender)| {
331 (ri.timeline_update_sender.clone(), receiver, request_sender)
332 })
333 })
334}
335
336/// A per-room async task that listens for timeline updates and sends them to the UI thread.
337///
338/// One instance of this async task is spawned for each room the client knows about.
339pub async fn timeline_subscriber_handler(
340 room: Room,
341 timeline: Arc<Timeline>,
342 timeline_update_sender: crossbeam_channel::Sender<TimelineUpdate>,
343 mut request_receiver: watch::Receiver<Vec<BackwardsPaginateUntilEventRequest>>,
344) {
345 /// An inner function that searches the given new timeline items for a target event.
346 ///
347 /// If the target event is found, it is removed from the `target_event_id_opt` and returned,
348 /// along with the index/position of that event in the given iterator of new items.
349 fn find_target_event<'a>(
350 target_event_id_opt: &mut Option<OwnedEventId>,
351 mut new_items_iter: impl Iterator<Item = &'a Arc<TimelineItem>>,
352 ) -> Option<(usize, OwnedEventId)> {
353 let found_index = target_event_id_opt.as_ref().and_then(|target_event_id| {
354 new_items_iter.position(|new_item| {
355 new_item
356 .as_event()
357 .is_some_and(|new_ev| new_ev.event_id() == Some(target_event_id))
358 })
359 });
360
361 if let Some(index) = found_index {
362 target_event_id_opt.take().map(|ev| (index, ev))
363 } else {
364 None
365 }
366 }
367
368 let room_id = room.room_id().to_owned();
369 println!("Starting timeline subscriber for room {room_id}...");
370 let (mut timeline_items, mut subscriber) = timeline.subscribe().await;
371 println!(
372 "Received initial timeline update of {} items for room {room_id}.",
373 timeline_items.len()
374 );
375
376 timeline_update_sender.send(TimelineUpdate::FirstUpdate {
377 initial_items: timeline_items.clone(),
378 }).unwrap_or_else(
379 |_e| panic!("Error: timeline update sender couldn't send first update ({} items) to room {room_id}!", timeline_items.len())
380 );
381
382 let mut latest_event = timeline.latest_event().await;
383
384 // the event ID to search for while loading previous items into the timeline.
385 let mut target_event_id = None;
386 // the timeline index and event ID of the target event, if it has been found.
387 let mut found_target_event_id: Option<(usize, OwnedEventId)> = None;
388
389 loop {
390 tokio::select! {
391 // we must check for new requests before handling new timeline updates.
392 biased;
393
394 // Handle updates to the current backwards pagination requests.
395 Ok(()) = request_receiver.changed() => {
396 let prev_target_event_id = target_event_id.clone();
397 let new_request_details = request_receiver
398 .borrow_and_update()
399 .iter()
400 .find_map(|req| req.room_id
401 .eq(&room_id)
402 .then(|| (req.target_event_id.clone(), req.starting_index, req.current_tl_len))
403 );
404
405 target_event_id = new_request_details.as_ref().map(|(ev, ..)| ev.clone());
406
407 // If we received a new request, start searching backwards for the target event.
408 if let Some((new_target_event_id, starting_index, current_tl_len)) = new_request_details {
409 if prev_target_event_id.as_ref() != Some(&new_target_event_id) {
410 let starting_index = if current_tl_len == timeline_items.len() {
411 starting_index
412 } else {
413 // The timeline has changed since the request was made, so we can't rely on the `starting_index`.
414 // Instead, we have no choice but to start from the end of the timeline.
415 timeline_items.len()
416 };
417 // println!("Received new request to search for event {new_target_event_id} in room {room_id} starting from index {starting_index} (tl len {}).", timeline_items.len());
418 // Search backwards for the target event in the timeline, starting from the given index.
419 if let Some(target_event_tl_index) = timeline_items
420 .focus()
421 .narrow(..starting_index)
422 .into_iter()
423 .rev()
424 .position(|i| i.as_event()
425 .and_then(|e| e.event_id())
426 .is_some_and(|ev_id| ev_id == new_target_event_id)
427 )
428 .map(|i| starting_index.saturating_sub(i).saturating_sub(1))
429 {
430 // println!("Found existing target event {new_target_event_id} in room {room_id} at index {target_event_tl_index}.");
431
432 // Nice! We found the target event in the current timeline items,
433 // so there's no need to actually proceed with backwards pagination;
434 // thus, we can clear the locally-tracked target event ID.
435 target_event_id = None;
436 found_target_event_id = None;
437 timeline_update_sender.send(
438 TimelineUpdate::TargetEventFound {
439 target_event_id: new_target_event_id.clone(),
440 index: target_event_tl_index,
441 }
442 ).unwrap_or_else(
443 |_e| panic!("Error: timeline update sender couldn't send TargetEventFound({new_target_event_id}, {target_event_tl_index}) to room {room_id}!")
444 );
445 // Update this room's timeline UI view.
446 broadcast_event(UIUpdateMessage::RefreshUI).expect("Couldn't broadcast event to UI");
447 }
448 else {
449 // println!("Target event not in timeline. Starting backwards pagination in room {room_id} to find target event {new_target_event_id} starting from index {starting_index}.");
450
451 // If we didn't find the target event in the current timeline items,
452 // we need to start loading previous items into the timeline.
453 submit_async_request(MatrixRequest::PaginateRoomTimeline {
454 room_id: room_id.clone(),
455 num_events: 50,
456 direction: PaginationDirection::Backwards,
457 });
458 }
459 }
460 }
461 }
462
463 // Handle updates to the actual timeline content.
464 batch_opt = subscriber.next() => {
465 let Some(batch) = batch_opt else { break };
466 let mut num_updates = 0;
467 // For now we always requery the latest event, but this can be better optimized.
468 let mut reobtain_latest_event = true;
469 let mut index_of_first_change = usize::MAX;
470 let mut index_of_last_change = usize::MIN;
471 // whether to clear the entire cache of drawn items
472 let mut clear_cache = false;
473 // whether the changes include items being appended to the end of the timeline
474 let mut is_append = false;
475 for diff in batch {
476 num_updates += 1;
477 match diff {
478 VectorDiff::Append { values } => {
479 let _values_len = values.len();
480 index_of_first_change = min(index_of_first_change, timeline_items.len());
481 timeline_items.extend(values);
482 index_of_last_change = max(index_of_last_change, timeline_items.len());
483 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Append {_values_len}. Changes: {index_of_first_change}..{index_of_last_change}"); }
484 reobtain_latest_event = true;
485 is_append = true;
486 }
487 VectorDiff::Clear => {
488 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Clear"); }
489 clear_cache = true;
490 timeline_items.clear();
491 reobtain_latest_event = true;
492 }
493 VectorDiff::PushFront { value } => {
494 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff PushFront"); }
495 if let Some((index, _ev)) = found_target_event_id.as_mut() {
496 *index += 1; // account for this new `value` being prepended.
497 } else {
498 found_target_event_id = find_target_event(&mut target_event_id, std::iter::once(&value));
499 }
500
501 clear_cache = true;
502 timeline_items.push_front(value);
503 reobtain_latest_event |= latest_event.is_none();
504 }
505 VectorDiff::PushBack { value } => {
506 index_of_first_change = min(index_of_first_change, timeline_items.len());
507 timeline_items.push_back(value);
508 index_of_last_change = max(index_of_last_change, timeline_items.len());
509 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff PushBack. Changes: {index_of_first_change}..{index_of_last_change}"); }
510 reobtain_latest_event = true;
511 is_append = true;
512 }
513 VectorDiff::PopFront => {
514 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff PopFront"); }
515 clear_cache = true;
516 timeline_items.pop_front();
517 if let Some((i, _ev)) = found_target_event_id.as_mut() {
518 *i = i.saturating_sub(1); // account for the first item being removed.
519 }
520 // This doesn't affect whether we should reobtain the latest event.
521 }
522 VectorDiff::PopBack => {
523 timeline_items.pop_back();
524 index_of_first_change = min(index_of_first_change, timeline_items.len());
525 index_of_last_change = usize::MAX;
526 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff PopBack. Changes: {index_of_first_change}..{index_of_last_change}"); }
527 reobtain_latest_event = true;
528 }
529 VectorDiff::Insert { index, value } => {
530 if index == 0 {
531 clear_cache = true;
532 } else {
533 index_of_first_change = min(index_of_first_change, index);
534 index_of_last_change = usize::MAX;
535 }
536 if index >= timeline_items.len() {
537 is_append = true;
538 }
539
540 if let Some((i, _ev)) = found_target_event_id.as_mut() {
541 // account for this new `value` being inserted before the previously-found target event's index.
542 if index <= *i {
543 *i += 1;
544 }
545 } else {
546 found_target_event_id = find_target_event(&mut target_event_id, std::iter::once(&value))
547 .map(|(i, ev)| (i + index, ev));
548 }
549
550 timeline_items.insert(index, value);
551 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Insert at {index}. Changes: {index_of_first_change}..{index_of_last_change}"); }
552 reobtain_latest_event = true;
553 }
554 VectorDiff::Set { index, value } => {
555 index_of_first_change = min(index_of_first_change, index);
556 index_of_last_change = max(index_of_last_change, index.saturating_add(1));
557 timeline_items.set(index, value);
558 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Set at {index}. Changes: {index_of_first_change}..{index_of_last_change}"); }
559 reobtain_latest_event = true;
560 }
561 VectorDiff::Remove { index } => {
562 if index == 0 {
563 clear_cache = true;
564 } else {
565 index_of_first_change = min(index_of_first_change, index.saturating_sub(1));
566 index_of_last_change = usize::MAX;
567 }
568 if let Some((i, _ev)) = found_target_event_id.as_mut() {
569 // account for an item being removed before the previously-found target event's index.
570 if index <= *i {
571 *i = i.saturating_sub(1);
572 }
573 }
574 timeline_items.remove(index);
575 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Remove at {index}. Changes: {index_of_first_change}..{index_of_last_change}"); }
576 reobtain_latest_event = true;
577 }
578 VectorDiff::Truncate { length } => {
579 if length == 0 {
580 clear_cache = true;
581 } else {
582 index_of_first_change = min(index_of_first_change, length.saturating_sub(1));
583 index_of_last_change = usize::MAX;
584 }
585 timeline_items.truncate(length);
586 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Truncate to length {length}. Changes: {index_of_first_change}..{index_of_last_change}"); }
587 reobtain_latest_event = true;
588 }
589 VectorDiff::Reset { values } => {
590 if LOG_TIMELINE_DIFFS { println!("timeline_subscriber: room {room_id} diff Reset, new length {}", values.len()); }
591 clear_cache = true; // we must assume all items have changed.
592 timeline_items = values;
593 reobtain_latest_event = true;
594 }
595 }
596 }
597
598
599 if num_updates > 0 {
600 let new_latest_event = if reobtain_latest_event {
601 timeline.latest_event().await
602 } else {
603 None
604 };
605
606 let changed_indices = index_of_first_change..index_of_last_change;
607
608 if LOG_TIMELINE_DIFFS {
609 println!("timeline_subscriber: applied {num_updates} updates for room {room_id}, timeline now has {} items. is_append? {is_append}, clear_cache? {clear_cache}. Changes: {changed_indices:?}.", timeline_items.len());
610 }
611 timeline_update_sender.send(TimelineUpdate::NewItems {
612 new_items: timeline_items.clone(),
613 changed_indices,
614 clear_cache,
615 is_append,
616 }).expect("Error: timeline update sender couldn't send update with new items!");
617
618 // We must send this update *after* the actual NewItems update,
619 // otherwise the UI thread (RoomScreen) won't be able to correctly locate the target event.
620 if let Some((index, found_event_id)) = found_target_event_id.take() {
621 target_event_id = None;
622 timeline_update_sender.send(
623 TimelineUpdate::TargetEventFound {
624 target_event_id: found_event_id.clone(),
625 index,
626 }
627 ).unwrap_or_else(
628 |_e| panic!("Error: timeline update sender couldn't send TargetEventFound({found_event_id}, {index}) to room {room_id}!")
629 );
630 }
631
632 // Update the latest event for this room.
633 // We always do this in case a redaction or other event has changed the latest event.
634 if let Some(new_latest) = new_latest_event {
635 let _room_avatar_changed = update_latest_event(room_id.clone(), &new_latest, Some(&timeline_update_sender));
636 // if room_avatar_changed {
637 // spawn_fetch_room_avatar(room.clone());
638 // }
639 latest_event = Some(new_latest);
640 }
641
642 // Update this room's timeline UI view.
643 broadcast_event(UIUpdateMessage::RefreshUI).expect("Couldn't broadcast event to UI");
644 }
645 }
646
647 else => {
648 break;
649 }
650 }
651 }
652
653 eprintln!("Error: unexpectedly ended timeline subscriber for room {room_id}.");
654}
655
656/// Handles the given updated latest event for the given room.
657///
658/// This currently includes checking the given event for:
659/// * room name changes, in which it sends a `RoomsListUpdate`.
660/// * room power level changes to see if the current user's permissions
661/// have changed; if so, it sends a [`TimelineUpdate::UserPowerLevels`].
662/// * room avatar changes, which is not handled here.
663/// Instead, we return `true` such that other code can fetch the new avatar.
664/// * membership changes to see if the current user has joined or left a room.
665///
666/// Finally, this function sends a `RoomsListUpdate::UpdateLatestEvent`
667/// to update the latest event in the RoomsList's room preview for the given room.
668///
669/// Returns `true` if room avatar has changed and should be fetched and updated.
670pub fn update_latest_event(
671 room_id: OwnedRoomId,
672 event_tl_item: &EventTimelineItem,
673 timeline_update_sender: Option<&crossbeam_channel::Sender<TimelineUpdate>>,
674) -> bool {
675 let mut room_avatar_changed = false;
676
677 let (timestamp, latest_message_text) = get_latest_event_details(event_tl_item, &room_id);
678 match event_tl_item.content() {
679 // Check for relevant state events.
680 TimelineItemContent::OtherState(other) => {
681 match other.content() {
682 // Check for room name changes.
683 AnyOtherFullStateEventContent::RoomName(FullStateEventContent::Original {
684 content,
685 ..
686 }) => {
687 enqueue_rooms_list_update(RoomsListUpdate::UpdateRoomName {
688 room_id: room_id.clone(),
689 new_room_name: content.name.clone(),
690 });
691 }
692 // Check for room avatar changes.
693 AnyOtherFullStateEventContent::RoomAvatar(_avatar_event) => {
694 room_avatar_changed = true;
695 }
696 // Check for if can user send message.
697 AnyOtherFullStateEventContent::RoomPowerLevels(
698 FullStateEventContent::Original {
699 content,
700 prev_content: _,
701 },
702 ) => {
703 if let (Some(sender), Some(user_id)) =
704 (timeline_update_sender, current_user_id())
705 {
706 match sender.send(TimelineUpdate::UserPowerLevels(UserPowerLevels::from(
707 &content.clone().into(),
708 &user_id,
709 ))) {
710 Ok(_) => {
711 broadcast_event(UIUpdateMessage::RefreshUI)
712 .expect("Couldn't broadcast event to UI");
713 }
714 Err(e) => {
715 eprintln!("Failed to send the new RoomPowerLevels from an updated latest event: {e}");
716 }
717 }
718 }
719 }
720 _ => {} // TODO: implement behaviour
721 }
722 }
723 TimelineItemContent::MembershipChange(room_membership_change) => {
724 if matches!(
725 room_membership_change.change(),
726 Some(MembershipChange::InvitationAccepted | MembershipChange::Joined)
727 ) {
728 if current_user_id().as_deref() == Some(room_membership_change.user_id()) {
729 submit_async_request(MatrixRequest::GetRoomPowerLevels {
730 room_id: room_id.clone(),
731 });
732 }
733 }
734 }
735 _ => {} // TODO: implement behaviour
736 }
737
738 enqueue_rooms_list_update(RoomsListUpdate::UpdateLatestEvent {
739 room_id,
740 timestamp,
741 latest_message_text,
742 });
743 room_avatar_changed
744}