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}