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 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        // 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(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 (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        // It's important that the initial items are passed *before* we forward the
218        // stream updates, with a guaranteed ordering. Otherwise, it could
219        // be that the listener be called before the initial items have been
220        // handled by the caller. See #3535 for details.
221
222        // First, pass all the items as a reset update.
223        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            // 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        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        // Send the current state even if it hasn't changed right away.
259        //
260        // Note: don't do it in the spawned function, so that the caller is immediately
261        // aware of the current state, and this doesn't depend on the async runtime
262        // having an available worker
263        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    /// Paginate backwards, whether we are in focused mode or in live mode.
273    ///
274    /// Returns whether we hit the start of the timeline or not.
275    pub async fn paginate_backwards(&self, num_events: u16) -> Result<bool, ClientError> {
276        Ok(self.inner.paginate_backwards(num_events).await?)
277    }
278
279    /// Paginate forwards, whether we are in focused mode or in live mode.
280    ///
281    /// Returns whether we hit the end of the timeline or not.
282    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    /// Mark the room as read by trying to attach an *unthreaded* read receipt
299    /// to the latest room event.
300    ///
301    /// This works even if the latest event belongs to a thread, as a threaded
302    /// reply also belongs to the unthreaded timeline. No threaded receipt
303    /// will be sent here (see also #3123).
304    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    /// Queues an event in the room's send queue so it's processed for
310    /// sending later.
311    ///
312    /// Returns an abort handle that allows to abort sending, if it hasn't
313    /// happened yet.
314    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    /// Send a reply.
466    ///
467    /// If the replied to event has a thread relation, it is forwarded on the
468    /// reply so that clients that support threads can render the reply
469    /// inside the thread.
470    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    /// Send a message on a thread.
484    ///
485    /// If the replied to event does not have a thread relation, it becomes the
486    /// root of a new thread.
487    ///
488    /// # Arguments
489    ///
490    /// * `msg` - Message content to send
491    ///
492    /// * `event_id` - ID of the event to reply to
493    ///
494    /// * `is_reply` - Whether the message is a reply on a thread
495    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    /// Edits an event from the timeline.
518    ///
519    /// If it was a local event, this will *try* to edit it, if it was not
520    /// being sent already. If the event was a remote event, then it will be
521    /// redacted by sending an edit request to the server.
522    ///
523    /// Returns whether the edit did happen. It can only return false for
524    /// local events that are being processed.
525    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                // If we couldn't edit, assume it was an (remote) event that wasn't in the
539                // timeline, and try to edit it via the room itself.
540                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        // Errors are logged in `Self::send` already.
582        let _ = self.send(Arc::new(room_message_event_content)).await;
583    }
584
585    /// Toggle a reaction on an event.
586    ///
587    /// Adds or redacts a reaction based on the state of the reaction at the
588    /// time it is called.
589    ///
590    /// This method works both on local echoes and remote items.
591    ///
592    /// When redacting a previous reaction, the redaction reason is not set.
593    ///
594    /// Ensures that only one reaction is sent at a time to avoid race
595    /// conditions and spamming the homeserver with requests.
596    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    /// Get the current timeline item for the given event ID, if any.
612    ///
613    /// Will return a remote event, *or* a local echo that has been sent but not
614    /// yet replaced by a remote echo.
615    ///
616    /// It's preferable to store the timeline items in the model for your UI, if
617    /// possible, instead of just storing IDs and coming back to the timeline
618    /// object to look up items.
619    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    /// Redacts an event from the timeline.
633    ///
634    /// Only works for events that exist as timeline items.
635    ///
636    /// If it was a local event, this will *try* to cancel it, if it was not
637    /// being sent already. If the event was a remote event, then it will be
638    /// redacted by sending a redaction request to the server.
639    ///
640    /// Will return an error if the event couldn't be redacted.
641    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    /// Load the reply details for the given event id.
650    ///
651    /// This will return an `InReplyToDetails` object that contains the details
652    /// which will either be ready or an error.
653    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    /// Adds a new pinned event by sending an updated `m.room.pinned_events`
684    /// event containing the new event id.
685    ///
686    /// Returns `true` if we sent the request, `false` if the event was already
687    /// pinned.
688    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    /// Adds a new pinned event by sending an updated `m.room.pinned_events`
694    /// event without the event id we want to remove.
695    ///
696    /// Returns `true` if we sent the request, `false` if the event wasn't
697    /// pinned
698    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/// A handle to perform actions onto a local echo.
713#[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    /// Try to abort the sending of the current event.
727    ///
728    /// If this returns `true`, then the sending could be aborted, because the
729    /// event hasn't been sent yet. Otherwise, if this returns `false`, the
730    /// event had already been sent and could not be aborted.
731    ///
732    /// This has an effect only on the first call; subsequent calls will always
733    /// return `false`.
734    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    /// Attempt to manually resend messages that failed to send due to issues
747    /// that should now have been fixed.
748    ///
749    /// This is useful for example, when there's a
750    /// `SessionRecipientCollectionError::VerifiedUserChangedIdentity` error;
751    /// the user may have re-verified on a different device and would now
752    /// like to send the failed message that's waiting on this device.
753    ///
754    /// # Arguments
755    ///
756    /// * `transaction_id` - The send queue transaction identifier of the local
757    ///   echo that should be unwedged.
758    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        // SAFETY: This is valid because Self is a repr(transparent) wrapper
948        //         around the other Timeline type.
949        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    /// An opaque unique identifier for this timeline item.
970    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/// This type represents the “send state” of a local event timeline item.
980#[derive(Clone, uniffi::Enum)]
981pub enum EventSendState {
982    /// The local event has not been sent yet.
983    NotSentYet,
984
985    /// The local event has been sent to the server, but unsuccessfully: The
986    /// sending has failed.
987    SendingFailed {
988        /// The error reason, with information for the user.
989        error: QueueWedgeError,
990
991        /// Whether the error is considered recoverable or not.
992        ///
993        /// An error that's recoverable will disable the room's send queue,
994        /// while an unrecoverable error will be parked, until the user
995        /// decides to cancel sending it.
996        is_recoverable: bool,
997    },
998
999    /// The local event has been sent successfully to the server.
1000    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/// Recommended decorations for decrypted messages, representing the message's
1022/// authenticity properties.
1023#[derive(uniffi::Enum, Clone)]
1024pub enum ShieldState {
1025    /// A red shield with a tooltip containing the associated message should be
1026    /// presented.
1027    Red { code: ShieldStateCode, message: String },
1028    /// A grey shield with a tooltip containing the associated message should be
1029    /// presented.
1030    Grey { code: ShieldStateCode, message: String },
1031    /// No shield should be presented.
1032    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    /// Indicates that an event is remote.
1052    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    /// Wait until the attachment has been sent.
1207    ///
1208    /// If the sending had been cancelled, will return immediately.
1209    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    /// Cancel the current sending task.
1226    ///
1227    /// A subsequent call to [`Self::join`] will return immediately.
1228    pub fn cancel(&self) {
1229        self.abort_hdl.abort();
1230    }
1231}
1232
1233/// A [`TimelineItem`](super::TimelineItem) that doesn't correspond to an event.
1234#[derive(uniffi::Enum)]
1235pub enum VirtualTimelineItem {
1236    /// A divider between messages of different day or month depending on
1237    /// timeline settings.
1238    DateDivider {
1239        /// A timestamp in milliseconds since Unix Epoch on that day in local
1240        /// time.
1241        ts: Timestamp,
1242    },
1243
1244    /// The user's own read marker.
1245    ReadMarker,
1246
1247    /// The timeline start, that is, the *oldest* event in time for that room.
1248    TimelineStart,
1249}
1250
1251/// A [`TimelineItem`](super::TimelineItem) that doesn't correspond to an event.
1252#[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/// Create a caption edit.
1311///
1312/// If no `formatted_caption` is provided, then it's assumed the `caption`
1313/// represents valid Markdown that can be used as the formatted caption.
1314#[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/// Wrapper to retrieve some timeline item info lazily.
1330#[derive(Clone, uniffi::Object)]
1331pub struct LazyTimelineItemProvider(Arc<matrix_sdk_ui::timeline::EventTimelineItem>);
1332
1333#[matrix_sdk_ffi_macros::export]
1334impl LazyTimelineItemProvider {
1335    /// Returns the shields for this event timeline item.
1336    fn get_shields(&self, strict: bool) -> Option<ShieldState> {
1337        self.0.get_shield(strict).map(Into::into)
1338    }
1339
1340    /// Returns some debug information for this event timeline item.
1341    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    /// For local echoes, return the associated send handle; returns `None` for
1350    /// remote echoes.
1351    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}