Skip to content

Commit

Permalink
feat: add contacts status to tui (#3868)
Browse files Browse the repository at this point in the history
Description
---
Added the contacts liveness status to the console wallet's TUI using the recently merged contacts liveness service feature.

```
  Alias                   Public Key                                                       Emoji ID          Last Seen   Status   
node01 bn                 cca97dacf0667b8a94fa0e17d7449cad23b7ad59940733dca488c0eb2943ed1e 📡👾🐢..🚁🍍🌂             NeverSeen
node02 has long alias nam fa9a9b87a5d575737d8559524c62c5cccb1cc8732299560a16d4f060d7095930 🚪👖👗..🎸🍬🌞  02-24T15:49 Online   
node02 bn                 fc8488da9a83d7c851166fc0b18986155958856658d20a9819061df12ef83400 🚲🐮🐳..🍳🌀📚             NeverSeen
node03                    d258580f20f2d2b712905b8867529b02a376dc066f2c7028d67926f8bbdc1b7a 🔎🎷🎷..🍊🐜🍈 02-24T15:49 Online   
nodetest                  d60429291ef2d6a8698a1789794cbf7ce31a138d0a7bae00d857df616407cd26 🔦🌋🍟..📣🍗🏦 02-22T22:54 Offline  
│```

Motivation and Context
---
Experimental.

How Has This Been Tested?
---
System-level tests.
  • Loading branch information
hansieodendaal authored Feb 25, 2022
1 parent 7f85073 commit 30bf86b
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 114 deletions.
1 change: 1 addition & 0 deletions applications/tari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ pub async fn init_wallet(
Some(updater_config),
config.autoupdate_check_interval,
Some(Duration::from_secs(config.contacts_auto_ping_interval)),
Some(config.contacts_online_ping_window),
);

let mut wallet = Wallet::start(
Expand Down
26 changes: 21 additions & 5 deletions applications/tari_console_wallet/src/ui/components/contacts_tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
components::{Component, KeyHandled},
state::AppState,
widgets::{centered_rect_absolute, draw_dialog, MultiColumnList, WindowedListState},
UiContact,
MAX_WIDTH,
},
utils::formatting::display_compressed_string,
Expand Down Expand Up @@ -76,28 +77,43 @@ impl ContactsTab {
let window = self.contacts_list_state.get_start_end();
let windowed_view = app_state.get_contacts_slice(window.0, window.1);

let column_list = ContactsTab::create_column_view(windowed_view);
column_list.render(f, list_areas[1], &mut list_state);
}

// Helper function to create the column list to be rendered
pub fn create_column_view(windowed_view: &[UiContact]) -> MultiColumnList<Vec<ListItem>> {
let mut column0_items = Vec::new();
let mut column1_items = Vec::new();
let mut column2_items = Vec::new();
let mut column3_items = Vec::new();
let mut column4_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())));
column1_items.push(ListItem::new(Span::raw(c.public_key.clone())));
column2_items.push(ListItem::new(Span::raw(display_compressed_string(
c.emoji_id.clone(),
3,
3,
))));
column3_items.push(ListItem::new(Span::raw(c.last_seen.clone())));
column4_items.push(ListItem::new(Span::raw(c.online_status.clone())));
}
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(None, Some(1), 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);
.add_column(None, Some(1), Vec::new())
.add_column(Some("Emoji ID"), Some(14), column2_items)
.add_column(None, Some(1), Vec::new())
.add_column(Some("Last Seen"), Some(11), column3_items)
.add_column(None, Some(1), Vec::new())
.add_column(Some("Status"), Some(10), column4_items);

column_list
}

fn draw_edit_contact<B>(&mut self, f: &mut Frame<B>, area: Rect, _app_state: &AppState)
Expand Down
36 changes: 6 additions & 30 deletions applications/tari_console_wallet/src/ui/components/send_tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ use tui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, ListItem, Paragraph, Row, Table, TableState, Wrap},
widgets::{Block, Borders, Paragraph, Row, Table, TableState, Wrap},
Frame,
};
use unicode_width::UnicodeWidthStr;

use crate::{
ui::{
components::{balance::Balance, styles, Component, KeyHandled},
state::{AppState, UiTransactionSendStatus},
widgets::{draw_dialog, MultiColumnList, WindowedListState},
MAX_WIDTH,
},
utils::formatting::display_compressed_string,
use crate::ui::{
components::{balance::Balance, contacts_tab::ContactsTab, styles, Component, KeyHandled},
state::{AppState, UiTransactionSendStatus},
widgets::{draw_dialog, WindowedListState},
};

pub struct SendTab {
Expand Down Expand Up @@ -217,27 +213,7 @@ impl SendTab {
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);
let column_list = ContactsTab::create_column_view(windowed_view);
column_list.render(f, list_areas[1], &mut list_state);
}

Expand Down
31 changes: 19 additions & 12 deletions applications/tari_console_wallet/src/ui/state/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use tari_wallet::{
assets::Asset,
base_node_service::{handle::BaseNodeEventReceiver, service::BaseNodeState},
connectivity_service::{OnlineStatus, WalletConnectivityHandle, WalletConnectivityInterface},
contacts_service::storage::database::Contact,
contacts_service::{handle::ContactsLivenessEvent, storage::database::Contact},
output_manager_service::{handle::OutputManagerEventReceiver, service::Balance},
tokens::Token,
transaction_service::{
Expand All @@ -64,7 +64,7 @@ use tari_wallet::{
WalletSqlite,
};
use tokio::{
sync::{watch, RwLock},
sync::{broadcast, watch, RwLock},
task,
};

Expand Down Expand Up @@ -735,22 +735,25 @@ impl AppStateInner {
}

pub async fn refresh_contacts_state(&mut self) -> Result<(), UiError> {
let mut contacts: Vec<UiContact> = self
.wallet
.contacts_service
.get_contacts()
.await?
.iter()
.map(|c| UiContact::from(c.clone()))
.collect();
let db_contacts = self.wallet.contacts_service.get_contacts().await?;
let mut ui_contacts: Vec<UiContact> = vec![];
for contact in db_contacts {
// A contact's online status is a function of current time and can therefore not be stored in a database
let online_status = self
.wallet
.contacts_service
.get_contact_online_status(contact.last_seen)
.await?;
ui_contacts.push(UiContact::from(contact.clone()).with_online_status(format!("{}", online_status)));
}

contacts.sort_by(|a, b| {
ui_contacts.sort_by(|a, b| {
a.alias
.partial_cmp(&b.alias)
.expect("Should be able to compare contact aliases")
});

self.data.contacts = contacts;
self.data.contacts = ui_contacts;
self.updated = true;
Ok(())
}
Expand Down Expand Up @@ -821,6 +824,10 @@ impl AppStateInner {
self.wallet.transaction_service.get_event_stream()
}

pub fn get_contacts_liveness_event_stream(&self) -> broadcast::Receiver<Arc<ContactsLivenessEvent>> {
self.wallet.contacts_service.get_contacts_liveness_event_stream()
}

pub fn get_output_manager_service_event_stream(&self) -> OutputManagerEventReceiver {
self.wallet.output_manager_service.get_event_stream()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use std::sync::Arc;
use std::{ops::Deref, sync::Arc};

use log::*;
use tari_common_types::transaction::TxId;
use tari_comms::{connectivity::ConnectivityEvent, peer_manager::Peer};
use tari_wallet::{
base_node_service::{handle::BaseNodeEvent, service::BaseNodeState},
connectivity_service::WalletConnectivityInterface,
contacts_service::handle::ContactsLivenessEvent,
output_manager_service::handle::OutputManagerEvent,
transaction_service::handle::TransactionEvent,
};
Expand Down Expand Up @@ -80,6 +81,8 @@ impl WalletEventMonitor {
.new_update_notifier()
.clone();

let mut contacts_liveness_events = self.app_state_inner.read().await.get_contacts_liveness_event_stream();

info!(target: LOG_TARGET, "Wallet Event Monitor starting");
loop {
tokio::select! {
Expand Down Expand Up @@ -213,6 +216,26 @@ impl WalletEventMonitor {
Err(broadcast::error::RecvError::Closed) => {}
}
},
event = contacts_liveness_events.recv() => {
match event {
Ok(liveness_event) => {
match liveness_event.deref() {
ContactsLivenessEvent::StatusUpdated(data) => {
trace!(target: LOG_TARGET,
"Contacts Liveness Service Callback Handler event 'StatusUpdated': {}",
data.clone(),
);
self.trigger_contacts_refresh().await;
}
ContactsLivenessEvent::NetworkSilence => {}
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
warn!(target: LOG_TARGET, "Missed {} from Output Manager Service events", n);
}
Err(broadcast::error::RecvError::Closed) => {}
}
}
_ = shutdown_signal.wait() => {
info!(target: LOG_TARGET, "Wallet Event Monitor shutting down because the shutdown signal was received");
break;
Expand Down Expand Up @@ -293,4 +316,12 @@ impl WalletEventMonitor {
let mut inner = self.app_state_inner.write().await;
inner.add_notification(notification);
}

async fn trigger_contacts_refresh(&mut self) {
let mut inner = self.app_state_inner.write().await;

if let Err(e) = inner.refresh_contacts_state().await {
warn!(target: LOG_TARGET, "Error refresh contacts state: {}", e);
}
}
}
17 changes: 17 additions & 0 deletions applications/tari_console_wallet/src/ui/ui_contact.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use chrono::{DateTime, Local};
use tari_common_types::emoji::EmojiId;
use tari_wallet::contacts_service::storage::database::Contact;

Expand All @@ -6,6 +7,15 @@ pub struct UiContact {
pub alias: String,
pub public_key: String,
pub emoji_id: String,
pub last_seen: String,
pub online_status: String,
}

impl UiContact {
pub fn with_online_status(mut self, online_status: String) -> Self {
self.online_status = online_status;
self
}
}

impl From<Contact> for UiContact {
Expand All @@ -14,6 +24,13 @@ impl From<Contact> for UiContact {
alias: c.alias,
public_key: c.public_key.to_string(),
emoji_id: EmojiId::from_pubkey(&c.public_key).as_str().to_string(),
last_seen: match c.last_seen {
Some(val) => DateTime::<Local>::from_utc(val, Local::now().offset().to_owned())
.format("%m-%dT%H:%M")
.to_string(),
None => "".to_string(),
},
online_status: "".to_string(),
}
}
}
6 changes: 3 additions & 3 deletions applications/tari_console_wallet/src/utils/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub fn display_compressed_string(string: String, len_first: usize, len_last: usi
for i in graphemes.iter().take(len_first) {
result.push_str(i);
}
result.push_str("...");
result.push_str("..");
for i in graphemes.iter().skip(graphemes.len() - len_last) {
result.push_str(i);
}
Expand All @@ -59,11 +59,11 @@ mod test {
let short_str = "testing".to_string();
assert_eq!(display_compressed_string(short_str.clone(), 5, 5), short_str);
let long_str = "abcdefghijklmnopqrstuvwxyz".to_string();
assert_eq!(display_compressed_string(long_str, 3, 3), "abc...xyz".to_string());
assert_eq!(display_compressed_string(long_str, 3, 3), "abc..xyz".to_string());
let emoji_str = "🐾💎🎤🎨📌🍄🎰🍉🚧💉💡👟🚒📌🔌🐶🐾🐢🔭🐨😻💨🐎🐊🚢👟🚧🐞🚜🌂🎩🎱📈".to_string();
assert_eq!(
display_compressed_string(emoji_str, 3, 6),
"🐾💎🎤...🐞🚜🌂🎩🎱📈".to_string()
"🐾💎🎤..🐞🚜🌂🎩🎱📈".to_string()
);
}
}
3 changes: 3 additions & 0 deletions base_layer/wallet/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub struct WalletConfig {
pub updater_config: Option<AutoUpdateConfig>,
pub autoupdate_check_interval: Option<Duration>,
pub contacts_auto_ping_interval: Duration,
pub contacts_online_ping_window: usize,
}

impl WalletConfig {
Expand All @@ -61,6 +62,7 @@ impl WalletConfig {
updater_config: Option<AutoUpdateConfig>,
autoupdate_check_interval: Option<Duration>,
contacts_auto_ping_interval: Option<Duration>,
contacts_online_ping_window: Option<usize>,
) -> Self {
Self {
comms_config,
Expand All @@ -74,6 +76,7 @@ impl WalletConfig {
updater_config,
autoupdate_check_interval,
contacts_auto_ping_interval: contacts_auto_ping_interval.unwrap_or_else(|| Duration::from_secs(20)),
contacts_online_ping_window: contacts_online_ping_window.unwrap_or(2),
}
}
}
Loading

0 comments on commit 30bf86b

Please sign in to comment.