matrix_sdk_ffi/timeline/
mod.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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        // SAFETY: repr(transparent) means transmuting the arc this way is allowed
99        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 (previously called "url") for the media to be sent.
199    filename: String,
200    /// Optional non-formatted caption, for clients that support it.
201    caption: Option<String>,
202    /// Optional HTML-formatted caption, for clients that support it.
203    formatted_caption: Option<FormattedBody>,
204    // Optional intentional mentions to be sent with the media.
205    mentions: Option<Mentions>,
206    /// Should the media be sent with the send queue, or synchronously?
207    ///
208    /// Watching progress only works with the synchronous method, at the moment.
209    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            // It's important that the initial items are passed *before* we forward the
221            // stream updates, with a guaranteed ordering. Otherwise, it could
222            // be that the listener be called before the initial items have been
223            // handled by the caller. See #3535 for details.
224
225            // First, pass all the items as a reset update.
226            listener.on_update(vec![Arc::new(TimelineDiff::new(VectorDiff::Reset {
227                values: timeline_items,
228            }))]);
229
230            // Then forward new items.
231            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            // Send the current state even if it hasn't changed right away.
260            listener.on_update(initial);
261
262            while let Some(status) = subscriber.next().await {
263                listener.on_update(status);
264            }
265        }))))
266    }
267
268    /// Paginate backwards, whether we are in focused mode or in live mode.
269    ///
270    /// Returns whether we hit the start of the timeline or not.
271    pub async fn paginate_backwards(&self, num_events: u16) -> Result<bool, ClientError> {
272        Ok(self.inner.paginate_backwards(num_events).await?)
273    }
274
275    /// Paginate forwards, whether we are in focused mode or in live mode.
276    ///
277    /// Returns whether we hit the end of the timeline or not.
278    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    /// Mark the room as read by trying to attach an *unthreaded* read receipt
295    /// to the latest room event.
296    ///
297    /// This works even if the latest event belongs to a thread, as a threaded
298    /// reply also belongs to the unthreaded timeline. No threaded receipt
299    /// will be sent here (see also #3123).
300    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    /// Queues an event in the room's send queue so it's processed for
306    /// sending later.
307    ///
308    /// Returns an abort handle that allows to abort sending, if it hasn't
309    /// happened yet.
310    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    /// Edits an event from the timeline.
483    ///
484    /// If it was a local event, this will *try* to edit it, if it was not
485    /// being sent already. If the event was a remote event, then it will be
486    /// redacted by sending an edit request to the server.
487    ///
488    /// Returns whether the edit did happen. It can only return false for
489    /// local events that are being processed.
490    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                // If we couldn't edit, assume it was an (remote) event that wasn't in the
504                // timeline, and try to edit it via the room itself.
505                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        // Errors are logged in `Self::send` already.
547        let _ = self.send(Arc::new(room_message_event_content)).await;
548    }
549
550    /// Toggle a reaction on an event.
551    ///
552    /// Adds or redacts a reaction based on the state of the reaction at the
553    /// time it is called.
554    ///
555    /// This method works both on local echoes and remote items.
556    ///
557    /// When redacting a previous reaction, the redaction reason is not set.
558    ///
559    /// Ensures that only one reaction is sent at a time to avoid race
560    /// conditions and spamming the homeserver with requests.
561    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    /// Get the current timeline item for the given event ID, if any.
577    ///
578    /// Will return a remote event, *or* a local echo that has been sent but not
579    /// yet replaced by a remote echo.
580    ///
581    /// It's preferable to store the timeline items in the model for your UI, if
582    /// possible, instead of just storing IDs and coming back to the timeline
583    /// object to look up items.
584    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    /// Redacts an event from the timeline.
598    ///
599    /// Only works for events that exist as timeline items.
600    ///
601    /// If it was a local event, this will *try* to cancel it, if it was not
602    /// being sent already. If the event was a remote event, then it will be
603    /// redacted by sending a redaction request to the server.
604    ///
605    /// Will return an error if the event couldn't be redacted.
606    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    /// Load the reply details for the given event id.
615    ///
616    /// This will return an `InReplyToDetails` object that contains the details
617    /// which will either be ready or an error.
618    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    /// Adds a new pinned event by sending an updated `m.room.pinned_events`
656    /// event containing the new event id.
657    ///
658    /// Returns `true` if we sent the request, `false` if the event was already
659    /// pinned.
660    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    /// Adds a new pinned event by sending an updated `m.room.pinned_events`
666    /// event without the event id we want to remove.
667    ///
668    /// Returns `true` if we sent the request, `false` if the event wasn't
669    /// pinned
670    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/// A handle to perform actions onto a local echo.
685#[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    /// Try to abort the sending of the current event.
699    ///
700    /// If this returns `true`, then the sending could be aborted, because the
701    /// event hasn't been sent yet. Otherwise, if this returns `false`, the
702    /// event had already been sent and could not be aborted.
703    ///
704    /// This has an effect only on the first call; subsequent calls will always
705    /// return `false`.
706    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    /// Attempt to manually resend messages that failed to send due to issues
719    /// that should now have been fixed.
720    ///
721    /// This is useful for example, when there's a
722    /// `SessionRecipientCollectionError::VerifiedUserChangedIdentity` error;
723    /// the user may have re-verified on a different device and would now
724    /// like to send the failed message that's waiting on this device.
725    ///
726    /// # Arguments
727    ///
728    /// * `transaction_id` - The send queue transaction identifier of the local
729    ///   echo that should be unwedged.
730    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        // SAFETY: This is valid because Self is a repr(transparent) wrapper
920        //         around the other Timeline type.
921        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    /// An opaque unique identifier for this timeline item.
941    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/// This type represents the “send state” of a local event timeline item.
951#[derive(Clone, uniffi::Enum)]
952pub enum EventSendState {
953    /// The local event has not been sent yet.
954    NotSentYet,
955
956    /// The local event has been sent to the server, but unsuccessfully: The
957    /// sending has failed.
958    SendingFailed {
959        /// The error reason, with information for the user.
960        error: QueueWedgeError,
961
962        /// Whether the error is considered recoverable or not.
963        ///
964        /// An error that's recoverable will disable the room's send queue,
965        /// while an unrecoverable error will be parked, until the user
966        /// decides to cancel sending it.
967        is_recoverable: bool,
968    },
969
970    /// The local event has been sent successfully to the server.
971    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/// Recommended decorations for decrypted messages, representing the message's
993/// authenticity properties.
994#[derive(uniffi::Enum, Clone)]
995pub enum ShieldState {
996    /// A red shield with a tooltip containing the associated message should be
997    /// presented.
998    Red { code: ShieldStateCode, message: String },
999    /// A grey shield with a tooltip containing the associated message should be
1000    /// presented.
1001    Grey { code: ShieldStateCode, message: String },
1002    /// No shield should be presented.
1003    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    /// Indicates that an event is remote.
1023    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    /// Wait until the attachment has been sent.
1178    ///
1179    /// If the sending had been cancelled, will return immediately.
1180    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    /// Cancel the current sending task.
1197    ///
1198    /// A subsequent call to [`Self::join`] will return immediately.
1199    pub fn cancel(&self) {
1200        self.abort_hdl.abort();
1201    }
1202}
1203
1204/// A [`TimelineItem`](super::TimelineItem) that doesn't correspond to an event.
1205#[derive(uniffi::Enum)]
1206pub enum VirtualTimelineItem {
1207    /// A divider between messages of different day or month depending on
1208    /// timeline settings.
1209    DateDivider {
1210        /// A timestamp in milliseconds since Unix Epoch on that day in local
1211        /// time.
1212        ts: Timestamp,
1213    },
1214
1215    /// The user's own read marker.
1216    ReadMarker,
1217}
1218
1219/// A [`TimelineItem`](super::TimelineItem) that doesn't correspond to an event.
1220#[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/// Create a caption edit.
1279///
1280/// If no `formatted_caption` is provided, then it's assumed the `caption`
1281/// represents valid Markdown that can be used as the formatted caption.
1282#[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/// Wrapper to retrieve some timeline item info lazily.
1298#[derive(Clone, uniffi::Object)]
1299pub struct LazyTimelineItemProvider(Arc<matrix_sdk_ui::timeline::EventTimelineItem>);
1300
1301#[matrix_sdk_ffi_macros::export]
1302impl LazyTimelineItemProvider {
1303    /// Returns the shields for this event timeline item.
1304    fn get_shields(&self, strict: bool) -> Option<ShieldState> {
1305        self.0.get_shield(strict).map(Into::into)
1306    }
1307
1308    /// Returns some debug information for this event timeline item.
1309    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    /// For local echoes, return the associated send handle; returns `None` for
1318    /// remote echoes.
1319    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}