diff --git a/src/app/event.rs b/src/app/event.rs index c7c4baa..02226f1 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -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 => (), + }; + }, _ => () }; }, diff --git a/src/app/mod.rs b/src/app/mod.rs index 38a58a5..09931fd 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -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"); diff --git a/src/app/status.rs b/src/app/status.rs index 6294602..c5ac502 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -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, timeline_end: Option, + view_scroll: Option, } 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 { &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 { + self.view_scroll + } + + pub fn set_view_scroll(&mut self, scroll: Option) { + 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 { &self.rooms } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f5b214b..5dbab21 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -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: +)") .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: +)") + .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::>() }, 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(())