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:
edouardparis 2024-03-06 16:14:50 +01:00
commit 505b218618
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
19 changed files with 285 additions and 151 deletions

View File

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

View File

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

View File

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

View File

@ -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![

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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![

View File

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

View File

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

View File

@ -38,6 +38,7 @@ pub enum CreateSpendMessage {
SelectPath(usize),
Generate,
SendMaxToRecipient(usize),
Clear,
}
#[derive(Debug, Clone)]

View File

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

View File

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

View File

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

View File

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