Skip to content

Commit

Permalink
Format the chat window a bit.
Browse files Browse the repository at this point in the history
  • Loading branch information
pkulak committed Apr 1, 2023
1 parent 95a247b commit e14ebeb
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 43 deletions.
42 changes: 42 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ once_cell = "1.17"
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
simple-logging = "2.0"
textwrap = "0.16"
tokio = { version = "1.24.2", features = ["rt-multi-thread"] }
tui = "0.19.0"

Expand Down
4 changes: 0 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@ impl App {
}

// send out the ticks
if let Some(c) = self.chat.as_mut() {
c.tick()
}

if let Some(p) = self.progress.as_mut() {
p.tick(self.timestamp)
}
Expand Down
18 changes: 18 additions & 0 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ use crate::widgets::signin::Signin;
use crate::widgets::Action::{ButtonNo, ButtonYes, SelectRoom};
use crate::widgets::EventResult::Consumed;
use crossterm::event::{KeyCode, KeyEvent};
use matrix_sdk::deserialized_responses::TimelineEvent;
use matrix_sdk::encryption::verification::{Emoji, SasVerification};
use matrix_sdk::room::RoomMember;

pub enum MatuiEvent {
Error(String),
LoginComplete,
LoginRequired,
LoginStarted,
Member(RoomMember),
SyncComplete,
SyncStarted(SyncType),
Timeline(TimelineEvent),
VerificationStarted(SasVerification, [Emoji; 7]),
VerificationCompleted,
}
Expand All @@ -44,6 +48,13 @@ pub fn handle_app_event(event: MatuiEvent, app: &mut App) {
app.error = None;
app.progress = None;
}

// Let the chat update when we learn about new usernames
MatuiEvent::Member(rm) => {
if let Some(c) = &mut app.chat {
c.room_member_event(rm);
}
}
MatuiEvent::SyncStarted(st) => {
app.error = None;
match st {
Expand All @@ -59,6 +70,13 @@ pub fn handle_app_event(event: MatuiEvent, app: &mut App) {
// now we can sync forever
app.matrix.sync();
}

// Let the chat update to new timeline events
MatuiEvent::Timeline(event) => {
if let Some(c) = &mut app.chat {
c.timeline_event(event);
}
}
MatuiEvent::VerificationStarted(sas, emoji) => {
app.sas = Some(sas);
app.confirm = Some(Confirm::new(
Expand Down
48 changes: 38 additions & 10 deletions src/matrix/matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use crate::handler::{MatuiEvent, SyncType};
use crate::matrix::roomcache::{DecoratedRoom, RoomCache};
use anyhow::{bail, Context};
use futures::stream::StreamExt;
use log::{error, info};
use log::{error, info, warn};
use matrix_sdk::config::SyncSettings;
use matrix_sdk::deserialized_responses::TimelineEvent;
use matrix_sdk::encryption::verification::{Emoji, SasState, SasVerification, Verification};
use matrix_sdk::room::{Joined, Messages, MessagesOptions, Room};
use matrix_sdk::room::{Joined, MessagesOptions, Room};
use matrix_sdk::ruma::api::client::filter::{
FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter,
};
Expand Down Expand Up @@ -40,10 +41,6 @@ pub struct Matrix {
send: Sender<MatuiEvent>,
}

pub enum MessageEvent {
FetchCompleted(Messages),
}

impl Matrix {
pub fn new(send: Sender<MatuiEvent>) -> Matrix {
let rt = tokio::runtime::Builder::new_multi_thread()
Expand Down Expand Up @@ -217,8 +214,9 @@ impl Matrix {
self.room_cache.get_rooms()
}

pub fn fetch_messages(&self, room: Joined, sender: Sender<MessageEvent>) {
pub fn fetch_messages(&self, room: Joined) {
let matrix = self.clone();
let sender = self.send.clone();

self.rt.spawn(async move {
let messages = match room
Expand All @@ -232,9 +230,39 @@ impl Matrix {
}
};

sender
.send(MessageEvent::FetchCompleted(messages))
.expect("count not send message event");
let mut reversed = messages.chunk.clone();
reversed.reverse();

for msg in reversed {
sender
.send(MatuiEvent::Timeline(msg))
.expect("could not send timeline event")
}

async fn send_member_event(
msg: &TimelineEvent,
room: Joined,
sender: Sender<MatuiEvent>,
) -> anyhow::Result<()> {
let deserialized = msg.event.deserialize()?;

let member = room
.get_member(deserialized.sender())
.await?
.context("not a member")?;

sender.send(MatuiEvent::Member(member))?;

Ok(())
}

for msg in &messages.chunk {
let sender = sender.clone();

if let Err(e) = send_member_event(msg, room.clone(), sender).await {
warn!("Could not send room member event: {}", e.to_string());
}
}
});
}
}
Expand Down
104 changes: 75 additions & 29 deletions src/widgets/chat.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,99 @@
use crate::matrix::matrix::{Matrix, MessageEvent};
use crate::matrix::matrix::Matrix;
use crate::widgets::get_margin;
use log::info;
use matrix_sdk::deserialized_responses::TimelineEvent;
use matrix_sdk::room::{Joined, Messages};
use matrix_sdk::room::{Joined, RoomMember};
use ruma::events::room::message::MessageType::Text;
use ruma::events::room::message::TextMessageEventContent;
use ruma::events::AnyMessageLikeEvent::RoomMessage;
use ruma::events::AnyTimelineEvent::MessageLike;
use ruma::events::MessageLikeEvent::Original;
use ruma::OwnedEventId;
use std::cell::Cell;
use std::sync::mpsc::{channel, Receiver, Sender};
use tui::buffer::Buffer;
use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style};
use tui::text::{Span, Spans};
use tui::widgets::{List, ListItem, ListState, StatefulWidget, Widget};

pub struct Chat {
matrix: Matrix,
room: Option<Joined>,
messages: Option<Messages>,
messages: Vec<Message>,
list_state: Cell<ListState>,
sender: Sender<MessageEvent>,
receiver: Receiver<MessageEvent>,
}

pub struct Message {
id: OwnedEventId,
body: String,
sender: String,
}

impl Message {
fn try_from(event: TimelineEvent) -> Option<Self> {
if let Ok(MessageLike(RoomMessage(Original(c)))) = event.event.deserialize() {
let body = match c.content.msgtype {
Text(TextMessageEventContent { body, .. }) => body,
_ => return None,
};

return Some(Message {
id: c.event_id,
body,
sender: c.sender.to_string(),
});
}

None
}

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

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

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

let wrapped = textwrap::wrap(&self.body, width);

for l in wrapped {
lines.extend(Text::from(l))
}

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

ListItem::new(lines)
}
}

impl Chat {
pub fn new(matrix: Matrix) -> Self {
let (sender, receiver) = channel();

Self {
matrix,
room: None,
messages: None,
messages: vec![],
list_state: Cell::new(ListState::default()),
sender,
receiver,
}
}

pub fn set_room(&mut self, room: Joined) {
self.matrix
.fetch_messages(room.clone(), self.sender.clone());

self.matrix.fetch_messages(room.clone());
self.room = Some(room);
}

pub fn tick(&mut self) {
if let Ok(MessageEvent::FetchCompleted(msg)) = self.receiver.try_recv() {
let total = msg.chunk.len();
self.messages = Some(msg);
info!("loaded {} messages", total);
pub fn timeline_event(&mut self, event: TimelineEvent) {
if let Some(message) = Message::try_from(event) {
self.messages.push(message);
}
}

pub fn room_member_event(&mut self, member: RoomMember) {
for msg in self.messages.iter_mut() {
if &msg.sender == member.user_id() {
msg.sender = member.name().to_string();
}
}
}

Expand All @@ -66,19 +115,16 @@ impl Widget for ChatWidget<'_> {
.constraints([Constraint::Percentage(100)].as_ref())
.split(area)[0];

let items: Vec<ListItem> = if let Some(msg) = &self.chat.messages {
msg.chunk.iter().map(make_list_item).collect()
} else {
vec![]
};
let items: Vec<ListItem> = self
.chat
.messages
.iter()
.map(|m| m.to_list_item(area.width as usize))
.collect();

let mut list_state = self.chat.list_state.take();
let list = List::new(items).highlight_style(Style::default().bg(Color::DarkGray));
StatefulWidget::render(list, area, buf, &mut list_state);
self.chat.list_state.set(list_state)
}
}

fn make_list_item(m: &TimelineEvent) -> ListItem {
ListItem::new(m.event.json().to_string())
}

0 comments on commit e14ebeb

Please sign in to comment.