Skip to content

Commit

Permalink
Add support for MSC2867 - Manually marking rooms as unread
Browse files Browse the repository at this point in the history
- also fixes how room account data is processed and adds tests for both when rooms and extensions are present or just extensions
  • Loading branch information
stefanceriu committed Jan 31, 2024
1 parent a356a20 commit 3f21678
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 25 deletions.
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ futures-executor = "0.3.21"
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
http = "0.2.6"
itertools = "0.12.0"
ruma = { git = "https://github.com/ruma/ruma", rev = "684ffc789877355bd25269451b2356817c17cc3f", features = ["client-api-c", "compat-upload-signatures", "compat-user-id", "compat-arbitrary-length-ids", "unstable-msc3401"] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "684ffc789877355bd25269451b2356817c17cc3f"}
ruma = { git = "https://github.com/ruma/ruma", rev = "68c9bb0930f2195fa8672fbef9633ef62737df5d", features = ["client-api-c", "compat-upload-signatures", "compat-user-id", "compat-arbitrary-length-ids", "unstable-msc3401", "unstable-msc2867"] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "68c9bb0930f2195fa8672fbef9633ef62737df5d"}
once_cell = "1.16.0"
rand = "0.8.5"
serde = "1.0.151"
Expand Down
34 changes: 33 additions & 1 deletion bindings/matrix-sdk-ffi/src/room.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
room_info::RoomInfo,
room_member::{MessageLikeEventType, RoomMember, StateEventType},
ruma::ImageInfo,
timeline::{EventTimelineItem, Timeline},
timeline::{EventTimelineItem, ReceiptType, Timeline},
utils::u64_to_uint,
TaskHandle,
};
Expand Down Expand Up @@ -516,6 +516,38 @@ impl Room {
pub async fn typing_notice(&self, is_typing: bool) -> Result<(), ClientError> {
Ok(self.inner.typing_notice(is_typing).await?)
}

/// Sets a flag on the room to indicate that the user has explicitly marked
/// it as unread
pub async fn mark_as_unread(&self) -> Result<(), ClientError> {
Ok(self.inner.mark_unread(true).await?)
}

/// Reverts a previously set unread flag.
pub async fn mark_as_read(&self) -> Result<(), ClientError> {
Ok(self.inner.mark_unread(false).await?)
}

/// Reverts a previously set unread flag and sends a read receipt to the
/// latest event in the room. Sending read receipts is useful when
/// executing this from the room list but shouldn't be use when entering
/// the room, the timeline should be left to its own devices in that
/// case.
pub async fn mark_as_read_and_send_read_receipt(
&self,
receipt_type: ReceiptType,
) -> Result<(), ClientError> {
let timeline = self.timeline().await?;

if let Some(event) = timeline.latest_event().await {
if let Err(error) = timeline.send_read_receipt(receipt_type, event.event_id().unwrap())
{
error!("Failed to send read receipt: {error}");
}
}

self.mark_as_read().await
}
}

#[uniffi::export(callback_interface)]
Expand Down
3 changes: 3 additions & 0 deletions bindings/matrix-sdk-ffi/src/room_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct RoomInfo {
user_defined_notification_mode: Option<RoomNotificationMode>,
has_room_call: bool,
active_room_call_participants: Vec<String>,
/// Whether this room has been explicitly marked as unread
is_marked_unread: bool,
/// "Interesting" messages received in that room, independently of the
/// notification settings.
num_unread_messages: u64,
Expand Down Expand Up @@ -84,6 +86,7 @@ impl RoomInfo {
.iter()
.map(|u| u.to_string())
.collect(),
is_marked_unread: room.is_marked_unread(),
num_unread_messages: room.num_unread_messages(),
num_unread_notifications: room.num_unread_notifications(),
num_unread_mentions: room.num_unread_mentions(),
Expand Down
6 changes: 6 additions & 0 deletions bindings/matrix-sdk-ffi/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,12 @@ impl Timeline {
Ok(Arc::new(RoomMessageEventContentWithoutRelation::new(msgtype)))
})
}

pub async fn latest_event(&self) -> Option<Arc<EventTimelineItem>> {
let latest_event = self.inner.latest_event().await;

latest_event.map(|item| Arc::new(EventTimelineItem(item)))
}
}

#[derive(uniffi::Record)]
Expand Down
16 changes: 15 additions & 1 deletion crates/matrix-sdk-base/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,21 @@ impl BaseClient {
) {
for raw_event in events {
if let Ok(event) = raw_event.deserialize() {
changes.add_room_account_data(room_id, event, raw_event.clone());
changes.add_room_account_data(room_id, event.clone(), raw_event.clone());

// Rooms can either appear in the current request or already be
// known to the store. If neither of
// those are true then the room is `unknown` and we cannot
// process its account data
if let AnyRoomAccountDataEvent::MarkedUnread(e) = event {
if let Some(room) = changes.room_infos.get_mut(room_id) {
room.base_info.is_marked_unread = e.content.unread;
} else if let Some(room) = self.store.get_room(room_id) {
let mut info = room.clone_info();
info.base_info.is_marked_unread = e.content.unread;
changes.add_room(info);
}
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/matrix-sdk-base/src/rooms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub struct BaseRoomInfo {
/// memberships.
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub(crate) rtc_member: BTreeMap<OwnedUserId, MinimalStateEvent<CallMemberEventContent>>,
// Whether this room has been manually marked as unread
#[serde(default)]
pub(crate) is_marked_unread: bool,
}

impl BaseRoomInfo {
Expand Down Expand Up @@ -317,6 +320,7 @@ impl Default for BaseRoomInfo {
tombstone: None,
topic: None,
rtc_member: BTreeMap::new(),
is_marked_unread: false,
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions crates/matrix-sdk-base/src/rooms/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,12 @@ impl Room {
.get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
.await
}

/// Returns a boolean indicating if this room has been manually marked as
/// unread
pub fn is_marked_unread(&self) -> bool {
self.inner.read().base_info.is_marked_unread
}
}

/// The underlying pure data structure for joined and left rooms.
Expand Down Expand Up @@ -1382,6 +1388,7 @@ mod tests {
"encryption": null,
"guest_access": null,
"history_visibility": null,
"is_marked_unread": false,
"join_rules": null,
"max_power_level": 100,
"name": null,
Expand Down
12 changes: 3 additions & 9 deletions crates/matrix-sdk-base/src/sliding_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,7 @@ impl BaseClient {
.push(raw.clone().cast());
}

// Handles the remaining rooms account data not handled by
// process_sliding_sync_room.
// Handle room account data
for (room_id, raw) in &rooms_account_data {
self.handle_room_account_data(room_id, raw, &mut changes).await;

Expand Down Expand Up @@ -358,13 +357,6 @@ impl BaseClient {
.await?;
}

let room_account_data = if let Some(events) = rooms_account_data.remove(room_id) {
self.handle_room_account_data(room_id, &events, changes).await;
Some(events)
} else {
None
};

process_room_properties(room_data, &mut room_info);

let timeline = self
Expand Down Expand Up @@ -408,6 +400,8 @@ impl BaseClient {
let notification_count = room_data.unread_notifications.clone().into();
room_info.update_notification_count(notification_count);

let room_account_data = rooms_account_data.get(room_id).cloned();

match room_info.state() {
RoomState::Joined => {
// Ephemeral events are added separately, because we might not
Expand Down
1 change: 1 addition & 0 deletions crates/matrix-sdk-base/src/store/migration_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ impl BaseRoomInfoV1 {
tombstone,
topic,
rtc_member: BTreeMap::new(),
is_marked_unread: false,
})
}
}
Expand Down
24 changes: 21 additions & 3 deletions crates/matrix-sdk/src/room/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ use matrix_sdk_common::timeout::timeout;
use mime::Mime;
#[cfg(feature = "e2e-encryption")]
use ruma::events::{
room::encrypted::OriginalSyncRoomEncryptedEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
SyncMessageLikeEvent,
marked_unread::MarkedUnreadEventContent, room::encrypted::OriginalSyncRoomEncryptedEvent,
AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent,
};
use ruma::{
api::client::{
config::set_global_account_data,
config::{set_global_account_data, set_room_account_data},
context,
error::ErrorKind,
filter::LazyLoadOptions,
Expand Down Expand Up @@ -2394,6 +2394,24 @@ impl Room {
);
Ok(self.client.send(request, None).await?)
}

/// Sets a flag on the room to indicate that the user has explicitly marked
/// it as (un)read
pub async fn mark_unread(&self, unread: bool) -> Result<()> {
let user_id =
self.client.user_id().ok_or_else(|| Error::from(HttpError::AuthenticationRequired))?;

let content = MarkedUnreadEventContent::new(unread);

let request = set_room_account_data::v3::Request::new(
user_id.to_owned(),
self.inner.room_id().to_owned(),
&content,
)?;

self.client.send(request, None).await?;
Ok(())
}
}

/// Details of the (latest) invite.
Expand Down
Loading

0 comments on commit 3f21678

Please sign in to comment.