Merge #1724: Make user finish signing process before opening new modal

874d2d406804b853e399800435bb0ee223d5d769 Make user finish sign process before opening new modal (edouardparis)
1d1a28195527cc04ce99969e5c85d2632402d73e Revert "gui: store SignModal state and allow to open export modal after signing in PSBT panel" (edouardparis)

Pull request description:

  This Pr is to mitigate three issues:
  close #1719
  close #1685
  and try to prevent a little bit the problem of impossible cancellation of the signing Task by disabling any other buttons and making aware of the user that he has to finish it (reject of approve).
  Sadly it is not the case for the sidebar menu button, so if user start the signing process and quit by switching panel, he may still have the problem of the signing task blocking in the background and may be surprised by the impossibility of iced to close properly.

  Proper signing task cancellation will require a change in the async-hwi library: https://github.com/wizardsardine/async-hwi/issues/108

  ![20250523_16h55m19s_grim](https://github.com/user-attachments/assets/8b92b975-29d1-4281-b8a9-b6a4a63495fe)
  ![20250523_16h54m17s_grim](https://github.com/user-attachments/assets/3ac8cf39-da75-42af-8390-cc5db77e8722)

ACKs for top commit:
  pythcoiner:
    tACK 874d2d40, it fixes back #1681
  jp1ac4:
    tACK 874d2d406804b853e399800435bb0ee223d5d769.

Tree-SHA512: bb25dcc086787cf491ca11359db756ef19508f1518d600d7091b0cb14422d9148ae3ee1f3ed47aa5dc642d7089152b7b36e1746be7dbe94d22bab71f2899645b
This commit is contained in:
edouardparis 2025-05-23 18:35:47 +02:00
commit 2172abfe0a
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
4 changed files with 89 additions and 21 deletions

View File

@ -92,9 +92,6 @@ pub struct PsbtState {
pub warning: Option<Error>,
pub labels_edited: LabelsEdited,
pub modal: Option<PsbtModal>,
// NOTE: sign_modal is used to store state of SignModal
// when another modal is selected
pub sign_modal: Option<PsbtModal>,
}
impl PsbtState {
@ -105,7 +102,6 @@ impl PsbtState {
labels_edited: LabelsEdited::default(),
warning: None,
modal: None,
sign_modal: None,
tx,
saved,
}
@ -169,19 +165,26 @@ impl PsbtState {
}
}
Message::View(view::Message::Spend(view::SpendTxMessage::Cancel)) => {
if matches!(self.modal, Some(PsbtModal::Sign(_))) {
// store SignModal state
self.sign_modal = self.modal.take();
if let Some(PsbtModal::Sign(SignModal {
display_modal,
signing,
..
})) = &mut self.modal
{
if !signing.is_empty() {
*display_modal = false;
return Task::none();
}
}
self.modal = None;
}
Message::View(view::Message::Spend(view::SpendTxMessage::Delete)) => {
self.modal = Some(PsbtModal::Delete(DeleteModal::default()));
}
Message::View(view::Message::Spend(view::SpendTxMessage::Sign)) => {
if self.sign_modal.is_some() {
// restore SignModal state
self.modal = self.sign_modal.take();
if let Some(PsbtModal::Sign(SignModal { display_modal, .. })) = &mut self.modal {
*display_modal = true;
return Task::none();
}
@ -235,7 +238,15 @@ impl PsbtState {
Message::Updated(Ok(_)) => {
self.saved = true;
if let Some(modal) = self.modal.as_mut() {
return modal.as_mut().update(daemon.clone(), message, &mut self.tx);
let cmd = modal.as_mut().update(daemon.clone(), message, &mut self.tx);
// if modal is only the pending notif then we remove it once the psbt was
// updated.
if let PsbtModal::Sign(SignModal { display_modal, .. }) = modal {
if !*display_modal {
self.modal = None;
}
}
return cmd;
}
}
Message::BroadcastModal(res) => match res {
@ -275,6 +286,11 @@ impl PsbtState {
&self.wallet.keys_aliases,
self.labels_edited.cache(),
cache.network,
if let Some(PsbtModal::Sign(m)) = &self.modal {
m.is_signing()
} else {
false
},
self.warning.as_ref(),
);
if let Some(modal) = &self.modal {
@ -466,6 +482,10 @@ impl SignModal {
recovery_timelock,
}
}
pub fn is_signing(&self) -> bool {
!self.signing.is_empty()
}
}
impl Modal for SignModal {
@ -487,6 +507,7 @@ impl Modal for SignModal {
..
}) = self.hws.list.get(i)
{
self.display_modal = false;
self.signing.insert(*fingerprint);
let psbt = tx.psbt.clone();
let fingerprint = *fingerprint;
@ -506,6 +527,7 @@ impl Modal for SignModal {
self.signing.remove(&fingerprint);
match res {
Err(e) => {
self.display_modal = true;
if !matches!(e, Error::HardwareWallet(async_hwi::Error::UserRefused)) {
self.error = Some(e)
}
@ -561,7 +583,7 @@ impl Modal for SignModal {
Task::none()
}
fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> {
let content: Element<'a, view::Message> = toast::Manager::new(
let content = toast::Manager::new(
content,
view::psbt::sign_action_toasts(self.error.as_ref(), &self.hws.list, &self.signing),
)

View File

@ -1018,6 +1018,11 @@ impl Step for SaveSpend {
&psbt_state.wallet.keys_aliases,
psbt_state.labels_edited.cache(),
cache.network,
if let Some(psbt::PsbtModal::Sign(m)) = &psbt_state.modal {
m.is_signing()
} else {
false
},
psbt_state.warning.as_ref(),
);
if let Some(modal) = &psbt_state.modal {

View File

@ -46,6 +46,7 @@ pub fn psbt_view<'a>(
key_aliases: &'a HashMap<Fingerprint, String>,
labels_editing: &'a HashMap<String, form::Value<String>>,
network: Network,
currently_signing: bool,
warning: Option<&Error>,
) -> Element<'a, Message> {
dashboard(
@ -72,7 +73,12 @@ pub fn psbt_view<'a>(
}),
)
.push(spend_header(tx, labels_editing))
.push(spend_overview_view(tx, desc_info, key_aliases))
.push(spend_overview_view(
tx,
desc_info,
key_aliases,
currently_signing,
))
.push(
Column::new()
.spacing(20)
@ -96,7 +102,11 @@ pub fn psbt_view<'a>(
.push(
button::secondary(None, "Delete")
.width(Length::Fixed(200.0))
.on_press(Message::Spend(SpendTxMessage::Delete)),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::Spend(SpendTxMessage::Delete))
}),
)
.width(Length::Fill)
} else {
@ -105,7 +115,11 @@ pub fn psbt_view<'a>(
.push(
button::secondary(None, "Save")
.width(Length::Fixed(150.0))
.on_press(Message::Spend(SpendTxMessage::Save)),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::Spend(SpendTxMessage::Save))
}),
)
.width(Length::Fill)
})
@ -321,6 +335,7 @@ pub fn spend_overview_view<'a>(
tx: &'a SpendTx,
desc_info: &'a LianaPolicy,
key_aliases: &'a HashMap<Fingerprint, String>,
currently_signing: bool,
) -> Element<'a, Message> {
Column::new()
.spacing(20)
@ -343,14 +358,22 @@ pub fn spend_overview_view<'a>(
Some(icon::backup_icon()),
"Export",
)
.on_press(Message::ExportPsbt),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::ExportPsbt)
}),
)
.push(
button::secondary(
Some(icon::restore_icon()),
"Import",
)
.on_press(Message::ImportPsbt),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::ImportPsbt)
}),
),
)
.align_y(Alignment::Center),

View File

@ -37,6 +37,7 @@ pub fn spend_view<'a>(
key_aliases: &'a HashMap<Fingerprint, String>,
labels_editing: &'a HashMap<String, form::Value<String>>,
network: Network,
currently_signing: bool,
warning: Option<&Error>,
) -> Element<'a, Message> {
let is_recovery = tx
@ -75,7 +76,12 @@ pub fn spend_view<'a>(
},
))
})
.push(psbt::spend_overview_view(tx, desc_info, key_aliases))
.push(psbt::spend_overview_view(
tx,
desc_info,
key_aliases,
currently_signing,
))
.push(
Column::new()
.spacing(20)
@ -99,7 +105,11 @@ pub fn spend_view<'a>(
.push(
button::secondary(None, "Delete")
.width(Length::Fixed(200.0))
.on_press(Message::Spend(SpendTxMessage::Delete)),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::Spend(SpendTxMessage::Delete))
}),
)
.width(Length::Fill)
} else {
@ -107,13 +117,21 @@ pub fn spend_view<'a>(
.push(
button::secondary(None, "< Previous")
.width(Length::Fixed(150.0))
.on_press(Message::Previous),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::Previous)
}),
)
.push(Space::with_width(Length::Fill))
.push(
button::secondary(None, "Save")
.width(Length::Fixed(150.0))
.on_press(Message::Spend(SpendTxMessage::Save)),
.on_press_maybe(if currently_signing {
None
} else {
Some(Message::Spend(SpendTxMessage::Save))
}),
)
.width(Length::Fill)
}),