Merge #455: Gui new coins theme
d4c2f702abc0d72045734b824c6f69e3fcb92381 fix gui sidebar: remove counter (edouard) 56e1aa04aa2889e5941e624df3d989de7debf7a5 gui: change home warning (edouard) fd67c5dea85bb2cd5a1f47199cc8fb46ff1c5286 gui: change date time format (edouard) 36d4968ebbdd78fe6c7d9fbd904612d92334f2db Add unconfirmed balance to home (edouard) 956b7901c1aea72f2311dfb22fca2304fb0b582f gui: change coins remaining time label (edouard) Pull request description: close #441 ACKs for top commit: edouardparis: Self-ACK d4c2f702abc0d72045734b824c6f69e3fcb92381 Tree-SHA512: b220ee0803f30b36eb31853c3575fede725fd96091312260bb40b2b1c2e35a7374a75e3ab3964a7e8c69ddf6e56bd9ff165447739e42f337040f671e78d92e90
This commit is contained in:
commit
06dfcc428a
@ -47,8 +47,9 @@ pub trait State {
|
||||
pub struct Home {
|
||||
wallet: Arc<Wallet>,
|
||||
balance: Amount,
|
||||
recovery_warning: Option<(Amount, usize)>,
|
||||
recovery_alert: Option<(Amount, usize)>,
|
||||
unconfirmed_balance: Amount,
|
||||
remaining_sequence: Option<u32>,
|
||||
number_of_expiring_coins: usize,
|
||||
pending_events: Vec<HistoryTransaction>,
|
||||
events: Vec<HistoryTransaction>,
|
||||
selected_event: Option<usize>,
|
||||
@ -57,23 +58,24 @@ 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(),
|
||||
),
|
||||
recovery_alert: None,
|
||||
recovery_warning: None,
|
||||
balance,
|
||||
unconfirmed_balance,
|
||||
remaining_sequence: None,
|
||||
number_of_expiring_coins: 0,
|
||||
selected_event: None,
|
||||
events: Vec::new(),
|
||||
pending_events: Vec::new(),
|
||||
@ -103,8 +105,9 @@ impl State for Home {
|
||||
None,
|
||||
view::home::home_view(
|
||||
&self.balance,
|
||||
self.recovery_warning.as_ref(),
|
||||
self.recovery_alert.as_ref(),
|
||||
&self.unconfirmed_balance,
|
||||
&self.remaining_sequence,
|
||||
self.number_of_expiring_coins,
|
||||
&self.pending_events,
|
||||
&self.events,
|
||||
),
|
||||
@ -123,28 +126,32 @@ impl State for Home {
|
||||
Ok(coins) => {
|
||||
self.warning = None;
|
||||
self.balance = Amount::from_sat(0);
|
||||
let mut recovery_warning = (Amount::from_sat(0), 0);
|
||||
let mut recovery_alert = (Amount::from_sat(0), 0);
|
||||
self.unconfirmed_balance = Amount::from_sat(0);
|
||||
self.remaining_sequence = None;
|
||||
self.number_of_expiring_coins = 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);
|
||||
// number of block in a day
|
||||
if seq <= 144 {
|
||||
self.number_of_expiring_coins += 1;
|
||||
}
|
||||
if let Some(last) = &mut self.remaining_sequence {
|
||||
if seq < *last {
|
||||
*last = seq
|
||||
}
|
||||
} else {
|
||||
self.remaining_sequence = Some(seq);
|
||||
}
|
||||
} else {
|
||||
self.unconfirmed_balance += coin.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
||||
@ -2,8 +2,10 @@ use iced::{Alignment, Length};
|
||||
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{amount::*, badge, separation, text::*},
|
||||
icon, theme,
|
||||
component::{amount::*, badge, text::*},
|
||||
icon,
|
||||
image::*,
|
||||
theme,
|
||||
util::Collection,
|
||||
widget::*,
|
||||
};
|
||||
@ -20,14 +22,7 @@ pub fn coins_view<'a>(
|
||||
selected: &[usize],
|
||||
) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.push(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.push(text(format!(" {}", coins.len())))
|
||||
.push(text(" coins")),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(Container::new(h3("Coins")).width(Length::Fill))
|
||||
.push(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
@ -45,7 +40,7 @@ pub fn coins_view<'a>(
|
||||
)),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.spacing(30)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -65,52 +60,13 @@ fn coin_list_view(
|
||||
.push(
|
||||
Row::new()
|
||||
.push(badge::coin())
|
||||
.push_maybe(if coin.spend_info.is_some() {
|
||||
Some(badge::spent())
|
||||
.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);
|
||||
if seq == 0 {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(" 0").small().style(color::RED))
|
||||
.push(
|
||||
icon::hourglass_done_icon()
|
||||
.small()
|
||||
.style(color::RED),
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
} else if seq < timelock as u32 * 10 / 100 {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(format!(" {}", seq))
|
||||
.small()
|
||||
.style(color::ORANGE),
|
||||
)
|
||||
.push(
|
||||
icon::hourglass_icon()
|
||||
.small()
|
||||
.style(color::ORANGE),
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
} else {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(format!(" {}", seq)).small())
|
||||
.push(icon::hourglass_icon().small())
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
}
|
||||
})
|
||||
.push_maybe(if coin.block_height.is_none() {
|
||||
Some(badge::unconfirmed())
|
||||
} else {
|
||||
None
|
||||
coin_sequence_label(seq, timelock as u32)
|
||||
})
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
@ -127,74 +83,83 @@ fn coin_list_view(
|
||||
.push_maybe(if collapsed {
|
||||
Some(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(separation().width(Length::Fill))
|
||||
.padding(10)
|
||||
.spacing(5)
|
||||
.push_maybe(if coin.spend_info.is_none() {
|
||||
if let Some(b) = coin.block_height {
|
||||
if blockheight > b as u32 + timelock as u32 {
|
||||
Some(Container::new(
|
||||
p1_bold("One of the recovery path is available")
|
||||
.style(color::RED),
|
||||
))
|
||||
} else {
|
||||
Some(Container::new(p1_bold(format!(
|
||||
"One of the recovery path will be available in {} blocks",
|
||||
b as u32 + timelock as u32 - blockheight
|
||||
))))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(5)
|
||||
.push_maybe(if coin.spend_info.is_none() {
|
||||
if let Some(b) = coin.block_height {
|
||||
if blockheight > b as u32 + timelock as u32 {
|
||||
Some(Container::new(
|
||||
text("One of the recovery path is available")
|
||||
.bold()
|
||||
.small()
|
||||
.style(color::RED),
|
||||
))
|
||||
} else {
|
||||
Some(Container::new(
|
||||
text(format!("One of the recovery path will be available in {} blocks", b as u32 + timelock as u32 - blockheight))
|
||||
.bold()
|
||||
.small(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(
|
||||
Column::new()
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(p2_regular("Outpoint:").bold().style(color::GREY_2))
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(text("Outpoint:").small().bold())
|
||||
.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(theme::Button::TransparentBorder)
|
||||
))
|
||||
.spacing(5),
|
||||
.push(
|
||||
p2_regular(format!("{}", coin.outpoint))
|
||||
.style(color::GREY_2),
|
||||
)
|
||||
.push(
|
||||
Button::new(icon::clipboard_icon())
|
||||
.on_press(Message::Clipboard(
|
||||
coin.outpoint.to_string(),
|
||||
))
|
||||
.style(theme::Button::TransparentBorder),
|
||||
),
|
||||
)
|
||||
.push_maybe(coin.block_height.map(|b| {
|
||||
Row::new()
|
||||
.push(text("Block height:").small().bold())
|
||||
.push(text(format!("{}", b)).small())
|
||||
.spacing(5)
|
||||
})),
|
||||
.spacing(5),
|
||||
)
|
||||
.push_maybe(coin.spend_info.map(|info| {
|
||||
Column::new()
|
||||
.push_maybe(coin.block_height.map(|b| {
|
||||
Row::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.push(text("Spend txid:").small().bold())
|
||||
.push(text(format!("{}", info.txid)).small())
|
||||
.spacing(5),
|
||||
p2_regular("Block height:").bold().style(color::GREY_2),
|
||||
)
|
||||
.push(if let Some(height) = info.height {
|
||||
Row::new()
|
||||
.push(text("Spend block height:").small().bold())
|
||||
.push(text(format!("{}", height)).small())
|
||||
.spacing(5)
|
||||
} else {
|
||||
Row::new().push(text("Not in a block").bold().small())
|
||||
})
|
||||
.push(p2_regular(format!("{}", b)).style(color::GREY_2))
|
||||
.spacing(5)
|
||||
})),
|
||||
),
|
||||
)
|
||||
.push_maybe(coin.spend_info.map(|info| {
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.push(p2_regular("Spend txid:").bold().style(color::GREY_2))
|
||||
.push(p2_regular(format!("{}", info.txid)))
|
||||
.spacing(5),
|
||||
)
|
||||
.push(if let Some(height) = info.height {
|
||||
Row::new()
|
||||
.push(
|
||||
p2_regular("Spend block height:")
|
||||
.bold()
|
||||
.style(color::GREY_2),
|
||||
)
|
||||
.push(p2_regular(format!("{}", height)))
|
||||
.spacing(5)
|
||||
} else {
|
||||
Row::new().push(
|
||||
p2_regular("Not in a block").bold().style(color::GREY_2),
|
||||
)
|
||||
})
|
||||
.spacing(5)
|
||||
})),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -202,3 +167,68 @@ fn coin_list_view(
|
||||
)
|
||||
.style(theme::Container::Card(theme::Card::Simple))
|
||||
}
|
||||
|
||||
pub fn coin_sequence_label<'a, T: 'a>(seq: u32, timelock: u32) -> Container<'a, T> {
|
||||
if seq == 0 {
|
||||
Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(clock_red_icon().width(Length::Units(20)))
|
||||
.push(p2_regular("Expired"))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Warning))
|
||||
} else if seq < timelock as u32 * 10 / 100 {
|
||||
Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(clock_red_icon().width(Length::Units(20)))
|
||||
.push(p2_regular(expire_message(seq)))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple))
|
||||
} else {
|
||||
Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(clock_icon().width(Length::Units(20)))
|
||||
.push(p2_regular(expire_message(seq)).style(color::GREY_3))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expire_message(sequence: u32) -> String {
|
||||
if sequence <= 144 {
|
||||
"Expires today".to_string()
|
||||
} else if sequence <= 2 * 144 {
|
||||
"Expires in ≈ 2 days".to_string()
|
||||
} else {
|
||||
format!("Expires in {}", expire_message_units(sequence).join(","))
|
||||
}
|
||||
}
|
||||
|
||||
/// returns y,m,d
|
||||
pub fn expire_message_units(sequence: u32) -> Vec<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_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()
|
||||
}
|
||||
|
||||
@ -11,53 +11,77 @@ use liana_ui::{
|
||||
widget::*,
|
||||
};
|
||||
|
||||
use crate::{app::view::message::Message, daemon::model::HistoryTransaction};
|
||||
use crate::{
|
||||
app::view::{coins, message::Message},
|
||||
daemon::model::HistoryTransaction,
|
||||
};
|
||||
|
||||
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)>,
|
||||
unconfirmed_balance: &'a bitcoin::Amount,
|
||||
remaining_sequence: &Option<u32>,
|
||||
number_of_expiring_coins: usize,
|
||||
pending_events: &[HistoryTransaction],
|
||||
events: &Vec<HistoryTransaction>,
|
||||
) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.push(h3("Balance"))
|
||||
.push(amount_with_size(balance, H1_SIZE))
|
||||
.push_maybe(recovery_warning.map(|(a, c)| {
|
||||
Row::new()
|
||||
.spacing(15)
|
||||
.align_items(Alignment::Center)
|
||||
.push(icon::hourglass_icon().size(30).style(color::ORANGE))
|
||||
.push(
|
||||
.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(if number_of_expiring_coins == 0 {
|
||||
remaining_sequence.map(|sequence| {
|
||||
Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(format!(
|
||||
"Recovery path will be soon available for {} coins",
|
||||
c
|
||||
)))
|
||||
.push(text("("))
|
||||
.push(amount(a))
|
||||
.push(text(")")),
|
||||
.spacing(15)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
h4_regular(format!(
|
||||
"Your next coin to expire will in ≈ {}",
|
||||
coins::expire_message_units(sequence).join(",")
|
||||
))
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
icon::tooltip_icon()
|
||||
.size(20)
|
||||
.style(color::GREY_3)
|
||||
.width(Length::Units(20)),
|
||||
)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.padding(10)
|
||||
}))
|
||||
.push_maybe(recovery_alert.map(|(a, c)| {
|
||||
Row::new()
|
||||
.spacing(15)
|
||||
.align_items(Alignment::Center)
|
||||
.push(icon::hourglass_done_icon().style(color::RED))
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(format!("Recovery path is available for {} coins", c)))
|
||||
.push(text("("))
|
||||
.push(amount(a))
|
||||
.push(text(")")),
|
||||
.padding(25)
|
||||
.style(theme::Card::Border)
|
||||
})
|
||||
} else {
|
||||
Some(
|
||||
Container::new(
|
||||
Row::new().spacing(15).align_items(Alignment::Center).push(
|
||||
h4_regular(format!(
|
||||
"You have {} coins that are already or about to be expired",
|
||||
number_of_expiring_coins
|
||||
))
|
||||
.width(Length::Fill),
|
||||
),
|
||||
)
|
||||
.padding(10)
|
||||
}))
|
||||
.padding(25)
|
||||
.style(theme::Card::Invalid),
|
||||
)
|
||||
})
|
||||
.push(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
|
||||
@ -21,7 +21,7 @@ use iced::{
|
||||
|
||||
use liana_ui::{
|
||||
component::{button, text::*},
|
||||
icon::{coin_icon, cross_icon, home_icon, receive_icon, send_icon, settings_icon},
|
||||
icon::{cross_icon, home_icon, receive_icon, send_icon, settings_icon},
|
||||
image::*,
|
||||
theme,
|
||||
util::Collection,
|
||||
@ -73,30 +73,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
Button::new(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.push(coin_icon())
|
||||
.push(text("Coins"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
)
|
||||
.push(
|
||||
Container::new(
|
||||
text(format!(
|
||||
" {} ",
|
||||
cache
|
||||
.coins
|
||||
.iter()
|
||||
// TODO: Remove when cache contains only current coins.
|
||||
.filter(|coin| coin.spend_info.is_none())
|
||||
.count()
|
||||
))
|
||||
.small()
|
||||
.bold(),
|
||||
)
|
||||
.style(theme::Container::Pill(theme::Pill::Primary)),
|
||||
)
|
||||
.push(coins_icon().width(Length::Units(20)))
|
||||
.push(text("Coins"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
@ -112,30 +90,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
Button::new(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.push(coin_icon())
|
||||
.push(text("Coins"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
)
|
||||
.push(
|
||||
Container::new(
|
||||
text(format!(
|
||||
" {} ",
|
||||
cache
|
||||
.coins
|
||||
.iter()
|
||||
// TODO: Remove when cache contains only current coins.
|
||||
.filter(|coin| coin.spend_info.is_none())
|
||||
.count()
|
||||
))
|
||||
.small()
|
||||
.bold(),
|
||||
)
|
||||
.style(theme::Pill::Primary),
|
||||
)
|
||||
.push(coins_icon().width(Length::Units(20)))
|
||||
.push(text("Coins"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
@ -153,26 +109,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
Button::new(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.push(history_icon().width(Length::Units(20)))
|
||||
.push(text("PSBTs"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
)
|
||||
.push_maybe(if cache.spend_txs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Container::new(
|
||||
text(format!(" {} ", cache.spend_txs.len()))
|
||||
.small()
|
||||
.bold(),
|
||||
)
|
||||
.style(theme::Pill::Primary),
|
||||
)
|
||||
})
|
||||
.push(history_icon().width(Length::Units(20)))
|
||||
.push(text("PSBTs"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
@ -188,26 +126,8 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
Button::new(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.push(history_icon().width(Length::Units(20)))
|
||||
.push(text("PSBTs"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
)
|
||||
.push_maybe(if cache.spend_txs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Container::new(
|
||||
text(format!(" {} ", cache.spend_txs.len()))
|
||||
.small()
|
||||
.bold(),
|
||||
)
|
||||
.style(theme::Pill::Primary),
|
||||
)
|
||||
})
|
||||
.push(history_icon().width(Length::Units(20)))
|
||||
.push(text("PSBTs"))
|
||||
.spacing(10)
|
||||
.width(iced::Length::Fill)
|
||||
.align_items(iced::Alignment::Center),
|
||||
@ -284,10 +204,10 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
.push(
|
||||
Container::new(
|
||||
liana_grey_logo()
|
||||
.height(Length::Units(150))
|
||||
.height(Length::Units(120))
|
||||
.width(Length::Units(60)),
|
||||
)
|
||||
.padding(15),
|
||||
.padding(10),
|
||||
)
|
||||
.push(home_button)
|
||||
.push(spend_button)
|
||||
@ -295,7 +215,6 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
.push(coins_button)
|
||||
.push(psbt_button)
|
||||
.push(transactions_button)
|
||||
.spacing(15)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
|
||||
@ -86,7 +86,7 @@ pub fn psbts_view<'a>(spend_txs: &[SpendTx]) -> Element<'a, Message> {
|
||||
),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.spacing(25)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -99,8 +99,8 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
|
||||
.push(badge::spend())
|
||||
.push(if !tx.sigs.recovery_paths().is_empty() {
|
||||
Row::new().push(
|
||||
Container::new(text(" Recovery ").small())
|
||||
.padding(3)
|
||||
Container::new(p2_regular(" Recovery "))
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple)),
|
||||
)
|
||||
} else {
|
||||
|
||||
@ -18,7 +18,7 @@ use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
error::Error,
|
||||
view::{message::*, modal},
|
||||
view::{coins, message::*, modal},
|
||||
},
|
||||
daemon::model::{remaining_sequence, Coin},
|
||||
};
|
||||
@ -207,42 +207,13 @@ fn coin_list_view<'a>(
|
||||
icon::square_icon()
|
||||
})
|
||||
.push(badge::coin())
|
||||
.push_maybe(if coin.spend_info.is_some() {
|
||||
Some(badge::spent())
|
||||
.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);
|
||||
if seq == 0 {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(" 0").small().style(color::RED))
|
||||
.push(icon::hourglass_done_icon().small().style(color::RED))
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
} else if seq < timelock as u32 * 10 / 100 {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(format!(" {}", seq)).small().style(color::ORANGE),
|
||||
)
|
||||
.push(icon::hourglass_icon().small().style(color::ORANGE))
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
} else {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(format!(" {}", seq)).small())
|
||||
.push(icon::hourglass_icon().small())
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
}
|
||||
})
|
||||
.push_maybe(if coin.block_height.is_none() {
|
||||
Some(badge::unconfirmed())
|
||||
} else {
|
||||
None
|
||||
coins::coin_sequence_label(seq, timelock as u32)
|
||||
})
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
|
||||
@ -25,14 +25,18 @@ pub fn transactions_view<'a>(
|
||||
.push(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(
|
||||
pending_txs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(Column::new().spacing(10), |col, (i, tx)| {
|
||||
col.push(tx_list_view(i, tx))
|
||||
}),
|
||||
)
|
||||
.push_maybe(if !pending_txs.is_empty() {
|
||||
Some(
|
||||
pending_txs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(Column::new().spacing(10), |col, (i, tx)| {
|
||||
col.push(tx_list_view(i, tx))
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(
|
||||
txs.iter()
|
||||
.enumerate()
|
||||
@ -63,7 +67,7 @@ pub fn transactions_view<'a>(
|
||||
),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.spacing(30)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -82,7 +86,9 @@ fn tx_list_view<'a>(i: usize, tx: &HistoryTransaction) -> Element<'a, Message> {
|
||||
Container::new(
|
||||
text(format!(
|
||||
"{}",
|
||||
NaiveDateTime::from_timestamp_opt(t as i64, 0).unwrap(),
|
||||
NaiveDateTime::from_timestamp_opt(t as i64, 0)
|
||||
.unwrap()
|
||||
.format("%b. %d, %Y - %T"),
|
||||
))
|
||||
.small(),
|
||||
)
|
||||
@ -143,7 +149,9 @@ pub fn tx_view<'a>(cache: &Cache, tx: &'a HistoryTransaction) -> Element<'a, Mes
|
||||
.push(card::simple(
|
||||
Column::new()
|
||||
.push_maybe(tx.time.map(|t| {
|
||||
let date = NaiveDateTime::from_timestamp_opt(t as i64, 0).unwrap();
|
||||
let date = NaiveDateTime::from_timestamp_opt(t as i64, 0)
|
||||
.unwrap()
|
||||
.format("%b. %d, %Y - %T");
|
||||
Row::new()
|
||||
.width(Length::Fill)
|
||||
.push(Container::new(text("Date:").bold()).width(Length::Fill))
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use iced::{widget::tooltip, Length};
|
||||
|
||||
use crate::{component::text, icon, theme, widget::*};
|
||||
use crate::{component::text, icon, image, theme, widget::*};
|
||||
|
||||
pub struct Badge {
|
||||
icon: crate::widget::Text<'static>,
|
||||
@ -53,19 +53,23 @@ pub fn spend<T>() -> Container<'static, T> {
|
||||
}
|
||||
|
||||
pub fn coin<T>() -> Container<'static, T> {
|
||||
Container::new(icon::coin_icon().width(Length::Units(20)))
|
||||
.width(Length::Units(40))
|
||||
.height(Length::Units(40))
|
||||
.style(theme::Container::Badge(theme::Badge::Standard))
|
||||
.center_x()
|
||||
.center_y()
|
||||
Container::new(
|
||||
image::liana_grey_logo()
|
||||
.height(Length::Units(25))
|
||||
.width(Length::Units(25)),
|
||||
)
|
||||
.width(Length::Units(40))
|
||||
.height(Length::Units(40))
|
||||
.style(theme::Container::Badge(theme::Badge::Standard))
|
||||
.center_x()
|
||||
.center_y()
|
||||
}
|
||||
|
||||
pub fn unconfirmed<'a, T: 'a>() -> Container<'a, T> {
|
||||
Container::new(
|
||||
tooltip::Tooltip::new(
|
||||
Container::new(text::p2_regular(" Unconfirmed "))
|
||||
.padding(3)
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple)),
|
||||
"Do not treat this as a payment until it is confirmed",
|
||||
tooltip::Position::Top,
|
||||
@ -78,7 +82,7 @@ pub fn deprecated<'a, T: 'a>() -> Container<'a, T> {
|
||||
Container::new(
|
||||
tooltip::Tooltip::new(
|
||||
Container::new(text::p2_regular(" Deprecated "))
|
||||
.padding(3)
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple)),
|
||||
"This spend cannot be included anymore in the blockchain",
|
||||
tooltip::Position::Top,
|
||||
@ -91,7 +95,7 @@ pub fn spent<'a, T: 'a>() -> Container<'a, T> {
|
||||
Container::new(
|
||||
tooltip::Tooltip::new(
|
||||
Container::new(text::p2_regular(" Spent "))
|
||||
.padding(3)
|
||||
.padding(10)
|
||||
.style(theme::Container::Pill(theme::Pill::Simple)),
|
||||
"The spend transaction was included in the blockchain",
|
||||
tooltip::Position::Top,
|
||||
|
||||
@ -39,10 +39,13 @@ pub fn confirmed_outgoing_event<'a, T: Clone + 'a>(
|
||||
Container::new(
|
||||
button(
|
||||
row!(
|
||||
row!(badge::spend(), text::p2_regular(date.to_string()))
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.width(Length::Fill),
|
||||
row!(
|
||||
badge::spend(),
|
||||
text::p2_regular(date.format("%b. %d, %Y - %T").to_string())
|
||||
)
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.width(Length::Fill),
|
||||
row!(text::p1_regular("-"), amount::amount(amount))
|
||||
.spacing(5)
|
||||
.align_items(Alignment::Center),
|
||||
@ -87,10 +90,13 @@ pub fn confirmed_incoming_event<'a, T: Clone + 'a>(
|
||||
Container::new(
|
||||
button(
|
||||
row!(
|
||||
row!(badge::receive(), text::p2_regular(date.to_string()))
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.width(Length::Fill),
|
||||
row!(
|
||||
badge::receive(),
|
||||
text::p2_regular(date.format("%b. %d, %Y - %T").to_string())
|
||||
)
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.width(Length::Fill),
|
||||
row!(text::p1_regular("+"), amount::amount(amount))
|
||||
.spacing(5)
|
||||
.align_items(Alignment::Center),
|
||||
|
||||
@ -18,3 +18,21 @@ pub fn history_icon() -> Svg {
|
||||
let h = Handle::from_memory(HISTORY_ICON.to_vec());
|
||||
Svg::new(h)
|
||||
}
|
||||
|
||||
const COINS_ICON: &[u8] = include_bytes!("../static/icons/coins-icon.svg");
|
||||
pub fn coins_icon() -> Svg {
|
||||
let h = Handle::from_memory(COINS_ICON.to_vec());
|
||||
Svg::new(h)
|
||||
}
|
||||
|
||||
const CLOCK_ICON: &[u8] = include_bytes!("../static/icons/clock-icon.svg");
|
||||
pub fn clock_icon() -> Svg {
|
||||
let h = Handle::from_memory(CLOCK_ICON.to_vec());
|
||||
Svg::new(h)
|
||||
}
|
||||
|
||||
const CLOCK_RED_ICON: &[u8] = include_bytes!("../static/icons/clock-red-icon.svg");
|
||||
pub fn clock_red_icon() -> Svg {
|
||||
let h = Handle::from_memory(CLOCK_RED_ICON.to_vec());
|
||||
Svg::new(h)
|
||||
}
|
||||
|
||||
@ -288,6 +288,7 @@ pub enum Pill {
|
||||
Simple,
|
||||
Primary,
|
||||
Success,
|
||||
Warning,
|
||||
}
|
||||
|
||||
impl Pill {
|
||||
@ -312,6 +313,13 @@ impl Pill {
|
||||
border_color: color::GREY_3,
|
||||
text_color: color::GREY_3.into(),
|
||||
},
|
||||
Self::Warning => container::Appearance {
|
||||
background: iced::Color::TRANSPARENT.into(),
|
||||
border_radius: 25.0,
|
||||
border_width: 1.0,
|
||||
border_color: color::RED,
|
||||
text_color: color::RED.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
gui/ui/static/icons/clock-icon.svg
Normal file
6
gui/ui/static/icons/clock-icon.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="13" r="9" stroke="#717171" stroke-width="1.5"/>
|
||||
<path d="M10 1H14" stroke="#717171" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12 1L12 4" stroke="#717171" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12 13L15 10" stroke="#717171" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
6
gui/ui/static/icons/clock-red-icon.svg
Normal file
6
gui/ui/static/icons/clock-red-icon.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="13" r="9" stroke="#E9431F" stroke-width="1.5"/>
|
||||
<path d="M10 1H14" stroke="#E9431F" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12 1L12 4" stroke="#E9431F" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12 13L15 10" stroke="#E9431F" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
3
gui/ui/static/icons/coins-icon.svg
Normal file
3
gui/ui/static/icons/coins-icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="20" height="23" viewBox="0 0 20 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.7189 9.02728L17.8442 10.177L18.2891 10.4269C19.237 10.9595 19.237 12.3325 18.2891 12.865L12.2525 16.2566C10.8524 17.0432 9.14756 17.0432 7.74754 16.2566L1.71092 12.865C0.763024 12.3325 0.763026 10.9595 1.71093 10.4269L2.15585 10.177L4.16334 9.02728M16.1811 14.1461L18.1129 15.1185C19.1092 15.6201 19.1412 17.0398 18.1685 17.5862L12.2525 20.9101C10.8524 21.6966 9.14756 21.6966 7.74754 20.9101L1.9178 17.6347C0.928395 17.0789 0.982913 15.6278 2.0112 15.1487L4.16334 14.1461M12.2525 11.3112L18.2891 7.91961C19.237 7.38705 19.237 6.01405 18.2891 5.48149L12.2525 2.08993C10.8524 1.30336 9.14756 1.30336 7.74754 2.08993L1.71093 5.48149C0.763027 6.01405 0.763024 7.38705 1.71092 7.91961L7.74754 11.3112C9.14756 12.0977 10.8524 12.0977 12.2525 11.3112Z" stroke="#7A7A7A" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
Loading…
x
Reference in New Issue
Block a user