matrix_sdk_ui/timeline/controller/
state.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::sync::Arc;
16
17use eyeball_im::VectorDiff;
18use matrix_sdk::{deserialized_responses::TimelineEvent, send_queue::SendHandle};
19#[cfg(test)]
20use ruma::events::receipt::ReceiptEventContent;
21use ruma::{
22    MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
23    events::{AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent},
24    room_version_rules::RoomVersionRules,
25    serde::Raw,
26};
27use tracing::{instrument, trace, warn};
28
29use super::{
30    super::{
31        Profile,
32        date_dividers::DateDividerAdjuster,
33        event_handler::{Flow, TimelineAction, TimelineEventContext, TimelineEventHandler},
34        event_item::RemoteEventOrigin,
35        traits::RoomDataProvider,
36    },
37    DateDividerMode, TimelineMetadata, TimelineSettings, TimelineStateTransaction,
38    observable_items::ObservableItems,
39};
40use crate::{timeline::controller::TimelineFocusKind, unable_to_decrypt_hook::UtdHookManager};
41
42#[derive(Debug)]
43pub(in crate::timeline) struct TimelineState<P: RoomDataProvider> {
44    pub items: ObservableItems,
45    pub meta: TimelineMetadata,
46
47    /// The kind of focus of this timeline.
48    pub(super) focus: Arc<TimelineFocusKind<P>>,
49}
50
51impl<P: RoomDataProvider> TimelineState<P> {
52    pub(super) fn new(
53        focus: Arc<TimelineFocusKind<P>>,
54        own_user_id: OwnedUserId,
55        room_version_rules: RoomVersionRules,
56        internal_id_prefix: Option<String>,
57        unable_to_decrypt_hook: Option<Arc<UtdHookManager>>,
58        is_room_encrypted: bool,
59    ) -> Self {
60        Self {
61            items: ObservableItems::new(),
62            meta: TimelineMetadata::new(
63                own_user_id,
64                room_version_rules,
65                internal_id_prefix,
66                unable_to_decrypt_hook,
67                is_room_encrypted,
68            ),
69            focus,
70        }
71    }
72
73    /// Handle updates on events as [`VectorDiff`]s.
74    pub(super) async fn handle_remote_events_with_diffs(
75        &mut self,
76        diffs: Vec<VectorDiff<TimelineEvent>>,
77        origin: RemoteEventOrigin,
78        room_data: &P,
79        settings: &TimelineSettings,
80    ) {
81        if diffs.is_empty() {
82            return;
83        }
84
85        let mut transaction = self.transaction();
86        transaction.handle_remote_events_with_diffs(diffs, origin, room_data, settings).await;
87        transaction.commit();
88    }
89
90    /// Handle remote aggregations on events as [`VectorDiff`]s.
91    pub(super) async fn handle_remote_aggregations(
92        &mut self,
93        diffs: Vec<VectorDiff<TimelineEvent>>,
94        origin: RemoteEventOrigin,
95        room_data: &P,
96        settings: &TimelineSettings,
97    ) {
98        if diffs.is_empty() {
99            return;
100        }
101
102        let mut transaction = self.transaction();
103        transaction.handle_remote_aggregations(diffs, origin, room_data, settings).await;
104        transaction.commit();
105    }
106
107    /// Marks the given event as fully read, using the read marker received from
108    /// sync.
109    pub(super) fn handle_fully_read_marker(&mut self, fully_read_event_id: OwnedEventId) {
110        let mut txn = self.transaction();
111        txn.set_fully_read_event(fully_read_event_id);
112        txn.commit();
113    }
114
115    #[instrument(skip_all)]
116    pub(super) async fn handle_ephemeral_events(
117        &mut self,
118        events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
119        room_data_provider: &P,
120    ) {
121        if events.is_empty() {
122            return;
123        }
124
125        let mut txn = self.transaction();
126
127        trace!("Handling ephemeral room events");
128        let own_user_id = room_data_provider.own_user_id();
129        for raw_event in events {
130            match raw_event.deserialize() {
131                Ok(AnySyncEphemeralRoomEvent::Receipt(ev)) => {
132                    txn.handle_explicit_read_receipts(ev.content, own_user_id);
133                }
134                Ok(_) => {}
135                Err(e) => {
136                    let event_type = raw_event.get_field::<String>("type").ok().flatten();
137                    warn!(event_type, "Failed to deserialize ephemeral event: {e}");
138                }
139            }
140        }
141
142        txn.commit();
143    }
144
145    /// Adds a local echo (for an event) to the timeline.
146    #[allow(clippy::too_many_arguments)]
147    #[instrument(skip_all)]
148    pub(super) async fn handle_local_event(
149        &mut self,
150        own_user_id: OwnedUserId,
151        own_profile: Option<Profile>,
152        date_divider_mode: DateDividerMode,
153        txn_id: OwnedTransactionId,
154        send_handle: Option<SendHandle>,
155        content: AnyMessageLikeEventContent,
156    ) {
157        let mut txn = self.transaction();
158
159        let mut date_divider_adjuster = DateDividerAdjuster::new(date_divider_mode);
160
161        let is_thread_focus = txn.focus.is_thread();
162        let (in_reply_to, thread_root) =
163            txn.meta.process_content_relations(&content, None, &txn.items, is_thread_focus);
164
165        // TODO merge with other should_add, one way or another?
166        let should_add_new_items = match &txn.focus {
167            TimelineFocusKind::Live { hide_threaded_events } => {
168                thread_root.is_none() || !hide_threaded_events
169            }
170            TimelineFocusKind::Thread { root_event_id, .. } => {
171                thread_root.as_ref().is_some_and(|r| r == root_event_id)
172            }
173            TimelineFocusKind::Event { .. } | TimelineFocusKind::PinnedEvents { .. } => {
174                // Don't add new items to these timelines; aggregations are added independently
175                // of the `should_add_new_items` value.
176                false
177            }
178        };
179
180        let ctx = TimelineEventContext {
181            sender: own_user_id,
182            sender_profile: own_profile,
183            timestamp: MilliSecondsSinceUnixEpoch::now(),
184            read_receipts: Default::default(),
185            // An event sent by ourselves is never matched against push rules.
186            is_highlighted: false,
187            flow: Flow::Local { txn_id, send_handle },
188            should_add_new_items,
189        };
190
191        let timeline_action = TimelineAction::from_content(content, in_reply_to, thread_root, None);
192        TimelineEventHandler::new(&mut txn, ctx)
193            .handle_event(&mut date_divider_adjuster, timeline_action)
194            .await;
195        txn.adjust_date_dividers(date_divider_adjuster);
196
197        txn.commit();
198    }
199
200    #[cfg(test)]
201    pub(super) fn handle_read_receipts(
202        &mut self,
203        receipt_event_content: ReceiptEventContent,
204        own_user_id: &ruma::UserId,
205    ) {
206        let mut txn = self.transaction();
207        txn.handle_explicit_read_receipts(receipt_event_content, own_user_id);
208        txn.commit();
209    }
210
211    pub(super) fn clear(&mut self) {
212        let mut txn = self.transaction();
213        txn.clear();
214        txn.commit();
215    }
216
217    /// Replaces the existing events in the timeline with the given remote ones.
218    ///
219    /// Note: when the `position` is [`TimelineEnd::Front`], prepended events
220    /// should be ordered in *reverse* topological order, that is, `events[0]`
221    /// is the most recent.
222    pub(super) async fn replace_with_remote_events<Events>(
223        &mut self,
224        events: Events,
225        origin: RemoteEventOrigin,
226        room_data_provider: &P,
227        settings: &TimelineSettings,
228    ) where
229        Events: IntoIterator,
230        Events::Item: Into<TimelineEvent>,
231    {
232        let mut txn = self.transaction();
233        txn.clear();
234        txn.handle_remote_events_with_diffs(
235            vec![VectorDiff::Append { values: events.into_iter().map(Into::into).collect() }],
236            origin,
237            room_data_provider,
238            settings,
239        )
240        .await;
241        txn.commit();
242    }
243
244    pub(super) fn mark_all_events_as_encrypted(&mut self) {
245        // When this transaction finishes, all items in the timeline will be emitted
246        // again with the updated encryption value.
247        let mut txn = self.transaction();
248        txn.mark_all_events_as_encrypted();
249        txn.commit();
250    }
251
252    pub(super) fn transaction(&mut self) -> TimelineStateTransaction<'_, P> {
253        TimelineStateTransaction::new(&mut self.items, &mut self.meta, &*self.focus)
254    }
255}