1use std::{collections::HashMap, fmt::Write as _, fs, panic, sync::Arc};
16
17use anyhow::{Context, Result};
18use as_variant::as_variant;
19use async_compat::get_runtime_handle;
20use content::{InReplyToDetails, RepliedToEventDetails};
21use eyeball_im::VectorDiff;
22use futures_util::{pin_mut, StreamExt as _};
23use matrix_sdk::{
24 attachment::{
25 AttachmentConfig, AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo,
26 BaseVideoInfo, Thumbnail,
27 },
28 deserialized_responses::{ShieldState as SdkShieldState, ShieldStateCode},
29 event_cache::RoomPaginationStatus,
30 room::{edit::EditedContent as SdkEditedContent, reply::EnforceThread},
31};
32use matrix_sdk_ui::timeline::{
33 self, EventItemOrigin, Profile, RepliedToEvent, TimelineDetails,
34 TimelineUniqueId as SdkTimelineUniqueId,
35};
36use mime::Mime;
37use ruma::{
38 events::{
39 location::{AssetType as RumaAssetType, LocationContent, ZoomLevel},
40 poll::{
41 unstable_end::UnstablePollEndEventContent,
42 unstable_response::UnstablePollResponseEventContent,
43 unstable_start::{
44 NewUnstablePollStartEventContent, UnstablePollAnswer, UnstablePollAnswers,
45 UnstablePollStartContentBlock,
46 },
47 },
48 receipt::ReceiptThread,
49 room::message::{
50 LocationMessageEventContent, MessageType, ReplyWithinThread,
51 RoomMessageEventContentWithoutRelation,
52 },
53 AnyMessageLikeEventContent,
54 },
55 EventId, UInt,
56};
57use tokio::{
58 sync::Mutex,
59 task::{AbortHandle, JoinHandle},
60};
61use tracing::{error, warn};
62use uuid::Uuid;
63
64use self::content::{Reaction, ReactionSenderData, TimelineItemContent};
65use crate::{
66 client::ProgressWatcher,
67 error::{ClientError, RoomError},
68 event::EventOrTransactionId,
69 helpers::unwrap_or_clone_arc,
70 ruma::{
71 AssetType, AudioInfo, FileInfo, FormattedBody, ImageInfo, Mentions, PollKind,
72 ThumbnailInfo, VideoInfo,
73 },
74 task_handle::TaskHandle,
75 utils::Timestamp,
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(get_runtime_handle().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 get_runtime_handle().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 listener.on_update(vec![Arc::new(TimelineDiff::new(VectorDiff::Reset {
224 values: timeline_items,
225 }))]);
226
227 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
228 pin_mut!(timeline_stream);
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 get_runtime_handle().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 listener.on_update(initial);
264
265 Ok(Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
266 while let Some(status) = subscriber.next().await {
267 listener.on_update(status);
268 }
269 }))))
270 }
271
272 pub async fn paginate_backwards(&self, num_events: u16) -> Result<bool, ClientError> {
276 Ok(self.inner.paginate_backwards(num_events).await?)
277 }
278
279 pub async fn paginate_forwards(&self, num_events: u16) -> Result<bool, ClientError> {
283 Ok(self.inner.paginate_forwards(num_events).await?)
284 }
285
286 pub async fn send_read_receipt(
287 &self,
288 receipt_type: ReceiptType,
289 event_id: String,
290 ) -> Result<(), ClientError> {
291 let event_id = EventId::parse(event_id)?;
292 self.inner
293 .send_single_receipt(receipt_type.into(), ReceiptThread::Unthreaded, event_id)
294 .await?;
295 Ok(())
296 }
297
298 pub async fn mark_as_read(&self, receipt_type: ReceiptType) -> Result<(), ClientError> {
305 self.inner.mark_as_read(receipt_type.into()).await?;
306 Ok(())
307 }
308
309 pub async fn send(
315 self: Arc<Self>,
316 msg: Arc<RoomMessageEventContentWithoutRelation>,
317 ) -> Result<Arc<SendHandle>, ClientError> {
318 match self.inner.send((*msg).to_owned().with_relation(None).into()).await {
319 Ok(handle) => Ok(Arc::new(SendHandle::new(handle))),
320 Err(err) => {
321 error!("error when sending a message: {err}");
322 Err(anyhow::anyhow!(err).into())
323 }
324 }
325 }
326
327 pub fn send_image(
328 self: Arc<Self>,
329 params: UploadParameters,
330 thumbnail_path: Option<String>,
331 image_info: ImageInfo,
332 progress_watcher: Option<Box<dyn ProgressWatcher>>,
333 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
334 let attachment_info = AttachmentInfo::Image(
335 BaseImageInfo::try_from(&image_info).map_err(|_| RoomError::InvalidAttachmentData)?,
336 );
337 let thumbnail = build_thumbnail_info(thumbnail_path, image_info.thumbnail_info)?;
338 self.send_attachment(
339 params,
340 attachment_info,
341 image_info.mimetype,
342 progress_watcher,
343 thumbnail,
344 )
345 }
346
347 pub fn send_video(
348 self: Arc<Self>,
349 params: UploadParameters,
350 thumbnail_path: Option<String>,
351 video_info: VideoInfo,
352 progress_watcher: Option<Box<dyn ProgressWatcher>>,
353 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
354 let attachment_info = AttachmentInfo::Video(
355 BaseVideoInfo::try_from(&video_info).map_err(|_| RoomError::InvalidAttachmentData)?,
356 );
357 let thumbnail = build_thumbnail_info(thumbnail_path, video_info.thumbnail_info)?;
358 self.send_attachment(
359 params,
360 attachment_info,
361 video_info.mimetype,
362 progress_watcher,
363 thumbnail,
364 )
365 }
366
367 pub fn send_audio(
368 self: Arc<Self>,
369 params: UploadParameters,
370 audio_info: AudioInfo,
371 progress_watcher: Option<Box<dyn ProgressWatcher>>,
372 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
373 let attachment_info = AttachmentInfo::Audio(
374 BaseAudioInfo::try_from(&audio_info).map_err(|_| RoomError::InvalidAttachmentData)?,
375 );
376 self.send_attachment(params, attachment_info, audio_info.mimetype, progress_watcher, None)
377 }
378
379 pub fn send_voice_message(
380 self: Arc<Self>,
381 params: UploadParameters,
382 audio_info: AudioInfo,
383 waveform: Vec<u16>,
384 progress_watcher: Option<Box<dyn ProgressWatcher>>,
385 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
386 let attachment_info = AttachmentInfo::Voice {
387 audio_info: BaseAudioInfo::try_from(&audio_info)
388 .map_err(|_| RoomError::InvalidAttachmentData)?,
389 waveform: Some(waveform),
390 };
391 self.send_attachment(params, attachment_info, audio_info.mimetype, progress_watcher, None)
392 }
393
394 pub fn send_file(
395 self: Arc<Self>,
396 params: UploadParameters,
397 file_info: FileInfo,
398 progress_watcher: Option<Box<dyn ProgressWatcher>>,
399 ) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
400 let attachment_info = AttachmentInfo::File(
401 BaseFileInfo::try_from(&file_info).map_err(|_| RoomError::InvalidAttachmentData)?,
402 );
403 self.send_attachment(params, attachment_info, file_info.mimetype, progress_watcher, None)
404 }
405
406 pub async fn create_poll(
407 self: Arc<Self>,
408 question: String,
409 answers: Vec<String>,
410 max_selections: u8,
411 poll_kind: PollKind,
412 ) -> Result<(), ClientError> {
413 let poll_data = PollData { question, answers, max_selections, poll_kind };
414
415 let poll_start_event_content = NewUnstablePollStartEventContent::plain_text(
416 poll_data.fallback_text(),
417 poll_data.try_into()?,
418 );
419 let event_content =
420 AnyMessageLikeEventContent::UnstablePollStart(poll_start_event_content.into());
421
422 if let Err(err) = self.inner.send(event_content).await {
423 error!("unable to start poll: {err}");
424 }
425
426 Ok(())
427 }
428
429 pub async fn send_poll_response(
430 self: Arc<Self>,
431 poll_start_event_id: String,
432 answers: Vec<String>,
433 ) -> Result<(), ClientError> {
434 let poll_start_event_id =
435 EventId::parse(poll_start_event_id).context("Failed to parse EventId")?;
436 let poll_response_event_content =
437 UnstablePollResponseEventContent::new(answers, poll_start_event_id);
438 let event_content =
439 AnyMessageLikeEventContent::UnstablePollResponse(poll_response_event_content);
440
441 if let Err(err) = self.inner.send(event_content).await {
442 error!("unable to send poll response: {err}");
443 }
444
445 Ok(())
446 }
447
448 pub async fn end_poll(
449 self: Arc<Self>,
450 poll_start_event_id: String,
451 text: String,
452 ) -> Result<(), ClientError> {
453 let poll_start_event_id =
454 EventId::parse(poll_start_event_id).context("Failed to parse EventId")?;
455 let poll_end_event_content = UnstablePollEndEventContent::new(text, poll_start_event_id);
456 let event_content = AnyMessageLikeEventContent::UnstablePollEnd(poll_end_event_content);
457
458 if let Err(err) = self.inner.send(event_content).await {
459 error!("unable to end poll: {err}");
460 }
461
462 Ok(())
463 }
464
465 pub async fn send_reply(
471 &self,
472 msg: Arc<RoomMessageEventContentWithoutRelation>,
473 event_id: String,
474 ) -> Result<(), ClientError> {
475 let event_id = EventId::parse(event_id)?;
476 self.inner
477 .send_reply((*msg).clone(), event_id, EnforceThread::MaybeThreaded)
478 .await
479 .map_err(|err| anyhow::anyhow!(err))?;
480 Ok(())
481 }
482
483 pub async fn send_thread_reply(
496 &self,
497 msg: Arc<RoomMessageEventContentWithoutRelation>,
498 event_id: String,
499 is_reply: bool,
500 ) -> Result<(), ClientError> {
501 let event_id = EventId::parse(event_id)?;
502 self.inner
503 .send_reply(
504 (*msg).clone(),
505 event_id,
506 EnforceThread::Threaded(if is_reply {
507 ReplyWithinThread::Yes
508 } else {
509 ReplyWithinThread::No
510 }),
511 )
512 .await
513 .map_err(|err| anyhow::anyhow!(err))?;
514 Ok(())
515 }
516
517 pub async fn edit(
526 &self,
527 event_or_transaction_id: EventOrTransactionId,
528 new_content: EditedContent,
529 ) -> Result<(), ClientError> {
530 match self
531 .inner
532 .edit(&event_or_transaction_id.clone().try_into()?, new_content.clone().try_into()?)
533 .await
534 {
535 Ok(()) => Ok(()),
536
537 Err(timeline::Error::EventNotInTimeline(_)) => {
538 let event_id = match event_or_transaction_id {
541 EventOrTransactionId::EventId { event_id } => EventId::parse(event_id)?,
542 EventOrTransactionId::TransactionId { .. } => {
543 warn!("trying to apply an edit to a local echo that doesn't exist in this timeline, aborting");
544 return Ok(());
545 }
546 };
547 let room = self.inner.room();
548 let edit_event = room.make_edit_event(&event_id, new_content.try_into()?).await?;
549 room.send_queue().send(edit_event).await?;
550 Ok(())
551 }
552
553 Err(err) => Err(err.into()),
554 }
555 }
556
557 pub async fn send_location(
558 self: Arc<Self>,
559 body: String,
560 geo_uri: String,
561 description: Option<String>,
562 zoom_level: Option<u8>,
563 asset_type: Option<AssetType>,
564 ) {
565 let mut location_event_message_content =
566 LocationMessageEventContent::new(body, geo_uri.clone());
567
568 if let Some(asset_type) = asset_type {
569 location_event_message_content =
570 location_event_message_content.with_asset_type(RumaAssetType::from(asset_type));
571 }
572
573 let mut location_content = LocationContent::new(geo_uri);
574 location_content.description = description;
575 location_content.zoom_level = zoom_level.and_then(ZoomLevel::new);
576 location_event_message_content.location = Some(location_content);
577
578 let room_message_event_content = RoomMessageEventContentWithoutRelation::new(
579 MessageType::Location(location_event_message_content),
580 );
581 let _ = self.send(Arc::new(room_message_event_content)).await;
583 }
584
585 pub async fn toggle_reaction(
597 &self,
598 item_id: EventOrTransactionId,
599 key: String,
600 ) -> Result<(), ClientError> {
601 self.inner.toggle_reaction(&item_id.try_into()?, &key).await?;
602 Ok(())
603 }
604
605 pub async fn fetch_details_for_event(&self, event_id: String) -> Result<(), ClientError> {
606 let event_id = <&EventId>::try_from(event_id.as_str())?;
607 self.inner.fetch_details_for_event(event_id).await.context("Fetching event details")?;
608 Ok(())
609 }
610
611 pub async fn get_event_timeline_item_by_event_id(
620 &self,
621 event_id: String,
622 ) -> Result<EventTimelineItem, ClientError> {
623 let event_id = EventId::parse(event_id)?;
624 let item = self
625 .inner
626 .item_by_event_id(&event_id)
627 .await
628 .context("Item with given event ID not found")?;
629 Ok(item.into())
630 }
631
632 pub async fn redact_event(
642 &self,
643 event_or_transaction_id: EventOrTransactionId,
644 reason: Option<String>,
645 ) -> Result<(), ClientError> {
646 Ok(self.inner.redact(&(event_or_transaction_id.try_into()?), reason.as_deref()).await?)
647 }
648
649 pub async fn load_reply_details(
654 &self,
655 event_id_str: String,
656 ) -> Result<Arc<InReplyToDetails>, ClientError> {
657 let event_id = EventId::parse(&event_id_str)?;
658
659 let replied_to = match self.inner.room().load_or_fetch_event(&event_id, None).await {
660 Ok(event) => RepliedToEvent::try_from_timeline_event_for_room(event, self.inner.room())
661 .await
662 .map_err(ClientError::from),
663 Err(e) => Err(ClientError::from(e)),
664 };
665
666 match replied_to {
667 Ok(replied_to) => Ok(Arc::new(InReplyToDetails::new(
668 event_id_str,
669 RepliedToEventDetails::Ready {
670 content: replied_to.content().clone().into(),
671 sender: replied_to.sender().to_string(),
672 sender_profile: replied_to.sender_profile().into(),
673 },
674 ))),
675
676 Err(e) => Ok(Arc::new(InReplyToDetails::new(
677 event_id_str,
678 RepliedToEventDetails::Error { message: e.to_string() },
679 ))),
680 }
681 }
682
683 async fn pin_event(&self, event_id: String) -> Result<bool, ClientError> {
689 let event_id = EventId::parse(event_id).map_err(ClientError::from)?;
690 self.inner.pin_event(&event_id).await.map_err(ClientError::from)
691 }
692
693 async fn unpin_event(&self, event_id: String) -> Result<bool, ClientError> {
699 let event_id = EventId::parse(event_id).map_err(ClientError::from)?;
700 self.inner.unpin_event(&event_id).await.map_err(ClientError::from)
701 }
702
703 pub fn create_message_content(
704 &self,
705 msg_type: crate::ruma::MessageType,
706 ) -> Option<Arc<RoomMessageEventContentWithoutRelation>> {
707 let msg_type: Option<MessageType> = msg_type.try_into().ok();
708 msg_type.map(|m| Arc::new(RoomMessageEventContentWithoutRelation::new(m)))
709 }
710}
711
712#[derive(uniffi::Object)]
714pub struct SendHandle {
715 inner: Mutex<Option<matrix_sdk::send_queue::SendHandle>>,
716}
717
718impl SendHandle {
719 fn new(handle: matrix_sdk::send_queue::SendHandle) -> Self {
720 Self { inner: Mutex::new(Some(handle)) }
721 }
722}
723
724#[matrix_sdk_ffi_macros::export]
725impl SendHandle {
726 async fn abort(self: Arc<Self>) -> Result<bool, ClientError> {
735 if let Some(inner) = self.inner.lock().await.take() {
736 Ok(inner
737 .abort()
738 .await
739 .map_err(|err| anyhow::anyhow!("error when saving in store: {err}"))?)
740 } else {
741 warn!("trying to abort a send handle that's already been actioned");
742 Ok(false)
743 }
744 }
745
746 pub async fn try_resend(self: Arc<Self>) -> Result<(), ClientError> {
759 let locked = self.inner.lock().await;
760 if let Some(handle) = locked.as_ref() {
761 handle.unwedge().await?;
762 } else {
763 warn!("trying to unwedge a send handle that's been aborted");
764 }
765 Ok(())
766 }
767}
768
769#[derive(Debug, thiserror::Error, uniffi::Error)]
770pub enum FocusEventError {
771 #[error("the event id parameter {event_id} is incorrect: {err}")]
772 InvalidEventId { event_id: String, err: String },
773
774 #[error("the event {event_id} could not be found")]
775 EventNotFound { event_id: String },
776
777 #[error("error when trying to focus on an event: {msg}")]
778 Other { msg: String },
779}
780
781#[matrix_sdk_ffi_macros::export(callback_interface)]
782pub trait TimelineListener: Sync + Send {
783 fn on_update(&self, diff: Vec<Arc<TimelineDiff>>);
784}
785
786#[matrix_sdk_ffi_macros::export(callback_interface)]
787pub trait PaginationStatusListener: Sync + Send {
788 fn on_update(&self, status: RoomPaginationStatus);
789}
790
791#[derive(Clone, uniffi::Object)]
792pub enum TimelineDiff {
793 Append { values: Vec<Arc<TimelineItem>> },
794 Clear,
795 PushFront { value: Arc<TimelineItem> },
796 PushBack { value: Arc<TimelineItem> },
797 PopFront,
798 PopBack,
799 Insert { index: usize, value: Arc<TimelineItem> },
800 Set { index: usize, value: Arc<TimelineItem> },
801 Remove { index: usize },
802 Truncate { length: usize },
803 Reset { values: Vec<Arc<TimelineItem>> },
804}
805
806impl TimelineDiff {
807 pub(crate) fn new(inner: VectorDiff<Arc<matrix_sdk_ui::timeline::TimelineItem>>) -> Self {
808 match inner {
809 VectorDiff::Append { values } => {
810 Self::Append { values: values.into_iter().map(TimelineItem::from_arc).collect() }
811 }
812 VectorDiff::Clear => Self::Clear,
813 VectorDiff::Insert { index, value } => {
814 Self::Insert { index, value: TimelineItem::from_arc(value) }
815 }
816 VectorDiff::Set { index, value } => {
817 Self::Set { index, value: TimelineItem::from_arc(value) }
818 }
819 VectorDiff::Truncate { length } => Self::Truncate { length },
820 VectorDiff::Remove { index } => Self::Remove { index },
821 VectorDiff::PushBack { value } => {
822 Self::PushBack { value: TimelineItem::from_arc(value) }
823 }
824 VectorDiff::PushFront { value } => {
825 Self::PushFront { value: TimelineItem::from_arc(value) }
826 }
827 VectorDiff::PopBack => Self::PopBack,
828 VectorDiff::PopFront => Self::PopFront,
829 VectorDiff::Reset { values } => {
830 Self::Reset { values: values.into_iter().map(TimelineItem::from_arc).collect() }
831 }
832 }
833 }
834}
835
836#[matrix_sdk_ffi_macros::export]
837impl TimelineDiff {
838 pub fn change(&self) -> TimelineChange {
839 match self {
840 Self::Append { .. } => TimelineChange::Append,
841 Self::Insert { .. } => TimelineChange::Insert,
842 Self::Set { .. } => TimelineChange::Set,
843 Self::Remove { .. } => TimelineChange::Remove,
844 Self::PushBack { .. } => TimelineChange::PushBack,
845 Self::PushFront { .. } => TimelineChange::PushFront,
846 Self::PopBack => TimelineChange::PopBack,
847 Self::PopFront => TimelineChange::PopFront,
848 Self::Clear => TimelineChange::Clear,
849 Self::Truncate { .. } => TimelineChange::Truncate,
850 Self::Reset { .. } => TimelineChange::Reset,
851 }
852 }
853
854 pub fn append(self: Arc<Self>) -> Option<Vec<Arc<TimelineItem>>> {
855 let this = unwrap_or_clone_arc(self);
856 as_variant!(this, Self::Append { values } => values)
857 }
858
859 pub fn insert(self: Arc<Self>) -> Option<InsertData> {
860 let this = unwrap_or_clone_arc(self);
861 as_variant!(this, Self::Insert { index, value } => {
862 InsertData { index: index.try_into().unwrap(), item: value }
863 })
864 }
865
866 pub fn set(self: Arc<Self>) -> Option<SetData> {
867 let this = unwrap_or_clone_arc(self);
868 as_variant!(this, Self::Set { index, value } => {
869 SetData { index: index.try_into().unwrap(), item: value }
870 })
871 }
872
873 pub fn remove(&self) -> Option<u32> {
874 as_variant!(self, Self::Remove { index } => (*index).try_into().unwrap())
875 }
876
877 pub fn push_back(self: Arc<Self>) -> Option<Arc<TimelineItem>> {
878 let this = unwrap_or_clone_arc(self);
879 as_variant!(this, Self::PushBack { value } => value)
880 }
881
882 pub fn push_front(self: Arc<Self>) -> Option<Arc<TimelineItem>> {
883 let this = unwrap_or_clone_arc(self);
884 as_variant!(this, Self::PushFront { value } => value)
885 }
886
887 pub fn reset(self: Arc<Self>) -> Option<Vec<Arc<TimelineItem>>> {
888 let this = unwrap_or_clone_arc(self);
889 as_variant!(this, Self::Reset { values } => values)
890 }
891
892 pub fn truncate(&self) -> Option<u32> {
893 as_variant!(self, Self::Truncate { length } => (*length).try_into().unwrap())
894 }
895}
896
897#[derive(uniffi::Record)]
898pub struct InsertData {
899 pub index: u32,
900 pub item: Arc<TimelineItem>,
901}
902
903#[derive(uniffi::Record)]
904pub struct SetData {
905 pub index: u32,
906 pub item: Arc<TimelineItem>,
907}
908
909#[derive(Clone, Copy, uniffi::Enum)]
910pub enum TimelineChange {
911 Append,
912 Clear,
913 Insert,
914 Set,
915 Remove,
916 PushBack,
917 PushFront,
918 PopBack,
919 PopFront,
920 Truncate,
921 Reset,
922}
923
924#[derive(Clone, uniffi::Record)]
925pub struct TimelineUniqueId {
926 id: String,
927}
928
929impl From<&SdkTimelineUniqueId> for TimelineUniqueId {
930 fn from(value: &SdkTimelineUniqueId) -> Self {
931 Self { id: value.0.clone() }
932 }
933}
934
935impl From<&TimelineUniqueId> for SdkTimelineUniqueId {
936 fn from(value: &TimelineUniqueId) -> Self {
937 Self(value.id.clone())
938 }
939}
940
941#[repr(transparent)]
942#[derive(Clone, uniffi::Object)]
943pub struct TimelineItem(pub(crate) matrix_sdk_ui::timeline::TimelineItem);
944
945impl TimelineItem {
946 pub(crate) fn from_arc(arc: Arc<matrix_sdk_ui::timeline::TimelineItem>) -> Arc<Self> {
947 unsafe { Arc::from_raw(Arc::into_raw(arc) as _) }
950 }
951}
952
953#[matrix_sdk_ffi_macros::export]
954impl TimelineItem {
955 pub fn as_event(self: Arc<Self>) -> Option<EventTimelineItem> {
956 let event_item = self.0.as_event()?;
957 Some(event_item.clone().into())
958 }
959
960 pub fn as_virtual(self: Arc<Self>) -> Option<VirtualTimelineItem> {
961 use matrix_sdk_ui::timeline::VirtualTimelineItem as VItem;
962 match self.0.as_virtual()? {
963 VItem::DateDivider(ts) => Some(VirtualTimelineItem::DateDivider { ts: (*ts).into() }),
964 VItem::ReadMarker => Some(VirtualTimelineItem::ReadMarker),
965 VItem::TimelineStart => Some(VirtualTimelineItem::TimelineStart),
966 }
967 }
968
969 pub fn unique_id(&self) -> TimelineUniqueId {
971 self.0.unique_id().into()
972 }
973
974 pub fn fmt_debug(&self) -> String {
975 format!("{:#?}", self.0)
976 }
977}
978
979#[derive(Clone, uniffi::Enum)]
981pub enum EventSendState {
982 NotSentYet,
984
985 SendingFailed {
988 error: QueueWedgeError,
990
991 is_recoverable: bool,
997 },
998
999 Sent { event_id: String },
1001}
1002
1003impl From<&matrix_sdk_ui::timeline::EventSendState> for EventSendState {
1004 fn from(value: &matrix_sdk_ui::timeline::EventSendState) -> Self {
1005 use matrix_sdk_ui::timeline::EventSendState::*;
1006
1007 match value {
1008 NotSentYet => Self::NotSentYet,
1009 SendingFailed { error, is_recoverable } => {
1010 let as_queue_wedge_error: matrix_sdk::QueueWedgeError = (&**error).into();
1011 Self::SendingFailed {
1012 is_recoverable: *is_recoverable,
1013 error: as_queue_wedge_error.into(),
1014 }
1015 }
1016 Sent { event_id } => Self::Sent { event_id: event_id.to_string() },
1017 }
1018 }
1019}
1020
1021#[derive(uniffi::Enum, Clone)]
1024pub enum ShieldState {
1025 Red { code: ShieldStateCode, message: String },
1028 Grey { code: ShieldStateCode, message: String },
1031 None,
1033}
1034
1035impl From<SdkShieldState> for ShieldState {
1036 fn from(value: SdkShieldState) -> Self {
1037 match value {
1038 SdkShieldState::Red { code, message } => {
1039 Self::Red { code, message: message.to_owned() }
1040 }
1041 SdkShieldState::Grey { code, message } => {
1042 Self::Grey { code, message: message.to_owned() }
1043 }
1044 SdkShieldState::None => Self::None,
1045 }
1046 }
1047}
1048
1049#[derive(Clone, uniffi::Record)]
1050pub struct EventTimelineItem {
1051 is_remote: bool,
1053 event_or_transaction_id: EventOrTransactionId,
1054 sender: String,
1055 sender_profile: ProfileDetails,
1056 is_own: bool,
1057 is_editable: bool,
1058 content: TimelineItemContent,
1059 timestamp: Timestamp,
1060 reactions: Vec<Reaction>,
1061 local_send_state: Option<EventSendState>,
1062 local_created_at: Option<u64>,
1063 read_receipts: HashMap<String, Receipt>,
1064 origin: Option<EventItemOrigin>,
1065 can_be_replied_to: bool,
1066 lazy_provider: Arc<LazyTimelineItemProvider>,
1067}
1068
1069impl From<matrix_sdk_ui::timeline::EventTimelineItem> for EventTimelineItem {
1070 fn from(item: matrix_sdk_ui::timeline::EventTimelineItem) -> Self {
1071 let reactions = item
1072 .content()
1073 .reactions()
1074 .iter()
1075 .map(|(k, v)| Reaction {
1076 key: k.to_owned(),
1077 senders: v
1078 .into_iter()
1079 .map(|(sender_id, info)| ReactionSenderData {
1080 sender_id: sender_id.to_string(),
1081 timestamp: info.timestamp.into(),
1082 })
1083 .collect(),
1084 })
1085 .collect();
1086 let item = Arc::new(item);
1087 let lazy_provider = Arc::new(LazyTimelineItemProvider(item.clone()));
1088 let read_receipts =
1089 item.read_receipts().iter().map(|(k, v)| (k.to_string(), v.clone().into())).collect();
1090 Self {
1091 is_remote: !item.is_local_echo(),
1092 event_or_transaction_id: item.identifier().into(),
1093 sender: item.sender().to_string(),
1094 sender_profile: item.sender_profile().into(),
1095 is_own: item.is_own(),
1096 is_editable: item.is_editable(),
1097 content: item.content().clone().into(),
1098 timestamp: item.timestamp().into(),
1099 reactions,
1100 local_send_state: item.send_state().map(|s| s.into()),
1101 local_created_at: item.local_created_at().map(|t| t.0.into()),
1102 read_receipts,
1103 origin: item.origin(),
1104 can_be_replied_to: item.can_be_replied_to(),
1105 lazy_provider,
1106 }
1107 }
1108}
1109
1110#[derive(Clone, uniffi::Record)]
1111pub struct Receipt {
1112 pub timestamp: Option<Timestamp>,
1113}
1114
1115impl From<ruma::events::receipt::Receipt> for Receipt {
1116 fn from(value: ruma::events::receipt::Receipt) -> Self {
1117 Receipt { timestamp: value.ts.map(|ts| ts.into()) }
1118 }
1119}
1120
1121#[derive(Clone, uniffi::Record)]
1122pub struct EventTimelineItemDebugInfo {
1123 model: String,
1124 original_json: Option<String>,
1125 latest_edit_json: Option<String>,
1126}
1127
1128#[derive(Clone, uniffi::Enum)]
1129pub enum ProfileDetails {
1130 Unavailable,
1131 Pending,
1132 Ready { display_name: Option<String>, display_name_ambiguous: bool, avatar_url: Option<String> },
1133 Error { message: String },
1134}
1135
1136impl From<&TimelineDetails<Profile>> for ProfileDetails {
1137 fn from(details: &TimelineDetails<Profile>) -> Self {
1138 match details {
1139 TimelineDetails::Unavailable => Self::Unavailable,
1140 TimelineDetails::Pending => Self::Pending,
1141 TimelineDetails::Ready(profile) => Self::Ready {
1142 display_name: profile.display_name.clone(),
1143 display_name_ambiguous: profile.display_name_ambiguous,
1144 avatar_url: profile.avatar_url.as_ref().map(ToString::to_string),
1145 },
1146 TimelineDetails::Error(e) => Self::Error { message: e.to_string() },
1147 }
1148 }
1149}
1150
1151#[derive(Clone, uniffi::Record)]
1152pub struct PollData {
1153 question: String,
1154 answers: Vec<String>,
1155 max_selections: u8,
1156 poll_kind: PollKind,
1157}
1158
1159impl PollData {
1160 fn fallback_text(&self) -> String {
1161 self.answers.iter().enumerate().fold(self.question.clone(), |mut acc, (index, answer)| {
1162 write!(&mut acc, "\n{}. {answer}", index + 1).unwrap();
1163 acc
1164 })
1165 }
1166}
1167
1168impl TryFrom<PollData> for UnstablePollStartContentBlock {
1169 type Error = ClientError;
1170
1171 fn try_from(value: PollData) -> Result<Self, Self::Error> {
1172 let poll_answers_vec: Vec<UnstablePollAnswer> = value
1173 .answers
1174 .iter()
1175 .map(|answer| UnstablePollAnswer::new(Uuid::new_v4().to_string(), answer))
1176 .collect();
1177
1178 let poll_answers = UnstablePollAnswers::try_from(poll_answers_vec)
1179 .context("Failed to create poll answers")?;
1180
1181 let mut poll_content_block =
1182 UnstablePollStartContentBlock::new(value.question.clone(), poll_answers);
1183 poll_content_block.kind = value.poll_kind.into();
1184 poll_content_block.max_selections = value.max_selections.into();
1185
1186 Ok(poll_content_block)
1187 }
1188}
1189
1190#[derive(uniffi::Object)]
1191pub struct SendAttachmentJoinHandle {
1192 join_hdl: Arc<Mutex<JoinHandle<Result<(), RoomError>>>>,
1193 abort_hdl: AbortHandle,
1194}
1195
1196impl SendAttachmentJoinHandle {
1197 fn new(join_hdl: JoinHandle<Result<(), RoomError>>) -> Arc<Self> {
1198 let abort_hdl = join_hdl.abort_handle();
1199 let join_hdl = Arc::new(Mutex::new(join_hdl));
1200 Arc::new(Self { join_hdl, abort_hdl })
1201 }
1202}
1203
1204#[matrix_sdk_ffi_macros::export]
1205impl SendAttachmentJoinHandle {
1206 pub async fn join(&self) -> Result<(), RoomError> {
1210 let handle = self.join_hdl.clone();
1211 let mut locked_handle = handle.lock().await;
1212 let join_result = (&mut *locked_handle).await;
1213 match join_result {
1214 Ok(res) => res,
1215 Err(err) => {
1216 if err.is_cancelled() {
1217 return Ok(());
1218 }
1219 error!("task panicked! resuming panic from here.");
1220 panic::resume_unwind(err.into_panic());
1221 }
1222 }
1223 }
1224
1225 pub fn cancel(&self) {
1229 self.abort_hdl.abort();
1230 }
1231}
1232
1233#[derive(uniffi::Enum)]
1235pub enum VirtualTimelineItem {
1236 DateDivider {
1239 ts: Timestamp,
1242 },
1243
1244 ReadMarker,
1246
1247 TimelineStart,
1249}
1250
1251#[derive(uniffi::Enum)]
1253pub enum ReceiptType {
1254 Read,
1255 ReadPrivate,
1256 FullyRead,
1257}
1258
1259impl From<ReceiptType> for ruma::api::client::receipt::create_receipt::v3::ReceiptType {
1260 fn from(value: ReceiptType) -> Self {
1261 match value {
1262 ReceiptType::Read => Self::Read,
1263 ReceiptType::ReadPrivate => Self::ReadPrivate,
1264 ReceiptType::FullyRead => Self::FullyRead,
1265 }
1266 }
1267}
1268
1269#[derive(Clone, uniffi::Enum)]
1270pub enum EditedContent {
1271 RoomMessage {
1272 content: Arc<RoomMessageEventContentWithoutRelation>,
1273 },
1274 MediaCaption {
1275 caption: Option<String>,
1276 formatted_caption: Option<FormattedBody>,
1277 mentions: Option<Mentions>,
1278 },
1279 PollStart {
1280 poll_data: PollData,
1281 },
1282}
1283
1284impl TryFrom<EditedContent> for SdkEditedContent {
1285 type Error = ClientError;
1286
1287 fn try_from(value: EditedContent) -> Result<Self, Self::Error> {
1288 match value {
1289 EditedContent::RoomMessage { content } => {
1290 Ok(SdkEditedContent::RoomMessage((*content).clone()))
1291 }
1292 EditedContent::MediaCaption { caption, formatted_caption, mentions } => {
1293 Ok(SdkEditedContent::MediaCaption {
1294 caption,
1295 formatted_caption: formatted_caption.map(Into::into),
1296 mentions: mentions.map(Into::into),
1297 })
1298 }
1299 EditedContent::PollStart { poll_data } => {
1300 let block: UnstablePollStartContentBlock = poll_data.clone().try_into()?;
1301 Ok(SdkEditedContent::PollStart {
1302 fallback_text: poll_data.fallback_text(),
1303 new_content: block,
1304 })
1305 }
1306 }
1307 }
1308}
1309
1310#[matrix_sdk_ffi_macros::export]
1315fn create_caption_edit(
1316 caption: Option<String>,
1317 formatted_caption: Option<FormattedBody>,
1318 mentions: Option<Mentions>,
1319) -> EditedContent {
1320 let formatted_caption =
1321 formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into));
1322 EditedContent::MediaCaption {
1323 caption,
1324 formatted_caption: formatted_caption.as_ref().map(Into::into),
1325 mentions,
1326 }
1327}
1328
1329#[derive(Clone, uniffi::Object)]
1331pub struct LazyTimelineItemProvider(Arc<matrix_sdk_ui::timeline::EventTimelineItem>);
1332
1333#[matrix_sdk_ffi_macros::export]
1334impl LazyTimelineItemProvider {
1335 fn get_shields(&self, strict: bool) -> Option<ShieldState> {
1337 self.0.get_shield(strict).map(Into::into)
1338 }
1339
1340 fn debug_info(&self) -> EventTimelineItemDebugInfo {
1342 EventTimelineItemDebugInfo {
1343 model: format!("{:#?}", self.0),
1344 original_json: self.0.original_json().map(|raw| raw.json().get().to_owned()),
1345 latest_edit_json: self.0.latest_edit_json().map(|raw| raw.json().get().to_owned()),
1346 }
1347 }
1348
1349 fn get_send_handle(&self) -> Option<Arc<SendHandle>> {
1352 self.0.local_echo_send_handle().map(|handle| Arc::new(SendHandle::new(handle)))
1353 }
1354
1355 fn contains_only_emojis(&self) -> bool {
1356 self.0.contains_only_emojis()
1357 }
1358}