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:
edouard 2023-04-21 17:42:46 +02:00
commit 06dfcc428a
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
15 changed files with 398 additions and 347 deletions

View File

@ -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 {

View File

@ -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()
}

View File

@ -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)

View File

@ -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(

View File

@ -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 {

View File

@ -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)

View File

@ -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))

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))
}
}

View File

@ -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,

View File

@ -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),

View File

@ -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)
}

View File

@ -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(),
},
}
}
}

View 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

View 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

View 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