From d1e970a2af841608264cb3e3092809bb4e29add0 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Sun, 8 Dec 2024 06:27:23 +0100 Subject: [PATCH] gui: add a modal for export tasks in transactions panels --- liana-gui/src/app/message.rs | 3 + liana-gui/src/app/mod.rs | 2 +- liana-gui/src/app/state/export.rs | 2 - liana-gui/src/app/state/mod.rs | 1 + liana-gui/src/app/state/transactions.rs | 72 +++++++++++++++----- liana-gui/src/app/view/export.rs | 88 +++++++++++++++++++++++++ liana-gui/src/app/view/message.rs | 6 +- liana-gui/src/app/view/mod.rs | 1 + liana-gui/src/app/view/transactions.rs | 24 ++++++- 9 files changed, 176 insertions(+), 23 deletions(-) create mode 100644 liana-gui/src/app/view/export.rs diff --git a/liana-gui/src/app/message.rs b/liana-gui/src/app/message.rs index d0ae6af56..5bd1fa476 100644 --- a/liana-gui/src/app/message.rs +++ b/liana-gui/src/app/message.rs @@ -14,6 +14,8 @@ use crate::{ hw::HardwareWalletMessage, }; +use super::state::export::ExportMessage; + #[derive(Debug)] pub enum Message { Tick, @@ -43,4 +45,5 @@ pub enum Message { LabelsUpdated(Result>, Error>), BroadcastModal(Result, Error>), RbfModal(Box, bool, Result, Error>), + Export(ExportMessage), } diff --git a/liana-gui/src/app/mod.rs b/liana-gui/src/app/mod.rs index 3a0f553b8..7827570f2 100644 --- a/liana-gui/src/app/mod.rs +++ b/liana-gui/src/app/mod.rs @@ -1,12 +1,12 @@ pub mod cache; pub mod config; +pub mod export; pub mod menu; pub mod message; pub mod settings; pub mod state; pub mod view; pub mod wallet; -pub mod export; mod error; diff --git a/liana-gui/src/app/state/export.rs b/liana-gui/src/app/state/export.rs index 0e36e5ce7..ace5f3967 100644 --- a/liana-gui/src/app/state/export.rs +++ b/liana-gui/src/app/state/export.rs @@ -135,8 +135,6 @@ impl ExportModal { if let Some(handle) = self.handle.take() { handle.lock().expect("poisoined").abort(); self.state = state; - } else { - log::warn!("no handle !!!!!!!!!!!!!!!!!!!!!!!!"); } } diff --git a/liana-gui/src/app/state/mod.rs b/liana-gui/src/app/state/mod.rs index cda029120..f5ce8619d 100644 --- a/liana-gui/src/app/state/mod.rs +++ b/liana-gui/src/app/state/mod.rs @@ -1,4 +1,5 @@ mod coins; +pub mod export; mod label; mod psbt; mod psbts; diff --git a/liana-gui/src/app/state/transactions.rs b/liana-gui/src/app/state/transactions.rs index 8a7c334af..34e102457 100644 --- a/liana-gui/src/app/state/transactions.rs +++ b/liana-gui/src/app/state/transactions.rs @@ -35,13 +35,22 @@ use crate::daemon::{ Daemon, }; +use super::export::{ExportMessage, ExportModal}; + +#[derive(Debug)] +pub enum TransactionsModal { + CreateRbf(CreateRbfModal), + Export(ExportModal), + None, +} + pub struct TransactionsPanel { wallet: Arc, txs: Vec, labels_edited: LabelsEdited, selected_tx: Option, warning: Option, - create_rbf_modal: Option, + modal: TransactionsModal, is_last_page: bool, processing: bool, } @@ -54,7 +63,7 @@ impl TransactionsPanel { txs: Vec::new(), labels_edited: LabelsEdited::default(), warning: None, - create_rbf_modal: None, + modal: TransactionsModal::None, is_last_page: false, processing: false, } @@ -63,7 +72,7 @@ impl TransactionsPanel { pub fn preselect(&mut self, tx: HistoryTransaction) { self.selected_tx = Some(tx); self.warning = None; - self.create_rbf_modal = None; + self.modal = TransactionsModal::None; } } @@ -76,19 +85,22 @@ impl State for TransactionsPanel { self.labels_edited.cache(), self.warning.as_ref(), ); - if let Some(modal) = &self.create_rbf_modal { - modal.view(content) - } else { - content + match &self.modal { + TransactionsModal::CreateRbf(rbf) => rbf.view(content), + _ => content, } } else { - view::transactions::transactions_view( + let content = view::transactions::transactions_view( cache, &self.txs, self.warning.as_ref(), self.is_last_page, self.processing, - ) + ); + match &self.modal { + TransactionsModal::Export(export) => export.view(content), + _ => content, + } } } @@ -134,7 +146,7 @@ impl State for TransactionsPanel { Message::RbfModal(tx, is_cancel, res) => match res { Ok(descendant_txids) => { let modal = CreateRbfModal::new(*tx, is_cancel, descendant_txids); - self.create_rbf_modal = Some(modal); + self.modal = TransactionsModal::CreateRbf(modal); } Err(e) => { self.warning = e.into(); @@ -146,16 +158,16 @@ impl State for TransactionsPanel { Message::View(view::Message::Select(i)) => { self.selected_tx = self.txs.get(i).cloned(); // Clear modal if it's for a different tx. - if let Some(modal) = &self.create_rbf_modal { + if let TransactionsModal::CreateRbf(modal) = &self.modal { if Some(modal.tx.tx.txid()) != self.selected_tx.as_ref().map(|selected| selected.tx.txid()) { - self.create_rbf_modal = None; + self.modal = TransactionsModal::None; } } } Message::View(view::Message::CreateRbf(view::CreateRbfMessage::Cancel)) => { - self.create_rbf_modal = None; + self.modal = TransactionsModal::None; } Message::View(view::Message::CreateRbf(view::CreateRbfMessage::New(is_cancel))) => { if let Some(tx) = &self.selected_tx { @@ -246,11 +258,27 @@ impl State for TransactionsPanel { ); } } - _ => { - if let Some(modal) = &mut self.create_rbf_modal { - return modal.update(daemon, _cache, message); + Message::View(view::Message::Export(ExportMessage::Open)) => { + if let TransactionsModal::None = &self.modal { + self.modal = TransactionsModal::Export(ExportModal::new(daemon)); + if let TransactionsModal::Export(m) = &mut self.modal { + return m.launch(); + } } } + Message::View(view::Message::Export(ExportMessage::Close)) => { + // Message::Export(ExportMessage::Close) => { + if let TransactionsModal::Export(_) = &self.modal { + self.modal = TransactionsModal::None; + } + } + _ => { + return match &mut self.modal { + TransactionsModal::CreateRbf(modal) => modal.update(daemon, _cache, message), + TransactionsModal::Export(modal) => modal.update(message), + TransactionsModal::None => Command::none(), + }; + } }; Command::none() } @@ -281,6 +309,17 @@ impl State for TransactionsPanel { Message::HistoryTransactions, )]) } + + fn subscription(&self) -> iced::Subscription { + if let TransactionsModal::Export(modal) = &self.modal { + if let Some(sub) = modal.subscription() { + return sub.map(|m| { + Message::View(view::Message::Export(ExportMessage::ExportProgress(m))) + }); + } + } + iced::Subscription::none() + } } impl From for Box { @@ -289,6 +328,7 @@ impl From for Box { } } +#[derive(Debug)] pub struct CreateRbfModal { /// Transaction to replace. tx: model::HistoryTransaction, diff --git a/liana-gui/src/app/view/export.rs b/liana-gui/src/app/view/export.rs new file mode 100644 index 000000000..40770de47 --- /dev/null +++ b/liana-gui/src/app/view/export.rs @@ -0,0 +1,88 @@ +use iced::{ + widget::{progress_bar, Button, Column, Container, Row, Space}, + Length, +}; +use liana_ui::{ + component::{ + card, + text::{h4_bold, text}, + }, + theme, + widget::Element, +}; + +use crate::app::{export::Error, state::export::ExportState}; +use crate::app::{state::export::ExportMessage, view::message::Message}; + +/// Return the modal view for an export task +pub fn export_modal<'a>( + state: &ExportState, + error: Option<&'a Error>, + export_type: &str, +) -> Element<'a, Message> { + let button = match state { + ExportState::Started | ExportState::Progress(_) => { + Some(Button::new("Cancel").on_press(ExportMessage::UserStop.into())) + } + ExportState::Ended | ExportState::TimedOut | ExportState::Aborted => { + Some(Button::new("Close").on_press(ExportMessage::Close.into())) + } + _ => None, + } + .map(|b| b.height(32).style(theme::Button::Primary)); + let msg = if let Some(error) = error { + format!("{:?}", error) + } else { + match state { + ExportState::Init => "".to_string(), + ExportState::ChoosePath => { + "Select the path you want to export in the popup window...".into() + } + ExportState::Path(_) => "".into(), + ExportState::Started => "Starting export...".into(), + ExportState::Progress(p) => format!("Progress: {}%", p.round()), + ExportState::TimedOut => "Export failed: timeout".into(), + ExportState::Aborted => "Export canceled".into(), + ExportState::Ended => "Export successfull!".into(), + ExportState::Closed => "".into(), + } + }; + let p = match state { + ExportState::Init => 0.0, + ExportState::ChoosePath | ExportState::Path(_) | ExportState::Started => 5.0, + ExportState::Progress(p) => *p, + ExportState::TimedOut | ExportState::Aborted | ExportState::Ended | ExportState::Closed => { + 100.0 + } + }; + let progress_bar_row = Row::new() + .push(Space::with_width(30)) + .push(progress_bar(0.0..=100.0, p)) + .push(Space::with_width(30)); + let button_row = button.map(|b| { + Row::new() + .push(Space::with_width(Length::Fill)) + .push(b) + .push(Space::with_width(Length::Fill)) + }); + card::simple( + Column::new() + .spacing(10) + .push(Container::new(h4_bold(format!("Export {export_type}"))).width(Length::Fill)) + .push(Space::with_height(Length::Fill)) + .push(progress_bar_row) + .push(Space::with_height(Length::Fill)) + .push( + Row::new() + .push(Space::with_width(Length::Fill)) + .push(text(msg)) + .push(Space::with_width(Length::Fill)), + ) + .push(Space::with_height(Length::Fill)) + .push_maybe(button_row) + .push(Space::with_height(5)), + ) + .width(Length::Fixed(500.0)) + .height(Length::Fixed(220.0)) + .into() +} diff --git a/liana-gui/src/app/view/message.rs b/liana-gui/src/app/view/message.rs index c726b5fc5..9cb033185 100644 --- a/liana-gui/src/app/view/message.rs +++ b/liana-gui/src/app/view/message.rs @@ -1,4 +1,7 @@ -use crate::{app::menu::Menu, node::bitcoind::RpcAuthType}; +use crate::{ + app::{menu::Menu, state::export::ExportMessage}, + node::bitcoind::RpcAuthType, +}; use liana::miniscript::bitcoin::bip32::Fingerprint; #[derive(Debug, Clone)] @@ -19,6 +22,7 @@ pub enum Message { SelectHardwareWallet(usize), CreateRbf(CreateRbfMessage), ShowQrCode(usize), + Export(ExportMessage), } #[derive(Debug, Clone)] diff --git a/liana-gui/src/app/view/mod.rs b/liana-gui/src/app/view/mod.rs index 66e25af41..d6ba29ee7 100644 --- a/liana-gui/src/app/view/mod.rs +++ b/liana-gui/src/app/view/mod.rs @@ -3,6 +3,7 @@ mod message; mod warning; pub mod coins; +pub mod export; pub mod home; pub mod hw; pub mod psbt; diff --git a/liana-gui/src/app/view/transactions.rs b/liana-gui/src/app/view/transactions.rs index dc51f9193..032f6cb95 100644 --- a/liana-gui/src/app/view/transactions.rs +++ b/liana-gui/src/app/view/transactions.rs @@ -1,7 +1,11 @@ use std::collections::{HashMap, HashSet}; use chrono::{DateTime, Local, Utc}; -use iced::{alignment, widget::tooltip, Alignment, Length}; +use iced::{ + alignment, + widget::{tooltip, Space}, + Alignment, Length, +}; use liana_ui::{ color, @@ -15,7 +19,12 @@ use crate::{ cache::Cache, error::Error, menu::Menu, - view::{dashboard, label, message::CreateRbfMessage, message::Message, warning::warn}, + state::export::ExportMessage, + view::{ + dashboard, label, + message::{CreateRbfMessage, Message}, + warning::warn, + }, }, daemon::model::{HistoryTransaction, Txid}, }; @@ -32,7 +41,16 @@ pub fn transactions_view<'a>( cache, warning, Column::new() - .push(Container::new(h3("Transactions")).width(Length::Fill)) + .push( + Row::new() + .push(Container::new(h3("Transactions"))) + .push(Space::with_width(Length::Fill)) + .push( + Button::new("Export") + .on_press(ExportMessage::Open.into()) + .style(theme::Button::Secondary), + ), + ) .push( Column::new() .spacing(10)