Style(treewide): Format all files with rustfmt
This commit is contained in:
parent
196641959e
commit
35225a14db
|
@ -1,13 +1,13 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use matrix_sdk::{Client,
|
|
||||||
config::SyncSettings,
|
|
||||||
ruma::{user_id,
|
|
||||||
events::room::message::SyncRoomMessageEvent,
|
|
||||||
exports::serde_json},
|
|
||||||
Session};
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
|
use cli_log::{error, info, warn};
|
||||||
|
use matrix_sdk::{
|
||||||
|
config::SyncSettings,
|
||||||
|
ruma::{events::room::message::SyncRoomMessageEvent, exports::serde_json, user_id},
|
||||||
|
Client, Session,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use cli_log::{error, warn, info};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
|
@ -33,7 +33,7 @@ pub struct AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
pub fn name (&self) -> &String {
|
pub fn name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,11 +43,12 @@ impl Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountsManager {
|
impl AccountsManager {
|
||||||
pub fn new(config:Option<String>) -> Self {
|
pub fn new(config: Option<String>) -> Self {
|
||||||
return match config {
|
return match config {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
info!("Loading serialized AccountsManager");
|
info!("Loading serialized AccountsManager");
|
||||||
let accounts_data:AccountsData = serde_json::from_str(&s).expect("failed to deserialize json");
|
let accounts_data: AccountsData =
|
||||||
|
serde_json::from_str(&s).expect("failed to deserialize json");
|
||||||
let mut clients = Vec::new();
|
let mut clients = Vec::new();
|
||||||
clients.resize(accounts_data.accounts.len(), None);
|
clients.resize(accounts_data.accounts.len(), None);
|
||||||
Self {
|
Self {
|
||||||
|
@ -56,7 +57,7 @@ impl AccountsManager {
|
||||||
accounts: accounts_data.accounts,
|
accounts: accounts_data.accounts,
|
||||||
clients,
|
clients,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
info!("Creating empty AccountsManager");
|
info!("Creating empty AccountsManager");
|
||||||
Self {
|
Self {
|
||||||
|
@ -66,7 +67,7 @@ impl AccountsManager {
|
||||||
clients: Vec::new(),
|
clients: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn restore(&mut self) -> Result<()> {
|
pub async fn restore(&mut self) -> Result<()> {
|
||||||
|
@ -74,7 +75,12 @@ impl AccountsManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(&mut self, homeserver: &String, username: &String, password: &String) -> Result<u32> {
|
pub async fn add(
|
||||||
|
&mut self,
|
||||||
|
homeserver: &String,
|
||||||
|
username: &String,
|
||||||
|
password: &String,
|
||||||
|
) -> Result<u32> {
|
||||||
let id = self.num_accounts;
|
let id = self.num_accounts;
|
||||||
self.num_accounts += 1;
|
self.num_accounts += 1;
|
||||||
|
|
||||||
|
@ -84,7 +90,8 @@ impl AccountsManager {
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
client.login_username(username, password)
|
client
|
||||||
|
.login_username(username, password)
|
||||||
.initial_device_display_name("Trinitrix")
|
.initial_device_display_name("Trinitrix")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -94,9 +101,13 @@ impl AccountsManager {
|
||||||
let account = Account {
|
let account = Account {
|
||||||
homeserver: homeserver.to_string(),
|
homeserver: homeserver.to_string(),
|
||||||
id,
|
id,
|
||||||
name: client.account().get_display_name().await?.expect("failed to fetch display name"),
|
name: client
|
||||||
|
.account()
|
||||||
|
.get_display_name()
|
||||||
|
.await?
|
||||||
|
.expect("failed to fetch display name"),
|
||||||
session: session.clone(),
|
session: session.clone(),
|
||||||
sync_token: None
|
sync_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.logout().await?;
|
self.logout().await?;
|
||||||
|
@ -105,12 +116,16 @@ impl AccountsManager {
|
||||||
self.clients.push(Some(client));
|
self.clients.push(Some(client));
|
||||||
self.save()?;
|
self.save()?;
|
||||||
|
|
||||||
info!("Logged in as '{}' device ID: {}", session.user_id.to_string(), session.device_id.to_string());
|
info!(
|
||||||
|
"Logged in as '{}' device ID: {}",
|
||||||
|
session.user_id.to_string(),
|
||||||
|
session.device_id.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(&mut self, account_id:u32) -> Result<()> {
|
pub async fn login(&mut self, account_id: u32) -> Result<()> {
|
||||||
self.logout().await?; // log out the current account
|
self.logout().await?; // log out the current account
|
||||||
|
|
||||||
let account = if account_id >= self.num_accounts {
|
let account = if account_id >= self.num_accounts {
|
||||||
|
@ -120,17 +135,28 @@ impl AccountsManager {
|
||||||
self.get(account_id).expect("Account lookup failed")
|
self.get(account_id).expect("Account lookup failed")
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.clients.get(account_id as usize).expect("client lookup failed").is_none() {
|
if self
|
||||||
info!("No client cached for account: '{}' -> requesting a new one", &account.session.user_id);
|
.clients
|
||||||
|
.get(account_id as usize)
|
||||||
|
.expect("client lookup failed")
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
"No client cached for account: '{}' -> requesting a new one",
|
||||||
|
&account.session.user_id
|
||||||
|
);
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.homeserver_url(&account.homeserver)
|
.homeserver_url(&account.homeserver)
|
||||||
.sled_store(format!("userdata/{account_id}"), Some("supersecure")) ?
|
.sled_store(format!("userdata/{account_id}"), Some("supersecure"))?
|
||||||
.build()
|
.build()
|
||||||
.await?;
|
.await?;
|
||||||
client.restore_login(account.session.clone()).await?;
|
client.restore_login(account.session.clone()).await?;
|
||||||
self.clients.insert(account_id as usize, Some(client));
|
self.clients.insert(account_id as usize, Some(client));
|
||||||
} else {
|
} else {
|
||||||
info!("Using cached client for account: '{}'", &account.session.user_id);
|
info!(
|
||||||
|
"Using cached client for account: '{}'",
|
||||||
|
&account.session.user_id
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Restored account");
|
info!("Restored account");
|
||||||
|
@ -156,7 +182,7 @@ impl AccountsManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) -> Result<()>{
|
pub fn save(&self) -> Result<()> {
|
||||||
let accounts_data = AccountsData {
|
let accounts_data = AccountsData {
|
||||||
current_account: self.current_account,
|
current_account: self.current_account,
|
||||||
accounts: self.accounts.clone(),
|
accounts: self.accounts.clone(),
|
||||||
|
@ -188,5 +214,7 @@ impl AccountsManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn num_accounts(&self) -> u32 { self.num_accounts }
|
pub fn num_accounts(&self) -> u32 {
|
||||||
|
self.num_accounts
|
||||||
|
}
|
||||||
}
|
}
|
331
src/app/event.rs
331
src/app/event.rs
|
@ -1,14 +1,27 @@
|
||||||
use crate::app::{App, status::{Status, State}};
|
use anyhow::{Error, Result};
|
||||||
use crate::ui;
|
use cli_log::{error, info, warn};
|
||||||
use tokio::time::Duration;
|
use matrix_sdk::{
|
||||||
use tokio::sync::{mpsc, broadcast};
|
config::SyncSettings,
|
||||||
|
room::Room,
|
||||||
|
ruma::events::room::{
|
||||||
|
member::StrippedRoomMemberEvent,
|
||||||
|
message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent},
|
||||||
|
},
|
||||||
|
Client, LoopCtrl,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::{broadcast, mpsc},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use anyhow::{Result, Error};
|
|
||||||
use matrix_sdk::{Client, room::{Room}, config::SyncSettings, ruma::events::room::{
|
use crate::{
|
||||||
member::StrippedRoomMemberEvent,
|
app::{
|
||||||
message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent},
|
status::{State, Status},
|
||||||
}, LoopCtrl};
|
App,
|
||||||
use cli_log::{error, warn, info};
|
},
|
||||||
|
ui,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EventStatus {
|
pub enum EventStatus {
|
||||||
|
@ -20,7 +33,7 @@ pub enum EventStatus {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
input_event: Option<crossterm::event::Event>,
|
input_event: Option<crossterm::event::Event>,
|
||||||
matrix_event: Option<matrix_sdk::deserialized_responses::SyncResponse>
|
matrix_event: Option<matrix_sdk::deserialized_responses::SyncResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventBuilder {
|
pub struct EventBuilder {
|
||||||
|
@ -42,7 +55,6 @@ impl Default for EventBuilder {
|
||||||
event: Event::default(),
|
event: Event::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventBuilder {
|
impl EventBuilder {
|
||||||
|
@ -51,7 +63,10 @@ impl EventBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matrix_event(&mut self, matrix_event: matrix_sdk::deserialized_responses::SyncResponse) -> &Self {
|
fn matrix_event(
|
||||||
|
&mut self,
|
||||||
|
matrix_event: matrix_sdk::deserialized_responses::SyncResponse,
|
||||||
|
) -> &Self {
|
||||||
self.event.matrix_event = Some(matrix_event);
|
self.event.matrix_event = Some(matrix_event);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -66,7 +81,6 @@ impl EventBuilder {
|
||||||
|
|
||||||
impl Event {
|
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() {
|
||||||
return self.handle_matrix(app).await;
|
return self.handle_matrix(app).await;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +102,11 @@ impl Event {
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
for m_event in m_room.timeline.events.clone() {
|
for m_event in m_room.timeline.events.clone() {
|
||||||
let event = m_event.event.deserialize().unwrap().into_full_event(m_room_id.clone());
|
let event = m_event
|
||||||
|
.event
|
||||||
|
.deserialize()
|
||||||
|
.unwrap()
|
||||||
|
.into_full_event(m_room_id.clone());
|
||||||
room.timeline_add(event);
|
room.timeline_add(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,99 +117,125 @@ impl Event {
|
||||||
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()) {
|
||||||
tui_textarea::Input { key: tui_textarea::Key::Esc, .. } => return Ok(EventStatus::Terminate),
|
tui_textarea::Input {
|
||||||
|
key: tui_textarea::Key::Esc,
|
||||||
|
..
|
||||||
|
} => return Ok(EventStatus::Terminate),
|
||||||
tui_textarea::Input {
|
tui_textarea::Input {
|
||||||
key: tui_textarea::Key::Tab,
|
key: tui_textarea::Key::Tab,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
app.ui.cycle_main_input_position();
|
app.ui.cycle_main_input_position();
|
||||||
}
|
}
|
||||||
input => {
|
input => match app.ui.input_position() {
|
||||||
match app.ui.input_position() {
|
ui::MainInputPosition::MessageCompose => {
|
||||||
ui::MainInputPosition::MessageCompose => {
|
match input {
|
||||||
match input {
|
tui_textarea::Input {
|
||||||
tui_textarea::Input {key: tui_textarea::Key::Enter, alt: true, ..} => {
|
key: tui_textarea::Key::Enter,
|
||||||
match app.status.room_mut() {
|
alt: true,
|
||||||
Some(room) => {
|
..
|
||||||
room.send(app.ui.message_compose.lines().join("\n")).await?;
|
} => {
|
||||||
app.ui.message_compose_clear();
|
match app.status.room_mut() {
|
||||||
},
|
Some(room) => {
|
||||||
None => ()
|
room.send(app.ui.message_compose.lines().join("\n"))
|
||||||
};
|
.await?;
|
||||||
}
|
app.ui.message_compose_clear();
|
||||||
_ => { app.ui.message_compose.input(input); }
|
}
|
||||||
};
|
None => (),
|
||||||
},
|
};
|
||||||
ui::MainInputPosition::Rooms => {
|
}
|
||||||
match input {
|
_ => {
|
||||||
tui_textarea::Input {key: tui_textarea::Key::Up, ..} => {
|
app.ui.message_compose.input(input);
|
||||||
let i = match app.ui.rooms_state.selected() {
|
}
|
||||||
Some(i) => {
|
};
|
||||||
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 => (),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
ui::MainInputPosition::Rooms => {
|
||||||
|
match input {
|
||||||
|
tui_textarea::Input {
|
||||||
|
key: tui_textarea::Key::Up,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let i = match app.ui.rooms_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
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 => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(EventStatus::Ok)
|
Ok(EventStatus::Ok)
|
||||||
|
@ -200,18 +244,21 @@ impl Event {
|
||||||
async fn handle_setup(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
async fn handle_setup(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
let ui = match &mut app.ui.setup_ui {
|
let ui = match &mut app.ui.setup_ui {
|
||||||
Some(ui) => ui,
|
Some(ui) => ui,
|
||||||
None => return Err(Error::msg("SetupUI instance not found"))
|
None => return Err(Error::msg("SetupUI instance not found")),
|
||||||
};
|
};
|
||||||
|
|
||||||
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()) {
|
||||||
tui_textarea::Input { key: tui_textarea::Key::Esc, .. } => return Ok(EventStatus::Terminate),
|
tui_textarea::Input {
|
||||||
|
key: tui_textarea::Key::Esc,
|
||||||
|
..
|
||||||
|
} => return Ok(EventStatus::Terminate),
|
||||||
tui_textarea::Input {
|
tui_textarea::Input {
|
||||||
key: tui_textarea::Key::Tab,
|
key: tui_textarea::Key::Tab,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
ui.cycle_input_position();
|
ui.cycle_input_position();
|
||||||
},
|
}
|
||||||
tui_textarea::Input {
|
tui_textarea::Input {
|
||||||
key: tui_textarea::Key::Enter,
|
key: tui_textarea::Key::Enter,
|
||||||
..
|
..
|
||||||
|
@ -225,33 +272,40 @@ impl Event {
|
||||||
if login.is_ok() {
|
if login.is_ok() {
|
||||||
return Ok(EventStatus::Finished);
|
return Ok(EventStatus::Finished);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => ui.cycle_input_position(),
|
_ => ui.cycle_input_position(),
|
||||||
};
|
};
|
||||||
},
|
|
||||||
input => {
|
|
||||||
match ui.input_position() {
|
|
||||||
ui::SetupInputPosition::Homeserver => { ui.homeserver.input(input); },
|
|
||||||
ui::SetupInputPosition::Username => { ui.username.input(input); },
|
|
||||||
ui::SetupInputPosition::Password => {
|
|
||||||
ui.password_data.input(input.clone());
|
|
||||||
match input.key {
|
|
||||||
tui_textarea::Key::Char(_) => {
|
|
||||||
ui.password.input(tui_textarea::Input { key: tui_textarea::Key::Char('*'), ctrl: false, alt: false });
|
|
||||||
},
|
|
||||||
_ => { ui.password.input(input); },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
input => match ui.input_position() {
|
||||||
|
ui::SetupInputPosition::Homeserver => {
|
||||||
|
ui.homeserver.input(input);
|
||||||
|
}
|
||||||
|
ui::SetupInputPosition::Username => {
|
||||||
|
ui.username.input(input);
|
||||||
|
}
|
||||||
|
ui::SetupInputPosition::Password => {
|
||||||
|
ui.password_data.input(input.clone());
|
||||||
|
match input.key {
|
||||||
|
tui_textarea::Key::Char(_) => {
|
||||||
|
ui.password.input(tui_textarea::Input {
|
||||||
|
key: tui_textarea::Key::Char('*'),
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ui.password.input(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(EventStatus::Ok)
|
Ok(EventStatus::Ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn poll_input_events_stage_2(channel: mpsc::Sender<Event>) -> Result<()> {
|
async fn poll_input_events_stage_2(channel: mpsc::Sender<Event>) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
if crossterm::event::poll(Duration::from_millis(100))? {
|
if crossterm::event::poll(Duration::from_millis(100))? {
|
||||||
|
@ -266,7 +320,10 @@ async fn poll_input_events_stage_2(channel: mpsc::Sender<Event>) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn poll_input_events(channel: mpsc::Sender<Event>, kill: CancellationToken) -> Result<()> {
|
pub async fn poll_input_events(
|
||||||
|
channel: mpsc::Sender<Event>,
|
||||||
|
kill: CancellationToken,
|
||||||
|
) -> Result<()> {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
output = poll_input_events_stage_2(channel) => output,
|
output = poll_input_events_stage_2(channel) => output,
|
||||||
_ = kill.cancelled() => Err(Error::msg("received kill signal"))
|
_ = kill.cancelled() => Err(Error::msg("received kill signal"))
|
||||||
|
@ -275,26 +332,30 @@ pub async fn poll_input_events(channel: mpsc::Sender<Event>, kill: CancellationT
|
||||||
|
|
||||||
async fn poll_matrix_events_stage_2(channel: mpsc::Sender<Event>, client: Client) -> Result<()> {
|
async fn poll_matrix_events_stage_2(channel: mpsc::Sender<Event>, client: Client) -> Result<()> {
|
||||||
let sync_settings = SyncSettings::default();
|
let sync_settings = SyncSettings::default();
|
||||||
// .token(sync_token)
|
// .token(sync_token)
|
||||||
// .timeout(Duration::from_secs(30));
|
// .timeout(Duration::from_secs(30));
|
||||||
|
|
||||||
let tx = &channel;
|
let tx = &channel;
|
||||||
|
|
||||||
client.sync_with_callback(sync_settings, |response| async move {
|
client
|
||||||
let event = EventBuilder::default()
|
.sync_with_callback(sync_settings, |response| async move {
|
||||||
.matrix_event(response)
|
let event = EventBuilder::default().matrix_event(response).build();
|
||||||
.build();
|
|
||||||
|
|
||||||
match tx.send(event).await {
|
match tx.send(event).await {
|
||||||
Ok(_) => LoopCtrl::Continue,
|
Ok(_) => LoopCtrl::Continue,
|
||||||
Err(_) => LoopCtrl::Break,
|
Err(_) => LoopCtrl::Break,
|
||||||
}
|
}
|
||||||
}).await?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn poll_matrix_events(channel: mpsc::Sender<Event>, kill: CancellationToken, client: Client) -> Result<()> {
|
pub async fn poll_matrix_events(
|
||||||
|
channel: mpsc::Sender<Event>,
|
||||||
|
kill: CancellationToken,
|
||||||
|
client: Client,
|
||||||
|
) -> Result<()> {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
output = poll_matrix_events_stage_2(channel, client) => output,
|
output = poll_matrix_events_stage_2(channel, client) => output,
|
||||||
_ = kill.cancelled() => Err(Error::msg("received kill signal")),
|
_ = kill.cancelled() => Err(Error::msg("received kill signal")),
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use crate::accounts;
|
|
||||||
use crate::ui;
|
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use matrix_sdk::{Client,
|
|
||||||
room::{Room},
|
|
||||||
config::SyncSettings,
|
|
||||||
ruma::events::room::{
|
|
||||||
member::StrippedRoomMemberEvent,
|
|
||||||
message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent},
|
|
||||||
},
|
|
||||||
event_handler::Ctx
|
|
||||||
};
|
|
||||||
use accounts::Account;
|
|
||||||
use accounts::AccountsManager;
|
|
||||||
use anyhow::{Result, Error};
|
|
||||||
use cli_log::{error, warn, info};
|
|
||||||
use tokio::{time::{sleep, Duration}, sync::{mpsc, broadcast}};
|
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
use status::{Status, State};
|
|
||||||
|
|
||||||
|
use accounts::{Account, AccountsManager};
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
use cli_log::{error, info, warn};
|
||||||
|
use matrix_sdk::{
|
||||||
|
config::SyncSettings,
|
||||||
|
event_handler::Ctx,
|
||||||
|
room::Room,
|
||||||
|
ruma::events::room::{
|
||||||
|
member::StrippedRoomMemberEvent,
|
||||||
|
message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent},
|
||||||
|
},
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
use status::{State, Status};
|
||||||
|
use tokio::{
|
||||||
|
sync::{broadcast, mpsc},
|
||||||
|
time::{sleep, Duration},
|
||||||
|
};
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
use crate::{accounts, ui};
|
||||||
|
|
||||||
pub struct App<'a> {
|
pub struct App<'a> {
|
||||||
ui: ui::UI<'a>,
|
ui: ui::UI<'a>,
|
||||||
|
@ -35,14 +37,12 @@ pub struct App<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for App<'_> {
|
impl Drop for App<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let path:&std::path::Path = Path::new("userdata/accounts.json");
|
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
||||||
let config = if path.exists() {
|
let config = if path.exists() {
|
||||||
info!("Reading account config (userdata/accounts.json)");
|
info!("Reading account config (userdata/accounts.json)");
|
||||||
Some(std::fs::read_to_string(path).expect("failed to read accounts config"))
|
Some(std::fs::read_to_string(path).expect("failed to read accounts config"))
|
||||||
|
@ -65,9 +65,11 @@ impl App<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
|
||||||
// Spawn input event listener
|
// Spawn input event listener
|
||||||
tokio::task::spawn(event::poll_input_events(self.channel_tx.clone(), self.input_listener_killer.clone()));
|
tokio::task::spawn(event::poll_input_events(
|
||||||
|
self.channel_tx.clone(),
|
||||||
|
self.input_listener_killer.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
if self.account().is_err() {
|
if self.account().is_err() {
|
||||||
info!("No saved sessions found -> jumping into setup");
|
info!("No saved sessions found -> jumping into setup");
|
||||||
|
@ -77,14 +79,13 @@ impl App<'_> {
|
||||||
self.init_account().await?;
|
self.init_account().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
self.status.set_state(State::Main);
|
self.status.set_state(State::Main);
|
||||||
self.ui.update(&self.status).await?;
|
self.ui.update(&self.status).await?;
|
||||||
|
|
||||||
let event: event::Event = match self.channel_rx.recv().await {
|
let event: event::Event = match self.channel_rx.recv().await {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => return Err(Error::msg("Event channel has no senders"))
|
None => return Err(Error::msg("Event channel has no senders")),
|
||||||
};
|
};
|
||||||
|
|
||||||
match event.handle(self).await? {
|
match event.handle(self).await? {
|
||||||
|
@ -107,7 +108,7 @@ impl App<'_> {
|
||||||
|
|
||||||
let event: event::Event = match self.channel_rx.recv().await {
|
let event: event::Event = match self.channel_rx.recv().await {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => return Err(Error::msg("Event channel has no senders"))
|
None => return Err(Error::msg("Event channel has no senders")),
|
||||||
};
|
};
|
||||||
|
|
||||||
match event.handle(self).await? {
|
match event.handle(self).await? {
|
||||||
|
@ -121,14 +122,19 @@ impl App<'_> {
|
||||||
pub async fn init_account(&mut self) -> Result<()> {
|
pub async fn init_account(&mut self) -> Result<()> {
|
||||||
let client = match self.client() {
|
let client = match self.client() {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => return Err(Error::msg("failed to get current client"))
|
None => return Err(Error::msg("failed to get current client")),
|
||||||
}.clone();
|
}
|
||||||
|
.clone();
|
||||||
|
|
||||||
self.matrix_listener_killer.cancel();
|
self.matrix_listener_killer.cancel();
|
||||||
self.matrix_listener_killer = CancellationToken::new();
|
self.matrix_listener_killer = CancellationToken::new();
|
||||||
|
|
||||||
// Spawn Matrix Event Listener
|
// Spawn Matrix Event Listener
|
||||||
tokio::task::spawn(event::poll_matrix_events(self.channel_tx.clone(), self.matrix_listener_killer.clone(), client.clone()));
|
tokio::task::spawn(event::poll_matrix_events(
|
||||||
|
self.channel_tx.clone(),
|
||||||
|
self.matrix_listener_killer.clone(),
|
||||||
|
client.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
// Reset Status
|
// Reset Status
|
||||||
self.status = Status::new(Some(client));
|
self.status = Status::new(Some(client));
|
||||||
|
@ -139,10 +145,11 @@ impl App<'_> {
|
||||||
self.status.set_account_name(name);
|
self.status.set_account_name(name);
|
||||||
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.update_name().await?;
|
||||||
for _ in 0..3 { room.poll_old_timeline().await?; }
|
for _ in 0..3 {
|
||||||
|
room.poll_old_timeline().await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Initializing client for the current account");
|
info!("Initializing client for the current account");
|
||||||
|
@ -151,12 +158,18 @@ impl App<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn switch_account(&mut self, account_id: u32) -> Result<()> {
|
pub async fn switch_account(&mut self, account_id: u32) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(&mut self, homeserver: &String, username: &String, password: &String) -> Result<()> {
|
pub async fn login(
|
||||||
self.accounts_manager.add(homeserver, username, password).await?;
|
&mut self,
|
||||||
|
homeserver: &String,
|
||||||
|
username: &String,
|
||||||
|
password: &String,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.accounts_manager
|
||||||
|
.add(homeserver, username, password)
|
||||||
|
.await?;
|
||||||
self.init_account().await?;
|
self.init_account().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -165,7 +178,7 @@ impl App<'_> {
|
||||||
let account = self.accounts_manager.current();
|
let account = self.accounts_manager.current();
|
||||||
match account {
|
match account {
|
||||||
None => Err(Error::msg("failed to resolve current account")),
|
None => Err(Error::msg("failed to resolve current account")),
|
||||||
Some(a) => Ok(a)
|
Some(a) => Ok(a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
use cli_log::{error, info, warn};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use matrix_sdk::{Client,
|
use matrix_sdk::{
|
||||||
ruma::{events::{AnyTimelineEvent,
|
room::MessagesOptions,
|
||||||
room::message::RoomMessageEventContent,
|
ruma::{
|
||||||
StateEventType},
|
events::{room::message::RoomMessageEventContent, AnyTimelineEvent, StateEventType},
|
||||||
RoomId,
|
RoomId, TransactionId,
|
||||||
TransactionId},
|
},
|
||||||
room::MessagesOptions};
|
Client,
|
||||||
use anyhow::{Result, Error};
|
};
|
||||||
use cli_log::{error, warn, info};
|
|
||||||
|
|
||||||
pub enum State {
|
pub enum State {
|
||||||
None,
|
None,
|
||||||
|
@ -36,7 +38,7 @@ pub struct Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
name: "".to_string(),
|
||||||
|
@ -50,26 +52,29 @@ impl Room {
|
||||||
pub async fn poll_old_timeline(&mut self) -> Result<()> {
|
pub async fn poll_old_timeline(&mut self) -> Result<()> {
|
||||||
if let Some(AnyTimelineEvent::State(event)) = &self.timeline.get(0) {
|
if let Some(AnyTimelineEvent::State(event)) = &self.timeline.get(0) {
|
||||||
if event.event_type() == StateEventType::RoomCreate {
|
if event.event_type() == StateEventType::RoomCreate {
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut messages_options = MessagesOptions::backward();
|
let mut messages_options = MessagesOptions::backward();
|
||||||
messages_options = match &self.timeline_end {
|
messages_options = match &self.timeline_end {
|
||||||
Some(end) => messages_options.from(end.as_str()),
|
Some(end) => messages_options.from(end.as_str()),
|
||||||
None => messages_options
|
None => messages_options,
|
||||||
};
|
};
|
||||||
let events = self.matrix_room.messages(messages_options).await?;
|
let events = self.matrix_room.messages(messages_options).await?;
|
||||||
self.timeline_end = events.end;
|
self.timeline_end = events.end;
|
||||||
|
|
||||||
for event in events.chunk.iter() {
|
for event in events.chunk.iter() {
|
||||||
self.timeline.insert(0, match event.event.deserialize() {
|
self.timeline.insert(
|
||||||
Ok(ev) => ev,
|
0,
|
||||||
Err(err) => {
|
match event.event.deserialize() {
|
||||||
warn!("Failed to deserialize timeline event - {err}");
|
Ok(ev) => ev,
|
||||||
continue;
|
Err(err) => {
|
||||||
}
|
warn!("Failed to deserialize timeline event - {err}");
|
||||||
});
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -87,7 +92,9 @@ impl Room {
|
||||||
self.timeline.push(event);
|
self.timeline.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timeline(&self) -> &Vec<AnyTimelineEvent> { &self.timeline }
|
pub fn timeline(&self) -> &Vec<AnyTimelineEvent> {
|
||||||
|
&self.timeline
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send(&mut self, message: String) -> Result<()> {
|
pub async fn send(&mut self, message: String) -> Result<()> {
|
||||||
let content = RoomMessageEventContent::text_plain(message);
|
let content = RoomMessageEventContent::text_plain(message);
|
||||||
|
@ -114,9 +121,7 @@ impl Status {
|
||||||
let mut rooms = IndexMap::new();
|
let mut rooms = IndexMap::new();
|
||||||
if let Some(c) = &client {
|
if let Some(c) = &client {
|
||||||
for r in c.joined_rooms() {
|
for r in c.joined_rooms() {
|
||||||
rooms.insert(
|
rooms.insert(r.room_id().to_string(), Room::new(r.clone()));
|
||||||
r.room_id().to_string(),
|
|
||||||
Room::new(r.clone()));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -167,7 +172,10 @@ impl Status {
|
||||||
self.current_room_id = room_id.to_string();
|
self.current_room_id = room_id.to_string();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg(format!("failed to set room -> invalid room id {}", room_id.to_string())))
|
Err(Error::msg(format!(
|
||||||
|
"failed to set room -> invalid room id {}",
|
||||||
|
room_id.to_string()
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +184,10 @@ impl Status {
|
||||||
self.current_room_id = room_id.clone();
|
self.current_room_id = room_id.clone();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg(format!("failed to set room -> invalid room index {}", room_index)))
|
Err(Error::msg(format!(
|
||||||
|
"failed to set room -> invalid room index {}",
|
||||||
|
room_index
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
mod ui;
|
|
||||||
mod accounts;
|
mod accounts;
|
||||||
mod app;
|
mod app;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
use cli_log::{error, warn, info};
|
use cli_log::{error, info, warn};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
|
333
src/ui/mod.rs
333
src/ui/mod.rs
|
@ -1,30 +1,35 @@
|
||||||
use crate::app::status::Status;
|
use std::{cmp, io, io::Stdout};
|
||||||
|
|
||||||
use std::cmp;
|
|
||||||
use crossterm::{
|
|
||||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, read},
|
|
||||||
execute,
|
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
||||||
style::{style},
|
|
||||||
};
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use std::io::Stdout;
|
use cli_log::{error, info, warn};
|
||||||
use std::io;
|
use crossterm::{
|
||||||
use tui::{backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, widgets::{Block, Borders, Widget}, Terminal, Frame};
|
event::{self, read, DisableMouseCapture, EnableMouseCapture, Event},
|
||||||
use tui::layout::{Alignment, Corner};
|
execute,
|
||||||
use tui::style::{Color, Modifier, Style};
|
style::style,
|
||||||
use tui::text::{Spans, Span, Text};
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
use tui::widgets::{List, ListItem, ListState, Paragraph, Wrap};
|
};
|
||||||
|
use matrix_sdk::{
|
||||||
|
room::MessagesOptions,
|
||||||
|
ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent},
|
||||||
|
};
|
||||||
|
use tui::{
|
||||||
|
backend::CrosstermBackend,
|
||||||
|
layout::{Alignment, Constraint, Corner, Direction, Layout},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::{Span, Spans, Text},
|
||||||
|
widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Widget, Wrap},
|
||||||
|
Frame, Terminal,
|
||||||
|
};
|
||||||
use tui_textarea::{Input, Key, TextArea};
|
use tui_textarea::{Input, Key, TextArea};
|
||||||
use cli_log::{error, warn, info};
|
|
||||||
use matrix_sdk::{room::MessagesOptions, ruma::events::{AnyTimelineEvent, AnyMessageLikeEvent}};
|
use crate::app::status::Status;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum SetupInputPosition {
|
pub enum SetupInputPosition {
|
||||||
Homeserver,
|
Homeserver,
|
||||||
Username,
|
Username,
|
||||||
Password,
|
Password,
|
||||||
Ok
|
Ok,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -33,7 +38,7 @@ pub enum MainInputPosition {
|
||||||
Rooms,
|
Rooms,
|
||||||
Messages,
|
Messages,
|
||||||
MessageCompose,
|
MessageCompose,
|
||||||
RoomInfo
|
RoomInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SetupUI<'a> {
|
pub struct SetupUI<'a> {
|
||||||
|
@ -54,7 +59,6 @@ pub struct UI<'a> {
|
||||||
pub setup_ui: Option<SetupUI<'a>>,
|
pub setup_ui: Option<SetupUI<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn terminal_prepare() -> Result<Stdout> {
|
fn terminal_prepare() -> Result<Stdout> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
|
@ -80,9 +84,7 @@ pub fn textarea_inactivate(textarea: &mut TextArea) {
|
||||||
.block()
|
.block()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
||||||
textarea.set_block(
|
textarea.set_block(b.style(Style::default().fg(Color::DarkGray)));
|
||||||
b.style(Style::default().fg(Color::DarkGray))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UI<'_> {
|
impl Drop for UI<'_> {
|
||||||
|
@ -94,7 +96,9 @@ impl Drop for UI<'_> {
|
||||||
LeaveAlternateScreen,
|
LeaveAlternateScreen,
|
||||||
DisableMouseCapture
|
DisableMouseCapture
|
||||||
).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)");
|
).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)");
|
||||||
self.terminal.show_cursor().expect("While destructing UI -> Failed to re-enable cursor");
|
self.terminal
|
||||||
|
.show_cursor()
|
||||||
|
.expect("While destructing UI -> Failed to re-enable cursor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,18 +109,9 @@ impl SetupUI<'_> {
|
||||||
let mut password = TextArea::default();
|
let mut password = TextArea::default();
|
||||||
let mut password_data = TextArea::default();
|
let mut password_data = TextArea::default();
|
||||||
|
|
||||||
homeserver.set_block(
|
homeserver.set_block(Block::default().title("Homeserver").borders(Borders::ALL));
|
||||||
Block::default()
|
username.set_block(Block::default().title("Username").borders(Borders::ALL));
|
||||||
.title("Homeserver")
|
password.set_block(Block::default().title("Password").borders(Borders::ALL));
|
||||||
.borders(Borders::ALL));
|
|
||||||
username.set_block(
|
|
||||||
Block::default()
|
|
||||||
.title("Username")
|
|
||||||
.borders(Borders::ALL));
|
|
||||||
password.set_block(
|
|
||||||
Block::default()
|
|
||||||
.title("Password")
|
|
||||||
.borders(Borders::ALL));
|
|
||||||
|
|
||||||
textarea_activate(&mut homeserver);
|
textarea_activate(&mut homeserver);
|
||||||
textarea_inactivate(&mut username);
|
textarea_inactivate(&mut username);
|
||||||
|
@ -138,68 +133,74 @@ impl SetupUI<'_> {
|
||||||
textarea_activate(&mut self.username);
|
textarea_activate(&mut self.username);
|
||||||
textarea_inactivate(&mut self.password);
|
textarea_inactivate(&mut self.password);
|
||||||
SetupInputPosition::Username
|
SetupInputPosition::Username
|
||||||
},
|
}
|
||||||
SetupInputPosition::Username => {
|
SetupInputPosition::Username => {
|
||||||
textarea_inactivate(&mut self.homeserver);
|
textarea_inactivate(&mut self.homeserver);
|
||||||
textarea_inactivate(&mut self.username);
|
textarea_inactivate(&mut self.username);
|
||||||
textarea_activate(&mut self.password);
|
textarea_activate(&mut self.password);
|
||||||
SetupInputPosition::Password
|
SetupInputPosition::Password
|
||||||
},
|
}
|
||||||
SetupInputPosition::Password => {
|
SetupInputPosition::Password => {
|
||||||
textarea_inactivate(&mut self.homeserver);
|
textarea_inactivate(&mut self.homeserver);
|
||||||
textarea_inactivate(&mut self.username);
|
textarea_inactivate(&mut self.username);
|
||||||
textarea_inactivate(&mut self.password);
|
textarea_inactivate(&mut self.password);
|
||||||
SetupInputPosition::Ok
|
SetupInputPosition::Ok
|
||||||
},
|
}
|
||||||
SetupInputPosition::Ok => {
|
SetupInputPosition::Ok => {
|
||||||
textarea_activate(&mut self.homeserver);
|
textarea_activate(&mut self.homeserver);
|
||||||
textarea_inactivate(&mut self.username);
|
textarea_inactivate(&mut self.username);
|
||||||
textarea_inactivate(&mut self.password);
|
textarea_inactivate(&mut self.password);
|
||||||
SetupInputPosition::Homeserver
|
SetupInputPosition::Homeserver
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_position(&self) -> &SetupInputPosition { &self.input_position }
|
pub fn input_position(&self) -> &SetupInputPosition {
|
||||||
|
&self.input_position
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update(&'_ mut self, terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
pub async fn update(
|
||||||
|
&'_ mut self,
|
||||||
|
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
|
||||||
|
) -> Result<()> {
|
||||||
let mut strings: Vec<String> = Vec::new();
|
let mut strings: Vec<String> = Vec::new();
|
||||||
strings.resize(3, "".to_string());
|
strings.resize(3, "".to_string());
|
||||||
|
|
||||||
let content_ok = match self.input_position {
|
let content_ok = match self.input_position {
|
||||||
SetupInputPosition:: Ok => Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)),
|
SetupInputPosition::Ok => {
|
||||||
|
Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED))
|
||||||
|
}
|
||||||
_ => Span::styled("OK", Style::default().fg(Color::DarkGray)),
|
_ => Span::styled("OK", Style::default().fg(Color::DarkGray)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default().title("Login").borders(Borders::ALL);
|
||||||
.title("Login")
|
|
||||||
.borders(Borders::ALL);
|
|
||||||
|
|
||||||
let mut ok = Paragraph::new(content_ok)
|
|
||||||
.alignment(Alignment::Center);
|
|
||||||
|
|
||||||
|
let mut ok = Paragraph::new(content_ok).alignment(Alignment::Center);
|
||||||
|
|
||||||
// define a 32 * 6 chunk in the middle of the screen
|
// define a 32 * 6 chunk in the middle of the screen
|
||||||
let mut chunk = terminal.size()?;
|
let mut chunk = terminal.size()?;
|
||||||
chunk.x = (chunk.width / 2) - 16;
|
chunk.x = (chunk.width / 2) - 16;
|
||||||
chunk.y = (chunk.height / 2) - 5;
|
chunk.y = (chunk.height / 2) - 5;
|
||||||
chunk.height = 12;
|
chunk.height = 12;
|
||||||
chunk.width = 32;
|
chunk.width = 32;
|
||||||
|
|
||||||
let mut split_chunk = chunk.clone();
|
let mut split_chunk = chunk.clone();
|
||||||
split_chunk.x += 1;
|
split_chunk.x += 1;
|
||||||
split_chunk.y += 1;
|
split_chunk.y += 1;
|
||||||
split_chunk.height -= 1;
|
split_chunk.height -= 1;
|
||||||
split_chunk.width -= 2;
|
split_chunk.width -= 2;
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints(
|
||||||
Constraint::Length(3), // 0. Homserver:
|
[
|
||||||
Constraint::Length(3), // 1. Username:
|
Constraint::Length(3), // 0. Homserver:
|
||||||
Constraint::Length(3), // 2. Password:
|
Constraint::Length(3), // 1. Username:
|
||||||
Constraint::Length(1) // 3. OK
|
Constraint::Length(3), // 2. Password:
|
||||||
].as_ref())
|
Constraint::Length(1), // 3. OK
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.split(split_chunk);
|
.split(split_chunk);
|
||||||
|
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
|
@ -226,7 +227,8 @@ impl UI<'_> {
|
||||||
message_compose.set_block(
|
message_compose.set_block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Message Compose (send: <Alt>+<Enter>)")
|
.title("Message Compose (send: <Alt>+<Enter>)")
|
||||||
.borders(Borders::ALL));
|
.borders(Borders::ALL),
|
||||||
|
);
|
||||||
|
|
||||||
info!("Initialized UI");
|
info!("Initialized UI");
|
||||||
|
|
||||||
|
@ -235,7 +237,7 @@ impl UI<'_> {
|
||||||
input_position: MainInputPosition::Rooms,
|
input_position: MainInputPosition::Rooms,
|
||||||
rooms_state: ListState::default(),
|
rooms_state: ListState::default(),
|
||||||
message_compose,
|
message_compose,
|
||||||
setup_ui: None
|
setup_ui: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,14 +251,17 @@ impl UI<'_> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_position(&self) -> &MainInputPosition { &self.input_position }
|
pub fn input_position(&self) -> &MainInputPosition {
|
||||||
|
&self.input_position
|
||||||
|
}
|
||||||
|
|
||||||
pub fn message_compose_clear(&mut self) {
|
pub fn message_compose_clear(&mut self) {
|
||||||
self.message_compose = TextArea::default();
|
self.message_compose = TextArea::default();
|
||||||
self.message_compose.set_block(
|
self.message_compose.set_block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Message Compose (send: <Alt>+<Enter>)")
|
.title("Message Compose (send: <Alt>+<Enter>)")
|
||||||
.borders(Borders::ALL));
|
.borders(Borders::ALL),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&mut self, status: &Status) -> Result<()> {
|
pub async fn update(&mut self, status: &Status) -> Result<()> {
|
||||||
|
@ -264,7 +269,14 @@ impl UI<'_> {
|
||||||
|
|
||||||
let main_chunks = Layout::default()
|
let main_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Length(32), Constraint::Min(16), Constraint::Length(32)].as_ref())
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(32),
|
||||||
|
Constraint::Min(16),
|
||||||
|
Constraint::Length(32),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.split(chunk);
|
.split(chunk);
|
||||||
|
|
||||||
let left_chunks = Layout::default()
|
let left_chunks = Layout::default()
|
||||||
|
@ -274,7 +286,13 @@ impl UI<'_> {
|
||||||
|
|
||||||
let middle_chunks = Layout::default()
|
let middle_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Min(4), Constraint::Length(cmp::min(2 + self.message_compose.lines().len() as u16, 8))].as_ref())
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Min(4),
|
||||||
|
Constraint::Length(cmp::min(2 + self.message_compose.lines().len() as u16, 8)),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.split(main_chunks[1]);
|
.split(main_chunks[1]);
|
||||||
|
|
||||||
let right_chunks = Layout::default()
|
let right_chunks = Layout::default()
|
||||||
|
@ -282,15 +300,22 @@ impl UI<'_> {
|
||||||
.constraints([Constraint::Min(4)].as_ref())
|
.constraints([Constraint::Min(4)].as_ref())
|
||||||
.split(main_chunks[2]);
|
.split(main_chunks[2]);
|
||||||
|
|
||||||
let mut status_content = Text::styled(status.account_name(), Style::default().add_modifier(Modifier::BOLD));
|
let mut status_content = Text::styled(
|
||||||
|
status.account_name(),
|
||||||
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
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()
|
let rooms_content = status
|
||||||
|
.rooms()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, room)| {
|
.map(|(_, room)| ListItem::new(Span::styled(room.name(), Style::default())))
|
||||||
ListItem::new(Span::styled(room.name(), Style::default()))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let messages_content = match status.room() {
|
let messages_content = match status.room() {
|
||||||
|
@ -300,7 +325,6 @@ impl UI<'_> {
|
||||||
.rev()
|
.rev()
|
||||||
.map(|event| {
|
.map(|event| {
|
||||||
match event {
|
match event {
|
||||||
|
|
||||||
// Message Like Events
|
// Message Like Events
|
||||||
AnyTimelineEvent::MessageLike(message_like_event) => {
|
AnyTimelineEvent::MessageLike(message_like_event) => {
|
||||||
let (content, color) = match &message_like_event {
|
let (content, color) = match &message_like_event {
|
||||||
|
@ -312,30 +336,48 @@ impl UI<'_> {
|
||||||
.body();
|
.body();
|
||||||
|
|
||||||
(message_content.to_string(), Color::White)
|
(message_content.to_string(), Color::White)
|
||||||
},
|
}
|
||||||
_ => ("~~~ not supported message like event ~~~".to_string(), Color::Red)
|
_ => (
|
||||||
|
"~~~ not supported message like event ~~~".to_string(),
|
||||||
|
Color::Red,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
let mut text = Text::styled(message_like_event.sender().to_string(),
|
let mut text = Text::styled(
|
||||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));
|
message_like_event.sender().to_string(),
|
||||||
text.extend(Text::styled(content.to_string(),
|
Style::default()
|
||||||
Style::default().fg(color)));
|
.fg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
text.extend(Text::styled(
|
||||||
|
content.to_string(),
|
||||||
|
Style::default().fg(color),
|
||||||
|
));
|
||||||
ListItem::new(text)
|
ListItem::new(text)
|
||||||
},
|
}
|
||||||
|
|
||||||
// State Events
|
// State Events
|
||||||
AnyTimelineEvent::State(state) => {
|
AnyTimelineEvent::State(state) => {
|
||||||
ListItem::new(vec![Spans::from(vec![
|
ListItem::new(vec![Spans::from(vec![
|
||||||
Span::styled(state.sender().to_string(), Style::default().fg(Color::DarkGray)),
|
Span::styled(
|
||||||
|
state.sender().to_string(),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
Span::styled(": ", Style::default().fg(Color::DarkGray)),
|
Span::styled(": ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(state.event_type().to_string(), Style::default().fg(Color::DarkGray))
|
Span::styled(
|
||||||
|
state.event_type().to_string(),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
])])
|
])])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
vec![ListItem::new(Text::styled("No room selected!", Style::default().fg(Color::Red)))]
|
vec![ListItem::new(Text::styled(
|
||||||
|
"No room selected!",
|
||||||
|
Style::default().fg(Color::Red),
|
||||||
|
))]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -347,78 +389,129 @@ impl UI<'_> {
|
||||||
|
|
||||||
room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan)));
|
room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan)));
|
||||||
if room.encrypted() {
|
if room.encrypted() {
|
||||||
room_info_content.extend(Text::styled("Encrypted", Style::default().fg(Color::Green)));
|
room_info_content
|
||||||
|
.extend(Text::styled("Encrypted", Style::default().fg(Color::Green)));
|
||||||
} else {
|
} else {
|
||||||
room_info_content.extend(Text::styled("Not Encrypted!", Style::default().fg(Color::Red)));
|
room_info_content.extend(Text::styled(
|
||||||
|
"Not Encrypted!",
|
||||||
|
Style::default().fg(Color::Red),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
room_info_content.extend(Text::styled("No room selected!", Style::default().fg(Color::Red)));
|
room_info_content.extend(Text::styled(
|
||||||
|
"No room selected!",
|
||||||
|
Style::default().fg(Color::Red),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// calculate to widgets colors, based of which widget is currently selected
|
// calculate to widgets colors, based of which widget is currently selected
|
||||||
let colors = match self.input_position {
|
let colors = match self.input_position {
|
||||||
MainInputPosition::Status => {
|
MainInputPosition::Status => {
|
||||||
textarea_inactivate(&mut self.message_compose);
|
textarea_inactivate(&mut self.message_compose);
|
||||||
vec![Color::White, Color::DarkGray, Color::DarkGray, Color::DarkGray, Color::DarkGray]
|
vec![
|
||||||
},
|
Color::White,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
MainInputPosition::Rooms => {
|
MainInputPosition::Rooms => {
|
||||||
textarea_inactivate(&mut self.message_compose);
|
textarea_inactivate(&mut self.message_compose);
|
||||||
vec![Color::DarkGray, Color::White, Color::DarkGray, Color::DarkGray, Color::DarkGray]
|
vec![
|
||||||
},
|
Color::DarkGray,
|
||||||
|
Color::White,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
MainInputPosition::Messages => {
|
MainInputPosition::Messages => {
|
||||||
textarea_inactivate(&mut self.message_compose);
|
textarea_inactivate(&mut self.message_compose);
|
||||||
vec![Color::DarkGray, Color::DarkGray, Color::White, Color::DarkGray, Color::DarkGray]
|
vec![
|
||||||
},
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::White,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
MainInputPosition::MessageCompose => {
|
MainInputPosition::MessageCompose => {
|
||||||
textarea_activate(&mut self.message_compose);
|
textarea_activate(&mut self.message_compose);
|
||||||
vec![Color::DarkGray, Color::DarkGray, Color::DarkGray, Color::DarkGray, Color::DarkGray]
|
vec![
|
||||||
},
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
MainInputPosition::RoomInfo => {
|
MainInputPosition::RoomInfo => {
|
||||||
textarea_inactivate(&mut self.message_compose);
|
textarea_inactivate(&mut self.message_compose);
|
||||||
vec![Color::DarkGray, Color::DarkGray, Color::DarkGray, Color::DarkGray, Color::White]
|
vec![
|
||||||
},
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::White,
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// initiate the widgets
|
// initiate the widgets
|
||||||
let status_panel = Paragraph::new(status_content)
|
let status_panel = Paragraph::new(status_content)
|
||||||
.block(Block::default()
|
.block(
|
||||||
.title("Status")
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.title("Status")
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Status as usize])))
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[MainInputPosition::Status as usize])),
|
||||||
|
)
|
||||||
.alignment(Alignment::Left);
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
let rooms_panel = List::new(rooms_content)
|
let rooms_panel = List::new(rooms_content)
|
||||||
.block(Block::default()
|
.block(
|
||||||
.title("Rooms (navigate: arrow keys)")
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.title("Rooms (navigate: arrow keys)")
|
||||||
.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))
|
.style(Style::default().fg(Color::DarkGray))
|
||||||
.highlight_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
.highlight_symbol(">");
|
.highlight_symbol(">");
|
||||||
|
|
||||||
let messages_panel = List::new(messages_content)
|
let messages_panel = List::new(messages_content)
|
||||||
.block(Block::default()
|
.block(
|
||||||
.title("Messages")
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.title("Messages")
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Messages as usize])))
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[MainInputPosition::Messages as usize])),
|
||||||
|
)
|
||||||
.start_corner(Corner::BottomLeft)
|
.start_corner(Corner::BottomLeft)
|
||||||
.highlight_symbol(">")
|
.highlight_symbol(">")
|
||||||
.highlight_style(Style::default().fg(Color::LightMagenta).add_modifier(Modifier::BOLD));
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightMagenta)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
|
||||||
let room_info_panel = Paragraph::new(room_info_content)
|
let room_info_panel = Paragraph::new(room_info_content)
|
||||||
.block(Block::default()
|
.block(
|
||||||
.title("Room Info")
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.title("Room Info")
|
||||||
.style(Style::default().fg(colors[MainInputPosition::RoomInfo as usize])))
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[MainInputPosition::RoomInfo as usize])),
|
||||||
|
)
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
|
|
||||||
// render the widgets
|
// render the widgets
|
||||||
self.terminal.draw(|frame| {
|
self.terminal.draw(|frame| {
|
||||||
frame.render_widget(status_panel, left_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(rooms_panel, left_chunks[1], &mut self.rooms_state);
|
||||||
frame.render_stateful_widget(messages_panel, middle_chunks[0], & mut messages_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(self.message_compose.widget(), middle_chunks[1]);
|
||||||
frame.render_widget(room_info_panel, right_chunks[0]);
|
frame.render_widget(room_info_panel, right_chunks[0]);
|
||||||
})?;
|
})?;
|
||||||
|
|
Reference in New Issue