Add select inputs step

This commit is contained in:
edouard 2022-10-28 11:36:56 +02:00
parent 0b2f64279b
commit 7fda64f4ad
5 changed files with 177 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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