From 30d905a614f210af20ee20a80b923acba99c86a5 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Fri, 11 Feb 2022 10:43:46 +0200 Subject: [PATCH] Updated console wallet TUI to better accommodate the expanding number of tabs: - Moved base node status to the bottom of the screen, expanding tab selection to the entire width. - Regained 1 line of real estate screen space at the bottom of the screen (with menu). - Refined some help text to fit on the screen. - Added a separate contacts tab to enable better handling of contacts not needing to access it via the transaction send tab. - Improved column spacing on the contacts tab/pane. - Improved the recieve tab layout (moved connection details above the QR code). - Removed contacts editing ability from the sent tab; only selection remains. --- .../tari_console_wallet/src/ui/app.rs | 23 +- .../src/ui/components/base_node.rs | 74 +++- .../src/ui/components/contacts_tab.rs | 371 ++++++++++++++++++ .../src/ui/components/mod.rs | 1 + .../src/ui/components/network_tab.rs | 2 +- .../src/ui/components/receive_tab.rs | 120 +++--- .../src/ui/components/send_tab.rs | 239 +---------- .../src/ui/components/transactions_tab.rs | 12 +- base_layer/common_types/src/transaction.rs | 16 +- 9 files changed, 533 insertions(+), 325 deletions(-) create mode 100644 applications/tari_console_wallet/src/ui/components/contacts_tab.rs diff --git a/applications/tari_console_wallet/src/ui/app.rs b/applications/tari_console_wallet/src/ui/app.rs index 145ce992e43..a67078909bf 100644 --- a/applications/tari_console_wallet/src/ui/app.rs +++ b/applications/tari_console_wallet/src/ui/app.rs @@ -36,6 +36,7 @@ use crate::{ components::{ assets_tab::AssetsTab, base_node::BaseNode, + contacts_tab::ContactsTab, events_component::EventsComponent, log_tab::LogTab, menu::Menu, @@ -91,6 +92,7 @@ impl App { .add("Transactions".into(), Box::new(TransactionsTab::new())) .add("Send".into(), Box::new(SendTab::new(&app_state))) .add("Receive".into(), Box::new(ReceiveTab::new())) + .add("Contacts".into(), Box::new(ContactsTab::new())) .add("Network".into(), Box::new(NetworkTab::new(base_node_selected))) .add("Assets".into(), Box::new(AssetsTab::new())) .add("Tokens".into(), Box::new(TokensComponent::new())) @@ -173,17 +175,20 @@ impl App { .constraints([Constraint::Length(MAX_WIDTH), Constraint::Min(0)].as_ref()) .split(f.size()); let title_chunks = Layout::default() - .constraints([Constraint::Length(3), Constraint::Min(0), Constraint::Length(2)].as_ref()) + .constraints( + [ + Constraint::Length(3), + Constraint::Min(0), + Constraint::Length(2), + Constraint::Length(1), + ] + .as_ref(), + ) .split(max_width_layout[0]); - let title_halves = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(65), Constraint::Percentage(35)].as_ref()) - .split(title_chunks[0]); - - self.tabs.draw_titles(f, title_halves[0], &self.app_state); - self.base_node_status.draw(f, title_halves[1], &self.app_state); + self.tabs.draw_titles(f, title_chunks[0], &self.app_state); self.tabs.draw_content(f, title_chunks[1], &mut self.app_state); - self.menu.draw(f, title_chunks[2], &self.app_state); + self.base_node_status.draw(f, title_chunks[2], &self.app_state); + self.menu.draw(f, title_chunks[3], &self.app_state); } } diff --git a/applications/tari_console_wallet/src/ui/components/base_node.rs b/applications/tari_console_wallet/src/ui/components/base_node.rs index bfe2bd1c799..118f7064559 100644 --- a/applications/tari_console_wallet/src/ui/components/base_node.rs +++ b/applications/tari_console_wallet/src/ui/components/base_node.rs @@ -23,14 +23,14 @@ use tari_wallet::connectivity_service::{OnlineStatus, WalletConnectivityInterface}; use tui::{ backend::Backend, - layout::Rect, - style::{Color, Modifier, Style}, + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Style}, text::{Span, Spans}, widgets::{Block, Borders, Paragraph}, Frame, }; -use crate::ui::{components::Component, state::AppState}; +use crate::ui::{components::Component, state::AppState, MAX_WIDTH}; pub struct BaseNode {} @@ -43,9 +43,13 @@ impl BaseNode { impl Component for BaseNode { fn draw(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) where B: Backend { - let base_node_state = app_state.get_base_node_state(); - let current_online_status = app_state.get_wallet_connectivity().get_connectivity_status(); + let title = Spans::from(vec![Span::styled( + " Base Node Status - ", + Style::default().fg(Color::White), + )]); + let current_online_status = app_state.get_wallet_connectivity().get_connectivity_status(); + let mut base_node_id_color = Color::White; let chain_info = match current_online_status { OnlineStatus::Connecting => Spans::from(vec![ Span::styled("Chain Tip:", Style::default().fg(Color::Magenta)), @@ -58,14 +62,27 @@ impl Component for BaseNode { Span::styled("Offline", Style::default().fg(Color::Red)), ]), OnlineStatus::Online => { + let base_node_state = app_state.get_base_node_state(); if let Some(ref metadata) = base_node_state.chain_metadata { let tip = metadata.height_of_longest_chain(); let synced = base_node_state.is_synced.unwrap_or_default(); let (tip_color, sync_text) = if synced { - (Color::Green, "Synced.") + ( + { + base_node_id_color = Color::Green; + base_node_id_color + }, + "Synced.", + ) } else { - (Color::Yellow, "Syncing...") + ( + { + base_node_id_color = Color::Yellow; + base_node_id_color + }, + "Syncing...", + ) }; let latency = base_node_state.latency.unwrap_or_default().as_millis(); @@ -100,11 +117,42 @@ impl Component for BaseNode { }, }; - let chain_metadata_paragraph = - Paragraph::new(chain_info).block(Block::default().borders(Borders::ALL).title(Span::styled( - "Base Node Status:", - Style::default().fg(Color::White).add_modifier(Modifier::BOLD), - ))); - f.render_widget(chain_metadata_paragraph, area); + let base_node_id = Spans::from(vec![ + Span::styled(" Connected Base Node ID: ", Style::default().fg(Color::Magenta)), + Span::styled( + format!("{}", app_state.get_selected_base_node().node_id.clone()), + Style::default().fg(base_node_id_color), + ), + Span::styled(" ", Style::default().fg(Color::White)), + ]); + + let chunks = Layout::default() + .constraints([Constraint::Length(1), Constraint::Length(1)].as_ref()) + .split(area); + + let columns = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Ratio(title.width() as u32, MAX_WIDTH as u32), + Constraint::Ratio( + MAX_WIDTH.saturating_sub((title.width() + base_node_id.width()) as u16) as u32, + MAX_WIDTH as u32, + ), + Constraint::Ratio(base_node_id.width() as u32, MAX_WIDTH as u32), + ] + .as_ref(), + ) + .split(chunks[0]); + + let paragraph = Paragraph::new(title).block(Block::default()); + f.render_widget(paragraph, columns[0]); + let paragraph = Paragraph::new(chain_info).block(Block::default()); + f.render_widget(paragraph, columns[1]); + let paragraph = Paragraph::new(base_node_id).block(Block::default()); + f.render_widget(paragraph, columns[2]); + + let divider = Block::default().borders(Borders::BOTTOM); + f.render_widget(divider, chunks[1]); } } diff --git a/applications/tari_console_wallet/src/ui/components/contacts_tab.rs b/applications/tari_console_wallet/src/ui/components/contacts_tab.rs new file mode 100644 index 00000000000..169388104c8 --- /dev/null +++ b/applications/tari_console_wallet/src/ui/components/contacts_tab.rs @@ -0,0 +1,371 @@ +use tokio::runtime::Handle; +use tui::{ + backend::Backend, + layout::{Constraint, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Span, Spans}, + widgets::{Block, Borders, Clear, ListItem, Paragraph, Wrap}, + Frame, +}; +use unicode_width::UnicodeWidthStr; + +use crate::{ + ui::{ + components::{Component, KeyHandled}, + state::AppState, + widgets::{centered_rect_absolute, draw_dialog, MultiColumnList, WindowedListState}, + MAX_WIDTH, + }, + utils::formatting::display_compressed_string, +}; + +pub struct ContactsTab { + edit_contact_mode: ContactInputMode, + show_edit_contact: bool, + alias_field: String, + public_key_field: String, + error_message: Option, + contacts_list_state: WindowedListState, + confirmation_dialog: Option, +} + +impl ContactsTab { + pub fn new() -> Self { + Self { + edit_contact_mode: ContactInputMode::None, + show_edit_contact: false, + alias_field: String::new(), + public_key_field: String::new(), + error_message: None, + contacts_list_state: WindowedListState::new(), + confirmation_dialog: None, + } + } + + fn draw_contacts(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) + where B: Backend { + let block = Block::default().borders(Borders::ALL).title(Span::styled( + "Contacts", + Style::default().fg(Color::White).add_modifier(Modifier::BOLD), + )); + f.render_widget(block, area); + let list_areas = Layout::default() + .constraints([Constraint::Length(1), Constraint::Min(42)].as_ref()) + .margin(1) + .split(area); + + let instructions = Paragraph::new(Spans::from(vec![ + Span::raw("Use "), + Span::styled("Up↑/Down↓ Keys", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to select a contact, "), + Span::styled("E", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" or "), + Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to (e)dit a contact, "), + Span::styled("D", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to (d)elete a contact and "), + Span::styled("N", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to create a (n)ew contact."), + ])) + .wrap(Wrap { trim: true }); + f.render_widget(instructions, list_areas[0]); + self.contacts_list_state.set_num_items(app_state.get_contacts().len()); + let mut list_state = self + .contacts_list_state + .get_list_state((list_areas[1].height as usize).saturating_sub(3)); + let window = self.contacts_list_state.get_start_end(); + let windowed_view = app_state.get_contacts_slice(window.0, window.1); + + let mut column0_items = Vec::new(); + let mut column1_items = Vec::new(); + let mut column2_items = Vec::new(); + for c in windowed_view.iter() { + column0_items.push(ListItem::new(Span::raw(c.alias.clone()))); + column1_items.push(ListItem::new(Span::raw(c.public_key.to_string()))); + column2_items.push(ListItem::new(Span::raw(display_compressed_string( + c.emoji_id.clone(), + 3, + 3, + )))); + } + let column_list = MultiColumnList::new() + .highlight_style(Style::default().add_modifier(Modifier::BOLD).fg(Color::Magenta)) + .heading_style(Style::default().fg(Color::Magenta)) + .max_width(MAX_WIDTH) + .add_column(Some("Alias"), Some(25), column0_items) + .add_column(None, Some(2), Vec::new()) + .add_column(Some("Public Key"), Some(64), column1_items) + .add_column(None, Some(2), Vec::new()) + .add_column(Some("Emoji ID"), None, column2_items); + column_list.render(f, list_areas[1], &mut list_state); + } + + fn draw_edit_contact(&mut self, f: &mut Frame, area: Rect, _app_state: &AppState) + where B: Backend { + let popup_area = centered_rect_absolute(120, 10, area); + + f.render_widget(Clear, popup_area); + + let block = Block::default().borders(Borders::ALL).title(Span::styled( + "Add/Edit Contact", + Style::default().fg(Color::White).add_modifier(Modifier::BOLD), + )); + f.render_widget(block, popup_area); + let vert_chunks = Layout::default() + .constraints([Constraint::Length(2), Constraint::Length(3), Constraint::Length(3)].as_ref()) + .margin(1) + .split(popup_area); + + let instructions = Paragraph::new(Spans::from(vec![ + Span::raw("Press "), + Span::styled("L", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to edit "), + Span::styled("Alias", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" field, "), + Span::styled("K", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to edit "), + Span::styled("Public Key/Emoji ID", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" field, "), + Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to save Contact."), + ])) + .block(Block::default()); + f.render_widget(instructions, vert_chunks[0]); + + let alias_input = Paragraph::new(self.alias_field.as_ref()) + .style(match self.edit_contact_mode { + ContactInputMode::Alias => Style::default().fg(Color::Magenta), + _ => Style::default(), + }) + .block(Block::default().borders(Borders::ALL).title("A(l)ias:")); + f.render_widget(alias_input, vert_chunks[1]); + + let pubkey_input = Paragraph::new(self.public_key_field.as_ref()) + .style(match self.edit_contact_mode { + ContactInputMode::PubkeyEmojiId => Style::default().fg(Color::Magenta), + _ => Style::default(), + }) + .block(Block::default().borders(Borders::ALL).title("Public (K)ey / Emoji Id:")); + f.render_widget(pubkey_input, vert_chunks[2]); + + match self.edit_contact_mode { + ContactInputMode::None => (), + ContactInputMode::Alias => f.set_cursor( + // Put cursor past the end of the input text + vert_chunks[1].x + self.alias_field.width() as u16 + 1, + // Move one line down, from the border to the input line + vert_chunks[1].y + 1, + ), + ContactInputMode::PubkeyEmojiId => f.set_cursor( + // Put cursor past the end of the input text + vert_chunks[2].x + self.public_key_field.width() as u16 + 1, + // Move one line down, from the border to the input line + vert_chunks[2].y + 1, + ), + } + } + + fn on_key_confirmation_dialog(&mut self, c: char, app_state: &mut AppState) -> KeyHandled { + if self.confirmation_dialog.is_some() { + if 'n' == c { + self.confirmation_dialog = None; + return KeyHandled::Handled; + } else if 'y' == c { + match self.confirmation_dialog { + None => (), + Some(ConfirmationDialogType::DeleteContact) => { + if 'y' == c { + if let Some(c) = self + .contacts_list_state + .selected() + .and_then(|i| app_state.get_contact(i)) + .cloned() + { + if let Err(_e) = Handle::current().block_on(app_state.delete_contact(c.public_key)) { + self.error_message = + Some("Could not delete selected contact\nPress Enter to continue.".to_string()); + } + } + self.confirmation_dialog = None; + return KeyHandled::Handled; + } + }, + } + } + } + + KeyHandled::NotHandled + } + + fn on_key_edit_contact(&mut self, c: char, app_state: &mut AppState) -> KeyHandled { + if self.show_edit_contact && self.edit_contact_mode != ContactInputMode::None { + match self.edit_contact_mode { + ContactInputMode::None => return KeyHandled::Handled, + ContactInputMode::Alias => match c { + '\n' | '\t' => { + self.edit_contact_mode = ContactInputMode::PubkeyEmojiId; + return KeyHandled::Handled; + }, + c => { + self.alias_field.push(c); + return KeyHandled::Handled; + }, + }, + ContactInputMode::PubkeyEmojiId => match c { + '\n' => { + self.edit_contact_mode = ContactInputMode::None; + self.show_edit_contact = false; + + if let Err(_e) = Handle::current() + .block_on(app_state.upsert_contact(self.alias_field.clone(), self.public_key_field.clone())) + { + self.error_message = + Some("Invalid Public key or Emoji ID provided\n Press Enter to continue.".to_string()); + } + + self.alias_field = "".to_string(); + self.public_key_field = "".to_string(); + return KeyHandled::Handled; + }, + c => { + self.public_key_field.push(c); + return KeyHandled::Handled; + }, + }, + } + } + + KeyHandled::NotHandled + } + + fn on_key_show_contacts(&mut self, c: char, _app_state: &mut AppState) -> KeyHandled { + match c { + 'd' => { + if self.contacts_list_state.selected().is_none() { + return KeyHandled::NotHandled; + } + self.confirmation_dialog = Some(ConfirmationDialogType::DeleteContact); + return KeyHandled::Handled; + }, + 'n' => { + self.show_edit_contact = true; + self.edit_contact_mode = ContactInputMode::Alias; + return KeyHandled::Handled; + }, + _ => (), + } + + KeyHandled::NotHandled + } +} + +impl Component for ContactsTab { + fn draw(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) { + self.draw_contacts(f, area, app_state); + if self.show_edit_contact { + self.draw_edit_contact(f, area, app_state); + } + + match self.confirmation_dialog { + None => (), + Some(ConfirmationDialogType::DeleteContact) => { + draw_dialog( + f, + area, + "Confirm Delete".to_string(), + "Are you sure you want to delete this contact?\n(Y)es / (N)o".to_string(), + Color::Red, + 120, + 9, + ); + }, + } + } + + fn on_key(&mut self, app_state: &mut AppState, c: char) { + if self.error_message.is_some() { + if '\n' == c { + self.error_message = None; + } + return; + } + + if self.on_key_confirmation_dialog(c, app_state) == KeyHandled::Handled { + return; + } + + if self.on_key_edit_contact(c, app_state) == KeyHandled::Handled { + return; + } + + if self.on_key_show_contacts(c, app_state) == KeyHandled::Handled { + return; + } + + match c { + 'e' | '\n' => { + if let Some(c) = self + .contacts_list_state + .selected() + .and_then(|i| app_state.get_contact(i)) + { + self.public_key_field = c.public_key.clone(); + self.alias_field = c.alias.clone(); + self.show_edit_contact = true; + self.edit_contact_mode = ContactInputMode::Alias; + } + }, + _ => { + self.show_edit_contact = false; + self.edit_contact_mode = ContactInputMode::Alias; + self.public_key_field = "".to_string(); + }, + } + } + + fn on_up(&mut self, app_state: &mut AppState) { + self.contacts_list_state.set_num_items(app_state.get_contacts().len()); + self.contacts_list_state.previous(); + } + + fn on_down(&mut self, app_state: &mut AppState) { + self.contacts_list_state.set_num_items(app_state.get_contacts().len()); + self.contacts_list_state.next(); + } + + fn on_esc(&mut self, _: &mut AppState) { + if self.confirmation_dialog.is_some() { + return; + } + self.edit_contact_mode = ContactInputMode::None; + if self.show_edit_contact { + self.show_edit_contact = false; + } else { + self.contacts_list_state.select(None); + } + } + + fn on_backspace(&mut self, _app_state: &mut AppState) { + match self.edit_contact_mode { + ContactInputMode::Alias => { + let _ = self.alias_field.pop(); + }, + ContactInputMode::PubkeyEmojiId => { + let _ = self.public_key_field.pop(); + }, + ContactInputMode::None => {}, + } + } +} + +#[derive(PartialEq, Debug)] +pub enum ContactInputMode { + None, + Alias, + PubkeyEmojiId, +} + +#[derive(PartialEq, Debug)] +pub enum ConfirmationDialogType { + DeleteContact, +} diff --git a/applications/tari_console_wallet/src/ui/components/mod.rs b/applications/tari_console_wallet/src/ui/components/mod.rs index 80b299bdc5c..43d129edcde 100644 --- a/applications/tari_console_wallet/src/ui/components/mod.rs +++ b/applications/tari_console_wallet/src/ui/components/mod.rs @@ -35,6 +35,7 @@ pub mod tabs_container; pub mod tokens_component; pub mod transactions_tab; pub use self::component::*; +pub mod contacts_tab; pub mod events_component; #[derive(PartialEq, Eq)] diff --git a/applications/tari_console_wallet/src/ui/components/network_tab.rs b/applications/tari_console_wallet/src/ui/components/network_tab.rs index 146262c1170..0ac5233d27f 100644 --- a/applications/tari_console_wallet/src/ui/components/network_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/network_tab.rs @@ -74,7 +74,7 @@ impl NetworkTab { Span::raw("Press "), Span::styled("B", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" and use "), - Span::styled("Up/Down Arrow Keys", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("Up↑/Down↓ Keys", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to select a new Base Node, "), Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to set."), diff --git a/applications/tari_console_wallet/src/ui/components/receive_tab.rs b/applications/tari_console_wallet/src/ui/components/receive_tab.rs index aefe4dfc0b6..0e46ac1844d 100644 --- a/applications/tari_console_wallet/src/ui/components/receive_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/receive_tab.rs @@ -2,7 +2,7 @@ use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, - text::Span, + text::{Span, Spans}, widgets::{Block, Borders, Paragraph}, Frame, }; @@ -24,84 +24,84 @@ impl ReceiveTab { )); f.render_widget(block, area); - let help_body_area = Layout::default() - .constraints([Constraint::Min(42)].as_ref()) - .margin(1) - .split(area); - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Length(48), Constraint::Min(1)].as_ref()) + .direction(Direction::Vertical) + .constraints([Constraint::Length(6), Constraint::Length(23)].as_ref()) .margin(1) - .split(help_body_area[0]); + .split(area); + // QR Code let qr_code = Paragraph::new(app_state.get_identity().qr_code.as_str()).block(Block::default()); + f.render_widget(qr_code, chunks[1]); - f.render_widget(qr_code, chunks[0]); - - let info_chunks = Layout::default() + // Connection details + let details_chunks = Layout::default() + .direction(Direction::Vertical) .constraints( [ - Constraint::Length(1), // Lining up fields with Qr Code - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Min(1), + Constraint::Length(1), + Constraint::Length(1), + Constraint::Length(1), + Constraint::Length(1), ] .as_ref(), ) - .horizontal_margin(1) - .split(chunks[1]); + .margin(1) + .split(chunks[0]); - // Public Key let block = Block::default() .borders(Borders::ALL) - .title(Span::styled("Public Key", Style::default().fg(Color::White))); - f.render_widget(block, info_chunks[1]); - let label_layout = Layout::default() - .constraints([Constraint::Length(1)].as_ref()) - .margin(1) - .split(info_chunks[1]); - // Put a space in front of pub key so it's easy to select - let public_key = Paragraph::new(format!(" {}", app_state.get_identity().public_key)); - f.render_widget(public_key, label_layout[0]); + .title(Span::styled("Connection Details", Style::default().fg(Color::White))); + f.render_widget(block, chunks[0]); + + const ITEM_01: &str = "Public Key: "; + const ITEM_02: &str = "Node ID: "; + const ITEM_03: &str = "Public Address: "; + const ITEM_04: &str = "Emoji ID: "; + + // Public Key + let public_key_text = Spans::from(vec![ + Span::styled(ITEM_01, Style::default().fg(Color::Magenta)), + Span::styled( + app_state.get_identity().public_key.clone(), + Style::default().fg(Color::White), + ), + ]); + let paragraph = Paragraph::new(public_key_text).block(Block::default()); + f.render_widget(paragraph, details_chunks[0]); // NodeId - let block = Block::default() - .borders(Borders::ALL) - .title(Span::styled("Node ID", Style::default().fg(Color::White))); - f.render_widget(block, info_chunks[2]); - let label_layout = Layout::default() - .constraints([Constraint::Length(1)].as_ref()) - .margin(1) - .split(info_chunks[2]); - let node_id = Paragraph::new(app_state.get_identity().node_id.as_str()); - f.render_widget(node_id, label_layout[0]); + let node_id_text = Spans::from(vec![ + Span::styled(ITEM_02, Style::default().fg(Color::Magenta)), + Span::styled( + app_state.get_identity().node_id.clone(), + Style::default().fg(Color::White), + ), + ]); + let paragraph = Paragraph::new(node_id_text).block(Block::default()); + f.render_widget(paragraph, details_chunks[1]); // Public Address - let block = Block::default() - .borders(Borders::ALL) - .title(Span::styled("Public Address", Style::default().fg(Color::White))); - f.render_widget(block, info_chunks[3]); - let label_layout = Layout::default() - .constraints([Constraint::Length(1)].as_ref()) - .margin(1) - .split(info_chunks[3]); - let public_address = Paragraph::new(format!(" {}", app_state.get_identity().public_address)); - f.render_widget(public_address, label_layout[0]); + let public_ddress_text = Spans::from(vec![ + Span::styled(ITEM_03, Style::default().fg(Color::Magenta)), + Span::styled( + app_state.get_identity().public_address.clone(), + Style::default().fg(Color::White), + ), + ]); + let paragraph = Paragraph::new(public_ddress_text).block(Block::default()); + f.render_widget(paragraph, details_chunks[2]); // Emoji ID - let block = Block::default() - .borders(Borders::ALL) - .title(Span::styled("Emoji ID", Style::default().fg(Color::White))); - f.render_widget(block, info_chunks[4]); - let label_layout = Layout::default() - .constraints([Constraint::Length(1)].as_ref()) - .margin(1) - .split(info_chunks[4]); - let emoji_id = Paragraph::new(app_state.get_identity().emoji_id.as_str()); - f.render_widget(emoji_id, label_layout[0]); + let emoji_id_text = Spans::from(vec![ + Span::styled(ITEM_04, Style::default().fg(Color::Magenta)), + Span::styled( + app_state.get_identity().emoji_id.clone(), + Style::default().fg(Color::White), + ), + ]); + let paragraph = Paragraph::new(emoji_id_text).block(Block::default()); + f.render_widget(paragraph, details_chunks[3]); } } diff --git a/applications/tari_console_wallet/src/ui/components/send_tab.rs b/applications/tari_console_wallet/src/ui/components/send_tab.rs index 2595deec50b..5537f2b1665 100644 --- a/applications/tari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/send_tab.rs @@ -7,7 +7,7 @@ use tui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Span, Spans}, - widgets::{Block, Borders, Clear, ListItem, Paragraph, Row, Table, TableState, Wrap}, + widgets::{Block, Borders, ListItem, Paragraph, Row, Table, TableState, Wrap}, Frame, }; use unicode_width::UnicodeWidthStr; @@ -16,7 +16,7 @@ use crate::{ ui::{ components::{balance::Balance, styles, Component, KeyHandled}, state::{AppState, UiTransactionSendStatus}, - widgets::{centered_rect_absolute, draw_dialog, MultiColumnList, WindowedListState}, + widgets::{draw_dialog, MultiColumnList, WindowedListState}, MAX_WIDTH, }, utils::formatting::display_compressed_string, @@ -25,15 +25,11 @@ use crate::{ pub struct SendTab { balance: Balance, send_input_mode: SendInputMode, - edit_contact_mode: ContactInputMode, show_contacts: bool, - show_edit_contact: bool, to_field: String, amount_field: String, fee_field: String, message_field: String, - alias_field: String, - public_key_field: String, error_message: Option, success_message: Option, contacts_list_state: WindowedListState, @@ -48,15 +44,11 @@ impl SendTab { Self { balance: Balance::new(), send_input_mode: SendInputMode::None, - edit_contact_mode: ContactInputMode::None, show_contacts: false, - show_edit_contact: false, to_field: String::new(), amount_field: String::new(), fee_field: app_state.get_default_fee_per_gram().as_u64().to_string(), message_field: String::new(), - alias_field: String::new(), - public_key_field: String::new(), error_message: None, success_message: None, contacts_list_state: WindowedListState::new(), @@ -211,14 +203,8 @@ impl SendTab { let instructions = Paragraph::new(Spans::from(vec![ Span::raw(" Use "), - Span::styled("Up/Down Arrow Keys", Style::default().add_modifier(Modifier::BOLD)), + Span::styled("Up↑/Down↓ Keys", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to choose a contact, "), - Span::styled("E", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to (e)dit and "), - Span::styled("D", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to (d)elete a contact, "), - Span::styled("N", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to create a (n)ew contact, "), Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to select."), ])) @@ -253,71 +239,6 @@ impl SendTab { column_list.render(f, list_areas[1], &mut list_state); } - fn draw_edit_contact(&mut self, f: &mut Frame, area: Rect, _app_state: &AppState) - where B: Backend { - let popup_area = centered_rect_absolute(120, 10, area); - - f.render_widget(Clear, popup_area); - - let block = Block::default().borders(Borders::ALL).title(Span::styled( - "Add/Edit Contact", - Style::default().fg(Color::White).add_modifier(Modifier::BOLD), - )); - f.render_widget(block, popup_area); - let vert_chunks = Layout::default() - .constraints([Constraint::Length(2), Constraint::Length(3), Constraint::Length(3)].as_ref()) - .margin(1) - .split(popup_area); - - let instructions = Paragraph::new(Spans::from(vec![ - Span::raw("Press "), - Span::styled("L", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to edit "), - Span::styled("Alias", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field, "), - Span::styled("K", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to edit "), - Span::styled("Public Key/Emoji ID", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" field, "), - Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to save Contact."), - ])) - .block(Block::default()); - f.render_widget(instructions, vert_chunks[0]); - - let alias_input = Paragraph::new(self.alias_field.as_ref()) - .style(match self.edit_contact_mode { - ContactInputMode::Alias => Style::default().fg(Color::Magenta), - _ => Style::default(), - }) - .block(Block::default().borders(Borders::ALL).title("A(l)ias:")); - f.render_widget(alias_input, vert_chunks[1]); - - let pubkey_input = Paragraph::new(self.public_key_field.as_ref()) - .style(match self.edit_contact_mode { - ContactInputMode::PubkeyEmojiId => Style::default().fg(Color::Magenta), - _ => Style::default(), - }) - .block(Block::default().borders(Borders::ALL).title("Public (K)ey / Emoji Id:")); - f.render_widget(pubkey_input, vert_chunks[2]); - - match self.edit_contact_mode { - ContactInputMode::None => (), - ContactInputMode::Alias => f.set_cursor( - // Put cursor past the end of the input text - vert_chunks[1].x + self.alias_field.width() as u16 + 1, - // Move one line down, from the border to the input line - vert_chunks[1].y + 1, - ), - ContactInputMode::PubkeyEmojiId => f.set_cursor( - // Put cursor past the end of the input text - vert_chunks[2].x + self.public_key_field.width() as u16 + 1, - // Move one line down, from the border to the input line - vert_chunks[2].y + 1, - ), - } - } - fn draw_tokens(&mut self, f: &mut Frame, area: Rect, app_state: &AppState) where B: Backend { let tokens = app_state.get_owned_tokens(); @@ -446,23 +367,6 @@ impl SendTab { return KeyHandled::Handled; } }, - Some(ConfirmationDialogType::DeleteContact) => { - if 'y' == c { - if let Some(c) = self - .contacts_list_state - .selected() - .and_then(|i| app_state.get_contact(i)) - .cloned() - { - if let Err(_e) = Handle::current().block_on(app_state.delete_contact(c.public_key)) { - self.error_message = - Some("Could not delete selected contact\nPress Enter to continue.".to_string()); - } - } - self.confirmation_dialog = None; - return KeyHandled::Handled; - } - }, } } } @@ -520,74 +424,19 @@ impl SendTab { KeyHandled::NotHandled } - fn on_key_edit_contact(&mut self, c: char, app_state: &mut AppState) -> KeyHandled { - if self.show_edit_contact && self.edit_contact_mode != ContactInputMode::None { - match self.edit_contact_mode { - ContactInputMode::None => return KeyHandled::Handled, - ContactInputMode::Alias => match c { - '\n' | '\t' => { - self.edit_contact_mode = ContactInputMode::PubkeyEmojiId; - return KeyHandled::Handled; - }, - c => { - self.alias_field.push(c); - return KeyHandled::Handled; - }, - }, - ContactInputMode::PubkeyEmojiId => match c { - '\n' => { - self.edit_contact_mode = ContactInputMode::None; - self.show_edit_contact = false; - - if let Err(_e) = Handle::current() - .block_on(app_state.upsert_contact(self.alias_field.clone(), self.public_key_field.clone())) - { - self.error_message = - Some("Invalid Public key or Emoji ID provided\n Press Enter to continue.".to_string()); - } - - self.alias_field = "".to_string(); - self.public_key_field = "".to_string(); - return KeyHandled::Handled; - }, - c => { - self.public_key_field.push(c); - return KeyHandled::Handled; - }, - }, - } - } - - KeyHandled::NotHandled - } - fn on_key_show_contacts(&mut self, c: char, app_state: &mut AppState) -> KeyHandled { - if self.show_contacts { - match c { - 'd' => { - self.confirmation_dialog = Some(ConfirmationDialogType::DeleteContact); - return KeyHandled::Handled; - }, - '\n' => { - if let Some(c) = self - .contacts_list_state - .selected() - .and_then(|i| app_state.get_contact(i)) - .cloned() - { - self.to_field = c.public_key; - self.send_input_mode = SendInputMode::Amount; - self.show_contacts = false; - } - return KeyHandled::Handled; - }, - 'n' => { - self.show_edit_contact = true; - self.edit_contact_mode = ContactInputMode::Alias; - return KeyHandled::Handled; - }, - _ => (), + if self.show_contacts && c == '\n' { + if let Some(c) = self + .contacts_list_state + .selected() + .and_then(|i| app_state.get_contact(i)) + .cloned() + { + self.to_field = c.public_key; + self.send_input_mode = SendInputMode::Amount; + self.show_contacts = false; } + return KeyHandled::Handled; } KeyHandled::NotHandled @@ -613,9 +462,6 @@ impl Component for SendTab { if self.show_contacts { self.draw_contacts(f, areas[2], app_state); - if self.show_edit_contact { - self.draw_edit_contact(f, area, app_state); - } }; if self.send_input_mode == SendInputMode::Amount { @@ -686,17 +532,6 @@ impl Component for SendTab { 9, ); }, - Some(ConfirmationDialogType::DeleteContact) => { - draw_dialog( - f, - area, - "Confirm Delete".to_string(), - "Are you sure you want to delete this contact?\n(Y)es / (N)o".to_string(), - Color::Red, - 120, - 9, - ); - }, } } @@ -727,10 +562,6 @@ impl Component for SendTab { return; } - if self.on_key_edit_contact(c, app_state) == KeyHandled::Handled { - return; - } - if self.on_key_show_contacts(c, app_state) == KeyHandled::Handled { return; } @@ -738,28 +569,6 @@ impl Component for SendTab { match c { 'c' => { self.show_contacts = !self.show_contacts; - if self.show_contacts { - self.show_edit_contact = false; - self.edit_contact_mode = ContactInputMode::Alias; - self.public_key_field = "".to_string(); - self.amount_field = "".to_string(); - self.message_field = "".to_string(); - self.send_input_mode = SendInputMode::None; - } - }, - 'e' => { - if let Some(c) = self - .contacts_list_state - .selected() - .and_then(|i| app_state.get_contact(i)) - { - self.public_key_field = c.public_key.clone(); - self.alias_field = c.alias.clone(); - if self.show_contacts { - self.show_edit_contact = true; - self.edit_contact_mode = ContactInputMode::Alias; - } - } }, 't' => self.send_input_mode = SendInputMode::To, 'a' => { @@ -835,10 +644,8 @@ impl Component for SendTab { } fn on_esc(&mut self, _: &mut AppState) { - self.edit_contact_mode = ContactInputMode::None; self.send_input_mode = SendInputMode::None; self.show_contacts = false; - self.show_edit_contact = false; } fn on_backspace(&mut self, _app_state: &mut AppState) { @@ -859,16 +666,6 @@ impl Component for SendTab { }, SendInputMode::None => {}, } - - match self.edit_contact_mode { - ContactInputMode::Alias => { - let _ = self.alias_field.pop(); - }, - ContactInputMode::PubkeyEmojiId => { - let _ = self.public_key_field.pop(); - }, - ContactInputMode::None => {}, - } } } @@ -881,16 +678,8 @@ pub enum SendInputMode { Fee, } -#[derive(PartialEq, Debug)] -pub enum ContactInputMode { - None, - Alias, - PubkeyEmojiId, -} - #[derive(PartialEq, Debug)] pub enum ConfirmationDialogType { NormalSend, OneSidedSend, - DeleteContact, } diff --git a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs index 3d1117c7326..323df3f79f2 100644 --- a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs @@ -477,18 +477,20 @@ impl Component for TransactionsTab { }; span_vec.push(Span::styled( - "Up/Down Arrow", + " Up↑/Down↓", Style::default().add_modifier(Modifier::BOLD), )); - span_vec.push(Span::raw(" selects a transaction, ")); + span_vec.push(Span::raw(" selects Tx, ")); span_vec.push(Span::styled("C", Style::default().add_modifier(Modifier::BOLD))); - span_vec.push(Span::raw(" cancels a selected Pending Tx, ")); + span_vec.push(Span::raw(" cancels selected Pending Tx, ")); span_vec.push(Span::styled("A", Style::default().add_modifier(Modifier::BOLD))); span_vec.push(Span::raw(" shows abandoned coinbase Txs, ")); + span_vec.push(Span::styled("R", Style::default().add_modifier(Modifier::BOLD))); + span_vec.push(Span::raw(" rebroadcast all Broadcast, ")); span_vec.push(Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD))); - span_vec.push(Span::raw(" exits the list. R: Rebroadcast all in Broadcast")); + span_vec.push(Span::raw(" exits list.")); - let instructions = Paragraph::new(Spans::from(span_vec)).wrap(Wrap { trim: true }); + let instructions = Paragraph::new(Spans::from(span_vec)).wrap(Wrap { trim: false }); f.render_widget(instructions, areas[1]); self.draw_transaction_lists(f, areas[2], app_state); diff --git a/base_layer/common_types/src/transaction.rs b/base_layer/common_types/src/transaction.rs index 45908a7c89b..94ef6d12b7c 100644 --- a/base_layer/common_types/src/transaction.rs +++ b/base_layer/common_types/src/transaction.rs @@ -35,18 +35,10 @@ pub enum TransactionStatus { impl TransactionStatus { pub fn is_faux(&self) -> bool { - match self { - TransactionStatus::Completed => false, - TransactionStatus::Broadcast => false, - TransactionStatus::MinedUnconfirmed => false, - TransactionStatus::Imported => true, - TransactionStatus::Pending => false, - TransactionStatus::Coinbase => false, - TransactionStatus::MinedConfirmed => false, - TransactionStatus::Rejected => false, - TransactionStatus::FauxUnconfirmed => true, - TransactionStatus::FauxConfirmed => true, - } + matches!( + self, + TransactionStatus::Imported | TransactionStatus::FauxUnconfirmed | TransactionStatus::FauxConfirmed + ) } }