1use std::{collections::HashMap, fmt::Write as _, fs, panic, sync::Arc};
16
17use anyhow::{Context, Result};
18use as_variant::as_variant;
19use content::{InReplyToDetails, RepliedToEventDetails};
20use eyeball_im::VectorDiff;
21use futures_util::{pin_mut, StreamExt as _};
22use matrix_sdk::{
23 attachment::{
24 AttachmentConfig, AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo,
25 BaseVideoInfo, Thumbnail,
26 },
27 deserialized_responses::{ShieldState as SdkShieldState, ShieldStateCode},
28 room::edit::EditedContent as SdkEditedContent,
29 Error,
30};
31use matrix_sdk_ui::timeline::{
32 self, EventItemOrigin, LiveBackPaginationStatus, Profile, RepliedToEvent, TimelineDetails,
33 TimelineUniqueId as SdkTimelineUniqueId,
34};
35use mime::Mime;
36use ruma::{
37 events::{
38 location::{AssetType as RumaAssetType, LocationContent, ZoomLevel},
39 poll::{
40 unstable_end::UnstablePollEndEventContent,
41 unstable_response::UnstablePollResponseEventContent,
42 unstable_start::{
43 NewUnstablePollStartEventContent, UnstablePollAnswer, UnstablePollAnswers,
44 UnstablePollStartContentBlock,
45 },
46 },
47 receipt::ReceiptThread,
48 room::message::{
49 ForwardThread, LocationMessageEventContent, MessageType,
50 RoomMessageEventContentWithoutRelation,
51 },
52 AnyMessageLikeEventContent,
53 },
54 EventId, UInt,
55};
56use tokio::{
57 sync::Mutex,
58 task::{AbortHandle, JoinHandle},
59};
60use tracing::{error, warn};
61use uuid::Uuid;
62
63use self::content::{Reaction, ReactionSenderData, TimelineItemContent};
64use crate::{
65 client::ProgressWatcher,
66 error::{ClientError, RoomError},
67 event::EventOrTransactionId,
68 helpers::unwrap_or_clone_arc,
69 ruma::{
70 AssetType, AudioInfo, FileInfo, FormattedBody, ImageInfo, Mentions, PollKind,
71 ThumbnailInfo, VideoInfo,
72 },
73 task_handle::TaskHandle,
74 utils::Timestamp,
75 RUNTIME,
76};
77
78pub mod configuration;
79mod content;
80
81pub use content::MessageContent;
82use matrix_sdk::utils::formatted_body_from;
83
84use crate::error::QueueWedgeError;
85
86#[derive(uniffi::Object)]
87#[repr(transparent)]
88pub struct Timeline {
89 pub(crate) inner: matrix_sdk_ui::timeline::Timeline,
90}
91
92impl Timeline {
93 pub(crate) fn new(inner: matrix_sdk_ui::timeline::Timeline) -> Arc<Self> {
94 Arc::new(Self { inner })
95 }
96
97 pub(crate) fn from_arc(inner: Arc<matrix_sdk_ui::timeline::Timeline>) -> Arc<Self> {
98 unsafe { Arc::from_raw(Arc::into_raw(inner) as _) }
100 }
101
102 fn send_attachment(
103 self: Arc<Self>,
104 params: UploadParameters,
105 attachment_info: AttachmentInfo,
106 mime_type: Option<String>,
107 progress_watcher: Option<Box<dyn ProgressWatcher>>,
108 thumbnail: Option<Thumbnail>,
109 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
110 let mime_str = mime_type.as_ref().ok_or(RoomError::InvalidAttachmentMimeType)?;
111 let mime_type =
112 mime_str.parse::<Mime>().map_err(|_| RoomError::InvalidAttachmentMimeType)?;
113
114 let formatted_caption = formatted_body_from(
115 params.caption.as_deref(),
116 params.formatted_caption.map(Into::into),
117 );
118
119 let attachment_config = AttachmentConfig::new()
120 .thumbnail(thumbnail)
121 .info(attachment_info)
122 .caption(params.caption)
123 .formatted_caption(formatted_caption)
124 .mentions(params.mentions.map(Into::into));
125
126 let handle = SendAttachmentJoinHandle::new(RUNTIME.spawn(async move {
127 let mut request =
128 self.inner.send_attachment(params.filename, mime_type, attachment_config);
129
130 if params.use_send_queue {
131 request = request.use_send_queue();
132 }
133
134 if let Some(progress_watcher) = progress_watcher {
135 let mut subscriber = request.subscribe_to_send_progress();
136 RUNTIME.spawn(async move {
137 while let Some(progress) = subscriber.next().await {
138 progress_watcher.transmission_progress(progress.into());
139 }
140 });
141 }
142
143 request.await.map_err(|_| RoomError::FailedSendingAttachment)?;
144 Ok(())
145 }));
146
147 Ok(handle)
148 }
149}
150
151fn build_thumbnail_info(
152 thumbnail_path: Option<String>,
153 thumbnail_info: Option<ThumbnailInfo>,
154) -> Result<Option<Thumbnail>, RoomError> {
155 match (thumbnail_path, thumbnail_info) {
156 (None, None) => Ok(None),
157
158 (Some(thumbnail_path), Some(thumbnail_info)) => {
159 let thumbnail_data =
160 fs::read(thumbnail_path).map_err(|_| RoomError::InvalidThumbnailData)?;
161
162 let height = thumbnail_info
163 .height
164 .and_then(|u| UInt::try_from(u).ok())
165 .ok_or(RoomError::InvalidAttachmentData)?;
166 let width = thumbnail_info
167 .width
168 .and_then(|u| UInt::try_from(u).ok())
169 .ok_or(RoomError::InvalidAttachmentData)?;
170 let size = thumbnail_info
171 .size
172 .and_then(|u| UInt::try_from(u).ok())
173 .ok_or(RoomError::InvalidAttachmentData)?;
174
175 let mime_str =
176 thumbnail_info.mimetype.as_ref().ok_or(RoomError::InvalidAttachmentMimeType)?;
177 let mime_type =
178 mime_str.parse::<Mime>().map_err(|_| RoomError::InvalidAttachmentMimeType)?;
179
180 Ok(Some(Thumbnail {
181 data: thumbnail_data,
182 content_type: mime_type,
183 height,
184 width,
185 size,
186 }))
187 }
188
189 _ => {
190 warn!("Ignoring thumbnail because either the thumbnail path or info isn't defined");
191 Ok(None)
192 }
193 }
194}
195
196#[derive(uniffi::Record)]
197pub struct UploadParameters {
198 filename: String,
200 caption: Option<String>,
202 formatted_caption: Option<FormattedBody>,
204 mentions: Option<Mentions>,
206 use_send_queue: bool,
210}
211
212#[matrix_sdk_ffi_macros::export]
213impl Timeline {
214 pub async fn add_listener(&self, listener: Box<dyn TimelineListener>) -> Arc<TaskHandle> {
215 let (timeline_items, timeline_stream) = self.inner.subscribe().await;
216
217 Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
218 pin_mut!(timeline_stream);
219
220 listener.on_update(vec![Arc::new(TimelineDiff::new(VectorDiff::Reset {
227 values: timeline_items,
228 }))]);
229
230 while let Some(diffs) = timeline_stream.next().await {
232 listener
233 .on_update(diffs.into_iter().map(|d| Arc::new(TimelineDiff::new(d))).collect());
234 }
235 })))
236 }
237
238 pub fn retry_decryption(self: Arc<Self>, session_ids: Vec<String>) {
239 RUNTIME.spawn(async move {
240 self.inner.retry_decryption(&session_ids).await;
241 });
242 }
243
244 pub async fn fetch_members(&self) {
245 self.inner.fetch_members().await
246 }
247
248 pub async fn subscribe_to_back_pagination_status(
249 &self,
250 listener: Box<dyn PaginationStatusListener>,
251 ) -> Result<Arc<TaskHandle>, ClientError> {
252 let (initial, mut subscriber) = self
253 .inner
254 .live_back_pagination_status()
255 .await
256 .context("can't subscribe to the back-pagination status on a focused timeline")?;
257
258 Ok(Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
259 listener.on_update(initial);
261
262 while let Some(status) = subscriber.next().await {
263 listener.on_update(status);
264 }
265 }))))
266 }
267
268 pub async fn paginate_backwards(&self, num_events: u16) -> Result<bool, ClientError> {
272 Ok(self.inner.paginate_backwards(num_events).await?)
273 }
274
275 pub async fn paginate_forwards(&self, num_events: u16) -> Result<bool, ClientError> {
279 Ok(self.inner.paginate_forwards(num_events).await?)
280 }
281
282 pub async fn send_read_receipt(
283 &self,
284 receipt_type: ReceiptType,
285 event_id: String,
286 ) -> Result<(), ClientError> {
287 let event_id = EventId::parse(event_id)?;
288 self.inner
289 .send_single_receipt(receipt_type.into(), ReceiptThread::Unthreaded, event_id)
290 .await?;
291 Ok(())
292 }
293
294 pub async fn mark_as_read(&self, receipt_type: ReceiptType) -> Result<(), ClientError> {
301 self.inner.mark_as_read(receipt_type.into()).await?;
302 Ok(())
303 }
304
305 pub async fn send(
311 self: Arc<Self>,
312 msg: Arc<RoomMessageEventContentWithoutRelation>,
313 ) -> Result<Arc<SendHandle>, ClientError> {
314 match self.inner.send((*msg).to_owned().with_relation(None).into()).await {
315 Ok(handle) => Ok(Arc::new(SendHandle::new(handle))),
316 Err(err) => {
317 error!("error when sending a message: {err}");
318 Err(anyhow::anyhow!(err).into())
319 }
320 }
321 }
322
323 pub fn send_image(
324 self: Arc<Self>,
325 params: UploadParameters,
326 thumbnail_path: Option<String>,
327 image_info: ImageInfo,
328 progress_watcher: Option<Box<dyn ProgressWatcher>>,
329 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
330 let attachment_info = AttachmentInfo::Image(
331 BaseImageInfo::try_from(&image_info).map_err(|_| RoomError::InvalidAttachmentData)?,
332 );
333 let thumbnail = build_thumbnail_info(thumbnail_path, image_info.thumbnail_info)?;
334 self.send_attachment(
335 params,
336 attachment_info,
337 image_info.mimetype,
338 progress_watcher,
339 thumbnail,
340 )
341 }
342
343 pub fn send_video(
344 self: Arc<Self>,
345 params: UploadParameters,
346 thumbnail_path: Option<String>,
347 video_info: VideoInfo,
348 progress_watcher: Option<Box<dyn ProgressWatcher>>,
349 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
350 let attachment_info = AttachmentInfo::Video(
351 BaseVideoInfo::try_from(&video_info).map_err(|_| RoomError::InvalidAttachmentData)?,
352 );
353 let thumbnail = build_thumbnail_info(thumbnail_path, video_info.thumbnail_info)?;
354 self.send_attachment(
355 params,
356 attachment_info,
357 video_info.mimetype,
358 progress_watcher,
359 thumbnail,
360 )
361 }
362
363 pub fn send_audio(
364 self: Arc<Self>,
365 params: UploadParameters,
366 audio_info: AudioInfo,
367 progress_watcher: Option<Box<dyn ProgressWatcher>>,
368 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
369 let attachment_info = AttachmentInfo::Audio(
370 BaseAudioInfo::try_from(&audio_info).map_err(|_| RoomError::InvalidAttachmentData)?,
371 );
372 self.send_attachment(params, attachment_info, audio_info.mimetype, progress_watcher, None)
373 }
374
375 pub fn send_voice_message(
376 self: Arc<Self>,
377 params: UploadParameters,
378 audio_info: AudioInfo,
379 waveform: Vec<u16>,
380 progress_watcher: Option<Box<dyn ProgressWatcher>>,
381 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
382 let attachment_info = AttachmentInfo::Voice {
383 audio_info: BaseAudioInfo::try_from(&audio_info)
384 .map_err(|_| RoomError::InvalidAttachmentData)?,
385 waveform: Some(waveform),
386 };
387 self.send_attachment(params, attachment_info, audio_info.mimetype, progress_watcher, None)
388 }
389
390 pub fn send_file(
391 self: Arc<Self>,
392 params: UploadParameters,
393 file_info: FileInfo,
394 progress_watcher: Option<Box<dyn ProgressWatcher>>,
395 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
396 let attachment_info = AttachmentInfo::File(
397 BaseFileInfo::try_from(&file_info).map_err(|_| RoomError::InvalidAttachmentData)?,
398 );
399 self.send_attachment(params, attachment_info, file_info.mimetype, progress_watcher, None)
400 }
401
402 pub async fn create_poll(
403 self: Arc<Self>,
404 question: String,
405 answers: Vec<String>,
406 max_selections: u8,
407 poll_kind: PollKind,
408 ) -> Result<(), ClientError> {
409 let poll_data = PollData { question, answers, max_selections, poll_kind };
410
411 let poll_start_event_content = NewUnstablePollStartEventContent::plain_text(
412 poll_data.fallback_text(),
413 poll_data.try_into()?,
414 );
415 let event_content =
416 AnyMessageLikeEventContent::UnstablePollStart(poll_start_event_content.into());
417
418 if let Err(err) = self.inner.send(event_content).await {
419 error!("unable to start poll: {err}");
420 }
421
422 Ok(())
423 }
424
425 pub async fn send_poll_response(
426 self: Arc<Self>,
427 poll_start_event_id: String,
428 answers: Vec<String>,
429 ) -> Result<(), ClientError> {
430 let poll_start_event_id =
431 EventId::parse(poll_start_event_id).context("Failed to parse EventId")?;
432 let poll_response_event_content =
433 UnstablePollResponseEventContent::new(answers, poll_start_event_id);
434 let event_content =
435 AnyMessageLikeEventContent::UnstablePollResponse(poll_response_event_content);
436
437 if let Err(err) = self.inner.send(event_content).await {
438 error!("unable to send poll response: {err}");
439 }
440
441 Ok(())
442 }
443
444 pub fn end_poll(
445 self: Arc<Self>,
446 poll_start_event_id: String,
447 text: String,
448 ) -> Result<(), ClientError> {
449 let poll_start_event_id =
450 EventId::parse(poll_start_event_id).context("Failed to parse EventId")?;
451 let poll_end_event_content = UnstablePollEndEventContent::new(text, poll_start_event_id);
452 let event_content = AnyMessageLikeEventContent::UnstablePollEnd(poll_end_event_content);
453
454 RUNTIME.spawn(async move {
455 if let Err(err) = self.inner.send(event_content).await {
456 error!("unable to end poll: {err}");
457 }
458 });
459
460 Ok(())
461 }
462
463 pub async fn send_reply(
464 &self,
465 msg: Arc<RoomMessageEventContentWithoutRelation>,
466 event_id: String,
467 ) -> Result<(), ClientError> {
468 let event_id = EventId::parse(event_id)?;
469 let replied_to_info = self
470 .inner
471 .replied_to_info_from_event_id(&event_id)
472 .await
473 .map_err(|err| anyhow::anyhow!(err))?;
474
475 self.inner
476 .send_reply((*msg).clone(), replied_to_info, ForwardThread::Yes)
477 .await
478 .map_err(|err| anyhow::anyhow!(err))?;
479 Ok(())
480 }
481
482 pub async fn edit(
491 &self,
492 event_or_transaction_id: EventOrTransactionId,
493 new_content: EditedContent,
494 ) -> Result<(), ClientError> {
495 match self
496 .inner
497 .edit(&event_or_transaction_id.clone().try_into()?, new_content.clone().try_into()?)
498 .await
499 {
500 Ok(()) => Ok(()),
501
502 Err(timeline::Error::EventNotInTimeline(_)) => {
503 let event_id = match event_or_transaction_id {
506 EventOrTransactionId::EventId { event_id } => EventId::parse(event_id)?,
507 EventOrTransactionId::TransactionId { .. } => {
508 warn!("trying to apply an edit to a local echo that doesn't exist in this timeline, aborting");
509 return Ok(());
510 }
511 };
512 let room = self.inner.room();
513 let edit_event = room.make_edit_event(&event_id, new_content.try_into()?).await?;
514 room.send_queue().send(edit_event).await?;
515 Ok(())
516 }
517
518 Err(err) => Err(err.into()),
519 }
520 }
521
522 pub async fn send_location(
523 self: Arc<Self>,
524 body: String,
525 geo_uri: String,
526 description: Option<String>,
527 zoom_level: Option<u8>,
528 asset_type: Option<AssetType>,
529 ) {
530 let mut location_event_message_content =
531 LocationMessageEventContent::new(body, geo_uri.clone());
532
533 if let Some(asset_type) = asset_type {
534 location_event_message_content =
535 location_event_message_content.with_asset_type(RumaAssetType::from(asset_type));
536 }
537
538 let mut location_content = LocationContent::new(geo_uri);
539 location_content.description = description;
540 location_content.zoom_level = zoom_level.and_then(ZoomLevel::new);
541 location_event_message_content.location = Some(location_content);
542
543 let room_message_event_content = RoomMessageEventContentWithoutRelation::new(
544 MessageType::Location(location_event_message_content),
545 );
546 let _ = self.send(Arc::new(room_message_event_content)).await;
548 }
549
550 pub async fn toggle_reaction(
562 &self,
563 item_id: EventOrTransactionId,
564 key: String,
565 ) -> Result<(), ClientError> {
566 self.inner.toggle_reaction(&item_id.try_into()?, &key).await?;
567 Ok(())
568 }
569
570 pub async fn fetch_details_for_event(&self, event_id: String) -> Result<(), ClientError> {
571 let event_id = <&EventId>::try_from(event_id.as_str())?;
572 self.inner.fetch_details_for_event(event_id).await.context("Fetching event details")?;
573 Ok(())
574 }
575
576 pub async fn get_event_timeline_item_by_event_id(
585 &self,
586 event_id: String,
587 ) -> Result<EventTimelineItem, ClientError> {
588 let event_id = EventId::parse(event_id)?;
589 let item = self
590 .inner
591 .item_by_event_id(&event_id)
592 .await
593 .context("Item with given event ID not found")?;
594 Ok(item.into())
595 }
596
597 pub async fn redact_event(
607 &self,
608 event_or_transaction_id: EventOrTransactionId,
609 reason: Option<String>,
610 ) -> Result<(), ClientError> {
611 Ok(self.inner.redact(&(event_or_transaction_id.try_into()?), reason.as_deref()).await?)
612 }
613
614 pub async fn load_reply_details(
619 &self,
620 event_id_str: String,
621 ) -> Result<Arc<InReplyToDetails>, ClientError> {
622 let event_id = EventId::parse(&event_id_str)?;
623
624 let replied_to: Result<RepliedToEvent, Error> =
625 if let Some(event) = self.inner.item_by_event_id(&event_id).await {
626 Ok(RepliedToEvent::from_timeline_item(&event))
627 } else {
628 match self.inner.room().event(&event_id, None).await {
629 Ok(timeline_event) => Ok(RepliedToEvent::try_from_timeline_event_for_room(
630 timeline_event,
631 self.inner.room(),
632 )
633 .await?),
634 Err(e) => Err(e),
635 }
636 };
637
638 match replied_to {
639 Ok(replied_to) => Ok(Arc::new(InReplyToDetails::new(
640 event_id_str,
641 RepliedToEventDetails::Ready {
642 content: replied_to.content().clone().into(),
643 sender: replied_to.sender().to_string(),
644 sender_profile: replied_to.sender_profile().into(),
645 },
646 ))),
647
648 Err(e) => Ok(Arc::new(InReplyToDetails::new(
649 event_id_str,
650 RepliedToEventDetails::Error { message: e.to_string() },
651 ))),
652 }
653 }
654
655 async fn pin_event(&self, event_id: String) -> Result<bool, ClientError> {
661 let event_id = EventId::parse(event_id).map_err(ClientError::from)?;
662 self.inner.pin_event(&event_id).await.map_err(ClientError::from)
663 }
664
665 async fn unpin_event(&self, event_id: String) -> Result<bool, ClientError> {
671 let event_id = EventId::parse(event_id).map_err(ClientError::from)?;
672 self.inner.unpin_event(&event_id).await.map_err(ClientError::from)
673 }
674
675 pub fn create_message_content(
676 &self,
677 msg_type: crate::ruma::MessageType,
678 ) -> Option<Arc<RoomMessageEventContentWithoutRelation>> {
679 let msg_type: Option<MessageType> = msg_type.try_into().ok();
680 msg_type.map(|m| Arc::new(RoomMessageEventContentWithoutRelation::new(m)))
681 }
682}
683
684#[derive(uniffi::Object)]
686pub struct SendHandle {
687 inner: Mutex<Option<matrix_sdk::send_queue::SendHandle>>,
688}
689
690impl SendHandle {
691 fn new(handle: matrix_sdk::send_queue::SendHandle) -> Self {
692 Self { inner: Mutex::new(Some(handle)) }
693 }
694}
695
696#[matrix_sdk_ffi_macros::export]
697impl SendHandle {
698 async fn abort(self: Arc<Self>) -> Result<bool, ClientError> {
707 if let Some(inner) = self.inner.lock().await.take() {
708 Ok(inner
709 .abort()
710 .await
711 .map_err(|err| anyhow::anyhow!("error when saving in store: {err}"))?)
712 } else {
713 warn!("trying to abort a send handle that's already been actioned");
714 Ok(false)
715 }
716 }
717
718 pub async fn try_resend(self: Arc<Self>) -> Result<(), ClientError> {
731 let locked = self.inner.lock().await;
732 if let Some(handle) = locked.as_ref() {
733 handle.unwedge().await?;
734 } else {
735 warn!("trying to unwedge a send handle that's been aborted");
736 }
737 Ok(())
738 }
739}
740
741#[derive(Debug, thiserror::Error, uniffi::Error)]
742pub enum FocusEventError {
743 #[error("the event id parameter {event_id} is incorrect: {err}")]
744 InvalidEventId { event_id: String, err: String },
745
746 #[error("the event {event_id} could not be found")]
747 EventNotFound { event_id: String },
748
749 #[error("error when trying to focus on an event: {msg}")]
750 Other { msg: String },
751}
752
753#[matrix_sdk_ffi_macros::export(callback_interface)]
754pub trait TimelineListener: Sync + Send {
755 fn on_update(&self, diff: Vec<Arc<TimelineDiff>>);
756}
757
758#[matrix_sdk_ffi_macros::export(callback_interface)]
759pub trait PaginationStatusListener: Sync + Send {
760 fn on_update(&self, status: LiveBackPaginationStatus);
761}
762
763#[derive(Clone, uniffi::Object)]
764pub enum TimelineDiff {
765 Append { values: Vec<Arc<TimelineItem>> },
766 Clear,
767 PushFront { value: Arc<TimelineItem> },
768 PushBack { value: Arc<TimelineItem> },
769 PopFront,
770 PopBack,
771 Insert { index: usize, value: Arc<TimelineItem> },
772 Set { index: usize, value: Arc<TimelineItem> },
773 Remove { index: usize },
774 Truncate { length: usize },
775 Reset { values: Vec<Arc<TimelineItem>> },
776}
777
778impl TimelineDiff {
779 pub(crate) fn new(inner: VectorDiff<Arc<matrix_sdk_ui::timeline::TimelineItem>>) -> Self {
780 match inner {
781 VectorDiff::Append { values } => {
782 Self::Append { values: values.into_iter().map(TimelineItem::from_arc).collect() }
783 }
784 VectorDiff::Clear => Self::Clear,
785 VectorDiff::Insert { index, value } => {
786 Self::Insert { index, value: TimelineItem::from_arc(value) }
787 }
788 VectorDiff::Set { index, value } => {
789 Self::Set { index, value: TimelineItem::from_arc(value) }
790 }
791 VectorDiff::Truncate { length } => Self::Truncate { length },
792 VectorDiff::Remove { index } => Self::Remove { index },
793 VectorDiff::PushBack { value } => {
794 Self::PushBack { value: TimelineItem::from_arc(value) }
795 }
796 VectorDiff::PushFront { value } => {
797 Self::PushFront { value: TimelineItem::from_arc(value) }
798 }
799 VectorDiff::PopBack => Self::PopBack,
800 VectorDiff::PopFront => Self::PopFront,
801 VectorDiff::Reset { values } => {
802 Self::Reset { values: values.into_iter().map(TimelineItem::from_arc).collect() }
803 }
804 }
805 }
806}
807
808#[matrix_sdk_ffi_macros::export]
809impl TimelineDiff {
810 pub fn change(&self) -> TimelineChange {
811 match self {
812 Self::Append { .. } => TimelineChange::Append,
813 Self::Insert { .. } => TimelineChange::Insert,
814 Self::Set { .. } => TimelineChange::Set,
815 Self::Remove { .. } => TimelineChange::Remove,
816 Self::PushBack { .. } => TimelineChange::PushBack,
817 Self::PushFront { .. } => TimelineChange::PushFront,
818 Self::PopBack => TimelineChange::PopBack,
819 Self::PopFront => TimelineChange::PopFront,
820 Self::Clear => TimelineChange::Clear,
821 Self::Truncate { .. } => TimelineChange::Truncate,
822 Self::Reset { .. } => TimelineChange::Reset,
823 }
824 }
825
826 pub fn append(self: Arc<Self>) -> Option<Vec<Arc<TimelineItem>>> {
827 let this = unwrap_or_clone_arc(self);
828 as_variant!(this, Self::Append { values } => values)
829 }
830
831 pub fn insert(self: Arc<Self>) -> Option<InsertData> {
832 let this = unwrap_or_clone_arc(self);
833 as_variant!(this, Self::Insert { index, value } => {
834 InsertData { index: index.try_into().unwrap(), item: value }
835 })
836 }
837
838 pub fn set(self: Arc<Self>) -> Option<SetData> {
839 let this = unwrap_or_clone_arc(self);
840 as_variant!(this, Self::Set { index, value } => {
841 SetData { index: index.try_into().unwrap(), item: value }
842 })
843 }
844
845 pub fn remove(&self) -> Option<u32> {
846 as_variant!(self, Self::Remove { index } => (*index).try_into().unwrap())
847 }
848
849 pub fn push_back(self: Arc<Self>) -> Option<Arc<TimelineItem>> {
850 let this = unwrap_or_clone_arc(self);
851 as_variant!(this, Self::PushBack { value } => value)
852 }
853
854 pub fn push_front(self: Arc<Self>) -> Option<Arc<TimelineItem>> {
855 let this = unwrap_or_clone_arc(self);
856 as_variant!(this, Self::PushFront { value } => value)
857 }
858
859 pub fn reset(self: Arc<Self>) -> Option<Vec<Arc<TimelineItem>>> {
860 let this = unwrap_or_clone_arc(self);
861 as_variant!(this, Self::Reset { values } => values)
862 }
863
864 pub fn truncate(&self) -> Option<u32> {
865 as_variant!(self, Self::Truncate { length } => (*length).try_into().unwrap())
866 }
867}
868
869#[derive(uniffi::Record)]
870pub struct InsertData {
871 pub index: u32,
872 pub item: Arc<TimelineItem>,
873}
874
875#[derive(uniffi::Record)]
876pub struct SetData {
877 pub index: u32,
878 pub item: Arc<TimelineItem>,
879}
880
881#[derive(Clone, Copy, uniffi::Enum)]
882pub enum TimelineChange {
883 Append,
884 Clear,
885 Insert,
886 Set,
887 Remove,
888 PushBack,
889 PushFront,
890 PopBack,
891 PopFront,
892 Truncate,
893 Reset,
894}
895
896#[derive(Clone, uniffi::Record)]
897pub struct TimelineUniqueId {
898 id: String,
899}
900
901impl From<&SdkTimelineUniqueId> for TimelineUniqueId {
902 fn from(value: &SdkTimelineUniqueId) -> Self {
903 Self { id: value.0.clone() }
904 }
905}
906
907impl From<&TimelineUniqueId> for SdkTimelineUniqueId {
908 fn from(value: &TimelineUniqueId) -> Self {
909 Self(value.id.clone())
910 }
911}
912
913#[repr(transparent)]
914#[derive(Clone, uniffi::Object)]
915pub struct TimelineItem(pub(crate) matrix_sdk_ui::timeline::TimelineItem);
916
917impl TimelineItem {
918 pub(crate) fn from_arc(arc: Arc<matrix_sdk_ui::timeline::TimelineItem>) -> Arc<Self> {
919 unsafe { Arc::from_raw(Arc::into_raw(arc) as _) }
922 }
923}
924
925#[matrix_sdk_ffi_macros::export]
926impl TimelineItem {
927 pub fn as_event(self: Arc<Self>) -> Option<EventTimelineItem> {
928 let event_item = self.0.as_event()?;
929 Some(event_item.clone().into())
930 }
931
932 pub fn as_virtual(self: Arc<Self>) -> Option<VirtualTimelineItem> {
933 use matrix_sdk_ui::timeline::VirtualTimelineItem as VItem;
934 match self.0.as_virtual()? {
935 VItem::DateDivider(ts) => Some(VirtualTimelineItem::DateDivider { ts: (*ts).into() }),
936 VItem::ReadMarker => Some(VirtualTimelineItem::ReadMarker),
937 }
938 }
939
940 pub fn unique_id(&self) -> TimelineUniqueId {
942 self.0.unique_id().into()
943 }
944
945 pub fn fmt_debug(&self) -> String {
946 format!("{:#?}", self.0)
947 }
948}
949
950#[derive(Clone, uniffi::Enum)]
952pub enum EventSendState {
953 NotSentYet,
955
956 SendingFailed {
959 error: QueueWedgeError,
961
962 is_recoverable: bool,
968 },
969
970 Sent { event_id: String },
972}
973
974impl From<&matrix_sdk_ui::timeline::EventSendState> for EventSendState {
975 fn from(value: &matrix_sdk_ui::timeline::EventSendState) -> Self {
976 use matrix_sdk_ui::timeline::EventSendState::*;
977
978 match value {
979 NotSentYet => Self::NotSentYet,
980 SendingFailed { error, is_recoverable } => {
981 let as_queue_wedge_error: matrix_sdk::QueueWedgeError = (&**error).into();
982 Self::SendingFailed {
983 is_recoverable: *is_recoverable,
984 error: as_queue_wedge_error.into(),
985 }
986 }
987 Sent { event_id } => Self::Sent { event_id: event_id.to_string() },
988 }
989 }
990}
991
992#[derive(uniffi::Enum, Clone)]
995pub enum ShieldState {
996 Red { code: ShieldStateCode, message: String },
999 Grey { code: ShieldStateCode, message: String },
1002 None,
1004}
1005
1006impl From<SdkShieldState> for ShieldState {
1007 fn from(value: SdkShieldState) -> Self {
1008 match value {
1009 SdkShieldState::Red { code, message } => {
1010 Self::Red { code, message: message.to_owned() }
1011 }
1012 SdkShieldState::Grey { code, message } => {
1013 Self::Grey { code, message: message.to_owned() }
1014 }
1015 SdkShieldState::None => Self::None,
1016 }
1017 }
1018}
1019
1020#[derive(Clone, uniffi::Record)]
1021pub struct EventTimelineItem {
1022 is_remote: bool,
1024 event_or_transaction_id: EventOrTransactionId,
1025 sender: String,
1026 sender_profile: ProfileDetails,
1027 is_own: bool,
1028 is_editable: bool,
1029 content: TimelineItemContent,
1030 timestamp: Timestamp,
1031 reactions: Vec<Reaction>,
1032 local_send_state: Option<EventSendState>,
1033 local_created_at: Option<u64>,
1034 read_receipts: HashMap<String, Receipt>,
1035 origin: Option<EventItemOrigin>,
1036 can_be_replied_to: bool,
1037 lazy_provider: Arc<LazyTimelineItemProvider>,
1038}
1039
1040impl From<matrix_sdk_ui::timeline::EventTimelineItem> for EventTimelineItem {
1041 fn from(item: matrix_sdk_ui::timeline::EventTimelineItem) -> Self {
1042 let reactions = item
1043 .content()
1044 .reactions()
1045 .iter()
1046 .map(|(k, v)| Reaction {
1047 key: k.to_owned(),
1048 senders: v
1049 .into_iter()
1050 .map(|(sender_id, info)| ReactionSenderData {
1051 sender_id: sender_id.to_string(),
1052 timestamp: info.timestamp.into(),
1053 })
1054 .collect(),
1055 })
1056 .collect();
1057 let item = Arc::new(item);
1058 let lazy_provider = Arc::new(LazyTimelineItemProvider(item.clone()));
1059 let read_receipts =
1060 item.read_receipts().iter().map(|(k, v)| (k.to_string(), v.clone().into())).collect();
1061 Self {
1062 is_remote: !item.is_local_echo(),
1063 event_or_transaction_id: item.identifier().into(),
1064 sender: item.sender().to_string(),
1065 sender_profile: item.sender_profile().into(),
1066 is_own: item.is_own(),
1067 is_editable: item.is_editable(),
1068 content: item.content().clone().into(),
1069 timestamp: item.timestamp().into(),
1070 reactions,
1071 local_send_state: item.send_state().map(|s| s.into()),
1072 local_created_at: item.local_created_at().map(|t| t.0.into()),
1073 read_receipts,
1074 origin: item.origin(),
1075 can_be_replied_to: item.can_be_replied_to(),
1076 lazy_provider,
1077 }
1078 }
1079}
1080
1081#[derive(Clone, uniffi::Record)]
1082pub struct Receipt {
1083 pub timestamp: Option<Timestamp>,
1084}
1085
1086impl From<ruma::events::receipt::Receipt> for Receipt {
1087 fn from(value: ruma::events::receipt::Receipt) -> Self {
1088 Receipt { timestamp: value.ts.map(|ts| ts.into()) }
1089 }
1090}
1091
1092#[derive(Clone, uniffi::Record)]
1093pub struct EventTimelineItemDebugInfo {
1094 model: String,
1095 original_json: Option<String>,
1096 latest_edit_json: Option<String>,
1097}
1098
1099#[derive(Clone, uniffi::Enum)]
1100pub enum ProfileDetails {
1101 Unavailable,
1102 Pending,
1103 Ready { display_name: Option<String>, display_name_ambiguous: bool, avatar_url: Option<String> },
1104 Error { message: String },
1105}
1106
1107impl From<&TimelineDetails<Profile>> for ProfileDetails {
1108 fn from(details: &TimelineDetails<Profile>) -> Self {
1109 match details {
1110 TimelineDetails::Unavailable => Self::Unavailable,
1111 TimelineDetails::Pending => Self::Pending,
1112 TimelineDetails::Ready(profile) => Self::Ready {
1113 display_name: profile.display_name.clone(),
1114 display_name_ambiguous: profile.display_name_ambiguous,
1115 avatar_url: profile.avatar_url.as_ref().map(ToString::to_string),
1116 },
1117 TimelineDetails::Error(e) => Self::Error { message: e.to_string() },
1118 }
1119 }
1120}
1121
1122#[derive(Clone, uniffi::Record)]
1123pub struct PollData {
1124 question: String,
1125 answers: Vec<String>,
1126 max_selections: u8,
1127 poll_kind: PollKind,
1128}
1129
1130impl PollData {
1131 fn fallback_text(&self) -> String {
1132 self.answers.iter().enumerate().fold(self.question.clone(), |mut acc, (index, answer)| {
1133 write!(&mut acc, "\n{}. {answer}", index + 1).unwrap();
1134 acc
1135 })
1136 }
1137}
1138
1139impl TryFrom<PollData> for UnstablePollStartContentBlock {
1140 type Error = ClientError;
1141
1142 fn try_from(value: PollData) -> Result<Self, Self::Error> {
1143 let poll_answers_vec: Vec<UnstablePollAnswer> = value
1144 .answers
1145 .iter()
1146 .map(|answer| UnstablePollAnswer::new(Uuid::new_v4().to_string(), answer))
1147 .collect();
1148
1149 let poll_answers = UnstablePollAnswers::try_from(poll_answers_vec)
1150 .context("Failed to create poll answers")?;
1151
1152 let mut poll_content_block =
1153 UnstablePollStartContentBlock::new(value.question.clone(), poll_answers);
1154 poll_content_block.kind = value.poll_kind.into();
1155 poll_content_block.max_selections = value.max_selections.into();
1156
1157 Ok(poll_content_block)
1158 }
1159}
1160
1161#[derive(uniffi::Object)]
1162pub struct SendAttachmentJoinHandle {
1163 join_hdl: Arc<Mutex<JoinHandle<Result<(), RoomError>>>>,
1164 abort_hdl: AbortHandle,
1165}
1166
1167impl SendAttachmentJoinHandle {
1168 fn new(join_hdl: JoinHandle<Result<(), RoomError>>) -> Arc<Self> {
1169 let abort_hdl = join_hdl.abort_handle();
1170 let join_hdl = Arc::new(Mutex::new(join_hdl));
1171 Arc::new(Self { join_hdl, abort_hdl })
1172 }
1173}
1174
1175#[matrix_sdk_ffi_macros::export]
1176impl SendAttachmentJoinHandle {
1177 pub async fn join(&self) -> Result<(), RoomError> {
1181 let handle = self.join_hdl.clone();
1182 let mut locked_handle = handle.lock().await;
1183 let join_result = (&mut *locked_handle).await;
1184 match join_result {
1185 Ok(res) => res,
1186 Err(err) => {
1187 if err.is_cancelled() {
1188 return Ok(());
1189 }
1190 error!("task panicked! resuming panic from here.");
1191 panic::resume_unwind(err.into_panic());
1192 }
1193 }
1194 }
1195
1196 pub fn cancel(&self) {
1200 self.abort_hdl.abort();
1201 }
1202}
1203
1204#[derive(uniffi::Enum)]
1206pub enum VirtualTimelineItem {
1207 DateDivider {
1210 ts: Timestamp,
1213 },
1214
1215 ReadMarker,
1217}
1218
1219#[derive(uniffi::Enum)]
1221pub enum ReceiptType {
1222 Read,
1223 ReadPrivate,
1224 FullyRead,
1225}
1226
1227impl From<ReceiptType> for ruma::api::client::receipt::create_receipt::v3::ReceiptType {
1228 fn from(value: ReceiptType) -> Self {
1229 match value {
1230 ReceiptType::Read => Self::Read,
1231 ReceiptType::ReadPrivate => Self::ReadPrivate,
1232 ReceiptType::FullyRead => Self::FullyRead,
1233 }
1234 }
1235}
1236
1237#[derive(Clone, uniffi::Enum)]
1238pub enum EditedContent {
1239 RoomMessage {
1240 content: Arc<RoomMessageEventContentWithoutRelation>,
1241 },
1242 MediaCaption {
1243 caption: Option<String>,
1244 formatted_caption: Option<FormattedBody>,
1245 mentions: Option<Mentions>,
1246 },
1247 PollStart {
1248 poll_data: PollData,
1249 },
1250}
1251
1252impl TryFrom<EditedContent> for SdkEditedContent {
1253 type Error = ClientError;
1254
1255 fn try_from(value: EditedContent) -> Result<Self, Self::Error> {
1256 match value {
1257 EditedContent::RoomMessage { content } => {
1258 Ok(SdkEditedContent::RoomMessage((*content).clone()))
1259 }
1260 EditedContent::MediaCaption { caption, formatted_caption, mentions } => {
1261 Ok(SdkEditedContent::MediaCaption {
1262 caption,
1263 formatted_caption: formatted_caption.map(Into::into),
1264 mentions: mentions.map(Into::into),
1265 })
1266 }
1267 EditedContent::PollStart { poll_data } => {
1268 let block: UnstablePollStartContentBlock = poll_data.clone().try_into()?;
1269 Ok(SdkEditedContent::PollStart {
1270 fallback_text: poll_data.fallback_text(),
1271 new_content: block,
1272 })
1273 }
1274 }
1275 }
1276}
1277
1278#[matrix_sdk_ffi_macros::export]
1283fn create_caption_edit(
1284 caption: Option<String>,
1285 formatted_caption: Option<FormattedBody>,
1286 mentions: Option<Mentions>,
1287) -> EditedContent {
1288 let formatted_caption =
1289 formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into));
1290 EditedContent::MediaCaption {
1291 caption,
1292 formatted_caption: formatted_caption.as_ref().map(Into::into),
1293 mentions,
1294 }
1295}
1296
1297#[derive(Clone, uniffi::Object)]
1299pub struct LazyTimelineItemProvider(Arc<matrix_sdk_ui::timeline::EventTimelineItem>);
1300
1301#[matrix_sdk_ffi_macros::export]
1302impl LazyTimelineItemProvider {
1303 fn get_shields(&self, strict: bool) -> Option<ShieldState> {
1305 self.0.get_shield(strict).map(Into::into)
1306 }
1307
1308 fn debug_info(&self) -> EventTimelineItemDebugInfo {
1310 EventTimelineItemDebugInfo {
1311 model: format!("{:#?}", self.0),
1312 original_json: self.0.original_json().map(|raw| raw.json().get().to_owned()),
1313 latest_edit_json: self.0.latest_edit_json().map(|raw| raw.json().get().to_owned()),
1314 }
1315 }
1316
1317 fn get_send_handle(&self) -> Option<Arc<SendHandle>> {
1320 self.0.local_echo_send_handle().map(|handle| Arc::new(SendHandle::new(handle)))
1321 }
1322
1323 fn contains_only_emojis(&self) -> bool {
1324 self.0.contains_only_emojis()
1325 }
1326}