mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-15 03:25:46 -04:00
feat(sdk): Attach profile information to timeline items
This commit is contained in:
@@ -129,7 +129,7 @@ impl SlidingSyncRoom {
|
||||
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
pub fn latest_room_message(&self) -> Option<Arc<EventTimelineItem>> {
|
||||
let item = self.inner.latest_event()?;
|
||||
let item = RUNTIME.block_on(self.inner.latest_event())?;
|
||||
Some(Arc::new(EventTimelineItem(item)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ impl Common {
|
||||
/// independent events.
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
pub async fn timeline(&self) -> Timeline {
|
||||
Timeline::new(self).await.with_fully_read_tracking().await
|
||||
Timeline::new(self).with_fully_read_tracking().await
|
||||
}
|
||||
|
||||
/// Fetch the event with the given `EventId` in this room.
|
||||
|
||||
@@ -39,7 +39,7 @@ use ruma::{
|
||||
use tracing::{debug, error, field::debug, info, instrument, trace, warn};
|
||||
|
||||
use super::{
|
||||
event_item::{BundledReactions, Sticker, TimelineDetails},
|
||||
event_item::{BundledReactions, Profile, Sticker, TimelineDetails},
|
||||
find_event_by_id, find_event_by_txn_id, find_read_marker, EventTimelineItem, Message,
|
||||
TimelineInnerMetadata, TimelineItem, TimelineItemContent, TimelineKey, VirtualTimelineItem,
|
||||
};
|
||||
@@ -84,6 +84,7 @@ impl Flow {
|
||||
|
||||
pub(super) struct TimelineEventMetadata {
|
||||
pub(super) sender: OwnedUserId,
|
||||
pub(super) sender_profile: Profile,
|
||||
pub(super) is_own_event: bool,
|
||||
pub(super) relations: BundledRelations,
|
||||
pub(super) encryption_info: Option<EncryptionInfo>,
|
||||
@@ -457,6 +458,7 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
|
||||
key: self.flow.to_key(),
|
||||
event_id: None,
|
||||
sender: self.meta.sender.to_owned(),
|
||||
sender_profile: self.meta.sender_profile.clone(),
|
||||
content,
|
||||
reactions,
|
||||
timestamp: self.flow.timestamp(),
|
||||
|
||||
@@ -27,8 +27,8 @@ use ruma::{
|
||||
AnySyncTimelineEvent, MessageLikeEventType, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
uint, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedTransactionId,
|
||||
OwnedUserId, TransactionId, UInt, UserId,
|
||||
uint, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedMxcUri,
|
||||
OwnedTransactionId, OwnedUserId, TransactionId, UInt, UserId,
|
||||
};
|
||||
|
||||
/// An item in the timeline that represents at least one event.
|
||||
@@ -44,6 +44,7 @@ pub struct EventTimelineItem {
|
||||
// response.
|
||||
pub(super) event_id: Option<OwnedEventId>,
|
||||
pub(super) sender: OwnedUserId,
|
||||
pub(super) sender_profile: Profile,
|
||||
pub(super) content: TimelineItemContent,
|
||||
pub(super) reactions: BundledReactions,
|
||||
pub(super) timestamp: MilliSecondsSinceUnixEpoch,
|
||||
@@ -96,6 +97,11 @@ impl EventTimelineItem {
|
||||
&self.sender
|
||||
}
|
||||
|
||||
/// Get the profile of the sender.
|
||||
pub fn sender_profile(&self) -> &Profile {
|
||||
&self.sender_profile
|
||||
}
|
||||
|
||||
/// Get the content of this item.
|
||||
pub fn content(&self) -> &TimelineItemContent {
|
||||
&self.content
|
||||
@@ -195,6 +201,15 @@ impl PartialEq<TransactionId> for TimelineKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// The display name and avatar URL of a room member.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Profile {
|
||||
/// The display name, if set.
|
||||
pub display_name: Option<String>,
|
||||
/// The avatar URL, if set.
|
||||
pub avatar_url: Option<OwnedMxcUri>,
|
||||
}
|
||||
|
||||
/// Some details of an [`EventTimelineItem`] that may require server requests
|
||||
/// other than just the regular
|
||||
/// [`sync_events`][ruma::api::client::sync::sync_events].
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_signals::signal_vec::{MutableVec, MutableVecLockRef, SignalVec};
|
||||
use matrix_sdk_base::{
|
||||
crypto::OlmMachine,
|
||||
@@ -27,7 +28,7 @@ use super::{
|
||||
update_read_marker, Flow, HandleEventResult, TimelineEventHandler, TimelineEventKind,
|
||||
TimelineEventMetadata, TimelineItemPosition,
|
||||
},
|
||||
find_event_by_txn_id, TimelineItem, TimelineKey,
|
||||
find_event_by_txn_id, Profile, TimelineItem, TimelineKey,
|
||||
};
|
||||
use crate::{events::SyncTimelineEventWithoutContent, room};
|
||||
|
||||
@@ -60,7 +61,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
self.items.signal_vec_cloned()
|
||||
}
|
||||
|
||||
pub(super) fn add_initial_events(&mut self, events: Vec<SyncTimelineEvent>) {
|
||||
pub(super) async fn add_initial_events(&mut self, events: Vec<SyncTimelineEvent>) {
|
||||
if events.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -77,7 +78,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
&self.items,
|
||||
timeline_meta,
|
||||
&self.profile_provider,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +96,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
&self.items,
|
||||
&mut timeline_meta,
|
||||
&self.profile_provider,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(super) async fn handle_local_event(
|
||||
@@ -102,8 +105,11 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
txn_id: OwnedTransactionId,
|
||||
content: AnyMessageLikeEventContent,
|
||||
) {
|
||||
let sender = self.profile_provider.own_user_id().to_owned();
|
||||
let sender_profile = self.profile_provider.profile(&sender).await;
|
||||
let event_meta = TimelineEventMetadata {
|
||||
sender: self.profile_provider.own_user_id().to_owned(),
|
||||
sender,
|
||||
sender_profile,
|
||||
is_own_event: true,
|
||||
relations: Default::default(),
|
||||
// FIXME: Should we supply something here for encrypted rooms?
|
||||
@@ -135,6 +141,7 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
&mut metadata_lock,
|
||||
&self.profile_provider,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) fn add_event_id(&self, txn_id: &TransactionId, event_id: OwnedEventId) {
|
||||
@@ -279,7 +286,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
&self.items,
|
||||
&mut metadata_lock,
|
||||
&self.profile_provider,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,20 +298,37 @@ impl TimelineInner {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub(super) trait ProfileProvider {
|
||||
fn own_user_id(&self) -> &UserId;
|
||||
async fn profile(&self, user_id: &UserId) -> Profile;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProfileProvider for room::Common {
|
||||
fn own_user_id(&self) -> &UserId {
|
||||
(**self).own_user_id()
|
||||
}
|
||||
|
||||
async fn profile(&self, user_id: &UserId) -> Profile {
|
||||
match self.get_member_no_sync(user_id).await {
|
||||
Ok(Some(member)) => Profile {
|
||||
display_name: member.display_name().map(ToOwned::to_owned),
|
||||
avatar_url: member.avatar_url().map(ToOwned::to_owned),
|
||||
},
|
||||
Ok(None) => Profile { display_name: None, avatar_url: None },
|
||||
Err(e) => {
|
||||
error!(%user_id, "Failed to getch room member information: {e}");
|
||||
Profile { display_name: None, avatar_url: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a remote event.
|
||||
///
|
||||
/// Returns the number of timeline updates that were made.
|
||||
fn handle_remote_event<P: ProfileProvider>(
|
||||
async fn handle_remote_event<P: ProfileProvider>(
|
||||
raw: Raw<AnySyncTimelineEvent>,
|
||||
encryption_info: Option<EncryptionInfo>,
|
||||
position: TimelineItemPosition,
|
||||
@@ -340,7 +365,9 @@ fn handle_remote_event<P: ProfileProvider>(
|
||||
};
|
||||
|
||||
let is_own_event = sender == profile_provider.own_user_id();
|
||||
let event_meta = TimelineEventMetadata { sender, is_own_event, relations, encryption_info };
|
||||
let sender_profile = profile_provider.profile(&sender).await;
|
||||
let event_meta =
|
||||
TimelineEventMetadata { sender, sender_profile, is_own_event, relations, encryption_info };
|
||||
let flow = Flow::Remote { event_id, origin_server_ts, raw_event: raw, txn_id, position };
|
||||
|
||||
let mut timeline_items = timeline_items.lock_mut();
|
||||
|
||||
@@ -50,8 +50,8 @@ mod virtual_item;
|
||||
|
||||
pub use self::{
|
||||
event_item::{
|
||||
EncryptedMessage, EventTimelineItem, Message, ReactionDetails, Sticker, TimelineDetails,
|
||||
TimelineItemContent, TimelineKey,
|
||||
EncryptedMessage, EventTimelineItem, Message, Profile, ReactionDetails, Sticker,
|
||||
TimelineDetails, TimelineItemContent, TimelineKey,
|
||||
},
|
||||
pagination::{PaginationOptions, PaginationOutcome},
|
||||
virtual_item::VirtualTimelineItem,
|
||||
@@ -83,19 +83,22 @@ impl Drop for Timeline {
|
||||
}
|
||||
|
||||
impl Timeline {
|
||||
pub(super) async fn new(room: &room::Common) -> Self {
|
||||
Self::with_events(room, None, Vec::new())
|
||||
pub(super) fn new(room: &room::Common) -> Self {
|
||||
Self::from_inner(Arc::new(TimelineInner::new(room.to_owned())), None)
|
||||
}
|
||||
|
||||
pub(crate) fn with_events(
|
||||
pub(crate) async fn with_events(
|
||||
room: &room::Common,
|
||||
prev_token: Option<String>,
|
||||
events: Vec<SyncTimelineEvent>,
|
||||
) -> Self {
|
||||
let mut inner = TimelineInner::new(room.to_owned());
|
||||
inner.add_initial_events(events);
|
||||
inner.add_initial_events(events).await;
|
||||
Self::from_inner(Arc::new(inner), prev_token)
|
||||
}
|
||||
|
||||
let inner = Arc::new(inner);
|
||||
fn from_inner(inner: Arc<TimelineInner>, prev_token: Option<String>) -> Timeline {
|
||||
let room = inner.room();
|
||||
|
||||
let timeline_event_handle = room.add_event_handler({
|
||||
let inner = inner.clone();
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::sync::{
|
||||
};
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use async_trait::async_trait;
|
||||
use futures_core::Stream;
|
||||
use futures_signals::signal_vec::{SignalVecExt, VecDiff};
|
||||
use futures_util::StreamExt;
|
||||
@@ -47,8 +48,8 @@ use ruma::{
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
use super::{
|
||||
inner::ProfileProvider, EncryptedMessage, TimelineInner, TimelineItem, TimelineItemContent,
|
||||
TimelineKey, VirtualTimelineItem,
|
||||
inner::ProfileProvider, EncryptedMessage, Profile, TimelineInner, TimelineItem,
|
||||
TimelineItemContent, TimelineKey, VirtualTimelineItem,
|
||||
};
|
||||
|
||||
static ALICE: Lazy<&UserId> = Lazy::new(|| user_id!("@alice:server.name"));
|
||||
@@ -528,7 +529,8 @@ async fn initial_events() {
|
||||
let timeline = TestTimeline::with_initial_events([
|
||||
(*ALICE, RoomMessageEventContent::text_plain("A").into()),
|
||||
(*BOB, RoomMessageEventContent::text_plain("B").into()),
|
||||
]);
|
||||
])
|
||||
.await;
|
||||
let mut stream = timeline.stream();
|
||||
|
||||
let items = assert_matches!(stream.next().await, Some(VecDiff::Replace { values }) => values);
|
||||
@@ -544,23 +546,25 @@ struct TestTimeline {
|
||||
|
||||
impl TestTimeline {
|
||||
fn new() -> Self {
|
||||
Self::with_initial_events([])
|
||||
Self { inner: TimelineInner::new(TestProfileProvider) }
|
||||
}
|
||||
|
||||
fn with_initial_events<'a>(
|
||||
async fn with_initial_events<'a>(
|
||||
events: impl IntoIterator<Item = (&'a UserId, AnyMessageLikeEventContent)>,
|
||||
) -> Self {
|
||||
let mut inner = TimelineInner::new(TestProfileProvider);
|
||||
inner.add_initial_events(
|
||||
events
|
||||
.into_iter()
|
||||
.map(|(sender, content)| {
|
||||
let event =
|
||||
serde_json::from_value(make_message_event(sender, content)).unwrap();
|
||||
SyncTimelineEvent { event, encryption_info: None }
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
inner
|
||||
.add_initial_events(
|
||||
events
|
||||
.into_iter()
|
||||
.map(|(sender, content)| {
|
||||
let event =
|
||||
serde_json::from_value(make_message_event(sender, content)).unwrap();
|
||||
SyncTimelineEvent { event, encryption_info: None }
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.await;
|
||||
|
||||
Self { inner }
|
||||
}
|
||||
@@ -605,10 +609,15 @@ impl TestTimeline {
|
||||
|
||||
struct TestProfileProvider;
|
||||
|
||||
#[async_trait]
|
||||
impl ProfileProvider for TestProfileProvider {
|
||||
fn own_user_id(&self) -> &UserId {
|
||||
&ALICE
|
||||
}
|
||||
|
||||
async fn profile(&self, _user_id: &UserId) -> Profile {
|
||||
Profile { display_name: None, avatar_url: None }
|
||||
}
|
||||
}
|
||||
|
||||
fn make_message_event<C: MessageLikeEventContent>(sender: &UserId, content: C) -> JsonValue {
|
||||
|
||||
@@ -235,16 +235,16 @@ impl SlidingSyncRoom {
|
||||
/// `Timeline` of this room
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
pub async fn timeline(&self) -> Option<Timeline> {
|
||||
Some(self.timeline_no_fully_read_tracking()?.with_fully_read_tracking().await)
|
||||
Some(self.timeline_no_fully_read_tracking().await?.with_fully_read_tracking().await)
|
||||
}
|
||||
|
||||
fn timeline_no_fully_read_tracking(&self) -> Option<Timeline> {
|
||||
async fn timeline_no_fully_read_tracking(&self) -> Option<Timeline> {
|
||||
if let Some(room) = self.client.get_room(&self.room_id) {
|
||||
let current_timeline = self.timeline.lock_ref().to_vec();
|
||||
let prev_batch = self.prev_batch.lock_ref().clone();
|
||||
Some(Timeline::with_events(&room, prev_batch, current_timeline))
|
||||
Some(Timeline::with_events(&room, prev_batch, current_timeline).await)
|
||||
} else if let Some(invited_room) = self.client.get_invited_room(&self.room_id) {
|
||||
Some(Timeline::with_events(&invited_room, None, vec![]))
|
||||
Some(Timeline::with_events(&invited_room, None, vec![]).await)
|
||||
} else {
|
||||
error!(
|
||||
room_id = ?self.room_id,
|
||||
@@ -259,8 +259,8 @@ impl SlidingSyncRoom {
|
||||
/// Use `Timeline::latest_event` instead if you already have a timeline for
|
||||
/// this `SlidingSyncRoom`.
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
pub fn latest_event(&self) -> Option<EventTimelineItem> {
|
||||
self.timeline_no_fully_read_tracking()?.latest_event()
|
||||
pub async fn latest_event(&self) -> Option<EventTimelineItem> {
|
||||
self.timeline_no_fully_read_tracking().await?.latest_event()
|
||||
}
|
||||
|
||||
/// This rooms name as calculated by the server, if any
|
||||
|
||||
Reference in New Issue
Block a user