diff --git a/gui/src/app/state/psbt.rs b/gui/src/app/state/psbt.rs index d6d2a832..cc1f1dae 100644 --- a/gui/src/app/state/psbt.rs +++ b/gui/src/app/state/psbt.rs @@ -30,7 +30,7 @@ use crate::{ hw::{list_hardware_wallets, HardwareWallet}, }; -trait Action { +pub trait Action { fn warning(&self) -> Option<&Error> { None } @@ -49,11 +49,11 @@ trait Action { } pub struct PsbtState { - wallet: Arc, - desc_policy: LianaPolicy, - tx: SpendTx, - saved: bool, - action: Option>, + pub wallet: Arc, + pub desc_policy: LianaPolicy, + pub tx: SpendTx, + pub saved: bool, + pub action: Option>, } impl PsbtState { @@ -129,7 +129,7 @@ impl PsbtState { } pub fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - let content = view::psbt::spend_view( + let content = view::psbt::psbt_view( cache, &self.tx, self.saved, diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 206c9be9..1f58f805 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -10,7 +10,10 @@ use liana::{ }, }; -use liana_ui::{component::form, widget::Element}; +use liana_ui::{ + component::{form, modal}, + widget::Element, +}; use crate::{ app::{cache::Cache, error::Error, message::Message, state::psbt, view, wallet::Wallet}, @@ -427,6 +430,21 @@ impl Step for SaveSpend { } fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { - self.spend.as_ref().unwrap().view(cache) + let spend = self.spend.as_ref().unwrap(); + let content = view::spend::spend_view( + cache, + &spend.tx, + spend.saved, + &spend.desc_policy, + &spend.wallet.keys_aliases, + cache.network, + ); + if let Some(action) = &spend.action { + modal::Modal::new(content, action.view()) + .on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel))) + .into() + } else { + content + } } } diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index c7c3d787..5844948e 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -121,7 +121,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> { .center_x(), ) .style(theme::Button::Menu(true)) - .on_press(Message::Reload) + .on_press(Message::Menu(Menu::PSBTs)) .width(iced::Length::Fill) } else { Button::new( diff --git a/gui/src/app/view/psbt.rs b/gui/src/app/view/psbt.rs index 9d2a7731..e1178cd0 100644 --- a/gui/src/app/view/psbt.rs +++ b/gui/src/app/view/psbt.rs @@ -16,11 +16,7 @@ use liana::{ use liana_ui::{ color, component::{ - amount::*, - badge, button, card, - collapse::Collapse, - form, hw, separation, - text::{text, Text}, + amount::*, badge, button, card, collapse::Collapse, form, hw, separation, text::*, }, icon, theme, util::Collection, @@ -31,27 +27,44 @@ use crate::{ app::{ cache::Cache, error::Error, + menu::Menu, view::{dashboard, hw::hw_list_view, message::*, warning::warn}, }, daemon::model::{Coin, SpendStatus, SpendTx}, hw::HardwareWallet, }; -pub fn spend_view<'a>( +pub fn psbt_view<'a>( cache: &'a Cache, tx: &'a SpendTx, - _saved: bool, + saved: bool, desc_info: &'a LianaPolicy, key_aliases: &'a HashMap, network: Network, ) -> Element<'a, Message> { dashboard( - &crate::app::menu::Menu::CreateSpendTx, - &cache, + &Menu::PSBTs, + cache, None, Column::new() - .align_items(Alignment::Center) .spacing(20) + .push( + Row::new() + .align_items(Alignment::Center) + .spacing(10) + .push(Container::new(h3("PSBT")).width(Length::Fill)) + .push_maybe(if !tx.sigs.recovery_paths().is_empty() { + Some(badge::recovery()) + } else { + None + }) + .push_maybe(match tx.status { + SpendStatus::Deprecated => Some(badge::deprecated()), + SpendStatus::Broadcast => Some(badge::unconfirmed()), + SpendStatus::Spent => Some(badge::spent()), + _ => None, + }), + ) .push(spend_header(tx)) .push(spend_overview_view(tx, desc_info, key_aliases)) .push(inputs_and_outputs_view( @@ -60,7 +73,25 @@ pub fn spend_view<'a>( network, Some(tx.change_indexes.clone()), None, - )), + )) + .push(if saved { + Row::new() + .push( + button::secondary(None, "Delete") + .width(Length::Units(200)) + .on_press(Message::Spend(SpendTxMessage::Delete)), + ) + .width(Length::Fill) + } else { + Row::new() + .push(Space::with_width(Length::Fill)) + .push( + button::secondary(None, "Save") + .width(Length::Units(150)) + .on_press(Message::Spend(SpendTxMessage::Save)), + ) + .width(Length::Fill) + }), ) } @@ -151,86 +182,21 @@ pub fn delete_action<'a>(warning: Option<&Error>, deleted: bool) -> Element<'a, } } -pub fn spend_modal<'a, T: Into>>( - saved: bool, - warning: Option<&Error>, - content: T, -) -> Element<'a, Message> { - Column::new() - .push(warn(warning)) - .push( - Container::new( - Row::new() - .push(if saved { - Column::new() - .push( - button::alert(Some(icon::trash_icon()), "Delete") - .on_press(Message::Spend(SpendTxMessage::Delete)), - ) - .width(Length::Fill) - } else { - Column::new() - .push( - button::transparent(None, "< Previous").on_press(Message::Previous), - ) - .width(Length::Fill) - }) - .align_items(iced::Alignment::Center) - .push(if saved { - button::primary(Some(icon::cross_icon()), "Close").on_press(Message::Close) - } else { - button::primary(Some(icon::cross_icon()), "Close") - .on_press(Message::Spend(SpendTxMessage::Save)) - }), - ) - .padding(10) - .style(theme::Container::Background), - ) - .push( - Container::new(scrollable( - Container::new(Container::new(content).max_width(800)) - .width(Length::Fill) - .center_x(), - )) - .height(Length::Fill) - .style(theme::Container::Background), - ) - .width(Length::Fill) - .height(Length::Fill) - .into() -} - pub fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> { Column::new() .spacing(20) - .align_items(Alignment::Center) - .push( - Row::new() - .push(badge::Badge::new(icon::send_icon()).style(theme::Badge::Standard)) - .push(if !tx.sigs.recovery_paths().is_empty() { - text("Recovery").bold() - } else if tx.spend_amount == Amount::from_sat(0) { - text("Self send").bold() - } else { - text("Spend").bold() - }) - .spacing(5) - .align_items(Alignment::Center), - ) - .push_maybe(match tx.status { - SpendStatus::Deprecated => Some(badge::deprecated()), - SpendStatus::Broadcast => Some(badge::unconfirmed()), - SpendStatus::Spent => Some(badge::spent()), - _ => None, - }) .push( Column::new() - .align_items(Alignment::Center) - .push(amount_with_size(&tx.spend_amount, 50)) + .push(if tx.is_self_send() { + Container::new(h1("Self send")) + } else { + Container::new(amount_with_size(&tx.spend_amount, H1_SIZE)) + }) .push( Row::new() - .push(text("Miner fee: ")) - .push(amount(&tx.fee_amount)), + .align_items(Alignment::Center) + .push(h3("Miner fee: ").style(color::GREY_3)) + .push(amount_with_size(&tx.fee_amount, H3_SIZE)), ), ) .into() @@ -241,48 +207,84 @@ pub fn spend_overview_view<'a>( desc_info: &'a LianaPolicy, key_aliases: &'a HashMap, ) -> Element<'a, Message> { - Container::new( - Column::new() - .push( + Column::new() + .spacing(20) + .push( + Container::new( Column::new() - .padding(15) - .spacing(10) .push( - Row::new() - .align_items(Alignment::Center) - .push(text("PSBT:").bold().width(Length::Fill)) + Column::new() + .padding(15) + .spacing(10) .push( Row::new() - .spacing(5) + .align_items(Alignment::Center) + .push(text("PSBT").bold().width(Length::Fill)) .push( - button::secondary(Some(icon::clipboard_icon()), "Copy") - .on_press(Message::Clipboard(tx.psbt.to_string())), + Row::new() + .spacing(5) + .push( + button::secondary( + Some(icon::clipboard_icon()), + "Copy", + ) + .on_press(Message::Clipboard(tx.psbt.to_string())), + ) + .push( + button::secondary( + Some(icon::import_icon()), + "Update", + ) + .on_press(Message::Spend(SpendTxMessage::EditPsbt)), + ), + ) + .align_items(Alignment::Center), + ) + .push( + Row::new() + .push(p1_bold("Tx ID").width(Length::Fill)) + .push( + p2_regular(tx.psbt.unsigned_tx.txid().to_string()) + .style(color::GREY_3), ) .push( - button::secondary(Some(icon::import_icon()), "Update") - .on_press(Message::Spend(SpendTxMessage::EditPsbt)), - ), - ) - .align_items(Alignment::Center), + Button::new(icon::clipboard_icon().style(color::GREY_3)) + .on_press(Message::Clipboard( + tx.psbt.unsigned_tx.txid().to_string(), + )) + .style(theme::Button::TransparentBorder), + ) + .align_items(Alignment::Center), + ), ) - .push( - Row::new() - .push(text("Tx ID:").bold().width(Length::Fill)) - .push(text(tx.psbt.unsigned_tx.txid().to_string()).small()) - .push( - Button::new(icon::clipboard_icon()) - .on_press(Message::Clipboard( - tx.psbt.unsigned_tx.txid().to_string(), - )) - .style(theme::Button::TransparentBorder), - ) - .align_items(Alignment::Center), - ), + .push(signatures(tx, desc_info, key_aliases)), ) - .push(signatures(tx, desc_info, key_aliases)), - ) - .style(theme::Container::Card(theme::Card::Simple)) - .into() + .style(theme::Container::Card(theme::Card::Simple)), + ) + .push_maybe(if tx.status == SpendStatus::Pending { + Some( + Row::new() + .push(Space::with_width(Length::Fill)) + .push_maybe(if tx.path_ready().is_none() { + Some( + button::primary(None, "Sign") + .on_press(Message::Spend(SpendTxMessage::Sign)) + .width(Length::Units(150)), + ) + } else { + Some( + button::primary(None, "Broadcast") + .on_press(Message::Spend(SpendTxMessage::Broadcast)) + .width(Length::Units(150)), + ) + }) + .align_items(Alignment::Center) + .spacing(20), + ) + } else { + None + }) + .into() } pub fn signatures<'a>( @@ -298,9 +300,11 @@ pub fn signatures<'a>( Row::new() .spacing(5) .align_items(Alignment::Center) + .spacing(10) + .push(p1_bold("Status")) .push(icon::circle_check_icon().style(color::GREEN)) .push(text("Ready").bold().style(color::GREEN)) - .push(text(", signed by")) + .push(text(" signed by")) .push( sigs.signed_pubkeys .keys() @@ -309,7 +313,7 @@ pub fn signatures<'a>( Container::new( tooltip::Tooltip::new( Container::new(text(alias)) - .padding(3) + .padding(10) .style(theme::Container::Pill(theme::Pill::Simple)), value.0.to_string(), tooltip::Position::Bottom, @@ -318,7 +322,7 @@ pub fn signatures<'a>( ) } else { Container::new(text(value.0.to_string())) - .padding(3) + .padding(10) .style(theme::Container::Pill(theme::Pill::Simple)) }) }), @@ -332,11 +336,13 @@ pub fn signatures<'a>( Button::new( Row::new() .align_items(Alignment::Center) + .spacing(20) + .push(p1_bold("Status")) .push(Row::new() .spacing(5) .align_items(Alignment::Center) - .push(icon::circle_cross_icon()) - .push(text("Not ready").bold()) + .push(icon::circle_cross_icon().style(color::RED)) + .push(text("Not ready").style(color::RED)) .width(Length::Fill) ) .push(icon::collapse_icon()), @@ -349,12 +355,14 @@ pub fn signatures<'a>( Button::new( Row::new() .align_items(Alignment::Center) + .spacing(20) + .push(p1_bold("Status")) .push( Row::new() .spacing(5) .align_items(Alignment::Center) - .push(icon::circle_cross_icon()) - .push(text("Not ready").bold()) + .push(icon::circle_cross_icon().style(color::RED)) + .push(text("Not ready").style(color::RED)) .width(Length::Fill) ) .push(icon::collapsed_icon()), @@ -365,7 +373,6 @@ pub fn signatures<'a>( }, move || { Into::>::into( - Column::new().push(separation().width(Length::Fill)).push( Column::new() .padding(15) .spacing(10) @@ -383,38 +390,9 @@ pub fn signatures<'a>( let keys = &desc_info.recovery_paths()[seq]; col.push(path_view(keys, path, keys_aliases)) })), - ), ) }, ))}) - .push_maybe(if tx.status == SpendStatus::Pending { - Some( - Column::new().push(separation().width(Length::Fill)).push( - Container::new( - Row::new() - .push(Space::with_width(Length::Fill)) - .push_maybe(if tx.path_ready().is_none() { - Some( - button::primary(None, "Sign") - .on_press(Message::Spend(SpendTxMessage::Sign)) - .width(Length::Units(150)), - ) - } else { - Some( - button::primary(None, "Broadcast") - .on_press(Message::Spend(SpendTxMessage::Broadcast)) - .width(Length::Units(150)), - ) - }) - .align_items(Alignment::Center) - .spacing(20), - ) - .padding(15), - ), - ) - } else { - None - }) .into() } @@ -434,22 +412,29 @@ pub fn path_view<'a>( scrollable( Row::new() .align_items(Alignment::Center) - .push(if sigs.sigs_count >= sigs.threshold { - icon::circle_check_icon().style(color::GREEN) - } else { - icon::circle_cross_icon() - }) - .push(text(format!(" {}", missing_signatures)).bold()) - .push(text(format!( - " more signature{}", - if missing_signatures > 1 { - "s from " - } else if missing_signatures == 0 { - "" - } else { - " from " - } - ))) + .push( + Row::new() + .push(if sigs.sigs_count >= sigs.threshold { + icon::circle_check_icon().style(color::GREEN) + } else { + icon::circle_cross_icon().style(color::GREY_3) + }) + .push(Space::with_width(Length::Units(20))), + ) + .push( + p1_regular(format!( + "{} more signature{}", + missing_signatures, + if missing_signatures > 1 { + "s from " + } else if missing_signatures == 0 { + "" + } else { + " from " + } + )) + .style(color::GREY_3), + ) .push_maybe(if keys.is_empty() { None } else { @@ -459,7 +444,7 @@ pub fn path_view<'a>( Container::new( tooltip::Tooltip::new( Container::new(text(alias)) - .padding(3) + .padding(10) .style(theme::Container::Pill(theme::Pill::Simple)), value.0.to_string(), tooltip::Position::Bottom, @@ -468,7 +453,7 @@ pub fn path_view<'a>( ) } else { Container::new(text(value.0.to_string())) - .padding(3) + .padding(10) .style(theme::Container::Pill(theme::Pill::Simple)) }) } else { @@ -479,7 +464,7 @@ pub fn path_view<'a>( .push_maybe(if sigs.signed_pubkeys.is_empty() { None } else { - Some(text(", already signed by ")) + Some(p1_regular(", already signed by ").style(color::GREY_3)) }) .push( sigs.signed_pubkeys @@ -489,7 +474,7 @@ pub fn path_view<'a>( Container::new( tooltip::Tooltip::new( Container::new(text(alias)) - .padding(3) + .padding(10) .style(theme::Container::Pill(theme::Pill::Simple)), value.0.to_string(), tooltip::Position::Bottom, @@ -516,198 +501,184 @@ pub fn inputs_and_outputs_view<'a>( receive_indexes: Option>, ) -> Element<'a, Message> { Column::new() - .push( - Column::new() - .spacing(10) - .push_maybe(if !coins.is_empty() { - Some( - Container::new(Collapse::new( - move || { - Button::new( - Row::new() - .align_items(Alignment::Center) - .push( - text(format!( - "{} spent coin{}", - coins.len(), - if coins.len() == 1 { "" } else { "s" } - )) - .bold() - .width(Length::Fill), - ) - .push(icon::collapse_icon()), + .spacing(20) + .push_maybe(if !coins.is_empty() { + Some( + Container::new(Collapse::new( + move || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .push( + h4_bold(format!( + "{} spent coin{}", + coins.len(), + if coins.len() == 1 { "" } else { "s" } + )) + .width(Length::Fill), ) - .padding(15) - .width(Length::Fill) - .style(theme::Button::TransparentBorder) - }, - move || { - Button::new( - Row::new() - .align_items(Alignment::Center) - .push( - text(format!( - "{} spent coin{}", - coins.len(), - if coins.len() == 1 { "" } else { "s" } - )) - .bold() - .width(Length::Fill), - ) - .push(icon::collapsed_icon()), + .push(icon::collapse_icon()), + ) + .padding(20) + .width(Length::Fill) + .style(theme::Button::TransparentBorder) + }, + move || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .push( + h4_bold(format!( + "{} spent coin{}", + coins.len(), + if coins.len() == 1 { "" } else { "s" } + )) + .width(Length::Fill), ) - .padding(15) - .width(Length::Fill) - .style(theme::Button::TransparentBorder) - }, - move || { - coins - .iter() - .fold(Column::new(), |col: Column<'a, Message>, coin| { - col.push( - Row::new() - .padding(15) - .align_items(Alignment::Center) - .width(Length::Fill) - .push( - Row::new() - .width(Length::Fill) - .align_items(Alignment::Center) - .push( - text(coin.outpoint.to_string()) - .small() - ) - .push( - Button::new(icon::clipboard_icon()) - .on_press(Message::Clipboard( - coin.outpoint.to_string(), - )) - .style( - theme::Button::TransparentBorder, - ), - ), - ) - .push(amount(&coin.amount)), - ) - }) - .into() - }, - )) - .style(theme::Container::Card(theme::Card::Simple)), - ) - } else { - None - }) - .push( - Container::new(Collapse::new( - move || { - Button::new( - Row::new() - .align_items(Alignment::Center) - .push( - text(format!( - "{} recipient{}", - tx.output.len(), - if tx.output.len() == 1 { "" } else { "s" } - )) - .bold() - .width(Length::Fill), - ) - .push(icon::collapse_icon()), - ) - .padding(15) - .width(Length::Fill) - .style(theme::Button::TransparentBorder) - }, - move || { - Button::new( - Row::new() - .align_items(Alignment::Center) - .push( - text(format!( - "{} recipient{}", - tx.output.len(), - if tx.output.len() == 1 { "" } else { "s" } - )) - .bold() - .width(Length::Fill), - ) - .push(icon::collapsed_icon()), - ) - .padding(15) - .width(Length::Fill) - .style(theme::Button::TransparentBorder) - }, - move || { - tx.output - .iter() - .enumerate() - .fold(Column::new(), |col: Column<'a, Message>, (i, output)| { - let addr = Address::from_script(&output.script_pubkey, network).unwrap(); + .push(icon::collapsed_icon()), + ) + .padding(20) + .width(Length::Fill) + .style(theme::Button::TransparentBorder) + }, + move || { + coins + .iter() + .fold( + Column::new().padding(20), + |col: Column<'a, Message>, coin| { col.push( - Column::new() - .padding(15) + Row::new() + .align_items(Alignment::Center) .width(Length::Fill) - .spacing(10) .push( Row::new() .width(Length::Fill) + .align_items(Alignment::Center) + .push(p2_regular(coin.outpoint.to_string()).style(color::GREY_3)) .push( - Row::new() - .align_items(Alignment::Center) - .width(Length::Fill) - .push(text(addr.to_string()).small()) - .push( - Button::new(icon::clipboard_icon()) - .on_press(Message::Clipboard( - addr.to_string(), - )) - .style( - theme::Button::TransparentBorder, - ), - ), - ) - .push( - amount(&Amount::from_sat(output.value)) + Button::new(icon::clipboard_icon().style(color::GREY_3)) + .on_press(Message::Clipboard( + coin.outpoint.to_string(), + )) + .style( + theme::Button::TransparentBorder, + ), ), ) - .push_maybe( - if let Some(indexes) = change_indexes.as_ref() { - if indexes.contains(&i) { - Some( - Container::new(text("Change")) - .padding(5) - .style(theme::Container::Pill(theme::Pill::Success)), - ) - } else { - None - } - } else { - None - }, - ) - .push_maybe( - if let Some(indexes) = receive_indexes.as_ref() { - if indexes.contains(&i) { - Some( - Container::new(text("Deposit")) - .padding(5) - .style(theme::Container::Pill(theme::Pill::Success)), - ) - } else { - None - } - } else { - None - }, - ), + .push(amount(&coin.amount)), ) - }) - .into() - }, - )) - .style(theme::Container::Card(theme::Card::Simple)), - ), + }, + ) + .into() + }, + )) + .style(theme::Container::Card(theme::Card::Simple)), + ) + } else { + None + }) + .push( + Container::new(Collapse::new( + move || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .push( + h4_bold(format!( + "{} recipient{}", + tx.output.len(), + if tx.output.len() == 1 { "" } else { "s" } + )) + .width(Length::Fill), + ) + .push(icon::collapse_icon()), + ) + .padding(20) + .width(Length::Fill) + .style(theme::Button::TransparentBorder) + }, + move || { + Button::new( + Row::new() + .align_items(Alignment::Center) + .push( + h4_bold(format!( + "{} recipient{}", + tx.output.len(), + if tx.output.len() == 1 { "" } else { "s" } + )) + .width(Length::Fill), + ) + .push(icon::collapsed_icon()), + ) + .padding(20) + .width(Length::Fill) + .style(theme::Button::TransparentBorder) + }, + move || { + tx.output + .iter() + .enumerate() + .fold( + Column::new().padding(20), + |col: Column<'a, Message>, (i, output)| { + let addr = + Address::from_script(&output.script_pubkey, network).unwrap(); + col.push( + Column::new() + .width(Length::Fill) + .spacing(5) + .push( + Row::new() + .align_items(Alignment::Center) + .width(Length::Fill) + .push( + Row::new() + .align_items(Alignment::Center) + .width(Length::Fill) + .push(p2_regular(addr.to_string()).style(color::GREY_3)) + .push( + Button::new(icon::clipboard_icon().style(color::GREY_3)) + .on_press(Message::Clipboard( + addr.to_string(), + )) + .style( + theme::Button::TransparentBorder, + ), + ), + ) + .push(amount(&Amount::from_sat(output.value))), + ) + .push_maybe(if let Some(indexes) = change_indexes.as_ref() { + if indexes.contains(&i) { + Some(Container::new(text("Change")).padding(5).style( + theme::Container::Pill(theme::Pill::Success), + )) + } else { + None + } + } else { + None + }) + .push_maybe(if let Some(indexes) = receive_indexes.as_ref() { + if indexes.contains(&i) { + Some(Container::new(text("Deposit")).padding(5).style( + theme::Container::Pill(theme::Pill::Success), + )) + } else { + None + } + } else { + None + }), + ) + }, + ) + .into() + }, + )) + .style(theme::Container::Card(theme::Card::Simple)), ) .into() } diff --git a/gui/src/app/view/psbts.rs b/gui/src/app/view/psbts.rs index 214a0739..ef6b893b 100644 --- a/gui/src/app/view/psbts.rs +++ b/gui/src/app/view/psbts.rs @@ -98,29 +98,27 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> { Row::new() .push(badge::spend()) .push(if !tx.sigs.recovery_paths().is_empty() { - Row::new().push( - Container::new(p2_regular(" Recovery ")) - .padding(10) - .style(theme::Container::Pill(theme::Pill::Simple)), - ) + badge::recovery() } else { let sigs = tx.sigs.primary_path(); - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push( - p2_regular(format!( - "{}/{}", - if sigs.sigs_count <= sigs.threshold { - sigs.sigs_count - } else { + Container::new( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push( + p2_regular(format!( + "{}/{}", + if sigs.sigs_count <= sigs.threshold { + sigs.sigs_count + } else { + sigs.threshold + }, sigs.threshold - }, - sigs.threshold - )) - .style(color::GREY_3), - ) - .push(icon::key_icon().style(color::GREY_3)) + )) + .style(color::GREY_3), + ) + .push(icon::key_icon().style(color::GREY_3)), + ) }) .spacing(10) .align_items(Alignment::Center) @@ -134,7 +132,12 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> { }) .push( Column::new() - .push(amount(&tx.spend_amount)) + .align_items(Alignment::End) + .push(if tx.is_self_send() { + Container::new(amount(&tx.spend_amount)) + } else { + Container::new(p1_regular("Self send")) + }) .push(amount_with_size(&tx.fee_amount, P2_SIZE)) .width(Length::Shrink), ) diff --git a/gui/src/app/view/spend/mod.rs b/gui/src/app/view/spend/mod.rs index 8dd63617..5cf664fe 100644 --- a/gui/src/app/view/spend/mod.rs +++ b/gui/src/app/view/spend/mod.rs @@ -32,18 +32,18 @@ use crate::{ pub fn spend_view<'a>( cache: &'a Cache, tx: &'a SpendTx, - _saved: bool, + saved: bool, desc_info: &'a LianaPolicy, key_aliases: &'a HashMap, network: Network, ) -> Element<'a, Message> { dashboard( &Menu::CreateSpendTx, - &cache, + cache, None, Column::new() - .align_items(Alignment::Center) .spacing(20) + .push(Container::new(h3("Send")).width(Length::Fill)) .push(psbt::spend_header(tx)) .push(psbt::spend_overview_view(tx, desc_info, key_aliases)) .push(psbt::inputs_and_outputs_view( @@ -52,7 +52,30 @@ pub fn spend_view<'a>( network, Some(tx.change_indexes.clone()), None, - )), + )) + .push(if saved { + Row::new() + .push( + button::secondary(None, "Delete") + .width(Length::Units(200)) + .on_press(Message::Spend(SpendTxMessage::Delete)), + ) + .width(Length::Fill) + } else { + Row::new() + .push( + button::secondary(None, "< Previous") + .width(Length::Units(150)) + .on_press(Message::Previous), + ) + .push(Space::with_width(Length::Fill)) + .push( + button::secondary(None, "Save") + .width(Length::Units(150)) + .on_press(Message::Spend(SpendTxMessage::Save)), + ) + .width(Length::Fill) + }), ) } diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index 2737a4b0..178c02e0 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -121,6 +121,10 @@ impl SpendTx { signers } + + pub fn is_self_send(&self) -> bool { + !self.coins.is_empty() && self.spend_amount == Amount::from_sat(0) + } } #[derive(Debug, Clone)] diff --git a/gui/ui/src/component/badge.rs b/gui/ui/src/component/badge.rs index 41e09a53..b0b271e2 100644 --- a/gui/ui/src/component/badge.rs +++ b/gui/ui/src/component/badge.rs @@ -65,6 +65,19 @@ pub fn coin() -> Container<'static, T> { .center_y() } +pub fn recovery<'a, T: 'a>() -> Container<'a, T> { + Container::new( + tooltip::Tooltip::new( + Container::new(text::p2_regular(" Recovery ")) + .padding(10) + .style(theme::Container::Pill(theme::Pill::Simple)), + "This transaction is using a recovery path", + tooltip::Position::Top, + ) + .style(theme::Container::Card(theme::Card::Simple)), + ) +} + pub fn unconfirmed<'a, T: 'a>() -> Container<'a, T> { Container::new( tooltip::Tooltip::new(