ui: Add a unique ID for all timeline items

Co-authored-by: Jonas Platte <jplatte@matrix.org>
This commit is contained in:
Mauro Romito
2023-07-03 18:31:51 +02:00
committed by Jonas Platte
parent 2acc13ed2c
commit bc811cc1dc
11 changed files with 226 additions and 138 deletions

View File

@@ -218,23 +218,22 @@ impl TimelineItem {
#[uniffi::export]
impl TimelineItem {
pub fn as_event(self: Arc<Self>) -> Option<Arc<EventTimelineItem>> {
use matrix_sdk_ui::timeline::TimelineItem as Item;
unwrap_or_clone_arc_into_variant!(self, .0, Item::Event(evt) => {
Arc::new(EventTimelineItem(evt))
})
let event_item = self.0.as_event()?;
Some(Arc::new(EventTimelineItem(event_item.clone())))
}
pub fn as_virtual(self: Arc<Self>) -> Option<VirtualTimelineItem> {
use matrix_sdk_ui::timeline::{TimelineItem as Item, VirtualTimelineItem as VItem};
match &self.0 {
Item::Virtual(VItem::DayDivider(ts)) => {
Some(VirtualTimelineItem::DayDivider { ts: ts.0.into() })
}
Item::Virtual(VItem::ReadMarker) => Some(VirtualTimelineItem::ReadMarker),
Item::Event(_) => None,
use matrix_sdk_ui::timeline::VirtualTimelineItem as VItem;
match self.0.as_virtual()? {
VItem::DayDivider(ts) => Some(VirtualTimelineItem::DayDivider { ts: ts.0.into() }),
VItem::ReadMarker => Some(VirtualTimelineItem::ReadMarker),
}
}
pub fn unique_id(&self) -> u64 {
self.0.unique_id()
}
pub fn fmt_debug(&self) -> String {
format!("{:#?}", self.0)
}
@@ -281,10 +280,6 @@ impl EventTimelineItem {
!self.0.is_local_echo()
}
pub fn unique_identifier(&self) -> String {
self.0.unique_identifier()
}
pub fn transaction_id(&self) -> Option<String> {
self.0.transaction_id().map(ToString::to_string)
}

View File

@@ -46,10 +46,10 @@ use super::{
EventTimelineItemKind, LocalEventTimelineItem, Profile, RemoteEventOrigin,
RemoteEventTimelineItem,
},
find_read_marker,
find_read_marker, new_timeline_item,
read_receipts::maybe_add_implicit_read_receipt,
rfind_event_by_id, rfind_event_item, EventTimelineItem, Message, OtherState, ReactionGroup,
Sticker, TimelineDetails, TimelineInnerState, TimelineItem, TimelineItemContent,
rfind_event_by_id, rfind_event_item, timeline_item, EventTimelineItem, Message, OtherState,
ReactionGroup, Sticker, TimelineDetails, TimelineInnerState, TimelineItem, TimelineItemContent,
VirtualTimelineItem, DEFAULT_SANITIZER_MODE,
};
use crate::{events::SyncTimelineEventWithoutContent, timeline::event_item::ReactionSenderData};
@@ -209,6 +209,7 @@ pub(super) struct TimelineEventHandler<'a> {
meta: TimelineEventMetadata,
flow: Flow,
items: &'a mut ObservableVector<Arc<TimelineItem>>,
next_internal_id: &'a mut u64,
reaction_map: &'a mut HashMap<EventItemIdentifier, (ReactionSenderData, Annotation)>,
pending_reactions: &'a mut HashMap<OwnedEventId, IndexSet<OwnedEventId>>,
fully_read_event: &'a mut Option<OwnedEventId>,
@@ -245,6 +246,7 @@ impl<'a> TimelineEventHandler<'a> {
meta: event_meta,
flow,
items: &mut state.items,
next_internal_id: &mut state.next_internal_id,
reaction_map: &mut state.reaction_map,
pending_reactions: &mut state.pending_reactions,
fully_read_event: &mut state.fully_read_event,
@@ -474,9 +476,7 @@ impl<'a> TimelineEventHandler<'a> {
trace!("Adding reaction");
self.items.set(
idx,
Arc::new(TimelineItem::Event(
event_item.with_kind(remote_event_item.with_reactions(reactions)),
)),
event_item.with_inner_kind(remote_event_item.with_reactions(reactions)),
);
self.result.items_updated += 1;
}
@@ -707,19 +707,24 @@ 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)
{
if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps(
old_ts,
timestamp,
self.next_internal_id,
) {
trace!("Adding day divider (local)");
self.items.push_back(Arc::new(day_divider_item));
self.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.items.push_back(Arc::new(TimelineItem::day_divider(timestamp)));
self.items.push_back(new_timeline_item(
VirtualTimelineItem::DayDivider(timestamp),
self.next_internal_id,
));
}
self.items.push_back(Arc::new(item.into()));
self.items.push_back(new_timeline_item(item, self.next_internal_id));
}
Flow::Remote { position: TimelineItemPosition::Start, event_id, .. } => {
@@ -739,14 +744,19 @@ impl<'a> TimelineEventHandler<'a> {
if let Some(VirtualTimelineItem::DayDivider(divider_ts)) =
self.items.front().and_then(|item| item.as_virtual())
{
if let Some(day_divider_item) =
maybe_create_day_divider_from_timestamps(*divider_ts, timestamp)
{
self.items.push_front(Arc::new(day_divider_item));
if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps(
*divider_ts,
timestamp,
self.next_internal_id,
) {
self.items.push_front(day_divider_item);
}
} else {
// The list must always start with a day divider.
self.items.push_front(Arc::new(TimelineItem::day_divider(timestamp)));
self.items.push_front(new_timeline_item(
VirtualTimelineItem::DayDivider(timestamp),
self.next_internal_id,
));
}
if self.track_read_receipts {
@@ -759,7 +769,7 @@ impl<'a> TimelineEventHandler<'a> {
);
}
self.items.insert(1, Arc::new(item.into()));
self.items.insert(1, new_timeline_item(item, self.next_internal_id));
}
Flow::Remote {
@@ -775,7 +785,7 @@ impl<'a> TimelineEventHandler<'a> {
// Item was previously received from the server. This
// should be very rare normally, but with the sliding-
// sync proxy, it is actually very common.
trace!(?item, ?old_item, "Received duplicate event");
trace!(?item, old_item = ?*old_item, "Received duplicate event");
if old_item.content.is_redacted() && !item.content.is_redacted() {
warn!("Got original form of an event that was previously redacted");
@@ -799,6 +809,8 @@ impl<'a> TimelineEventHandler<'a> {
// TODO: Check whether anything is different about the
// old and new item?
let old_item_id = old_item.internal_id;
if idx == self.items.len() - 1
&& timestamp_to_date(old_item.timestamp()) == timestamp_to_date(timestamp)
{
@@ -816,7 +828,7 @@ impl<'a> TimelineEventHandler<'a> {
}
trace!(idx, "Replacing existing event");
self.items.set(idx, Arc::new(item.into()));
self.items.set(idx, timeline_item(item, old_item_id));
return;
}
@@ -888,14 +900,16 @@ impl<'a> TimelineEventHandler<'a> {
// Check if that event has the same date as the new one.
let old_ts = latest_event.timestamp();
if let Some(day_divider_item) =
maybe_create_day_divider_from_timestamps(old_ts, timestamp)
{
if let Some(day_divider_item) = maybe_create_day_divider_from_timestamps(
old_ts,
timestamp,
self.next_internal_id,
) {
trace!("Adding day divider (remote)");
if should_push {
self.items.push_back(Arc::new(day_divider_item));
self.items.push_back(day_divider_item);
} else {
self.items.insert(insert_idx, Arc::new(day_divider_item));
self.items.insert(insert_idx, day_divider_item);
insert_idx += 1;
}
}
@@ -903,10 +917,18 @@ impl<'a> TimelineEventHandler<'a> {
// If there is no event item, there is no day divider yet.
trace!("Adding first day divider (remote)");
if should_push {
self.items.push_back(Arc::new(TimelineItem::day_divider(timestamp)));
self.items.push_back(new_timeline_item(
VirtualTimelineItem::DayDivider(timestamp),
self.next_internal_id,
));
} else {
self.items
.insert(insert_idx, Arc::new(TimelineItem::day_divider(timestamp)));
self.items.insert(
insert_idx,
new_timeline_item(
VirtualTimelineItem::DayDivider(timestamp),
self.next_internal_id,
),
);
insert_idx += 1;
}
}
@@ -923,16 +945,16 @@ impl<'a> TimelineEventHandler<'a> {
trace!("Adding new remote timeline item after all non-pending events");
if should_push {
self.items.push_back(Arc::new(item.into()));
self.items.push_back(new_timeline_item(item, self.next_internal_id));
} else {
self.items.insert(insert_idx, Arc::new(item.into()));
self.items.insert(insert_idx, new_timeline_item(item, self.next_internal_id));
}
}
#[cfg(feature = "e2e-encryption")]
Flow::Remote { position: TimelineItemPosition::Update(idx), .. } => {
trace!("Updating timeline item at position {idx}");
self.items.set(*idx, Arc::new(item.into()));
self.items.set(*idx, new_timeline_item(item, self.next_internal_id));
}
}
@@ -994,7 +1016,7 @@ pub(crate) fn update_read_marker(
// We don't want to insert the read marker if it is at the end of the timeline.
if idx + 1 < items.len() {
*event_should_update_fully_read_marker = false;
items.insert(idx + 1, Arc::new(TimelineItem::read_marker()));
items.insert(idx + 1, TimelineItem::read_marker());
} else {
*event_should_update_fully_read_marker = true;
}
@@ -1035,9 +1057,9 @@ fn _update_timeline_item(
) {
if let Some((idx, item)) = rfind_event_by_id(items, event_id) {
trace!("Found timeline item to update");
if let Some(new_item) = update(item) {
if let Some(new_item) = update(item.inner) {
trace!("Updating item");
items.set(idx, Arc::new(TimelineItem::Event(new_item)));
items.set(idx, timeline_item(new_item, item.internal_id));
*items_updated += 1;
}
} else {
@@ -1070,7 +1092,8 @@ fn timestamp_to_date(ts: MilliSecondsSinceUnixEpoch) -> Date {
fn maybe_create_day_divider_from_timestamps(
old_ts: MilliSecondsSinceUnixEpoch,
new_ts: MilliSecondsSinceUnixEpoch,
) -> Option<TimelineItem> {
next_internal_id: &mut u64,
) -> Option<Arc<TimelineItem>> {
(timestamp_to_date(old_ts) != timestamp_to_date(new_ts))
.then(|| TimelineItem::day_divider(new_ts))
.then(|| new_timeline_item(VirtualTimelineItem::DayDivider(new_ts), next_internal_id))
}

View File

@@ -200,19 +200,6 @@ impl EventTimelineItem {
}
}
/// Get a unique identifier to identify the event item, either by using
/// transaction ID or event ID in case of a local event, or by event ID in
/// case of a remote event.
pub fn unique_identifier(&self) -> String {
match &self.kind {
EventTimelineItemKind::Local(item) => match &item.send_state {
EventSendState::Sent { event_id } => event_id.to_string(),
_ => item.transaction_id.to_string(),
},
EventTimelineItemKind::Remote(item) => item.event_id.to_string(),
}
}
/// Get the event's send state, if it is a local echo.
pub fn send_state(&self) -> Option<&EventSendState> {
match &self.kind {

View File

@@ -64,12 +64,13 @@ use super::{
},
event_item::{EventItemIdentifier, ReactionSenderData},
reactions::ReactionToggleResult,
rfind_event_by_id, rfind_event_item,
rfind_event_by_id, rfind_event_item, timeline_item,
traits::RoomDataProvider,
AnnotationKey, EventSendState, EventTimelineItem, InReplyToDetails, Message, Profile,
RelativePosition, RepliedToEvent, TimelineDetails, TimelineItem, TimelineItemContent,
TimelineItemKind,
};
use crate::events::SyncTimelineEventWithoutContent;
use crate::{events::SyncTimelineEventWithoutContent, timeline::new_timeline_item};
#[derive(Clone, Debug)]
pub(super) struct TimelineInner<P: RoomDataProvider = room::Common> {
@@ -81,6 +82,7 @@ pub(super) struct TimelineInner<P: RoomDataProvider = room::Common> {
#[derive(Debug, Default)]
pub(super) struct TimelineInnerState {
pub(super) items: ObservableVector<Arc<TimelineItem>>,
pub(super) next_internal_id: u64,
/// Reaction event / txn ID => sender and reaction data.
pub(super) reaction_map: HashMap<EventItemIdentifier, (ReactionSenderData, Annotation)>,
/// ID of event that is not in the timeline yet => List of reaction event
@@ -602,8 +604,8 @@ impl<P: RoomDataProvider> TimelineInner<P> {
let is_error = matches!(send_state, EventSendState::SendingFailed { .. });
let new_item = TimelineItem::Event(item.with_kind(local_item.with_send_state(send_state)));
state.items.set(idx, Arc::new(new_item));
let new_item = item.with_inner_kind(local_item.with_send_state(send_state));
state.items.set(idx, new_item);
if is_error {
// When there is an error, sending further messages is paused. This
@@ -611,12 +613,13 @@ impl<P: RoomDataProvider> TimelineInner<P> {
// events to cancelled.
let num_items = state.items.len();
for idx in 0..num_items {
let Some(item) = state.items[idx].as_event() else { continue };
let Some(local_item) = item.as_local() else { continue };
let item = state.items[idx].clone();
let Some(event_item) = item.as_event() else { continue };
let Some(local_item) = event_item.as_local() else { continue };
if matches!(&local_item.send_state, EventSendState::NotSentYet) {
let new_item =
item.with_kind(local_item.with_send_state(EventSendState::Cancelled));
state.items.set(idx, Arc::new(TimelineItem::Event(new_item)));
let new_event_item =
event_item.with_kind(local_item.with_send_state(EventSendState::Cancelled));
state.items.set(idx, item.with_kind(new_event_item));
}
}
}
@@ -691,12 +694,9 @@ impl<P: RoomDataProvider> TimelineInner<P> {
EventSendState::SendingFailed { .. } | EventSendState::Cancelled => {}
}
let new_item = TimelineItem::Event(
item.with_kind(local_item.with_send_state(EventSendState::NotSentYet)),
);
let new_item = item.with_inner_kind(local_item.with_send_state(EventSendState::NotSentYet));
let content = item.content.clone();
state.items.set(idx, Arc::new(new_item));
state.items.set(idx, new_item);
Some(content)
}
@@ -894,9 +894,10 @@ impl<P: RoomDataProvider> TimelineInner<P> {
async fn set_non_ready_sender_profiles(&self, profile_state: TimelineDetails<Profile>) {
let mut state = self.state.lock().await;
for idx in 0..state.items.len() {
let Some(event_item) = state.items[idx].as_event() else { continue };
let item = state.items[idx].clone();
let Some(event_item) = item.as_event() else { continue };
if !matches!(event_item.sender_profile(), TimelineDetails::Ready(_)) {
let item = Arc::new(TimelineItem::Event(
let item = item.with_kind(TimelineItemKind::Event(
event_item.with_sender_profile(profile_state.clone()),
));
state.items.set(idx, item);
@@ -919,20 +920,21 @@ impl<P: RoomDataProvider> TimelineInner<P> {
assert_eq!(state.items.len(), num_items);
let event_item = state.items[idx].as_event().unwrap();
let item = state.items[idx].clone();
let event_item = item.as_event().unwrap();
match maybe_profile {
Some(profile) => {
if !event_item.sender_profile().contains(&profile) {
let updated_item =
event_item.with_sender_profile(TimelineDetails::Ready(profile));
state.items.set(idx, Arc::new(TimelineItem::Event(updated_item)));
state.items.set(idx, item.with_kind(updated_item));
}
}
None => {
if !event_item.sender_profile().is_unavailable() {
let updated_item =
event_item.with_sender_profile(TimelineDetails::Unavailable);
state.items.set(idx, Arc::new(TimelineItem::Event(updated_item)));
state.items.set(idx, item.with_kind(updated_item));
}
}
}
@@ -1028,6 +1030,7 @@ impl TimelineInner {
};
trace!("Updating in-reply-to details");
let internal_id = item.internal_id;
let mut item = item.clone();
item.set_content(TimelineItemContent::Message(
message.with_in_reply_to(InReplyToDetails {
@@ -1035,7 +1038,7 @@ impl TimelineInner {
event,
}),
));
state.items.set(index, Arc::new(item.into()));
state.items.set(index, timeline_item(item, internal_id));
Ok(())
}
@@ -1279,7 +1282,9 @@ async fn fetch_replied_to_event(
event: TimelineDetails::Pending,
});
let event_item = item.with_content(TimelineItemContent::Message(reply), None);
state.items.set(index, Arc::new(event_item.into()));
let state_ref = &mut *state;
state_ref.items.set(index, new_timeline_item(event_item, &mut state_ref.next_internal_id));
// Don't hold the state lock while the network request is made
drop(state);
@@ -1384,7 +1389,7 @@ fn update_timeline_reaction(
}
}
state.items.set(idx, Arc::new(TimelineItem::Event(new_related)));
state.items.set(idx, timeline_item(new_related, related.internal_id));
Ok(())
}

View File

@@ -16,7 +16,7 @@
//!
//! See [`Timeline`] for details.
use std::{pin::Pin, sync::Arc, task::Poll, time::Duration};
use std::{ops::Deref, pin::Pin, sync::Arc, task::Poll, time::Duration};
use async_std::sync::{Condvar, Mutex};
use eyeball::{SharedObservable, Subscriber};
@@ -42,7 +42,7 @@ use ruma::{
room::{message::sanitize::HtmlSanitizerMode, redaction::RoomRedactionEventContent},
AnyMessageLikeEventContent,
},
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, TransactionId, UserId,
EventId, OwnedEventId, OwnedTransactionId, TransactionId, UserId,
};
use thiserror::Error;
use tokio::sync::mpsc::Sender;
@@ -82,6 +82,7 @@ pub use self::{
virtual_item::VirtualTimelineItem,
};
use self::{
event_item::EventTimelineItemKind,
inner::{ReactionAction, TimelineInner, TimelineInnerState},
queue::LocalMessage,
reactions::ReactionToggleResult,
@@ -708,10 +709,9 @@ impl<S: Stream> Stream for TimelineStream<S> {
}
}
/// A single entry in timeline.
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum TimelineItem {
pub enum TimelineItemKind {
/// An event or aggregation of multiple events.
Event(EventTimelineItem),
/// An item that doesn't correspond to an event, for example the user's own
@@ -719,75 +719,144 @@ pub enum TimelineItem {
Virtual(VirtualTimelineItem),
}
/// A single entry in timeline.
#[derive(Clone, Debug)]
pub struct TimelineItem {
kind: TimelineItemKind,
internal_id: u64,
}
impl TimelineItem {
/// Get the inner `EventTimelineItem`, if this is a `TimelineItem::Event`.
pub(crate) fn with_kind(&self, kind: impl Into<TimelineItemKind>) -> Arc<Self> {
Arc::new(Self { kind: kind.into(), internal_id: self.internal_id })
}
/// Get the inner `EventTimelineItem`, if this is a
/// `TimelineItemKind::Event`.
pub fn as_event(&self) -> Option<&EventTimelineItem> {
match self {
Self::Event(v) => Some(v),
match &self.kind {
TimelineItemKind::Event(v) => Some(v),
_ => None,
}
}
/// Get the inner `VirtualTimelineItem`, if this is a
/// `TimelineItem::Virtual`.
/// `TimelineItemKind::Virtual`.
pub fn as_virtual(&self) -> Option<&VirtualTimelineItem> {
match self {
Self::Virtual(v) => Some(v),
match &self.kind {
TimelineItemKind::Virtual(v) => Some(v),
_ => None,
}
}
/// Creates a new day divider from the given timestamp.
fn day_divider(ts: MilliSecondsSinceUnixEpoch) -> Self {
Self::Virtual(VirtualTimelineItem::DayDivider(ts))
/// Get a unique ID for this timeline item.
///
/// It identifies the item on a best-effort basis. For instance, edits to an
/// [`EventTimelineItem`] will not change the ID of the enclosing
/// `TimelineItem`. For some virtual items like day dividers, identity isn't
/// easy to define though and you might see a new ID getting generated for a
/// day divider that you perceive to be "the same" as a previous one.
pub fn unique_id(&self) -> u64 {
self.internal_id
}
fn read_marker() -> Self {
Self::Virtual(VirtualTimelineItem::ReadMarker)
fn read_marker() -> Arc<TimelineItem> {
Arc::new(Self {
kind: TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker),
internal_id: u64::MAX,
})
}
fn is_virtual(&self) -> bool {
matches!(self, Self::Virtual(_))
matches!(self.kind, TimelineItemKind::Virtual(_))
}
fn is_day_divider(&self) -> bool {
matches!(self, Self::Virtual(VirtualTimelineItem::DayDivider(_)))
matches!(self.kind, TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(_)))
}
fn is_read_marker(&self) -> bool {
matches!(self, Self::Virtual(VirtualTimelineItem::ReadMarker))
matches!(self.kind, TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker))
}
}
impl From<EventTimelineItem> for TimelineItem {
impl Deref for TimelineItem {
type Target = TimelineItemKind;
fn deref(&self) -> &Self::Target {
&self.kind
}
}
impl From<EventTimelineItem> for TimelineItemKind {
fn from(item: EventTimelineItem) -> Self {
Self::Event(item)
}
}
impl From<VirtualTimelineItem> for TimelineItem {
impl From<VirtualTimelineItem> for TimelineItemKind {
fn from(item: VirtualTimelineItem) -> Self {
Self::Virtual(item)
}
}
fn timeline_item(kind: impl Into<TimelineItemKind>, internal_id: u64) -> Arc<TimelineItem> {
Arc::new(TimelineItem { kind: kind.into(), internal_id })
}
fn new_timeline_item(
kind: impl Into<TimelineItemKind>,
next_internal_id: &mut u64,
) -> Arc<TimelineItem> {
let internal_id = *next_internal_id;
*next_internal_id += 1;
timeline_item(kind, internal_id)
}
struct EventTimelineItemWithId<'a> {
inner: &'a EventTimelineItem,
internal_id: u64,
}
impl<'a> EventTimelineItemWithId<'a> {
fn with_inner_kind(&self, kind: impl Into<EventTimelineItemKind>) -> Arc<TimelineItem> {
Arc::new(TimelineItem {
kind: self.inner.with_kind(kind).into(),
internal_id: self.internal_id,
})
}
}
impl Deref for EventTimelineItemWithId<'_> {
type Target = EventTimelineItem;
fn deref(&self) -> &Self::Target {
self.inner
}
}
// FIXME: Put an upper bound on timeline size or add a separate map to look up
// the index of a timeline item by its key, to avoid large linear scans.
fn rfind_event_item(
items: &Vector<Arc<TimelineItem>>,
mut f: impl FnMut(&EventTimelineItem) -> bool,
) -> Option<(usize, &EventTimelineItem)> {
) -> Option<(usize, EventTimelineItemWithId<'_>)> {
items
.iter()
.enumerate()
.filter_map(|(idx, item)| Some((idx, item.as_event()?)))
.rfind(|(_, it)| f(it))
.filter_map(|(idx, item)| {
Some((
idx,
EventTimelineItemWithId { inner: item.as_event()?, internal_id: item.internal_id },
))
})
.rfind(|(_, it)| f(it.inner))
}
fn rfind_event_by_id<'a>(
items: &'a Vector<Arc<TimelineItem>>,
event_id: &EventId,
) -> Option<(usize, &'a EventTimelineItem)> {
) -> Option<(usize, EventTimelineItemWithId<'a>)> {
rfind_event_item(items, |it| it.event_id() == Some(event_id))
}

View File

@@ -25,7 +25,8 @@ use tracing::{error, warn};
use super::{
compare_events_positions, event_item::EventTimelineItemKind, inner::TimelineInnerState,
rfind_event_by_id, traits::RoomDataProvider, EventTimelineItem, RelativePosition, TimelineItem,
rfind_event_by_id, timeline_item, traits::RoomDataProvider, EventTimelineItem,
RelativePosition, TimelineItem,
};
struct FullReceipt<'a> {
@@ -44,10 +45,11 @@ impl TimelineInnerState {
receipt: Receipt,
) {
let Some(pos) = receipt_item_pos else { return };
let Some(mut event_item) = self.items[pos].as_event().cloned() else { return };
let timeline_item = self.items[pos].clone();
let Some(mut event_item) = timeline_item.as_event().cloned() else { return };
event_item.as_remote_mut().unwrap().add_read_receipt(user_id, receipt);
self.items.set(pos, Arc::new(event_item.into()));
self.items.set(pos, timeline_item.with_kind(event_item));
}
pub(super) fn handle_explicit_read_receipts(
@@ -273,6 +275,7 @@ fn maybe_update_read_receipt(
if !is_own_user_id {
// Remove the read receipt for this user from the old event.
let old_event_item_id = old_event_item.internal_id;
let mut old_event_item = old_event_item.clone();
if let Some(old_remote_event_item) = old_event_item.as_remote_mut() {
if !old_remote_event_item.remove_read_receipt(receipt.user_id) {
@@ -281,7 +284,8 @@ fn maybe_update_read_receipt(
receipt doesn't have a receipt for the user"
);
}
timeline_items.set(old_receipt_pos, Arc::new(old_event_item.into()));
timeline_items
.set(old_receipt_pos, timeline_item(old_event_item, old_event_item_id));
} else {
warn!("received a read receipt for a local item, this should not be possible");
}

View File

@@ -34,8 +34,8 @@ use stream_assert::assert_next_matches;
use super::{sync_timeline_event, TestTimeline, ALICE, BOB};
use crate::timeline::{
event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineDetails, TimelineItem,
TimelineItemContent, VirtualTimelineItem,
event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineDetails,
TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
};
#[async_test]
@@ -56,7 +56,7 @@ async fn initial_events() {
.await;
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
assert_matches!(&*item, TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_)));
assert_matches!(&item.kind, TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(_)));
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
assert_eq!(item.as_event().unwrap().sender(), *ALICE);
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
@@ -203,8 +203,11 @@ async fn dedup_pagination() {
let timeline_items = timeline.inner.items().await;
assert_eq!(timeline_items.len(), 2);
assert_matches!(*timeline_items[0], TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_)));
assert_matches!(*timeline_items[1], TimelineItem::Event(_));
assert_matches!(
timeline_items[0].kind,
TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(_))
);
assert_matches!(timeline_items[1].kind, TimelineItemKind::Event(_));
}
#[async_test]

View File

@@ -23,7 +23,7 @@ use ruma::{
use stream_assert::assert_next_matches;
use super::{TestTimeline, ALICE, BOB};
use crate::timeline::{TimelineItem, VirtualTimelineItem};
use crate::timeline::{TimelineItemKind, VirtualTimelineItem};
#[async_test]
async fn day_divider() {
@@ -132,7 +132,7 @@ async fn update_read_marker() {
// Now the read marker is reinserted after the second event.
let marker = assert_next_matches!(stream, VectorDiff::Insert { index: 3, value } => value);
assert_matches!(*marker, TimelineItem::Virtual(VirtualTimelineItem::ReadMarker));
assert_matches!(marker.kind, TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker));
// Nothing should happen if the fully read event is set back to an older event.
timeline.inner.set_fully_read_event(first_event_id).await;
@@ -153,5 +153,5 @@ async fn update_read_marker() {
// The read marker is moved after the third event.
assert_next_matches!(stream, VectorDiff::Remove { index: 3 });
let marker = assert_next_matches!(stream, VectorDiff::Insert { index: 4, value } => value);
assert_matches!(*marker, TimelineItem::Virtual(VirtualTimelineItem::ReadMarker));
assert_matches!(marker.kind, TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker));
}

View File

@@ -11,7 +11,7 @@ use matrix_sdk_ui::{
ALL_ROOMS_LIST_NAME as ALL_ROOMS, INVITES_LIST_NAME as INVITES,
VISIBLE_ROOMS_LIST_NAME as VISIBLE_ROOMS,
},
timeline::{TimelineItem, VirtualTimelineItem},
timeline::{TimelineItemKind, VirtualTimelineItem},
RoomListService,
};
use ruma::{
@@ -2027,12 +2027,12 @@ async fn test_room_timeline() -> Result<(), Error> {
// Previous timeline items.
assert_matches!(
previous_timeline_items[0].as_ref(),
TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_))
**previous_timeline_items[0],
TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(_))
);
assert_matches!(
previous_timeline_items[1].as_ref(),
TimelineItem::Event(item) => {
&**previous_timeline_items[1],
TimelineItemKind::Event(item) => {
assert_eq!(item.event_id().unwrap().as_str(), "$x0:bar.org");
}
);

View File

@@ -20,7 +20,7 @@ use futures_util::StreamExt;
use matrix_sdk::{config::SyncSettings, executor::spawn, ruma::MilliSecondsSinceUnixEpoch};
use matrix_sdk_test::{async_test, EventBuilder, JoinedRoomBuilder, TimelineTestEvent};
use matrix_sdk_ui::timeline::{
EventSendState, RoomExt, TimelineItem, TimelineItemContent, VirtualTimelineItem,
EventSendState, RoomExt, TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
};
use ruma::{
event_id,
@@ -121,7 +121,7 @@ async fn echo() {
timeline_stream.next().await,
Some(VectorDiff::PushBack { value }) => value
);
assert_matches!(&*new_item, TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_)));
assert_matches!(**new_item, TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(_)));
// Remote echo is added
let remote_echo = assert_matches!(

View File

@@ -22,7 +22,9 @@ use matrix_sdk::{
SlidingSync, SlidingSyncList, SlidingSyncListBuilder, SlidingSyncMode, UpdateSummary,
};
use matrix_sdk_test::async_test;
use matrix_sdk_ui::timeline::{SlidingSyncRoomExt, TimelineItem, VirtualTimelineItem};
use matrix_sdk_ui::timeline::{
SlidingSyncRoomExt, TimelineItem, TimelineItemKind, VirtualTimelineItem,
};
use ruma::{room_id, RoomId};
use serde_json::json;
use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate};
@@ -80,8 +82,8 @@ macro_rules! assert_timeline_stream {
$stream.next().now_or_never(),
Some(Some(VectorDiff::PushBack { value })) => {
assert_matches!(
value.as_ref(),
TimelineItem::Virtual(
**value,
TimelineItemKind::Virtual(
VirtualTimelineItem::DayDivider(_)
)
);
@@ -105,8 +107,8 @@ macro_rules! assert_timeline_stream {
$stream.next().now_or_never(),
Some(Some(VectorDiff::PushBack { value })) => {
assert_matches!(
value.as_ref(),
TimelineItem::Event(event_timeline_item) => {
&**value,
TimelineItemKind::Event(event_timeline_item) => {
assert_eq!(event_timeline_item.event_id().unwrap().as_str(), $event_id);
}
);
@@ -130,8 +132,8 @@ macro_rules! assert_timeline_stream {
$stream.next().now_or_never(),
Some(Some(VectorDiff::Insert { index: $index, value })) => {
assert_matches!(
value.as_ref(),
TimelineItem::Event(event_timeline_item) => {
&**value,
TimelineItemKind::Event(event_timeline_item) => {
assert_eq!(event_timeline_item.event_id().unwrap().as_str(), $event_id);
}
);
@@ -155,8 +157,8 @@ macro_rules! assert_timeline_stream {
$stream.next().now_or_never(),
Some(Some(VectorDiff::Set { index: $index, value })) => {
assert_matches!(
value.as_ref(),
TimelineItem::Event(event_timeline_item) => {
&**value,
TimelineItemKind::Event(event_timeline_item) => {
assert_eq!(event_timeline_item.event_id().unwrap().as_str(), $event_id);
}
);