Skip to content

Commit

Permalink
chore(room_list): allow knock state event as latest_event
Browse files Browse the repository at this point in the history
This allows clients to display pending knocking requests in the room list items.
  • Loading branch information
jmartinesp committed Oct 25, 2024
1 parent 3558886 commit a4519f2
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 9 deletions.
3 changes: 2 additions & 1 deletion crates/matrix-sdk-base/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,8 @@ impl BaseClient {
| PossibleLatestEvent::YesPoll(_)
| PossibleLatestEvent::YesCallInvite(_)
| PossibleLatestEvent::YesCallNotify(_)
| PossibleLatestEvent::YesSticker(_) => {
| PossibleLatestEvent::YesSticker(_)
| PossibleLatestEvent::YesKnockedStateEvent(_) => {
// The event is the right type for us to use as latest_event
return Some((Box::new(LatestEvent::new(decrypted)), i));
}
Expand Down
24 changes: 21 additions & 3 deletions crates/matrix-sdk-base/src/latest_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ use ruma::events::{
room::message::SyncRoomMessageEvent,
AnySyncMessageLikeEvent, AnySyncTimelineEvent,
};
use ruma::{events::sticker::SyncStickerEvent, MxcUri, OwnedEventId};
use ruma::{
events::{
room::member::{MembershipState, SyncRoomMemberEvent},
sticker::SyncStickerEvent,
AnySyncStateEvent,
},
MxcUri, OwnedEventId,
};
use serde::{Deserialize, Serialize};

use crate::MinimalRoomMemberEvent;
Expand All @@ -37,6 +44,9 @@ pub enum PossibleLatestEvent<'a> {
/// This message is suitable - it's a call notification
YesCallNotify(&'a SyncCallNotifyEvent),

/// This state event is suitable - it's a knock membership change
YesKnockedStateEvent(&'a SyncRoomMemberEvent),

// Later: YesState(),
// Later: YesReaction(),
/// Not suitable - it's a state event
Expand Down Expand Up @@ -102,8 +112,16 @@ pub fn is_suitable_for_latest_event(event: &AnySyncTimelineEvent) -> PossibleLat
// suitable
AnySyncTimelineEvent::MessageLike(_) => PossibleLatestEvent::NoUnsupportedMessageLikeType,

// We don't currently support state events
AnySyncTimelineEvent::State(_) => PossibleLatestEvent::NoUnsupportedEventType,
// We don't currently support most state events
AnySyncTimelineEvent::State(state) => {
// But we make an exception for knocked state events
if let AnySyncStateEvent::RoomMember(member) = state {
if matches!(member.membership(), MembershipState::Knock) {
return PossibleLatestEvent::YesKnockedStateEvent(member);
}
}
PossibleLatestEvent::NoUnsupportedEventType
}
}
}

Expand Down
60 changes: 59 additions & 1 deletion crates/matrix-sdk-base/src/sliding_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,8 @@ async fn cache_latest_events(
| PossibleLatestEvent::YesPoll(_)
| PossibleLatestEvent::YesCallInvite(_)
| PossibleLatestEvent::YesCallNotify(_)
| PossibleLatestEvent::YesSticker(_) => {
| PossibleLatestEvent::YesSticker(_)
| PossibleLatestEvent::YesKnockedStateEvent(_) => {
// We found a suitable latest event. Store it.

// In order to make the latest event fast to read, we want to keep the
Expand Down Expand Up @@ -1738,6 +1739,63 @@ mod tests {
);
}

#[async_test]
async fn test_last_knock_member_state_event_from_sliding_sync_is_cached() {
// Given a logged-in client
let client = logged_in_base_client(None).await;
let room_id = room_id!("!r:e.uk");
// And a knock member state event
let knock_event = json!({
"sender":"@alice:example.com",
"state_key":"@alice:example.com",
"type":"m.room.member",
"event_id": "$ida",
"origin_server_ts": 12344446,
"content":{"membership": "knock"},
"room_id": room_id,
});

// When the sliding sync response contains a timeline
let events = &[knock_event];
let room = room_with_timeline(events);
let response = response_with_room(room_id, room);
client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");

// Then the room holds the latest knock state event
let client_room = client.get_room(room_id).expect("No room found");
assert_eq!(
ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
"$ida"
);
}

#[async_test]
async fn test_last_member_state_event_from_sliding_sync_is_not_cached() {
// Given a logged-in client
let client = logged_in_base_client(None).await;
let room_id = room_id!("!r:e.uk");
// And a join member state event
let join_event = json!({
"sender":"@alice:example.com",
"state_key":"@alice:example.com",
"type":"m.room.member",
"event_id": "$ida",
"origin_server_ts": 12344446,
"content":{"membership": "join"},
"room_id": room_id,
});

// When the sliding sync response contains a timeline
let events = &[join_event];
let room = room_with_timeline(events);
let response = response_with_room(room_id, room);
client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");

// Then the room doesn't hold the join state event as the latest event
let client_room = client.get_room(room_id).expect("No room found");
assert!(client_room.latest_event().is_none());
}

#[async_test]
async fn test_cached_latest_event_can_be_redacted() {
// Given a logged-in client
Expand Down
22 changes: 21 additions & 1 deletion crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use ruma::{
guest_access::RoomGuestAccessEventContent,
history_visibility::RoomHistoryVisibilityEventContent,
join_rules::RoomJoinRulesEventContent,
member::{Change, RoomMemberEventContent},
member::{Change, RoomMemberEventContent, SyncRoomMemberEvent},
message::{
Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
SyncRoomMessageEvent,
Expand Down Expand Up @@ -173,6 +173,9 @@ impl TimelineItemContent {
);
None
}
PossibleLatestEvent::YesKnockedStateEvent(member) => {
Some(Self::from_suitable_latest_knock_state_event_content(member))
}
PossibleLatestEvent::NoEncrypted => {
warn!("Found an encrypted event cached as latest_event! ID={}", event.event_id());
None
Expand Down Expand Up @@ -220,6 +223,23 @@ impl TimelineItemContent {
}
}

fn from_suitable_latest_knock_state_event_content(
event: &SyncRoomMemberEvent,
) -> TimelineItemContent {
match event {
SyncRoomMemberEvent::Original(event) => {
let content = event.content.clone();
let prev_content = event.prev_content().cloned();
TimelineItemContent::room_member(
event.state_key.to_owned(),
FullStateEventContent::Original { content, prev_content },
event.sender.to_owned(),
)
}
SyncRoomMemberEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
}
}

/// Given some sticker content that is from an event that we have already
/// determined is suitable for use as a latest event in a message preview,
/// extract its contents and wrap it as a `TimelineItemContent`.
Expand Down
43 changes: 40 additions & 3 deletions crates/matrix-sdk-ui/src/timeline/event_item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ mod tests {
};

use super::{EventTimelineItem, Profile};
use crate::timeline::TimelineDetails;
use crate::timeline::{MembershipChange, TimelineDetails, TimelineItemContent};

#[async_test]
async fn test_latest_message_event_can_be_wrapped_as_a_timeline_item() {
Expand Down Expand Up @@ -764,6 +764,41 @@ mod tests {
}
}

#[async_test]
async fn test_latest_knock_member_state_event_can_be_wrapped_as_a_timeline_item() {
// Given a sync knock member state event that is suitable to be used as a
// latest_event

let room_id = room_id!("!q:x.uk");
let user_id = user_id!("@t:o.uk");
let raw_event = member_event_as_state_event(
room_id,
user_id,
"knock",
"Alice Margatroid",
"mxc://e.org/SEs",
);
let client = logged_in_client(None).await;

// When we construct a timeline event from it
let event = SyncTimelineEvent::new(raw_event.cast());
let timeline_item =
EventTimelineItem::from_latest_event(client, room_id, LatestEvent::new(event))
.await
.unwrap();

// Then its properties correctly translate
assert_eq!(timeline_item.sender, user_id);
assert_matches!(timeline_item.sender_profile, TimelineDetails::Unavailable);
assert_eq!(timeline_item.timestamp.0, UInt::new(143273583).unwrap());
if let TimelineItemContent::MembershipChange(change) = timeline_item.content {
assert_eq!(change.user_id, user_id);
assert_matches!(change.change, Some(MembershipChange::Knocked));
} else {
panic!("Unexpected state event type");
}
}

#[async_test]
async fn test_latest_message_includes_bundled_edit() {
// Given a sync event that is suitable to be used as a latest_event, and
Expand Down Expand Up @@ -885,6 +920,7 @@ mod tests {
room.required_state.push(member_event_as_state_event(
room_id,
user_id,
"join",
"Alice Margatroid",
"mxc://e.org/SEs",
));
Expand Down Expand Up @@ -987,6 +1023,7 @@ mod tests {
fn member_event_as_state_event(
room_id: &RoomId,
user_id: &UserId,
membership: &str,
display_name: &str,
avatar_url: &str,
) -> Raw<AnySyncStateEvent> {
Expand All @@ -995,13 +1032,13 @@ mod tests {
"content": {
"avatar_url": avatar_url,
"displayname": display_name,
"membership": "join",
"membership": membership,
"reason": ""
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 143273583,
"room_id": room_id,
"sender": "@example:example.org",
"sender": user_id,
"state_key": user_id,
"type": "m.room.member",
"unsigned": {
Expand Down

0 comments on commit a4519f2

Please sign in to comment.