diff --git a/crates/matrix-sdk-ui/src/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs index 49a048fa1..afe73f9a9 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -14,7 +14,6 @@ use std::sync::Arc; -use chrono::{Datelike, Local, TimeZone}; use eyeball_im::{ObservableVector, ObservableVectorEntry}; use indexmap::{map::Entry, IndexMap}; use matrix_sdk::deserialized_responses::EncryptionInfo; @@ -50,13 +49,16 @@ use super::{ RemoteEventTimelineItem, }, find_read_marker, - item::{new_timeline_item, timeline_item}, + item::timeline_item, read_receipts::maybe_add_implicit_read_receipt, rfind_event_by_id, rfind_event_item, EventTimelineItem, Message, OtherState, ReactionGroup, ReactionSenderData, Sticker, TimelineDetails, TimelineInnerState, TimelineItem, TimelineItemContent, VirtualTimelineItem, DEFAULT_SANITIZER_MODE, }; -use crate::{events::SyncTimelineEventWithoutContent, timeline::InReplyToDetails}; +use crate::{ + events::SyncTimelineEventWithoutContent, + timeline::{timestamp_to_date, InReplyToDetails}, +}; #[derive(Clone)] pub(super) enum Flow { @@ -713,26 +715,22 @@ impl<'a> TimelineEventHandler<'a> { { let old_ts = latest_event.timestamp(); - if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps( - old_ts, - timestamp, - &mut self.state.next_internal_id, - ) { + if let Some(day_divider_item) = + self.state.maybe_create_day_divider_from_timestamps(old_ts, timestamp) + { trace!("Adding day divider (local)"); self.state.items.push_back(day_divider_item); } } else { // If there is no event item, there is no day divider yet. trace!("Adding first day divider (local)"); - self.state.items.push_back(new_timeline_item( - VirtualTimelineItem::DayDivider(timestamp), - &mut self.state.next_internal_id, - )); + let day_divider = + self.state.new_timeline_item(VirtualTimelineItem::DayDivider(timestamp)); + self.state.items.push_back(day_divider); } - self.state - .items - .push_back(new_timeline_item(item, &mut self.state.next_internal_id)); + let item = self.state.new_timeline_item(item); + self.state.items.push_back(item); } Flow::Remote { position: TimelineItemPosition::Start, event_id, .. } => { @@ -753,19 +751,16 @@ impl<'a> TimelineEventHandler<'a> { if let Some(VirtualTimelineItem::DayDivider(divider_ts)) = self.state.items.front().and_then(|item| item.as_virtual()) { - if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps( - *divider_ts, - timestamp, - &mut self.state.next_internal_id, - ) { + if let Some(day_divider_item) = + self.state.maybe_create_day_divider_from_timestamps(*divider_ts, timestamp) + { self.state.items.push_front(day_divider_item); } } else { // The list must always start with a day divider. - self.state.items.push_front(new_timeline_item( - VirtualTimelineItem::DayDivider(timestamp), - &mut self.state.next_internal_id, - )); + let day_divider = + self.state.new_timeline_item(VirtualTimelineItem::DayDivider(timestamp)); + self.state.items.push_front(day_divider); } if self.track_read_receipts { @@ -778,9 +773,8 @@ impl<'a> TimelineEventHandler<'a> { ); } - self.state - .items - .insert(1, new_timeline_item(item, &mut self.state.next_internal_id)); + let item = self.state.new_timeline_item(item); + self.state.items.insert(1, item); } Flow::Remote { @@ -921,11 +915,7 @@ impl<'a> TimelineEventHandler<'a> { // If a day divider was removed for an item about to be moved and we // now need to add a new one, reuse the previous one's ID. Some(day_divider_id) => day_divider_id, - None => { - let internal_id = self.state.next_internal_id; - self.state.next_internal_id += 1; - internal_id - } + None => self.state.next_internal_id(), }; let day_divider_item = @@ -941,10 +931,8 @@ impl<'a> TimelineEventHandler<'a> { } else { // If there is no event item, there is no day divider yet. trace!("Adding first day divider (remote)"); - let new_day_divider = new_timeline_item( - VirtualTimelineItem::DayDivider(timestamp), - &mut self.state.next_internal_id, - ); + let new_day_divider = + self.state.new_timeline_item(VirtualTimelineItem::DayDivider(timestamp)); if should_push { self.state.items.push_back(new_day_divider); } else { @@ -968,11 +956,7 @@ impl<'a> TimelineEventHandler<'a> { // echo) was removed and we now need to add it again, reuse // the previous item's ID. Some(id) => id, - None => { - let internal_id = self.state.next_internal_id; - self.state.next_internal_id += 1; - internal_id - } + None => self.state.next_internal_id(), }; trace!("Adding new remote timeline item after all non-pending events"); @@ -987,9 +971,8 @@ impl<'a> TimelineEventHandler<'a> { #[cfg(feature = "e2e-encryption")] Flow::Remote { position: TimelineItemPosition::Update(idx), .. } => { trace!("Updating timeline item at position {idx}"); - self.state - .items - .set(*idx, new_timeline_item(item, &mut self.state.next_internal_id)); + let item = self.state.new_timeline_item(item); + self.state.items.set(*idx, item); } } @@ -1101,34 +1084,3 @@ fn _update_timeline_item( debug!("Timeline item not found, discarding {action}"); } } - -#[derive(PartialEq)] -struct Date { - year: i32, - month: u32, - day: u32, -} - -/// Converts a timestamp since Unix Epoch to a year, month and day. -fn timestamp_to_date(ts: MilliSecondsSinceUnixEpoch) -> Date { - let datetime = Local - .timestamp_millis_opt(ts.0.into()) - // Only returns `None` if date is after Dec 31, 262143 BCE. - .single() - // Fallback to the current date to avoid issues with malicious - // homeservers. - .unwrap_or_else(Local::now); - - Date { year: datetime.year(), month: datetime.month(), day: datetime.day() } -} - -/// Returns a new day divider item for the new timestamp if it is on a different -/// day than the old timestamp -fn maybe_create_day_divider_from_timestamps( - old_ts: MilliSecondsSinceUnixEpoch, - new_ts: MilliSecondsSinceUnixEpoch, - next_internal_id: &mut u64, -) -> Option> { - (timestamp_to_date(old_ts) != timestamp_to_date(new_ts)) - .then(|| new_timeline_item(VirtualTimelineItem::DayDivider(new_ts), next_internal_id)) -} diff --git a/crates/matrix-sdk-ui/src/timeline/inner/mod.rs b/crates/matrix-sdk-ui/src/timeline/inner/mod.rs index 174fdaf5e..9a48288e7 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/mod.rs @@ -56,7 +56,7 @@ use super::{ compare_events_positions, event_handler::{HandleEventResult, TimelineItemPosition}, event_item::EventItemIdentifier, - item::{new_timeline_item, timeline_item}, + item::timeline_item, reactions::ReactionToggleResult, rfind_event_by_id, rfind_event_item, traits::RoomDataProvider, @@ -1029,8 +1029,8 @@ async fn fetch_replied_to_event( }); let event_item = item.with_content(TimelineItemContent::Message(reply), None); - let state_ref = &mut *state; - state_ref.items.set(index, new_timeline_item(event_item, &mut state_ref.next_internal_id)); + let new_timeline_item = state.new_timeline_item(event_item); + state.items.set(index, new_timeline_item); // Don't hold the state lock while the network request is made drop(state); diff --git a/crates/matrix-sdk-ui/src/timeline/inner/state.rs b/crates/matrix-sdk-ui/src/timeline/inner/state.rs index 7d52313ac..bbf6b0277 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/state.rs @@ -41,16 +41,17 @@ use crate::{ event_item::EventItemIdentifier, item::timeline_item, reactions::{ReactionToggleResult, Reactions}, - rfind_event_item, + rfind_event_item, timestamp_to_date, traits::RoomDataProvider, AnnotationKey, Error as TimelineError, Profile, ReactionSenderData, TimelineItem, + TimelineItemKind, VirtualTimelineItem, }, }; #[derive(Debug)] pub(in crate::timeline) struct TimelineInnerState { pub items: ObservableVector>, - pub next_internal_id: u64, + next_internal_id: u64, pub reactions: Reactions, pub fully_read_event: Option, /// Whether the fully-read marker item should try to be updated when an @@ -87,6 +88,27 @@ impl TimelineInnerState { } } + pub fn next_internal_id(&mut self) -> u64 { + let val = self.next_internal_id; + self.next_internal_id += 1; + val + } + + pub fn new_timeline_item(&mut self, kind: impl Into) -> Arc { + timeline_item(kind, self.next_internal_id()) + } + + /// Returns a new day divider item for the new timestamp if it is on a + /// different day than the old timestamp + pub fn maybe_create_day_divider_from_timestamps( + &mut self, + old_ts: MilliSecondsSinceUnixEpoch, + new_ts: MilliSecondsSinceUnixEpoch, + ) -> Option> { + (timestamp_to_date(old_ts) != timestamp_to_date(new_ts)) + .then(|| self.new_timeline_item(VirtualTimelineItem::DayDivider(new_ts))) + } + pub async fn handle_sync_timeline( &mut self, timeline: Timeline, diff --git a/crates/matrix-sdk-ui/src/timeline/item.rs b/crates/matrix-sdk-ui/src/timeline/item.rs index c6a963f98..8d66d3adb 100644 --- a/crates/matrix-sdk-ui/src/timeline/item.rs +++ b/crates/matrix-sdk-ui/src/timeline/item.rs @@ -119,12 +119,3 @@ pub(crate) fn timeline_item( ) -> Arc { Arc::new(TimelineItem { kind: kind.into(), internal_id }) } - -pub(crate) fn new_timeline_item( - kind: impl Into, - next_internal_id: &mut u64, -) -> Arc { - let internal_id = *next_internal_id; - *next_internal_id += 1; - timeline_item(kind, internal_id) -} diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index b4d128df7..f70d43d60 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -19,6 +19,7 @@ use std::{ops::Deref, pin::Pin, sync::Arc, task::Poll, time::Duration}; use async_std::sync::{Condvar, Mutex}; +use chrono::{Datelike, Local, TimeZone}; use eyeball::{SharedObservable, Subscriber}; use eyeball_im::VectorDiff; use futures_core::Stream; @@ -43,7 +44,7 @@ use ruma::{ room::{message::sanitize::HtmlSanitizerMode, redaction::RoomRedactionEventContent}, AnyMessageLikeEventContent, }, - EventId, OwnedEventId, OwnedTransactionId, TransactionId, UserId, + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, TransactionId, UserId, }; use thiserror::Error; use tokio::sync::mpsc::Sender; @@ -819,3 +820,23 @@ fn compare_events_positions( Some(RelativePosition::After) } } + +#[derive(PartialEq)] +struct Date { + year: i32, + month: u32, + day: u32, +} + +/// Converts a timestamp since Unix Epoch to a year, month and day. +fn timestamp_to_date(ts: MilliSecondsSinceUnixEpoch) -> Date { + let datetime = Local + .timestamp_millis_opt(ts.0.into()) + // Only returns `None` if date is after Dec 31, 262143 BCE. + .single() + // Fallback to the current date to avoid issues with malicious + // homeservers. + .unwrap_or_else(Local::now); + + Date { year: datetime.year(), month: datetime.month(), day: datetime.day() } +}