matrix_sdk_ui/timeline/event_item/content/
reply.rs

1// Copyright 2024 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::sync::Arc;
16
17use imbl::Vector;
18use matrix_sdk::deserialized_responses::TimelineEvent;
19use ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId};
20use tracing::{debug, instrument, warn};
21
22use super::TimelineItemContent;
23use crate::timeline::{
24    controller::TimelineMetadata,
25    event_handler::TimelineAction,
26    event_item::{EventTimelineItem, Profile, TimelineDetails},
27    traits::RoomDataProvider,
28    Error as TimelineError, TimelineEventItemId, TimelineItem,
29};
30
31/// Details about an event being replied to.
32#[derive(Clone, Debug)]
33pub struct InReplyToDetails {
34    /// The ID of the event.
35    pub event_id: OwnedEventId,
36
37    /// The details of the event.
38    ///
39    /// Use [`Timeline::fetch_details_for_event`] to fetch the data if it is
40    /// unavailable.
41    ///
42    /// [`Timeline::fetch_details_for_event`]: crate::Timeline::fetch_details_for_event
43    pub event: TimelineDetails<Box<EmbeddedEvent>>,
44}
45
46impl InReplyToDetails {
47    pub fn new(
48        event_id: OwnedEventId,
49        timeline_items: &Vector<Arc<TimelineItem>>,
50    ) -> InReplyToDetails {
51        let event = timeline_items
52            .iter()
53            .filter_map(|it| it.as_event())
54            .find(|it| it.event_id() == Some(&*event_id))
55            .map(|item| Box::new(EmbeddedEvent::from_timeline_item(item)));
56
57        InReplyToDetails { event_id, event: TimelineDetails::from_initial_value(event) }
58    }
59}
60
61/// An event that is embedded in another event, such as a replied-to event, or a
62/// thread latest event.
63#[derive(Clone, Debug)]
64pub struct EmbeddedEvent {
65    /// The content of the embedded item.
66    pub content: TimelineItemContent,
67    /// The user ID of the sender of the related embedded event.
68    pub sender: OwnedUserId,
69    /// The profile of the sender of the related embedded event.
70    pub sender_profile: TimelineDetails<Profile>,
71    /// The timestamp of the event.
72    pub timestamp: MilliSecondsSinceUnixEpoch,
73    /// The unique identifier of this event.
74    ///
75    /// This is the transaction ID for a local echo that has not been sent and
76    /// the event ID for a local echo that has been sent or a remote event.
77    pub identifier: TimelineEventItemId,
78}
79
80impl EmbeddedEvent {
81    /// Create a [`EmbeddedEvent`] from a loaded event timeline item.
82    pub fn from_timeline_item(timeline_item: &EventTimelineItem) -> Self {
83        Self {
84            content: timeline_item.content.clone(),
85            sender: timeline_item.sender.clone(),
86            sender_profile: timeline_item.sender_profile.clone(),
87            timestamp: timeline_item.timestamp,
88            identifier: timeline_item.identifier(),
89        }
90    }
91
92    #[instrument(skip_all)]
93    pub(in crate::timeline) async fn try_from_timeline_event<P: RoomDataProvider>(
94        timeline_event: TimelineEvent,
95        room_data_provider: &P,
96        meta: &TimelineMetadata,
97    ) -> Result<Option<Self>, TimelineError> {
98        let (raw_event, unable_to_decrypt_info) = match timeline_event.kind {
99            matrix_sdk::deserialized_responses::TimelineEventKind::UnableToDecrypt {
100                utd_info,
101                event,
102            } => (event, Some(utd_info)),
103            _ => (timeline_event.kind.into_raw(), None),
104        };
105
106        let event = match raw_event.deserialize() {
107            Ok(event) => event,
108            Err(err) => {
109                warn!("can't get details, event couldn't be deserialized: {err}");
110                return Err(TimelineError::UnsupportedEvent);
111            }
112        };
113
114        debug!(event_type = %event.event_type(), "got deserialized event");
115
116        // We don't need to fill relation information or process metadata for an
117        // embedded reply.
118        let in_reply_to = None;
119        let thread_root = None;
120        let thread_summary = None;
121
122        let sender = event.sender().to_owned();
123        let timestamp = event.origin_server_ts();
124        let identifier = TimelineEventItemId::EventId(event.event_id().to_owned());
125        let action = TimelineAction::from_event(
126            event,
127            &raw_event,
128            room_data_provider,
129            unable_to_decrypt_info,
130            meta,
131            in_reply_to,
132            thread_root,
133            thread_summary,
134        )
135        .await;
136
137        let Some(TimelineAction::AddItem { content }) = action else {
138            // The event can't be represented as a standalone timeline item.
139            return Ok(None);
140        };
141
142        let sender_profile = TimelineDetails::from_initial_value(
143            room_data_provider.profile_from_user_id(&sender).await,
144        );
145
146        Ok(Some(Self { content, sender, sender_profile, timestamp, identifier }))
147    }
148}