Add unconfirmed balance to home

This commit is contained in:
edouard 2023-04-21 11:24:11 +02:00
parent 956b7901c1
commit 36d4968ebb
5 changed files with 121 additions and 65 deletions

View File

@ -47,6 +47,7 @@ pub trait State {
pub struct Home {
wallet: Arc<Wallet>,
balance: Amount,
unconfirmed_balance: Amount,
recovery_warning: Option<(Amount, usize)>,
recovery_alert: Option<(Amount, usize)>,
pending_events: Vec<HistoryTransaction>,
@ -57,21 +58,22 @@ pub struct Home {
impl Home {
pub fn new(wallet: Arc<Wallet>, coins: &[Coin]) -> Self {
let (balance, unconfirmed_balance) = coins.iter().fold(
(Amount::from_sat(0), Amount::from_sat(0)),
|(balance, unconfirmed_balance), coin| {
if coin.spend_info.is_some() {
(balance, unconfirmed_balance)
} else if coin.block_height.is_some() {
(balance + coin.amount, unconfirmed_balance)
} else {
(balance, unconfirmed_balance + coin.amount)
}
},
);
Self {
wallet,
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(),
),
balance,
unconfirmed_balance,
recovery_alert: None,
recovery_warning: None,
selected_event: None,
@ -103,6 +105,7 @@ impl State for Home {
None,
view::home::home_view(
&self.balance,
&self.unconfirmed_balance,
self.recovery_warning.as_ref(),
self.recovery_alert.as_ref(),
&self.pending_events,
@ -123,19 +126,25 @@ impl State for Home {
Ok(coins) => {
self.warning = None;
self.balance = Amount::from_sat(0);
self.unconfirmed_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 = self.wallet.main_descriptor.first_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 as u32 * 10 / 100 {
recovery_warning.0 += coin.amount;
recovery_warning.1 += 1;
if coin.spend_info.is_none() {
if coin.block_height.is_some() {
self.balance += coin.amount;
let timelock = self.wallet.main_descriptor.first_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 as u32 * 10 / 100 {
recovery_warning.0 += coin.amount;
recovery_warning.1 += 1;
}
} else {
self.unconfirmed_balance += coin.amount;
}
}
}

View File

@ -62,15 +62,12 @@ fn coin_list_view(
.push(badge::coin())
.push(if coin.spend_info.is_some() {
badge::spent()
} else if coin.block_height.is_none() {
badge::unconfirmed()
} else {
let seq = remaining_sequence(coin, blockheight, timelock);
coin_sequence_label(seq, timelock as u32)
})
.push_maybe(if coin.block_height.is_none() {
Some(badge::unconfirmed())
} else {
None
})
.spacing(10)
.align_items(Alignment::Center)
.width(Length::Fill),
@ -208,31 +205,28 @@ pub fn coin_sequence_label<'a, T: 'a>(seq: u32, timelock: u32) -> Container<'a,
/// returns y,m,d,h,m
pub fn expire_message(sequence: u32) -> String {
let mut n_minutes = sequence * 10;
let n_years = n_minutes / 525960;
n_minutes -= n_years * 525960;
let n_months = n_minutes / 43830;
n_minutes -= n_months * 43830;
let n_days = n_minutes / 1440;
n_minutes -= n_days * 1440;
let n_hours = n_minutes / 60;
n_minutes -= n_hours * 60;
if n_minutes <= 1440 {
"Expires today".to_string()
} else if n_minutes <= 2 * 1440 {
"Expires in ≈ 2 days".to_string()
} else {
let n_years = n_minutes / 525960;
n_minutes -= n_years * 525960;
let n_months = n_minutes / 43830;
n_minutes -= n_months * 43830;
let n_days = n_minutes / 1440;
let units: Vec<String> = [
(n_years, "year"),
(n_months, "month"),
(n_days, "day"),
(n_hours, "hour"),
(n_minutes, "minute"),
]
.iter()
.filter_map(|(n, u)| {
if *n != 0 {
Some(format!("{} {}{}", n, u, if *n > 1 { "s" } else { "" }))
} else {
None
}
})
.collect();
let units: Vec<String> = [(n_years, "year"), (n_months, "month"), (n_days, "day")]
.iter()
.filter_map(|(n, u)| {
if *n != 0 {
Some(format!("{} {}{}", n, u, if *n > 1 { "s" } else { "" }))
} else {
None
}
})
.collect();
format!("Expires in {}", units.join(","))
format!("Expires in {}", units.join(","))
}
}

View File

@ -17,6 +17,7 @@ pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20;
pub fn home_view<'a>(
balance: &'a bitcoin::Amount,
unconfirmed_balance: &'a bitcoin::Amount,
recovery_warning: Option<&(bitcoin::Amount, usize)>,
recovery_alert: Option<&(bitcoin::Amount, usize)>,
pending_events: &[HistoryTransaction],
@ -24,7 +25,21 @@ pub fn home_view<'a>(
) -> Element<'a, Message> {
Column::new()
.push(h3("Balance"))
.push(amount_with_size(balance, H1_SIZE))
.push(
Column::new()
.push(amount_with_size(balance, H1_SIZE))
.push_maybe(if unconfirmed_balance.to_sat() != 0 {
Some(
Row::new()
.spacing(10)
.push(text("+").size(H3_SIZE).style(color::GREY_3))
.push(unconfirmed_amount_with_size(unconfirmed_balance, H3_SIZE))
.push(text("unconfirmed").size(H3_SIZE).style(color::GREY_3)),
)
} else {
None
}),
)
.push_maybe(recovery_warning.map(|(a, c)| {
Row::new()
.spacing(15)

View File

@ -209,15 +209,12 @@ fn coin_list_view<'a>(
.push(badge::coin())
.push(if coin.spend_info.is_some() {
badge::spent()
} else if coin.block_height.is_none() {
badge::unconfirmed()
} else {
let seq = remaining_sequence(coin, blockheight, timelock);
coins::coin_sequence_label(seq, timelock as u32)
})
.push_maybe(if coin.block_height.is_none() {
Some(badge::unconfirmed())
} else {
None
})
.spacing(10)
.align_items(Alignment::Center)
.width(Length::Fill),

View File

@ -12,9 +12,9 @@ pub fn amount_with_size<'a, T: 'a>(a: &Amount, size: u16) -> Row<'a, T> {
assert!(sats.len() >= 9);
let row = Row::new()
.spacing(spacing)
.push(split_digits(sats[0..sats.len() - 6].to_string(), size).into())
.push(split_digits(sats[0..sats.len() - 6].to_string(), size, true).into())
.push(if a.to_sat() < 1_000_000 {
split_digits(sats[sats.len() - 6..sats.len() - 3].to_string(), size).into()
split_digits(sats[sats.len() - 6..sats.len() - 3].to_string(), size, true).into()
} else {
Row::new()
.push(
@ -25,7 +25,7 @@ pub fn amount_with_size<'a, T: 'a>(a: &Amount, size: u16) -> Row<'a, T> {
.into()
})
.push(if a.to_sat() < 1000 {
split_digits(sats[sats.len() - 3..sats.len()].to_string(), size).into()
split_digits(sats[sats.len() - 3..sats.len()].to_string(), size, true).into()
} else {
Row::new()
.push(
@ -44,7 +44,42 @@ pub fn amount_with_size<'a, T: 'a>(a: &Amount, size: u16) -> Row<'a, T> {
.align_items(iced::Alignment::Center)
}
fn split_digits<'a, T: 'a>(mut s: String, size: u16) -> impl Into<Element<'a, T>> {
pub fn unconfirmed_amount_with_size<'a, T: 'a>(a: &Amount, size: u16) -> Row<'a, T> {
let spacing = if size > P1_SIZE { 10 } else { 5 };
let sats = format!("{:.8}", a.to_btc());
assert!(sats.len() >= 9);
let row = Row::new()
.spacing(spacing)
.push(split_digits(sats[0..sats.len() - 6].to_string(), size, false).into())
.push(if a.to_sat() < 1_000_000 {
split_digits(
sats[sats.len() - 6..sats.len() - 3].to_string(),
size,
false,
)
.into()
} else {
Row::new()
.push(text(sats[sats.len() - 6..sats.len() - 3].to_string()).size(size))
.into()
})
.push(if a.to_sat() < 1000 {
split_digits(sats[sats.len() - 3..sats.len()].to_string(), size, false).into()
} else {
Row::new()
.push(text(sats[sats.len() - 3..sats.len()].to_string()).size(size))
.into()
});
Row::with_children(vec![
row.into(),
text("BTC").size(size).style(color::GREY_3).into(),
])
.spacing(spacing)
.align_items(iced::Alignment::Center)
}
fn split_digits<'a, T: 'a>(mut s: String, size: u16, bold: bool) -> impl Into<Element<'a, T>> {
let prefixes = vec!["0.00", "0.0", "0.", "000", "00", "0"];
for prefix in prefixes {
if s.starts_with(prefix) {
@ -53,10 +88,16 @@ fn split_digits<'a, T: 'a>(mut s: String, size: u16) -> impl Into<Element<'a, T>
.push(text(s).size(size).style(color::GREY_3))
.push_maybe(if right.is_empty() {
None
} else {
} else if bold {
Some(text(right).bold().size(size))
} else {
Some(text(right).size(size))
});
}
}
Row::new().push(text(s).bold().size(size))
if bold {
Row::new().push(text(s).bold().size(size))
} else {
Row::new().push(text(s).size(size))
}
}