Merge #478: Add refresh coins buttons
aa23bba9d48b3ce683c7090c7d7ab8c6e218868d ui: set default_features = false (edouard) 79984718bc65c3b536255e3737852de4aa3049d5 gui: add refresh coins buttons (edouard) 032cf801d5c18ba20d102b8334f09cc2cd4464c4 gui: create self send transaction (edouard) 89162262e9d72f9f78061dd890a25569a329af44 Update Liana core lib to master (Antoine Poinsot) Pull request description: Fixes #371. Fixes #192. ACKs for top commit: darosior: re-ACK aa23bba9d48b3ce683c7090c7d7ab8c6e218868d Tree-SHA512: bf4685491ca404a659d99bf202bcceb103f6df89fb63e0e8ea1d25058c0022a7472fa354dc35e380d6adb06f1f0b337e6c476cf5d0c9c04e611248aacd496d6d
This commit is contained in:
commit
ad5fdbae20
2
gui/Cargo.lock
generated
2
gui/Cargo.lock
generated
@ -1841,7 +1841,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "liana"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/wizardsardine/liana?branch=0.4-lianad#81b81b2f789052cd8ef6b13964f9953f4fcbc0a4"
|
||||
source = "git+https://github.com/wizardsardine/liana?branch=master#9a78b8cfffd5075dea3a041a9e661c27b14c57a2"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"base64",
|
||||
|
||||
@ -15,7 +15,7 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
async-hwi = "0.0.6"
|
||||
liana = { git = "https://github.com/wizardsardine/liana", branch = "0.4-lianad", default-features = false }
|
||||
liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false }
|
||||
liana_ui = { path = "ui" }
|
||||
backtrace = "0.3"
|
||||
base64 = "0.13"
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use liana::miniscript::bitcoin::OutPoint;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Menu {
|
||||
Home,
|
||||
@ -8,4 +9,5 @@ pub enum Menu {
|
||||
Coins,
|
||||
CreateSpendTx,
|
||||
Recovery,
|
||||
RefreshCoins(Vec<OutPoint>),
|
||||
}
|
||||
|
||||
@ -92,6 +92,13 @@ impl App {
|
||||
self.cache.blockheight as u32,
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::RefreshCoins(preselected) => CreateSpendPanel::new_self_send(
|
||||
self.wallet.clone(),
|
||||
&self.cache.coins,
|
||||
self.cache.blockheight as u32,
|
||||
preselected,
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
self.state.load(self.daemon.clone())
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use iced::{widget::qr_code, Command, Subscription};
|
||||
use liana::miniscript::bitcoin::{Address, Amount};
|
||||
use liana::miniscript::bitcoin::{Address, Amount, OutPoint};
|
||||
use liana_ui::widget::*;
|
||||
|
||||
use super::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet};
|
||||
@ -50,7 +50,7 @@ pub struct Home {
|
||||
balance: Amount,
|
||||
unconfirmed_balance: Amount,
|
||||
remaining_sequence: Option<u32>,
|
||||
number_of_expiring_coins: usize,
|
||||
expiring_coins: Vec<OutPoint>,
|
||||
pending_events: Vec<HistoryTransaction>,
|
||||
events: Vec<HistoryTransaction>,
|
||||
selected_event: Option<(usize, usize)>,
|
||||
@ -76,7 +76,7 @@ impl Home {
|
||||
balance,
|
||||
unconfirmed_balance,
|
||||
remaining_sequence: None,
|
||||
number_of_expiring_coins: 0,
|
||||
expiring_coins: Vec::new(),
|
||||
selected_event: None,
|
||||
events: Vec::new(),
|
||||
pending_events: Vec::new(),
|
||||
@ -103,7 +103,7 @@ impl State for Home {
|
||||
&self.balance,
|
||||
&self.unconfirmed_balance,
|
||||
&self.remaining_sequence,
|
||||
self.number_of_expiring_coins,
|
||||
&self.expiring_coins,
|
||||
&self.pending_events,
|
||||
&self.events,
|
||||
),
|
||||
@ -125,7 +125,7 @@ impl State for Home {
|
||||
self.balance = Amount::from_sat(0);
|
||||
self.unconfirmed_balance = Amount::from_sat(0);
|
||||
self.remaining_sequence = None;
|
||||
self.number_of_expiring_coins = 0;
|
||||
self.expiring_coins = Vec::new();
|
||||
for coin in coins {
|
||||
if coin.spend_info.is_none() {
|
||||
if coin.block_height.is_some() {
|
||||
@ -135,7 +135,7 @@ impl State for Home {
|
||||
remaining_sequence(&coin, cache.blockheight as u32, timelock);
|
||||
// number of block in a day
|
||||
if seq <= 144 {
|
||||
self.number_of_expiring_coins += 1;
|
||||
self.expiring_coins.push(coin.outpoint);
|
||||
}
|
||||
if let Some(last) = &mut self.remaining_sequence {
|
||||
if seq < *last {
|
||||
|
||||
@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use iced::Command;
|
||||
|
||||
use liana::miniscript::bitcoin::OutPoint;
|
||||
use liana_ui::widget::Element;
|
||||
|
||||
use super::{redirect, State};
|
||||
@ -25,12 +26,33 @@ impl CreateSpendPanel {
|
||||
draft: step::TransactionDraft::default(),
|
||||
current: 0,
|
||||
steps: vec![
|
||||
Box::new(step::DefineSpend::new(
|
||||
descriptor,
|
||||
coins.to_vec(),
|
||||
timelock,
|
||||
blockheight,
|
||||
)),
|
||||
Box::new(
|
||||
step::DefineSpend::new(descriptor, coins, timelock)
|
||||
.with_coins_sorted(blockheight),
|
||||
),
|
||||
Box::new(step::SaveSpend::new(wallet)),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_self_send(
|
||||
wallet: Arc<Wallet>,
|
||||
coins: &[Coin],
|
||||
blockheight: u32,
|
||||
preselected_coins: &[OutPoint],
|
||||
) -> Self {
|
||||
let descriptor = wallet.main_descriptor.clone();
|
||||
let timelock = descriptor.first_timelock_value();
|
||||
Self {
|
||||
draft: step::TransactionDraft::default(),
|
||||
current: 0,
|
||||
steps: vec![
|
||||
Box::new(
|
||||
step::DefineSpend::new(descriptor, coins, timelock)
|
||||
.with_preselected_coins(preselected_coins)
|
||||
.with_coins_sorted(blockheight)
|
||||
.self_send(),
|
||||
),
|
||||
Box::new(step::SaveSpend::new(wallet)),
|
||||
],
|
||||
}
|
||||
|
||||
@ -60,12 +60,7 @@ pub struct DefineSpend {
|
||||
}
|
||||
|
||||
impl DefineSpend {
|
||||
pub fn new(
|
||||
descriptor: LianaDescriptor,
|
||||
coins: Vec<Coin>,
|
||||
timelock: u16,
|
||||
blockheight: u32,
|
||||
) -> Self {
|
||||
pub fn new(descriptor: LianaDescriptor, coins: &[Coin], timelock: u16) -> Self {
|
||||
let balance_available = coins
|
||||
.iter()
|
||||
.filter_map(|coin| {
|
||||
@ -76,27 +71,17 @@ impl DefineSpend {
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
let mut coins: Vec<(Coin, bool)> = coins
|
||||
.into_iter()
|
||||
let coins: Vec<(Coin, bool)> = coins
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
if c.spend_info.is_none() {
|
||||
Some((c, false))
|
||||
Some((*c, false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
coins.sort_by(|(a, _), (b, _)| {
|
||||
if remaining_sequence(a, blockheight, timelock)
|
||||
== remaining_sequence(b, blockheight, timelock)
|
||||
{
|
||||
// bigger amount first
|
||||
b.amount.cmp(&a.amount)
|
||||
} else {
|
||||
// smallest blockheight (remaining_sequence) first
|
||||
a.block_height.cmp(&b.block_height)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
balance_available,
|
||||
descriptor,
|
||||
@ -111,9 +96,43 @@ impl DefineSpend {
|
||||
warning: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_preselected_coins(mut self, preselected_coins: &[OutPoint]) -> Self {
|
||||
for (coin, selected) in &mut self.coins {
|
||||
*selected = preselected_coins.contains(&coin.outpoint);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_coins_sorted(mut self, blockheight: u32) -> Self {
|
||||
let timelock = self.timelock;
|
||||
self.coins.sort_by(|(a, a_selected), (b, b_selected)| {
|
||||
if *a_selected && !b_selected || !a_selected && *b_selected {
|
||||
b_selected.cmp(a_selected)
|
||||
} else if remaining_sequence(a, blockheight, timelock)
|
||||
== remaining_sequence(b, blockheight, timelock)
|
||||
{
|
||||
// bigger amount first
|
||||
b.amount.cmp(&a.amount)
|
||||
} else {
|
||||
// smallest blockheight (remaining_sequence) first
|
||||
a.block_height.cmp(&b.block_height)
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn self_send(mut self) -> Self {
|
||||
self.recipients = Vec::new();
|
||||
self
|
||||
}
|
||||
|
||||
fn check_valid(&mut self) {
|
||||
self.is_valid = !self.recipients.is_empty();
|
||||
self.is_valid = self.feerate.valid && !self.feerate.value.is_empty();
|
||||
self.is_duplicate = false;
|
||||
if !self.coins.iter().any(|(_, selected)| *selected) {
|
||||
self.is_valid = false;
|
||||
}
|
||||
for (i, recipient) in self.recipients.iter().enumerate() {
|
||||
if !recipient.valid() {
|
||||
self.is_valid = false;
|
||||
@ -212,9 +231,9 @@ impl Step for DefineSpend {
|
||||
}
|
||||
|
||||
view::CreateSpendMessage::FeerateEdited(s) => {
|
||||
if s.parse::<u64>().is_ok() {
|
||||
if let Ok(value) = s.parse::<u64>() {
|
||||
self.feerate.value = s;
|
||||
self.feerate.valid = true;
|
||||
self.feerate.valid = value != 0;
|
||||
self.amount_left_to_select();
|
||||
} else if s.is_empty() {
|
||||
self.feerate.value = "".to_string();
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use iced::{Alignment, Length};
|
||||
use iced::{widget::Space, Alignment, Length};
|
||||
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{amount::*, badge, text::*},
|
||||
component::{amount::*, badge, button, text::*},
|
||||
icon,
|
||||
image::*,
|
||||
theme,
|
||||
@ -11,7 +11,7 @@ use liana_ui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{cache::Cache, view::message::Message},
|
||||
app::{cache::Cache, menu::Menu, view::message::Message},
|
||||
daemon::model::{remaining_sequence, Coin},
|
||||
};
|
||||
|
||||
@ -136,7 +136,7 @@ fn coin_list_view(
|
||||
.spacing(5)
|
||||
})),
|
||||
)
|
||||
.push_maybe(coin.spend_info.map(|info| {
|
||||
.push(if let Some(info) = coin.spend_info {
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new()
|
||||
@ -159,7 +159,16 @@ fn coin_list_view(
|
||||
)
|
||||
})
|
||||
.spacing(5)
|
||||
})),
|
||||
} else {
|
||||
Column::new().push(
|
||||
Row::new().push(Space::with_width(Length::Fill)).push(
|
||||
button::primary(Some(icon::arrow_repeat()), "Refresh coin")
|
||||
.on_press(Message::Menu(Menu::RefreshCoins(vec![
|
||||
coin.outpoint,
|
||||
]))),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
|
||||
@ -5,7 +5,7 @@ use iced::{alignment, Alignment, Length};
|
||||
use liana::miniscript::bitcoin;
|
||||
use liana_ui::{
|
||||
color,
|
||||
component::{amount::*, card, event, text::*},
|
||||
component::{amount::*, button, card, event, text::*},
|
||||
icon, theme,
|
||||
util::Collection,
|
||||
widget::*,
|
||||
@ -27,7 +27,7 @@ pub fn home_view<'a>(
|
||||
balance: &'a bitcoin::Amount,
|
||||
unconfirmed_balance: &'a bitcoin::Amount,
|
||||
remaining_sequence: &Option<u32>,
|
||||
number_of_expiring_coins: usize,
|
||||
expiring_coins: &Vec<bitcoin::OutPoint>,
|
||||
pending_events: &[HistoryTransaction],
|
||||
events: &Vec<HistoryTransaction>,
|
||||
) -> Element<'a, Message> {
|
||||
@ -48,7 +48,7 @@ pub fn home_view<'a>(
|
||||
None
|
||||
}),
|
||||
)
|
||||
.push_maybe(if number_of_expiring_coins == 0 {
|
||||
.push_maybe(if expiring_coins.is_empty() {
|
||||
remaining_sequence.map(|sequence| {
|
||||
Container::new(
|
||||
Row::new()
|
||||
@ -75,13 +75,21 @@ pub fn home_view<'a>(
|
||||
} else {
|
||||
Some(
|
||||
Container::new(
|
||||
Row::new().spacing(15).align_items(Alignment::Center).push(
|
||||
h4_regular(format!(
|
||||
"You have {} coins that are already or about to be expired",
|
||||
number_of_expiring_coins
|
||||
))
|
||||
.width(Length::Fill),
|
||||
),
|
||||
Row::new()
|
||||
.spacing(15)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
h4_regular(format!(
|
||||
"You have {} coins that are already or about to be expired",
|
||||
expiring_coins.len(),
|
||||
))
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
button::primary(Some(icon::arrow_repeat()), "Refresh coins").on_press(
|
||||
Message::Menu(Menu::RefreshCoins(expiring_coins.clone())),
|
||||
),
|
||||
),
|
||||
)
|
||||
.padding(25)
|
||||
.style(theme::Card::Invalid),
|
||||
|
||||
@ -177,7 +177,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::CreateSpendTx))
|
||||
.width(iced::Length::Fill),
|
||||
menu_green_bar()
|
||||
)
|
||||
|
||||
@ -93,12 +93,13 @@ pub fn create_spend_tx<'a>(
|
||||
feerate: &form::Value<String>,
|
||||
error: Option<&Error>,
|
||||
) -> Element<'a, Message> {
|
||||
let is_self_send = recipients.is_empty();
|
||||
dashboard(
|
||||
&Menu::CreateSpendTx,
|
||||
cache,
|
||||
error,
|
||||
Column::new()
|
||||
.push(h3("Send"))
|
||||
.push(h3(if is_self_send { "Self send" } else { "Send" }))
|
||||
.push(
|
||||
Column::new()
|
||||
.push(Column::with_children(recipients).spacing(10))
|
||||
@ -116,12 +117,16 @@ pub fn create_spend_tx<'a>(
|
||||
None
|
||||
})
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push(
|
||||
button::secondary(Some(icon::plus_icon()), "Add recipient")
|
||||
.on_press(Message::CreateSpend(
|
||||
CreateSpendMessage::AddRecipient,
|
||||
)),
|
||||
),
|
||||
.push_maybe(if is_self_send {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
button::secondary(Some(icon::plus_icon()), "Add recipient")
|
||||
.on_press(Message::CreateSpend(
|
||||
CreateSpendMessage::AddRecipient,
|
||||
)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.spacing(20),
|
||||
)
|
||||
@ -151,7 +156,26 @@ pub fn create_spend_tx<'a>(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(p1_bold("Coins selection").width(Length::Fill))
|
||||
.push(Container::new(if let Some(amount_left) = amount_left {
|
||||
.push(if is_self_send {
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(amount_with_size(
|
||||
&Amount::from_sat(
|
||||
coins
|
||||
.iter()
|
||||
.filter_map(|(coin, selected)| {
|
||||
if *selected {
|
||||
Some(coin.amount.to_sat())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum(),
|
||||
),
|
||||
P2_SIZE,
|
||||
))
|
||||
.push(p2_regular("selected").style(color::GREY_3))
|
||||
} else if let Some(amount_left) = amount_left {
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.push(amount_with_size(amount_left, P2_SIZE))
|
||||
@ -159,7 +183,7 @@ pub fn create_spend_tx<'a>(
|
||||
} else {
|
||||
Row::new()
|
||||
.push(text("Feerate needs to be set.").style(color::GREY_3))
|
||||
}))
|
||||
})
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
@ -193,8 +217,9 @@ pub fn create_spend_tx<'a>(
|
||||
)
|
||||
.push(
|
||||
if is_valid
|
||||
&& total_amount < *balance_available
|
||||
&& Some(&Amount::from_sat(0)) == amount_left
|
||||
&& (is_self_send
|
||||
|| (total_amount < *balance_available
|
||||
&& Some(&Amount::from_sat(0)) == amount_left))
|
||||
{
|
||||
button::primary(None, "Next")
|
||||
.on_press(Message::CreateSpend(CreateSpendMessage::Generate))
|
||||
|
||||
@ -6,7 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
iced = { version = "0.7", features = ["svg", "image"] }
|
||||
iced = { version = "0.7", default_features = false, features = ["svg", "image", "glow"] }
|
||||
iced_native = "0.8"
|
||||
iced_lazy = { version = "0.4"}
|
||||
bitcoin = "0.29"
|
||||
|
||||
@ -22,6 +22,10 @@ pub fn arrow_right() -> Text<'static> {
|
||||
icon('\u{F138}')
|
||||
}
|
||||
|
||||
pub fn arrow_repeat() -> Text<'static> {
|
||||
icon('\u{F130}')
|
||||
}
|
||||
|
||||
pub fn arrow_return_right() -> Text<'static> {
|
||||
icon('\u{F132}')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user