1use std::collections::HashMap;
16
17use matrix_sdk::room::power_levels::power_level_user_changes;
18use matrix_sdk_ui::timeline::RoomPinnedEventsChange;
19use ruma::events::{
20 room::history_visibility::HistoryVisibility as RumaHistoryVisibility, FullStateEventContent,
21};
22
23use crate::{client::JoinRule, timeline::msg_like::MsgLikeContent, utils::Timestamp};
24
25impl From<matrix_sdk_ui::timeline::TimelineItemContent> for TimelineItemContent {
26 fn from(value: matrix_sdk_ui::timeline::TimelineItemContent) -> Self {
27 use matrix_sdk_ui::timeline::TimelineItemContent as Content;
28
29 match value {
30 Content::MsgLike(msg_like) => match msg_like.try_into() {
31 Ok(content) => TimelineItemContent::MsgLike { content },
32 Err((error, event_type)) => TimelineItemContent::FailedToParseMessageLike {
33 event_type,
34 error: error.to_string(),
35 },
36 },
37
38 Content::CallInvite => TimelineItemContent::CallInvite,
39
40 Content::RtcNotification => TimelineItemContent::RtcNotification,
41
42 Content::MembershipChange(membership) => {
43 let reason = match membership.content() {
44 FullStateEventContent::Original { content, .. } => content.reason.clone(),
45 _ => None,
46 };
47 TimelineItemContent::RoomMembership {
48 user_id: membership.user_id().to_string(),
49 user_display_name: membership.display_name(),
50 change: membership.change().map(Into::into),
51 reason,
52 }
53 }
54
55 Content::ProfileChange(profile) => {
56 let (display_name, prev_display_name) = profile
57 .displayname_change()
58 .map(|change| (change.new.clone(), change.old.clone()))
59 .unzip();
60 let (avatar_url, prev_avatar_url) = profile
61 .avatar_url_change()
62 .map(|change| {
63 (
64 change.new.as_ref().map(ToString::to_string),
65 change.old.as_ref().map(ToString::to_string),
66 )
67 })
68 .unzip();
69 TimelineItemContent::ProfileChange {
70 display_name: display_name.flatten(),
71 prev_display_name: prev_display_name.flatten(),
72 avatar_url: avatar_url.flatten(),
73 prev_avatar_url: prev_avatar_url.flatten(),
74 }
75 }
76
77 Content::OtherState(state) => TimelineItemContent::State {
78 state_key: state.state_key().to_owned(),
79 content: state.content().into(),
80 },
81
82 Content::FailedToParseMessageLike { event_type, error } => {
83 TimelineItemContent::FailedToParseMessageLike {
84 event_type: event_type.to_string(),
85 error: error.to_string(),
86 }
87 }
88
89 Content::FailedToParseState { event_type, state_key, error } => {
90 TimelineItemContent::FailedToParseState {
91 event_type: event_type.to_string(),
92 state_key,
93 error: error.to_string(),
94 }
95 }
96 }
97 }
98}
99
100#[derive(Debug, Clone, uniffi::Enum)]
101pub enum HistoryVisibility {
102 Invited,
108
109 Joined,
114
115 Shared,
120
121 WorldReadable,
125
126 Custom {
128 repr: String,
130 },
131}
132
133impl From<RumaHistoryVisibility> for HistoryVisibility {
134 fn from(value: RumaHistoryVisibility) -> Self {
135 match value {
136 RumaHistoryVisibility::Invited => Self::Invited,
137 RumaHistoryVisibility::Joined => Self::Joined,
138 RumaHistoryVisibility::Shared => Self::Shared,
139 RumaHistoryVisibility::WorldReadable => Self::WorldReadable,
140 _ => Self::Custom { repr: value.to_string() },
141 }
142 }
143}
144
145#[derive(Clone, uniffi::Enum)]
146#[allow(clippy::large_enum_variant)]
154pub enum TimelineItemContent {
155 MsgLike {
156 content: MsgLikeContent,
157 },
158 CallInvite,
159 RtcNotification,
160 RoomMembership {
161 user_id: String,
162 user_display_name: Option<String>,
163 change: Option<MembershipChange>,
164 reason: Option<String>,
165 },
166 ProfileChange {
167 display_name: Option<String>,
168 prev_display_name: Option<String>,
169 avatar_url: Option<String>,
170 prev_avatar_url: Option<String>,
171 },
172 State {
173 state_key: String,
174 content: OtherState,
175 },
176 FailedToParseMessageLike {
177 event_type: String,
178 error: String,
179 },
180 FailedToParseState {
181 event_type: String,
182 state_key: String,
183 error: String,
184 },
185}
186
187#[derive(Clone, uniffi::Record)]
188pub struct Reaction {
189 pub key: String,
190 pub senders: Vec<ReactionSenderData>,
191}
192
193#[derive(Clone, uniffi::Record)]
194pub struct ReactionSenderData {
195 pub sender_id: String,
196 pub timestamp: Timestamp,
197}
198
199#[derive(Clone, uniffi::Enum)]
200pub enum MembershipChange {
201 None,
202 Error,
203 Joined,
204 Left,
205 Banned,
206 Unbanned,
207 Kicked,
208 Invited,
209 KickedAndBanned,
210 InvitationAccepted,
211 InvitationRejected,
212 InvitationRevoked,
213 Knocked,
214 KnockAccepted,
215 KnockRetracted,
216 KnockDenied,
217 NotImplemented,
218}
219
220impl From<matrix_sdk_ui::timeline::MembershipChange> for MembershipChange {
221 fn from(membership_change: matrix_sdk_ui::timeline::MembershipChange) -> Self {
222 use matrix_sdk_ui::timeline::MembershipChange as Change;
223 match membership_change {
224 Change::None => Self::None,
225 Change::Error => Self::Error,
226 Change::Joined => Self::Joined,
227 Change::Left => Self::Left,
228 Change::Banned => Self::Banned,
229 Change::Unbanned => Self::Unbanned,
230 Change::Kicked => Self::Kicked,
231 Change::Invited => Self::Invited,
232 Change::KickedAndBanned => Self::KickedAndBanned,
233 Change::InvitationAccepted => Self::InvitationAccepted,
234 Change::InvitationRejected => Self::InvitationRejected,
235 Change::InvitationRevoked => Self::InvitationRevoked,
236 Change::Knocked => Self::Knocked,
237 Change::KnockAccepted => Self::KnockAccepted,
238 Change::KnockRetracted => Self::KnockRetracted,
239 Change::KnockDenied => Self::KnockDenied,
240 Change::NotImplemented => Self::NotImplemented,
241 }
242 }
243}
244
245#[derive(Clone, uniffi::Enum)]
246pub enum OtherState {
247 PolicyRuleRoom,
248 PolicyRuleServer,
249 PolicyRuleUser,
250 RoomAliases,
251 RoomAvatar { url: Option<String> },
252 RoomCanonicalAlias,
253 RoomCreate { federate: Option<bool> },
254 RoomEncryption,
255 RoomGuestAccess,
256 RoomHistoryVisibility { history_visibility: Option<HistoryVisibility> },
257 RoomJoinRules { join_rule: Option<JoinRule> },
258 RoomName { name: Option<String> },
259 RoomPinnedEvents { change: RoomPinnedEventsChange },
260 RoomPowerLevels { users: HashMap<String, i64>, previous: Option<HashMap<String, i64>> },
261 RoomServerAcl,
262 RoomThirdPartyInvite { display_name: Option<String> },
263 RoomTombstone,
264 RoomTopic { topic: Option<String> },
265 SpaceChild,
266 SpaceParent,
267 Custom { event_type: String },
268}
269
270impl From<&matrix_sdk_ui::timeline::AnyOtherFullStateEventContent> for OtherState {
271 fn from(content: &matrix_sdk_ui::timeline::AnyOtherFullStateEventContent) -> Self {
272 use matrix_sdk::ruma::events::FullStateEventContent as FullContent;
273 use matrix_sdk_ui::timeline::AnyOtherFullStateEventContent as Content;
274
275 match content {
276 Content::PolicyRuleRoom(_) => Self::PolicyRuleRoom,
277 Content::PolicyRuleServer(_) => Self::PolicyRuleServer,
278 Content::PolicyRuleUser(_) => Self::PolicyRuleUser,
279 Content::RoomAliases(_) => Self::RoomAliases,
280 Content::RoomAvatar(c) => {
281 let url = match c {
282 FullContent::Original { content, .. } => {
283 content.url.as_ref().map(ToString::to_string)
284 }
285 FullContent::Redacted(_) => None,
286 };
287 Self::RoomAvatar { url }
288 }
289 Content::RoomCanonicalAlias(_) => Self::RoomCanonicalAlias,
290 Content::RoomCreate(c) => {
291 let federate = match c {
292 FullContent::Original { content, .. } => Some(content.federate),
293 FullContent::Redacted(_) => None,
294 };
295 Self::RoomCreate { federate }
296 }
297 Content::RoomEncryption(_) => Self::RoomEncryption,
298 Content::RoomGuestAccess(_) => Self::RoomGuestAccess,
299 Content::RoomHistoryVisibility(c) => {
300 let history_visibility = match c {
301 FullContent::Original { content, .. } => {
302 Some(content.history_visibility.clone().into())
303 }
304 FullContent::Redacted(_) => None,
305 };
306 Self::RoomHistoryVisibility { history_visibility }
307 }
308 Content::RoomJoinRules(c) => {
309 let join_rule = match c {
310 FullContent::Original { content, .. } => {
311 match content.join_rule.clone().try_into() {
312 Ok(jr) => Some(jr),
313 Err(err) => {
314 tracing::error!("Failed to convert join rule: {}", err);
315 None
316 }
317 }
318 }
319 FullContent::Redacted(_) => None,
320 };
321 Self::RoomJoinRules { join_rule }
322 }
323 Content::RoomName(c) => {
324 let name = match c {
325 FullContent::Original { content, .. } => Some(content.name.clone()),
326 FullContent::Redacted(_) => None,
327 };
328 Self::RoomName { name }
329 }
330 Content::RoomPinnedEvents(c) => Self::RoomPinnedEvents { change: c.into() },
331 Content::RoomPowerLevels(c) => match c {
332 FullContent::Original { content, prev_content } => Self::RoomPowerLevels {
333 users: power_level_user_changes(content, prev_content)
334 .iter()
335 .map(|(k, v)| (k.to_string(), *v))
336 .collect(),
337 previous: prev_content.as_ref().map(|prev_content| {
338 prev_content.users.iter().map(|(k, &v)| (k.to_string(), v.into())).collect()
339 }),
340 },
341 FullContent::Redacted(_) => {
342 Self::RoomPowerLevels { users: Default::default(), previous: None }
343 }
344 },
345 Content::RoomServerAcl(_) => Self::RoomServerAcl,
346 Content::RoomThirdPartyInvite(c) => {
347 let display_name = match c {
348 FullContent::Original { content, .. } => Some(content.display_name.clone()),
349 FullContent::Redacted(_) => None,
350 };
351 Self::RoomThirdPartyInvite { display_name }
352 }
353 Content::RoomTombstone(_) => Self::RoomTombstone,
354 Content::RoomTopic(c) => {
355 let topic = match c {
356 FullContent::Original { content, .. } => Some(content.topic.clone()),
357 FullContent::Redacted(_) => None,
358 };
359 Self::RoomTopic { topic }
360 }
361 Content::SpaceChild(_) => Self::SpaceChild,
362 Content::SpaceParent(_) => Self::SpaceParent,
363 Content::_Custom { event_type, .. } => Self::Custom { event_type: event_type.clone() },
364 }
365 }
366}