Merge #137: Refac spend steps
9d60db3abf04cc03a36462a8b45b7d4da3978339 fix view: spend detail width (edouard)
70527a002ed04718789ba05389899b731e40146b spend: display amount left to select (edouard)
c3471c066540743b3725bd68abbff7779b1e79e6 spend: order coins by remaining sequence and amount (edouard)
c88c39a8b92854f5d50f267ee82be429c328e011 spend steps: add total sent (edouard)
f0758d28abf2ee255f02bafb0deb806ba4ac98aa spend: add remaining sequence to coins (edouard)
6d92056b9d5ea4c64d89845ead13b2fff2a3689d bump liana#38e342c8 (edouard)
Pull request description:
- [x] Add remaining sequence to coins
- [x] Order coins by remaining sequence or amount
- [x] Move feerate form to coins selection step
- [x] Check amount left to select at coins selection step
ACKs for top commit:
edouardparis:
Self-ACK 9d60db3abf04cc03a36462a8b45b7d4da3978339
Tree-SHA512: 8ecb9a7c75e2ecf2dcd90111d532542a83128209dc9d6057636fc5e6ceb5c1091633b987d64f30c46e1a75a6f27a26e05f469eb8b45b980e1fabd3a798e69a71
This commit is contained in:
commit
552920589e
2
gui/Cargo.lock
generated
2
gui/Cargo.lock
generated
@ -1541,7 +1541,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "liana"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/revault/liana?branch=master#dc23f3667a977dae93cfeb13ee9694e41d020251"
|
||||
source = "git+https://github.com/revault/liana?branch=master#38e342c8cd4c7d5d9d497013aa466fe62c0e3a4b"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"base64",
|
||||
|
||||
@ -71,9 +71,13 @@ impl App {
|
||||
.into(),
|
||||
menu::Menu::Receive => ReceivePanel::default().into(),
|
||||
menu::Menu::Spend => SpendPanel::new(self.config.clone(), &self.cache.spend_txs).into(),
|
||||
menu::Menu::CreateSpendTx => {
|
||||
CreateSpendPanel::new(self.config.clone(), &self.cache.coins).into()
|
||||
}
|
||||
menu::Menu::CreateSpendTx => CreateSpendPanel::new(
|
||||
self.config.clone(),
|
||||
&self.cache.coins,
|
||||
self.daemon.config().main_descriptor.timelock_value(),
|
||||
self.cache.blockheight as u32,
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
self.state.load(self.daemon.clone())
|
||||
}
|
||||
|
||||
@ -75,6 +75,7 @@ impl State for Home {
|
||||
false,
|
||||
self.warning.as_ref(),
|
||||
view::home::event_view(cache, &self.events[i]),
|
||||
None::<Element<view::Message>>,
|
||||
);
|
||||
}
|
||||
view::dashboard(
|
||||
|
||||
@ -104,14 +104,17 @@ pub struct CreateSpendPanel {
|
||||
}
|
||||
|
||||
impl CreateSpendPanel {
|
||||
pub fn new(config: Config, coins: &[Coin]) -> Self {
|
||||
pub fn new(config: Config, coins: &[Coin], timelock: u32, blockheight: u32) -> Self {
|
||||
Self {
|
||||
draft: step::TransactionDraft::default(),
|
||||
current: 0,
|
||||
steps: vec![
|
||||
Box::new(step::ChooseRecipients::default()),
|
||||
Box::new(step::ChooseCoins::new(coins.to_vec())),
|
||||
Box::new(step::ChooseFeerate::default()),
|
||||
Box::new(step::ChooseCoins::new(
|
||||
coins.to_vec(),
|
||||
timelock,
|
||||
blockheight,
|
||||
)),
|
||||
Box::new(step::SaveSpend::new(config)),
|
||||
],
|
||||
}
|
||||
|
||||
@ -3,8 +3,9 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::{Command, Element};
|
||||
use liana::miniscript::bitcoin::{
|
||||
util::psbt::Psbt, Address, Amount, Denomination, OutPoint, Script,
|
||||
use liana::{
|
||||
config::Config as DaemonConfig,
|
||||
miniscript::bitcoin::{util::psbt::Psbt, Address, Amount, Denomination, OutPoint, Script},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -12,7 +13,7 @@ use crate::{
|
||||
cache::Cache, config::Config, error::Error, message::Message, state::spend::detail, view,
|
||||
},
|
||||
daemon::{
|
||||
model::{Coin, SpendTx},
|
||||
model::{remaining_sequence, Coin, SpendTx},
|
||||
Daemon,
|
||||
},
|
||||
ui::component::form,
|
||||
@ -22,7 +23,6 @@ use crate::{
|
||||
pub struct TransactionDraft {
|
||||
inputs: Vec<Coin>,
|
||||
outputs: HashMap<Address, u64>,
|
||||
feerate: u64,
|
||||
generated: Option<Psbt>,
|
||||
}
|
||||
|
||||
@ -94,6 +94,12 @@ impl Step for ChooseRecipients {
|
||||
.enumerate()
|
||||
.map(|(i, recipient)| recipient.view(i).map(view::Message::CreateSpend))
|
||||
.collect(),
|
||||
Amount::from_sat(
|
||||
self.recipients
|
||||
.iter()
|
||||
.map(|r| r.amount().unwrap_or(0_u64))
|
||||
.sum(),
|
||||
),
|
||||
!self.recipients.iter().any(|recipient| !recipient.valid()),
|
||||
)
|
||||
}
|
||||
@ -171,13 +177,111 @@ impl Recipient {
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ChooseFeerate {
|
||||
pub struct ChooseCoins {
|
||||
timelock: u32,
|
||||
coins: Vec<(Coin, bool)>,
|
||||
recipients: Vec<(Address, Amount)>,
|
||||
|
||||
amount_left_to_select: Option<Amount>,
|
||||
feerate: form::Value<String>,
|
||||
generated: Option<Psbt>,
|
||||
warning: Option<Error>,
|
||||
}
|
||||
|
||||
impl Step for ChooseFeerate {
|
||||
impl ChooseCoins {
|
||||
pub fn new(coins: Vec<Coin>, timelock: u32, blockheight: u32) -> Self {
|
||||
let mut coins: Vec<(Coin, bool)> = coins
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
if c.spend_info.is_none() {
|
||||
Some((c, false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
coins.sort_by(|(a, _), (b, _)| {
|
||||
if remaining_sequence(a, blockheight, timelock)
|
||||
== remaining_sequence(b, blockheight, timelock)
|
||||
{
|
||||
// bigger amount first
|
||||
b.amount.cmp(&a.amount)
|
||||
} else {
|
||||
// smallest blockheight (remaining_sequence) first
|
||||
a.block_height.cmp(&b.block_height)
|
||||
}
|
||||
});
|
||||
Self {
|
||||
timelock,
|
||||
coins,
|
||||
recipients: Vec::new(),
|
||||
feerate: form::Value::default(),
|
||||
generated: None,
|
||||
warning: None,
|
||||
amount_left_to_select: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn amount_left_to_select(&mut self, cfg: &DaemonConfig) {
|
||||
let mut tx_size = 0_u64;
|
||||
let mut outgoing_amount = 0_u64;
|
||||
for (address, amount) in &self.recipients {
|
||||
outgoing_amount += amount.to_sat();
|
||||
tx_size += 8 + address.script_pubkey().len() as u64;
|
||||
}
|
||||
|
||||
// change output
|
||||
tx_size += 8 + 34;
|
||||
// overhead
|
||||
tx_size += 11;
|
||||
|
||||
let input_size = cfg
|
||||
.main_descriptor
|
||||
.receive_descriptor()
|
||||
.spender_input_size();
|
||||
|
||||
let mut selected_amount = 0_u64;
|
||||
for (coin, selected) in &self.coins {
|
||||
if *selected {
|
||||
selected_amount += coin.amount.to_sat();
|
||||
tx_size += input_size as u64;
|
||||
}
|
||||
}
|
||||
|
||||
// If feerate is set we can calcul the required amount.
|
||||
if let Ok(feerate) = self.feerate.value.parse::<u64>() {
|
||||
let required_amount = tx_size * feerate + outgoing_amount;
|
||||
|
||||
if selected_amount > required_amount {
|
||||
self.amount_left_to_select = Some(Amount::from_sat(0));
|
||||
} else {
|
||||
self.amount_left_to_select =
|
||||
Some(Amount::from_sat(required_amount - selected_amount));
|
||||
}
|
||||
} else {
|
||||
self.amount_left_to_select = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step for ChooseCoins {
|
||||
fn load(&mut self, draft: &TransactionDraft) {
|
||||
self.recipients = draft
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), Amount::from_sat(*v)))
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn apply(&self, draft: &mut TransactionDraft) {
|
||||
draft.inputs = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(|(coin, selected)| if *selected { Some(*coin) } else { None })
|
||||
.collect();
|
||||
draft.generated = self.generated.clone();
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
@ -192,16 +296,25 @@ impl Step for ChooseFeerate {
|
||||
if s.parse::<u64>().is_ok() {
|
||||
self.feerate.value = s;
|
||||
self.feerate.valid = true;
|
||||
self.amount_left_to_select(daemon.config());
|
||||
} else if s.is_empty() {
|
||||
self.feerate.value = "".to_string();
|
||||
self.feerate.valid = true;
|
||||
self.amount_left_to_select = None;
|
||||
} else {
|
||||
self.feerate.valid = false;
|
||||
self.amount_left_to_select = None;
|
||||
}
|
||||
self.warning = None;
|
||||
}
|
||||
Message::View(view::Message::CreateSpend(view::CreateSpendMessage::Generate)) => {
|
||||
let inputs: Vec<OutPoint> = draft.inputs.iter().map(|c| c.outpoint).collect();
|
||||
let inputs: Vec<OutPoint> = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(
|
||||
|(coin, selected)| if *selected { Some(coin.outpoint) } else { None },
|
||||
)
|
||||
.collect();
|
||||
let outputs = draft.outputs.clone();
|
||||
let feerate_vb = self.feerate.value.parse::<u64>().unwrap_or(0);
|
||||
self.warning = None;
|
||||
@ -222,104 +335,30 @@ impl Step for ChooseFeerate {
|
||||
}
|
||||
Err(e) => self.warning = Some(e),
|
||||
},
|
||||
Message::View(view::Message::CreateSpend(view::CreateSpendMessage::SelectCoin(i))) => {
|
||||
if let Some(coin) = self.coins.get_mut(i) {
|
||||
coin.1 = !coin.1;
|
||||
self.amount_left_to_select(daemon.config());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn apply(&self, draft: &mut TransactionDraft) {
|
||||
draft.feerate = self.feerate.value.parse::<u64>().expect("Checked before");
|
||||
draft.generated = self.generated.clone();
|
||||
}
|
||||
|
||||
fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
view::spend::step::choose_feerate_view(
|
||||
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
view::spend::step::choose_coins_view(
|
||||
cache,
|
||||
self.timelock,
|
||||
&self.coins,
|
||||
self.amount_left_to_select.as_ref(),
|
||||
&self.feerate,
|
||||
self.feerate.valid && !self.feerate.value.is_empty(),
|
||||
self.warning.as_ref(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ChooseCoins {
|
||||
coins: Vec<(Coin, bool)>,
|
||||
/// draft output amount must be superior to total input amount.
|
||||
is_valid: bool,
|
||||
total_needed: Option<Amount>,
|
||||
}
|
||||
|
||||
impl ChooseCoins {
|
||||
pub fn new(coins: Vec<Coin>) -> Self {
|
||||
Self {
|
||||
coins: coins
|
||||
.into_iter()
|
||||
.filter_map(|c| {
|
||||
if c.spend_info.is_none() {
|
||||
Some((c, false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
is_valid: false,
|
||||
total_needed: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step for ChooseCoins {
|
||||
fn load(&mut self, draft: &TransactionDraft) {
|
||||
self.total_needed = Some(Amount::from_sat(
|
||||
draft.outputs.values().fold(0, |acc, a| acc + *a),
|
||||
));
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
_cache: &Cache,
|
||||
_draft: &TransactionDraft,
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
if let Message::View(view::Message::CreateSpend(view::CreateSpendMessage::SelectCoin(i))) =
|
||||
message
|
||||
{
|
||||
if let Some(coin) = self.coins.get_mut(i) {
|
||||
coin.1 = !coin.1;
|
||||
}
|
||||
|
||||
self.is_valid = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(|(coin, selected)| {
|
||||
if *selected {
|
||||
Some(coin.amount.to_sat())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum::<u64>()
|
||||
> self.total_needed.map(|a| a.to_sat()).unwrap_or(0);
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn apply(&self, draft: &mut TransactionDraft) {
|
||||
draft.inputs = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(|(coin, selected)| if *selected { Some(*coin) } else { None })
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
view::spend::step::choose_coins_view(&self.coins, self.total_needed.as_ref(), self.is_valid)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SaveSpend {
|
||||
config: Config,
|
||||
spend: Option<detail::SpendTxState>,
|
||||
|
||||
@ -273,10 +273,11 @@ fn main_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<
|
||||
.height(Length::Fill)
|
||||
}
|
||||
|
||||
pub fn modal<'a, T: Into<Element<'a, Message>>>(
|
||||
pub fn modal<'a, T: Into<Element<'a, Message>>, F: Into<Element<'a, Message>>>(
|
||||
is_previous: bool,
|
||||
warning: Option<&Error>,
|
||||
content: T,
|
||||
fixed_footer: Option<F>,
|
||||
) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.push(warn(warning))
|
||||
@ -299,6 +300,7 @@ pub fn modal<'a, T: Into<Element<'a, Message>>>(
|
||||
.style(container::Style::Background),
|
||||
)
|
||||
.push(modal_section(Container::new(scrollable(content))))
|
||||
.push_maybe(fixed_footer)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
@ -306,7 +308,6 @@ pub fn modal<'a, T: Into<Element<'a, Message>>>(
|
||||
|
||||
fn modal_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<'a, T> {
|
||||
Container::new(menu.max_width(1500))
|
||||
.padding(20)
|
||||
.style(container::Style::Background)
|
||||
.center_x()
|
||||
.width(Length::Fill)
|
||||
|
||||
@ -155,7 +155,9 @@ pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
|
||||
.padding(10)
|
||||
.style(container::Style::Background),
|
||||
)
|
||||
.push(modal_section(Container::new(Scrollable::new(content))))
|
||||
.push(modal_section(Container::new(
|
||||
Container::new(Scrollable::new(content)).max_width(750),
|
||||
)))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
|
||||
@ -22,7 +22,7 @@ pub fn spend_view<'a>(spend_txs: &[SpendTx]) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new().push(Column::new().width(Length::Fill)).push(
|
||||
button::primary(Some(icon::plus_icon()), "Create a new transaction")
|
||||
button::primary(Some(icon::plus_icon()), "New transaction")
|
||||
.on_press(Message::Menu(Menu::CreateSpendTx)),
|
||||
),
|
||||
)
|
||||
|
||||
@ -7,11 +7,13 @@ use liana::miniscript::bitcoin::Amount;
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache,
|
||||
error::Error,
|
||||
view::{message::*, modal},
|
||||
},
|
||||
daemon::model::Coin,
|
||||
ui::{
|
||||
color,
|
||||
component::{
|
||||
badge, button, card, form,
|
||||
text::{text, Text},
|
||||
@ -23,6 +25,7 @@ use crate::{
|
||||
|
||||
pub fn choose_recipients_view(
|
||||
recipients: Vec<Element<Message>>,
|
||||
total_amount: Amount,
|
||||
is_valid: bool,
|
||||
) -> Element<Message> {
|
||||
modal(
|
||||
@ -37,20 +40,30 @@ pub fn choose_recipients_view(
|
||||
button::transparent(Some(icon::plus_icon()), "Add recipient")
|
||||
.on_press(Message::CreateSpend(CreateSpendMessage::AddRecipient)),
|
||||
)
|
||||
.padding(10)
|
||||
.max_width(1000)
|
||||
.spacing(10),
|
||||
)
|
||||
.push_maybe(if is_valid {
|
||||
Some(
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::Next)
|
||||
.width(Length::Units(100)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center),
|
||||
Some(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
Container::new(text(format!("{}", total_amount)).bold())
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(if is_valid {
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::Next)
|
||||
.width(Length::Units(100))
|
||||
} else {
|
||||
button::primary(None, "Next").width(Length::Units(100))
|
||||
}),
|
||||
)
|
||||
.padding(20),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -89,16 +102,19 @@ pub fn recipient_view<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn choose_feerate_view<'a>(
|
||||
pub fn choose_coins_view<'a>(
|
||||
cache: &Cache,
|
||||
timelock: u32,
|
||||
coins: &[(Coin, bool)],
|
||||
amount_left: Option<&Amount>,
|
||||
feerate: &form::Value<String>,
|
||||
is_valid: bool,
|
||||
error: Option<&Error>,
|
||||
) -> Element<'a, Message> {
|
||||
modal(
|
||||
true,
|
||||
None,
|
||||
error,
|
||||
Column::new()
|
||||
.push(text("Choose feerate").bold().size(50))
|
||||
.push(text("Choose coins and feerate").bold().size(50))
|
||||
.push(
|
||||
Container::new(
|
||||
form::Form::new("Feerate", feerate, move |msg| {
|
||||
@ -110,61 +126,60 @@ pub fn choose_feerate_view<'a>(
|
||||
)
|
||||
.width(Length::Units(250)),
|
||||
)
|
||||
.push_maybe(error.map(|e| card::error("Failed to create spend", e.to_string())))
|
||||
.push_maybe(if is_valid {
|
||||
Some(
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::CreateSpend(CreateSpendMessage::Generate))
|
||||
.width(Length::Units(100)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn choose_coins_view<'a>(
|
||||
coins: &[(Coin, bool)],
|
||||
total_needed: Option<&Amount>,
|
||||
is_valid: bool,
|
||||
) -> Element<'a, Message> {
|
||||
modal(
|
||||
true,
|
||||
None,
|
||||
Column::new()
|
||||
.push(text("Choose coins").bold().size(50))
|
||||
.push(
|
||||
Column::new().spacing(10).push(
|
||||
coins
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(Column::new().spacing(10), |col, (i, (coin, selected))| {
|
||||
col.push(coin_list_view(i, coin, *selected))
|
||||
}),
|
||||
),
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.push(coins.iter().enumerate().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (i, (coin, selected))| {
|
||||
col.push(coin_list_view(
|
||||
i,
|
||||
coin,
|
||||
timelock,
|
||||
cache.blockheight as u32,
|
||||
*selected,
|
||||
))
|
||||
},
|
||||
)),
|
||||
)
|
||||
.push_maybe(if is_valid {
|
||||
Some(Container::new(
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::Next)
|
||||
.width(Length::Units(100)),
|
||||
))
|
||||
} else if total_needed.is_some() {
|
||||
Some(Container::new(card::warning(format!(
|
||||
"Total amount must be superior to {}",
|
||||
total_needed.unwrap().to_btc(),
|
||||
))))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center),
|
||||
Some(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
Container::new(if let Some(amount_left) = amount_left {
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text("Amount left to select:"))
|
||||
.push(text(amount_left.to_string()).bold())
|
||||
} else {
|
||||
Row::new().push(text("Please, define feerate"))
|
||||
})
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(if Some(&Amount::from_sat(0)) == amount_left {
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::CreateSpend(CreateSpendMessage::Generate))
|
||||
.width(Length::Units(100))
|
||||
} else {
|
||||
button::primary(None, "Next").width(Length::Units(100))
|
||||
}),
|
||||
)
|
||||
.padding(20),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn coin_list_view<'a>(i: usize, coin: &Coin, selected: bool) -> Element<'a, Message> {
|
||||
fn coin_list_view<'a>(
|
||||
i: usize,
|
||||
coin: &Coin,
|
||||
timelock: u32,
|
||||
blockheight: u32,
|
||||
selected: bool,
|
||||
) -> Element<'a, Message> {
|
||||
Container::new(
|
||||
Button::new(
|
||||
Row::new()
|
||||
@ -176,7 +191,32 @@ fn coin_list_view<'a>(i: usize, coin: &Coin, selected: bool) -> Element<'a, Mess
|
||||
icon::square_icon()
|
||||
})
|
||||
.push(badge::coin())
|
||||
.push(text(format!("block: {}", coin.block_height.unwrap_or(0))).small())
|
||||
.push_maybe(if let Some(b) = coin.block_height {
|
||||
if blockheight > b as u32 + timelock {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(text(" 0").small().style(color::ALERT))
|
||||
.push(
|
||||
icon::hourglass_done_icon().small().style(color::ALERT),
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
))
|
||||
} else {
|
||||
Some(Container::new(
|
||||
Row::new()
|
||||
.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),
|
||||
|
||||
@ -8,6 +8,18 @@ pub use liana::{
|
||||
|
||||
pub type Coin = ListCoinsEntry;
|
||||
|
||||
pub fn remaining_sequence(coin: &Coin, blockheight: u32, timelock: u32) -> u32 {
|
||||
if let Some(coin_blockheight) = coin.block_height {
|
||||
if blockheight > coin_blockheight as u32 + timelock {
|
||||
0
|
||||
} else {
|
||||
coin_blockheight as u32 + timelock - blockheight
|
||||
}
|
||||
} else {
|
||||
timelock
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpendTx {
|
||||
pub coins: Vec<Coin>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user