feature: (room timeline): Changed timeline view to a scrollable list / Made single messages/events selectable / Implemented Message sending
This commit is contained in:
parent
658f05b8d3
commit
98f7e806de
|
@ -108,20 +108,84 @@ impl Event {
|
|||
}
|
||||
input => {
|
||||
match app.ui.input_position() {
|
||||
ui::MainInputPosition::MessageCompose => { app.ui.message_compose.input(input); },
|
||||
ui::MainInputPosition::MessageCompose => {
|
||||
match input {
|
||||
tui_textarea::Input {key: tui_textarea::Key::Enter, alt: true, ..} => {
|
||||
match app.status.room_mut() {
|
||||
Some(room) => {
|
||||
room.send(app.ui.message_compose.lines().join("\n")).await?;
|
||||
app.ui.message_compose_clear();
|
||||
},
|
||||
None => ()
|
||||
};
|
||||
}
|
||||
_ => { app.ui.message_compose.input(input); }
|
||||
};
|
||||
},
|
||||
ui::MainInputPosition::Rooms => {
|
||||
match input {
|
||||
tui_textarea::Input {key: tui_textarea::Key::Down, ..} => {
|
||||
tui_textarea::Input {key: tui_textarea::Key::Up, ..} => {
|
||||
let i = match app.ui.rooms_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= app.status.rooms().len() - 1 { 0 }
|
||||
else { i + 1 }
|
||||
if i > 0 { i - 1 }
|
||||
else { i }
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
app.ui.rooms_state.select(Some(i));
|
||||
app.status.set_room_by_index(i)?;
|
||||
},
|
||||
tui_textarea::Input {key: tui_textarea::Key::Down, ..} => {
|
||||
let i = match app.ui.rooms_state.selected() {
|
||||
Some(i) => {
|
||||
if i < app.status.rooms().len() { i + 1 }
|
||||
else { i }
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
app.ui.rooms_state.select(Some(i));
|
||||
app.status.set_room_by_index(i)?;
|
||||
},
|
||||
_ => ()
|
||||
};
|
||||
},
|
||||
ui::MainInputPosition::Messages => {
|
||||
match input {
|
||||
tui_textarea::Input {key: tui_textarea::Key::Up, ..} => {
|
||||
match app.status.room_mut(){
|
||||
Some(room) => {
|
||||
let len = room.timeline().len();
|
||||
let i = match room.view_scroll() {
|
||||
Some(i) => i+1,
|
||||
None => 0,
|
||||
};
|
||||
if i < len {
|
||||
room.set_view_scroll(Some(i))
|
||||
}
|
||||
if i <= len - 5 {
|
||||
room.poll_old_timeline().await?;
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
};
|
||||
},
|
||||
tui_textarea::Input {key: tui_textarea::Key::Down, ..} => {
|
||||
match app.status.room_mut(){
|
||||
Some(room) => {
|
||||
match room.view_scroll() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
room.set_view_scroll(None);
|
||||
} else {
|
||||
room.set_view_scroll(Some(i - 1));
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
};
|
||||
},
|
||||
None => (),
|
||||
};
|
||||
},
|
||||
_ => ()
|
||||
};
|
||||
},
|
||||
|
|
|
@ -142,9 +142,7 @@ impl App<'_> {
|
|||
|
||||
for (_, room) in self.status.rooms_mut() {
|
||||
room.update_name().await?;
|
||||
room.poll_old_timeline().await?;
|
||||
room.poll_old_timeline().await?;
|
||||
room.poll_old_timeline().await?;
|
||||
for _ in 0..3 { room.poll_old_timeline().await?; }
|
||||
}
|
||||
|
||||
info!("Initializing client for the current account");
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use std::any::Any;
|
||||
use indexmap::IndexMap;
|
||||
use matrix_sdk::{Client,
|
||||
ruma::{events::{AnyTimelineEvent}, RoomId},
|
||||
ruma::{events::{AnyTimelineEvent,
|
||||
room::message::RoomMessageEventContent,
|
||||
StateEventType},
|
||||
RoomId,
|
||||
TransactionId},
|
||||
room::MessagesOptions};
|
||||
use anyhow::{Result, Error};
|
||||
use cli_log::{error, warn, info};
|
||||
|
@ -16,6 +21,7 @@ pub struct Room {
|
|||
name: String,
|
||||
timeline: Vec<AnyTimelineEvent>,
|
||||
timeline_end: Option<String>,
|
||||
view_scroll: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct Status {
|
||||
|
@ -35,10 +41,17 @@ impl Room {
|
|||
name: "".to_string(),
|
||||
timeline: Vec::new(),
|
||||
timeline_end: None,
|
||||
view_scroll: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn poll_old_timeline(&mut self) -> Result<()> {
|
||||
if let Some(AnyTimelineEvent::State(event)) = &self.timeline.get(0) {
|
||||
if event.event_type() == StateEventType::RoomCreate {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut messages_options = MessagesOptions::backward();
|
||||
messages_options = match &self.timeline_end {
|
||||
Some(end) => messages_options.from(end.as_str()),
|
||||
|
@ -73,6 +86,21 @@ impl Room {
|
|||
}
|
||||
|
||||
pub fn timeline(&self) -> &Vec<AnyTimelineEvent> { &self.timeline }
|
||||
|
||||
pub async fn send(&mut self, message: String) -> Result<()> {
|
||||
let content = RoomMessageEventContent::text_plain(message);
|
||||
let id = TransactionId::new();
|
||||
self.matrix_room.send(content, Some(&id)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn view_scroll(&self) -> Option<usize> {
|
||||
self.view_scroll
|
||||
}
|
||||
|
||||
pub fn set_view_scroll(&mut self, scroll: Option<usize>) {
|
||||
self.view_scroll = scroll;
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
|
@ -116,6 +144,10 @@ impl Status {
|
|||
self.rooms.get(self.current_room_id.as_str())
|
||||
}
|
||||
|
||||
pub fn room_mut(&mut self) -> Option<&mut Room> {
|
||||
self.rooms.get_mut(self.current_room_id.as_str())
|
||||
}
|
||||
|
||||
pub fn rooms(&self) -> &IndexMap<String, Room> {
|
||||
&self.rooms
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use anyhow::{Error, Result};
|
|||
use std::io::Stdout;
|
||||
use std::io;
|
||||
use tui::{backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, widgets::{Block, Borders, Widget}, Terminal, Frame};
|
||||
use tui::layout::Alignment;
|
||||
use tui::layout::{Alignment, Corner};
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
use tui::text::{Spans, Span, Text};
|
||||
use tui::widgets::{List, ListItem, ListState, Paragraph, Wrap};
|
||||
|
@ -225,7 +225,7 @@ impl UI<'_> {
|
|||
let mut message_compose = TextArea::default();
|
||||
message_compose.set_block(
|
||||
Block::default()
|
||||
.title("Message Compose")
|
||||
.title("Message Compose (send: <Alt>+<Enter>)")
|
||||
.borders(Borders::ALL));
|
||||
|
||||
info!("Initialized UI");
|
||||
|
@ -251,6 +251,14 @@ impl UI<'_> {
|
|||
|
||||
pub fn input_position(&self) -> &MainInputPosition { &self.input_position }
|
||||
|
||||
pub fn message_compose_clear(&mut self) {
|
||||
self.message_compose = TextArea::default();
|
||||
self.message_compose.set_block(
|
||||
Block::default()
|
||||
.title("Message Compose (send: <Alt>+<Enter>)")
|
||||
.borders(Borders::ALL));
|
||||
}
|
||||
|
||||
pub async fn update(&mut self, status: &Status) -> Result<()> {
|
||||
let chunk = self.terminal.size()?;
|
||||
|
||||
|
@ -289,6 +297,7 @@ impl UI<'_> {
|
|||
Some(r) => {
|
||||
r.timeline()
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|event| {
|
||||
match event {
|
||||
|
||||
|
@ -306,28 +315,33 @@ impl UI<'_> {
|
|||
},
|
||||
_ => ("~~~ not supported message like event ~~~".to_string(), Color::Red)
|
||||
};
|
||||
Spans::from(vec![
|
||||
Span::styled(message_like_event.sender().to_string(), Style::default().fg(Color::Cyan)),
|
||||
Span::styled(": ", Style::default().fg(Color::Cyan)),
|
||||
Span::styled(content, Style::default().fg(color)),
|
||||
])
|
||||
let mut text = Text::styled(message_like_event.sender().to_string(),
|
||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));
|
||||
text.extend(Text::styled(content.to_string(),
|
||||
Style::default().fg(color)));
|
||||
ListItem::new(text)
|
||||
},
|
||||
|
||||
// State Events
|
||||
AnyTimelineEvent::State(state) => {
|
||||
Spans::from(vec![
|
||||
ListItem::new(vec![Spans::from(vec![
|
||||
Span::styled(state.sender().to_string(), Style::default().fg(Color::DarkGray)),
|
||||
Span::styled(": ", Style::default().fg(Color::DarkGray)),
|
||||
Span::styled(state.event_type().to_string(), Style::default().fg(Color::DarkGray))
|
||||
])
|
||||
])])
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
None => {
|
||||
vec![Spans::from(Span::styled("No room selected!", Style::default().fg(Color::Magenta)))]
|
||||
},
|
||||
vec![ListItem::new(Text::styled("No room selected!", Style::default().fg(Color::Magenta)))]
|
||||
}
|
||||
};
|
||||
|
||||
let mut messages_state = ListState::default();
|
||||
if let Some(room) = status.room() {
|
||||
messages_state.select(room.view_scroll());
|
||||
};
|
||||
|
||||
// calculate to widgets colors, based of which widget is currently selected
|
||||
|
@ -355,31 +369,32 @@ impl UI<'_> {
|
|||
};
|
||||
|
||||
// initiate the widgets
|
||||
let status = Paragraph::new(status_content)
|
||||
let status_panel = Paragraph::new(status_content)
|
||||
.block(Block::default()
|
||||
.title("Status")
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(colors[MainInputPosition::Status as usize])))
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
let rooms = List::new(rooms_content)
|
||||
let rooms_panel = List::new(rooms_content)
|
||||
.block(Block::default()
|
||||
.title("Rooms")
|
||||
.title("Rooms (navigate: arrow keys)")
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(colors[MainInputPosition::Rooms as usize])))
|
||||
.style(Style::default().fg(Color::DarkGray))
|
||||
.highlight_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
|
||||
.highlight_symbol(">");
|
||||
|
||||
let messages = Paragraph::new(messages_content)
|
||||
let messages_panel = List::new(messages_content)
|
||||
.block(Block::default()
|
||||
.title("Messages")
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(colors[MainInputPosition::Messages as usize])))
|
||||
.alignment(Alignment::Left)
|
||||
.wrap(Wrap {trim: false});
|
||||
.start_corner(Corner::BottomLeft)
|
||||
.highlight_symbol(">")
|
||||
.highlight_style(Style::default().fg(Color::LightMagenta).add_modifier(Modifier::BOLD));
|
||||
|
||||
let room_info = Block::default()
|
||||
let room_info_panel = Block::default()
|
||||
.title("Room Info")
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(colors[MainInputPosition::RoomInfo as usize]));
|
||||
|
@ -387,11 +402,11 @@ impl UI<'_> {
|
|||
|
||||
// render the widgets
|
||||
self.terminal.draw(|frame| {
|
||||
frame.render_widget(status, left_chunks[0]);
|
||||
frame.render_stateful_widget(rooms, left_chunks[1], &mut self.rooms_state);
|
||||
frame.render_widget(messages, middle_chunks[0]);
|
||||
frame.render_widget(status_panel, left_chunks[0]);
|
||||
frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state);
|
||||
frame.render_stateful_widget(messages_panel, middle_chunks[0], & mut messages_state);
|
||||
frame.render_widget(self.message_compose.widget(), middle_chunks[1]);
|
||||
frame.render_widget(room_info, right_chunks[0]);
|
||||
frame.render_widget(room_info_panel, right_chunks[0]);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
|
Reference in New Issue