multiverse/widgets/room_view/
timeline.rs
1use std::sync::Arc;
2
3use imbl::Vector;
4use matrix_sdk::ruma::events::room::message::MessageType;
5use matrix_sdk_ui::timeline::{
6 MembershipChange, MsgLikeContent, MsgLikeKind, TimelineItem, TimelineItemContent,
7 TimelineItemKind, VirtualTimelineItem,
8};
9use ratatui::{prelude::*, widgets::*};
10
11use crate::{ALT_ROW_COLOR, NORMAL_ROW_COLOR, TEXT_COLOR};
12
13pub struct TimelineView<'a> {
14 items: &'a Vector<Arc<TimelineItem>>,
15}
16
17impl<'a> TimelineView<'a> {
18 pub fn new(items: &'a Vector<Arc<TimelineItem>>) -> Self {
19 Self { items }
20 }
21}
22
23impl Widget for &mut TimelineView<'_> {
24 fn render(self, area: Rect, buf: &mut Buffer)
25 where
26 Self: Sized,
27 {
28 let mut content = Vec::new();
29
30 for item in self.items.iter() {
31 match item.kind() {
32 TimelineItemKind::Event(ev) => {
33 let sender = ev.sender();
34
35 match ev.content() {
36 TimelineItemContent::MsgLike(MsgLikeContent {
37 kind: MsgLikeKind::Message(message),
38 ..
39 }) => {
40 if let MessageType::Text(text) = message.msgtype() {
41 content.push(format!("{}: {}", sender, text.body))
42 }
43 }
44
45 TimelineItemContent::MsgLike(MsgLikeContent {
46 kind: MsgLikeKind::Redacted,
47 ..
48 }) => content.push(format!("{}: -- redacted --", sender)),
49
50 TimelineItemContent::MsgLike(MsgLikeContent {
51 kind: MsgLikeKind::UnableToDecrypt(_),
52 ..
53 }) => content.push(format!("{}: (UTD)", sender)),
54
55 TimelineItemContent::MembershipChange(m) => {
56 if let Some(change) = m.change() {
57 let display_name =
58 m.display_name().unwrap_or_else(|| m.user_id().to_string());
59
60 let change = match change {
61 MembershipChange::Joined => "has joined the room",
62 MembershipChange::Left => "has left the room",
63 MembershipChange::Banned => "has been banned",
64 MembershipChange::Unbanned => "has been unbanned",
65 MembershipChange::Kicked => "has been kicked from the room",
66 MembershipChange::Invited => "has been invited to the room",
67 MembershipChange::KickedAndBanned => {
68 "has been kicked and banned from the room"
69 }
70 MembershipChange::InvitationAccepted => {
71 "has accepted the invitation to the room"
72 }
73 MembershipChange::InvitationRejected => {
74 "has rejected the invitation to the room"
75 }
76 MembershipChange::Knocked => "knocked on the room",
77 MembershipChange::KnockAccepted => {
78 "has accepted a knock on the room"
79 }
80 MembershipChange::KnockRetracted => {
81 "has retracted a knock on the room"
82 }
83 MembershipChange::KnockDenied => "has denied a knock",
84 MembershipChange::None
85 | MembershipChange::Error
86 | MembershipChange::InvitationRevoked
87 | MembershipChange::NotImplemented => {
88 "has changed it's membership status"
89 }
90 };
91
92 content.push(format!("{display_name} {change}"));
93 }
94 }
95
96 TimelineItemContent::MsgLike(MsgLikeContent {
97 kind: MsgLikeKind::Sticker(_),
98 ..
99 })
100 | TimelineItemContent::ProfileChange(_)
101 | TimelineItemContent::OtherState(_)
102 | TimelineItemContent::FailedToParseMessageLike { .. }
103 | TimelineItemContent::FailedToParseState { .. }
104 | TimelineItemContent::MsgLike(MsgLikeContent {
105 kind: MsgLikeKind::Poll(_),
106 ..
107 })
108 | TimelineItemContent::CallInvite
109 | TimelineItemContent::CallNotify => {
110 continue;
111 }
112 }
113 }
114
115 TimelineItemKind::Virtual(virt) => match virt {
116 VirtualTimelineItem::DateDivider(unix_ts) => {
117 content.push(format!("Date: {unix_ts:?}"));
118 }
119 VirtualTimelineItem::ReadMarker => {
120 content.push("Read marker".to_owned());
121 }
122 VirtualTimelineItem::TimelineStart => {
123 content.push("🥳 Timeline start! 🥳".to_owned());
124 }
125 },
126 }
127 }
128
129 let list_items = content
130 .into_iter()
131 .enumerate()
132 .map(|(i, line)| {
133 let bg_color = match i % 2 {
134 0 => NORMAL_ROW_COLOR,
135 _ => ALT_ROW_COLOR,
136 };
137 let line = Line::styled(line, TEXT_COLOR);
138 ListItem::new(line).bg(bg_color)
139 })
140 .collect::<Vec<_>>();
141
142 let list = List::new(list_items).highlight_spacing(HighlightSpacing::Always);
143
144 let mut dummy_list_state = ListState::default();
145
146 StatefulWidget::render(list, area, buf, &mut dummy_list_state);
147 }
148}