use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use color_eyre::Result; use ratatui::{ layout::{Alignment, Constraint, Flex, Layout, Rect}, widgets::{Block, Cell, Clear, Row, Table}, text::Text, Frame, }; use tokio::sync::mpsc::UnboundedSender; use super::{Component, PartialComponent, CurrentTab}; use crate::{ action::Action, config::Config, palette::StylePalette, widgets::DotSpinner, }; #[derive(Debug)] pub struct AccountDetails { is_active: bool, action_tx: Option>, palette: StylePalette, name: String, transferable_balance: Option, locked_balance: Option, reserved_balance: Option, total_balance: Option, nonce: Option, } impl Default for AccountDetails { fn default() -> Self { Self::new() } } impl AccountDetails { const TICKER: &str = " CSPR"; const DECIMALS: usize = 6; pub fn new() -> Self { Self { is_active: false, action_tx: None, palette: StylePalette::default(), name: String::new(), transferable_balance: None, locked_balance: None, reserved_balance: None, total_balance: None, nonce: None, } } fn close_popup(&mut self) { self.is_active = false; if let Some(action_tx) = &self.action_tx { let _ = action_tx.send(Action::ClosePopup); } } fn prepare_u128(&self, maybe_value: Option) -> String { match maybe_value { Some(value) => { let value = value as f64 / 10f64.powi(18); let after = Self::DECIMALS; format!("{:.after$}{}", value, Self::TICKER) }, None => format!("{}{}", DotSpinner::default().to_string(), Self::TICKER) } } } impl PartialComponent for AccountDetails { fn set_active(&mut self, current_tab: CurrentTab) { match current_tab { CurrentTab::AccountDetails => self.is_active = true, _ => { if self.is_active { self.is_active = false; self.name = String::new(); self.transferable_balance = None; self.locked_balance = None; self.reserved_balance = None; self.total_balance = None; self.nonce = None; } } }; } } impl Component for AccountDetails { fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { self.action_tx = Some(tx); Ok(()) } fn register_config_handler(&mut self, config: Config) -> Result<()> { if let Some(style) = config.styles.get(&crate::app::Mode::Wallet) { self.palette.with_normal_style(style.get("normal_style").copied()); self.palette.with_normal_border_style(style.get("normal_border_style").copied()); self.palette.with_normal_title_style(style.get("normal_title_style").copied()); self.palette.with_popup_style(style.get("popup_style").copied()); self.palette.with_popup_title_style(style.get("popup_title_style").copied()); } Ok(()) } fn update(&mut self, action: Action) -> Result> { match action { Action::AccountDetailsOf(name, maybe_account_info) => { self.name = name; match maybe_account_info { Some(account_info) => { self.total_balance = Some(account_info.free); self.locked_balance = Some(account_info.frozen); self.reserved_balance = Some(account_info.reserved); self.nonce = Some(account_info.nonce); let transferable = account_info.free .saturating_sub(account_info.reserved) .saturating_sub(account_info.frozen); self.transferable_balance = Some(transferable); }, None => { self.transferable_balance = None; self.locked_balance = None; self.reserved_balance = None; self.total_balance = None; self.nonce = None; } } } _ => {} }; Ok(None) } fn handle_key_event(&mut self, key: KeyEvent) -> Result> { if self.is_active && key.kind == KeyEventKind::Press { match key.code { KeyCode::Esc | KeyCode::Enter => self.close_popup(), _ => {}, }; } Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { if self.is_active { let (border_style, border_type) = self.palette.create_popup_style(); let table = Table::new( [ Row::new(vec![ Cell::from(Text::from("nonce: ".to_string()).alignment(Alignment::Left)), Cell::from(Text::from(self.nonce .map(|n| n.to_string()) .unwrap_or(DotSpinner::default().to_string()) ).alignment(Alignment::Right)), ]), Row::new(vec![ Cell::from(Text::from("total: ".to_string()).alignment(Alignment::Left)), Cell::from(Text::from(self.prepare_u128(self.total_balance)).alignment(Alignment::Right)) ]), Row::new(vec![ Cell::from(Text::from("free: ".to_string()).alignment(Alignment::Left)), Cell::from(Text::from(self.prepare_u128(self.transferable_balance)).alignment(Alignment::Right)) ]), Row::new(vec![ Cell::from(Text::from("locked: ".to_string()).alignment(Alignment::Left)), Cell::from(Text::from(self.prepare_u128(self.locked_balance)).alignment(Alignment::Right)), ]), Row::new(vec![ Cell::from(Text::from("reserved: ".to_string()).alignment(Alignment::Left)), Cell::from(Text::from(self.prepare_u128(self.reserved_balance)).alignment(Alignment::Right)), ]), ], [ Constraint::Max(10), Constraint::Min(14), ] ) .block(Block::bordered() .border_style(border_style) .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style(false)) .title(format!("Details for {}", &self.name))); let v = Layout::vertical([Constraint::Max(7)]).flex(Flex::Center); let h = Layout::horizontal([Constraint::Max(50)]).flex(Flex::Center); let [area] = v.areas(area); let [area] = h.areas(area); frame.render_widget(Clear, area); frame.render_widget(table, area); } Ok(()) } }