Skip to content

Commit d36b2a6

Browse files
authored
feat(ffi): introduce a ThreadSummary type within MsgLikeContent (#4933)
…that holds information on the thread the given item is the root of - it holds the latest event content and sender at the moment but will hold more information in the future e.g. number of replies, if it's unread etc. - the field is not currently being populate but is delivered earlier so it can power shipping the UI side on the embedders
1 parent ed232df commit d36b2a6

File tree

10 files changed

+160
-64
lines changed

10 files changed

+160
-64
lines changed

bindings/matrix-sdk-ffi/src/timeline/msg_like.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
use std::{collections::HashMap, sync::Arc};
1616

1717
use matrix_sdk::crypto::types::events::UtdCause;
18+
use matrix_sdk_ui::timeline::TimelineDetails;
1819
use ruma::events::{room::MediaSource as RumaMediaSource, EventContent};
1920

20-
use super::{content::Reaction, reply::InReplyToDetails};
21+
use super::{
22+
content::{Reaction, TimelineItemContent},
23+
reply::InReplyToDetails,
24+
};
2125
use crate::{
2226
error::ClientError,
2327
ruma::{ImageInfo, MediaSource, MediaSourceExt, Mentions, MessageType, PollKind},
24-
timeline::content::ReactionSenderData,
28+
timeline::{content::ReactionSenderData, ProfileDetails},
2529
utils::Timestamp,
2630
};
2731

@@ -56,10 +60,12 @@ pub enum MsgLikeKind {
5660
pub struct MsgLikeContent {
5761
pub kind: MsgLikeKind,
5862
pub reactions: Vec<Reaction>,
59-
/// Event ID of the thread root, if this is a threaded message.
60-
pub thread_root: Option<String>,
6163
/// The event this message is replying to, if any.
6264
pub in_reply_to: Option<Arc<InReplyToDetails>>,
65+
/// Event ID of the thread root, if this is a message in a thread.
66+
pub thread_root: Option<String>,
67+
/// Details about the thread this message is the root of.
68+
pub thread_summary: Option<Arc<ThreadSummary>>,
6369
}
6470

6571
#[derive(Clone, uniffi::Record)]
@@ -95,6 +101,8 @@ impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
95101

96102
let thread_root = value.thread_root.map(|id| id.to_string());
97103

104+
let thread_summary = value.thread_summary.map(|t| Arc::new(t.into()));
105+
98106
Ok(match value.kind {
99107
Kind::Message(message) => {
100108
let msg_type = TryInto::<MessageType>::try_into(message.msgtype().clone())
@@ -112,6 +120,7 @@ impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
112120
reactions,
113121
in_reply_to,
114122
thread_root,
123+
thread_summary,
115124
}
116125
}
117126
Kind::Sticker(sticker) => {
@@ -134,6 +143,7 @@ impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
134143
reactions,
135144
in_reply_to,
136145
thread_root,
146+
thread_summary,
137147
}
138148
}
139149
Kind::Poll(poll_state) => {
@@ -156,16 +166,22 @@ impl TryFrom<matrix_sdk_ui::timeline::MsgLikeContent> for MsgLikeContent {
156166
reactions,
157167
in_reply_to,
158168
thread_root,
169+
thread_summary,
159170
}
160171
}
161-
Kind::Redacted => {
162-
Self { kind: MsgLikeKind::Redacted, reactions, in_reply_to, thread_root }
163-
}
172+
Kind::Redacted => Self {
173+
kind: MsgLikeKind::Redacted,
174+
reactions,
175+
in_reply_to,
176+
thread_root,
177+
thread_summary,
178+
},
164179
Kind::UnableToDecrypt(msg) => Self {
165180
kind: MsgLikeKind::UnableToDecrypt { msg: EncryptedMessage::new(&msg) },
166181
reactions,
167182
in_reply_to,
168183
thread_root,
184+
thread_summary,
169185
},
170186
})
171187
}
@@ -222,3 +238,42 @@ pub struct PollAnswer {
222238
pub id: String,
223239
pub text: String,
224240
}
241+
242+
#[derive(Clone, uniffi::Object)]
243+
pub struct ThreadSummary {
244+
pub latest_event: ThreadSummaryLatestEventDetails,
245+
}
246+
247+
#[matrix_sdk_ffi_macros::export]
248+
impl ThreadSummary {
249+
pub fn latest_event(&self) -> ThreadSummaryLatestEventDetails {
250+
self.latest_event.clone()
251+
}
252+
}
253+
254+
#[derive(Clone, uniffi::Enum)]
255+
pub enum ThreadSummaryLatestEventDetails {
256+
Unavailable,
257+
Pending,
258+
Ready { sender: String, sender_profile: ProfileDetails, content: TimelineItemContent },
259+
Error { message: String },
260+
}
261+
262+
impl From<matrix_sdk_ui::timeline::ThreadSummary> for ThreadSummary {
263+
fn from(value: matrix_sdk_ui::timeline::ThreadSummary) -> Self {
264+
let latest_event = match value.latest_event {
265+
TimelineDetails::Unavailable => ThreadSummaryLatestEventDetails::Unavailable,
266+
TimelineDetails::Pending => ThreadSummaryLatestEventDetails::Pending,
267+
TimelineDetails::Ready(event) => ThreadSummaryLatestEventDetails::Ready {
268+
content: event.content.into(),
269+
sender: event.sender.to_string(),
270+
sender_profile: (&event.sender_profile).into(),
271+
},
272+
TimelineDetails::Error(message) => {
273+
ThreadSummaryLatestEventDetails::Error { message: message.to_string() }
274+
}
275+
};
276+
277+
Self { latest_event }
278+
}
279+
}

crates/matrix-sdk-ui/src/timeline/controller/decryption_retry_task.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ mod tests {
542542
ReactionsByKeyBySender::default(),
543543
None,
544544
None,
545+
None,
545546
),
546547
event_kind,
547548
true,

crates/matrix-sdk-ui/src/timeline/controller/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,16 +1026,14 @@ impl<P: RoomDataProvider, D: Decryptor> TimelineController<P, D> {
10261026
};
10271027

10281028
// Replace the local-related state (kind) and the content state.
1029-
let prev_reactions = prev_item.content().reactions();
1030-
let prev_thread_root = prev_item.content().thread_root();
1031-
let prev_in_reply_to = prev_item.content().in_reply_to();
10321029
let new_item = TimelineItem::new(
10331030
prev_item.with_kind(ti_kind).with_content(TimelineItemContent::message(
10341031
content,
10351032
None,
1036-
prev_reactions,
1037-
prev_thread_root,
1038-
prev_in_reply_to,
1033+
prev_item.content().reactions(),
1034+
prev_item.content().thread_root(),
1035+
prev_item.content().in_reply_to(),
1036+
prev_item.content().thread_summary(),
10391037
)),
10401038
prev_item.internal_id.to_owned(),
10411039
);
@@ -1388,6 +1386,7 @@ impl TimelineController {
13881386
reactions,
13891387
thread_root,
13901388
in_reply_to,
1389+
thread_summary,
13911390
}) = item.content().clone()
13921391
else {
13931392
info!("Event is no longer a message (redacted?)");
@@ -1408,6 +1407,7 @@ impl TimelineController {
14081407
reactions,
14091408
thread_root,
14101409
in_reply_to: Some(InReplyToDetails { event_id: in_reply_to.event_id, event }),
1410+
thread_summary,
14111411
}));
14121412
state.items.replace(index, TimelineItem::new(item, internal_id));
14131413

crates/matrix-sdk-ui/src/timeline/controller/observable_items.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ mod observable_items_tests {
408408
reactions: Default::default(),
409409
thread_root: None,
410410
in_reply_to: None,
411+
thread_summary: None,
411412
}),
412413
EventTimelineItemKind::Remote(RemoteEventTimelineItem {
413414
event_id: event_id.parse().unwrap(),

crates/matrix-sdk-ui/src/timeline/event_handler.rs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
418418
reactions: Default::default(),
419419
thread_root: None,
420420
in_reply_to: None,
421+
thread_summary: None,
421422
}),
422423
None,
423424
);
@@ -622,6 +623,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
622623
Default::default(),
623624
thread_root,
624625
in_reply_to_details,
626+
None,
625627
),
626628
edit_json,
627629
);
@@ -739,13 +741,15 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
739741
return None;
740742
}
741743

742-
let TimelineItemContent::MsgLike(MsgLikeContent {
743-
kind: MsgLikeKind::Message(msg),
744-
reactions,
745-
thread_root,
746-
in_reply_to,
747-
}) = item.content()
748-
else {
744+
let TimelineItemContent::MsgLike(content) = item.content() else {
745+
info!(
746+
"Edit of message event applies to {:?}, discarding",
747+
item.content().debug_string(),
748+
);
749+
return None;
750+
};
751+
752+
let MsgLikeContent { kind: MsgLikeKind::Message(msg), .. } = content else {
749753
info!(
750754
"Edit of message event applies to {:?}, discarding",
751755
item.content().debug_string(),
@@ -757,12 +761,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
757761
new_msg.apply_edit(new_content);
758762

759763
let mut new_item = item.with_content_and_latest_edit(
760-
TimelineItemContent::MsgLike(MsgLikeContent {
761-
kind: MsgLikeKind::Message(new_msg),
762-
reactions: reactions.clone(),
763-
thread_root: thread_root.clone(),
764-
in_reply_to: in_reply_to.clone(),
765-
}),
764+
TimelineItemContent::MsgLike(content.with_kind(MsgLikeKind::Message(new_msg))),
766765
edit_json,
767766
);
768767

@@ -852,24 +851,20 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
852851
return None;
853852
}
854853

855-
let TimelineItemContent::MsgLike(MsgLikeContent {
856-
kind: MsgLikeKind::Poll(poll_state),
857-
reactions,
858-
thread_root,
859-
in_reply_to,
860-
}) = &item.content()
861-
else {
854+
let TimelineItemContent::MsgLike(content) = &item.content() else {
855+
info!("Edit of poll event applies to {}, discarding", item.content().debug_string(),);
856+
return None;
857+
};
858+
859+
let MsgLikeContent { kind: MsgLikeKind::Poll(poll_state), .. } = content else {
862860
info!("Edit of poll event applies to {}, discarding", item.content().debug_string(),);
863861
return None;
864862
};
865863

866864
let new_content = match poll_state.edit(replacement.new_content) {
867-
Some(edited_poll_state) => TimelineItemContent::MsgLike(MsgLikeContent {
868-
kind: MsgLikeKind::Poll(edited_poll_state),
869-
reactions: reactions.clone(),
870-
thread_root: thread_root.clone(),
871-
in_reply_to: in_reply_to.clone(),
872-
}),
865+
Some(edited_poll_state) => TimelineItemContent::MsgLike(
866+
content.with_kind(MsgLikeKind::Poll(edited_poll_state)),
867+
),
873868
None => {
874869
info!("Not applying edit to a poll that's already ended");
875870
return None;
@@ -921,6 +916,7 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
921916
reactions: Default::default(),
922917
thread_root: None,
923918
in_reply_to: None,
919+
thread_summary: None,
924920
}),
925921
edit_json,
926922
);
@@ -1391,19 +1387,18 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
13911387
let Some(in_reply_to) = msglike.in_reply_to.as_ref() else { continue };
13921388

13931389
trace!(reply_event_id = ?event_item.identifier(), "Updating response to updated event");
1394-
let in_reply_to = Some(InReplyToDetails {
1390+
let in_reply_to = InReplyToDetails {
13951391
event_id: in_reply_to.event_id.clone(),
13961392
event: TimelineDetails::Ready(Box::new(RepliedToEvent::from_timeline_item(
13971393
new_item,
13981394
))),
1399-
});
1395+
};
14001396

1401-
let new_reply_content = TimelineItemContent::MsgLike(MsgLikeContent {
1402-
kind: MsgLikeKind::Message(message.clone()),
1403-
reactions: msglike.reactions.clone(),
1404-
thread_root: msglike.thread_root.clone(),
1405-
in_reply_to,
1406-
});
1397+
let new_reply_content = TimelineItemContent::MsgLike(
1398+
msglike
1399+
.with_in_reply_to(in_reply_to)
1400+
.with_kind(MsgLikeKind::Message(message.clone())),
1401+
);
14071402
let new_reply_item = item.with_kind(event_item.with_content(new_reply_content));
14081403
items.replace(timeline_item_index, new_reply_item);
14091404
}

crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub(in crate::timeline) use self::message::{
7474
};
7575
pub use self::{
7676
message::Message,
77-
msg_like::{MsgLikeContent, MsgLikeKind},
77+
msg_like::{MsgLikeContent, MsgLikeKind, ThreadSummary, ThreadSummaryLatestEvent},
7878
polls::{PollResult, PollState},
7979
reply::{InReplyToDetails, RepliedToEvent},
8080
};
@@ -198,6 +198,7 @@ impl TimelineItemContent {
198198
let reactions = Default::default();
199199
let thread_root = None;
200200
let in_reply_to = None;
201+
let thread_summary = None;
201202

202203
let msglike = MsgLikeContent {
203204
kind: MsgLikeKind::Message(Message::from_event(
@@ -208,6 +209,7 @@ impl TimelineItemContent {
208209
reactions,
209210
thread_root,
210211
in_reply_to,
212+
thread_summary,
211213
};
212214

213215
TimelineItemContent::MsgLike(msglike)
@@ -251,12 +253,14 @@ impl TimelineItemContent {
251253
let reactions = Default::default();
252254
let thread_root = None;
253255
let in_reply_to = None;
256+
let thread_summary = None;
254257

255258
let msglike = MsgLikeContent {
256259
kind: MsgLikeKind::Sticker(Sticker { content: event_content }),
257260
reactions,
258261
thread_root,
259262
in_reply_to,
263+
thread_summary,
260264
};
261265

262266
TimelineItemContent::MsgLike(msglike)
@@ -292,6 +296,7 @@ impl TimelineItemContent {
292296
let reactions = Default::default();
293297
let thread_root = None;
294298
let in_reply_to = None;
299+
let thread_summary = None;
295300

296301
let msglike = MsgLikeContent {
297302
kind: MsgLikeKind::Poll(PollState::new(
@@ -301,6 +306,7 @@ impl TimelineItemContent {
301306
reactions,
302307
thread_root,
303308
in_reply_to,
309+
thread_summary,
304310
};
305311

306312
TimelineItemContent::MsgLike(msglike)
@@ -408,6 +414,7 @@ impl TimelineItemContent {
408414
reactions: ReactionsByKeyBySender,
409415
thread_root: Option<OwnedEventId>,
410416
in_reply_to: Option<InReplyToDetails>,
417+
thread_summary: Option<ThreadSummary>,
411418
) -> Self {
412419
let remove_reply_fallback =
413420
if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
@@ -417,6 +424,7 @@ impl TimelineItemContent {
417424
reactions,
418425
thread_root,
419426
in_reply_to,
427+
thread_summary,
420428
})
421429
}
422430

@@ -511,7 +519,7 @@ impl TimelineItemContent {
511519
}
512520
}
513521

514-
/// Event ID of the thread root, if this is a threaded message.
522+
/// Event ID of the thread root, if this is a message in a thread.
515523
pub fn thread_root(&self) -> Option<OwnedEventId> {
516524
as_variant!(self, Self::MsgLike)?.thread_root.clone()
517525
}
@@ -540,6 +548,11 @@ impl TimelineItemContent {
540548
}
541549
}
542550

551+
/// Information about the thread this item is the root for.
552+
pub fn thread_summary(&self) -> Option<ThreadSummary> {
553+
as_variant!(self, Self::MsgLike)?.thread_summary.clone()
554+
}
555+
543556
/// Return a mutable handle to the reactions of this item.
544557
///
545558
/// See also [`Self::reactions()`] to explain the optional return type.

0 commit comments

Comments
 (0)