Skip to content

Commit

Permalink
Show reactions.
Browse files Browse the repository at this point in the history
  • Loading branch information
pkulak committed Apr 7, 2023
1 parent 7144a3b commit 1c320eb
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 13 deletions.
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ edition = "2021"
anyhow = { version = "1.0", features = ["backtrace"] }
crossterm = "0.26"
dirs = "5.0"
emojis = "0.5"
futures = "0.3.24"
log = "0.4"
once_cell = "1.17"
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
simple-logging = "2.0"
sorted-vec = "0.8"
tempfile = "3"
textwrap = "0.16"
tokio = { version = "1.24.2", features = ["rt-multi-thread"] }
Expand Down
136 changes: 123 additions & 13 deletions src/widgets/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ use crate::widgets::EventResult::Consumed;
use crate::widgets::{get_margin, EventResult};
use anyhow::bail;
use crossterm::event::{KeyCode, KeyEvent};
use log::info;
use matrix_sdk::room::{Joined, RoomMember};
use ruma::events::room::message::MessageType::Text;
use ruma::events::room::message::TextMessageEventContent;
use ruma::events::AnyMessageLikeEvent::Reaction as Rctn;
use ruma::events::AnyMessageLikeEvent::RoomMessage;
use ruma::events::AnyTimelineEvent;
use ruma::events::AnyTimelineEvent::MessageLike;
use ruma::events::MessageLikeEvent::Original;
use ruma::OwnedEventId;
use sorted_vec::SortedVec;
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::ops::Deref;
use tui::buffer::Buffer;
use tui::layout::{Constraint, Corner, Direction, Layout, Rect};
use tui::style::{Color, Style};
Expand All @@ -26,23 +31,70 @@ use tui::widgets::{List, ListItem, ListState, StatefulWidget, Widget};
pub struct Chat {
matrix: Matrix,
room: Option<Joined>,
events: Vec<AnyTimelineEvent>,
events: SortedVec<OrderedEvent>,
messages: Vec<Message>,
members: HashMap<String, String>,
list_state: Cell<ListState>,
}

#[allow(dead_code)]
// a good PR would be to add Ord to AnyTimelineEvent
pub struct OrderedEvent {
inner: AnyTimelineEvent,
}

impl OrderedEvent {
pub fn new(inner: AnyTimelineEvent) -> OrderedEvent {
OrderedEvent { inner }
}
}

impl Deref for OrderedEvent {
type Target = AnyTimelineEvent;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl Ord for OrderedEvent {
fn cmp(&self, other: &Self) -> Ordering {
self.origin_server_ts().cmp(&other.origin_server_ts())
}
}

impl PartialOrd for OrderedEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.origin_server_ts()
.partial_cmp(&other.origin_server_ts())
}
}

impl PartialEq for OrderedEvent {
fn eq(&self, other: &Self) -> bool {
self.event_id().eq(other.event_id())
}
}

impl Eq for OrderedEvent {}

pub struct Message {
id: OwnedEventId,
body: String,
sender: String,
reactions: Vec<Reaction>,
}

pub struct Reaction {
body: String,
sender: String,
}

impl Message {
// can we make a brand-new message, just from this event?
fn try_from(event: &AnyTimelineEvent) -> Option<Self> {
if let MessageLike(RoomMessage(Original(c))) = event.clone() {
if let MessageLike(RoomMessage(Original(c))) = event {
let c = c.clone();

let body = match c.content.msgtype {
Text(TextMessageEventContent { body, .. }) => body,
_ => return None,
Expand All @@ -52,20 +104,52 @@ impl Message {
id: c.event_id,
body,
sender: c.sender.to_string(),
reactions: Vec::new(),
});
}

None
}

fn merge_into_message_list(messages: &mut Vec<Message>, event: &AnyTimelineEvent) {
if let MessageLike(Rctn(Original(c))) = event {
let relates = c.content.relates_to.clone();

let sender = c.sender.to_string();
let body = relates.key;
let event_id = relates.event_id;

for message in messages.iter_mut() {
if message.id == event_id {
message.reactions.push(Reaction { body, sender });
return;
}
}
}
}

fn update_senders(&mut self, map: &HashMap<String, String>) {
if let Some(sender) = map.get(&self.sender) {
self.sender = sender.clone();
}

for reaction in self.reactions.iter_mut() {
if let Some(sender) = map.get(&reaction.sender) {
reaction.sender = sender.clone();
}
}
}

fn to_list_item(&self, width: usize) -> ListItem {
use tui::text::Text;

// author
let spans = vec![Span::styled(
self.sender.clone(),
Style::default().fg(Color::Green),
)];

// message
let mut lines = Text::from(Spans::from(spans));

let wrapped = textwrap::wrap(&self.body, width);
Expand All @@ -74,6 +158,23 @@ impl Message {
lines.extend(Text::from(l))
}

// reactions
for r in &self.reactions {
let line = if let Some(emoji) = emojis::get(&r.body) {
if let Some(shortcode) = emoji.shortcode() {
format!("{} ({})", emoji.as_str(), shortcode)
} else {
r.body.clone()
}
} else {
r.body.clone()
};

let line = format!("{} {}", line, r.sender);

lines.extend(Text::styled(line, Style::default().fg(Color::DarkGray)))
}

lines.extend(Text::from(" ".to_string()));

ListItem::new(lines)
Expand All @@ -85,7 +186,7 @@ impl Chat {
Self {
matrix,
room: None,
events: vec![],
events: SortedVec::new(),
messages: vec![],
members: HashMap::new(),
list_state: Cell::new(ListState::default()),
Expand Down Expand Up @@ -138,7 +239,7 @@ impl Chat {
return;
}

self.events.push(event.clone());
self.events.push(OrderedEvent::new(event.clone()));
self.messages = make_message_list(&self.events, &self.members);
self.reset();
}
Expand Down Expand Up @@ -186,23 +287,32 @@ impl Widget for ChatWidget<'_> {
}

fn make_message_list(
timeline: &Vec<AnyTimelineEvent>,
timeline: &SortedVec<OrderedEvent>,
members: &HashMap<String, String>,
) -> Vec<Message> {
let mut messages = vec![];
let mut modifiers = vec![];

for event in timeline {
if let Some(mut message) = Message::try_from(event) {
if members.contains_key(&message.sender) {
message.sender = members.get(&message.sender).unwrap().to_string();
}

// split everything into either a starting message, or something that
// modifies an existing message
for event in timeline.iter() {
if let Some(message) = Message::try_from(event) {
messages.push(message);
} else {
modifiers.push(event);
}
}

// now apply all the modifiers to the message list
for event in modifiers {
Message::merge_into_message_list(&mut messages, event)
}

// update senders to friendly names
messages.iter_mut().for_each(|m| m.update_senders(members));

// our message list is reversed because we start at the bottom of the
// window and move up, like any good chat list
// window and move up, like any good chat
messages.reverse();

messages
Expand Down

0 comments on commit 1c320eb

Please sign in to comment.