additional pop-up for balance detalization in address book

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2025-02-02 16:41:08 +03:00
parent 9b1136e4ca
commit 853bdcd76e
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
5 changed files with 264 additions and 24 deletions

View File

@ -45,6 +45,7 @@ pub enum Action {
UpdateAddressBookRecord(String),
UpdateKnownValidator(String),
TransferTo(String),
AccountDetailsOf(String, Option<SystemAccount>),
TransferBalance(String, [u8; 32], u128),
EventLog(String, ActionLevel, ActionTarget),

View File

@ -231,6 +231,19 @@ impl AddressBook {
}
}
fn show_account_details(&mut self) {
if let Some(index) = self.table_state.selected() {
if let Some(action_tx) = &self.action_tx {
let _ = action_tx.send(Action::AccountDetailsOf(
self.address_book[index].name.clone(),
self.balances
.get(&self.address_book[index].account_id)
.map(|data| data.clone()),
));
}
}
}
fn swap_up(&mut self) {
if let Some(src_index) = self.table_state.selected() {
let dst_index = src_index.saturating_sub(1);
@ -390,6 +403,7 @@ impl Component for AddressBook {
KeyCode::Char('J') => self.swap_down(),
KeyCode::Char('D') => self.delete_row(),
KeyCode::Char('R') => self.update_address_book_record(),
KeyCode::Char('I') => self.show_account_details(),
KeyCode::Enter => self.send_transfer_to(),
_ => {},
};
@ -407,11 +421,7 @@ impl Component for AddressBook {
.map(|info| {
let balance = self.balances
.get(&info.account_id)
.map(|inner_balance| {
inner_balance.free
.saturating_add(inner_balance.reserved)
.saturating_add(inner_balance.frozen)
});
.map(|inner_balance| inner_balance.free);
Row::new(vec![
Cell::from(Text::from(info.name.clone()).alignment(Alignment::Left)),
Cell::from(Text::from(info.address.clone()).alignment(Alignment::Center)),

View File

@ -20,7 +20,7 @@ pub struct Balance {
total_balance: Option<u128>,
transferable_balance: Option<u128>,
locked_balance: Option<u128>,
bonded_balance: Option<u128>,
reserved_balance: Option<u128>,
nonce: Option<u32>,
palette: StylePalette
}
@ -41,7 +41,7 @@ impl Balance {
total_balance: None,
transferable_balance: None,
locked_balance: None,
bonded_balance: None,
reserved_balance: None,
nonce: None,
palette: StylePalette::default(),
}
@ -87,19 +87,19 @@ impl Component for Balance {
match maybe_balance {
Some(balance) => {
self.total_balance = Some(balance.free);
self.locked_balance = Some(balance.reserved);
self.bonded_balance = Some(balance.frozen);
self.locked_balance = Some(balance.frozen);
self.reserved_balance = Some(balance.reserved);
self.nonce = Some(balance.nonce);
let transferable = balance.free
.saturating_add(balance.reserved)
.saturating_add(balance.frozen);
.saturating_sub(balance.reserved)
.saturating_sub(balance.frozen);
self.transferable_balance = Some(transferable);
},
None => {
self.transferable_balance = None;
self.locked_balance = None;
self.bonded_balance = None;
self.reserved_balance = None;
self.total_balance = None;
self.nonce = None;
}
@ -137,8 +137,8 @@ impl Component for Balance {
Cell::from(Text::from(self.prepare_u128(self.locked_balance)).alignment(Alignment::Right)),
]),
Row::new(vec![
Cell::from(Text::from("bonded: ".to_string()).alignment(Alignment::Left)),
Cell::from(Text::from(self.prepare_u128(self.bonded_balance)).alignment(Alignment::Right)),
Cell::from(Text::from("reserved: ".to_string()).alignment(Alignment::Left)),
Cell::from(Text::from(self.prepare_u128(self.reserved_balance)).alignment(Alignment::Right)),
]),
],
[

View File

@ -0,0 +1,202 @@
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<UnboundedSender<Action>>,
palette: StylePalette,
name: String,
transferable_balance: Option<u128>,
locked_balance: Option<u128>,
reserved_balance: Option<u128>,
total_balance: Option<u128>,
nonce: Option<u32>,
}
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<u128>) -> 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<Action>) -> 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<Option<Action>> {
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<Option<Action>> {
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(())
}
}

View File

@ -18,6 +18,7 @@ mod accounts;
mod overview;
mod add_address_book_record;
mod rename_address_book_record;
mod details;
use balance::Balance;
use transfer::Transfer;
@ -29,11 +30,12 @@ use accounts::Accounts;
use overview::Overview;
use add_address_book_record::AddAddressBookRecord;
use rename_address_book_record::RenameAddressBookRecord;
use details::AccountDetails;
use super::Component;
use crate::{action::Action, app::Mode, config::Config};
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CurrentTab {
Nothing,
Accounts,
@ -44,6 +46,7 @@ pub enum CurrentTab {
RenameAccount,
RenameAddressBookRecord,
Transfer,
AccountDetails,
}
pub trait PartialComponent: Component {
@ -53,6 +56,7 @@ pub trait PartialComponent: Component {
pub struct Wallet {
is_active: bool,
current_tab: CurrentTab,
previous_tab: CurrentTab,
components: Vec<Box<dyn PartialComponent>>,
}
@ -61,6 +65,7 @@ impl Default for Wallet {
Self {
is_active: false,
current_tab: CurrentTab::Accounts,
previous_tab: CurrentTab::Accounts,
components: vec![
Box::new(Overview::default()),
Box::new(Accounts::default()),
@ -72,6 +77,7 @@ impl Default for Wallet {
Box::new(AddAddressBookRecord::default()),
Box::new(RenameAddressBookRecord::default()),
Box::new(Transfer::default()),
Box::new(AccountDetails::default()),
],
}
}
@ -127,9 +133,10 @@ impl Component for Wallet {
CurrentTab::RenameAccount |
CurrentTab::RenameAddressBookRecord |
CurrentTab::Transfer |
CurrentTab::AccountDetails |
CurrentTab::AddAddressBookRecord => match key.code {
KeyCode::Esc => {
self.current_tab = CurrentTab::Accounts;
self.current_tab = self.previous_tab;
for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone());
}
@ -150,18 +157,21 @@ impl Component for Wallet {
return Ok(Some(Action::SetActiveScreen(Mode::Menu)));
},
KeyCode::Char('W') => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::AddAccount;
for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone());
}
},
KeyCode::Char('A') => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::AddAddressBookRecord;
for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone());
}
},
KeyCode::Char('T') => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::Transfer;
for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone());
@ -194,15 +204,32 @@ impl Component for Wallet {
Action::SetActiveScreen(Mode::Wallet) => {
self.is_active = true;
self.current_tab = CurrentTab::Accounts;
self.previous_tab = CurrentTab::Accounts;
},
Action::UpdateAccountName(_) | Action::NewAccount(_) => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::Accounts;
},
Action::UpdateAddressBookRecord(_) | Action::NewAddressBookRecord(_, _) | Action::ClosePopup => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::AddressBook;
},
Action::RenameAccount(_) => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::RenameAccount;
},
Action::RenameAddressBookRecord(_) => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::RenameAddressBookRecord;
}
Action::TransferTo(_) => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::Transfer;
},
Action::AccountDetailsOf(_, _) => {
self.previous_tab = self.current_tab;
self.current_tab = CurrentTab::AccountDetails;
},
Action::UpdateAccountName(_) | Action::NewAccount(_) =>
self.current_tab = CurrentTab::Accounts,
Action::UpdateAddressBookRecord(_) | Action::NewAddressBookRecord(_, _) | Action::ClosePopup =>
self.current_tab = CurrentTab::AddressBook,
Action::RenameAccount(_) => self.current_tab = CurrentTab::RenameAccount,
Action::RenameAddressBookRecord(_) =>
self.current_tab = CurrentTab::RenameAddressBookRecord,
Action::TransferTo(_) => self.current_tab = CurrentTab::Transfer,
_ => {}
}
for component in self.components.iter_mut() {