From 6d92056b9d5ea4c64d89845ead13b2fff2a3689d Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 5 Dec 2022 15:29:11 +0100 Subject: [PATCH 1/6] bump liana#38e342c8 --- gui/Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 70d4d897..f77c87fa 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -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", From f0758d28abf2ee255f02bafb0deb806ba4ac98aa Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 30 Nov 2022 10:20:31 +0100 Subject: [PATCH 2/6] spend: add remaining sequence to coins --- gui/src/app/mod.rs | 9 +++-- gui/src/app/state/spend/mod.rs | 4 +-- gui/src/app/state/spend/step.rs | 14 ++++++-- gui/src/app/view/spend/step.rs | 61 +++++++++++++++++++++++++++------ 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 3aab6ded..37114dd7 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -71,9 +71,12 @@ 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(), + ) + .into(), }; self.state.load(self.daemon.clone()) } diff --git a/gui/src/app/state/spend/mod.rs b/gui/src/app/state/spend/mod.rs index e66da1a6..67afbab9 100644 --- a/gui/src/app/state/spend/mod.rs +++ b/gui/src/app/state/spend/mod.rs @@ -104,13 +104,13 @@ pub struct CreateSpendPanel { } impl CreateSpendPanel { - pub fn new(config: Config, coins: &[Coin]) -> Self { + pub fn new(config: Config, coins: &[Coin], timelock: 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::ChooseCoins::new(coins.to_vec(), timelock)), Box::new(step::ChooseFeerate::default()), Box::new(step::SaveSpend::new(config)), ], diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index e519a475..b2c7a0ed 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -244,6 +244,7 @@ impl Step for ChooseFeerate { #[derive(Default)] pub struct ChooseCoins { + timelock: u32, coins: Vec<(Coin, bool)>, /// draft output amount must be superior to total input amount. is_valid: bool, @@ -251,8 +252,9 @@ pub struct ChooseCoins { } impl ChooseCoins { - pub fn new(coins: Vec) -> Self { + pub fn new(coins: Vec, timelock: u32) -> Self { Self { + timelock, coins: coins .into_iter() .filter_map(|c| { @@ -315,8 +317,14 @@ impl Step for ChooseCoins { .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) + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { + view::spend::step::choose_coins_view( + cache, + self.timelock, + &self.coins, + self.total_needed.as_ref(), + self.is_valid, + ) } } diff --git a/gui/src/app/view/spend/step.rs b/gui/src/app/view/spend/step.rs index 57238dc9..36ea6c34 100644 --- a/gui/src/app/view/spend/step.rs +++ b/gui/src/app/view/spend/step.rs @@ -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}, @@ -126,6 +128,8 @@ pub fn choose_feerate_view<'a>( } pub fn choose_coins_view<'a>( + cache: &Cache, + timelock: u32, coins: &[(Coin, bool)], total_needed: Option<&Amount>, is_valid: bool, @@ -136,14 +140,20 @@ pub fn choose_coins_view<'a>( 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() + .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( @@ -164,7 +174,13 @@ pub fn choose_coins_view<'a>( ) } -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 +192,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), From c88c39a8b92854f5d50f267ee82be429c328e011 Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 30 Nov 2022 12:10:02 +0100 Subject: [PATCH 3/6] spend steps: add total sent --- gui/src/app/state/mod.rs | 1 + gui/src/app/state/spend/step.rs | 6 ++++++ gui/src/app/view/mod.rs | 5 +++-- gui/src/app/view/spend/mod.rs | 2 +- gui/src/app/view/spend/step.rs | 30 +++++++++++++++++++++--------- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/gui/src/app/state/mod.rs b/gui/src/app/state/mod.rs index 59a90fda..121bb8c3 100644 --- a/gui/src/app/state/mod.rs +++ b/gui/src/app/state/mod.rs @@ -75,6 +75,7 @@ impl State for Home { false, self.warning.as_ref(), view::home::event_view(cache, &self.events[i]), + None::>, ); } view::dashboard( diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index b2c7a0ed..1342fd0b 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -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()), ) } diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 1b90c8e8..781441a0 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -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>>( +pub fn modal<'a, T: Into>, F: Into>>( is_previous: bool, warning: Option<&Error>, content: T, + fixed_footer: Option, ) -> Element<'a, Message> { Column::new() .push(warn(warning)) @@ -299,6 +300,7 @@ pub fn modal<'a, T: Into>>( .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>>( 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) diff --git a/gui/src/app/view/spend/mod.rs b/gui/src/app/view/spend/mod.rs index a7fe868b..d7f3e0df 100644 --- a/gui/src/app/view/spend/mod.rs +++ b/gui/src/app/view/spend/mod.rs @@ -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)), ), ) diff --git a/gui/src/app/view/spend/step.rs b/gui/src/app/view/spend/step.rs index 36ea6c34..c3743117 100644 --- a/gui/src/app/view/spend/step.rs +++ b/gui/src/app/view/spend/step.rs @@ -25,6 +25,7 @@ use crate::{ pub fn choose_recipients_view( recipients: Vec>, + total_amount: Amount, is_valid: bool, ) -> Element { modal( @@ -42,17 +43,26 @@ pub fn choose_recipients_view( .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), + ), ) } @@ -124,6 +134,7 @@ pub fn choose_feerate_view<'a>( }) .spacing(20) .align_items(Alignment::Center), + None::>, ) } @@ -171,6 +182,7 @@ pub fn choose_coins_view<'a>( }) .spacing(20) .align_items(Alignment::Center), + None::>, ) } From c3471c066540743b3725bd68abbff7779b1e79e6 Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 30 Nov 2022 15:44:05 +0100 Subject: [PATCH 4/6] spend: order coins by remaining sequence and amount --- gui/src/app/mod.rs | 1 + gui/src/app/state/spend/mod.rs | 8 ++++++-- gui/src/app/state/spend/step.rs | 36 ++++++++++++++++++++++----------- gui/src/app/view/spend/step.rs | 2 ++ gui/src/daemon/model.rs | 12 +++++++++++ 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 37114dd7..9403f27a 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -75,6 +75,7 @@ impl App { self.config.clone(), &self.cache.coins, self.daemon.config().main_descriptor.timelock_value(), + self.cache.blockheight as u32, ) .into(), }; diff --git a/gui/src/app/state/spend/mod.rs b/gui/src/app/state/spend/mod.rs index 67afbab9..0bef2239 100644 --- a/gui/src/app/state/spend/mod.rs +++ b/gui/src/app/state/spend/mod.rs @@ -104,13 +104,17 @@ pub struct CreateSpendPanel { } impl CreateSpendPanel { - pub fn new(config: Config, coins: &[Coin], timelock: u32) -> 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(), timelock)), + Box::new(step::ChooseCoins::new( + coins.to_vec(), + timelock, + blockheight, + )), Box::new(step::ChooseFeerate::default()), Box::new(step::SaveSpend::new(config)), ], diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 1342fd0b..04d869c5 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -12,7 +12,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, @@ -258,19 +258,31 @@ pub struct ChooseCoins { } impl ChooseCoins { - pub fn new(coins: Vec, timelock: u32) -> Self { + pub fn new(coins: Vec, 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: coins - .into_iter() - .filter_map(|c| { - if c.spend_info.is_none() { - Some((c, false)) - } else { - None - } - }) - .collect(), + coins, is_valid: false, total_needed: None, } diff --git a/gui/src/app/view/spend/step.rs b/gui/src/app/view/spend/step.rs index c3743117..67cbe38f 100644 --- a/gui/src/app/view/spend/step.rs +++ b/gui/src/app/view/spend/step.rs @@ -40,6 +40,7 @@ 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), ) @@ -152,6 +153,7 @@ pub fn choose_coins_view<'a>( .push(text("Choose coins").bold().size(50)) .push( Column::new() + .padding(10) .spacing(10) .push(coins.iter().enumerate().fold( Column::new().spacing(10), diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index e33c252a..f4ef62fc 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -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, From 70527a002ed04718789ba05389899b731e40146b Mon Sep 17 00:00:00 2001 From: edouard Date: Wed, 30 Nov 2022 17:08:53 +0100 Subject: [PATCH 5/6] spend: display amount left to select --- gui/src/app/state/spend/mod.rs | 1 - gui/src/app/state/spend/step.rs | 245 +++++++++++++++++--------------- gui/src/app/view/spend/step.rs | 79 +++++----- 3 files changed, 161 insertions(+), 164 deletions(-) diff --git a/gui/src/app/state/spend/mod.rs b/gui/src/app/state/spend/mod.rs index 0bef2239..9d10d729 100644 --- a/gui/src/app/state/spend/mod.rs +++ b/gui/src/app/state/spend/mod.rs @@ -115,7 +115,6 @@ impl CreateSpendPanel { timelock, blockheight, )), - Box::new(step::ChooseFeerate::default()), Box::new(step::SaveSpend::new(config)), ], } diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 04d869c5..e931028d 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -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::{ @@ -22,7 +23,6 @@ use crate::{ pub struct TransactionDraft { inputs: Vec, outputs: HashMap, - feerate: u64, generated: Option, } @@ -176,85 +176,16 @@ impl Recipient { } } -#[derive(Default)] -pub struct ChooseFeerate { - feerate: form::Value, - generated: Option, - warning: Option, -} - -impl Step for ChooseFeerate { - fn update( - &mut self, - daemon: Arc, - _cache: &Cache, - draft: &TransactionDraft, - message: Message, - ) -> Command { - match message { - Message::View(view::Message::CreateSpend(view::CreateSpendMessage::FeerateEdited( - s, - ))) => { - if s.parse::().is_ok() { - self.feerate.value = s; - self.feerate.valid = true; - } else if s.is_empty() { - self.feerate.value = "".to_string(); - self.feerate.valid = true; - } else { - self.feerate.valid = false; - } - self.warning = None; - } - Message::View(view::Message::CreateSpend(view::CreateSpendMessage::Generate)) => { - let inputs: Vec = draft.inputs.iter().map(|c| c.outpoint).collect(); - let outputs = draft.outputs.clone(); - let feerate_vb = self.feerate.value.parse::().unwrap_or(0); - self.warning = None; - return Command::perform( - async move { - daemon - .create_spend_tx(&inputs, &outputs, feerate_vb) - .map(|res| res.psbt) - .map_err(|e| e.into()) - }, - Message::Psbt, - ); - } - Message::Psbt(res) => match res { - Ok(psbt) => { - self.generated = Some(psbt); - return Command::perform(async {}, |_| Message::View(view::Message::Next)); - } - Err(e) => self.warning = Some(e), - }, - _ => {} - } - - Command::none() - } - - fn apply(&self, draft: &mut TransactionDraft) { - draft.feerate = self.feerate.value.parse::().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( - &self.feerate, - self.feerate.valid && !self.feerate.value.is_empty(), - self.warning.as_ref(), - ) - } -} - #[derive(Default)] pub struct ChooseCoins { timelock: u32, coins: Vec<(Coin, bool)>, - /// draft output amount must be superior to total input amount. - is_valid: bool, - total_needed: Option, + recipients: Vec<(Address, Amount)>, + + amount_left_to_select: Option, + feerate: form::Value, + generated: Option, + warning: Option, } impl ChooseCoins { @@ -283,48 +214,63 @@ impl ChooseCoins { Self { timelock, coins, - is_valid: false, - total_needed: None, + 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::() { + 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.total_needed = Some(Amount::from_sat( - draft.outputs.values().fold(0, |acc, a| acc + *a), - )); - } - - fn update( - &mut self, - _daemon: Arc, - _cache: &Cache, - _draft: &TransactionDraft, - message: Message, - ) -> Command { - 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::() - > self.total_needed.map(|a| a.to_sat()).unwrap_or(0); - } - - Command::none() + self.recipients = draft + .outputs + .iter() + .map(|(k, v)| (k.clone(), Amount::from_sat(*v))) + .collect(); } fn apply(&self, draft: &mut TransactionDraft) { @@ -333,6 +279,72 @@ impl Step for ChooseCoins { .iter() .filter_map(|(coin, selected)| if *selected { Some(*coin) } else { None }) .collect(); + draft.generated = self.generated.clone(); + } + + fn update( + &mut self, + daemon: Arc, + _cache: &Cache, + draft: &TransactionDraft, + message: Message, + ) -> Command { + match message { + Message::View(view::Message::CreateSpend(view::CreateSpendMessage::FeerateEdited( + s, + ))) => { + if s.parse::().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 = 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::().unwrap_or(0); + self.warning = None; + return Command::perform( + async move { + daemon + .create_spend_tx(&inputs, &outputs, feerate_vb) + .map(|res| res.psbt) + .map_err(|e| e.into()) + }, + Message::Psbt, + ); + } + Message::Psbt(res) => match res { + Ok(psbt) => { + self.generated = Some(psbt); + return Command::perform(async {}, |_| Message::View(view::Message::Next)); + } + 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 view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { @@ -340,8 +352,9 @@ impl Step for ChooseCoins { cache, self.timelock, &self.coins, - self.total_needed.as_ref(), - self.is_valid, + self.amount_left_to_select.as_ref(), + &self.feerate, + self.warning.as_ref(), ) } } diff --git a/gui/src/app/view/spend/step.rs b/gui/src/app/view/spend/step.rs index 67cbe38f..23f7e758 100644 --- a/gui/src/app/view/spend/step.rs +++ b/gui/src/app/view/spend/step.rs @@ -102,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, - 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| { @@ -123,34 +126,6 @@ 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), - None::>, - ) -} - -pub fn choose_coins_view<'a>( - cache: &Cache, - timelock: u32, - 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() .padding(10) @@ -168,23 +143,33 @@ pub fn choose_coins_view<'a>( }, )), ) - .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), - None::>, + 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), + ), ) } From 9d60db3abf04cc03a36462a8b45b7d4da3978339 Mon Sep 17 00:00:00 2001 From: edouard Date: Mon, 5 Dec 2022 18:22:27 +0100 Subject: [PATCH 6/6] fix view: spend detail width --- gui/src/app/view/spend/detail.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/src/app/view/spend/detail.rs b/gui/src/app/view/spend/detail.rs index fc64fe42..5e844bce 100644 --- a/gui/src/app/view/spend/detail.rs +++ b/gui/src/app/view/spend/detail.rs @@ -155,7 +155,9 @@ pub fn spend_modal<'a, T: Into>>( .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()