From dfc87ff937414e78a5fcce84fe2af7cadb963bb9 Mon Sep 17 00:00:00 2001 From: antifallobst Date: Tue, 4 Jul 2023 18:32:57 +0200 Subject: [PATCH] refactor (architecture): implemented an event based architecture --- Cargo.lock | 1 + Cargo.toml | 1 + src/app/event.rs | 207 ++++++++++++++++++++++++++ src/app/mod.rs | 133 +++++++++++++---- src/app/status.rs | 46 ++++++ src/main.rs | 10 +- src/ui/mod.rs | 365 ++++++++++++++++++++++------------------------ 7 files changed, 534 insertions(+), 229 deletions(-) create mode 100644 src/app/event.rs create mode 100644 src/app/status.rs diff --git a/Cargo.lock b/Cargo.lock index cf1259c..602688b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2651,6 +2651,7 @@ dependencies = [ "matrix-sdk", "serde", "tokio", + "tokio-util", "tui", "tui-textarea", ] diff --git a/Cargo.toml b/Cargo.toml index 143c4b6..392fad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,5 +13,6 @@ crossterm = "*" matrix-sdk = "0.6" 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 diff --git a/src/app/event.rs b/src/app/event.rs new file mode 100644 index 0000000..5ec1752 --- /dev/null +++ b/src/app/event.rs @@ -0,0 +1,207 @@ +use crate::app::{App, status::{Status, State}}; +use crate::ui; +use tokio::time::Duration; +use tokio::sync::{mpsc, broadcast}; +use tokio_util::sync::CancellationToken; +use anyhow::{Result, Error}; +use matrix_sdk::{ + Client, + room::{Room}, + config::SyncSettings, + ruma::events::room::{ + member::StrippedRoomMemberEvent, + message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent}, + }, + event_handler::Ctx +}; + +#[derive(Debug)] +pub enum EventStatus { + Ok, + Finished, + Terminate, +} + +#[derive(Debug)] +pub struct Event { + input_event: Option, +} + +pub struct EventBuilder { + event: Event, +} + +impl Default for Event { + fn default() -> Self { + Self { + input_event: None, + } + } +} + +impl Default for EventBuilder { + fn default() -> Self { + Self { + event: Event::default(), + } + } + +} + +impl EventBuilder { + fn input_event(&mut self, input_event: crossterm::event::Event) -> &Self { + self.event.input_event = Some(input_event); + self + } + + fn build(&self) -> Event { + Event { + input_event: self.event.input_event.clone(), + } + } +} + +impl Event { + pub async fn handle(&self, app: &mut App<'_>) -> Result { + + let status = match app.status.state() { + State::None => EventStatus::Ok, + State::Main => self.handle_main(app).await?, + State::Setup => self.handle_setup(app).await?, + }; + + Ok(status) + } + + 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()) { + tui_textarea::Input { key: tui_textarea::Key::Esc, .. } => return Ok(EventStatus::Terminate), + tui_textarea::Input { + key: tui_textarea::Key::Tab, + .. + } => { + app.ui.cycle_main_input_position(); + } + input => { + match app.ui.input_position() { + ui::MainInputPosition::MessageCompose => { app.ui.message_compose.input(input); }, + _ => (), + } + } + }; + } + Ok(EventStatus::Ok) + } + + async fn handle_setup(&self, app: &mut App<'_>) -> Result { + let ui = match &mut app.ui.setup_ui { + Some(ui) => ui, + None => return Err(Error::msg("SetupUI instance not found")) + }; + + if self.input_event.is_some() { + 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::Tab, + .. + } => { + ui.cycle_input_position(); + }, + tui_textarea::Input { + key: tui_textarea::Key::Enter, + .. + } => { + match ui.input_position() { + ui::SetupInputPosition::Ok => { + let homeserver = ui.homeserver.lines()[0].clone(); + let username = ui.username.lines()[0].clone(); + let password = ui.password_data.lines()[0].clone(); + let login = app.login(&homeserver, &username, &password).await; + if login.is_ok() { + return Ok(EventStatus::Finished); + } + }, + _ => 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); }, + } + }, + _ => (), + } + } + }; + } + Ok(EventStatus::Ok) + } +} + + +async fn poll_input_events_stage_2(channel: mpsc::Sender) -> Result<()> { + loop { + if crossterm::event::poll(Duration::from_millis(100))? { + // It's guaranteed that `read` won't block, because `poll` returned + // `Ok(true)`. + let event = EventBuilder::default() + .input_event(crossterm::event::read()?) + .build(); + + channel.send(event).await?; + } else { + tokio::task::yield_now().await; + } + } +} + +pub async fn poll_input_events(channel: mpsc::Sender, kill: CancellationToken) -> Result<()> { + tokio::select! { + output = poll_input_events_stage_2(channel) => output, + _ = kill.cancelled() => Err(Error::msg("received kill signal")) + } +} + +pub async fn poll_matrix_events_stage_2(channel: mpsc::Sender, app: &App<'_>) -> Result<()> { + app.accounts_manager.client(); + + let client = match app.client(){ + Some(c) => c, + None => return Err(Error::msg("Failed to fetch current client")), + }; + + client.add_event_handler_context(channel.clone()); + client.add_event_handler(on_stripped_state_member); + + Ok(()) +} + +pub async fn poll_matrix_events(channel: mpsc::Sender, kill: CancellationToken, app: &App<'_>) -> Result<()> { + tokio::select! { + output = poll_matrix_events_stage_2(channel, app) => output, + _ = kill.cancelled() => Err(Error::msg("received kill signal")) + } +} + +async fn on_stripped_state_member( + room_member: StrippedRoomMemberEvent, + client: Client, + room: Room, + context: Ctx> +) -> Result<()> { + let event = EventBuilder::default() + .build(); + + context.send(event).await?; + Ok(()) +} \ No newline at end of file diff --git a/src/app/mod.rs b/src/app/mod.rs index f7acb06..474d972 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,28 +1,43 @@ +pub mod event; +pub mod status; + use crate::accounts; +use crate::ui; use std::path::Path; -use matrix_sdk::{Client}; +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}; -pub struct Message { - pub author: String, - pub message: String, + +pub struct App<'a> { + ui: ui::UI<'a>, + accounts_manager: accounts::AccountsManager, + status: Status, + + input_listener_killer: CancellationToken, } -pub struct Room { - pub messages: Vec, +impl Drop for App<'_> { + fn drop(&mut self) { + + } } -pub struct App { - pub accounts_manager: accounts::AccountsManager, - client: Option, - current_room_id: u32, - rooms: Vec, -} - -impl App { +impl App<'_> { pub fn new() -> Self { let path:&std::path::Path = Path::new("userdata/accounts.json"); let config = if path.exists() { @@ -33,27 +48,89 @@ impl App { }; Self { + ui: ui::UI::new(), accounts_manager: AccountsManager::new(config), - client: None, - current_room_id: 0, - rooms: Vec::new(), + status: Status::default(), + input_listener_killer: CancellationToken::new(), } } - pub fn fill_test_data(&mut self) { - let mut room = Room { - messages: Vec::new() - }; + pub async fn run(&mut self) -> Result<()> { + let (channel_tx, mut channel_rx) = mpsc::channel(256); - room.messages.push(Message {author: "someone".to_string(), message: "test".to_string()}); + // Spawn input event listener + tokio::task::spawn(event::poll_input_events(channel_tx.clone(), self.input_listener_killer.clone())); - info!("Filling in test data"); + if self.account().is_err() { + info!("No saved sessions found -> jumping into setup"); + self.setup(&mut channel_rx).await?; + } - self.rooms.push(room); + + loop { + self.status.set_state(State::Main); + self.ui.update(&self.status).await?; + + let event: event::Event = match channel_rx.recv().await { + Some(e) => e, + None => return Err(Error::msg("Event channel has no senders")) + }; + + match event.handle(self).await? { + event::EventStatus::Ok => (), + event::EventStatus::Terminate => break, + _ => (), + }; + } + + self.input_listener_killer.cancel(); + Ok(()) } - pub fn room(&self) -> Option<&Room> { - self.rooms.get(self.current_room_id as usize) + async fn setup(&mut self, receiver: &mut mpsc::Receiver) -> Result<()> { + self.ui.setup_ui = Some(ui::SetupUI::new()); + + loop { + self.status.set_state(State::Setup); + self.ui.update_setup().await?; + + let event: event::Event = match receiver.recv().await { + Some(e) => e, + None => return Err(Error::msg("Event channel has no senders")) + }; + + match event.handle(self).await? { + event::EventStatus::Ok => (), + event::EventStatus::Finished => return Ok(()), + event::EventStatus::Terminate => return Err(Error::msg("Terminated by user")), + } + } + } + + pub async fn init_account(&mut self) -> Result<()> { + let client = match self.client() { + Some(c) => c, + None => return Err(Error::msg("failed to get current client")) + }; + + info!("Initializing client for the current account"); + + Ok(()) + } + + pub async fn switch_account(&mut self, account_id: u32) -> Result<()> { + + Ok(()) + } + + pub async fn login(&mut self, homeserver: &String, username: &String, password: &String) -> Result<()> { + self.accounts_manager.add(homeserver, username, password).await?; + self.init_account().await?; + Ok(()) + } + + pub fn room(&self) -> Option<()> { + None } pub fn account(&self) -> Result<&Account, ()> { @@ -63,4 +140,8 @@ impl App { Some(a) => Ok(a) } } + + pub fn client(&self) -> &Option { + self.accounts_manager.client() + } } \ No newline at end of file diff --git a/src/app/status.rs b/src/app/status.rs new file mode 100644 index 0000000..13d3251 --- /dev/null +++ b/src/app/status.rs @@ -0,0 +1,46 @@ + +pub enum State { + None, + Main, + Setup, +} + +pub struct Status { + state: State, + account_name: String, + account_user_id: String, + current_room_id: u32, +} + +impl Default for Status { + fn default() -> Self { + Self { + state: State::None, + account_name: "".to_string(), + account_user_id: "".to_string(), + current_room_id: 0, + } + } +} + +impl Status { + pub fn account_name(&self) -> &String { + &self.account_name + } + + pub fn account_user_id(&self) -> &String { + &self.account_user_id + } + + pub fn room(&self) -> Option<()> { + None + } + + pub fn state(&self) -> &State { + &self.state + } + + pub fn set_state(&mut self, state: State) { + self.state = state; + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dbea59f..24ccbfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,15 +9,7 @@ async fn main() -> anyhow::Result<()> { cli_log::init_cli_log!(); let mut app = app::App::new(); - app.fill_test_data(); - - let mut ui = ui::UI::new(); - if app.accounts_manager.num_accounts() == 0 { - info!("No saved sessions found -> jumping into setup"); - ui.setup(&mut app).await?; - } - - ui.main(&mut app).await?; + app.run().await?; Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 975d625..73e56dd 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,4 @@ -use crate::app::{App}; +use crate::app::status::Status; use std::cmp; use crossterm::{ @@ -16,11 +16,10 @@ use tui::style::{Color, Modifier, Style}; use tui::text::{Spans, Span, Text}; use tui::widgets::{Paragraph, Wrap}; use tui_textarea::{Input, Key, TextArea}; -use tui_textarea::Key::Char; use cli_log::{error, warn, info}; #[derive(Clone, Copy)] -enum SetupInputPosition { +pub enum SetupInputPosition { Homeserver, Username, Password, @@ -28,7 +27,7 @@ enum SetupInputPosition { } #[derive(Clone, Copy)] -enum MainInputPosition { +pub enum MainInputPosition { Status, Rooms, Messages, @@ -36,11 +35,21 @@ enum MainInputPosition { RoomInfo } +pub struct SetupUI<'a> { + input_position: SetupInputPosition, + + pub homeserver: TextArea<'a>, + pub username: TextArea<'a>, + pub password: TextArea<'a>, + pub password_data: TextArea<'a>, +} + pub struct UI<'a> { terminal: Terminal>, - input_position: MainInputPosition, - message_compose: TextArea<'a>, + pub message_compose: TextArea<'a>, + + pub setup_ui: Option>, } @@ -52,7 +61,7 @@ fn terminal_prepare() -> Result { Ok(stdout) } -fn textarea_activate(textarea: &mut TextArea) { +pub fn textarea_activate(textarea: &mut TextArea) { textarea.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED)); textarea.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); let b = textarea @@ -62,7 +71,7 @@ fn textarea_activate(textarea: &mut TextArea) { textarea.set_block(b.style(Style::default())); } -fn textarea_inactivate(textarea: &mut TextArea) { +pub fn textarea_inactivate(textarea: &mut TextArea) { textarea.set_cursor_line_style(Style::default()); textarea.set_cursor_style(Style::default()); let b = textarea @@ -74,16 +83,6 @@ fn textarea_inactivate(textarea: &mut TextArea) { ); } -fn cycle_main_input_position(input_position: &MainInputPosition) -> MainInputPosition { - match input_position { - MainInputPosition::Status => MainInputPosition::Rooms, - MainInputPosition::Rooms => MainInputPosition::Messages, - MainInputPosition::Messages => MainInputPosition::MessageCompose, - MainInputPosition::MessageCompose => MainInputPosition::RoomInfo, - MainInputPosition::RoomInfo => MainInputPosition::Status, - } -} - impl Drop for UI<'_> { fn drop(&mut self) { info!("Destructing UI"); @@ -97,6 +96,122 @@ impl Drop for UI<'_> { } } +impl SetupUI<'_> { + pub fn new() -> Self { + let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]); + let mut username = TextArea::default(); + let mut password = TextArea::default(); + let mut password_data = TextArea::default(); + + homeserver.set_block( + Block::default() + .title("Homeserver") + .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_inactivate(&mut username); + textarea_inactivate(&mut password); + + Self { + input_position: SetupInputPosition::Homeserver, + homeserver, + username, + password, + password_data, + } + } + + pub fn cycle_input_position(&mut self) { + self.input_position = match self.input_position { + SetupInputPosition::Homeserver => { + textarea_inactivate(&mut self.homeserver); + textarea_activate(&mut self.username); + textarea_inactivate(&mut self.password); + SetupInputPosition::Username + }, + SetupInputPosition::Username => { + textarea_inactivate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_activate(&mut self.password); + SetupInputPosition::Password + }, + SetupInputPosition::Password => { + textarea_inactivate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_inactivate(&mut self.password); + SetupInputPosition::Ok + }, + SetupInputPosition::Ok => { + textarea_activate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_inactivate(&mut self.password); + SetupInputPosition::Homeserver + }, + }; + } + + pub fn input_position(&self) -> &SetupInputPosition { &self.input_position } + + pub async fn update(&'_ mut self, terminal: &mut Terminal>) -> Result<()> { + let mut strings: Vec = Vec::new(); + strings.resize(3, "".to_string()); + + let content_ok = match self.input_position { + SetupInputPosition:: Ok => Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)), + _ => Span::styled("OK", Style::default().fg(Color::DarkGray)), + }; + + let block = Block::default() + .title("Login") + .borders(Borders::ALL); + + let mut ok = Paragraph::new(content_ok) + .alignment(Alignment::Center); + + + // define a 32 * 6 chunk in the middle of the screen + let mut chunk = terminal.size()?; + chunk.x = (chunk.width / 2) - 16; + chunk.y = (chunk.height / 2) - 5; + chunk.height = 12; + chunk.width = 32; + + let mut split_chunk = chunk.clone(); + split_chunk.x += 1; + split_chunk.y += 1; + split_chunk.height -= 1; + split_chunk.width -= 2; + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), // 0. Homserver: + Constraint::Length(3), // 1. Username: + Constraint::Length(3), // 2. Password: + Constraint::Length(1) // 3. OK + ].as_ref()) + .split(split_chunk); + + terminal.draw(|frame| { + frame.render_widget(block.clone(), chunk); + frame.render_widget(self.homeserver.widget(), chunks[0]); + frame.render_widget(self.username.widget(), chunks[1]); + frame.render_widget(self.password.widget(), chunks[2]); + frame.render_widget(ok.clone(), chunks[3]); + })?; + + Ok(()) + } +} + impl UI<'_> { pub fn new() -> Self { let stdout = terminal_prepare().expect("failed to prepare terminal"); @@ -117,10 +232,23 @@ impl UI<'_> { terminal, input_position: MainInputPosition::Rooms, message_compose, + setup_ui: None } } - pub fn main_update(&'_ mut self, app: &App) -> Result<()> { + pub fn cycle_main_input_position(&mut self) { + self.input_position = match self.input_position { + MainInputPosition::Status => MainInputPosition::Rooms, + MainInputPosition::Rooms => MainInputPosition::Messages, + MainInputPosition::Messages => MainInputPosition::MessageCompose, + MainInputPosition::MessageCompose => MainInputPosition::RoomInfo, + MainInputPosition::RoomInfo => MainInputPosition::Status, + }; + } + + pub fn input_position(&self) -> &MainInputPosition { &self.input_position } + + pub async fn update(&'_ mut self, status: &Status) -> Result<()> { // TODO: make thread safe let chunk = self.terminal.size()?; @@ -145,30 +273,27 @@ impl UI<'_> { .constraints([Constraint::Min(4)].as_ref()) .split(main_chunks[2]); - // collect data to render - let account = app.accounts_manager.current().expect("failed to resolve current account"); - - let mut status_content = Text::styled(account.name(), Style::default().add_modifier(Modifier::BOLD)); - status_content.extend(Text::styled(account.user_id(), Style::default())); + 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("settings", Style::default().fg(Color::LightMagenta).add_modifier(Modifier::ITALIC | Modifier::UNDERLINED))); - let messages_content = match app.room() { - None => { + let messages_content = match status.room() { + _ => { vec![Spans::from(Span::styled("No room selected!", Style::default().fg(Color::Magenta)))] }, - Some(r) => { - r.messages - .iter() - .rev() - .map(|msg| { - Spans::from(vec![ - Span::styled(&msg.author, Style::default().fg(Color::Cyan)), - Span::styled(": ", Style::default().fg(Color::Cyan)), - Span::styled(&msg.message, Style::default().fg(Color::White)), - ]) - }) - .collect::>() - }, + // Some(r) => { + // r.messages + // .iter() + // .rev() + // .map(|msg| { + // Spans::from(vec![ + // Span::styled(&msg.author, Style::default().fg(Color::Cyan)), + // Span::styled(": ", Style::default().fg(Color::Cyan)), + // Span::styled(&msg.message, Style::default().fg(Color::White)), + // ]) + // }) + // .collect::>() + // }, }; // calculate to widgets colors, based of which widget is currently selected @@ -234,162 +359,14 @@ impl UI<'_> { Ok(()) } - pub async fn main(&mut self, app: &mut App) -> Result<()> { - info!("Starting main UI"); + pub async fn update_setup(&mut self) -> Result<()> { + let ui = match &mut self.setup_ui { + Some(c) => c, + None => return Err(Error::msg("SetupUI instance not found")), + }; - textarea_activate(&mut self.message_compose); + ui.update(&mut self.terminal).await?; - self.terminal.clear().expect("failed to clear screen"); - - loop { - self.main_update(app)?; - - match Input::from(read()?.clone()) { - Input { key: Key::Esc, .. } => return Ok(()), - Input { - key: Key::Tab, - .. - } => { - self.input_position = cycle_main_input_position(&self.input_position); - } - input => { - match self.input_position { - MainInputPosition::MessageCompose => { self.message_compose.input(input); }, - _ => continue, - } - } - }; - } - } - - pub async fn setup(&mut self, app: &mut App) -> Result<()> { - info!("Starting setup UI"); - - let mut input_index = SetupInputPosition::Homeserver; - let mut strings: Vec = Vec::new(); - strings.resize(3, "".to_string()); - - let content_ok_active = Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)); - let content_ok_inactive = Span::styled("OK", Style::default().fg(Color::DarkGray)); - - let block = Block::default() - .title("Login") - .borders(Borders::ALL); - - let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]); - let mut username = TextArea::default(); - let mut password = TextArea::default(); - let mut password_data = TextArea::default(); - - - homeserver.set_block( - Block::default() - .title("Homeserver") - .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_inactivate(&mut username); - textarea_inactivate(&mut password); - - - loop { - let mut ok = Paragraph::new(match input_index { - SetupInputPosition::Ok => content_ok_active.clone(), - _ => content_ok_inactive.clone(), - }).alignment(Alignment::Center); - - // define a 32 * 6 chunk in the middle of the screen - let mut chunk = self.terminal.size()?; - chunk.x = (chunk.width / 2) - 16; - chunk.y = (chunk.height / 2) - 5; - chunk.height = 12; - chunk.width = 32; - - let mut split_chunk = chunk.clone(); - split_chunk.x += 1; - split_chunk.y += 1; - split_chunk.height -= 1; - split_chunk.width -= 2; - - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), // 0. Homserver: - Constraint::Length(3), // 1. Username: - Constraint::Length(3), // 2. Password: - Constraint::Length(1) // 3. OK - ].as_ref()) - .split(split_chunk); - - self.terminal.draw(|frame| { - - frame.render_widget(block.clone(), chunk); - frame.render_widget(homeserver.widget(), chunks[0]); - frame.render_widget(username.widget(), chunks[1]); - frame.render_widget(password.widget(), chunks[2]); - frame.render_widget(ok.clone(), chunks[3]); - })?; - - match Input::from(read()?.clone()) { - Input { key: Key::Esc, .. } => return Err(Error::msg("Login cancelled by user")), - Input { - key: Key::Enter, - .. - } => { - input_index = match input_index { - SetupInputPosition::Homeserver => { - textarea_inactivate(&mut homeserver); - textarea_activate(&mut username); - textarea_inactivate(&mut password); - SetupInputPosition::Username - }, - SetupInputPosition::Username => { - textarea_inactivate(&mut homeserver); - textarea_inactivate(&mut username); - textarea_activate(&mut password); - SetupInputPosition::Password - }, - SetupInputPosition::Password => { - textarea_inactivate(&mut homeserver); - textarea_inactivate(&mut username); - textarea_inactivate(&mut password); - SetupInputPosition::Ok - }, - SetupInputPosition::Ok => { - if app.accounts_manager.add(&homeserver.lines()[0], &username.lines()[0], &password_data.lines()[0]).await.is_ok() { - return Ok(()) - } - - textarea_activate(&mut homeserver); - textarea_inactivate(&mut username); - textarea_inactivate(&mut password); - SetupInputPosition::Homeserver - }, - } - } - input => { - match input_index { - SetupInputPosition::Homeserver => { homeserver.input(input); }, - SetupInputPosition::Username => { username.input(input); }, - SetupInputPosition::Password => { - password_data.input(input.clone()); - match input.key { - Char(_) => { password.input(Input { key: Char('*'), ctrl: false, alt: false }); }, - _ => { password.input(input); }, - } - }, - _ => continue, - } - } - }; - } + Ok(()) } }