diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs index 6496df2ef..b7d0ff6e8 100644 --- a/gui/src/app/message.rs +++ b/gui/src/app/message.rs @@ -29,7 +29,7 @@ pub enum Message { Coins(Result, Error>), Labels(Result, Error>), SpendTxs(Result, Error>), - Psbt(Result), + Psbt(Result<(Psbt, Vec), Error>), Recovery(Result), Signed(Fingerprint, Result), WalletRegistered(Result), diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index d5dd1dadc..a4f355d7a 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -32,7 +32,7 @@ pub struct TransactionDraft { network: Network, inputs: Vec, recipients: Vec, - generated: Option, + generated: Option<(Psbt, Vec)>, batch_label: Option, labels: HashMap, } @@ -83,7 +83,7 @@ pub struct DefineSpend { batch_label: form::Value, amount_left_to_select: Option, feerate: form::Value, - generated: Option, + generated: Option<(Psbt, Vec)>, warning: Option, } @@ -384,7 +384,9 @@ impl Step for DefineSpend { .create_spend_tx(&inputs, &outputs, feerate_vb) .map_err(|e| e.into()) .and_then(|res| match res { - CreateSpendResult::Success { psbt, .. } => Ok(psbt), + CreateSpendResult::Success { psbt, warnings } => { + Ok((psbt, warnings)) + } CreateSpendResult::InsufficientFunds { missing } => { Err(SpendCreationError::CoinSelection( liana::spend::InsufficientFunds { missing }, @@ -438,7 +440,7 @@ impl Step for DefineSpend { .filter_map(|(coin, selected)| if *selected { Some(coin) } else { None }) .cloned() .collect(); - if let Some(psbt) = &self.generated { + if let Some((psbt, _)) = &self.generated { draft.labels = self.coins_labels.clone(); for (i, output) in psbt.unsigned_tx.output.iter().enumerate() { if let Some(label) = self @@ -583,7 +585,7 @@ impl Recipient { pub struct SaveSpend { wallet: Arc, - spend: Option, + spend: Option<(psbt::PsbtState, Vec)>, curve: secp256k1::Secp256k1, } @@ -599,7 +601,7 @@ impl SaveSpend { impl Step for SaveSpend { fn load(&mut self, draft: &TransactionDraft) { - let psbt = draft.generated.clone().unwrap(); + let (psbt, warnings) = draft.generated.clone().unwrap(); let mut tx = SpendTx::new( None, psbt, @@ -623,12 +625,15 @@ impl Step for SaveSpend { } } - self.spend = Some(psbt::PsbtState::new(self.wallet.clone(), tx, false)); + self.spend = Some(( + psbt::PsbtState::new(self.wallet.clone(), tx, false), + warnings, + )); } fn subscription(&self) -> Subscription { - if let Some(spend) = &self.spend { - spend.subscription() + if let Some((psbt_state, _)) = &self.spend { + psbt_state.subscription() } else { Subscription::none() } @@ -640,26 +645,27 @@ impl Step for SaveSpend { cache: &Cache, message: Message, ) -> Command { - if let Some(spend) = &mut self.spend { - spend.update(daemon, cache, message) + if let Some((psbt_state, _)) = &mut self.spend { + psbt_state.update(daemon, cache, message) } else { Command::none() } } fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - let spend = self.spend.as_ref().unwrap(); + let (psbt_state, warnings) = self.spend.as_ref().unwrap(); let content = view::spend::spend_view( cache, - &spend.tx, - spend.saved, - &spend.desc_policy, - &spend.wallet.keys_aliases, - spend.labels_edited.cache(), + &psbt_state.tx, + warnings, + psbt_state.saved, + &psbt_state.desc_policy, + &psbt_state.wallet.keys_aliases, + psbt_state.labels_edited.cache(), cache.network, - spend.warning.as_ref(), + psbt_state.warning.as_ref(), ); - if let Some(action) = &spend.action { + if let Some(action) = &psbt_state.action { action.as_ref().view(content) } else { content diff --git a/gui/src/app/view/psbt.rs b/gui/src/app/view/psbt.rs index 081f37fe8..d1fd86153 100644 --- a/gui/src/app/view/psbt.rs +++ b/gui/src/app/view/psbt.rs @@ -73,7 +73,7 @@ pub fn psbt_view<'a>( }), ) .push(spend_header(tx, labels_editing)) - .push(spend_overview_view(tx, desc_info, key_aliases)) + .push(spend_overview_view(tx, desc_info, key_aliases, None)) .push( Column::new() .spacing(20) @@ -255,6 +255,7 @@ pub fn spend_overview_view<'a>( tx: &'a SpendTx, desc_info: &'a LianaPolicy, key_aliases: &'a HashMap, + warnings: Option<&'a Vec>, ) -> Element<'a, Message> { Column::new() .spacing(20) @@ -306,7 +307,8 @@ pub fn spend_overview_view<'a>( .align_items(Alignment::Center), ), ) - .push(signatures(tx, desc_info, key_aliases)), + .push(signatures(tx, desc_info, key_aliases)) + .push_maybe(warnings.map(|w| spend_warnings_view(w))), ) .style(theme::Container::Card(theme::Card::Simple)), ) @@ -336,6 +338,78 @@ pub fn spend_overview_view<'a>( .into() } +fn spend_warnings_button<'a>( + num_warnings: usize, + is_collapsed: bool, +) -> impl Fn() -> Button<'a, liana_ui::component::collapse::Event> { + let color = if num_warnings >= 1 { + color::ORANGE + } else { + color::GREEN + }; + move || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .spacing(20) + .push(p1_bold("Warnings")) + .push( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push(if num_warnings >= 1 { + icon::warning_icon().style(color) + } else { + icon::circle_check_icon().style(color) + }) + .push( + text(format!( + "{} warning{}", + num_warnings, + if num_warnings == 1 { "" } else { "s" } + )) + .style(color), + ) + .width(Length::Fill), + ) + .push(if is_collapsed { + icon::collapsed_icon() + } else { + icon::collapse_icon() + }), + ) + .padding(15) + .width(Length::Fill) + .style(theme::Button::TransparentBorder) + } +} + +fn spend_warnings_view(warnings: &Vec) -> Element { + Container::new(Collapse::new( + spend_warnings_button(warnings.len(), false), + spend_warnings_button(warnings.len(), true), + move || { + Into::>::into( + warnings.iter().fold( + Column::new() + .padding(15) + .spacing(10) + .push(text(if warnings.is_empty() { + "No warnings were generated.".to_string() + } else { + format!( + "The following warning{} generated:", + if warnings.len() > 1 { "s were" } else { " was" } + ) + })), + |col, warning| col.push(text(warning).style(color::GREY_3)), + ), + ) + }, + )) + .into() +} + pub fn signatures<'a>( tx: &'a SpendTx, desc_info: &'a LianaPolicy, diff --git a/gui/src/app/view/spend/mod.rs b/gui/src/app/view/spend/mod.rs index 18dc66a4f..1cdedaf3b 100644 --- a/gui/src/app/view/spend/mod.rs +++ b/gui/src/app/view/spend/mod.rs @@ -33,6 +33,7 @@ use crate::{ pub fn spend_view<'a>( cache: &'a Cache, tx: &'a SpendTx, + spend_warnings: &'a Vec, saved: bool, desc_info: &'a LianaPolicy, key_aliases: &'a HashMap, @@ -48,7 +49,12 @@ pub fn spend_view<'a>( .spacing(20) .push(Container::new(h3("Send")).width(Length::Fill)) .push(psbt::spend_header(tx, labels_editing)) - .push(psbt::spend_overview_view(tx, desc_info, key_aliases)) + .push(psbt::spend_overview_view( + tx, + desc_info, + key_aliases, + if saved { None } else { Some(spend_warnings) }, + )) .push( Column::new() .spacing(20)