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