Skip to content

Commit 1f9c339

Browse files
committed
refactor(multiverse): Split out the timeline item formatting logic
1 parent 005f002 commit 1f9c339

File tree

1 file changed

+132
-142
lines changed

1 file changed

+132
-142
lines changed
Lines changed: 132 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::sync::Arc;
22

33
use imbl::Vector;
4-
use matrix_sdk::ruma::events::room::message::MessageType;
4+
use matrix_sdk::ruma::{events::room::message::MessageType, UserId};
55
use matrix_sdk_ui::timeline::{
6-
MembershipChange, MsgLikeContent, MsgLikeKind, TimelineDetails, TimelineItem,
7-
TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
6+
MembershipChange, Message, MsgLikeContent, MsgLikeKind, RoomMembershipChange, ThreadSummary,
7+
TimelineDetails, TimelineItem, TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
88
};
99
use ratatui::{prelude::*, widgets::*};
1010

@@ -27,147 +27,10 @@ impl StatefulWidget for &mut TimelineView<'_> {
2727
where
2828
Self: Sized,
2929
{
30-
let mut content: Vec<ListItem<'_>> = Vec::new();
31-
32-
for item in self.items.iter() {
33-
match item.kind() {
34-
TimelineItemKind::Event(ev) => {
35-
let sender = ev.sender();
36-
37-
match ev.content() {
38-
TimelineItemContent::MsgLike(MsgLikeContent {
39-
kind: MsgLikeKind::Message(message),
40-
..
41-
}) => {
42-
if ev.content().thread_root().is_some() {
43-
continue;
44-
}
45-
46-
if let MessageType::Text(text) = message.msgtype() {
47-
let mut lines = Vec::new();
48-
let first_line = Line::from(format!("{}: {}", sender, text.body));
49-
50-
lines.push(first_line);
51-
52-
if let Some(thread_summary) = ev.content().thread_summary() {
53-
match thread_summary.latest_event {
54-
TimelineDetails::Unavailable => {}
55-
TimelineDetails::Pending => {}
56-
TimelineDetails::Ready(e) => {
57-
let sender = e.sender;
58-
let content =
59-
e.content.as_message().map(|m| m.msgtype());
60-
61-
if let Some(MessageType::Text(text)) = content {
62-
let replies = if thread_summary.num_replies == 1 {
63-
"1 reply".to_owned()
64-
} else {
65-
format!("{} replies", {
66-
thread_summary.num_replies
67-
})
68-
};
69-
let thread_line = Line::from(format!(
70-
" 💬 {replies} {sender}: {}",
71-
text.body
72-
));
73-
74-
lines.push(thread_line);
75-
}
76-
}
77-
TimelineDetails::Error(_) => {}
78-
}
79-
}
80-
81-
content.push(ListItem::from(lines));
82-
}
83-
}
84-
85-
TimelineItemContent::MsgLike(MsgLikeContent {
86-
kind: MsgLikeKind::Redacted,
87-
..
88-
}) => content.push(format!("{sender}: -- redacted --").into()),
89-
90-
TimelineItemContent::MsgLike(MsgLikeContent {
91-
kind: MsgLikeKind::UnableToDecrypt(_),
92-
..
93-
}) => content.push(format!("{sender}: (UTD)").into()),
94-
95-
TimelineItemContent::MembershipChange(m) => {
96-
if let Some(change) = m.change() {
97-
let display_name =
98-
m.display_name().unwrap_or_else(|| m.user_id().to_string());
99-
100-
let change = match change {
101-
MembershipChange::Joined => "has joined the room",
102-
MembershipChange::Left => "has left the room",
103-
MembershipChange::Banned => "has been banned",
104-
MembershipChange::Unbanned => "has been unbanned",
105-
MembershipChange::Kicked => "has been kicked from the room",
106-
MembershipChange::Invited => "has been invited to the room",
107-
MembershipChange::KickedAndBanned => {
108-
"has been kicked and banned from the room"
109-
}
110-
MembershipChange::InvitationAccepted => {
111-
"has accepted the invitation to the room"
112-
}
113-
MembershipChange::InvitationRejected => {
114-
"has rejected the invitation to the room"
115-
}
116-
MembershipChange::Knocked => "knocked on the room",
117-
MembershipChange::KnockAccepted => {
118-
"has accepted a knock on the room"
119-
}
120-
MembershipChange::KnockRetracted => {
121-
"has retracted a knock on the room"
122-
}
123-
MembershipChange::KnockDenied => "has denied a knock",
124-
MembershipChange::None
125-
| MembershipChange::Error
126-
| MembershipChange::InvitationRevoked
127-
| MembershipChange::NotImplemented => {
128-
"has changed it's membership status"
129-
}
130-
};
131-
132-
content.push(format!("{display_name} {change}").into());
133-
}
134-
}
135-
136-
TimelineItemContent::MsgLike(MsgLikeContent {
137-
kind: MsgLikeKind::Sticker(_),
138-
..
139-
})
140-
| TimelineItemContent::ProfileChange(_)
141-
| TimelineItemContent::OtherState(_)
142-
| TimelineItemContent::FailedToParseMessageLike { .. }
143-
| TimelineItemContent::FailedToParseState { .. }
144-
| TimelineItemContent::MsgLike(MsgLikeContent {
145-
kind: MsgLikeKind::Poll(_),
146-
..
147-
})
148-
| TimelineItemContent::CallInvite
149-
| TimelineItemContent::CallNotify => {
150-
continue;
151-
}
152-
}
153-
}
154-
155-
TimelineItemKind::Virtual(virt) => match virt {
156-
VirtualTimelineItem::DateDivider(unix_ts) => {
157-
content.push(format!("Date: {unix_ts:?}").into());
158-
}
159-
VirtualTimelineItem::ReadMarker => {
160-
content.push("Read marker".to_owned().into());
161-
}
162-
VirtualTimelineItem::TimelineStart => {
163-
content.push("🥳 Timeline start! 🥳".to_owned().into());
164-
}
165-
},
166-
}
167-
}
30+
let content = self.items.iter().map(format_timeline_item);
16831

16932
let list_items = content
170-
.into_iter()
33+
.flatten()
17134
.enumerate()
17235
.map(|(i, line)| {
17336
let bg_color = match i % 2 {
@@ -187,3 +50,130 @@ impl StatefulWidget for &mut TimelineView<'_> {
18750
StatefulWidget::render(list, area, buf, state);
18851
}
18952
}
53+
54+
fn format_timeline_item(item: &Arc<TimelineItem>) -> Option<ListItem<'_>> {
55+
let item = match item.kind() {
56+
TimelineItemKind::Event(ev) => {
57+
if ev.content().thread_root().is_some() {
58+
return None;
59+
}
60+
61+
let sender = ev.sender();
62+
63+
match ev.content() {
64+
TimelineItemContent::MsgLike(MsgLikeContent {
65+
kind: MsgLikeKind::Message(message),
66+
..
67+
}) => format_text_message(sender, message, ev.content().thread_summary())?,
68+
69+
TimelineItemContent::MsgLike(MsgLikeContent {
70+
kind: MsgLikeKind::Redacted,
71+
..
72+
}) => format!("{sender}: -- redacted --").into(),
73+
74+
TimelineItemContent::MsgLike(MsgLikeContent {
75+
kind: MsgLikeKind::UnableToDecrypt(_),
76+
..
77+
}) => format!("{sender}: (UTD)").into(),
78+
79+
TimelineItemContent::MembershipChange(m) => format_membership_change(m)?,
80+
81+
TimelineItemContent::MsgLike(MsgLikeContent {
82+
kind: MsgLikeKind::Sticker(_),
83+
..
84+
})
85+
| TimelineItemContent::ProfileChange(_)
86+
| TimelineItemContent::OtherState(_)
87+
| TimelineItemContent::FailedToParseMessageLike { .. }
88+
| TimelineItemContent::FailedToParseState { .. }
89+
| TimelineItemContent::MsgLike(MsgLikeContent {
90+
kind: MsgLikeKind::Poll(_), ..
91+
})
92+
| TimelineItemContent::CallInvite
93+
| TimelineItemContent::CallNotify => {
94+
return None;
95+
}
96+
}
97+
}
98+
99+
TimelineItemKind::Virtual(virt) => match virt {
100+
VirtualTimelineItem::DateDivider(unix_ts) => format!("Date: {unix_ts:?}").into(),
101+
VirtualTimelineItem::ReadMarker => "Read marker".to_owned().into(),
102+
VirtualTimelineItem::TimelineStart => "🥳 Timeline start! 🥳".to_owned().into(),
103+
},
104+
};
105+
106+
Some(item)
107+
}
108+
109+
fn format_text_message(
110+
sender: &UserId,
111+
message: &Message,
112+
thread_summary: Option<ThreadSummary>,
113+
) -> Option<ListItem<'static>> {
114+
if let MessageType::Text(text) = message.msgtype() {
115+
let mut lines = Vec::new();
116+
let first_line = Line::from(format!("{}: {}", sender, text.body));
117+
118+
lines.push(first_line);
119+
120+
if let Some(thread_summary) = thread_summary {
121+
match thread_summary.latest_event {
122+
TimelineDetails::Unavailable => {}
123+
TimelineDetails::Pending => {}
124+
TimelineDetails::Ready(e) => {
125+
let sender = e.sender;
126+
let content = e.content.as_message().map(|m| m.msgtype());
127+
128+
if let Some(MessageType::Text(text)) = content {
129+
let replies = if thread_summary.num_replies == 1 {
130+
"1 reply".to_owned()
131+
} else {
132+
format!("{} replies", { thread_summary.num_replies })
133+
};
134+
let thread_line =
135+
Line::from(format!(" 💬 {replies} {sender}: {}", text.body));
136+
137+
lines.push(thread_line);
138+
}
139+
}
140+
TimelineDetails::Error(_) => {}
141+
}
142+
}
143+
144+
Some(ListItem::from(lines))
145+
} else {
146+
None
147+
}
148+
}
149+
150+
fn format_membership_change(membership: &RoomMembershipChange) -> Option<ListItem<'static>> {
151+
if let Some(change) = membership.change() {
152+
let display_name =
153+
membership.display_name().unwrap_or_else(|| membership.user_id().to_string());
154+
155+
let change = match change {
156+
MembershipChange::Joined => "has joined the room",
157+
MembershipChange::Left => "has left the room",
158+
MembershipChange::Banned => "has been banned",
159+
MembershipChange::Unbanned => "has been unbanned",
160+
MembershipChange::Kicked => "has been kicked from the room",
161+
MembershipChange::Invited => "has been invited to the room",
162+
MembershipChange::KickedAndBanned => "has been kicked and banned from the room",
163+
MembershipChange::InvitationAccepted => "has accepted the invitation to the room",
164+
MembershipChange::InvitationRejected => "has rejected the invitation to the room",
165+
MembershipChange::Knocked => "knocked on the room",
166+
MembershipChange::KnockAccepted => "has accepted a knock on the room",
167+
MembershipChange::KnockRetracted => "has retracted a knock on the room",
168+
MembershipChange::KnockDenied => "has denied a knock",
169+
MembershipChange::None
170+
| MembershipChange::Error
171+
| MembershipChange::InvitationRevoked
172+
| MembershipChange::NotImplemented => "has changed it's membership status",
173+
};
174+
175+
Some(format!("{display_name} {change}").into())
176+
} else {
177+
None
178+
}
179+
}

0 commit comments

Comments
 (0)