1use matrix_sdk::{Client, Room, latest_events::LocalLatestEventValue};
16use matrix_sdk_base::latest_event::LatestEventValue as BaseLatestEventValue;
17use ruma::{MilliSecondsSinceUnixEpoch, OwnedUserId};
18use tracing::trace;
19
20use crate::timeline::{
21 Profile, TimelineDetails, TimelineItemContent, event_handler::TimelineAction,
22 traits::RoomDataProvider,
23};
24
25#[derive(Debug)]
28pub enum LatestEventValue {
29 None,
31
32 Remote {
34 timestamp: MilliSecondsSinceUnixEpoch,
36
37 sender: OwnedUserId,
39
40 is_own: bool,
42
43 profile: TimelineDetails<Profile>,
45
46 content: TimelineItemContent,
48 },
49
50 Local {
54 timestamp: MilliSecondsSinceUnixEpoch,
56
57 sender: OwnedUserId,
59
60 profile: TimelineDetails<Profile>,
62
63 content: TimelineItemContent,
65
66 is_sending: bool,
69 },
70}
71
72impl LatestEventValue {
73 pub(crate) async fn from_base_latest_event_value(
74 value: BaseLatestEventValue,
75 room: &Room,
76 client: &Client,
77 ) -> Self {
78 match value {
79 BaseLatestEventValue::None => Self::None,
80 BaseLatestEventValue::Remote(timeline_event) => {
81 let raw_any_sync_timeline_event = timeline_event.into_raw();
82 let Ok(any_sync_timeline_event) = raw_any_sync_timeline_event.deserialize() else {
83 return Self::None;
84 };
85
86 let timestamp = any_sync_timeline_event.origin_server_ts();
87 let sender = any_sync_timeline_event.sender().to_owned();
88 let is_own = client.user_id().map(|user_id| user_id == sender).unwrap_or(false);
89 let profile = room
90 .profile_from_user_id(&sender)
91 .await
92 .map(TimelineDetails::Ready)
93 .unwrap_or(TimelineDetails::Unavailable);
94
95 let Some(TimelineAction::AddItem { content }) = TimelineAction::from_event(
96 any_sync_timeline_event,
97 &raw_any_sync_timeline_event,
98 room,
99 None,
100 None,
101 None,
102 None,
103 )
104 .await
105 else {
106 return Self::None;
107 };
108
109 Self::Remote { timestamp, sender, is_own, profile, content }
110 }
111 BaseLatestEventValue::LocalIsSending(LocalLatestEventValue {
112 timestamp,
113 content: ref serialized_content,
114 })
115 | BaseLatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
116 timestamp,
117 content: ref serialized_content,
118 }) => {
119 let Ok(message_like_event_content) = serialized_content.deserialize() else {
120 return Self::None;
121 };
122
123 let sender =
124 client.user_id().expect("The `Client` is supposed to be logged").to_owned();
125 let profile = room
126 .profile_from_user_id(&sender)
127 .await
128 .map(TimelineDetails::Ready)
129 .unwrap_or(TimelineDetails::Unavailable);
130 let is_sending = matches!(value, BaseLatestEventValue::LocalIsSending(_));
131
132 match TimelineAction::from_content(message_like_event_content, None, None, None) {
133 TimelineAction::AddItem { content } => {
134 Self::Local { timestamp, sender, profile, content, is_sending }
135 }
136
137 TimelineAction::HandleAggregation { kind, .. } => {
138 trace!("latest event is an aggregation: {}", kind.debug_string());
141 Self::None
142 }
143 }
144 }
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use std::ops::Not;
152
153 use assert_matches::assert_matches;
154 use matrix_sdk::{
155 latest_events::{LocalLatestEventValue, RemoteLatestEventValue},
156 store::SerializableEventContent,
157 test_utils::mocks::MatrixMockServer,
158 };
159 use matrix_sdk_test::{JoinedRoomBuilder, async_test};
160 use ruma::{
161 MilliSecondsSinceUnixEpoch,
162 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
163 room_id,
164 serde::Raw,
165 uint, user_id,
166 };
167 use serde_json::json;
168
169 use super::{
170 super::{MsgLikeContent, MsgLikeKind, TimelineItemContent},
171 BaseLatestEventValue, LatestEventValue, TimelineDetails,
172 };
173
174 #[async_test]
175 async fn test_none() {
176 let server = MatrixMockServer::new().await;
177 let client = server.client_builder().build().await;
178 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
179
180 let base_value = BaseLatestEventValue::None;
181 let value =
182 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
183
184 assert_matches!(value, LatestEventValue::None);
185 }
186
187 #[async_test]
188 async fn test_remote() {
189 let server = MatrixMockServer::new().await;
190 let client = server.client_builder().build().await;
191 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
192 let sender = user_id!("@mnt_io:matrix.org");
193
194 let base_value = BaseLatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
195 Raw::from_json_string(
196 json!({
197 "content": RoomMessageEventContent::text_plain("raclette"),
198 "type": "m.room.message",
199 "event_id": "$ev0",
200 "room_id": "!r0",
201 "origin_server_ts": 42,
202 "sender": sender,
203 })
204 .to_string(),
205 )
206 .unwrap(),
207 ));
208 let value =
209 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
210
211 assert_matches!(value, LatestEventValue::Remote { timestamp, sender: received_sender, is_own, profile, content } => {
212 assert_eq!(u64::from(timestamp.get()), 42u64);
213 assert_eq!(received_sender, sender);
214 assert!(is_own.not());
215 assert_matches!(profile, TimelineDetails::Unavailable);
216 assert_matches!(
217 content,
218 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
219 );
220 })
221 }
222
223 #[async_test]
224 async fn test_local_is_sending() {
225 let server = MatrixMockServer::new().await;
226 let client = server.client_builder().build().await;
227 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
228
229 let base_value = BaseLatestEventValue::LocalIsSending(LocalLatestEventValue {
230 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
231 content: SerializableEventContent::from_raw(
232 Raw::new(&AnyMessageLikeEventContent::RoomMessage(
233 RoomMessageEventContent::text_plain("raclette"),
234 ))
235 .unwrap(),
236 "m.room.message".to_owned(),
237 ),
238 });
239 let value =
240 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
241
242 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, is_sending } => {
243 assert_eq!(u64::from(timestamp.get()), 42u64);
244 assert_eq!(sender, "@example:localhost");
245 assert_matches!(profile, TimelineDetails::Unavailable);
246 assert_matches!(
247 content,
248 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
249 );
250 assert!(is_sending);
251 })
252 }
253
254 #[async_test]
255 async fn test_local_cannot_be_sent() {
256 let server = MatrixMockServer::new().await;
257 let client = server.client_builder().build().await;
258 let room = server.sync_room(&client, JoinedRoomBuilder::new(room_id!("!r0"))).await;
259
260 let base_value = BaseLatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
261 timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
262 content: SerializableEventContent::from_raw(
263 Raw::new(&AnyMessageLikeEventContent::RoomMessage(
264 RoomMessageEventContent::text_plain("raclette"),
265 ))
266 .unwrap(),
267 "m.room.message".to_owned(),
268 ),
269 });
270 let value =
271 LatestEventValue::from_base_latest_event_value(base_value, &room, &client).await;
272
273 assert_matches!(value, LatestEventValue::Local { timestamp, sender, profile, content, is_sending } => {
274 assert_eq!(u64::from(timestamp.get()), 42u64);
275 assert_eq!(sender, "@example:localhost");
276 assert_matches!(profile, TimelineDetails::Unavailable);
277 assert_matches!(
278 content,
279 TimelineItemContent::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. })
280 );
281 assert!(is_sending.not());
282 })
283 }
284}