Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bindings/matrix-sdk-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ crate-type = [
]

[features]
default = ["bundled-sqlite", "unstable-msc4274", "experimental-element-recent-emojis", "experimental-push-secrets", "experimental-search"]
default = ["bundled-sqlite", "unstable-msc4274", "unstable-msc4426", "experimental-element-recent-emojis", "experimental-push-secrets", "experimental-search"]
experimental-search = ["matrix-sdk/experimental-search", "matrix-sdk-ui/experimental-search"]
# Use SQLite for the session storage.
sqlite = ["matrix-sdk/sqlite"]
Expand All @@ -33,6 +33,7 @@ bundled-sqlite = ["sqlite", "matrix-sdk/bundled-sqlite"]
# Use IndexedDB for the session storage.
indexeddb = ["matrix-sdk/indexeddb"]
unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
unstable-msc4426 = ["matrix-sdk-ui/unstable-msc4426"]
# Required when targeting a Javascript environment, like Wasm in a browser.
js = ["matrix-sdk-ui/js"]
# Enable sentry error monitoring, not compatible with Wasm platforms.
Expand Down Expand Up @@ -88,6 +89,8 @@ ruma = { workspace = true, features = [
"unstable-msc2545",
# Room language event type
"unstable-msc4334",
# User status profile fields (m.status, m.call).
"unstable-msc4426",
"unstable-uniffi",
] }
serde.workspace = true
Expand Down
1 change: 1 addition & 0 deletions bindings/matrix-sdk-ffi/changelog.d/6616.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose [MSC4426] user-status profile fields through the FFI.
4 changes: 4 additions & 0 deletions bindings/matrix-sdk-ffi/changelog.d/6704.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add `status` and `call` fields to the FFI `RoomMember` and `ProfileDetails`,
exposing the user's
[MSC4426](https://github.com/matrix-org/matrix-spec-proposals/pull/4426) user
status and call indicator from their global profile.
60 changes: 59 additions & 1 deletion bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ use matrix_sdk_ui::{
};
use mime::Mime;
use oauth2::Scope;
#[cfg(feature = "unstable-msc4426")]
use ruma::api::client::profile::{Call, Status};
use ruma::{
OwnedDeviceId, OwnedMxcUri, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
api::{
Expand Down Expand Up @@ -125,6 +127,8 @@ use super::{
room::{Room, room_info::RoomInfo},
session_verification::SessionVerificationController,
};
#[cfg(feature = "unstable-msc4426")]
use crate::ruma::{UserCall, UserStatus};
use crate::{
ClientError,
authentication::{
Expand Down Expand Up @@ -2494,6 +2498,17 @@ pub struct UserProfile {
pub user_id: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,

/// The user's status (MSC4426 `m.status` profile field), if set.
#[cfg(feature = "unstable-msc4426")]
pub status: Option<UserStatus>,

/// Set when the user is in a call (MSC4426 `m.call` profile field).
///
/// `None` means the user is not in a call. `Some(UserCall { call_joined_ts:
/// None })` means the user is in a call but the join time wasn't recorded.
#[cfg(feature = "unstable-msc4426")]
pub call: Option<UserCall>,
}

impl UserProfile {
Expand All @@ -2504,7 +2519,20 @@ impl UserProfile {
let display_name = response.get_static::<DisplayName>()?;
let avatar_url = response.get_static::<AvatarUrl>()?.map(|url| url.to_string());

Ok(UserProfile { user_id: user_id.to_string(), display_name, avatar_url })
#[cfg(feature = "unstable-msc4426")]
let status = response.get_static::<Status>()?.map(UserStatus::from);
#[cfg(feature = "unstable-msc4426")]
let call = response.get_static::<Call>()?.map(UserCall::from);

Ok(UserProfile {
user_id: user_id.to_string(),
display_name,
avatar_url,
#[cfg(feature = "unstable-msc4426")]
status,
#[cfg(feature = "unstable-msc4426")]
call,
})
}
}

Expand All @@ -2514,10 +2542,40 @@ impl From<&search_users::v3::User> for UserProfile {
user_id: value.user_id.to_string(),
display_name: value.display_name.clone(),
avatar_url: value.avatar_url.as_ref().map(|url| url.to_string()),
#[cfg(feature = "unstable-msc4426")]
status: None,
#[cfg(feature = "unstable-msc4426")]
call: None,
}
}
}

#[cfg(feature = "unstable-msc4426")]
#[matrix_sdk_ffi_macros::export]
impl Client {
/// Set the current user's status (MSC4426 `m.status` profile field).
///
/// Replaces any existing status. Use [`Self::clear_user_status`] to
/// remove it.
pub async fn set_user_status(&self, status: UserStatus) -> Result<(), ClientError> {
self.inner.account().set_status(status.emoji, status.text).await?;
Ok(())
}

/// Clear the current user's status (MSC4426).
///
/// Deletes both `m.status` and `m.call` concurrently. Clearing `m.status`
/// alone would let `m.call` immediately reappear if the user were in a
/// call.
pub async fn clear_user_status(&self) -> Result<(), ClientError> {
let account = self.inner.account();
let (status_res, call_res) = tokio::join!(account.clear_status(), account.clear_call());
status_res?;
call_res?;
Ok(())
}
}

impl Client {
fn process_session_change(&self, session_change: SessionChange) {
if let Some(delegate_data) = self.delegate_data.get() {
Expand Down
10 changes: 10 additions & 0 deletions bindings/matrix-sdk-ffi/src/room_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use matrix_sdk::room::{RoomMember as SdkRoomMember, RoomMemberRole};
use ruma::{UserId, events::room::power_levels::UserPowerLevel};

use crate::error::{ClientError, NotYetImplemented};
#[cfg(feature = "unstable-msc4426")]
use crate::ruma::{UserCall, UserStatus};

#[derive(Clone, uniffi::Enum)]
pub enum MembershipState {
Expand Down Expand Up @@ -90,6 +92,10 @@ pub struct RoomMember {
pub user_id: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
#[cfg(feature = "unstable-msc4426")]
pub status: Option<UserStatus>,
#[cfg(feature = "unstable-msc4426")]
pub call: Option<UserCall>,
pub membership: MembershipState,
pub is_name_ambiguous: bool,
pub power_level: PowerLevel,
Expand All @@ -107,6 +113,10 @@ impl TryFrom<SdkRoomMember> for RoomMember {
user_id: m.user_id().to_string(),
display_name: m.display_name().map(|s| s.to_owned()),
avatar_url: m.avatar_url().map(|a| a.to_string()),
#[cfg(feature = "unstable-msc4426")]
status: m.status().cloned().map(UserStatus::from),
#[cfg(feature = "unstable-msc4426")]
call: m.call().cloned().map(UserCall::from),
membership: m.membership().clone().try_into()?,
is_name_ambiguous: m.name_ambiguous(),
power_level: m.power_level().try_into()?,
Expand Down
55 changes: 55 additions & 0 deletions bindings/matrix-sdk-ffi/src/ruma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ use ruma::{
},
serde::JsonObject,
};
#[cfg(feature = "unstable-msc4426")]
use ruma::{
SecondsSinceUnixEpoch,
profile::{CallProfileField, StatusProfileField},
};
use tracing::info;

use crate::{
Expand Down Expand Up @@ -220,6 +225,56 @@ impl From<RumaPresenceState> for PresenceState {
}
}

/// A user-set status (MSC4426 `m.status` profile field value).
#[cfg(feature = "unstable-msc4426")]
#[derive(Debug, Clone, uniffi::Record)]
pub struct UserStatus {
pub emoji: String,
pub text: String,
}

#[cfg(feature = "unstable-msc4426")]
impl From<UserStatus> for StatusProfileField {
fn from(value: UserStatus) -> Self {
Self::new(value.text, value.emoji)
}
}

#[cfg(feature = "unstable-msc4426")]
impl From<StatusProfileField> for UserStatus {
fn from(value: StatusProfileField) -> Self {
Self { emoji: value.emoji, text: value.text }
}
}

/// The user's call indicator (MSC4426 `m.call` profile field value).
///
/// Presence of a `UserCall` value means the user is in a call. The optional
/// `call_joined_ts` is the Unix-epoch seconds when they joined, if known.
#[cfg(feature = "unstable-msc4426")]
#[derive(Debug, Clone, uniffi::Record)]
pub struct UserCall {
pub call_joined_ts: Option<u64>,
}

#[cfg(feature = "unstable-msc4426")]
impl From<CallProfileField> for UserCall {
fn from(value: CallProfileField) -> Self {
Self { call_joined_ts: value.call_joined_ts.map(|ts| u64::from(ts.get())) }
}
}

#[cfg(feature = "unstable-msc4426")]
impl From<UserCall> for CallProfileField {
fn from(value: UserCall) -> Self {
let mut field = CallProfileField::new();
field.call_joined_ts = value
.call_joined_ts
.map(|secs| SecondsSinceUnixEpoch(UInt::try_from(secs).unwrap_or_default()));
field
}
}

#[matrix_sdk_ffi_macros::export]
pub fn message_event_content_new(
msgtype: MessageType,
Expand Down
24 changes: 22 additions & 2 deletions bindings/matrix-sdk-ffi/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ use tracing::{error, warn};
use uuid::Uuid;

pub use self::{content::TimelineItemContent, msg_like::MessageContent};
#[cfg(feature = "unstable-msc4426")]
use crate::ruma::{UserCall, UserStatus};
use crate::{
error::{ClientError, RoomError},
event::EventOrTransactionId,
Expand Down Expand Up @@ -1075,8 +1077,18 @@ pub struct EventTimelineItemDebugInfo {
pub enum ProfileDetails {
Unavailable,
Pending,
Ready { display_name: Option<String>, display_name_ambiguous: bool, avatar_url: Option<String> },
Error { message: String },
Ready {
display_name: Option<String>,
display_name_ambiguous: bool,
avatar_url: Option<String>,
#[cfg(feature = "unstable-msc4426")]
status: Option<UserStatus>,
#[cfg(feature = "unstable-msc4426")]
call: Option<UserCall>,
},
Error {
message: String,
},
}

impl From<TimelineDetails<Profile>> for ProfileDetails {
Expand All @@ -1088,6 +1100,10 @@ impl From<TimelineDetails<Profile>> for ProfileDetails {
display_name: profile.display_name,
display_name_ambiguous: profile.display_name_ambiguous,
avatar_url: profile.avatar_url.as_ref().map(ToString::to_string),
#[cfg(feature = "unstable-msc4426")]
status: profile.status.map(UserStatus::from),
#[cfg(feature = "unstable-msc4426")]
call: profile.call.map(UserCall::from),
},
TimelineDetails::Error(e) => Self::Error { message: e.to_string() },
}
Expand All @@ -1103,6 +1119,10 @@ impl From<&TimelineDetails<Profile>> for ProfileDetails {
display_name: profile.display_name.clone(),
display_name_ambiguous: profile.display_name_ambiguous,
avatar_url: profile.avatar_url.as_ref().map(ToString::to_string),
#[cfg(feature = "unstable-msc4426")]
status: profile.status.clone().map(UserStatus::from),
#[cfg(feature = "unstable-msc4426")]
call: profile.call.clone().map(UserCall::from),
},
TimelineDetails::Error(e) => Self::Error { message: e.to_string() },
}
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ testing = [
# Add support for inline media galleries via msgtypes
unstable-msc4274 = []

# Add support for user status profile fields (m.status, m.call).
unstable-msc4426 = ["ruma/unstable-msc4426"]

experimental-element-recent-emojis = []

[dependencies]
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk-base/changelog.d/6704.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Surface the [MSC4426](https://github.com/matrix-org/matrix-spec-proposals/pull/4426)
user status and call indicator on `RoomMember`, sourced from the user's global
profile. Read them with `RoomMember::status()` and `RoomMember::call()`.
Loading
Loading