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}