diff --git a/bindings/matrix-sdk-ffi/CHANGELOG.md b/bindings/matrix-sdk-ffi/CHANGELOG.md index cdcff0e5d5e..e67e794ffcc 100644 --- a/bindings/matrix-sdk-ffi/CHANGELOG.md +++ b/bindings/matrix-sdk-ffi/CHANGELOG.md @@ -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 diff --git a/bindings/matrix-sdk-ffi/src/event.rs b/bindings/matrix-sdk-ffi/src/event.rs index 6f5f6d40e8a..97c7e175335 100644 --- a/bindings/matrix-sdk-ffi/src/event.rs +++ b/bindings/matrix-sdk-ffi/src/event.rs @@ -300,7 +300,7 @@ where Ok(original_content) } -#[derive(Clone, uniffi::Enum)] +#[derive(Clone, uniffi::Enum, Eq, PartialEq, Hash)] pub enum StateEventType { CallMember, PolicyRuleRoom, @@ -355,7 +355,7 @@ impl From for ruma::events::StateEventType { } } -#[derive(Clone, uniffi::Enum)] +#[derive(Clone, uniffi::Enum, Eq, PartialEq, Hash)] pub enum MessageLikeEventType { CallAnswer, CallCandidates, diff --git a/bindings/matrix-sdk-ffi/src/timeline/content.rs b/bindings/matrix-sdk-ffi/src/timeline/content.rs index 4b1b686881b..a8536997f0a 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/content.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/content.rs @@ -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 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 for TimelineItemContent { fn from(value: matrix_sdk_ui::timeline::TimelineItemContent) -> Self { @@ -242,29 +360,69 @@ impl From 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 }, + RoomAvatar { + url: Option, + }, RoomCanonicalAlias, - RoomCreate { federate: Option }, + RoomCreate { + federate: Option, + }, RoomEncryption, RoomGuestAccess, - RoomHistoryVisibility { history_visibility: Option }, - RoomJoinRules { join_rule: Option }, - RoomName { name: Option }, - RoomPinnedEvents { change: RoomPinnedEventsChange }, - RoomPowerLevels { users: HashMap, previous: Option> }, + RoomHistoryVisibility { + history_visibility: Option, + }, + RoomJoinRules { + join_rule: Option, + }, + RoomName { + name: Option, + }, + RoomPinnedEvents { + change: RoomPinnedEventsChange, + }, + RoomPowerLevels { + events: HashMap, + previous_events: Option>, + users: HashMap, + previous: Option>, + thresholds: Option, + previous_thresholds: Option, + }, RoomServerAcl, - RoomThirdPartyInvite { display_name: Option }, + RoomThirdPartyInvite { + display_name: Option, + }, RoomTombstone, - RoomTopic { topic: Option }, + RoomTopic { + topic: Option, + }, SpaceChild, SpaceParent, - Custom { event_type: String }, + Custom { + event_type: String, + }, } impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherState { @@ -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)) @@ -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) => {