feature (ui - rooms / events): implemented a rooms list area in the UI and live timeline updates on room timeline sync events
This commit is contained in:
parent
f8bf6ee07d
commit
658f05b8d3
|
@ -2648,6 +2648,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cli-log",
|
"cli-log",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"indexmap 1.9.3",
|
||||||
"matrix-sdk",
|
"matrix-sdk",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -15,4 +15,5 @@ anyhow = "1.0"
|
||||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
cli-log = "2.0"
|
cli-log = "2.0"
|
||||||
|
indexmap = "*"
|
|
@ -68,8 +68,7 @@ impl Event {
|
||||||
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
|
|
||||||
if self.matrix_event.is_some() {
|
if self.matrix_event.is_some() {
|
||||||
info!("Matrix Event: {:#?}", self.matrix_event.clone().unwrap());
|
return self.handle_matrix(app).await;
|
||||||
return Ok(EventStatus::Ok);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = match app.status.state() {
|
let status = match app.status.state() {
|
||||||
|
@ -81,6 +80,22 @@ impl Event {
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_matrix(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
|
let sync = self.matrix_event.clone().unwrap();
|
||||||
|
for (m_room_id, m_room) in sync.rooms.join.iter() {
|
||||||
|
let room = match app.status.get_room_mut(m_room_id) {
|
||||||
|
Some(r) => r,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
for m_event in m_room.timeline.events.clone() {
|
||||||
|
let event = m_event.event.deserialize().unwrap().into_full_event(m_room_id.clone());
|
||||||
|
room.timeline_add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(EventStatus::Ok)
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_main(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
async fn handle_main(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
if self.input_event.is_some() {
|
if self.input_event.is_some() {
|
||||||
match tui_textarea::Input::from(self.input_event.clone().unwrap()) {
|
match tui_textarea::Input::from(self.input_event.clone().unwrap()) {
|
||||||
|
@ -94,6 +109,22 @@ impl Event {
|
||||||
input => {
|
input => {
|
||||||
match app.ui.input_position() {
|
match app.ui.input_position() {
|
||||||
ui::MainInputPosition::MessageCompose => { app.ui.message_compose.input(input); },
|
ui::MainInputPosition::MessageCompose => { app.ui.message_compose.input(input); },
|
||||||
|
ui::MainInputPosition::Rooms => {
|
||||||
|
match input {
|
||||||
|
tui_textarea::Input {key: tui_textarea::Key::Down, ..} => {
|
||||||
|
let i = match app.ui.rooms_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= app.status.rooms().len() - 1 { 0 }
|
||||||
|
else { i + 1 }
|
||||||
|
},
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
app.ui.rooms_state.select(Some(i));
|
||||||
|
app.status.set_room_by_index(i)?;
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
};
|
||||||
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,8 @@ impl App<'_> {
|
||||||
self.status.set_account_user_id(user_id);
|
self.status.set_account_user_id(user_id);
|
||||||
|
|
||||||
|
|
||||||
for room in self.status.rooms_mut() {
|
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?;
|
room.poll_old_timeline().await?;
|
||||||
room.poll_old_timeline().await?;
|
room.poll_old_timeline().await?;
|
||||||
|
@ -162,10 +163,6 @@ impl App<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn room(&self) -> Option<()> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn account(&self) -> Result<&Account> {
|
pub fn account(&self) -> Result<&Account> {
|
||||||
let account = self.accounts_manager.current();
|
let account = self.accounts_manager.current();
|
||||||
match account {
|
match account {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use indexmap::IndexMap;
|
||||||
use matrix_sdk::{Client,
|
use matrix_sdk::{Client,
|
||||||
ruma::{events::{AnyTimelineEvent}},
|
ruma::{events::{AnyTimelineEvent}, RoomId},
|
||||||
deserialized_responses::TimelineEvent,
|
|
||||||
room::MessagesOptions};
|
room::MessagesOptions};
|
||||||
use anyhow::{Result, Error};
|
use anyhow::{Result, Error};
|
||||||
use cli_log::{error, warn, info};
|
use cli_log::{error, warn, info};
|
||||||
|
@ -13,6 +13,7 @@ pub enum State {
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
matrix_room: matrix_sdk::room::Joined,
|
matrix_room: matrix_sdk::room::Joined,
|
||||||
|
name: String,
|
||||||
timeline: Vec<AnyTimelineEvent>,
|
timeline: Vec<AnyTimelineEvent>,
|
||||||
timeline_end: Option<String>,
|
timeline_end: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -23,14 +24,15 @@ pub struct Status {
|
||||||
account_user_id: String,
|
account_user_id: String,
|
||||||
|
|
||||||
client: Option<Client>,
|
client: Option<Client>,
|
||||||
rooms: Vec<Room>,
|
rooms: IndexMap<String, Room>,
|
||||||
current_room_id: Option<u32>,
|
current_room_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
pub fn new (matrix_room: matrix_sdk::room::Joined) -> Self {
|
pub fn new (matrix_room: matrix_sdk::room::Joined) -> Self {
|
||||||
Self {
|
Self {
|
||||||
matrix_room,
|
matrix_room,
|
||||||
|
name: "".to_string(),
|
||||||
timeline: Vec::new(),
|
timeline: Vec::new(),
|
||||||
timeline_end: None,
|
timeline_end: None,
|
||||||
}
|
}
|
||||||
|
@ -57,6 +59,15 @@ impl Room {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &String {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_name(&mut self) -> Result<()> {
|
||||||
|
self.name = self.matrix_room.display_name().await?.to_string();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn timeline_add(&mut self, event: AnyTimelineEvent) {
|
pub fn timeline_add(&mut self, event: AnyTimelineEvent) {
|
||||||
self.timeline.push(event);
|
self.timeline.push(event);
|
||||||
}
|
}
|
||||||
|
@ -66,16 +77,13 @@ impl Room {
|
||||||
|
|
||||||
impl Status {
|
impl Status {
|
||||||
pub fn new(client: Option<Client>) -> Self {
|
pub fn new(client: Option<Client>) -> Self {
|
||||||
let rooms = match &client {
|
let mut rooms = IndexMap::new();
|
||||||
Some(c) => {
|
if let Some(c) = &client {
|
||||||
c.joined_rooms()
|
for r in c.joined_rooms() {
|
||||||
.iter()
|
rooms.insert(
|
||||||
.map(|r| {
|
r.room_id().to_string(),
|
||||||
Room::new(r.clone())
|
Room::new(r.clone()));
|
||||||
})
|
}
|
||||||
.collect::<Vec<_>>()
|
|
||||||
},
|
|
||||||
None => Vec::new(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -84,7 +92,7 @@ impl Status {
|
||||||
account_user_id: "".to_string(),
|
account_user_id: "".to_string(),
|
||||||
client,
|
client,
|
||||||
rooms,
|
rooms,
|
||||||
current_room_id: Some(0),
|
current_room_id: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,16 +113,42 @@ impl Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn room(&self) -> Option<&Room> {
|
pub fn room(&self) -> Option<&Room> {
|
||||||
self.rooms.get(self.current_room_id? as usize)
|
self.rooms.get(self.current_room_id.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rooms(&self) -> &Vec<Room> {
|
pub fn rooms(&self) -> &IndexMap<String, Room> {
|
||||||
&self.rooms
|
&self.rooms
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rooms_mut(&mut self) -> &mut Vec<Room> {
|
pub fn rooms_mut(&mut self) -> &mut IndexMap<String, Room> {
|
||||||
&mut self.rooms
|
&mut self.rooms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_room(&mut self, room_id: &RoomId) -> Result<()> {
|
||||||
|
if self.rooms.contains_key(room_id.as_str()) {
|
||||||
|
self.current_room_id = room_id.to_string();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::msg(format!("failed to set room -> invalid room id {}", room_id.to_string())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_room_by_index(&mut self, room_index: usize) -> Result<()> {
|
||||||
|
if let Some((room_id, _)) = self.rooms.get_index(room_index) {
|
||||||
|
self.current_room_id = room_id.clone();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::msg(format!("failed to set room -> invalid room index {}", room_index)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_room(&self, room_id: &RoomId) -> Option<&Room> {
|
||||||
|
self.rooms.get(room_id.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_room_mut(&mut self, room_id: &RoomId) -> Option<&mut Room> {
|
||||||
|
self.rooms.get_mut(room_id.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> &State {
|
pub fn state(&self) -> &State {
|
||||||
&self.state
|
&self.state
|
||||||
|
|
|
@ -14,7 +14,7 @@ use tui::{backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, wi
|
||||||
use tui::layout::Alignment;
|
use tui::layout::Alignment;
|
||||||
use tui::style::{Color, Modifier, Style};
|
use tui::style::{Color, Modifier, Style};
|
||||||
use tui::text::{Spans, Span, Text};
|
use tui::text::{Spans, Span, Text};
|
||||||
use tui::widgets::{Paragraph, Wrap};
|
use tui::widgets::{List, ListItem, ListState, Paragraph, Wrap};
|
||||||
use tui_textarea::{Input, Key, TextArea};
|
use tui_textarea::{Input, Key, TextArea};
|
||||||
use cli_log::{error, warn, info};
|
use cli_log::{error, warn, info};
|
||||||
use matrix_sdk::{room::MessagesOptions, ruma::events::{AnyTimelineEvent, AnyMessageLikeEvent}};
|
use matrix_sdk::{room::MessagesOptions, ruma::events::{AnyTimelineEvent, AnyMessageLikeEvent}};
|
||||||
|
@ -48,6 +48,7 @@ pub struct SetupUI<'a> {
|
||||||
pub struct UI<'a> {
|
pub struct UI<'a> {
|
||||||
terminal: Terminal<CrosstermBackend<Stdout>>,
|
terminal: Terminal<CrosstermBackend<Stdout>>,
|
||||||
input_position: MainInputPosition,
|
input_position: MainInputPosition,
|
||||||
|
pub rooms_state: ListState,
|
||||||
pub message_compose: TextArea<'a>,
|
pub message_compose: TextArea<'a>,
|
||||||
|
|
||||||
pub setup_ui: Option<SetupUI<'a>>,
|
pub setup_ui: Option<SetupUI<'a>>,
|
||||||
|
@ -232,6 +233,7 @@ impl UI<'_> {
|
||||||
Self {
|
Self {
|
||||||
terminal,
|
terminal,
|
||||||
input_position: MainInputPosition::Rooms,
|
input_position: MainInputPosition::Rooms,
|
||||||
|
rooms_state: ListState::default(),
|
||||||
message_compose,
|
message_compose,
|
||||||
setup_ui: None
|
setup_ui: None
|
||||||
}
|
}
|
||||||
|
@ -276,6 +278,13 @@ impl UI<'_> {
|
||||||
status_content.extend(Text::styled(status.account_user_id(), Style::default()));
|
status_content.extend(Text::styled(status.account_user_id(), Style::default()));
|
||||||
status_content.extend(Text::styled("settings", Style::default().fg(Color::LightMagenta).add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)));
|
status_content.extend(Text::styled("settings", Style::default().fg(Color::LightMagenta).add_modifier(Modifier::ITALIC | Modifier::UNDERLINED)));
|
||||||
|
|
||||||
|
let rooms_content = status.rooms()
|
||||||
|
.iter()
|
||||||
|
.map(|(_, room)| {
|
||||||
|
ListItem::new(Span::styled(room.name(), Style::default()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let messages_content = match status.room() {
|
let messages_content = match status.room() {
|
||||||
Some(r) => {
|
Some(r) => {
|
||||||
r.timeline()
|
r.timeline()
|
||||||
|
@ -353,10 +362,14 @@ impl UI<'_> {
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Status as usize])))
|
.style(Style::default().fg(colors[MainInputPosition::Status as usize])))
|
||||||
.alignment(Alignment::Left);
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
let rooms = Block::default()
|
let rooms = List::new(rooms_content)
|
||||||
.title("Rooms")
|
.block(Block::default()
|
||||||
.borders(Borders::ALL)
|
.title("Rooms")
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Rooms as usize]));
|
.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 = Paragraph::new(messages_content)
|
||||||
.block(Block::default()
|
.block(Block::default()
|
||||||
|
@ -375,7 +388,7 @@ impl UI<'_> {
|
||||||
// render the widgets
|
// render the widgets
|
||||||
self.terminal.draw(|frame| {
|
self.terminal.draw(|frame| {
|
||||||
frame.render_widget(status, left_chunks[0]);
|
frame.render_widget(status, left_chunks[0]);
|
||||||
frame.render_widget(rooms, left_chunks[1]);
|
frame.render_stateful_widget(rooms, left_chunks[1], &mut self.rooms_state);
|
||||||
frame.render_widget(messages, middle_chunks[0]);
|
frame.render_widget(messages, middle_chunks[0]);
|
||||||
frame.render_widget(self.message_compose.widget(), middle_chunks[1]);
|
frame.render_widget(self.message_compose.widget(), middle_chunks[1]);
|
||||||
frame.render_widget(room_info, right_chunks[0]);
|
frame.render_widget(room_info, right_chunks[0]);
|
||||||
|
|
Reference in New Issue