Skip to content
Open
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
3 changes: 3 additions & 0 deletions bindings/matrix-sdk-ffi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ All notable changes to this project will be documented in this file.
`Room::new_latest_event` overwrites the `Room::latest_event` method. See the
documentation of `matrix_sdk::latest_event` to learn about the new API.
[#5624](https://github.com/matrix-org/matrix-rust-sdk/pull/5624/)
- Expose room power level thresholds in `OtherState::RoomPowerLevels` (ban, kick, invite, redact, state &
events defaults, per-event overrides, notifications), so clients can compute the required power level
for actions and compare with previous values.

## [0.16.0] - 2025-12-04

Expand Down
4 changes: 2 additions & 2 deletions bindings/matrix-sdk-ffi/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ where
Ok(original_content)
}

#[derive(Clone, uniffi::Enum)]
#[derive(Clone, uniffi::Enum, Eq, PartialEq, Hash)]
pub enum StateEventType {
CallMember,
PolicyRuleRoom,
Expand Down Expand Up @@ -355,7 +355,7 @@ impl From<StateEventType> for ruma::events::StateEventType {
}
}

#[derive(Clone, uniffi::Enum)]
#[derive(Clone, uniffi::Enum, Eq, PartialEq, Hash)]
pub enum MessageLikeEventType {
CallAnswer,
CallCandidates,
Expand Down
225 changes: 211 additions & 14 deletions bindings/matrix-sdk-ffi/src/timeline/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,127 @@ use matrix_sdk::room::power_levels::power_level_user_changes;
use matrix_sdk_ui::timeline::RoomPinnedEventsChange;
use ruma::events::{
room::history_visibility::HistoryVisibility as RumaHistoryVisibility, FullStateEventContent,
TimelineEventType as RumaTimelineEventType,
};

use crate::{client::JoinRule, timeline::msg_like::MsgLikeContent, utils::Timestamp};
use crate::{
client::JoinRule,
event::{MessageLikeEventType, StateEventType},
timeline::msg_like::MsgLikeContent,
utils::Timestamp,
};

#[derive(Clone, uniffi::Enum, Eq, PartialEq, Hash)]
pub enum FFITimelineEventType {
StateEvent { event: StateEventType },
MessageLike { event: MessageLikeEventType },
Custom { event_type: String },
}

impl From<RumaTimelineEventType> for FFITimelineEventType {
fn from(event_type: RumaTimelineEventType) -> Self {
// Avoid duplication and endless copy paste
fn message_like(e: MessageLikeEventType) -> FFITimelineEventType {
FFITimelineEventType::MessageLike { event: e }
}

fn state_event(e: StateEventType) -> FFITimelineEventType {
FFITimelineEventType::StateEvent { event: e }
}

match event_type {
// Message-like events
RumaTimelineEventType::CallAnswer => message_like(MessageLikeEventType::CallAnswer),
RumaTimelineEventType::CallInvite => message_like(MessageLikeEventType::CallInvite),
RumaTimelineEventType::CallHangup => message_like(MessageLikeEventType::CallHangup),
RumaTimelineEventType::RtcNotification => {
message_like(MessageLikeEventType::RtcNotification)
}
RumaTimelineEventType::CallCandidates => {
message_like(MessageLikeEventType::CallCandidates)
}
RumaTimelineEventType::KeyVerificationReady => {
message_like(MessageLikeEventType::KeyVerificationReady)
}
RumaTimelineEventType::KeyVerificationStart => {
message_like(MessageLikeEventType::KeyVerificationStart)
}
RumaTimelineEventType::KeyVerificationCancel => {
message_like(MessageLikeEventType::KeyVerificationCancel)
}
RumaTimelineEventType::KeyVerificationAccept => {
message_like(MessageLikeEventType::KeyVerificationAccept)
}
RumaTimelineEventType::KeyVerificationKey => {
message_like(MessageLikeEventType::KeyVerificationKey)
}
RumaTimelineEventType::KeyVerificationMac => {
message_like(MessageLikeEventType::KeyVerificationMac)
}
RumaTimelineEventType::KeyVerificationDone => {
message_like(MessageLikeEventType::KeyVerificationDone)
}
RumaTimelineEventType::Reaction => message_like(MessageLikeEventType::Reaction),
RumaTimelineEventType::RoomEncrypted => {
message_like(MessageLikeEventType::RoomEncrypted)
}
RumaTimelineEventType::RoomMessage => message_like(MessageLikeEventType::RoomMessage),
RumaTimelineEventType::RoomRedaction => {
message_like(MessageLikeEventType::RoomRedaction)
}
RumaTimelineEventType::Sticker => message_like(MessageLikeEventType::Sticker),
RumaTimelineEventType::PollEnd => message_like(MessageLikeEventType::PollEnd),
RumaTimelineEventType::PollResponse => message_like(MessageLikeEventType::PollResponse),
RumaTimelineEventType::PollStart => message_like(MessageLikeEventType::PollStart),
RumaTimelineEventType::UnstablePollEnd => {
message_like(MessageLikeEventType::UnstablePollEnd)
}
RumaTimelineEventType::UnstablePollResponse => {
message_like(MessageLikeEventType::UnstablePollResponse)
}
RumaTimelineEventType::UnstablePollStart => {
message_like(MessageLikeEventType::UnstablePollStart)
}
RumaTimelineEventType::CallMember => state_event(StateEventType::CallMember),

// State events
RumaTimelineEventType::PolicyRuleRoom => state_event(StateEventType::PolicyRuleRoom),
RumaTimelineEventType::PolicyRuleServer => {
state_event(StateEventType::PolicyRuleServer)
}
RumaTimelineEventType::PolicyRuleUser => state_event(StateEventType::PolicyRuleUser),
RumaTimelineEventType::RoomAliases => state_event(StateEventType::RoomAliases),
RumaTimelineEventType::RoomAvatar => state_event(StateEventType::RoomAvatar),
RumaTimelineEventType::RoomCanonicalAlias => {
state_event(StateEventType::RoomCanonicalAlias)
}
RumaTimelineEventType::RoomCreate => state_event(StateEventType::RoomCreate),
RumaTimelineEventType::RoomEncryption => state_event(StateEventType::RoomEncryption),
RumaTimelineEventType::RoomGuestAccess => state_event(StateEventType::RoomGuestAccess),
RumaTimelineEventType::RoomHistoryVisibility => {
state_event(StateEventType::RoomHistoryVisibility)
}
RumaTimelineEventType::RoomJoinRules => state_event(StateEventType::RoomJoinRules),
RumaTimelineEventType::RoomName => state_event(StateEventType::RoomName),
RumaTimelineEventType::RoomMember => state_event(StateEventType::RoomMemberEvent),
RumaTimelineEventType::RoomPinnedEvents => {
state_event(StateEventType::RoomPinnedEvents)
}
RumaTimelineEventType::RoomPowerLevels => state_event(StateEventType::RoomPowerLevels),
RumaTimelineEventType::RoomServerAcl => state_event(StateEventType::RoomServerAcl),
RumaTimelineEventType::RoomThirdPartyInvite => {
state_event(StateEventType::RoomThirdPartyInvite)
}
RumaTimelineEventType::RoomTombstone => state_event(StateEventType::RoomTombstone),
RumaTimelineEventType::RoomTopic => state_event(StateEventType::RoomTopic),
RumaTimelineEventType::SpaceChild => state_event(StateEventType::SpaceChild),
RumaTimelineEventType::SpaceParent => state_event(StateEventType::SpaceParent),

// Custom event types not covered above
_ => FFITimelineEventType::Custom { event_type: event_type.to_string() },
}
}
}

impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent {
fn from(value: matrix_sdk_ui::timeline::TimelineItemContent) -> Self {
Expand Down Expand Up @@ -242,29 +360,69 @@ impl From<matrix_sdk_ui::timeline::MembershipChange> for MembershipChange {
}
}

#[derive(Clone, uniffi::Record)]
pub struct PowerLevelChanges {
ban: i64,
kick: i64,
events_default: i64,
invite: i64,
redact: i64,
state_default: i64,
users_default: i64,
notifications: i64,
}

#[derive(Clone, uniffi::Enum)]
#[allow(clippy::large_enum_variant)]
// Added because the RoomPowerLevels variant is quite large.
// This is the same issue than for TimelineItemContent.
pub enum OtherState {
PolicyRuleRoom,
PolicyRuleServer,
PolicyRuleUser,
RoomAliases,
RoomAvatar { url: Option<String> },
RoomAvatar {
url: Option<String>,
},
RoomCanonicalAlias,
RoomCreate { federate: Option<bool> },
RoomCreate {
federate: Option<bool>,
},
RoomEncryption,
RoomGuestAccess,
RoomHistoryVisibility { history_visibility: Option<HistoryVisibility> },
RoomJoinRules { join_rule: Option<JoinRule> },
RoomName { name: Option<String> },
RoomPinnedEvents { change: RoomPinnedEventsChange },
RoomPowerLevels { users: HashMap<String, i64>, previous: Option<HashMap<String, i64>> },
RoomHistoryVisibility {
history_visibility: Option<HistoryVisibility>,
},
RoomJoinRules {
join_rule: Option<JoinRule>,
},
RoomName {
name: Option<String>,
},
RoomPinnedEvents {
change: RoomPinnedEventsChange,
},
RoomPowerLevels {
events: HashMap<FFITimelineEventType, i64>,
previous_events: Option<HashMap<FFITimelineEventType, i64>>,
users: HashMap<String, i64>,
previous: Option<HashMap<String, i64>>,
thresholds: Option<PowerLevelChanges>,
previous_thresholds: Option<PowerLevelChanges>,
},
RoomServerAcl,
RoomThirdPartyInvite { display_name: Option<String> },
RoomThirdPartyInvite {
display_name: Option<String>,
},
RoomTombstone,
RoomTopic { topic: Option<String> },
RoomTopic {
topic: Option<String>,
},
SpaceChild,
SpaceParent,
Custom { event_type: String },
Custom {
event_type: String,
},
}

impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherState {
Expand Down Expand Up @@ -330,6 +488,40 @@ impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherStat
Content::RoomPinnedEvents(c) => Self::RoomPinnedEvents { change: c.into() },
Content::RoomPowerLevels(c) => match c {
FullContent::Original { content, prev_content } => Self::RoomPowerLevels {
events: content
.events
.iter()
.map(|(k, &v)| (k.clone().into(), v.into()))
.collect(),
previous_events: prev_content.as_ref().map(|prev_content| {
prev_content
.events
.iter()
.map(|(k, &v)| (k.clone().into(), v.into()))
.collect()
}),
thresholds: Some(PowerLevelChanges {
ban: content.ban.into(),
kick: content.kick.into(),
events_default: content.events_default.into(),
invite: content.invite.into(),
redact: content.redact.into(),
state_default: content.state_default.into(),
users_default: content.users_default.into(),
notifications: content.notifications.room.into(),
}),
previous_thresholds: prev_content.as_ref().map(|prev_content| {
PowerLevelChanges {
ban: prev_content.ban.into(),
kick: prev_content.kick.into(),
events_default: prev_content.events_default.into(),
invite: prev_content.invite.into(),
redact: prev_content.redact.into(),
state_default: prev_content.state_default.into(),
users_default: prev_content.users_default.into(),
notifications: prev_content.notifications.room.into(),
}
}),
users: power_level_user_changes(content, prev_content)
.iter()
.map(|(k, v)| (k.to_string(), *v))
Expand All @@ -338,9 +530,14 @@ impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherStat
prev_content.users.iter().map(|(k, &v)| (k.to_string(), v.into())).collect()
}),
},
FullContent::Redacted(_) => {
Self::RoomPowerLevels { users: Default::default(), previous: None }
}
FullContent::Redacted(_) => Self::RoomPowerLevels {
events: Default::default(),
previous_events: None,
users: Default::default(),
previous: None,
thresholds: None,
previous_thresholds: None,
},
},
Content::RoomServerAcl(_) => Self::RoomServerAcl,
Content::RoomThirdPartyInvite(c) => {
Expand Down
Loading