mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-24 08:36:10 -04:00
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:
@@ -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.
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user