use color_eyre::Result; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ layout::{Constraint, Layout, Rect}, Frame, }; mod balance; mod transfer; mod address_book; mod add_account; mod rename_account; mod event_logs; mod accounts; mod overview; mod add_address_book_record; mod rename_address_book_record; use balance::Balance; use tokio::sync::mpsc::UnboundedSender; use transfer::Transfer; use address_book::AddressBook; use add_account::AddAccount; use rename_account::RenameAccount; use event_logs::EventLogs; use accounts::Accounts; use overview::Overview; use add_address_book_record::AddAddressBookRecord; use rename_address_book_record::RenameAddressBookRecord; use super::Component; use crate::{action::Action, app::Mode, config::Config}; #[derive(Debug, Clone, PartialEq)] pub enum CurrentTab { Nothing, Accounts, AddressBook, EventLogs, AddAccount, AddAddressBookRecord, RenameAccount, RenameAddressBookRecord, } pub trait PartialComponent: Component { fn set_active(&mut self, current_tab: CurrentTab); } pub struct Wallet { is_active: bool, current_tab: CurrentTab, components: Vec>, } impl Default for Wallet { fn default() -> Self { Self { is_active: false, current_tab: CurrentTab::Accounts, components: vec![ Box::new(Overview::default()), Box::new(Accounts::default()), Box::new(Balance::default()), Box::new(AddressBook::default()), Box::new(EventLogs::default()), Box::new(Transfer::default()), Box::new(AddAccount::default()), Box::new(RenameAccount::default()), Box::new(AddAddressBookRecord::default()), Box::new(RenameAddressBookRecord::default()), ], } } } impl Wallet { fn move_left(&mut self) { match self.current_tab { CurrentTab::EventLogs => self.current_tab = CurrentTab::AddressBook, CurrentTab::AddressBook => self.current_tab = CurrentTab::Accounts, _ => {} } } fn move_right(&mut self) { match self.current_tab { CurrentTab::Nothing => self.current_tab = CurrentTab::Accounts, CurrentTab::Accounts => self.current_tab = CurrentTab::AddressBook, CurrentTab::AddressBook => self.current_tab = CurrentTab::EventLogs, _ => {} } } } impl Component for Wallet { fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { for component in self.components.iter_mut() { component.register_action_handler(tx.clone())?; } Ok(()) } fn register_config_handler(&mut self, config: Config) -> Result<()> { for component in self.components.iter_mut() { component.register_config_handler(config.clone())?; } Ok(()) } fn handle_key_event(&mut self, key: KeyEvent) -> Result> { if !self.is_active { return Ok(None) } match self.current_tab { // block the default key handle for popups CurrentTab::AddAccount | CurrentTab::RenameAccount | CurrentTab::RenameAddressBookRecord | CurrentTab::AddAddressBookRecord => match key.code { KeyCode::Esc => { self.current_tab = CurrentTab::Accounts; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, _ => { for component in self.components.iter_mut() { component.handle_key_event(key)?; } } }, _ => match key.code { KeyCode::Esc => { self.is_active = false; self.current_tab = CurrentTab::Nothing; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } return Ok(Some(Action::SetActiveScreen(Mode::Menu))); }, KeyCode::Char('W') => { self.current_tab = CurrentTab::AddAccount; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, KeyCode::Char('A') => { self.current_tab = CurrentTab::AddAddressBookRecord; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => { self.move_right(); for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, KeyCode::Char('h') | KeyCode::Left => { self.move_left(); for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, _ => { for component in self.components.iter_mut() { component.handle_key_event(key)?; } }, } } Ok(None) } fn update(&mut self, action: Action) -> Result> { match action { Action::SetActiveScreen(Mode::Wallet) => self.is_active = true, Action::UpdateAccountName(_) | Action::NewAccount(_) => self.current_tab = CurrentTab::Accounts, Action::UpdateAddressBookRecord(_) | Action::NewAddressBookRecord(_, _) => self.current_tab = CurrentTab::AddressBook, Action::RenameAccount(_) => self.current_tab = CurrentTab::RenameAccount, Action::RenameAddressBookRecord(_) => self.current_tab = CurrentTab::RenameAddressBookRecord, _ => {} } for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); component.update(action.clone())?; } Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { let screen = super::screen_layout(area); for component in self.components.iter_mut() { component.draw(frame, screen)?; } Ok(()) } } pub fn wallet_layout(area: Rect) -> [Rect; 2] { Layout::vertical([ Constraint::Percentage(75), Constraint::Percentage(25), ]).areas(area) } pub fn bars_layout(area: Rect) -> [Rect; 2] { let [place, _] = wallet_layout(area); Layout::horizontal([ Constraint::Percentage(50), Constraint::Percentage(50), ]).areas(place) } pub fn account_layout(area: Rect) -> [Rect; 3] { let [place, _] = bars_layout(area); Layout::vertical([ Constraint::Max(4), Constraint::Min(0), Constraint::Max(6), ]).areas(place) }