feat(ui): Implement Room::timeline and ::latest_event.

This patch changes `RoomInner` to have an `room:
Option<matrix_sdk::room::Room>` field to `room: matrix_sdk::room::Room`.
`room` is not longer an `Option`.

Then, it's easier to have a new `timeline: Timeline` field, along with a
`sneaky_timeline: Timeline` field. Both are used by the new
`Room::timeline()` and `Room::latest_event()` method.
This commit is contained in:
Ivan Enderlin
2023-06-02 22:32:57 +02:00
parent 67ef42f4c5
commit c1a24cf033
4 changed files with 243 additions and 14 deletions

View File

@@ -77,6 +77,8 @@ use once_cell::sync::Lazy;
use ruma::{OwnedRoomId, RoomId};
use thiserror::Error;
use crate::{timeline::EventTimelineItem, Timeline};
pub const ALL_ROOMS_LIST_NAME: &str = "all_rooms";
pub const VISIBLE_ROOMS_LIST_NAME: &str = "visible_rooms";
@@ -222,12 +224,12 @@ impl RoomList {
Ok(())
}
/// Get a [`Room`] if it exists.
pub async fn room(&self, room_id: &RoomId) -> Result<Room, Error> {
self.sliding_sync
.get_room(room_id)
.await
.map(Room::new)
.ok_or_else(|| Error::RoomNotFound(room_id.to_owned()))
match self.sliding_sync.get_room(room_id).await {
Some(room) => Room::new(room).await,
None => Err(Error::RoomNotFound(room_id.to_owned())),
}
}
#[cfg(any(test, feature = "testing"))]
@@ -246,16 +248,42 @@ pub struct Room {
#[derive(Debug)]
struct RoomInner {
/// The Sliding Sync room.
sliding_sync_room: SlidingSyncRoom,
room: Option<matrix_sdk::room::Room>,
/// The underlying client room.
room: matrix_sdk::room::Room,
/// The timeline of the room.
timeline: Timeline,
/// The “sneaky” timeline of the room, i.e. this timeline doesn't track the
/// read marker nor the receipts.
sneaky_timeline: Timeline,
}
impl Room {
/// Create a new `Room`.
fn new(sliding_sync_room: SlidingSyncRoom) -> Self {
let room = sliding_sync_room.client().get_room(sliding_sync_room.room_id());
async fn new(sliding_sync_room: SlidingSyncRoom) -> Result<Self, Error> {
let room = sliding_sync_room
.client()
.get_room(sliding_sync_room.room_id())
.ok_or_else(|| Error::RoomNotFound(sliding_sync_room.room_id().to_owned()))?;
Self { inner: Arc::new(RoomInner { sliding_sync_room, room }) }
let timeline = Timeline::builder(&room)
.events(sliding_sync_room.prev_batch(), sliding_sync_room.timeline_queue())
.track_read_marker_and_receipts()
.build()
.await;
let sneaky_timeline = Timeline::builder(&room)
.events(sliding_sync_room.prev_batch(), sliding_sync_room.timeline_queue())
.build()
.await;
Ok(Self {
inner: Arc::new(RoomInner { sliding_sync_room, room, timeline, sneaky_timeline }),
})
}
/// Get the best possible name for the room.
@@ -265,9 +293,22 @@ impl Room {
pub async fn name(&self) -> Option<String> {
Some(match self.inner.sliding_sync_room.name() {
Some(name) => name,
None => self.inner.room.as_ref()?.display_name().await.ok()?.to_string(),
None => self.inner.room.display_name().await.ok()?.to_string(),
})
}
/// Get the timeline of the room.
pub fn timeline(&self) -> &Timeline {
&self.inner.timeline
}
/// Get the latest event of the timeline.
///
/// It's different from `Self::timeline().latest_event()` as it won't track
/// the read marker and receipts.
pub async fn latest_event(&self) -> Option<EventTimelineItem> {
self.inner.sneaky_timeline.latest_event().await
}
}
/// [`RoomList`]'s errors.

View File

@@ -8,13 +8,17 @@ use matrix_sdk_ui::{
Error, Input, RoomListEntry, State, ALL_ROOMS_LIST_NAME as ALL_ROOMS,
VISIBLE_ROOMS_LIST_NAME as VISIBLE_ROOMS,
},
timeline::{TimelineItem, VirtualTimelineItem},
RoomList,
};
use ruma::room_id;
use ruma::{event_id, room_id};
use serde_json::json;
use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate};
use crate::logged_in_client;
use crate::{
logged_in_client,
timeline::sliding_sync::{assert_timeline_stream, timeline_event},
};
async fn new_room_list() -> Result<(MockServer, RoomList), Error> {
let (client, server) = logged_in_client().await;
@@ -827,6 +831,181 @@ async fn test_room_not_found() -> Result<(), Error> {
Ok(())
}
#[async_test]
async fn test_room_timeline() -> Result<(), Error> {
let (server, room_list) = new_room_list().await?;
let sync = room_list.sync();
pin_mut!(sync);
let room_id = room_id!("!r0:bar.org");
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
assert request = {},
respond with = {
"pos": "0",
"lists": {
ALL_ROOMS: {
"count": 2,
"ops": [
{
"op": "SYNC",
"range": [0, 0],
"room_ids": [room_id],
},
],
},
},
"rooms": {
room_id: {
"name": "Room #0",
"initial": true,
"timeline": [
timeline_event!("$x0:bar.org" at 0 sec),
],
},
},
},
};
let room = room_list.room(room_id).await?;
let timeline = room.timeline();
let (previous_timeline_items, mut timeline_items_stream) = timeline.subscribe().await;
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
assert request = {},
respond with = {
"pos": "0",
"lists": {},
"rooms": {
room_id: {
"timeline": [
timeline_event!("$x1:bar.org" at 1 sec),
timeline_event!("$x2:bar.org" at 2 sec),
],
},
},
},
};
// Previous timeline items.
assert_matches!(
previous_timeline_items[0].as_ref(),
TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_))
);
assert_matches!(
previous_timeline_items[1].as_ref(),
TimelineItem::Event(item) => {
assert_eq!(item.event_id().unwrap().as_str(), "$x0:bar.org");
}
);
// Timeline items stream.
assert_timeline_stream! {
[timeline_items_stream]
update[1] "$x0:bar.org";
append "$x1:bar.org";
update[2] "$x1:bar.org";
append "$x2:bar.org";
};
Ok(())
}
#[async_test]
async fn test_room_latest_event() -> Result<(), Error> {
let (server, room_list) = new_room_list().await?;
let sync = room_list.sync();
pin_mut!(sync);
let room_id = room_id!("!r0:bar.org");
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
assert request = {},
respond with = {
"pos": "0",
"lists": {
ALL_ROOMS: {
"count": 2,
"ops": [
{
"op": "SYNC",
"range": [0, 0],
"room_ids": [room_id],
},
],
},
},
"rooms": {
room_id: {
"name": "Room #0",
"initial": true,
},
},
},
};
let room = room_list.room(room_id).await?;
// The latest event does not exist.
assert!(room.latest_event().await.is_none());
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
assert request = {},
respond with = {
"pos": "0",
"lists": {},
"rooms": {
room_id: {
"timeline": [
timeline_event!("$x0:bar.org" at 0 sec),
],
},
},
},
};
// The latest event exists.
assert_matches!(
room.latest_event().await,
Some(event) => {
assert_eq!(event.event_id(), Some(event_id!("$x0:bar.org")));
}
);
sync_then_assert_request_and_fake_response! {
[server, room_list, sync]
assert request = {},
respond with = {
"pos": "0",
"lists": {},
"rooms": {
room_id: {
"timeline": [
timeline_event!("$x1:bar.org" at 1 sec),
],
},
},
},
};
// The latest event has been updated.
assert_matches!(
room.latest_event().await,
Some(event) => {
assert_eq!(event.event_id(), Some(event_id!("$x1:bar.org")));
}
);
Ok(())
}
#[async_test]
async fn test_input_viewport() -> Result<(), Error> {
let (server, room_list) = new_room_list().await?;

View File

@@ -28,7 +28,7 @@ use wiremock::{
mod read_receipts;
#[cfg(feature = "experimental-sliding-sync")]
mod sliding_sync;
pub(crate) mod sliding_sync;
use crate::{logged_in_client, mock_encryption_state, mock_sync};

View File

@@ -50,6 +50,8 @@ macro_rules! timeline_event {
}
}
pub(crate) use timeline_event;
macro_rules! assert_timeline_stream {
// `--- day divider ---`
( @_ [ $stream:ident ] [ --- day divider --- ; $( $rest:tt )* ] [ $( $accumulator:tt )* ] ) => {
@@ -63,7 +65,12 @@ macro_rules! assert_timeline_stream {
assert_matches!(
$stream.next().now_or_never(),
Some(Some(VectorDiff::PushBack { value })) => {
assert_matches!(value.as_ref(), TimelineItem::Virtual(VirtualTimelineItem::DayDivider(_)));
assert_matches!(
value.as_ref(),
TimelineItem::Virtual(
VirtualTimelineItem::DayDivider(_)
)
);
}
);
}
@@ -148,6 +155,8 @@ macro_rules! assert_timeline_stream {
};
}
pub(crate) use assert_timeline_stream;
async fn new_sliding_sync(lists: Vec<SlidingSyncListBuilder>) -> Result<(MockServer, SlidingSync)> {
let (client, server) = logged_in_client().await;