Merge #95: Add check height to coins panel
34b833f1484f1979087ad76847cc2942c06d0817 Add check height to coins panel (edouard) Pull request description: close #82 ACKs for top commit: edouardparis: Self-ACK 34b833f1484f1979087ad76847cc2942c06d0817 Tree-SHA512: 0d7ff21265d9efd6aabe17dcdca482a13b906a46027f90e43241191e5dd4f74cf5056832e54052bc995258a5ff75a3f15b5a159c127f00049402249a84caa668
This commit is contained in:
commit
1de3678409
81
gui/Cargo.lock
generated
81
gui/Cargo.lock
generated
@ -23,6 +23,12 @@ dependencies = [
|
||||
"mach 0.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
version = "0.2.15"
|
||||
@ -86,6 +92,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aliasable"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
@ -1159,6 +1171,17 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_lazy"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "333979d705964832864ee7676516ab3c3df4ab0b65efb603c86a256d4adbec6f"
|
||||
dependencies = [
|
||||
"iced_native",
|
||||
"iced_pure",
|
||||
"ouroboros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced_native"
|
||||
version = "0.5.1"
|
||||
@ -1624,7 +1647,9 @@ dependencies = [
|
||||
"dirs",
|
||||
"fern",
|
||||
"iced",
|
||||
"iced_lazy",
|
||||
"iced_native",
|
||||
"iced_pure",
|
||||
"log",
|
||||
"minisafe",
|
||||
"serde",
|
||||
@ -1920,6 +1945,30 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f357ef82d1b4db66fbed0b8d542cbd3c22d0bf5b393b3c257b9ba4568e70c9c3"
|
||||
dependencies = [
|
||||
"aliasable",
|
||||
"ouroboros_macro",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros_macro"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44a0b52c2cbaef7dffa5fec1a43274afe8bd2a644fa9fc50a9ef4ff0269b1257"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.15.0"
|
||||
@ -2012,6 +2061,30 @@ dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.41"
|
||||
@ -2537,6 +2610,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@ -2725,7 +2804,7 @@ version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if 1.0.0",
|
||||
"rand 0.8.5",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
@ -21,6 +21,8 @@ base64 = "0.13"
|
||||
|
||||
iced = { version = "0.4", default-features= false, features = ["tokio", "wgpu", "svg", "qr_code", "pure"] }
|
||||
iced_native = "0.5"
|
||||
iced_lazy = { version = "0.1.1", features = ["pure"] }
|
||||
iced_pure = "0.2.2"
|
||||
|
||||
tokio = {version = "1.21.0", features = ["signal"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@ -65,7 +65,11 @@ impl App {
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::Home => Home::new(&self.cache.coins).into(),
|
||||
menu::Menu::Coins => CoinsPanel::new(&self.cache.coins).into(),
|
||||
menu::Menu::Coins => CoinsPanel::new(
|
||||
&self.cache.coins,
|
||||
self.daemon.config().main_descriptor.timelock_value(),
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::Receive => ReceivePanel::default().into(),
|
||||
menu::Menu::Spend => SpendPanel::new(self.config.clone(), &self.cache.spend_txs).into(),
|
||||
menu::Menu::CreateSpendTx => {
|
||||
|
||||
@ -12,14 +12,26 @@ pub struct CoinsPanel {
|
||||
coins: Vec<Coin>,
|
||||
selected_coin: Option<usize>,
|
||||
warning: Option<Error>,
|
||||
/// timelock value to pass for the heir to consume a coin.
|
||||
timelock: u32,
|
||||
}
|
||||
|
||||
impl CoinsPanel {
|
||||
pub fn new(coins: &[Coin]) -> Self {
|
||||
pub fn new(coins: &[Coin], timelock: u32) -> Self {
|
||||
Self {
|
||||
coins: coins.to_owned(),
|
||||
coins: coins
|
||||
.iter()
|
||||
.filter_map(|coin| {
|
||||
if coin.spend_info.is_none() {
|
||||
Some(*coin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
selected_coin: None,
|
||||
warning: None,
|
||||
timelock,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +42,7 @@ impl State for CoinsPanel {
|
||||
&Menu::Coins,
|
||||
cache,
|
||||
self.warning.as_ref(),
|
||||
view::coins::coins_view(&self.coins),
|
||||
view::coins::coins_view(cache, &self.coins, self.timelock),
|
||||
)
|
||||
}
|
||||
|
||||
@ -45,7 +57,16 @@ impl State for CoinsPanel {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(coins) => {
|
||||
self.warning = None;
|
||||
self.coins = coins;
|
||||
self.coins = coins
|
||||
.iter()
|
||||
.filter_map(|coin| {
|
||||
if coin.spend_info.is_none() {
|
||||
Some(*coin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
},
|
||||
Message::View(view::Message::Close) => {
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
use iced::{
|
||||
pure::{button, column, container, row, Element},
|
||||
pure::{column, container, row, Element},
|
||||
Alignment, Length,
|
||||
};
|
||||
|
||||
use crate::ui::{
|
||||
component::{badge, button::Style, card, text::*},
|
||||
color,
|
||||
component::{badge, card, collapse::collapse, separation, text::*},
|
||||
icon,
|
||||
util::Collection,
|
||||
};
|
||||
|
||||
use crate::{app::view::message::Message, daemon::model::Coin};
|
||||
use crate::{
|
||||
app::{cache::Cache, view::message::Message},
|
||||
daemon::model::Coin,
|
||||
};
|
||||
|
||||
pub fn coins_view<'a>(coins: &[Coin]) -> Element<'a, Message> {
|
||||
pub fn coins_view<'a>(cache: &Cache, coins: &'a [Coin], timelock: u32) -> Element<'a, Message> {
|
||||
column()
|
||||
.push(
|
||||
container(
|
||||
@ -21,32 +26,64 @@ pub fn coins_view<'a>(coins: &[Coin]) -> Element<'a, Message> {
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
column().spacing(10).push(
|
||||
coins
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(column().spacing(10), |col, (i, coin)| {
|
||||
col.push(coin_list_view(i, coin))
|
||||
}),
|
||||
),
|
||||
column()
|
||||
.spacing(10)
|
||||
.push(coins.iter().fold(column().spacing(10), |col, coin| {
|
||||
col.push(coin_list_view(coin, timelock, cache.blockheight as u32))
|
||||
})),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn coin_list_view<'a>(i: usize, coin: &Coin) -> Element<'a, Message> {
|
||||
container(
|
||||
button(
|
||||
row()
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
fn coin_list_view(coin: &Coin, timelock: u32, blockheight: u32) -> Element<Message> {
|
||||
container(collapse::<_, _, _, _, _>(
|
||||
move || {
|
||||
row::<Message, _>()
|
||||
.push(
|
||||
row()
|
||||
.push(badge::coin())
|
||||
.push_maybe(coin.spend_info.map(|_| {
|
||||
container(text(" Spent ").small())
|
||||
.padding(3)
|
||||
.style(badge::PillStyle::Success)
|
||||
}))
|
||||
.push_maybe(if coin.spend_info.is_some() {
|
||||
Some(
|
||||
container(text(" Spent ").small())
|
||||
.padding(3)
|
||||
.style(badge::PillStyle::Success),
|
||||
)
|
||||
} else {
|
||||
if let Some(b) = coin.block_height {
|
||||
if blockheight > b as u32 + timelock {
|
||||
Some(container(
|
||||
row()
|
||||
.spacing(5)
|
||||
.push(text(" 0").small().color(color::ALERT))
|
||||
.push(
|
||||
icon::hourglass_done_icon()
|
||||
.small()
|
||||
.color(color::ALERT),
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
} else {
|
||||
Some(container(
|
||||
row()
|
||||
.spacing(5)
|
||||
.push(
|
||||
text(&format!(
|
||||
" {}",
|
||||
b as u32 + timelock - blockheight
|
||||
))
|
||||
.small(),
|
||||
)
|
||||
.push(icon::hourglass_icon().small())
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.width(Length::Fill),
|
||||
@ -57,12 +94,79 @@ fn coin_list_view<'a>(i: usize, coin: &Coin) -> Element<'a, Message> {
|
||||
.width(Length::Shrink),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20),
|
||||
)
|
||||
.padding(10)
|
||||
.on_press(Message::Select(i))
|
||||
.style(Style::TransparentBorder),
|
||||
)
|
||||
.spacing(20)
|
||||
.into()
|
||||
},
|
||||
move || {
|
||||
column()
|
||||
.spacing(10)
|
||||
.push(separation().width(Length::Fill))
|
||||
.push(
|
||||
column()
|
||||
.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 {
|
||||
Some(container(
|
||||
text("The recovery path is available")
|
||||
.bold()
|
||||
.small()
|
||||
.color(color::ALERT),
|
||||
))
|
||||
} else {
|
||||
Some(container(
|
||||
text(&format!(
|
||||
"The recovery path will be available in {} blocks",
|
||||
b as u32 + timelock - blockheight
|
||||
))
|
||||
.bold()
|
||||
.small(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(
|
||||
column()
|
||||
.push(
|
||||
row()
|
||||
.push(text("Outpoint:").small().bold())
|
||||
.push(text(&format!("{}", coin.outpoint)).small())
|
||||
.spacing(5),
|
||||
)
|
||||
.push_maybe(coin.block_height.map(|b| {
|
||||
row()
|
||||
.push(text("Block height:").small().bold())
|
||||
.push(text(&format!("{}", b)).small())
|
||||
.spacing(5)
|
||||
})),
|
||||
)
|
||||
.push_maybe(coin.spend_info.map(|info| {
|
||||
column()
|
||||
.push(
|
||||
row()
|
||||
.push(text("Spend txid:").small().bold())
|
||||
.push(text(&format!("{}", info.txid)).small())
|
||||
.spacing(5),
|
||||
)
|
||||
.push(if let Some(height) = info.height {
|
||||
row()
|
||||
.push(text("Spend block height:").small().bold())
|
||||
.push(text(&format!("{}", height)).small())
|
||||
.spacing(5)
|
||||
} else {
|
||||
row().push(text("Not in a block").bold().small())
|
||||
})
|
||||
.spacing(5)
|
||||
})),
|
||||
)
|
||||
.into()
|
||||
},
|
||||
))
|
||||
.style(card::SimpleCardStyle)
|
||||
.into()
|
||||
}
|
||||
|
||||
95
gui/src/ui/component/collapse.rs
Normal file
95
gui/src/ui/component/collapse.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use iced::pure::{button, column};
|
||||
use iced_lazy::pure::{self, Component};
|
||||
use iced_native::text;
|
||||
use iced_pure::Element;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::ui::component::button::Style;
|
||||
|
||||
pub fn collapse<
|
||||
'a,
|
||||
Message: 'a,
|
||||
T: Into<Message> + Clone + 'a,
|
||||
Renderer: text::Renderer + 'static,
|
||||
H: Fn() -> Element<'a, T, Renderer> + 'a,
|
||||
C: Fn() -> Element<'a, T, Renderer> + 'a,
|
||||
>(
|
||||
header: H,
|
||||
content: C,
|
||||
) -> impl Into<Element<'a, Message, Renderer>> {
|
||||
Collapse {
|
||||
header,
|
||||
content,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
struct Collapse<'a, H, C> {
|
||||
header: H,
|
||||
content: C,
|
||||
phantom: PhantomData<&'a H>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Event<T> {
|
||||
Internal(T),
|
||||
Collapse(bool),
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, T, H, C> Component<Message, Renderer> for Collapse<'a, H, C>
|
||||
where
|
||||
T: Into<Message> + Clone + 'a,
|
||||
H: Fn() -> Element<'a, T, Renderer>,
|
||||
C: Fn() -> Element<'a, T, Renderer>,
|
||||
Renderer: text::Renderer + 'static,
|
||||
{
|
||||
type State = bool;
|
||||
type Event = Event<T>;
|
||||
|
||||
fn update(&mut self, state: &mut Self::State, event: Event<T>) -> Option<Message> {
|
||||
match event {
|
||||
Event::Internal(e) => Some(e.into()),
|
||||
Event::Collapse(s) => {
|
||||
*state = s;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, state: &Self::State) -> Element<Self::Event, Renderer> {
|
||||
if *state {
|
||||
column()
|
||||
.push(
|
||||
button((self.header)().map(Event::Internal))
|
||||
.style(Style::TransparentBorder)
|
||||
.padding(10)
|
||||
.on_press(Event::Collapse(false)),
|
||||
)
|
||||
.push((self.content)().map(Event::Internal))
|
||||
.into()
|
||||
} else {
|
||||
column()
|
||||
.push(
|
||||
button((self.header)().map(Event::Internal))
|
||||
.padding(10)
|
||||
.style(Style::TransparentBorder)
|
||||
.on_press(Event::Collapse(true)),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, T, H: 'a, C: 'a> From<Collapse<'a, H, C>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'static + text::Renderer,
|
||||
T: Into<Message> + Clone + 'a,
|
||||
H: Fn() -> Element<'a, T, Renderer>,
|
||||
C: Fn() -> Element<'a, T, Renderer>,
|
||||
{
|
||||
fn from(c: Collapse<'a, H, C>) -> Self {
|
||||
pure::component(c)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
pub mod badge;
|
||||
pub mod button;
|
||||
pub mod card;
|
||||
pub mod collapse;
|
||||
pub mod form;
|
||||
pub mod text;
|
||||
|
||||
|
||||
@ -14,6 +14,14 @@ fn icon(unicode: char) -> Text {
|
||||
.size(20)
|
||||
}
|
||||
|
||||
pub fn hourglass_icon() -> Text {
|
||||
icon('\u{F41F}')
|
||||
}
|
||||
|
||||
pub fn hourglass_done_icon() -> Text {
|
||||
icon('\u{F41E}')
|
||||
}
|
||||
|
||||
pub fn vault_icon() -> Text {
|
||||
icon('\u{F65A}')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user