gui: refac psbt view

This commit is contained in:
edouard 2023-04-25 16:28:34 +02:00
parent 330f7e65bb
commit b4c7d1af50
8 changed files with 422 additions and 390 deletions

View File

@ -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<Wallet>,
desc_policy: LianaPolicy,
tx: SpendTx,
saved: bool,
action: Option<Box<dyn Action>>,
pub wallet: Arc<Wallet>,
pub desc_policy: LianaPolicy,
pub tx: SpendTx,
pub saved: bool,
pub action: Option<Box<dyn Action>>,
}
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,

View File

@ -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
}
}
}

View File

@ -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(

View File

@ -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<Fingerprint, String>,
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<Element<'a, Message>>>(
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<Fingerprint, String>,
) -> 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::<Element<'a, Message>>::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<Vec<usize>>,
) -> 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()
}

View File

@ -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),
)

View File

@ -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<Fingerprint, String>,
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)
}),
)
}

View File

@ -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)]

View File

@ -65,6 +65,19 @@ pub fn coin<T>() -> 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(