diff --git a/Cargo.lock b/Cargo.lock index 602688b..2a2e53a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2648,6 +2648,7 @@ dependencies = [ "anyhow", "cli-log", "crossterm", + "indexmap 1.9.3", "matrix-sdk", "serde", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 392fad3..b5f63dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ anyhow = "1.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tokio-util = "0.7" serde = "1.0" -cli-log = "2.0" \ No newline at end of file +cli-log = "2.0" +indexmap = "*" \ No newline at end of file diff --git a/src/app/event.rs b/src/app/event.rs index 7248281..c7c4baa 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -68,8 +68,7 @@ impl Event { pub async fn handle(&self, app: &mut App<'_>) -> Result { if self.matrix_event.is_some() { - info!("Matrix Event: {:#?}", self.matrix_event.clone().unwrap()); - return Ok(EventStatus::Ok); + return self.handle_matrix(app).await; } let status = match app.status.state() { @@ -81,6 +80,22 @@ impl Event { Ok(status) } + async fn handle_matrix(&self, app: &mut App<'_>) -> Result { + 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 { if self.input_event.is_some() { match tui_textarea::Input::from(self.input_event.clone().unwrap()) { @@ -94,6 +109,22 @@ impl Event { input => { match app.ui.input_position() { 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)?; + }, + _ => () + }; + }, _ => (), } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 8037ada..38a58a5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -140,7 +140,8 @@ impl App<'_> { 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?; @@ -162,10 +163,6 @@ impl App<'_> { Ok(()) } - pub fn room(&self) -> Option<()> { - None - } - pub fn account(&self) -> Result<&Account> { let account = self.accounts_manager.current(); match account { diff --git a/src/app/status.rs b/src/app/status.rs index d916645..6294602 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -1,6 +1,6 @@ +use indexmap::IndexMap; use matrix_sdk::{Client, - ruma::{events::{AnyTimelineEvent}}, - deserialized_responses::TimelineEvent, + ruma::{events::{AnyTimelineEvent}, RoomId}, room::MessagesOptions}; use anyhow::{Result, Error}; use cli_log::{error, warn, info}; @@ -13,6 +13,7 @@ pub enum State { pub struct Room { matrix_room: matrix_sdk::room::Joined, + name: String, timeline: Vec, timeline_end: Option, } @@ -23,14 +24,15 @@ pub struct Status { account_user_id: String, client: Option, - rooms: Vec, - current_room_id: Option, + rooms: IndexMap, + current_room_id: String, } impl Room { pub fn new (matrix_room: matrix_sdk::room::Joined) -> Self { Self { matrix_room, + name: "".to_string(), timeline: Vec::new(), timeline_end: None, } @@ -57,6 +59,15 @@ impl Room { 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) { self.timeline.push(event); } @@ -66,16 +77,13 @@ impl Room { impl Status { pub fn new(client: Option) -> Self { - let rooms = match &client { - Some(c) => { - c.joined_rooms() - .iter() - .map(|r| { - Room::new(r.clone()) - }) - .collect::>() - }, - None => Vec::new(), + let mut rooms = IndexMap::new(); + if let Some(c) = &client { + for r in c.joined_rooms() { + rooms.insert( + r.room_id().to_string(), + Room::new(r.clone())); + } }; Self { @@ -84,7 +92,7 @@ impl Status { account_user_id: "".to_string(), client, rooms, - current_room_id: Some(0), + current_room_id: "".to_string(), } } @@ -105,16 +113,42 @@ impl Status { } 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 { + pub fn rooms(&self) -> &IndexMap { &self.rooms } - pub fn rooms_mut(&mut self) -> &mut Vec { + pub fn rooms_mut(&mut self) -> &mut IndexMap { &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 { &self.state diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 565f208..f5b214b 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -14,7 +14,7 @@ use tui::{backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, wi use tui::layout::Alignment; use tui::style::{Color, Modifier, Style}; 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 cli_log::{error, warn, info}; use matrix_sdk::{room::MessagesOptions, ruma::events::{AnyTimelineEvent, AnyMessageLikeEvent}}; @@ -48,6 +48,7 @@ pub struct SetupUI<'a> { pub struct UI<'a> { terminal: Terminal>, input_position: MainInputPosition, + pub rooms_state: ListState, pub message_compose: TextArea<'a>, pub setup_ui: Option>, @@ -232,6 +233,7 @@ impl UI<'_> { Self { terminal, input_position: MainInputPosition::Rooms, + rooms_state: ListState::default(), message_compose, 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("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::>(); + let messages_content = match status.room() { Some(r) => { r.timeline() @@ -353,10 +362,14 @@ impl UI<'_> { .style(Style::default().fg(colors[MainInputPosition::Status as usize]))) .alignment(Alignment::Left); - let rooms = Block::default() - .title("Rooms") - .borders(Borders::ALL) - .style(Style::default().fg(colors[MainInputPosition::Rooms as usize])); + let rooms = List::new(rooms_content) + .block(Block::default() + .title("Rooms") + .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) .block(Block::default() @@ -375,7 +388,7 @@ impl UI<'_> { // render the widgets self.terminal.draw(|frame| { 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(self.message_compose.widget(), middle_chunks[1]); frame.render_widget(room_info, right_chunks[0]);