diff --git a/gui/src/app/view/coins.rs b/gui/src/app/view/coins.rs index 4e0ccf55..790bea90 100644 --- a/gui/src/app/view/coins.rs +++ b/gui/src/app/view/coins.rs @@ -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,47 +60,11 @@ 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 { 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), - )) - } + coin_sequence_label(seq, timelock as u32) }) .push_maybe(if coin.block_height.is_none() { Some(badge::unconfirmed()) @@ -127,74 +86,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 +170,69 @@ 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)) + } +} + +/// 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; + + let units: Vec = [ + (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(); + + format!("Expires in {}", units.join(",")) +} diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 4210afb7..ad9c11fd 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -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, @@ -75,7 +75,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> { Row::new() .push( Row::new() - .push(coin_icon()) + .push(coins_icon().width(Length::Units(20))) .push(text("Coins")) .spacing(10) .width(iced::Length::Fill) @@ -114,7 +114,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> { Row::new() .push( Row::new() - .push(coin_icon()) + .push(coins_icon().width(Length::Units(20))) .push(text("Coins")) .spacing(10) .width(iced::Length::Fill) diff --git a/gui/src/app/view/psbts.rs b/gui/src/app/view/psbts.rs index b7c6ea6c..6c187fb1 100644 --- a/gui/src/app/view/psbts.rs +++ b/gui/src/app/view/psbts.rs @@ -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 { diff --git a/gui/src/app/view/spend/step.rs b/gui/src/app/view/spend/step.rs index cd4288ac..df606985 100644 --- a/gui/src/app/view/spend/step.rs +++ b/gui/src/app/view/spend/step.rs @@ -18,7 +18,7 @@ use crate::{ app::{ cache::Cache, error::Error, - view::{message::*, modal}, + view::{coins, message::*, modal}, }, daemon::model::{remaining_sequence, Coin}, }; @@ -207,37 +207,11 @@ 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 { 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), - )) - } + coins::coin_sequence_label(seq, timelock as u32) }) .push_maybe(if coin.block_height.is_none() { Some(badge::unconfirmed()) diff --git a/gui/src/app/view/transactions.rs b/gui/src/app/view/transactions.rs index 102f64a5..454dfb43 100644 --- a/gui/src/app/view/transactions.rs +++ b/gui/src/app/view/transactions.rs @@ -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() } diff --git a/gui/ui/src/component/badge.rs b/gui/ui/src/component/badge.rs index afa653f9..41e09a53 100644 --- a/gui/ui/src/component/badge.rs +++ b/gui/ui/src/component/badge.rs @@ -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() -> Container<'static, T> { } pub fn coin() -> 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, diff --git a/gui/ui/src/image.rs b/gui/ui/src/image.rs index dde177a3..0403e7e8 100644 --- a/gui/ui/src/image.rs +++ b/gui/ui/src/image.rs @@ -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) +} diff --git a/gui/ui/src/theme.rs b/gui/ui/src/theme.rs index 6dce8354..3a853720 100644 --- a/gui/ui/src/theme.rs +++ b/gui/ui/src/theme.rs @@ -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(), + }, } } } diff --git a/gui/ui/static/icons/clock-icon.svg b/gui/ui/static/icons/clock-icon.svg new file mode 100644 index 00000000..a8a6e077 --- /dev/null +++ b/gui/ui/static/icons/clock-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/ui/static/icons/clock-red-icon.svg b/gui/ui/static/icons/clock-red-icon.svg new file mode 100644 index 00000000..ccc387cc --- /dev/null +++ b/gui/ui/static/icons/clock-red-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/ui/static/icons/coins-icon.svg b/gui/ui/static/icons/coins-icon.svg new file mode 100644 index 00000000..5abe3a3b --- /dev/null +++ b/gui/ui/static/icons/coins-icon.svg @@ -0,0 +1,3 @@ + + +