diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 121bb8c3..03be8873 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -13,7 +13,7 @@ use liana::miniscript::bitcoin::{Address, Amount}; use super::{cache::Cache, error::Error, menu::Menu, message::Message, view}; use crate::daemon::{ - model::{Coin, HistoryTransaction}, + model::{remaining_sequence, Coin, HistoryTransaction}, Daemon, }; pub use coins::CoinsPanel; @@ -38,6 +38,8 @@ pub trait State { pub struct Home { balance: Amount, + recovery_warning: Option<(Amount, usize)>, + recovery_alert: Option<(Amount, usize)>, pending_events: Vec, events: Vec, selected_event: Option, @@ -60,6 +62,8 @@ impl Home { }) .sum(), ), + recovery_alert: None, + recovery_warning: None, selected_event: None, events: Vec::new(), pending_events: Vec::new(), @@ -82,14 +86,20 @@ impl State for Home { &Menu::Home, cache, None, - view::home::home_view(&self.balance, &self.pending_events, &self.events), + view::home::home_view( + &self.balance, + self.recovery_warning.as_ref(), + self.recovery_alert.as_ref(), + &self.pending_events, + &self.events, + ), ) } fn update( &mut self, daemon: Arc, - _cache: &Cache, + cache: &Cache, message: Message, ) -> Command { match message { @@ -97,19 +107,29 @@ impl State for Home { Err(e) => self.warning = Some(e), Ok(coins) => { self.warning = None; - self.balance = Amount::from_sat( - coins - .iter() - .map(|coin| { - // If the coin is not spent and is its transaction is confirmed - if coin.spend_info.is_none() && coin.block_height.is_some() { - coin.amount.to_sat() - } else { - 0 - } - }) - .sum(), - ); + self.balance = Amount::from_sat(0); + let mut recovery_warning = (Amount::from_sat(0), 0); + let mut recovery_alert = (Amount::from_sat(0), 0); + for coin in coins { + if coin.spend_info.is_none() && coin.block_height.is_some() { + self.balance += coin.amount; + let timelock = daemon.config().main_descriptor.timelock_value(); + let seq = remaining_sequence(&coin, cache.blockheight as u32, timelock); + if seq == 0 { + recovery_alert.0 += coin.amount; + recovery_alert.1 += 1; + } else if seq < timelock * 10 / 100 { + recovery_warning.0 += coin.amount; + recovery_warning.1 += 1; + } + } + } + if recovery_warning.1 > 0 { + self.recovery_warning = Some(recovery_warning); + } + if recovery_alert.1 > 0 { + self.recovery_alert = Some(recovery_alert); + } } }, Message::HistoryTransactions(res) => match res { diff --git a/gui/src/app/view/coins.rs b/gui/src/app/view/coins.rs index 8af31a73..e036c595 100644 --- a/gui/src/app/view/coins.rs +++ b/gui/src/app/view/coins.rs @@ -12,7 +12,7 @@ use crate::ui::{ use crate::{ app::{cache::Cache, view::message::Message}, - daemon::model::Coin, + daemon::model::{remaining_sequence, Coin}, }; pub fn coins_view<'a>( @@ -74,36 +74,43 @@ fn coin_list_view( .style(badge::PillStyle::Success), ) } else { - if let Some(b) = coin.block_height { - if blockheight > b as u32 + timelock { - Some(Container::new( - Row::new() - .spacing(5) - .push(text(" 0").small().style(color::ALERT)) - .push( - icon::hourglass_done_icon() - .small() - .style(color::ALERT), - ) - .align_items(Alignment::Center), - )) - } else { - Some(Container::new( - Row::new() - .spacing(5) - .push( - text(format!( - " {}", - b as u32 + timelock - blockheight - )) - .small(), - ) - .push(icon::hourglass_icon().small()) - .align_items(Alignment::Center), - )) - } + let seq = remaining_sequence(coin, blockheight, timelock); + if seq == 0 { + Some(Container::new( + Row::new() + .spacing(5) + .push(text(" 0").small().style(color::ALERT)) + .push( + icon::hourglass_done_icon() + .small() + .style(color::ALERT), + ) + .align_items(Alignment::Center), + )) + } else if seq < timelock * 10 / 100 { + Some(Container::new( + Row::new() + .spacing(5) + .push( + text(format!(" {}", seq)) + .small() + .style(color::WARNING), + ) + .push( + icon::hourglass_icon() + .small() + .style(color::WARNING), + ) + .align_items(Alignment::Center), + )) } else { - None + Some(Container::new( + Row::new() + .spacing(5) + .push(text(format!(" {}", seq)).small()) + .push(icon::hourglass_icon().small()) + .align_items(Alignment::Center), + )) } }) .spacing(10) @@ -148,10 +155,7 @@ fn coin_list_view( )) } else { Some(Container::new( - text(format!( - "The recovery path will be available in {} blocks", - b as u32 + timelock - blockheight - )) + text(format!("The recovery path will be available in {} blocks", b as u32 + timelock - blockheight)) .bold() .small(), )) @@ -166,8 +170,14 @@ fn coin_list_view( Column::new() .push( Row::new() + .align_items(Alignment::Center) .push(text("Outpoint:").small().bold()) - .push(text(format!("{}", coin.outpoint)).small()) + .push(Row::new().align_items(Alignment::Center) + .push(text(format!("{}", coin.outpoint)).small()) + .push(Button::new(icon::clipboard_icon()) + .on_press(Message::Clipboard(coin.outpoint.to_string())) + .style(button::Style::TransparentBorder.into()) + )) .spacing(5), ) .push_maybe(coin.block_height.map(|b| { diff --git a/gui/src/app/view/home.rs b/gui/src/app/view/home.rs index 41f77a01..59ae930d 100644 --- a/gui/src/app/view/home.rs +++ b/gui/src/app/view/home.rs @@ -7,6 +7,7 @@ use iced::{ }; use crate::ui::{ + color, component::{badge, button::Style, card, text::*}, icon, util::Collection, @@ -22,12 +23,36 @@ pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20; pub fn home_view<'a>( balance: &'a bitcoin::Amount, + recovery_warning: Option<&(bitcoin::Amount, usize)>, + recovery_alert: Option<&(bitcoin::Amount, usize)>, pending_events: &[HistoryTransaction], events: &Vec, ) -> Element<'a, Message> { Column::new() .push(Column::new().padding(40)) .push(text(format!("{} BTC", balance.to_btc())).bold().size(50)) + .push_maybe(recovery_warning.map(|(a, c)| { + Row::new() + .spacing(15) + .align_items(Alignment::Center) + .push(icon::hourglass_icon().size(30).style(color::WARNING)) + .push(Container::new(text(format!( + "Recovery path will be soon available for {} coins ( {} )", + c, a + )))) + .padding(10) + })) + .push_maybe(recovery_alert.map(|(a, c)| { + Row::new() + .spacing(15) + .align_items(Alignment::Center) + .push(icon::hourglass_done_icon().style(color::ALERT)) + .push(Container::new(text(format!( + "Recovery path is available for {} coins ( {} )", + c, a + )))) + .padding(10) + })) .push( Column::new() .spacing(10)