From cfc8effa66a54565c58ad9e0360cf650de609cf6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 16 May 2023 14:12:51 +0200 Subject: [PATCH] Move timeline API into a new crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … aimed at interactive user interfaces. --- Cargo.lock | 32 ++++++- bindings/matrix-sdk-ffi/Cargo.toml | 3 +- bindings/matrix-sdk-ffi/src/lib.rs | 8 +- bindings/matrix-sdk-ffi/src/room.rs | 7 +- bindings/matrix-sdk-ffi/src/sliding_sync.rs | 7 +- bindings/matrix-sdk-ffi/src/timeline.rs | 59 ++++++------ crates/matrix-sdk-ui/Cargo.toml | 33 +++++++ .../src/events.rs | 0 crates/matrix-sdk-ui/src/lib.rs | 18 ++++ .../src}/timeline/builder.rs | 19 ++-- .../src}/timeline/event_handler.rs | 12 +-- .../src}/timeline/event_item/content.rs | 58 +++++------ .../src}/timeline/event_item/local.rs | 2 +- .../src}/timeline/event_item/mod.rs | 4 +- .../src}/timeline/event_item/remote.rs | 6 +- .../src}/timeline/inner.rs | 87 +++++++++++++---- .../src}/timeline/mod.rs | 96 +++++++++++-------- .../src}/timeline/pagination.rs | 0 .../src}/timeline/read_receipts.rs | 2 +- crates/matrix-sdk-ui/src/timeline/room_ext.rs | 35 +++++++ .../src/timeline/sliding_sync_ext.rs | 54 +++++++++++ .../src}/timeline/tests/basic.rs | 4 +- .../src}/timeline/tests/echo.rs | 3 +- .../src}/timeline/tests/edit.rs | 2 +- .../src}/timeline/tests/encryption.rs | 12 +-- .../src}/timeline/tests/invalid.rs | 2 +- .../src}/timeline/tests/mod.rs | 2 +- .../src}/timeline/tests/read_receipts.rs | 0 .../src}/timeline/tests/redaction.rs | 0 .../src}/timeline/tests/virt.rs | 2 +- .../src}/timeline/to_device.rs | 14 +-- .../src}/timeline/virtual_item.rs | 0 .../test/integration/main.rs} | 0 .../test/integration}/timeline/mod.rs | 4 +- .../integration}/timeline/read_receipts.rs | 0 .../integration/timeline/sliding_sync.rs} | 5 +- crates/matrix-sdk/Cargo.toml | 13 +-- crates/matrix-sdk/src/error.rs | 5 - crates/matrix-sdk/src/lib.rs | 2 - crates/matrix-sdk/src/room/common.rs | 12 --- crates/matrix-sdk/src/room/joined.rs | 18 ++-- crates/matrix-sdk/src/room/mod.rs | 2 - crates/matrix-sdk/src/sliding_sync/README.md | 8 +- crates/matrix-sdk/src/sliding_sync/room.rs | 73 ++++---------- crates/matrix-sdk/tests/integration/main.rs | 2 - .../matrix-sdk/tests/integration/room/mod.rs | 1 - examples/timeline/Cargo.toml | 5 +- examples/timeline/src/main.rs | 1 + xtask/src/ci.rs | 2 +- 49 files changed, 443 insertions(+), 293 deletions(-) create mode 100644 crates/matrix-sdk-ui/Cargo.toml rename crates/{matrix-sdk => matrix-sdk-ui}/src/events.rs (100%) create mode 100644 crates/matrix-sdk-ui/src/lib.rs rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/builder.rs (94%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/event_handler.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/event_item/content.rs (92%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/event_item/local.rs (93%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/event_item/mod.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/event_item/remote.rs (94%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/inner.rs (92%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/mod.rs (90%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/pagination.rs (100%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/read_receipts.rs (99%) create mode 100644 crates/matrix-sdk-ui/src/timeline/room_ext.rs create mode 100644 crates/matrix-sdk-ui/src/timeline/sliding_sync_ext.rs rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/basic.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/echo.rs (98%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/edit.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/encryption.rs (98%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/invalid.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/mod.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/read_receipts.rs (100%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/redaction.rs (100%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/tests/virt.rs (99%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/to_device.rs (87%) rename crates/{matrix-sdk/src/room => matrix-sdk-ui/src}/timeline/virtual_item.rs (100%) rename crates/{matrix-sdk/tests/integration/sliding_sync/mod.rs => matrix-sdk-ui/test/integration/main.rs} (100%) rename crates/{matrix-sdk/tests/integration/room => matrix-sdk-ui/test/integration}/timeline/mod.rs (99%) rename crates/{matrix-sdk/tests/integration/room => matrix-sdk-ui/test/integration}/timeline/read_receipts.rs (100%) rename crates/{matrix-sdk/tests/integration/sliding_sync/timeline.rs => matrix-sdk-ui/test/integration/timeline/sliding_sync.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index 575b825b0..a0493d3d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,6 +1546,7 @@ dependencies = [ "clap 4.2.4", "futures", "matrix-sdk", + "matrix-sdk-ui", "tokio", "tracing-subscriber", "url", @@ -2615,7 +2616,6 @@ dependencies = [ "backoff", "bytes", "bytesize", - "chrono", "ctor 0.2.0", "dashmap", "dirs", @@ -2632,7 +2632,6 @@ dependencies = [ "hyper", "image 0.24.6", "imbl", - "indexmap", "matrix-sdk-base", "matrix-sdk-common", "matrix-sdk-indexeddb", @@ -2640,8 +2639,6 @@ dependencies = [ "matrix-sdk-test", "mime", "mime2ext", - "once_cell", - "pin-project-lite", "rand 0.8.5", "reqwest", "ruma", @@ -2861,6 +2858,7 @@ dependencies = [ "futures-util", "log-panics", "matrix-sdk", + "matrix-sdk-ui", "mime", "once_cell", "opentelemetry", @@ -3032,6 +3030,32 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "matrix-sdk-ui" +version = "0.6.0" +dependencies = [ + "anyhow", + "assert_matches", + "async-trait", + "chrono", + "eyeball-im", + "futures-core", + "futures-util", + "imbl", + "indexmap", + "matrix-sdk", + "matrix-sdk-test", + "mime", + "once_cell", + "pin-project-lite", + "ruma", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "memchr" version = "2.5.0" diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index accec54c5..35dce273b 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -28,6 +28,7 @@ eyeball-im = { workspace = true } extension-trait = "1.0.1" futures-core = "0.3.17" futures-util = { version = "0.3.17", default-features = false } +matrix-sdk-ui = { path = "../../crates/matrix-sdk-ui", features = ["experimental-sliding-sync"] } mime = "0.3.16" # FIXME: we currently can't feature flag anything in the api.udl, therefore we must enforce experimental-sliding-sync being exposed here.. # see https://github.com/matrix-org/matrix-rust-sdk/issues/1014 @@ -62,7 +63,6 @@ default-features = false features = [ "anyhow", "experimental-sliding-sync", - "experimental-timeline", "e2e-encryption", "markdown", "socks", @@ -76,7 +76,6 @@ default-features = false features = [ "anyhow", "experimental-sliding-sync", - "experimental-timeline", "e2e-encryption", "markdown", "native-tls", diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 0afcfe36e..5cdb34516 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -45,10 +45,8 @@ use self::{client::Client, error::ClientError}; pub static RUNTIME: Lazy = Lazy::new(|| Runtime::new().expect("Can't start Tokio runtime")); -pub use matrix_sdk::{ - room::timeline::PaginationOutcome, - ruma::{api::client::account::register, UserId}, -}; +pub use matrix_sdk::ruma::{api::client::account::register, UserId}; +pub use matrix_sdk_ui::timeline::PaginationOutcome; pub use platform::*; pub use self::{ @@ -60,5 +58,5 @@ uniffi::include_scaffolding!("api"); #[uniffi::export] pub fn sdk_git_sha() -> String { - env!("VERGEN_GIT_SHA").to_string() + env!("VERGEN_GIT_SHA").to_owned() } diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 39aaab48f..b21838d13 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -11,7 +11,7 @@ use matrix_sdk::{ AttachmentConfig, AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, Thumbnail, }, - room::{timeline::Timeline, Receipts, Room as SdkRoom}, + room::{Receipts, Room as SdkRoom}, ruma::{ api::client::{receipt::create_receipt::v3::ReceiptType, room::report_content}, events::{ @@ -26,6 +26,7 @@ use matrix_sdk::{ }, RoomMemberships, }; +use matrix_sdk_ui::timeline::{RoomExt, Timeline}; use mime::Mime; use tracing::error; @@ -806,9 +807,9 @@ pub enum PaginationOptions { UntilNumItems { event_limit: u16, items: u16 }, } -impl From for matrix_sdk::room::timeline::PaginationOptions<'static> { +impl From for matrix_sdk_ui::timeline::PaginationOptions<'static> { fn from(value: PaginationOptions) -> Self { - use matrix_sdk::room::timeline::PaginationOptions as Opts; + use matrix_sdk_ui::timeline::PaginationOptions as Opts; match value { PaginationOptions::SingleRequest { event_limit } => Opts::single_request(event_limit), PaginationOptions::UntilNumItems { event_limit, items } => { diff --git a/bindings/matrix-sdk-ffi/src/sliding_sync.rs b/bindings/matrix-sdk-ffi/src/sliding_sync.rs index 0e1a9d1e7..7665e6927 100644 --- a/bindings/matrix-sdk-ffi/src/sliding_sync.rs +++ b/bindings/matrix-sdk-ffi/src/sliding_sync.rs @@ -11,10 +11,11 @@ use matrix_sdk::ruma::{ assign, IdParseError, OwnedRoomId, RoomId, }; pub use matrix_sdk::{ - room::timeline::Timeline, ruma::api::client::sync::sync_events::v4::SyncRequestListFilters, - Client as MatrixClient, LoopCtrl, RoomListEntry as MatrixRoomEntry, - SlidingSyncBuilder as MatrixSlidingSyncBuilder, SlidingSyncMode, SlidingSyncState, + ruma::api::client::sync::sync_events::v4::SyncRequestListFilters, Client as MatrixClient, + LoopCtrl, RoomListEntry as MatrixRoomEntry, SlidingSyncBuilder as MatrixSlidingSyncBuilder, + SlidingSyncMode, SlidingSyncState, }; +use matrix_sdk_ui::timeline::SlidingSyncRoomExt; use tokio::task::JoinHandle; use tracing::{debug, error, warn}; use url::Url; diff --git a/bindings/matrix-sdk-ffi/src/timeline.rs b/bindings/matrix-sdk-ffi/src/timeline.rs index f72440da8..bee61308b 100644 --- a/bindings/matrix-sdk-ffi/src/timeline.rs +++ b/bindings/matrix-sdk-ffi/src/timeline.rs @@ -3,11 +3,11 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use anyhow::bail; use extension_trait::extension_trait; use eyeball_im::VectorDiff; -pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource}; -use matrix_sdk::{ - attachment::{BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo}, - room::timeline::{Profile, TimelineDetails}, +use matrix_sdk::attachment::{ + BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, }; +pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource}; +use matrix_sdk_ui::timeline::{Profile, TimelineDetails}; use ruma::UInt; use tracing::warn; @@ -45,7 +45,7 @@ pub enum TimelineDiff { } impl TimelineDiff { - pub(crate) fn new(inner: VectorDiff>) -> Self { + pub(crate) fn new(inner: VectorDiff>) -> Self { match inner { VectorDiff::Append { values } => { Self::Append { values: values.into_iter().map(TimelineItem::from_arc).collect() } @@ -184,10 +184,10 @@ pub enum TimelineChange { #[repr(transparent)] #[derive(Clone)] -pub struct TimelineItem(pub(crate) matrix_sdk::room::timeline::TimelineItem); +pub struct TimelineItem(pub(crate) matrix_sdk_ui::timeline::TimelineItem); impl TimelineItem { - pub(crate) fn from_arc(arc: Arc) -> Arc { + pub(crate) fn from_arc(arc: Arc) -> Arc { // SAFETY: This is valid because Self is a repr(transparent) wrapper // around the other Timeline type. unsafe { Arc::from_raw(Arc::into_raw(arc) as _) } @@ -197,14 +197,14 @@ impl TimelineItem { #[uniffi::export] impl TimelineItem { pub fn as_event(self: Arc) -> Option> { - use matrix_sdk::room::timeline::TimelineItem as Item; + use matrix_sdk_ui::timeline::TimelineItem as Item; unwrap_or_clone_arc_into_variant!(self, .0, Item::Event(evt) => { Arc::new(EventTimelineItem(evt)) }) } pub fn as_virtual(self: Arc) -> Option { - use matrix_sdk::room::timeline::{TimelineItem as Item, VirtualTimelineItem as VItem}; + 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() }) @@ -233,9 +233,9 @@ pub enum EventSendState { Sent { event_id: String }, } -impl From<&matrix_sdk::room::timeline::EventSendState> for EventSendState { - fn from(value: &matrix_sdk::room::timeline::EventSendState) -> Self { - use matrix_sdk::room::timeline::EventSendState::*; +impl From<&matrix_sdk_ui::timeline::EventSendState> for EventSendState { + fn from(value: &matrix_sdk_ui::timeline::EventSendState) -> Self { + use matrix_sdk_ui::timeline::EventSendState::*; match value { NotSentYet => Self::NotSentYet, @@ -246,7 +246,7 @@ impl From<&matrix_sdk::room::timeline::EventSendState> for EventSendState { } #[derive(uniffi::Object)] -pub struct EventTimelineItem(pub(crate) matrix_sdk::room::timeline::EventTimelineItem); +pub struct EventTimelineItem(pub(crate) matrix_sdk_ui::timeline::EventTimelineItem); #[uniffi::export] impl EventTimelineItem { @@ -357,12 +357,12 @@ impl From<&TimelineDetails> for ProfileDetails { } #[derive(Clone, uniffi::Object)] -pub struct TimelineItemContent(matrix_sdk::room::timeline::TimelineItemContent); +pub struct TimelineItemContent(matrix_sdk_ui::timeline::TimelineItemContent); #[uniffi::export] impl TimelineItemContent { pub fn kind(&self) -> TimelineItemContentKind { - use matrix_sdk::room::timeline::TimelineItemContent as Content; + use matrix_sdk_ui::timeline::TimelineItemContent as Content; match &self.0 { Content::Message(_) => TimelineItemContentKind::Message, @@ -424,7 +424,7 @@ impl TimelineItemContent { } pub fn as_message(self: Arc) -> Option> { - use matrix_sdk::room::timeline::TimelineItemContent as Content; + use matrix_sdk_ui::timeline::TimelineItemContent as Content; unwrap_or_clone_arc_into_variant!(self, .0, Content::Message(msg) => Arc::new(Message(msg))) } } @@ -467,7 +467,7 @@ pub enum TimelineItemContentKind { } #[derive(Clone, uniffi::Object)] -pub struct Message(matrix_sdk::room::timeline::Message); +pub struct Message(matrix_sdk_ui::timeline::Message); #[uniffi::export] impl Message { @@ -838,8 +838,8 @@ pub struct InReplyToDetails { event: RepliedToEventDetails, } -impl From<&matrix_sdk::room::timeline::InReplyToDetails> for InReplyToDetails { - fn from(inner: &matrix_sdk::room::timeline::InReplyToDetails) -> Self { +impl From<&matrix_sdk_ui::timeline::InReplyToDetails> for InReplyToDetails { + fn from(inner: &matrix_sdk_ui::timeline::InReplyToDetails) -> Self { let event_id = inner.event_id.to_string(); let event = match &inner.event { TimelineDetails::Unavailable => RepliedToEventDetails::Unavailable, @@ -882,8 +882,8 @@ pub enum EncryptedMessage { } impl EncryptedMessage { - fn new(msg: &matrix_sdk::room::timeline::EncryptedMessage) -> Self { - use matrix_sdk::room::timeline::EncryptedMessage as Message; + fn new(msg: &matrix_sdk_ui::timeline::EncryptedMessage) -> Self { + use matrix_sdk_ui::timeline::EncryptedMessage as Message; match msg { Message::OlmV1Curve25519AesSha2 { sender_key } => { @@ -975,9 +975,9 @@ pub enum MembershipChange { NotImplemented, } -impl From for MembershipChange { - fn from(membership_change: matrix_sdk::room::timeline::MembershipChange) -> Self { - use matrix_sdk::room::timeline::MembershipChange as Change; +impl From for MembershipChange { + fn from(membership_change: matrix_sdk_ui::timeline::MembershipChange) -> Self { + use matrix_sdk_ui::timeline::MembershipChange as Change; match membership_change { Change::None => Self::None, Change::Error => Self::Error, @@ -1025,12 +1025,11 @@ pub enum OtherState { Custom { event_type: String }, } -impl From<&matrix_sdk::room::timeline::AnyOtherFullStateEventContent> for OtherState { - fn from(content: &matrix_sdk::room::timeline::AnyOtherFullStateEventContent) -> Self { - use matrix_sdk::{ - room::timeline::AnyOtherFullStateEventContent as Content, - ruma::events::FullStateEventContent as FullContent, - }; +impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherState { + fn from(content: &matrix_sdk_ui::timeline::AnyOtherFullStateEventContent) -> Self { + use matrix_sdk::ruma::events::FullStateEventContent as FullContent; + use matrix_sdk_ui::timeline::AnyOtherFullStateEventContent as Content; + match content { Content::PolicyRuleRoom(_) => Self::PolicyRuleRoom, Content::PolicyRuleServer(_) => Self::PolicyRuleServer, diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml new file mode 100644 index 000000000..8dbbbcf01 --- /dev/null +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "matrix-sdk-ui" +version = "0.6.0" +edition = "2021" + +[features] +default = ["e2e-encryption"] +e2e-encryption = ["matrix-sdk/e2e-encryption"] +experimental-sliding-sync = ["matrix-sdk/experimental-sliding-sync"] + +[dependencies] +async-trait = { workspace = true } +chrono = "0.4.23" +eyeball-im = { workspace = true } +futures-core = "0.3.21" +futures-util = { workspace = true } +imbl = { version = "2.0.0", features = ["serde"] } +indexmap = "1.9.1" +matrix-sdk = { version = "0.6.2", path = "../matrix-sdk", default-features = false } +mime = "0.3.16" +once_cell = { workspace = true } +pin-project-lite = "0.2.9" +ruma = { workspace = true, features = ["unstable-msc2677", "unstable-sanitize"] } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true, features = ["attributes"] } + +[dev-dependencies] +anyhow = { workspace = true } +assert_matches = { workspace = true } +matrix-sdk-test = { version = "0.6.0", path = "../../testing/matrix-sdk-test" } diff --git a/crates/matrix-sdk/src/events.rs b/crates/matrix-sdk-ui/src/events.rs similarity index 100% rename from crates/matrix-sdk/src/events.rs rename to crates/matrix-sdk-ui/src/events.rs diff --git a/crates/matrix-sdk-ui/src/lib.rs b/crates/matrix-sdk-ui/src/lib.rs new file mode 100644 index 000000000..6635ce881 --- /dev/null +++ b/crates/matrix-sdk-ui/src/lib.rs @@ -0,0 +1,18 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod events; +pub mod timeline; + +pub use self::timeline::Timeline; diff --git a/crates/matrix-sdk/src/room/timeline/builder.rs b/crates/matrix-sdk-ui/src/timeline/builder.rs similarity index 94% rename from crates/matrix-sdk/src/room/timeline/builder.rs rename to crates/matrix-sdk-ui/src/timeline/builder.rs index e5af68703..d68e73780 100644 --- a/crates/matrix-sdk/src/room/timeline/builder.rs +++ b/crates/matrix-sdk-ui/src/timeline/builder.rs @@ -15,7 +15,10 @@ use std::sync::Arc; use imbl::Vector; -use matrix_sdk_base::deserialized_responses::{EncryptionInfo, SyncTimelineEvent}; +use matrix_sdk::{ + deserialized_responses::{EncryptionInfo, SyncTimelineEvent}, + room, +}; use ruma::{ events::receipt::{ReceiptThread, ReceiptType, SyncReceiptEvent}, push::Action, @@ -26,7 +29,6 @@ use tracing::error; #[cfg(feature = "e2e-encryption")] use super::to_device::{handle_forwarded_room_key_event, handle_room_key_event}; use super::{inner::TimelineInner, Timeline, TimelineEventHandlerHandles}; -use crate::room; /// Builder that allows creating and configuring various parts of a /// [`Timeline`]. @@ -120,6 +122,7 @@ impl TimelineBuilder { let inner = Arc::new(inner); let room = inner.room(); + let client = room.client(); let timeline_event_handle = room.add_event_handler({ let inner = inner.clone(); @@ -133,14 +136,15 @@ impl TimelineBuilder { // Not using room.add_event_handler here because RoomKey events are // to-device events that are not received in the context of a room. + #[cfg(feature = "e2e-encryption")] - let room_key_handle = room - .client + let room_key_handle = client .add_event_handler(handle_room_key_event(inner.clone(), room.room_id().to_owned())); #[cfg(feature = "e2e-encryption")] - let forwarded_room_key_handle = room.client.add_event_handler( - handle_forwarded_room_key_event(inner.clone(), room.room_id().to_owned()), - ); + let forwarded_room_key_handle = client.add_event_handler(handle_forwarded_room_key_event( + inner.clone(), + room.room_id().to_owned(), + )); let mut handles = vec![ timeline_event_handle, @@ -176,7 +180,6 @@ impl TimelineBuilder { handles.push(read_receipts_handle); } - let client = room.client.clone(); let timeline = Timeline { inner, start_token: Mutex::new(prev_token), diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk-ui/src/timeline/event_handler.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/event_handler.rs rename to crates/matrix-sdk-ui/src/timeline/event_handler.rs index 101ba44d9..b9bfbd2e3 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_handler.rs @@ -17,7 +17,7 @@ use std::{collections::HashMap, sync::Arc}; use chrono::{Datelike, Local, TimeZone}; use eyeball_im::{ObservableVector, Vector}; use indexmap::{map::Entry, IndexMap, IndexSet}; -use matrix_sdk_base::deserialized_responses::EncryptionInfo; +use matrix_sdk::deserialized_responses::EncryptionInfo; use ruma::{ events::{ reaction::ReactionEventContent, @@ -49,13 +49,11 @@ use super::{ }, find_read_marker, read_receipts::maybe_add_implicit_read_receipt, - rfind_event_by_id, rfind_event_item, EventTimelineItem, Message, ReactionGroup, - TimelineDetails, TimelineInnerState, TimelineItem, TimelineItemContent, VirtualTimelineItem, -}; -use crate::{ - events::SyncTimelineEventWithoutContent, - room::timeline::{MembershipChange, DEFAULT_SANITIZER_MODE}, + rfind_event_by_id, rfind_event_item, EventTimelineItem, MembershipChange, Message, + ReactionGroup, TimelineDetails, TimelineInnerState, TimelineItem, TimelineItemContent, + VirtualTimelineItem, DEFAULT_SANITIZER_MODE, }; +use crate::events::SyncTimelineEventWithoutContent; pub(super) enum Flow { Local { diff --git a/crates/matrix-sdk/src/room/timeline/event_item/content.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content.rs similarity index 92% rename from crates/matrix-sdk/src/room/timeline/event_item/content.rs rename to crates/matrix-sdk-ui/src/timeline/event_item/content.rs index 88a4a3cea..72b355497 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item/content.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content.rs @@ -2,7 +2,7 @@ use std::{fmt, ops::Deref, sync::Arc}; use imbl::{vector, Vector}; use indexmap::IndexMap; -use matrix_sdk_base::deserialized_responses::TimelineEvent; +use matrix_sdk::{deserialized_responses::TimelineEvent, Result}; use ruma::{ events::{ policy::rule::{ @@ -43,11 +43,8 @@ use ruma::{ use tracing::{debug, error}; use super::{EventTimelineItem, Profile, TimelineDetails}; -use crate::{ - room::timeline::{ - inner::RoomDataProvider, Error as TimelineError, TimelineItem, DEFAULT_SANITIZER_MODE, - }, - Result, +use crate::timeline::{ + inner::RoomDataProvider, Error as TimelineError, TimelineItem, DEFAULT_SANITIZER_MODE, }; /// The content of an [`EventTimelineItem`][super::EventTimelineItem]. @@ -123,14 +120,14 @@ impl TimelineItemContent { /// An `m.room.message` event or extensible event, including edits. #[derive(Clone)] pub struct Message { - pub(in crate::room::timeline) msgtype: MessageType, - pub(in crate::room::timeline) in_reply_to: Option, - pub(in crate::room::timeline) edited: bool, + pub(in crate::timeline) msgtype: MessageType, + pub(in crate::timeline) in_reply_to: Option, + pub(in crate::timeline) edited: bool, } impl Message { /// Construct a `Message` from a `m.room.message` event. - pub(in crate::room::timeline) fn from_event( + pub(in crate::timeline) fn from_event( c: RoomMessageEventContent, relations: BundledMessageLikeRelations, timeline_items: &Vector>, @@ -216,10 +213,7 @@ impl Message { self.edited } - pub(in crate::room::timeline) fn with_in_reply_to( - &self, - in_reply_to: InReplyToDetails, - ) -> Self { + pub(in crate::timeline) fn with_in_reply_to(&self, in_reply_to: InReplyToDetails) -> Self { Self { in_reply_to: Some(in_reply_to), ..self.clone() } } } @@ -248,16 +242,16 @@ pub struct InReplyToDetails { /// Use [`Timeline::fetch_details_for_event`] to fetch the data if it is /// unavailable. /// - /// [`Timeline::fetch_details_for_event`]: crate::room::timeline::Timeline::fetch_details_for_event + /// [`Timeline::fetch_details_for_event`]: crate::Timeline::fetch_details_for_event pub event: TimelineDetails>, } /// An event that is replied to. #[derive(Clone, Debug)] pub struct RepliedToEvent { - pub(in crate::room::timeline) message: Message, - pub(in crate::room::timeline) sender: OwnedUserId, - pub(in crate::room::timeline) sender_profile: TimelineDetails, + pub(in crate::timeline) message: Message, + pub(in crate::timeline) sender: OwnedUserId, + pub(in crate::timeline) sender_profile: TimelineDetails, } impl RepliedToEvent { @@ -293,19 +287,19 @@ impl RepliedToEvent { }) } - pub(in crate::room::timeline) async fn try_from_timeline_event( + pub(in crate::timeline) async fn try_from_timeline_event( timeline_event: TimelineEvent, room_data_provider: &P, - ) -> Result { + ) -> Result { let event = match timeline_event.event.deserialize() { Ok(AnyTimelineEvent::MessageLike(event)) => event, _ => { - return Err(TimelineError::UnsupportedEvent.into()); + return Err(TimelineError::UnsupportedEvent); } }; let Some(AnyMessageLikeEventContent::RoomMessage(c)) = event.original_content() else { - return Err(TimelineError::UnsupportedEvent.into()); + return Err(TimelineError::UnsupportedEvent); }; let message = Message::from_event(c, event.relations(), &vector![]); @@ -376,7 +370,7 @@ type ReactionGroupInner = IndexMap<(Option, Option, - pub(in crate::room::timeline) change: Option, + pub(in crate::timeline) user_id: OwnedUserId, + pub(in crate::timeline) content: FullStateEventContent, + pub(in crate::timeline) change: Option, } impl RoomMembershipChange { @@ -498,9 +492,9 @@ pub enum MembershipChange { /// membership is already `join`. #[derive(Clone, Debug)] pub struct MemberProfileChange { - pub(in crate::room::timeline) user_id: OwnedUserId, - pub(in crate::room::timeline) displayname_change: Option>>, - pub(in crate::room::timeline) avatar_url_change: Option>>, + pub(in crate::timeline) user_id: OwnedUserId, + pub(in crate::timeline) displayname_change: Option>>, + pub(in crate::timeline) avatar_url_change: Option>>, } impl MemberProfileChange { @@ -654,8 +648,8 @@ impl AnyOtherFullStateEventContent { /// A state event that doesn't have its own variant. #[derive(Clone, Debug)] pub struct OtherState { - pub(in crate::room::timeline) state_key: String, - pub(in crate::room::timeline) content: AnyOtherFullStateEventContent, + pub(in crate::timeline) state_key: String, + pub(in crate::timeline) content: AnyOtherFullStateEventContent, } impl OtherState { diff --git a/crates/matrix-sdk/src/room/timeline/event_item/local.rs b/crates/matrix-sdk-ui/src/timeline/event_item/local.rs similarity index 93% rename from crates/matrix-sdk/src/room/timeline/event_item/local.rs rename to crates/matrix-sdk-ui/src/timeline/event_item/local.rs index 130f0ace3..44058792e 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item/local.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/local.rs @@ -5,7 +5,7 @@ use super::EventSendState; /// An item for an event that was created locally and not yet echoed back by /// the homeserver. #[derive(Debug, Clone)] -pub(in crate::room::timeline) struct LocalEventTimelineItem { +pub(in crate::timeline) struct LocalEventTimelineItem { /// The send state of this local event. pub send_state: EventSendState, /// The transaction ID. diff --git a/crates/matrix-sdk/src/room/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/event_item/mod.rs rename to crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 0626dcf7a..910ebb740 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use indexmap::IndexMap; -use matrix_sdk_base::deserialized_responses::EncryptionInfo; +use matrix_sdk::{deserialized_responses::EncryptionInfo, Error}; use once_cell::sync::Lazy; use ruma::{ events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent}, @@ -24,8 +24,6 @@ use ruma::{ UserId, }; -use crate::Error; - mod content; mod local; mod remote; diff --git a/crates/matrix-sdk/src/room/timeline/event_item/remote.rs b/crates/matrix-sdk-ui/src/timeline/event_item/remote.rs similarity index 94% rename from crates/matrix-sdk/src/room/timeline/event_item/remote.rs rename to crates/matrix-sdk-ui/src/timeline/event_item/remote.rs index d506ef0d5..411cf0087 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item/remote.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/remote.rs @@ -1,7 +1,7 @@ use std::fmt; use indexmap::IndexMap; -use matrix_sdk_base::deserialized_responses::EncryptionInfo; +use matrix_sdk::deserialized_responses::EncryptionInfo; use ruma::{ events::{receipt::Receipt, AnySyncTimelineEvent}, serde::Raw, @@ -12,7 +12,7 @@ use super::BundledReactions; /// An item for an event that was received from the homeserver. #[derive(Clone)] -pub(in crate::room::timeline) struct RemoteEventTimelineItem { +pub(in crate::timeline) struct RemoteEventTimelineItem { /// The event ID. pub event_id: OwnedEventId, /// All bundled reactions about the event. @@ -66,7 +66,7 @@ impl RemoteEventTimelineItem { /// Where we got an event from. #[derive(Clone, Copy, Debug)] -pub(in crate::room::timeline) enum RemoteEventOrigin { +pub(in crate::timeline) enum RemoteEventOrigin { /// The event came from a cache. Cache, /// The event came from a sync response. diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk-ui/src/timeline/inner.rs similarity index 92% rename from crates/matrix-sdk/src/room/timeline/inner.rs rename to crates/matrix-sdk-ui/src/timeline/inner.rs index 3824e3567..37c2a63c8 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner.rs @@ -20,10 +20,13 @@ use async_trait::async_trait; use eyeball_im::{ObservableVector, VectorSubscriber}; use imbl::Vector; use indexmap::{IndexMap, IndexSet}; -#[cfg(feature = "e2e-encryption")] -use matrix_sdk_base::crypto::OlmMachine; -use matrix_sdk_base::deserialized_responses::{EncryptionInfo, SyncTimelineEvent, TimelineEvent}; -#[cfg(feature = "e2e-encryption")] +#[cfg(all(test, feature = "e2e-encryption"))] +use matrix_sdk::crypto::OlmMachine; +use matrix_sdk::{ + deserialized_responses::{EncryptionInfo, SyncTimelineEvent, TimelineEvent}, + room, Error, Result, +}; +#[cfg(all(test, feature = "e2e-encryption"))] use ruma::RoomId; use ruma::{ api::client::receipt::create_receipt::v3::ReceiptType as SendReceiptType, @@ -57,7 +60,7 @@ use super::{ Message, Profile, RelativePosition, RepliedToEvent, TimelineDetails, TimelineItem, TimelineItemContent, }; -use crate::{events::SyncTimelineEventWithoutContent, room, Error, Result}; +use crate::events::SyncTimelineEventWithoutContent; #[derive(Debug)] pub(super) struct TimelineInner { @@ -347,12 +350,30 @@ impl TimelineInner

{ } #[cfg(feature = "e2e-encryption")] - #[instrument(skip(self, olm_machine))] + #[instrument(skip(self, room), fields(room_id = ?room.room_id()))] pub(super) async fn retry_event_decryption( + &self, + room: &room::Common, + session_ids: Option>, + ) { + self.retry_event_decryption_inner(room, session_ids).await + } + + #[cfg(all(test, feature = "e2e-encryption"))] + pub(super) async fn retry_event_decryption_test( &self, room_id: &RoomId, olm_machine: &OlmMachine, session_ids: Option>, + ) { + self.retry_event_decryption_inner((olm_machine, room_id), session_ids).await + } + + #[cfg(feature = "e2e-encryption")] + async fn retry_event_decryption_inner( + &self, + decryptor: impl Decryptor, + session_ids: Option>, ) { use super::EncryptedMessage; @@ -392,8 +413,7 @@ impl TimelineInner

{ tracing::Span::current().record("event_id", debug(&remote_event.event_id)); - let raw = remote_event.original_json.cast_ref(); - match olm_machine.decrypt_room_event(raw, room_id).await { + match decryptor.decrypt_event_impl(&remote_event.original_json).await { Ok(event) => { trace!("Successfully decrypted event that previously failed to decrypt"); Some(event) @@ -544,7 +564,10 @@ impl TimelineInner { } #[instrument(skip(self))] - pub(super) async fn fetch_in_reply_to_details(&self, event_id: &EventId) -> Result<()> { + pub(super) async fn fetch_in_reply_to_details( + &self, + event_id: &EventId, + ) -> Result<(), super::Error> { let state = self.state.lock().await; let (index, item) = rfind_event_by_id(&state.items, event_id) .ok_or(super::Error::RemoteEventNotInTimeline)?; @@ -572,7 +595,7 @@ impl TimelineInner { &in_reply_to.event_id, self.room(), ) - .await; + .await?; // We need to be sure to have the latest position of the event as it might have // changed while waiting for the request. @@ -686,7 +709,7 @@ async fn fetch_replied_to_event( message: &Message, in_reply_to: &EventId, room: &room::Common, -) -> TimelineDetails> { +) -> Result>, super::Error> { if let Some((_, item)) = rfind_event_by_id(&state.items, in_reply_to) { let details = match item.content() { TimelineItemContent::Message(message) => { @@ -696,11 +719,11 @@ async fn fetch_replied_to_event( sender_profile: item.sender_profile().clone(), })) } - _ => TimelineDetails::Error(Arc::new(super::Error::UnsupportedEvent.into())), + _ => return Err(super::Error::UnsupportedEvent), }; debug!("Found replied-to event locally"); - return details; + return Ok(details); }; trace!("Setting in-reply-to details to pending"); @@ -715,15 +738,13 @@ async fn fetch_replied_to_event( drop(state); trace!("Fetching replied-to event"); - match room.event(in_reply_to).await { - Ok(timeline_event) => { - match RepliedToEvent::try_from_timeline_event(timeline_event, room).await { - Ok(event) => TimelineDetails::Ready(Box::new(event)), - Err(e) => TimelineDetails::Error(Arc::new(e)), - } - } + let res = match room.event(in_reply_to).await { + Ok(timeline_event) => TimelineDetails::Ready(Box::new( + RepliedToEvent::try_from_timeline_event(timeline_event, room).await?, + )), Err(e) => TimelineDetails::Error(Arc::new(e)), - } + }; + Ok(res) } #[async_trait] @@ -850,3 +871,27 @@ async fn handle_remote_event( TimelineEventHandler::new(event_meta, flow, timeline_state, track_read_receipts) .handle_event(event_kind) } + +// Internal helper to make most of retry_event_decryption independent of a room +// object, which is annoying to create for testing and not really needed +#[async_trait] +trait Decryptor: Copy { + async fn decrypt_event_impl(&self, raw: &Raw) -> Result; +} + +#[async_trait] +impl Decryptor for &room::Common { + async fn decrypt_event_impl(&self, raw: &Raw) -> Result { + self.decrypt_event(raw.cast_ref()).await + } +} + +#[cfg(all(test, feature = "e2e-encryption"))] +#[async_trait] +impl Decryptor for (&OlmMachine, &RoomId) { + async fn decrypt_event_impl(&self, raw: &Raw) -> Result { + let (olm_machine, room_id) = self; + let event = olm_machine.decrypt_room_event(raw.cast_ref(), room_id).await?; + Ok(event) + } +} diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs similarity index 90% rename from crates/matrix-sdk/src/room/timeline/mod.rs rename to crates/matrix-sdk-ui/src/timeline/mod.rs index ae5a440d0..44075bf14 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -22,6 +22,12 @@ use eyeball_im::{VectorDiff, VectorSubscriber}; use futures_core::Stream; use futures_util::TryFutureExt; use imbl::Vector; +use matrix_sdk::{ + attachment::AttachmentConfig, + event_handler::EventHandlerHandle, + room::{self, MessagesOptions, Receipts, Room}, + Client, Result, +}; use mime::Mime; use pin_project_lite::pin_project; use ruma::{ @@ -38,20 +44,15 @@ use thiserror::Error; use tokio::sync::Mutex; use tracing::{error, instrument, warn}; -use super::{Joined, Receipts}; -use crate::{ - attachment::AttachmentConfig, - event_handler::EventHandlerHandle, - room::{self, MessagesOptions}, - Client, Result, -}; - mod builder; mod event_handler; mod event_item; mod inner; mod pagination; mod read_receipts; +mod room_ext; +#[cfg(feature = "experimental-sliding-sync")] +mod sliding_sync_ext; #[cfg(test)] mod tests; #[cfg(feature = "e2e-encryption")] @@ -60,6 +61,8 @@ mod virtual_item; pub(crate) use self::builder::TimelineBuilder; use self::inner::{TimelineInner, TimelineInnerState}; +#[cfg(feature = "experimental-sliding-sync")] +pub use self::sliding_sync_ext::SlidingSyncRoomExt; pub use self::{ event_item::{ AnyOtherFullStateEventContent, BundledReactions, EncryptedMessage, EventSendState, @@ -68,6 +71,7 @@ pub use self::{ TimelineDetails, TimelineItemContent, }, pagination::{PaginationOptions, PaginationOutcome}, + room_ext::RoomExt, virtual_item::VirtualTimelineItem, }; @@ -180,10 +184,8 @@ impl Timeline { /// /// ```no_run /// # use std::{path::PathBuf, time::Duration}; - /// # use matrix_sdk::{ - /// # Client, config::SyncSettings, - /// # room::timeline::Timeline, ruma::room_id, - /// # }; + /// # use matrix_sdk::{Client, config::SyncSettings, ruma::room_id}; + /// # use matrix_sdk_ui::Timeline; /// # async { /// # let mut client: Client = todo!(); /// # let room_id = ruma::room_id!("!example:example.org"); @@ -206,8 +208,7 @@ impl Timeline { ) { self.inner .retry_event_decryption( - self.room().room_id(), - self.room().client.olm_machine().expect("Olm machine wasn't started"), + self.room(), Some(session_ids.into_iter().map(AsRef::as_ref).collect()), ) .await; @@ -216,13 +217,7 @@ impl Timeline { #[cfg(feature = "e2e-encryption")] #[tracing::instrument(skip(self))] async fn retry_decryption_for_all_events(&self) { - self.inner - .retry_event_decryption( - self.room().room_id(), - self.room().client.olm_machine().expect("Olm machine wasn't started"), - None, - ) - .await; + self.inner.retry_event_decryption(self.room(), None).await; } /// Get the current list of timeline items. Do not use this in production! @@ -282,16 +277,23 @@ impl Timeline { let txn_id = txn_id.map_or_else(TransactionId::new, ToOwned::to_owned); self.inner.handle_local_event(txn_id.clone(), content.clone()).await; - // If this room isn't actually in joined state, we'll get a server error. - // Not ideal, but works for now. - let room = Joined { inner: self.room().clone() }; + let send_state = match Room::from(self.room().clone()) { + Room::Joined(room) => { + let response = room.send(content, Some(&txn_id)).await; - let response = room.send(content, Some(&txn_id)).await; - - let send_state = match response { - Ok(response) => EventSendState::Sent { event_id: response.event_id }, - Err(error) => EventSendState::SendingFailed { error: Arc::new(error) }, + match response { + Ok(response) => EventSendState::Sent { event_id: response.event_id }, + Err(error) => EventSendState::SendingFailed { error: Arc::new(error) }, + } + } + _ => { + EventSendState::SendingFailed { + // FIXME: Probably not exactly right + error: Arc::new(matrix_sdk::Error::InconsistentState), + } + } }; + self.inner.update_event_send_state(&txn_id, send_state).await; } @@ -316,9 +318,9 @@ impl Timeline { mime_type: Mime, config: AttachmentConfig, ) -> Result<(), Error> { - // If this room isn't actually in joined state, we'll get a server error. - // Not ideal, but works for now. - let room = Joined { inner: self.room().clone() }; + let Room::Joined(room) = Room::from(self.room().clone()) else { + return Err(Error::RoomNotJoined); + }; let body = Path::new(&url).file_name().ok_or(Error::InvalidAttachmentFileName)?.to_str().unwrap(); @@ -351,7 +353,7 @@ impl Timeline { /// echo in the timeline, or if the event is removed from the timeline /// before all requests are handled. #[instrument(skip(self), fields(room_id = ?self.room().room_id()))] - pub async fn fetch_details_for_event(&self, event_id: &EventId) -> Result<()> { + pub async fn fetch_details_for_event(&self, event_id: &EventId) -> Result<(), Error> { self.inner.fetch_in_reply_to_details(event_id).await } @@ -377,7 +379,7 @@ impl Timeline { /// Get the latest read receipt for the given user. /// - /// Contrary to [`Common::user_receipt()`](super::Common::user_receipt) that + /// Contrary to [`Common::user_receipt()`](room::Common::user_receipt) that /// only keeps track of read receipts received from the homeserver, this /// keeps also track of implicit read receipts in this timeline, i.e. /// when a room member sends an event. @@ -394,6 +396,8 @@ impl Timeline { /// This uses [`Joined::send_single_receipt`] internally, but checks /// first if the receipt points to an event in this timeline that is more /// recent than the current ones, to avoid unnecessary requests. + /// + /// [`Joined::send_single_receipt`]: room::Joined::send_single_receipt #[instrument(skip(self))] pub async fn send_single_receipt( &self, @@ -405,9 +409,10 @@ impl Timeline { return Ok(()); } - // If this room isn't actually in joined state, we'll get a server error. - // Not ideal, but works for now. - let room = Joined { inner: self.room().clone() }; + let Room::Joined(room) = Room::from(self.room().clone()) else { + // FIXME: Probably not exactly right + return Err(matrix_sdk::Error::InconsistentState); + }; room.send_single_receipt(receipt_type, thread, event_id).await } @@ -417,6 +422,8 @@ impl Timeline { /// This uses [`Joined::send_multiple_receipts`] internally, but checks /// first if the receipts point to events in this timeline that are more /// recent than the current ones, to avoid unnecessary requests. + /// + /// [`Joined::send_multiple_receipts`]: room::Joined::send_multiple_receipts #[instrument(skip(self))] pub async fn send_multiple_receipts(&self, mut receipts: Receipts) -> Result<()> { if let Some(fully_read) = &receipts.fully_read { @@ -433,13 +440,13 @@ impl Timeline { } } - if let Some(read_receipt) = &receipts.read_receipt { + if let Some(read_receipt) = &receipts.public_read_receipt { if !self .inner .should_send_receipt(&ReceiptType::Read, &ReceiptThread::Unthreaded, read_receipt) .await { - receipts.read_receipt = None; + receipts.public_read_receipt = None; } } @@ -457,9 +464,10 @@ impl Timeline { } } - // If this room isn't actually in joined state, we'll get a server error. - // Not ideal, but works for now. - let room = Joined { inner: self.room().clone() }; + let Room::Joined(room) = Room::from(self.room().clone()) else { + // FIXME: Probably not exactly right + return Err(matrix_sdk::Error::InconsistentState); + }; room.send_multiple_receipts(receipts).await } @@ -633,6 +641,10 @@ pub enum Error { /// The attachment could not be sent #[error("Failed sending attachment")] FailedSendingAttachment, + + /// The room is not in a joined state. + #[error("Room is not joined")] + RoomNotJoined, } /// Result of comparing events position in the timeline. diff --git a/crates/matrix-sdk/src/room/timeline/pagination.rs b/crates/matrix-sdk-ui/src/timeline/pagination.rs similarity index 100% rename from crates/matrix-sdk/src/room/timeline/pagination.rs rename to crates/matrix-sdk-ui/src/timeline/pagination.rs diff --git a/crates/matrix-sdk/src/room/timeline/read_receipts.rs b/crates/matrix-sdk-ui/src/timeline/read_receipts.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/read_receipts.rs rename to crates/matrix-sdk-ui/src/timeline/read_receipts.rs index 4b1574f45..9c5324133 100644 --- a/crates/matrix-sdk/src/room/timeline/read_receipts.rs +++ b/crates/matrix-sdk-ui/src/timeline/read_receipts.rs @@ -16,6 +16,7 @@ use std::{collections::HashMap, sync::Arc}; use eyeball_im::ObservableVector; use indexmap::IndexMap; +use matrix_sdk::room; use ruma::{ events::receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType}, EventId, OwnedEventId, OwnedUserId, UserId, @@ -28,7 +29,6 @@ use super::{ inner::{RoomDataProvider, TimelineInnerState}, rfind_event_by_id, EventTimelineItem, RelativePosition, TimelineItem, }; -use crate::room; struct FullReceipt<'a> { event_id: &'a EventId, diff --git a/crates/matrix-sdk-ui/src/timeline/room_ext.rs b/crates/matrix-sdk-ui/src/timeline/room_ext.rs new file mode 100644 index 000000000..d562a1c5f --- /dev/null +++ b/crates/matrix-sdk-ui/src/timeline/room_ext.rs @@ -0,0 +1,35 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_trait::async_trait; +use matrix_sdk::room; + +use crate::timeline::Timeline; + +#[async_trait] +pub trait RoomExt { + /// Get a [`Timeline`] for this room. + /// + /// This offers a higher-level API than event handlers, in treating things + /// like edits and reactions as updates of existing items rather than new + /// independent events. + async fn timeline(&self) -> Timeline; +} + +#[async_trait] +impl RoomExt for room::Common { + async fn timeline(&self) -> Timeline { + Timeline::builder(self).track_read_marker_and_receipts().build().await + } +} diff --git a/crates/matrix-sdk-ui/src/timeline/sliding_sync_ext.rs b/crates/matrix-sdk-ui/src/timeline/sliding_sync_ext.rs new file mode 100644 index 000000000..1399ff69a --- /dev/null +++ b/crates/matrix-sdk-ui/src/timeline/sliding_sync_ext.rs @@ -0,0 +1,54 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_trait::async_trait; +use matrix_sdk::SlidingSyncRoom; +use tracing::{error, instrument}; + +use super::{EventTimelineItem, Timeline, TimelineBuilder}; + +#[async_trait] +pub trait SlidingSyncRoomExt { + /// Get a `Timeline` for this room. + async fn timeline(&self) -> Option; + + /// Get the latest timeline item of this room. + /// + /// Use `Timeline::latest_event` instead if you already have a timeline for + /// this `SlidingSyncRoom`. + async fn latest_event(&self) -> Option; +} + +#[async_trait] +impl SlidingSyncRoomExt for SlidingSyncRoom { + async fn timeline(&self) -> Option { + Some(sliding_sync_timeline_builder(self)?.track_read_marker_and_receipts().build().await) + } + + #[instrument(skip_all)] + async fn latest_event(&self) -> Option { + sliding_sync_timeline_builder(self)?.build().await.latest_event().await + } +} + +fn sliding_sync_timeline_builder(room: &SlidingSyncRoom) -> Option { + let room_id = room.room_id(); + match room.client().get_room(room_id) { + Some(r) => Some(Timeline::builder(&r).events(room.prev_batch(), room.timeline_queue())), + None => { + error!(?room_id, "Room not found in client. Can't provide a timeline for it"); + None + } + } +} diff --git a/crates/matrix-sdk/src/room/timeline/tests/basic.rs b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/tests/basic.rs rename to crates/matrix-sdk-ui/src/timeline/tests/basic.rs index a128f6caa..dd5306018 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/basic.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/basic.rs @@ -16,7 +16,7 @@ use assert_matches::assert_matches; use eyeball_im::VectorDiff; use futures_util::StreamExt; use imbl::vector; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk::deserialized_responses::SyncTimelineEvent; use matrix_sdk_test::async_test; use ruma::{ assign, @@ -34,7 +34,7 @@ use ruma::{ use serde_json::{json, Value as JsonValue}; use super::{TestTimeline, ALICE, BOB}; -use crate::room::timeline::{ +use crate::timeline::{ event_item::AnyOtherFullStateEventContent, MembershipChange, TimelineDetails, TimelineItem, TimelineItemContent, VirtualTimelineItem, }; diff --git a/crates/matrix-sdk/src/room/timeline/tests/echo.rs b/crates/matrix-sdk-ui/src/timeline/tests/echo.rs similarity index 98% rename from crates/matrix-sdk/src/room/timeline/tests/echo.rs rename to crates/matrix-sdk-ui/src/timeline/tests/echo.rs index cf070f4fa..7209d9c01 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/echo.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/echo.rs @@ -17,6 +17,7 @@ use std::{io, sync::Arc}; use assert_matches::assert_matches; use eyeball_im::VectorDiff; use futures_util::StreamExt; +use matrix_sdk::Error; use matrix_sdk_test::async_test; use ruma::{ event_id, @@ -25,7 +26,7 @@ use ruma::{ use serde_json::json; use super::{TestTimeline, ALICE, BOB}; -use crate::{room::timeline::event_item::EventSendState, Error}; +use crate::timeline::event_item::EventSendState; #[async_test] async fn remote_echo_full_trip() { diff --git a/crates/matrix-sdk/src/room/timeline/tests/edit.rs b/crates/matrix-sdk-ui/src/timeline/tests/edit.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/tests/edit.rs rename to crates/matrix-sdk-ui/src/timeline/tests/edit.rs index 666f32f35..24ca673ea 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/edit.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/edit.rs @@ -30,7 +30,7 @@ use ruma::{ use serde_json::json; use super::{TestTimeline, ALICE}; -use crate::room::timeline::TimelineItemContent; +use crate::timeline::TimelineItemContent; #[async_test] async fn live_redacted() { diff --git a/crates/matrix-sdk/src/room/timeline/tests/encryption.rs b/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs similarity index 98% rename from crates/matrix-sdk/src/room/timeline/tests/encryption.rs rename to crates/matrix-sdk-ui/src/timeline/tests/encryption.rs index ab62f3d59..7b2dfa2e4 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/encryption.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs @@ -19,7 +19,7 @@ use std::{collections::BTreeSet, io::Cursor, iter}; use assert_matches::assert_matches; use eyeball_im::VectorDiff; use futures_util::StreamExt; -use matrix_sdk_base::crypto::{decrypt_room_key_export, OlmMachine}; +use matrix_sdk::crypto::{decrypt_room_key_export, OlmMachine}; use matrix_sdk_test::async_test; use ruma::{ assign, @@ -31,7 +31,7 @@ use ruma::{ }; use super::{TestTimeline, BOB}; -use crate::room::timeline::{EncryptedMessage, TimelineItemContent}; +use crate::timeline::{EncryptedMessage, TimelineItemContent}; #[async_test] async fn retry_message_decryption() { @@ -100,7 +100,7 @@ async fn retry_message_decryption() { timeline .inner - .retry_event_decryption( + .retry_event_decryption_test( room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost"), &olm_machine, Some(iter::once(SESSION_ID).collect()), @@ -203,7 +203,7 @@ async fn retry_edit_decryption() { timeline .inner - .retry_event_decryption( + .retry_event_decryption_test( room_id!("!bdsREiCPHyZAPkpXer:morpheus.localhost"), &olm_machine, None, @@ -306,7 +306,7 @@ async fn retry_edit_and_more() { timeline .inner - .retry_event_decryption( + .retry_event_decryption_test( room_id!("!wFnAUSQbxMcfIMgvNX:flipdot.org"), &olm_machine, Some(BTreeSet::from_iter([SESSION_ID])), @@ -392,7 +392,7 @@ async fn retry_message_decryption_highlighted() { timeline .inner - .retry_event_decryption( + .retry_event_decryption_test( room_id!("!rYtFvMGENJleNQVJzb:matrix.org"), &olm_machine, Some(iter::once(SESSION_ID).collect()), diff --git a/crates/matrix-sdk/src/room/timeline/tests/invalid.rs b/crates/matrix-sdk-ui/src/timeline/tests/invalid.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/tests/invalid.rs rename to crates/matrix-sdk-ui/src/timeline/tests/invalid.rs index f5cab1af8..78bfb67c8 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/invalid.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/invalid.rs @@ -28,7 +28,7 @@ use ruma::{ use serde_json::json; use super::{TestTimeline, ALICE, BOB}; -use crate::room::timeline::TimelineItemContent; +use crate::timeline::TimelineItemContent; #[async_test] async fn invalid_edit() { diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/tests/mod.rs rename to crates/matrix-sdk-ui/src/timeline/tests/mod.rs index 74191415e..3a4186c5c 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -26,7 +26,7 @@ use async_trait::async_trait; use eyeball_im::VectorDiff; use futures_core::Stream; use indexmap::IndexMap; -use matrix_sdk_base::deserialized_responses::TimelineEvent; +use matrix_sdk::deserialized_responses::TimelineEvent; use once_cell::sync::Lazy; use ruma::{ events::{ diff --git a/crates/matrix-sdk/src/room/timeline/tests/read_receipts.rs b/crates/matrix-sdk-ui/src/timeline/tests/read_receipts.rs similarity index 100% rename from crates/matrix-sdk/src/room/timeline/tests/read_receipts.rs rename to crates/matrix-sdk-ui/src/timeline/tests/read_receipts.rs diff --git a/crates/matrix-sdk/src/room/timeline/tests/redaction.rs b/crates/matrix-sdk-ui/src/timeline/tests/redaction.rs similarity index 100% rename from crates/matrix-sdk/src/room/timeline/tests/redaction.rs rename to crates/matrix-sdk-ui/src/timeline/tests/redaction.rs diff --git a/crates/matrix-sdk/src/room/timeline/tests/virt.rs b/crates/matrix-sdk-ui/src/timeline/tests/virt.rs similarity index 99% rename from crates/matrix-sdk/src/room/timeline/tests/virt.rs rename to crates/matrix-sdk-ui/src/timeline/tests/virt.rs index 0054c665a..33b2dd71c 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/virt.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/virt.rs @@ -23,7 +23,7 @@ use ruma::{ }; use super::{TestTimeline, ALICE, BOB}; -use crate::room::timeline::{TimelineItem, VirtualTimelineItem}; +use crate::timeline::{TimelineItem, VirtualTimelineItem}; #[async_test] async fn day_divider() { diff --git a/crates/matrix-sdk/src/room/timeline/to_device.rs b/crates/matrix-sdk-ui/src/timeline/to_device.rs similarity index 87% rename from crates/matrix-sdk/src/room/timeline/to_device.rs rename to crates/matrix-sdk-ui/src/timeline/to_device.rs index 5fde9a634..b30f204cc 100644 --- a/crates/matrix-sdk/src/room/timeline/to_device.rs +++ b/crates/matrix-sdk-ui/src/timeline/to_device.rs @@ -14,6 +14,7 @@ use std::{iter, sync::Arc}; +use matrix_sdk::{event_handler::EventHandler, Client}; use ruma::{ events::{forwarded_room_key::ToDeviceForwardedRoomKeyEvent, room_key::ToDeviceRoomKeyEvent}, OwnedRoomId, @@ -21,7 +22,6 @@ use ruma::{ use tracing::{debug_span, error, trace, Instrument}; use super::inner::TimelineInner; -use crate::{event_handler::EventHandler, Client}; pub(super) fn handle_room_key_event( inner: Arc, @@ -70,16 +70,10 @@ async fn retry_decryption( return; } - let Some(olm_machine) = client.olm_machine() else { - error!("The olm machine isn't yet available"); + let Some(room) = client.get_room(&room_id) else { + error!("Failed to fetch room object"); return; }; - inner - .retry_event_decryption( - &room_id, - olm_machine, - Some(iter::once(session_id.as_str()).collect()), - ) - .await; + inner.retry_event_decryption(&room, Some(iter::once(session_id.as_str()).collect())).await; } diff --git a/crates/matrix-sdk/src/room/timeline/virtual_item.rs b/crates/matrix-sdk-ui/src/timeline/virtual_item.rs similarity index 100% rename from crates/matrix-sdk/src/room/timeline/virtual_item.rs rename to crates/matrix-sdk-ui/src/timeline/virtual_item.rs diff --git a/crates/matrix-sdk/tests/integration/sliding_sync/mod.rs b/crates/matrix-sdk-ui/test/integration/main.rs similarity index 100% rename from crates/matrix-sdk/tests/integration/sliding_sync/mod.rs rename to crates/matrix-sdk-ui/test/integration/main.rs diff --git a/crates/matrix-sdk/tests/integration/room/timeline/mod.rs b/crates/matrix-sdk-ui/test/integration/timeline/mod.rs similarity index 99% rename from crates/matrix-sdk/tests/integration/room/timeline/mod.rs rename to crates/matrix-sdk-ui/test/integration/timeline/mod.rs index dcebad1cd..c0448ecb2 100644 --- a/crates/matrix-sdk/tests/integration/room/timeline/mod.rs +++ b/crates/matrix-sdk-ui/test/integration/timeline/mod.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "experimental-timeline")] - use std::{sync::Arc, time::Duration}; use assert_matches::assert_matches; @@ -34,6 +32,8 @@ use wiremock::{ }; mod read_receipts; +#[cfg(feature = "experimental-sliding-sync")] +mod sliding_sync; use crate::{logged_in_client, mock_encryption_state, mock_sync}; diff --git a/crates/matrix-sdk/tests/integration/room/timeline/read_receipts.rs b/crates/matrix-sdk-ui/test/integration/timeline/read_receipts.rs similarity index 100% rename from crates/matrix-sdk/tests/integration/room/timeline/read_receipts.rs rename to crates/matrix-sdk-ui/test/integration/timeline/read_receipts.rs diff --git a/crates/matrix-sdk/tests/integration/sliding_sync/timeline.rs b/crates/matrix-sdk-ui/test/integration/timeline/sliding_sync.rs similarity index 99% rename from crates/matrix-sdk/tests/integration/sliding_sync/timeline.rs rename to crates/matrix-sdk-ui/test/integration/timeline/sliding_sync.rs index adde6ca26..9e96c27a5 100644 --- a/crates/matrix-sdk/tests/integration/sliding_sync/timeline.rs +++ b/crates/matrix-sdk-ui/test/integration/timeline/sliding_sync.rs @@ -5,10 +5,13 @@ use assert_matches::assert_matches; use eyeball_im::{Vector, VectorDiff}; use futures::{pin_mut, Stream, StreamExt}; use matrix_sdk::{ - room::timeline::{TimelineItem, VirtualTimelineItem}, SlidingSync, SlidingSyncList, SlidingSyncListBuilder, SlidingSyncMode, UpdateSummary, }; use matrix_sdk_test::async_test; +use matrix_sdk_ui::{ + timeline::{TimelineItem, VirtualTimelineItem}, + SlidingSyncRoomExt, +}; use ruma::{room_id, RoomId}; use serde_json::json; use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate}; diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index a7ca2b981..926b3e22c 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -47,13 +47,7 @@ appservice = ["ruma/appservice-api-s"] image-proc = ["dep:image"] image-rayon = ["image-proc", "image?/jpeg_rayon"] -experimental-timeline = ["ruma/unstable-msc2677", "ruma/unstable-sanitize", "dep:chrono"] - -experimental-sliding-sync = [ - "matrix-sdk-base/experimental-sliding-sync", - "experimental-timeline", - "reqwest/gzip", -] +experimental-sliding-sync = ["matrix-sdk-base/experimental-sliding-sync", "reqwest/gzip"] docsrs = [ "e2e-encryption", @@ -70,7 +64,6 @@ async-stream = { workspace = true } async-trait = { workspace = true } bytes = "1.1.0" bytesize = "1.1" -chrono = { version = "0.4.23", optional = true } dashmap = { workspace = true } event-listener = "2.5.2" eyeball = { workspace = true } @@ -80,7 +73,6 @@ futures-core = "0.3.21" futures-util = { workspace = true } http = { workspace = true } imbl = { version = "2.0.0", features = ["serde"] } -indexmap = "1.9.1" hyper = { version = "0.14.20", features = ["http1", "http2", "server"], optional = true } matrix-sdk-base = { version = "0.6.0", path = "../matrix-sdk-base", default_features = false } matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" } @@ -88,8 +80,6 @@ matrix-sdk-indexeddb = { version = "0.2.0", path = "../matrix-sdk-indexeddb", de matrix-sdk-sqlite = { version = "0.1.0", path = "../matrix-sdk-sqlite", default-features = false, optional = true } mime = "0.3.16" mime2ext = "0.1.52" -once_cell = { workspace = true } -pin-project-lite = "0.2.9" rand = { version = "0.8.5", optional = true } reqwest = { version = "0.11.10", default_features = false } ruma = { workspace = true, features = ["rand", "unstable-msc2448", "unstable-msc2965"] } @@ -137,7 +127,6 @@ assert_matches = { workspace = true } dirs = "4.0.0" futures = { version = "0.3.21", default-features = false, features = ["executor"] } matrix-sdk-test = { version = "0.6.0", path = "../../testing/matrix-sdk-test" } -once_cell = { workspace = true } tracing-subscriber = { version = "0.3.11", features = ["env-filter"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index b73275885..b8a33ee3a 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -245,11 +245,6 @@ pub enum Error { #[error(transparent)] SlidingSync(#[from] crate::sliding_sync::Error), - /// An error occurred in the timeline. - #[cfg(feature = "experimental-timeline")] - #[error(transparent)] - Timeline(#[from] crate::room::timeline::Error), - /// The client is in inconsistent state. This happens when we set a room to /// a specific type, but then cannot get it in this type. #[error("The internal client state is inconsistent.")] diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index 95923c593..26780c1ae 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -45,8 +45,6 @@ pub mod sliding_sync; #[cfg(feature = "e2e-encryption")] pub mod encryption; -#[cfg(feature = "experimental-timeline")] -mod events; pub use account::Account; #[cfg(feature = "sso-login")] diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 9d418901a..c8ac7844c 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -49,8 +49,6 @@ use serde::de::DeserializeOwned; use tokio::sync::Mutex; use tracing::{debug, instrument}; -#[cfg(feature = "experimental-timeline")] -use super::timeline::Timeline; use super::Joined; use crate::{ event_handler::{EventHandler, EventHandlerHandle, SyncEvent}, @@ -269,16 +267,6 @@ impl Common { self.client.add_room_event_handler(self.room_id(), handler) } - /// Get a [`Timeline`] for this room. - /// - /// This offers a higher-level API than event handlers, in treating things - /// like edits and reactions as updates of existing items rather than new - /// independent events. - #[cfg(feature = "experimental-timeline")] - pub async fn timeline(&self) -> Timeline { - Timeline::builder(self).track_read_marker_and_receipts().build().await - } - /// Fetch the event with the given `EventId` in this room. pub async fn event(&self, event_id: &EventId) -> Result { let request = diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index 83dddeafd..e3d30f7ac 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -295,10 +295,10 @@ impl Joined { return Ok(()); } - let Receipts { fully_read, read_receipt, private_read_receipt } = receipts; + let Receipts { fully_read, public_read_receipt, private_read_receipt } = receipts; let request = assign!(set_read_marker::v3::Request::new(self.inner.room_id().to_owned()), { fully_read, - read_receipt, + read_receipt: public_read_receipt, private_read_receipt, }); @@ -1139,10 +1139,14 @@ impl Joined { /// Receipts to send all at once. #[derive(Debug, Clone, Default)] +#[non_exhaustive] pub struct Receipts { - pub(super) fully_read: Option, - pub(super) read_receipt: Option, - pub(super) private_read_receipt: Option, + /// Fully-read marker (room account data). + pub fully_read: Option, + /// Read receipt (public ephemeral room event). + pub public_read_receipt: Option, + /// Read receipt (private ephemeral room event). + pub private_read_receipt: Option, } impl Receipts { @@ -1170,7 +1174,7 @@ impl Receipts { /// This is used to reset the unread messages/notification count and /// advertise to other users the last event that the user has likely seen. pub fn public_read_receipt(mut self, event_id: impl Into>) -> Self { - self.read_receipt = event_id.into(); + self.public_read_receipt = event_id.into(); self } @@ -1185,7 +1189,7 @@ impl Receipts { /// Whether this `Receipts` is empty. pub fn is_empty(&self) -> bool { self.fully_read.is_none() - && self.read_receipt.is_none() + && self.public_read_receipt.is_none() && self.private_read_receipt.is_none() } } diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index 333b7f737..ee429084a 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -9,8 +9,6 @@ mod invited; mod joined; mod left; mod member; -#[cfg(feature = "experimental-timeline")] -pub mod timeline; pub use self::{ common::{Common, Messages, MessagesOptions}, diff --git a/crates/matrix-sdk/src/sliding_sync/README.md b/crates/matrix-sdk/src/sliding_sync/README.md index 5834ab1dd..3a4b610f1 100644 --- a/crates/matrix-sdk/src/sliding_sync/README.md +++ b/crates/matrix-sdk/src/sliding_sync/README.md @@ -216,8 +216,8 @@ the [`BaseClient`][`matrix_sdk_base::BaseClient`] as in previous sync. This allows for transparent decryption as well trigger the `client_handlers`. The current and then following live events list can be queried via the -[`timeline` API](`SlidingSyncRoom::timeline). This is prefilled with already -received data. +`timeline` API from `matrix-sdk-ui`. This is prefilled with already received +data. ### Timeline trickling @@ -349,8 +349,8 @@ copies accordingly. Because of where the loop sits in the stack, that can be a bit tedious though, so lists and rooms have an additional way of subscribing to updates via [`eyeball`]. -The `Timeline` one can receive per room by calling -[`.timeline()`][`SlidingSyncRoom::timeline`] will be populated with the +The `Timeline` one can receive per room by calling `.timeline()` (from +`matrix_sdk_ui::timeline::SlidingSyncRoomExt`) will be populated with the currently cached timeline events. ## Caching diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index 1aae25f28..c685b7f3e 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -13,12 +13,8 @@ use ruma::{ OwnedRoomId, RoomId, }; use serde::{Deserialize, Serialize}; -use tracing::{error, instrument}; -use crate::{ - room::timeline::{EventTimelineItem, Timeline, TimelineBuilder}, - Client, -}; +use crate::Client; /// The state of a [`SlidingSyncRoom`]. #[derive(Copy, Clone, Debug, Default, PartialEq)] @@ -108,23 +104,25 @@ impl SlidingSyncRoom { /// Get the required state. pub fn required_state(&self) -> Vec> { - let inner = self.inner.inner.read().unwrap(); - - inner.required_state.clone() + self.inner.inner.read().unwrap().required_state.clone() } - /// `Timeline` of this room - pub async fn timeline(&self) -> Option { - Some(self.inner.timeline_builder()?.track_read_marker_and_receipts().build().await) + /// Get the token for back-pagination. + pub fn prev_batch(&self) -> Option { + self.inner.inner.read().unwrap().prev_batch.clone() } - /// The latest timeline item of this room. + /// Get a copy of the cached timeline events. /// - /// Use `Timeline::latest_event` instead if you already have a timeline for - /// this `SlidingSyncRoom`. - #[instrument(skip_all)] - pub async fn latest_event(&self) -> Option { - self.inner.timeline_builder()?.build().await.latest_event().await + /// Note: This API only exists temporarily, it *will* be removed in the + /// future. + pub fn timeline_queue(&self) -> Vector { + self.inner.timeline_queue.read().unwrap().clone() + } + + /// Get a clone of the associated client. + pub fn client(&self) -> Client { + self.inner.client.clone() } pub(super) fn update( @@ -233,10 +231,6 @@ impl SlidingSyncRoom { fn set_state(&mut self, state: SlidingSyncRoomState) { *self.inner.state.write().unwrap() = state; } - - fn timeline_queue(&self) -> std::sync::RwLockReadGuard<'_, Vector> { - self.inner.timeline_queue.read().unwrap() - } } #[derive(Debug)] @@ -260,33 +254,6 @@ struct SlidingSyncRoomInner { timeline_queue: RwLock>, } -impl SlidingSyncRoomInner { - /// Get the previous batch. - fn prev_batch(&self) -> Option { - let inner = self.inner.read().unwrap(); - - inner.prev_batch.clone() - } - - fn timeline_builder(&self) -> Option { - if let Some(room) = self.client.get_room(&self.room_id) { - Some( - Timeline::builder(&room) - .events(self.prev_batch(), self.timeline_queue.read().unwrap().clone()), - ) - } else if let Some(invited_room) = self.client.get_invited_room(&self.room_id) { - Some(Timeline::builder(&invited_room).events(None, Vector::new())) - } else { - error!( - room_id = ?self.room_id, - "Room not found in client. Can't provide a timeline for it" - ); - - None - } - } -} - /// A “frozen” [`SlidingSyncRoom`], i.e. that can be written into, or read from /// a store. #[derive(Debug, Serialize, Deserialize)] @@ -515,7 +482,7 @@ mod tests { { let room = new_room(room_id!("!foo:bar.org"), room_response!({})).await; - assert_eq!(room.inner.prev_batch(), None); + assert_eq!(room.prev_batch(), None); } // Some value when initializing. @@ -524,20 +491,20 @@ mod tests { new_room(room_id!("!foo:bar.org"), room_response!({"prev_batch": "t111_222_333"})) .await; - assert_eq!(room.inner.prev_batch(), Some("t111_222_333".to_owned())); + assert_eq!(room.prev_batch(), Some("t111_222_333".to_owned())); } // Some value when updating. { let mut room = new_room(room_id!("!foo:bar.org"), room_response!({})).await; - assert_eq!(room.inner.prev_batch(), None); + assert_eq!(room.prev_batch(), None); room.update(room_response!({"prev_batch": "t111_222_333"}), vec![]); - assert_eq!(room.inner.prev_batch(), Some("t111_222_333".to_owned())); + assert_eq!(room.prev_batch(), Some("t111_222_333".to_owned())); room.update(room_response!({}), vec![]); - assert_eq!(room.inner.prev_batch(), Some("t111_222_333".to_owned())); + assert_eq!(room.prev_batch(), Some("t111_222_333".to_owned())); } } diff --git a/crates/matrix-sdk/tests/integration/main.rs b/crates/matrix-sdk/tests/integration/main.rs index 64b964d2b..2619a4c0a 100644 --- a/crates/matrix-sdk/tests/integration/main.rs +++ b/crates/matrix-sdk/tests/integration/main.rs @@ -16,8 +16,6 @@ use wiremock::{ mod client; mod refresh_token; mod room; -#[cfg(feature = "experimental-sliding-sync")] -mod sliding_sync; #[cfg(all(test, not(target_arch = "wasm32")))] #[ctor::ctor] diff --git a/crates/matrix-sdk/tests/integration/room/mod.rs b/crates/matrix-sdk/tests/integration/room/mod.rs index 3325d1320..b9e0e2c78 100644 --- a/crates/matrix-sdk/tests/integration/room/mod.rs +++ b/crates/matrix-sdk/tests/integration/room/mod.rs @@ -1,4 +1,3 @@ mod common; mod joined; mod left; -mod timeline; diff --git a/examples/timeline/Cargo.toml b/examples/timeline/Cargo.toml index aa1b2e1c7..b9c0e1520 100644 --- a/examples/timeline/Cargo.toml +++ b/examples/timeline/Cargo.toml @@ -18,5 +18,8 @@ url = "2.2.2" [dependencies.matrix-sdk] path = "../../crates/matrix-sdk" -features = ["experimental-timeline"] +version = "0.6.0" + +[dependencies.matrix-sdk-ui] +path = "../../crates/matrix-sdk-ui" version = "0.6.0" diff --git a/examples/timeline/src/main.rs b/examples/timeline/src/main.rs index 994449833..74a110dc3 100644 --- a/examples/timeline/src/main.rs +++ b/examples/timeline/src/main.rs @@ -2,6 +2,7 @@ use anyhow::Result; use clap::Parser; use futures::StreamExt; use matrix_sdk::{self, config::SyncSettings, ruma::OwnedRoomId, Client}; +use matrix_sdk_ui::timeline::RoomExt; use url::Url; #[derive(Parser, Debug)] diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 8e9ea61a2..4130898bd 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -174,7 +174,7 @@ fn check_clippy() -> Result<()> { "rustup run {NIGHTLY} cargo clippy --workspace --all-targets --exclude matrix-sdk-crypto --exclude xtask --no-default-features - --features native-tls,experimental-sliding-sync,sso-login,experimental-timeline + --features native-tls,experimental-sliding-sync,sso-login -- -D warnings" ) .run()?;