use std::{collections::HashMap, pin::pin, sync::Arc};
use anyhow::{Context, Result};
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::{
crypto::LocalTrust,
event_cache::paginator::PaginatorError,
room::{
edit::EditedContent, power_levels::RoomPowerLevelChanges, Room as SdkRoom, RoomMemberRole,
},
ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType,
RoomHero as SdkRoomHero, RoomMemberships, RoomState,
};
use matrix_sdk_ui::timeline::{default_event_filter, PaginationError, RoomExt, TimelineFocus};
use mime::Mime;
use ruma::{
api::client::room::report_content,
assign,
events::{
call::notify,
room::{
avatar::ImageInfo as RumaAvatarImageInfo,
message::RoomMessageEventContentWithoutRelation,
power_levels::RoomPowerLevels as RumaPowerLevels, MediaSource,
},
AnyMessageLikeEventContent, AnySyncTimelineEvent, TimelineEventType,
},
EventId, Int, OwnedDeviceId, OwnedUserId, RoomAliasId, UserId,
};
use tokio::sync::RwLock;
use tracing::error;
use super::RUNTIME;
use crate::{
chunk_iterator::ChunkIterator,
error::{ClientError, MediaInfoError, RoomError},
event::{MessageLikeEventType, RoomMessageEventMessageType, StateEventType},
identity_status_change::IdentityStatusChange,
room_info::RoomInfo,
room_member::RoomMember,
ruma::{ImageInfo, Mentions, NotifyType},
timeline::{DateDividerMode, FocusEventError, ReceiptType, SendHandle, Timeline},
utils::u64_to_uint,
TaskHandle,
};
#[derive(Debug, Clone, uniffi::Enum)]
pub enum Membership {
Invited,
Joined,
Left,
Knocked,
Banned,
}
impl From<RoomState> for Membership {
fn from(value: RoomState) -> Self {
match value {
RoomState::Invited => Membership::Invited,
RoomState::Joined => Membership::Joined,
RoomState::Left => Membership::Left,
RoomState::Knocked => Membership::Knocked,
RoomState::Banned => Membership::Banned,
}
}
}
pub(crate) type TimelineLock = Arc<RwLock<Option<Arc<Timeline>>>>;
#[derive(uniffi::Object)]
pub struct Room {
pub(super) inner: SdkRoom,
timeline: TimelineLock,
}
impl Room {
pub(crate) fn new(inner: SdkRoom) -> Self {
Room { inner, timeline: Default::default() }
}
pub(crate) fn with_timeline(inner: SdkRoom, timeline: TimelineLock) -> Self {
Room { inner, timeline }
}
}
#[matrix_sdk_ffi_macros::export]
impl Room {
pub fn id(&self) -> String {
self.inner.room_id().to_string()
}
pub fn display_name(&self) -> Option<String> {
Some(self.inner.cached_display_name()?.to_string())
}
pub fn raw_name(&self) -> Option<String> {
self.inner.name()
}
pub fn topic(&self) -> Option<String> {
self.inner.topic()
}
pub fn avatar_url(&self) -> Option<String> {
self.inner.avatar_url().map(|m| m.to_string())
}
pub fn is_direct(&self) -> bool {
RUNTIME.block_on(self.inner.is_direct()).unwrap_or(false)
}
pub fn is_public(&self) -> bool {
self.inner.is_public()
}
pub fn is_space(&self) -> bool {
self.inner.is_space()
}
pub fn is_tombstoned(&self) -> bool {
self.inner.is_tombstoned()
}
pub fn canonical_alias(&self) -> Option<String> {
self.inner.canonical_alias().map(|a| a.to_string())
}
pub fn alternative_aliases(&self) -> Vec<String> {
self.inner.alt_aliases().iter().map(|a| a.to_string()).collect()
}
pub fn membership(&self) -> Membership {
self.inner.state().into()
}
pub fn heroes(&self) -> Vec<RoomHero> {
self.inner.heroes().into_iter().map(Into::into).collect()
}
pub fn has_active_room_call(&self) -> bool {
self.inner.has_active_room_call()
}
pub fn active_room_call_participants(&self) -> Vec<String> {
self.inner.active_room_call_participants().iter().map(|u| u.to_string()).collect()
}
pub async fn inviter(&self) -> Option<RoomMember> {
if self.inner.state() == RoomState::Invited {
self.inner
.invite_details()
.await
.ok()
.and_then(|a| a.inviter)
.and_then(|m| m.try_into().ok())
} else {
None
}
}
pub async fn discard_room_key(&self) -> Result<(), ClientError> {
self.inner.discard_room_key().await?;
Ok(())
}
pub async fn timeline(&self) -> Result<Arc<Timeline>, ClientError> {
let mut write_guard = self.timeline.write().await;
if let Some(timeline) = &*write_guard {
Ok(timeline.clone())
} else {
let timeline = Timeline::new(self.inner.timeline().await?);
*write_guard = Some(timeline.clone());
Ok(timeline)
}
}
pub async fn timeline_focused_on_event(
&self,
event_id: String,
num_context_events: u16,
internal_id_prefix: Option<String>,
) -> Result<Arc<Timeline>, FocusEventError> {
let parsed_event_id = EventId::parse(&event_id).map_err(|err| {
FocusEventError::InvalidEventId { event_id: event_id.clone(), err: err.to_string() }
})?;
let room = &self.inner;
let mut builder = matrix_sdk_ui::timeline::Timeline::builder(room);
if let Some(internal_id_prefix) = internal_id_prefix {
builder = builder.with_internal_id_prefix(internal_id_prefix);
}
let timeline = match builder
.with_focus(TimelineFocus::Event { target: parsed_event_id, num_context_events })
.build()
.await
{
Ok(t) => t,
Err(err) => {
if let matrix_sdk_ui::timeline::Error::PaginationError(
PaginationError::Paginator(PaginatorError::EventNotFound(..)),
) = err
{
return Err(FocusEventError::EventNotFound { event_id: event_id.to_string() });
}
return Err(FocusEventError::Other { msg: err.to_string() });
}
};
Ok(Timeline::new(timeline))
}
pub async fn pinned_events_timeline(
&self,
internal_id_prefix: Option<String>,
max_events_to_load: u16,
max_concurrent_requests: u16,
) -> Result<Arc<Timeline>, ClientError> {
let room = &self.inner;
let mut builder = matrix_sdk_ui::timeline::Timeline::builder(room);
if let Some(internal_id_prefix) = internal_id_prefix {
builder = builder.with_internal_id_prefix(internal_id_prefix);
}
let timeline = builder
.with_focus(TimelineFocus::PinnedEvents { max_events_to_load, max_concurrent_requests })
.build()
.await?;
Ok(Timeline::new(timeline))
}
pub async fn message_filtered_timeline(
&self,
internal_id_prefix: Option<String>,
allowed_message_types: Vec<RoomMessageEventMessageType>,
date_divider_mode: DateDividerMode,
) -> Result<Arc<Timeline>, ClientError> {
let mut builder = matrix_sdk_ui::timeline::Timeline::builder(&self.inner);
if let Some(internal_id_prefix) = internal_id_prefix {
builder = builder.with_internal_id_prefix(internal_id_prefix);
}
builder = builder.with_date_divider_mode(date_divider_mode.into());
builder = builder.event_filter(move |event, room_version_id| {
default_event_filter(event, room_version_id)
&& match event {
AnySyncTimelineEvent::MessageLike(msg) => match msg.original_content() {
Some(AnyMessageLikeEventContent::RoomMessage(content)) => {
allowed_message_types.contains(&content.msgtype.into())
}
_ => false,
},
_ => false,
}
});
let timeline = builder.build().await?;
Ok(Timeline::new(timeline))
}
pub fn is_encrypted(&self) -> Result<bool, ClientError> {
Ok(RUNTIME.block_on(self.inner.is_encrypted())?)
}
pub async fn members(&self) -> Result<Arc<RoomMembersIterator>, ClientError> {
Ok(Arc::new(RoomMembersIterator::new(self.inner.members(RoomMemberships::empty()).await?)))
}
pub async fn members_no_sync(&self) -> Result<Arc<RoomMembersIterator>, ClientError> {
Ok(Arc::new(RoomMembersIterator::new(
self.inner.members_no_sync(RoomMemberships::empty()).await?,
)))
}
pub async fn member(&self, user_id: String) -> Result<RoomMember, ClientError> {
let user_id = UserId::parse(&*user_id).context("Invalid user id.")?;
let member = self.inner.get_member(&user_id).await?.context("User not found")?;
Ok(member.try_into().context("Unknown state membership")?)
}
pub async fn member_avatar_url(&self, user_id: String) -> Result<Option<String>, ClientError> {
let user_id = UserId::parse(&*user_id).context("Invalid user id.")?;
let member = self.inner.get_member(&user_id).await?.context("User not found")?;
let avatar_url_string = member.avatar_url().map(|m| m.to_string());
Ok(avatar_url_string)
}
pub async fn member_display_name(
&self,
user_id: String,
) -> Result<Option<String>, ClientError> {
let user_id = UserId::parse(&*user_id).context("Invalid user id.")?;
let member = self.inner.get_member(&user_id).await?.context("User not found")?;
let avatar_url_string = member.display_name().map(|m| m.to_owned());
Ok(avatar_url_string)
}
pub async fn room_info(&self) -> Result<RoomInfo, ClientError> {
Ok(RoomInfo::new(&self.inner).await?)
}
pub fn subscribe_to_room_info_updates(
self: Arc<Self>,
listener: Box<dyn RoomInfoListener>,
) -> Arc<TaskHandle> {
let mut subscriber = self.inner.subscribe_info();
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
while subscriber.next().await.is_some() {
match self.room_info().await {
Ok(room_info) => listener.call(room_info),
Err(e) => {
error!("Failed to compute new RoomInfo: {e}");
}
}
}
})))
}
pub async fn set_is_favourite(
&self,
is_favourite: bool,
tag_order: Option<f64>,
) -> Result<(), ClientError> {
self.inner.set_is_favourite(is_favourite, tag_order).await?;
Ok(())
}
pub async fn set_is_low_priority(
&self,
is_low_priority: bool,
tag_order: Option<f64>,
) -> Result<(), ClientError> {
self.inner.set_is_low_priority(is_low_priority, tag_order).await?;
Ok(())
}
pub async fn send_raw(&self, event_type: String, content: String) -> Result<(), ClientError> {
let content_json: serde_json::Value = serde_json::from_str(&content)
.map_err(|e| ClientError::Generic { msg: format!("Failed to parse JSON: {e}") })?;
self.inner.send_raw(&event_type, content_json).await?;
Ok(())
}
pub async fn redact(
&self,
event_id: String,
reason: Option<String>,
) -> Result<(), ClientError> {
let event_id = EventId::parse(event_id)?;
self.inner.redact(&event_id, reason.as_deref(), None).await?;
Ok(())
}
pub fn active_members_count(&self) -> u64 {
self.inner.active_members_count()
}
pub fn invited_members_count(&self) -> u64 {
self.inner.invited_members_count()
}
pub fn joined_members_count(&self) -> u64 {
self.inner.joined_members_count()
}
pub async fn report_content(
&self,
event_id: String,
score: Option<i32>,
reason: Option<String>,
) -> Result<(), ClientError> {
let event_id = EventId::parse(event_id)?;
let int_score = score.map(|value| value.into());
self.inner
.client()
.send(report_content::v3::Request::new(
self.inner.room_id().into(),
event_id,
int_score,
reason,
))
.await?;
Ok(())
}
pub async fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
let user_id = UserId::parse(user_id)?;
self.inner.client().account().ignore_user(&user_id).await?;
Ok(())
}
pub async fn leave(&self) -> Result<(), ClientError> {
self.inner.leave().await?;
Ok(())
}
pub async fn join(&self) -> Result<(), ClientError> {
self.inner.join().await?;
Ok(())
}
pub async fn set_name(&self, name: String) -> Result<(), ClientError> {
self.inner.set_name(name).await?;
Ok(())
}
pub async fn set_topic(&self, topic: String) -> Result<(), ClientError> {
self.inner.set_room_topic(&topic).await?;
Ok(())
}
pub async fn upload_avatar(
&self,
mime_type: String,
data: Vec<u8>,
media_info: Option<ImageInfo>,
) -> Result<(), ClientError> {
let mime: Mime = mime_type.parse()?;
self.inner
.upload_avatar(
&mime,
data,
media_info
.map(TryInto::try_into)
.transpose()
.map_err(|_| RoomError::InvalidMediaInfo)?,
)
.await?;
Ok(())
}
pub async fn remove_avatar(&self) -> Result<(), ClientError> {
self.inner.remove_avatar().await?;
Ok(())
}
pub async fn invite_user_by_id(&self, user_id: String) -> Result<(), ClientError> {
let user =
<&UserId>::try_from(user_id.as_str()).context("Could not create user from string")?;
self.inner.invite_user_by_id(user).await?;
Ok(())
}
pub async fn can_user_redact_own(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_redact_own(&user_id).await?)
}
pub async fn can_user_redact_other(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_redact_other(&user_id).await?)
}
pub async fn can_user_ban(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_ban(&user_id).await?)
}
pub async fn ban_user(
&self,
user_id: String,
reason: Option<String>,
) -> Result<(), ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.ban_user(&user_id, reason.as_deref()).await?)
}
pub async fn unban_user(
&self,
user_id: String,
reason: Option<String>,
) -> Result<(), ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.unban_user(&user_id, reason.as_deref()).await?)
}
pub async fn can_user_invite(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_invite(&user_id).await?)
}
pub async fn can_user_kick(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_kick(&user_id).await?)
}
pub async fn kick_user(
&self,
user_id: String,
reason: Option<String>,
) -> Result<(), ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.kick_user(&user_id, reason.as_deref()).await?)
}
pub async fn can_user_send_state(
&self,
user_id: String,
state_event: StateEventType,
) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_send_state(&user_id, state_event.into()).await?)
}
pub async fn can_user_send_message(
&self,
user_id: String,
message: MessageLikeEventType,
) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_send_message(&user_id, message.into()).await?)
}
pub async fn can_user_pin_unpin(&self, user_id: String) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_pin_unpin(&user_id).await?)
}
pub async fn can_user_trigger_room_notification(
&self,
user_id: String,
) -> Result<bool, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.can_user_trigger_room_notification(&user_id).await?)
}
pub fn own_user_id(&self) -> String {
self.inner.own_user_id().to_string()
}
pub async fn typing_notice(&self, is_typing: bool) -> Result<(), ClientError> {
Ok(self.inner.typing_notice(is_typing).await?)
}
pub fn subscribe_to_typing_notifications(
self: Arc<Self>,
listener: Box<dyn TypingNotificationsListener>,
) -> Arc<TaskHandle> {
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
let (_event_handler_drop_guard, mut subscriber) =
self.inner.subscribe_to_typing_notifications();
while let Ok(typing_user_ids) = subscriber.recv().await {
let typing_user_ids =
typing_user_ids.into_iter().map(|user_id| user_id.to_string()).collect();
listener.call(typing_user_ids);
}
})))
}
pub fn subscribe_to_identity_status_changes(
&self,
listener: Box<dyn IdentityStatusChangeListener>,
) -> Arc<TaskHandle> {
let room = self.inner.clone();
Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
let status_changes = room.subscribe_to_identity_status_changes().await;
if let Ok(status_changes) = status_changes {
let mut status_changes = pin!(status_changes);
while let Some(identity_status_changes) = status_changes.next().await {
listener.call(
identity_status_changes
.into_iter()
.map(|change| {
let user_id = change.user_id.to_string();
IdentityStatusChange { user_id, changed_to: change.changed_to }
})
.collect(),
);
}
}
})))
}
pub async fn set_unread_flag(&self, new_value: bool) -> Result<(), ClientError> {
Ok(self.inner.set_unread_flag(new_value).await?)
}
pub async fn mark_as_read(&self, receipt_type: ReceiptType) -> Result<(), ClientError> {
let timeline = self.timeline().await?;
timeline.mark_as_read(receipt_type).await?;
Ok(())
}
pub async fn get_power_levels(&self) -> Result<RoomPowerLevels, ClientError> {
let power_levels = self.inner.power_levels().await.map_err(matrix_sdk::Error::from)?;
Ok(RoomPowerLevels::from(power_levels))
}
pub async fn apply_power_level_changes(
&self,
changes: RoomPowerLevelChanges,
) -> Result<(), ClientError> {
self.inner.apply_power_level_changes(changes).await?;
Ok(())
}
pub async fn update_power_levels_for_users(
&self,
updates: Vec<UserPowerLevelUpdate>,
) -> Result<(), ClientError> {
let updates = updates
.iter()
.map(|update| {
let user_id: &UserId = update.user_id.as_str().try_into()?;
let power_level = Int::new(update.power_level).context("Invalid power level")?;
Ok((user_id, power_level))
})
.collect::<Result<Vec<_>>>()?;
self.inner
.update_power_levels(updates)
.await
.map_err(|e| ClientError::Generic { msg: e.to_string() })?;
Ok(())
}
pub async fn suggested_role_for_user(
&self,
user_id: String,
) -> Result<RoomMemberRole, ClientError> {
let user_id = UserId::parse(&user_id)?;
Ok(self.inner.get_suggested_user_role(&user_id).await?)
}
pub async fn reset_power_levels(&self) -> Result<RoomPowerLevels, ClientError> {
Ok(RoomPowerLevels::from(self.inner.reset_power_levels().await?))
}
pub async fn matrix_to_permalink(&self) -> Result<String, ClientError> {
Ok(self.inner.matrix_to_permalink().await?.to_string())
}
pub async fn matrix_to_event_permalink(&self, event_id: String) -> Result<String, ClientError> {
let event_id = EventId::parse(event_id)?;
Ok(self.inner.matrix_to_event_permalink(event_id).await?.to_string())
}
pub async fn send_call_notification_if_needed(&self) -> Result<(), ClientError> {
self.inner.send_call_notification_if_needed().await?;
Ok(())
}
pub async fn send_call_notification(
&self,
call_id: String,
application: RtcApplicationType,
notify_type: NotifyType,
mentions: Mentions,
) -> Result<(), ClientError> {
self.inner
.send_call_notification(
call_id,
application.into(),
notify_type.into(),
mentions.into(),
)
.await?;
Ok(())
}
pub fn is_send_queue_enabled(&self) -> bool {
self.inner.send_queue().is_enabled()
}
pub fn enable_send_queue(&self, enable: bool) {
self.inner.send_queue().set_enabled(enable);
}
pub async fn save_composer_draft(&self, draft: ComposerDraft) -> Result<(), ClientError> {
Ok(self.inner.save_composer_draft(draft.try_into()?).await?)
}
pub async fn load_composer_draft(&self) -> Result<Option<ComposerDraft>, ClientError> {
Ok(self.inner.load_composer_draft().await?.map(Into::into))
}
pub async fn clear_composer_draft(&self) -> Result<(), ClientError> {
Ok(self.inner.clear_composer_draft().await?)
}
pub async fn edit(
&self,
event_id: String,
new_content: Arc<RoomMessageEventContentWithoutRelation>,
) -> Result<(), ClientError> {
let event_id = EventId::parse(event_id)?;
let replacement_event = self
.inner
.make_edit_event(&event_id, EditedContent::RoomMessage((*new_content).clone()))
.await?;
self.inner.send_queue().send(replacement_event).await?;
Ok(())
}
pub async fn withdraw_verification_and_resend(
&self,
user_ids: Vec<String>,
send_handle: Arc<SendHandle>,
) -> Result<(), ClientError> {
let user_ids: Vec<OwnedUserId> =
user_ids.iter().map(UserId::parse).collect::<Result<_, _>>()?;
let encryption = self.inner.client().encryption();
for user_id in user_ids {
if let Some(user_identity) = encryption.get_user_identity(&user_id).await? {
user_identity.withdraw_verification().await?;
}
}
send_handle.try_resend().await?;
Ok(())
}
pub async fn ignore_device_trust_and_resend(
&self,
devices: HashMap<String, Vec<String>>,
send_handle: Arc<SendHandle>,
) -> Result<(), ClientError> {
let encryption = self.inner.client().encryption();
for (user_id, device_ids) in devices.iter() {
let user_id = UserId::parse(user_id)?;
for device_id in device_ids {
let device_id: OwnedDeviceId = device_id.as_str().into();
if let Some(device) = encryption.get_device(&user_id, &device_id).await? {
device.set_local_trust(LocalTrust::Ignored).await?;
}
}
}
send_handle.try_resend().await?;
Ok(())
}
pub async fn clear_event_cache_storage(&self) -> Result<(), ClientError> {
let (room_event_cache, _drop_handles) = self.inner.event_cache().await?;
room_event_cache.clear().await?;
Ok(())
}
pub async fn subscribe_to_knock_requests(
self: Arc<Self>,
listener: Box<dyn KnockRequestsListener>,
) -> Result<Arc<TaskHandle>, ClientError> {
let (stream, seen_ids_cleanup_handle) = self.inner.subscribe_to_knock_requests().await?;
let handle = Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
pin_mut!(stream);
while let Some(requests) = stream.next().await {
listener.call(requests.into_iter().map(Into::into).collect());
}
seen_ids_cleanup_handle.abort();
})));
Ok(handle)
}
pub async fn room_events_debug_string(&self) -> Result<Vec<String>, ClientError> {
let (cache, _drop_guards) = self.inner.event_cache().await?;
Ok(cache.debug_string().await)
}
}
impl From<matrix_sdk::room::knock_requests::KnockRequest> for KnockRequest {
fn from(request: matrix_sdk::room::knock_requests::KnockRequest) -> Self {
Self {
event_id: request.event_id.to_string(),
user_id: request.member_info.user_id.to_string(),
room_id: request.room_id().to_string(),
display_name: request.member_info.display_name.clone(),
avatar_url: request.member_info.avatar_url.as_ref().map(|url| url.to_string()),
reason: request.member_info.reason.clone(),
timestamp: request.timestamp.map(|ts| ts.into()),
is_seen: request.is_seen,
actions: Arc::new(KnockRequestActions { inner: request }),
}
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait KnockRequestsListener: Send + Sync {
fn call(&self, join_requests: Vec<KnockRequest>);
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct KnockRequest {
pub event_id: String,
pub user_id: String,
pub room_id: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub reason: Option<String>,
pub timestamp: Option<u64>,
pub is_seen: bool,
pub actions: Arc<KnockRequestActions>,
}
#[derive(Debug, Clone, uniffi::Object)]
pub struct KnockRequestActions {
inner: matrix_sdk::room::knock_requests::KnockRequest,
}
#[matrix_sdk_ffi_macros::export]
impl KnockRequestActions {
pub async fn accept(&self) -> Result<(), ClientError> {
self.inner.accept().await.map_err(Into::into)
}
pub async fn decline(&self, reason: Option<String>) -> Result<(), ClientError> {
self.inner.decline(reason.as_deref()).await.map_err(Into::into)
}
pub async fn decline_and_ban(&self, reason: Option<String>) -> Result<(), ClientError> {
self.inner.decline_and_ban(reason.as_deref()).await.map_err(Into::into)
}
pub async fn mark_as_seen(&self) -> Result<(), ClientError> {
self.inner.mark_as_seen().await.map_err(Into::into)
}
}
#[matrix_sdk_ffi_macros::export]
pub fn matrix_to_room_alias_permalink(
room_alias: String,
) -> std::result::Result<String, ClientError> {
let room_alias = RoomAliasId::parse(room_alias)?;
Ok(room_alias.matrix_to_uri().to_string())
}
#[derive(uniffi::Record)]
pub struct RoomPowerLevels {
pub ban: i64,
pub invite: i64,
pub kick: i64,
pub redact: i64,
pub events_default: i64,
pub state_default: i64,
pub users_default: i64,
pub room_name: i64,
pub room_avatar: i64,
pub room_topic: i64,
}
impl From<RumaPowerLevels> for RoomPowerLevels {
fn from(value: RumaPowerLevels) -> Self {
fn state_event_level_for(
power_levels: &RumaPowerLevels,
event_type: &TimelineEventType,
) -> i64 {
let default_state: i64 = power_levels.state_default.into();
power_levels.events.get(event_type).map_or(default_state, |&level| level.into())
}
Self {
ban: value.ban.into(),
invite: value.invite.into(),
kick: value.kick.into(),
redact: value.redact.into(),
events_default: value.events_default.into(),
state_default: value.state_default.into(),
users_default: value.users_default.into(),
room_name: state_event_level_for(&value, &TimelineEventType::RoomName),
room_avatar: state_event_level_for(&value, &TimelineEventType::RoomAvatar),
room_topic: state_event_level_for(&value, &TimelineEventType::RoomTopic),
}
}
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait RoomInfoListener: Sync + Send {
fn call(&self, room_info: RoomInfo);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait TypingNotificationsListener: Sync + Send {
fn call(&self, typing_user_ids: Vec<String>);
}
#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait IdentityStatusChangeListener: Sync + Send {
fn call(&self, identity_status_change: Vec<IdentityStatusChange>);
}
#[derive(uniffi::Object)]
pub struct RoomMembersIterator {
chunk_iterator: ChunkIterator<matrix_sdk::room::RoomMember>,
}
impl RoomMembersIterator {
fn new(members: Vec<matrix_sdk::room::RoomMember>) -> Self {
Self { chunk_iterator: ChunkIterator::new(members) }
}
}
#[matrix_sdk_ffi_macros::export]
impl RoomMembersIterator {
fn len(&self) -> u32 {
self.chunk_iterator.len()
}
fn next_chunk(&self, chunk_size: u32) -> Option<Vec<RoomMember>> {
self.chunk_iterator
.next(chunk_size)
.map(|members| members.into_iter().filter_map(|m| m.try_into().ok()).collect())
}
}
#[derive(uniffi::Record)]
pub struct RoomHero {
user_id: String,
display_name: Option<String>,
avatar_url: Option<String>,
}
impl From<SdkRoomHero> for RoomHero {
fn from(value: SdkRoomHero) -> Self {
Self {
user_id: value.user_id.to_string(),
display_name: value.display_name.clone(),
avatar_url: value.avatar_url.as_ref().map(ToString::to_string),
}
}
}
#[derive(uniffi::Record)]
pub struct UserPowerLevelUpdate {
user_id: String,
power_level: i64,
}
impl TryFrom<ImageInfo> for RumaAvatarImageInfo {
type Error = MediaInfoError;
fn try_from(value: ImageInfo) -> Result<Self, MediaInfoError> {
let thumbnail_url = if let Some(media_source) = value.thumbnail_source {
match &media_source.as_ref().media_source {
MediaSource::Plain(mxc_uri) => Some(mxc_uri.clone()),
MediaSource::Encrypted(_) => return Err(MediaInfoError::InvalidField),
}
} else {
None
};
Ok(assign!(RumaAvatarImageInfo::new(), {
height: value.height.map(u64_to_uint),
width: value.width.map(u64_to_uint),
mimetype: value.mimetype,
size: value.size.map(u64_to_uint),
thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
thumbnail_url: thumbnail_url,
blurhash: value.blurhash,
}))
}
}
#[derive(uniffi::Enum)]
pub enum RtcApplicationType {
Call,
}
impl From<RtcApplicationType> for notify::ApplicationType {
fn from(value: RtcApplicationType) -> Self {
match value {
RtcApplicationType::Call => notify::ApplicationType::Call,
}
}
}
#[derive(uniffi::Record)]
pub struct ComposerDraft {
pub plain_text: String,
pub html_text: Option<String>,
pub draft_type: ComposerDraftType,
}
impl From<SdkComposerDraft> for ComposerDraft {
fn from(value: SdkComposerDraft) -> Self {
let SdkComposerDraft { plain_text, html_text, draft_type } = value;
Self { plain_text, html_text, draft_type: draft_type.into() }
}
}
impl TryFrom<ComposerDraft> for SdkComposerDraft {
type Error = ruma::IdParseError;
fn try_from(value: ComposerDraft) -> std::result::Result<Self, Self::Error> {
let ComposerDraft { plain_text, html_text, draft_type } = value;
Ok(Self { plain_text, html_text, draft_type: draft_type.try_into()? })
}
}
#[derive(uniffi::Enum)]
pub enum ComposerDraftType {
NewMessage,
Reply {
event_id: String,
},
Edit {
event_id: String,
},
}
impl From<SdkComposerDraftType> for ComposerDraftType {
fn from(value: SdkComposerDraftType) -> Self {
match value {
SdkComposerDraftType::NewMessage => Self::NewMessage,
SdkComposerDraftType::Reply { event_id } => Self::Reply { event_id: event_id.into() },
SdkComposerDraftType::Edit { event_id } => Self::Edit { event_id: event_id.into() },
}
}
}
impl TryFrom<ComposerDraftType> for SdkComposerDraftType {
type Error = ruma::IdParseError;
fn try_from(value: ComposerDraftType) -> std::result::Result<Self, Self::Error> {
let draft_type = match value {
ComposerDraftType::NewMessage => Self::NewMessage,
ComposerDraftType::Reply { event_id } => Self::Reply { event_id: event_id.try_into()? },
ComposerDraftType::Edit { event_id } => Self::Edit { event_id: event_id.try_into()? },
};
Ok(draft_type)
}
}