diff --git a/liana-gui/src/app/state/export.rs b/liana-gui/src/app/state/export.rs index f4d82d24..b0e26efe 100644 --- a/liana-gui/src/app/state/export.rs +++ b/liana-gui/src/app/state/export.rs @@ -155,12 +155,6 @@ impl ExportModal { self.error = Some(e.clone()); } Progress::None => {} - Progress::Psbt(_) => { - if matches!(self.import_export_type, ImportExportType::ImportPsbt(_)) { - self.state = ImportExportState::Ended; - } - // TODO: forward PSBT - } Progress::Xpub(xpub_str) => { if matches!(self.import_export_type, ImportExportType::ExportXpub(_)) { self.state = ImportExportState::Ended; @@ -181,6 +175,7 @@ impl ExportModal { }); } Progress::WalletFromBackup(_) => {} + Progress::Psbt(_) => {} }, ImportExportMessage::TimedOut => { self.stop(ImportExportState::TimedOut); diff --git a/liana-gui/src/app/state/psbt.rs b/liana-gui/src/app/state/psbt.rs index 48a6c637..fffe8062 100644 --- a/liana-gui/src/app/state/psbt.rs +++ b/liana-gui/src/app/state/psbt.rs @@ -1,6 +1,5 @@ use std::collections::{HashMap, HashSet}; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; use iced::Subscription; @@ -13,13 +12,10 @@ use liana::{ use lianad::commands::CoinStatus; use liana_ui::component::toast; -use liana_ui::{ - component::{form, modal}, - widget::Element, -}; +use liana_ui::{component::modal, widget::Element}; use crate::daemon::model::LabelsLoader; -use crate::export::{ImportExportMessage, ImportExportType}; +use crate::export::{ImportExportMessage, ImportExportType, Progress}; use crate::{ app::{ cache::Cache, @@ -59,7 +55,6 @@ pub trait Modal { pub enum PsbtModal { Save(SaveModal), Sign(SignModal), - Update(UpdateModal), Broadcast(BroadcastModal), Delete(DeleteModal), Export(ExportModal), @@ -70,7 +65,6 @@ impl<'a> AsRef for PsbtModal { match &self { Self::Save(a) => a, Self::Sign(a) => a, - Self::Update(a) => a, Self::Broadcast(a) => a, Self::Delete(a) => a, Self::Export(a) => a, @@ -83,7 +77,6 @@ impl<'a> AsMut for PsbtModal { match self { Self::Save(a) => a, Self::Sign(a) => a, - Self::Update(a) => a, Self::Broadcast(a) => a, Self::Delete(a) => a, Self::Export(a) => a, @@ -150,6 +143,17 @@ impl PsbtState { return launch; } } + Message::View(view::Message::ImportPsbt) => { + if self.modal.is_none() { + let modal = ExportModal::new( + Some(daemon.clone()), + ImportExportType::ImportPsbt(Some(self.tx.psbt.unsigned_tx.compute_txid())), + ); + let launch = modal.launch(false); + self.modal = Some(PsbtModal::Export(modal)); + return launch; + } + } Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => { if matches!(self.modal, Some(PsbtModal::Export(_))) { self.modal = None; @@ -188,12 +192,6 @@ impl PsbtState { self.modal = Some(PsbtModal::Sign(modal)); return cmd; } - Message::View(view::Message::Spend(view::SpendTxMessage::EditPsbt)) => { - let modal = UpdateModal::new(self.wallet.clone(), self.tx.psbt.to_string()); - let cmd = modal.load(daemon); - self.modal = Some(PsbtModal::Update(modal)); - return cmd; - } Message::View(view::Message::Spend(view::SpendTxMessage::Broadcast)) => { let outpoints: Vec<_> = self.tx.coins.keys().cloned().collect(); return Task::perform( @@ -246,6 +244,16 @@ impl PsbtState { self.warning = Some(e); } }, + Message::Export(ImportExportMessage::Progress(Progress::Psbt(psbt))) => { + merge_signatures(&mut self.tx.psbt, &psbt); + self.tx.sigs = self + .wallet + .main_descriptor + .partial_spend_info(&self.tx.psbt) + // FIXME: we should not unwrap here, it seems importing a PSBT w/ an + // non-owned input will trigger a panic + .unwrap(); + } _ => { if let Some(modal) = self.modal.as_mut() { return modal.as_mut().update(daemon.clone(), message, &mut self.tx); @@ -658,97 +666,6 @@ async fn sign_psbt( Ok(psbt) } -pub struct UpdateModal { - wallet: Arc, - psbt: String, - updated: form::Value, - processing: bool, - error: Option, - success: bool, -} - -impl UpdateModal { - pub fn new(wallet: Arc, psbt: String) -> Self { - Self { - wallet, - psbt, - updated: form::Value::default(), - processing: false, - error: None, - success: false, - } - } -} - -impl Modal for UpdateModal { - fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> { - modal::Modal::new( - content, - if self.success { - view::psbt::update_spend_success_view() - } else { - view::psbt::update_spend_view( - self.psbt.clone(), - &self.updated, - self.error.as_ref(), - self.processing, - ) - }, - ) - .on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel))) - .into() - } - - fn update( - &mut self, - daemon: Arc, - message: Message, - tx: &mut SpendTx, - ) -> Task { - match message { - Message::Updated(res) => { - self.processing = false; - match res { - Ok(()) => { - self.success = true; - self.error = None; - let psbt = Psbt::from_str(&self.updated.value).expect("Already checked"); - merge_signatures(&mut tx.psbt, &psbt); - tx.sigs = self - .wallet - .main_descriptor - .partial_spend_info(&tx.psbt) - .unwrap(); - } - Err(e) => self.error = e.into(), - } - } - Message::View(view::Message::ImportSpend(view::ImportSpendMessage::PsbtEdited(s))) => { - self.updated.value = s; - if let Ok(psbt) = Psbt::from_str(&self.updated.value) { - self.updated.valid = - tx.psbt.unsigned_tx.compute_txid() == psbt.unsigned_tx.compute_txid(); - } else { - self.updated.valid = false; - } - } - Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Confirm)) => { - self.processing = true; - self.error = None; - if let Ok(updated) = Psbt::from_str(&self.updated.value) { - return Task::perform( - async move { daemon.update_spend_tx(&updated).await.map_err(|e| e.into()) }, - Message::Updated, - ); - } - } - _ => {} - } - - Task::none() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/liana-gui/src/app/state/psbts.rs b/liana-gui/src/app/state/psbts.rs index 173a2981..e6781e54 100644 --- a/liana-gui/src/app/state/psbts.rs +++ b/liana-gui/src/app/state/psbts.rs @@ -1,18 +1,14 @@ -use std::str::FromStr; use std::sync::Arc; use iced::{Subscription, Task}; -use liana::miniscript::bitcoin::psbt::Psbt; -use liana_ui::{ - component::{form, modal}, - widget::Element, -}; +use liana_ui::widget::Element; -use super::{psbt, State}; +use super::{export::ExportModal, psbt, State}; use crate::{ app::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet}, daemon::{model::SpendTx, Daemon}, + export::{ImportExportMessage, ImportExportType}, }; pub struct PsbtsPanel { @@ -20,7 +16,7 @@ pub struct PsbtsPanel { selected_tx: Option, spend_txs: Vec, warning: Option, - import_tx: Option, + modal: Option, } impl PsbtsPanel { @@ -30,7 +26,7 @@ impl PsbtsPanel { spend_txs: Vec::new(), warning: None, selected_tx: None, - import_tx: None, + modal: None, } } @@ -38,7 +34,7 @@ impl PsbtsPanel { 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; + self.modal = None; } } @@ -53,14 +49,8 @@ impl State for PsbtsPanel { self.warning.as_ref(), view::psbts::psbts_view(&self.spend_txs), ); - if let Some(import_tx) = &self.import_tx { - modal::Modal::new(list_view, import_tx.view()) - .on_blur(if import_tx.processing { - None - } else { - Some(view::Message::Close) - }) - .into() + if let Some(modal) = &self.modal { + modal.view(list_view) } else { list_view } @@ -99,9 +89,32 @@ impl State for PsbtsPanel { } } }, - Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Import)) => { - if self.import_tx.is_none() { - self.import_tx = Some(ImportPsbtModal::new()); + Message::View(view::Message::ImportPsbt) => { + if let Some(tx) = &mut self.selected_tx { + return tx.update(daemon, cache, message); + } else if self.modal.is_none() { + let modal = + ExportModal::new(Some(daemon.clone()), ImportExportType::ImportPsbt(None)); + let launch = modal.launch(false); + self.modal = Some(modal); + return launch; + } + } + Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => { + if let Some(tx) = &mut self.selected_tx { + return tx.update(daemon, cache, message); + } else if self.modal.is_some() { + self.modal = None; + return Task::perform(async {}, |_| Message::View(view::Message::Reload)); + } + } + Message::View(view::Message::ImportExport(m)) => { + let m = m.clone(); + if let Some(tx) = &mut self.selected_tx { + let message = Message::View(view::Message::ImportExport(m)); + return tx.update(daemon, cache, message); + } else if let Some(modal) = self.modal.as_mut() { + return modal.update(m.clone()); } } Message::View(view::Message::Select(i)) => { @@ -116,10 +129,6 @@ impl State for PsbtsPanel { if let Some(tx) = &mut self.selected_tx { return tx.update(daemon, cache, message); } - - if let Some(import_tx) = &mut self.import_tx { - return import_tx.update(daemon, cache, message); - } } } Task::none() @@ -128,6 +137,17 @@ impl State for PsbtsPanel { fn subscription(&self) -> Subscription { if let Some(psbt) = &self.selected_tx { psbt.subscription() + } else if let Some(modal) = &self.modal { + modal + .subscription() + .map(|s| { + s.map(|m| { + Message::View(view::Message::ImportExport(ImportExportMessage::Progress( + m, + ))) + }) + }) + .unwrap_or(Subscription::none()) } else { Subscription::none() } @@ -140,7 +160,7 @@ impl State for PsbtsPanel { ) -> Task { self.wallet = wallet; self.selected_tx = None; - self.import_tx = None; + self.modal = None; let daemon = daemon.clone(); Task::perform( async move { @@ -159,74 +179,3 @@ impl From for Box { Box::new(s) } } - -pub struct ImportPsbtModal { - imported: form::Value, - processing: bool, - error: Option, - success: bool, -} - -impl ImportPsbtModal { - pub fn new() -> Self { - Self { - imported: form::Value::default(), - processing: false, - error: None, - success: false, - } - } -} - -impl ImportPsbtModal { - fn view<'a>(&self) -> Element<'a, view::Message> { - if self.success { - view::psbts::import_psbt_success_view() - } else { - view::psbts::import_psbt_view(&self.imported, self.error.as_ref(), self.processing) - } - } - - fn update( - &mut self, - daemon: Arc, - _cache: &Cache, - message: Message, - ) -> Task { - match message { - Message::Updated(res) => { - self.processing = false; - match res { - Ok(()) => { - self.success = true; - self.error = None; - } - Err(e) => self.error = e.into(), - } - } - Message::View(view::Message::ImportSpend(view::ImportSpendMessage::PsbtEdited(s))) => { - self.imported.value = s; - self.imported.valid = Psbt::from_str(&self.imported.value).ok().is_some(); - } - Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Confirm)) => { - if self.imported.valid { - self.processing = true; - self.error = None; - let imported = Psbt::from_str(&self.imported.value).expect("Already checked"); - return Task::perform( - async move { - daemon - .update_spend_tx(&imported) - .await - .map_err(|e| e.into()) - }, - Message::Updated, - ); - } - } - _ => {} - } - - Task::none() - } -} diff --git a/liana-gui/src/app/view/message.rs b/liana-gui/src/app/view/message.rs index 04a4780f..543d8018 100644 --- a/liana-gui/src/app/view/message.rs +++ b/liana-gui/src/app/view/message.rs @@ -26,6 +26,7 @@ pub enum Message { ImportExport(ImportExportMessage), HideRescanWarning, ExportPsbt, + ImportPsbt, } impl Close for Message { diff --git a/liana-gui/src/app/view/psbt.rs b/liana-gui/src/app/view/psbt.rs index 4d22bf55..81927130 100644 --- a/liana-gui/src/app/view/psbt.rs +++ b/liana-gui/src/app/view/psbt.rs @@ -346,10 +346,10 @@ pub fn spend_overview_view<'a>( ) .push( button::secondary( - Some(icon::import_icon()), - "Update", + Some(icon::restore_icon()), + "Import", ) - .on_press(Message::Spend(SpendTxMessage::EditPsbt)), + .on_press(Message::ImportPsbt), ), ) .align_y(Alignment::Center), diff --git a/liana-gui/src/app/view/psbts.rs b/liana-gui/src/app/view/psbts.rs index adc0fa40..b0d7def2 100644 --- a/liana-gui/src/app/view/psbts.rs +++ b/liana-gui/src/app/view/psbts.rs @@ -68,8 +68,8 @@ pub fn psbts_view(spend_txs: &[SpendTx]) -> Element<'_, Message> { .spacing(10) .push(Container::new(h3("PSBTs")).width(Length::Fill)) .push( - button::secondary(Some(icon::import_icon()), "Import") - .on_press(Message::ImportSpend(ImportSpendMessage::Import)), + button::secondary(Some(icon::restore_icon()), "Import") + .on_press(Message::ImportPsbt), ) .push( button::secondary(Some(icon::plus_icon()), "New") diff --git a/liana-gui/src/export.rs b/liana-gui/src/export.rs index 5c63421b..91669782 100644 --- a/liana-gui/src/export.rs +++ b/liana-gui/src/export.rs @@ -605,9 +605,9 @@ pub async fn import_psbt( } daemon.update_spend_tx(&psbt).await?; + send_progress!(sender, Psbt(psbt)); send_progress!(sender, Progress(100.0)); - send_progress!(sender, Psbt(psbt)); Ok(()) }