Merge #959: Refac app: keep every panel states
18e040e51ff835f5f6c8218392496865b6f8737a fix: override unpaginated pending events and txs (edouardparis)
2995df870f711966d7d275f67de70719dd1498ce fix psbts panel: keep list of psbts in background (edouardparis)
a6832ad0b7a881889e26c10c65f4d0ecd6e8c098 fix send panel: reload coins (edouardparis)
710883b6a2467e4c8fab9b2382e8227b602dfa42 Restart spend process according to current state (edouardparis)
a91fdd791aedc997e4b43e23db8af8c5382cab17 Encapsulate cache with its own message (edouardparis)
dc3d29e3f06b7cb2aa0d502edd6f7fcf0aadf458 Remove spend_txs from cache (edouardparis)
2d2cd12bda47feff6faa01cb5a1338d7f0f9f7a0 Restart new spending process on user demand (edouardparis)
ed363963b34235d49291a0da7719fd655ff7a8dc Change state load method for reload (edouardparis)
c15424abe5a3255b63d45903f14b1044af859e6a fix clippy (edouardparis)
fa4483a4b71ceb17463e89a27d1337c3cfc4d20d gui: add reload cycle to reset state (edouardparis)
c268c3a093594b76d424d935840c8c5ef6c83482 gui: load receive panel only once (edouardparis)
9b4b6fef1bee12e3e370a34c9d9c30eb437bf404 gui: refac app, keep all states (edouardparis)
Pull request description:
This refac keeps each panel state in parallel.
ACKs for top commit:
jp1ac4:
ACK 18e040e51f.
Tree-SHA512: 87937a5e7cf315325e06aeda0789024a68cfb2c137cd1319445e670fa2641775a6d58cbc787271f2b73d43ff5b83e934f0561a4968afd05f8f52cff85e81a27e
This commit is contained in:
commit
505b218618
@ -1,4 +1,4 @@
|
||||
use crate::daemon::model::{Coin, SpendTx};
|
||||
use crate::daemon::model::Coin;
|
||||
use liana::miniscript::bitcoin::Network;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -8,7 +8,6 @@ pub struct Cache {
|
||||
pub network: Network,
|
||||
pub blockheight: i32,
|
||||
pub coins: Vec<Coin>,
|
||||
pub spend_txs: Vec<SpendTx>,
|
||||
pub rescan_progress: Option<f64>,
|
||||
}
|
||||
|
||||
@ -20,7 +19,6 @@ impl std::default::Default for Cache {
|
||||
network: Network::Bitcoin,
|
||||
blockheight: 0,
|
||||
coins: Vec::new(),
|
||||
spend_txs: Vec::new(),
|
||||
rescan_progress: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ use liana::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{error::Error, view, wallet::Wallet},
|
||||
app::{cache::Cache, error::Error, view, wallet::Wallet},
|
||||
daemon::model::*,
|
||||
hw::HardwareWalletMessage,
|
||||
};
|
||||
@ -19,6 +19,7 @@ use crate::{
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
Tick,
|
||||
UpdateCache(Result<Cache, Error>),
|
||||
View(view::Message),
|
||||
LoadDaemonConfig(Box<DaemonConfig>),
|
||||
DaemonConfigLoaded(Result<(), Error>),
|
||||
|
||||
@ -35,14 +35,89 @@ use crate::{
|
||||
daemon::{embedded::EmbeddedDaemon, Daemon},
|
||||
};
|
||||
|
||||
use self::state::SettingsState;
|
||||
|
||||
struct Panels {
|
||||
current: Menu,
|
||||
home: Home,
|
||||
coins: CoinsPanel,
|
||||
transactions: TransactionsPanel,
|
||||
psbts: PsbtsPanel,
|
||||
recovery: RecoveryPanel,
|
||||
receive: ReceivePanel,
|
||||
create_spend: CreateSpendPanel,
|
||||
settings: SettingsState,
|
||||
}
|
||||
|
||||
impl Panels {
|
||||
fn new(
|
||||
cache: &Cache,
|
||||
wallet: Arc<Wallet>,
|
||||
data_dir: PathBuf,
|
||||
internal_bitcoind: Option<&Bitcoind>,
|
||||
) -> Panels {
|
||||
Self {
|
||||
current: Menu::Home,
|
||||
home: Home::new(wallet.clone(), &cache.coins),
|
||||
coins: CoinsPanel::new(&cache.coins, wallet.main_descriptor.first_timelock_value()),
|
||||
transactions: TransactionsPanel::new(),
|
||||
psbts: PsbtsPanel::new(wallet.clone()),
|
||||
recovery: RecoveryPanel::new(wallet.clone(), &cache.coins, cache.blockheight),
|
||||
receive: ReceivePanel::new(data_dir.clone(), wallet.clone()),
|
||||
create_spend: CreateSpendPanel::new(
|
||||
wallet.clone(),
|
||||
&cache.coins,
|
||||
cache.blockheight as u32,
|
||||
cache.network,
|
||||
),
|
||||
settings: state::SettingsState::new(
|
||||
data_dir,
|
||||
wallet.clone(),
|
||||
internal_bitcoind.is_some(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn current(&self) -> &dyn State {
|
||||
match self.current {
|
||||
Menu::Home => &self.home,
|
||||
Menu::Receive => &self.receive,
|
||||
Menu::PSBTs => &self.psbts,
|
||||
Menu::Transactions => &self.transactions,
|
||||
Menu::Settings => &self.settings,
|
||||
Menu::Coins => &self.coins,
|
||||
Menu::CreateSpendTx => &self.create_spend,
|
||||
Menu::Recovery => &self.recovery,
|
||||
Menu::RefreshCoins(_) => &self.create_spend,
|
||||
Menu::PsbtPreSelected(_) => &self.psbts,
|
||||
}
|
||||
}
|
||||
|
||||
fn current_mut(&mut self) -> &mut dyn State {
|
||||
match self.current {
|
||||
Menu::Home => &mut self.home,
|
||||
Menu::Receive => &mut self.receive,
|
||||
Menu::PSBTs => &mut self.psbts,
|
||||
Menu::Transactions => &mut self.transactions,
|
||||
Menu::Settings => &mut self.settings,
|
||||
Menu::Coins => &mut self.coins,
|
||||
Menu::CreateSpendTx => &mut self.create_spend,
|
||||
Menu::Recovery => &mut self.recovery,
|
||||
Menu::RefreshCoins(_) => &mut self.create_spend,
|
||||
Menu::PsbtPreSelected(_) => &mut self.psbts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
data_dir: PathBuf,
|
||||
state: Box<dyn State>,
|
||||
cache: Cache,
|
||||
config: Config,
|
||||
wallet: Arc<Wallet>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
|
||||
panels: Panels,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@ -54,12 +129,17 @@ impl App {
|
||||
data_dir: PathBuf,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
) -> (App, Command<Message>) {
|
||||
let state: Box<dyn State> = Home::new(wallet.clone(), &cache.coins).into();
|
||||
let cmd = state.load(daemon.clone());
|
||||
let mut panels = Panels::new(
|
||||
&cache,
|
||||
wallet.clone(),
|
||||
data_dir.clone(),
|
||||
internal_bitcoind.as_ref(),
|
||||
);
|
||||
let cmd = panels.home.reload(daemon.clone());
|
||||
(
|
||||
Self {
|
||||
panels,
|
||||
data_dir,
|
||||
state,
|
||||
cache,
|
||||
config,
|
||||
daemon,
|
||||
@ -70,70 +150,52 @@ impl App {
|
||||
)
|
||||
}
|
||||
|
||||
fn load_state(&mut self, menu: &Menu) -> Command<Message> {
|
||||
self.state = match menu {
|
||||
menu::Menu::Settings => state::SettingsState::new(
|
||||
self.data_dir.clone(),
|
||||
self.wallet.clone(),
|
||||
self.internal_bitcoind.is_some(),
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::Home => Home::new(self.wallet.clone(), &self.cache.coins).into(),
|
||||
menu::Menu::Coins => CoinsPanel::new(
|
||||
&self.cache.coins,
|
||||
self.wallet.main_descriptor.first_timelock_value(),
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::Recovery => RecoveryPanel::new(
|
||||
self.wallet.clone(),
|
||||
&self.cache.coins,
|
||||
self.cache.blockheight,
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::Receive => {
|
||||
ReceivePanel::new(self.data_dir.clone(), self.wallet.clone()).into()
|
||||
}
|
||||
menu::Menu::Transactions => TransactionsPanel::new().into(),
|
||||
menu::Menu::PSBTs => PsbtsPanel::new(self.wallet.clone(), &self.cache.spend_txs).into(),
|
||||
fn set_current_panel(&mut self, menu: Menu) -> Command<Message> {
|
||||
match &menu {
|
||||
menu::Menu::PsbtPreSelected(txid) => {
|
||||
// Get preselected spend from DB in case it's not yet in the cache.
|
||||
// We only need this single spend as we will go straight to its view and not show the PSBTs list.
|
||||
// In case of any error loading the spend or if it doesn't exist, fall back to using the cache
|
||||
// and load PSBTs list in usual way.
|
||||
match self
|
||||
// In case of any error loading the spend or if it doesn't exist, load PSBTs list in usual way.
|
||||
if let Ok(Some(spend_tx)) = self
|
||||
.daemon
|
||||
.list_spend_transactions(Some(&[*txid]))
|
||||
.map(|txs| txs.first().cloned())
|
||||
{
|
||||
Ok(Some(spend_tx)) => {
|
||||
PsbtsPanel::new_preselected(self.wallet.clone(), spend_tx).into()
|
||||
}
|
||||
_ => PsbtsPanel::new(self.wallet.clone(), &self.cache.spend_txs).into(),
|
||||
self.panels.psbts.preselect(spend_tx);
|
||||
self.panels.current = menu;
|
||||
return Command::none();
|
||||
};
|
||||
}
|
||||
menu::Menu::RefreshCoins(preselected) => {
|
||||
self.panels.create_spend = CreateSpendPanel::new_self_send(
|
||||
self.wallet.clone(),
|
||||
&self.cache.coins,
|
||||
self.cache.blockheight as u32,
|
||||
preselected,
|
||||
self.cache.network,
|
||||
);
|
||||
}
|
||||
menu::Menu::CreateSpendTx => {
|
||||
// redo the process of spending only if user want to start a new one.
|
||||
if !self.panels.create_spend.is_first_step() {
|
||||
self.panels.create_spend = CreateSpendPanel::new(
|
||||
self.wallet.clone(),
|
||||
&self.cache.coins,
|
||||
self.cache.blockheight as u32,
|
||||
self.cache.network,
|
||||
);
|
||||
}
|
||||
}
|
||||
menu::Menu::CreateSpendTx => CreateSpendPanel::new(
|
||||
self.wallet.clone(),
|
||||
&self.cache.coins,
|
||||
self.cache.blockheight as u32,
|
||||
self.cache.network,
|
||||
)
|
||||
.into(),
|
||||
menu::Menu::RefreshCoins(preselected) => CreateSpendPanel::new_self_send(
|
||||
self.wallet.clone(),
|
||||
&self.cache.coins,
|
||||
self.cache.blockheight as u32,
|
||||
preselected,
|
||||
self.cache.network,
|
||||
)
|
||||
.into(),
|
||||
_ => {}
|
||||
};
|
||||
self.state.load(self.daemon.clone())
|
||||
self.panels.current = menu;
|
||||
self.panels.current_mut().reload(self.daemon.clone())
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::batch(vec![
|
||||
time::every(Duration::from_secs(5)).map(|_| Message::Tick),
|
||||
self.state.subscription(),
|
||||
time::every(Duration::from_secs(10)).map(|_| Message::Tick),
|
||||
self.panels.current().subscription(),
|
||||
])
|
||||
}
|
||||
|
||||
@ -149,33 +211,33 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: Message) -> Command<Message> {
|
||||
// Update cache when values are passing by.
|
||||
// State will handle the error case.
|
||||
match &message {
|
||||
Message::Coins(Ok(coins)) => {
|
||||
self.cache.coins = coins.clone();
|
||||
}
|
||||
Message::SpendTxs(Ok(txs)) => {
|
||||
self.cache.spend_txs = txs.clone();
|
||||
}
|
||||
Message::Info(Ok(info)) => {
|
||||
self.cache.blockheight = info.block_height;
|
||||
self.cache.rescan_progress = info.rescan_progress;
|
||||
}
|
||||
Message::StartRescan(Ok(())) => {
|
||||
self.cache.rescan_progress = Some(0.0);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match message {
|
||||
Message::Tick => {
|
||||
let daemon = self.daemon.clone();
|
||||
let datadir_path = self.cache.datadir_path.clone();
|
||||
Command::perform(
|
||||
async move { daemon.get_info().map_err(|e| e.into()) },
|
||||
Message::Info,
|
||||
async move {
|
||||
let info = daemon.get_info()?;
|
||||
// todo: filter coins to only have current coins.
|
||||
let coins = daemon.list_coins()?;
|
||||
Ok(Cache {
|
||||
datadir_path,
|
||||
coins: coins.coins,
|
||||
network: info.network,
|
||||
blockheight: info.block_height,
|
||||
rescan_progress: info.rescan_progress,
|
||||
})
|
||||
},
|
||||
Message::UpdateCache,
|
||||
)
|
||||
}
|
||||
Message::UpdateCache(res) => {
|
||||
match res {
|
||||
Ok(cache) => self.cache = cache,
|
||||
Err(e) => tracing::error!("Failed to update cache: {}", e),
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
Message::LoadDaemonConfig(cfg) => {
|
||||
let path = self.config.daemon_config_path.clone().expect(
|
||||
"Application config must have a daemon configuration file path at this point.",
|
||||
@ -187,9 +249,12 @@ impl App {
|
||||
let res = self.load_wallet();
|
||||
self.update(Message::WalletLoaded(res))
|
||||
}
|
||||
Message::View(view::Message::Menu(menu)) => self.load_state(&menu),
|
||||
Message::View(view::Message::Menu(menu)) => self.set_current_panel(menu),
|
||||
Message::View(view::Message::Clipboard(text)) => clipboard::write(text),
|
||||
_ => self.state.update(self.daemon.clone(), &self.cache, message),
|
||||
_ => self
|
||||
.panels
|
||||
.current_mut()
|
||||
.update(self.daemon.clone(), &self.cache, message),
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +295,6 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
self.state.view(&self.cache).map(Message::View)
|
||||
self.panels.current().view(&self.cache).map(Message::View)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ impl State for CoinsPanel {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let daemon1 = daemon.clone();
|
||||
let daemon2 = daemon.clone();
|
||||
Command::batch(vec![
|
||||
|
||||
@ -44,7 +44,7 @@ pub trait State {
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::none()
|
||||
}
|
||||
fn load(&self, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
@ -186,11 +186,7 @@ impl State for Home {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(events) => {
|
||||
self.warning = None;
|
||||
for event in events {
|
||||
if !self.pending_events.iter().any(|other| other.tx == event.tx) {
|
||||
self.pending_events.push(event);
|
||||
}
|
||||
}
|
||||
self.pending_events = events;
|
||||
}
|
||||
},
|
||||
Message::View(view::Message::Label(_, _)) | Message::LabelsUpdated(_) => {
|
||||
@ -210,6 +206,9 @@ impl State for Home {
|
||||
}
|
||||
};
|
||||
}
|
||||
Message::View(view::Message::Reload) => {
|
||||
return self.reload(daemon);
|
||||
}
|
||||
Message::View(view::Message::Close) => {
|
||||
self.selected_event = None;
|
||||
}
|
||||
@ -259,7 +258,8 @@ impl State for Home {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
self.selected_event = None;
|
||||
let daemon1 = daemon.clone();
|
||||
let daemon2 = daemon.clone();
|
||||
let daemon3 = daemon.clone();
|
||||
|
||||
@ -24,26 +24,21 @@ pub struct PsbtsPanel {
|
||||
}
|
||||
|
||||
impl PsbtsPanel {
|
||||
pub fn new(wallet: Arc<Wallet>, spend_txs: &[SpendTx]) -> Self {
|
||||
pub fn new(wallet: Arc<Wallet>) -> Self {
|
||||
Self {
|
||||
wallet,
|
||||
spend_txs: spend_txs.to_vec(),
|
||||
spend_txs: Vec::new(),
|
||||
warning: None,
|
||||
selected_tx: None,
|
||||
import_tx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_preselected(wallet: Arc<Wallet>, spend_tx: SpendTx) -> Self {
|
||||
let psbt_state = psbt::PsbtState::new(wallet.clone(), spend_tx.clone(), true);
|
||||
|
||||
Self {
|
||||
wallet,
|
||||
spend_txs: vec![spend_tx],
|
||||
warning: None,
|
||||
selected_tx: Some(psbt_state),
|
||||
import_tx: None,
|
||||
}
|
||||
pub fn preselect(&mut self, spend_tx: SpendTx) {
|
||||
let psbt_state = psbt::PsbtState::new(self.wallet.clone(), spend_tx, true);
|
||||
self.selected_tx = Some(psbt_state);
|
||||
self.warning = None;
|
||||
self.import_tx = None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +74,9 @@ impl State for PsbtsPanel {
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
match message {
|
||||
Message::View(view::Message::Reload) | Message::View(view::Message::Close) => {
|
||||
return self.reload(daemon);
|
||||
}
|
||||
Message::SpendTxs(res) => match res {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(txs) => {
|
||||
@ -91,16 +89,6 @@ impl State for PsbtsPanel {
|
||||
self.import_tx = Some(ImportPsbtModal::new());
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Close) => {
|
||||
if self.selected_tx.is_some() {
|
||||
self.selected_tx = None;
|
||||
return self.load(daemon);
|
||||
}
|
||||
if self.import_tx.is_some() {
|
||||
self.import_tx = None;
|
||||
return self.load(daemon);
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Select(i)) => {
|
||||
if let Some(tx) = self.spend_txs.get(i) {
|
||||
let tx = psbt::PsbtState::new(self.wallet.clone(), tx.clone(), true);
|
||||
@ -130,7 +118,9 @@ impl State for PsbtsPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
self.selected_tx = None;
|
||||
self.import_tx = None;
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move { daemon.list_spend_transactions(None).map_err(|e| e.into()) },
|
||||
|
||||
@ -34,6 +34,12 @@ pub struct Addresses {
|
||||
labels: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Addresses {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.list.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Labelled for Addresses {
|
||||
fn labelled(&self) -> Vec<LabelItem> {
|
||||
self.list
|
||||
@ -154,7 +160,18 @@ impl State for ReceivePanel {
|
||||
));
|
||||
Command::none()
|
||||
}
|
||||
Message::View(view::Message::Next) => self.load(daemon),
|
||||
Message::View(view::Message::Next) => {
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.get_new_address()
|
||||
.map(|res| (res.address, res.derivation_index))
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::ReceiveAddress,
|
||||
)
|
||||
}
|
||||
_ => self
|
||||
.modal
|
||||
.as_mut()
|
||||
@ -163,17 +180,22 @@ impl State for ReceivePanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.get_new_address()
|
||||
.map(|res| (res.address, res.derivation_index))
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::ReceiveAddress,
|
||||
)
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
// Fill at least with one address, user will then use the generate button.
|
||||
if self.addresses.is_empty() {
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.get_new_address()
|
||||
.map(|res| (res.address, res.derivation_index))
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::ReceiveAddress,
|
||||
)
|
||||
} else {
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -192,7 +192,7 @@ impl State for RecoveryPanel {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
|
||||
@ -16,7 +16,7 @@ use liana::{
|
||||
use liana_ui::{component::form, widget::Element};
|
||||
|
||||
use crate::{
|
||||
app::{cache::Cache, error::Error, message::Message, view, State},
|
||||
app::{cache::Cache, error::Error, message::Message, state::settings::State, view},
|
||||
bitcoind::{RpcAuthType, RpcAuthValues},
|
||||
daemon::Daemon,
|
||||
};
|
||||
|
||||
@ -55,14 +55,14 @@ impl State for SettingsState {
|
||||
);
|
||||
self.setting
|
||||
.as_mut()
|
||||
.map(|s| s.load(daemon))
|
||||
.map(|s| s.reload(daemon))
|
||||
.unwrap_or_else(Command::none)
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::AboutSection)) => {
|
||||
self.setting = Some(AboutSettingsState::default().into());
|
||||
self.setting
|
||||
.as_mut()
|
||||
.map(|s| s.load(daemon))
|
||||
.map(|s| s.reload(daemon))
|
||||
.unwrap_or_else(Command::none)
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::EditWalletSettings)) => {
|
||||
@ -71,7 +71,7 @@ impl State for SettingsState {
|
||||
);
|
||||
self.setting
|
||||
.as_mut()
|
||||
.map(|s| s.load(daemon))
|
||||
.map(|s| s.reload(daemon))
|
||||
.unwrap_or_else(Command::none)
|
||||
}
|
||||
_ => self
|
||||
@ -97,6 +97,11 @@ impl State for SettingsState {
|
||||
view::settings::list(cache)
|
||||
}
|
||||
}
|
||||
|
||||
fn reload(&mut self, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
self.setting = None;
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SettingsState> for Box<dyn State> {
|
||||
@ -145,7 +150,7 @@ impl State for AboutSettingsState {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
Command::perform(
|
||||
async move { daemon.get_info().map_err(|e| e.into()) },
|
||||
Message::Info,
|
||||
|
||||
@ -180,7 +180,7 @@ impl State for WalletSettingsState {
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
Command::perform(
|
||||
async move { daemon.get_info().map_err(|e| e.into()) },
|
||||
Message::Info,
|
||||
|
||||
@ -63,6 +63,10 @@ impl CreateSpendPanel {
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_first_step(&self) -> bool {
|
||||
self.current == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl State for CreateSpendPanel {
|
||||
@ -108,7 +112,7 @@ impl State for CreateSpendPanel {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let daemon1 = daemon.clone();
|
||||
let daemon2 = daemon.clone();
|
||||
Command::batch(vec![
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
use std::{cmp::Ordering, collections::HashMap, str::FromStr, sync::Arc};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, HashSet},
|
||||
iter::FromIterator,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use iced::{Command, Subscription};
|
||||
use liana::{
|
||||
commands::ListCoinsEntry,
|
||||
descriptors::LianaDescriptor,
|
||||
miniscript::bitcoin::{
|
||||
address, psbt::Psbt, secp256k1, Address, Amount, Denomination, Network, OutPoint,
|
||||
@ -142,6 +149,11 @@ impl DefineSpend {
|
||||
}
|
||||
|
||||
pub fn with_coins_sorted(mut self, blockheight: u32) -> Self {
|
||||
self.sort_coins(blockheight);
|
||||
self
|
||||
}
|
||||
|
||||
fn sort_coins(&mut self, blockheight: u32) {
|
||||
let timelock = self.timelock;
|
||||
self.coins.sort_by(|(a, a_selected), (b, b_selected)| {
|
||||
if *a_selected && !b_selected || !a_selected && *b_selected {
|
||||
@ -156,7 +168,6 @@ impl DefineSpend {
|
||||
a.block_height.cmp(&b.block_height)
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn self_send(mut self) -> Self {
|
||||
@ -399,6 +410,19 @@ impl Step for DefineSpend {
|
||||
self.batch_label.valid = label.len() <= 100;
|
||||
self.batch_label.value = label;
|
||||
}
|
||||
view::CreateSpendMessage::Clear => {
|
||||
*self = Self::new(
|
||||
self.network,
|
||||
self.descriptor.clone(),
|
||||
self.coins
|
||||
.iter()
|
||||
.map(|(c, _)| c.clone())
|
||||
.collect::<Vec<ListCoinsEntry>>()
|
||||
.as_slice(),
|
||||
self.timelock,
|
||||
);
|
||||
return Command::none();
|
||||
}
|
||||
view::CreateSpendMessage::AddRecipient => {
|
||||
self.recipients.push(Recipient::default());
|
||||
}
|
||||
@ -528,6 +552,35 @@ impl Step for DefineSpend {
|
||||
}
|
||||
Err(e) => self.warning = Some(e),
|
||||
},
|
||||
Message::Coins(res) => match res {
|
||||
Ok(coins) => {
|
||||
let selected: HashSet<OutPoint> =
|
||||
HashSet::from_iter(self.coins.iter().filter_map(|(c, selected)| {
|
||||
if *selected {
|
||||
Some(c.outpoint)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
self.coins = coins
|
||||
.into_iter()
|
||||
.filter_map(|coin| {
|
||||
if coin.spend_info.is_none() && !coin.is_immature {
|
||||
let selected = selected.contains(&coin.outpoint);
|
||||
Some((coin, selected))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.sort_coins(cache.blockheight as u32);
|
||||
// In case some selected coins are not spendable anymore and
|
||||
// new coins make more sense to be selected. A redraft is triggered
|
||||
// if all forms are valid (checked in the redraft method)
|
||||
self.redraft(daemon);
|
||||
}
|
||||
Err(e) => self.warning = Some(e),
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
Command::none()
|
||||
|
||||
@ -105,11 +105,7 @@ impl State for TransactionsPanel {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(txs) => {
|
||||
self.warning = None;
|
||||
for tx in txs {
|
||||
if !self.pending_txs.iter().any(|other| other.tx == tx.tx) {
|
||||
self.pending_txs.push(tx);
|
||||
}
|
||||
}
|
||||
self.pending_txs = txs;
|
||||
}
|
||||
},
|
||||
Message::RbfModal(tx, is_cancel, res) => match res {
|
||||
@ -121,8 +117,8 @@ impl State for TransactionsPanel {
|
||||
self.warning = e.into();
|
||||
}
|
||||
},
|
||||
Message::View(view::Message::Close) => {
|
||||
self.selected_tx = None;
|
||||
Message::View(view::Message::Reload) | Message::View(view::Message::Close) => {
|
||||
return self.reload(daemon);
|
||||
}
|
||||
Message::View(view::Message::Select(i)) => {
|
||||
self.selected_tx = Some(i);
|
||||
@ -225,7 +221,8 @@ impl State for TransactionsPanel {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
fn reload(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
self.selected_tx = None;
|
||||
let daemon1 = daemon.clone();
|
||||
let daemon2 = daemon.clone();
|
||||
let daemon3 = daemon.clone();
|
||||
|
||||
@ -38,6 +38,7 @@ pub enum CreateSpendMessage {
|
||||
SelectPath(usize),
|
||||
Generate,
|
||||
SendMaxToRecipient(usize),
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -45,7 +45,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
let home_button = if *menu == Menu::Home {
|
||||
row!(
|
||||
button::menu_active(Some(home_icon()), "Home")
|
||||
.on_press(Message::Menu(Menu::Home))
|
||||
.on_press(Message::Reload)
|
||||
.width(iced::Length::Fill),
|
||||
menu_green_bar(),
|
||||
)
|
||||
@ -58,7 +58,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
let transactions_button = if *menu == Menu::Transactions {
|
||||
row!(
|
||||
button::menu_active(Some(history_icon()), "Transactions")
|
||||
.on_press(Message::Menu(Menu::Transactions))
|
||||
.on_press(Message::Reload)
|
||||
.width(iced::Length::Fill),
|
||||
menu_green_bar()
|
||||
)
|
||||
@ -85,7 +85,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
let psbt_button = if *menu == Menu::PSBTs {
|
||||
row!(
|
||||
button::menu_active(Some(history_icon()), "PSBTs")
|
||||
.on_press(Message::Menu(Menu::PSBTs))
|
||||
.on_press(Message::Reload)
|
||||
.width(iced::Length::Fill),
|
||||
menu_green_bar()
|
||||
)
|
||||
@ -98,7 +98,7 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> Container<'a, Message> {
|
||||
let spend_button = if *menu == Menu::CreateSpendTx {
|
||||
row!(
|
||||
button::menu_active(Some(send_icon()), "Send")
|
||||
.on_press(Message::Menu(Menu::CreateSpendTx))
|
||||
.on_press(Message::Reload)
|
||||
.width(iced::Length::Fill),
|
||||
menu_green_bar()
|
||||
)
|
||||
|
||||
@ -294,7 +294,7 @@ pub fn create_spend_tx<'a>(
|
||||
.push(Space::with_width(Length::Fill))
|
||||
.push(
|
||||
button::primary(None, "Clear")
|
||||
.on_press(Message::Menu(Menu::CreateSpendTx))
|
||||
.on_press(Message::CreateSpend(CreateSpendMessage::Clear))
|
||||
.width(Length::Fixed(100.0)),
|
||||
)
|
||||
.push(
|
||||
|
||||
@ -368,14 +368,12 @@ pub async fn load_application(
|
||||
Wallet::new(info.descriptors.main).load_settings(&gui_config, &datadir_path, network)?;
|
||||
|
||||
let coins = daemon.list_coins().map(|res| res.coins)?;
|
||||
let spend_txs = daemon.list_spend_transactions(None)?;
|
||||
|
||||
let cache = Cache {
|
||||
datadir_path,
|
||||
network: info.network,
|
||||
blockheight: info.block_height,
|
||||
coins,
|
||||
spend_txs,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ impl<S: State + Send + 'static> Sandbox<S> {
|
||||
}
|
||||
|
||||
pub async fn load(mut self, daemon: Arc<dyn Daemon + Sync + Send>, cache: &Cache) -> Self {
|
||||
let cmd = self.state.load(daemon.clone());
|
||||
let cmd = self.state.reload(daemon.clone());
|
||||
for action in cmd.actions() {
|
||||
if let Action::Future(f) = action {
|
||||
let msg = f.await;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user