Add select inputs step
This commit is contained in:
parent
0b2f64279b
commit
7fda64f4ad
@ -87,12 +87,13 @@ pub struct CreateSpendPanel {
|
||||
}
|
||||
|
||||
impl CreateSpendPanel {
|
||||
pub fn new(_coins: &[Coin]) -> Self {
|
||||
pub fn new(coins: &[Coin]) -> 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()),
|
||||
],
|
||||
}
|
||||
@ -115,6 +116,10 @@ impl State for CreateSpendPanel {
|
||||
}
|
||||
|
||||
if matches!(message, Message::View(view::Message::Next)) {
|
||||
if let Some(step) = self.steps.get(self.current) {
|
||||
step.apply(&mut self.draft);
|
||||
}
|
||||
|
||||
if self.steps.get(self.current + 1).is_some() {
|
||||
self.current += 1;
|
||||
}
|
||||
@ -127,7 +132,7 @@ impl State for CreateSpendPanel {
|
||||
}
|
||||
|
||||
if let Some(step) = self.steps.get_mut(self.current) {
|
||||
return step.update(daemon, cache, message);
|
||||
return step.update(daemon, cache, &self.draft, message);
|
||||
}
|
||||
|
||||
Command::none()
|
||||
|
||||
@ -26,6 +26,7 @@ pub trait Step {
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
cache: &Cache,
|
||||
draft: &TransactionDraft,
|
||||
message: Message,
|
||||
) -> Command<Message>;
|
||||
|
||||
@ -49,6 +50,7 @@ impl Step for ChooseRecipients {
|
||||
&mut self,
|
||||
_daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
_cache: &Cache,
|
||||
_draft: &TransactionDraft,
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
match message {
|
||||
@ -173,6 +175,7 @@ impl Step for ChooseFeerate {
|
||||
&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::FeerateEdited(
|
||||
@ -204,3 +207,67 @@ impl Step for ChooseFeerate {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[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().map(|c| (c, false)).collect(),
|
||||
is_valid: false,
|
||||
total_needed: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step for ChooseCoins {
|
||||
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;
|
||||
}
|
||||
|
||||
let total_needed = draft
|
||||
.outputs
|
||||
.values()
|
||||
.fold(Amount::from_sat(0), |acc, a| acc + *a);
|
||||
|
||||
self.is_valid = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(|(coin, selected)| if *selected { Some(coin.amount) } else { None })
|
||||
.sum::<Amount>()
|
||||
> total_needed;
|
||||
|
||||
self.total_needed = Some(total_needed);
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn apply(&self, draft: &mut TransactionDraft) {
|
||||
draft.inputs = self
|
||||
.coins
|
||||
.iter()
|
||||
.filter_map(|(coin, selected)| if *selected { Some(coin.outpoint) } 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ pub enum Message {
|
||||
pub enum CreateSpendMessage {
|
||||
AddRecipient,
|
||||
DeleteRecipient(usize),
|
||||
SelectInput(usize),
|
||||
SelectCoin(usize),
|
||||
RecipientEdited(usize, &'static str, String),
|
||||
FeerateEdited(String),
|
||||
Generate,
|
||||
|
||||
@ -3,11 +3,14 @@ use iced::{
|
||||
Alignment, Length,
|
||||
};
|
||||
|
||||
use minisafe::miniscript::bitcoin::Amount;
|
||||
|
||||
use crate::{
|
||||
app::view::{message::*, modal},
|
||||
daemon::model::Coin,
|
||||
ui::{
|
||||
component::{
|
||||
button, form,
|
||||
badge, button, card, form,
|
||||
text::{text, Text},
|
||||
},
|
||||
icon,
|
||||
@ -117,3 +120,75 @@ pub fn choose_feerate_view<'a>(
|
||||
.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()
|
||||
.push(text("Choose coins").bold().size(50))
|
||||
.push(
|
||||
column().spacing(10).push(
|
||||
coins
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(column().spacing(10), |col, (i, (coin, selected))| {
|
||||
col.push(coin_list_view(i, coin, *selected))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.push_maybe(if is_valid {
|
||||
Some(container(
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::Next)
|
||||
.width(Length::Units(100)),
|
||||
))
|
||||
} else if total_needed.is_some() {
|
||||
Some(container(card::warning(&format!(
|
||||
"Total amount must be superior to {}",
|
||||
total_needed.unwrap().to_btc(),
|
||||
))))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
}
|
||||
|
||||
fn coin_list_view<'a>(i: usize, coin: &Coin, selected: bool) -> Element<'a, Message> {
|
||||
container(
|
||||
iced::pure::button(
|
||||
row()
|
||||
.push(
|
||||
row()
|
||||
.push(if selected {
|
||||
icon::square_check_icon()
|
||||
} else {
|
||||
icon::square_icon()
|
||||
})
|
||||
.push(badge::coin())
|
||||
.push(text(&format!("block: {}", coin.block_height.unwrap_or(0))).small())
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
text(&format!("{} BTC", coin.amount.to_btc()))
|
||||
.bold()
|
||||
.width(Length::Shrink),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20),
|
||||
)
|
||||
.padding(10)
|
||||
.on_press(Message::CreateSpend(CreateSpendMessage::SelectCoin(i)))
|
||||
.style(button::Style::TransparentBorder),
|
||||
)
|
||||
.style(card::SimpleCardStyle)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -17,6 +17,32 @@ impl widget::container::StyleSheet for SimpleCardStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// display an error card with the message and the error in a tooltip.
|
||||
pub fn warning<'a, T: 'a>(message: &str) -> widget::Container<'a, T> {
|
||||
container(
|
||||
row()
|
||||
.spacing(20)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.push(icon::warning_octagon_icon().color(color::WARNING))
|
||||
.push(text(message).color(color::WARNING)),
|
||||
)
|
||||
.padding(15)
|
||||
.style(WarningCardStyle)
|
||||
}
|
||||
|
||||
pub struct WarningCardStyle;
|
||||
impl widget::container::StyleSheet for WarningCardStyle {
|
||||
fn style(&self) -> widget::container::Style {
|
||||
widget::container::Style {
|
||||
border_radius: 10.0,
|
||||
border_color: color::WARNING,
|
||||
border_width: 1.5,
|
||||
background: color::FOREGROUND.into(),
|
||||
..widget::container::Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// display an error card with the message and the error in a tooltip.
|
||||
pub fn error<'a, T: 'a>(message: &str, error: &str) -> widget::Container<'a, T> {
|
||||
container(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user