Skip to main content

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