Skip to content

Commit

Permalink
gui: add a modal for export tasks in transactions panels
Browse files Browse the repository at this point in the history
  • Loading branch information
pythcoiner committed Dec 11, 2024
1 parent 5ea043b commit f6925d8
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 21 deletions.
3 changes: 3 additions & 0 deletions liana-gui/src/app/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
hw::HardwareWalletMessage,
};

use super::state::export::ExportMessage;

#[derive(Debug)]
pub enum Message {
Tick,
Expand Down Expand Up @@ -43,4 +45,5 @@ pub enum Message {
LabelsUpdated(Result<HashMap<String, Option<String>>, Error>),
BroadcastModal(Result<HashSet<Txid>, Error>),
RbfModal(Box<HistoryTransaction>, bool, Result<HashSet<Txid>, Error>),
Export(ExportMessage),
}
2 changes: 1 addition & 1 deletion liana-gui/src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
154 changes: 154 additions & 0 deletions liana-gui/src/app/state/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use std::{
path::PathBuf,
sync::{Arc, Mutex},
};

use iced::{Command, Subscription};
use liana_ui::{component::modal::Modal, widget::Element};
use tokio::task::JoinHandle;

use crate::app::{
export::{self, get_path, ExportProgress, ExportType},
message::Message,
view::{self, export::export_modal},
};
use crate::daemon::Daemon;

#[derive(Debug, Clone)]
pub enum ExportMessage {
Open,
ExportProgress(ExportProgress),
TimedOut,
UserStop,
Path(Option<PathBuf>),
Close,
}

impl From<ExportMessage> for view::Message {
fn from(value: ExportMessage) -> Self {
Self::Export(value)
}
}

#[derive(Debug, PartialEq)]
pub enum ExportState {
Init,
ChoosePath,
Path(PathBuf),
Started,
Progress(f32),
TimedOut,
Aborted,
Ended,
Closed,
}

#[derive(Debug)]
pub struct ExportModal {
path: Option<PathBuf>,
handle: Option<Arc<Mutex<JoinHandle<()>>>>,
state: ExportState,
error: Option<export::Error>,
daemon: Arc<dyn Daemon + Sync + Send>,
}

impl ExportModal {
#[allow(clippy::new_without_default)]
pub fn new(daemon: Arc<dyn Daemon + Sync + Send>) -> Self {
Self {
path: None,
handle: None,
state: ExportState::Init,
error: None,
daemon,
}
}

pub fn launch(&mut self) -> Command<crate::app::message::Message> {
Command::perform(get_path(), |m| {
Message::View(view::Message::Export(ExportMessage::Path(m)))
})
}

pub fn update(&mut self, message: crate::app::message::Message) -> Command<Message> {
if let crate::app::message::Message::View(view::Message::Export(m)) = message {
match m {
ExportMessage::ExportProgress(m) => match m {
ExportProgress::Started(_) => self.state = ExportState::Progress(0.0),
ExportProgress::Progress(p) => self.state = ExportState::Progress(p),
ExportProgress::Finnished | ExportProgress::Ended => {
self.state = ExportState::Ended
}
ExportProgress::Error(e) => self.error = Some(e),
ExportProgress::None => {}
},
ExportMessage::TimedOut => {
self.stop(ExportState::TimedOut);
}
ExportMessage::UserStop => {
self.stop(ExportState::Aborted);
}
ExportMessage::Path(p) => {
if let Some(path) = p {
self.path = Some(path);
self.start();
} else {
return Command::perform(async {}, |_| {
Message::View(view::Message::Export(ExportMessage::Close))
});
}
}
ExportMessage::Close | ExportMessage::Open => { /* unreachable */ }
}
Command::none()
} else {
Command::none()
}
}
pub fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<view::Message> {
let modal = Modal::new(
content,
export_modal(&self.state, self.error.as_ref(), "Transactions"),
);
match self.state {
ExportState::TimedOut
| ExportState::Aborted
| ExportState::Ended
| ExportState::Closed => modal.on_blur(Some(view::Message::Close)),
_ => modal,
}
.into()
}

pub fn start(&mut self) {
self.state = ExportState::Started;
}

pub fn stop(&mut self, state: ExportState) {
if let Some(handle) = self.handle.take() {
handle.lock().expect("poisoined").abort();
self.state = state;
}
}

pub fn subscription(&self) -> Option<Subscription<export::ExportProgress>> {
if let Some(path) = &self.path {
match &self.state {
ExportState::Started | ExportState::Progress(_) => {
Some(iced::subscription::unfold(
"transactions",
export::ExportState::new(
self.daemon.clone(),
ExportType::Transactions,
Box::new(path.to_path_buf()),
),
export::export_subscription,
))
}
_ => None,
}
} else {
None
}
}
}
1 change: 1 addition & 0 deletions liana-gui/src/app/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod coins;
pub mod export;
mod label;
mod psbt;
mod psbts;
Expand Down
72 changes: 56 additions & 16 deletions liana-gui/src/app/state/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Wallet>,
txs: Vec<HistoryTransaction>,
labels_edited: LabelsEdited,
selected_tx: Option<HistoryTransaction>,
warning: Option<Error>,
create_rbf_modal: Option<CreateRbfModal>,
modal: TransactionsModal,
is_last_page: bool,
processing: bool,
}
Expand All @@ -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,
}
Expand All @@ -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;
}
}

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

Expand Down Expand Up @@ -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();
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -281,6 +309,17 @@ impl State for TransactionsPanel {
Message::HistoryTransactions,
)])
}

fn subscription(&self) -> iced::Subscription<Message> {
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<TransactionsPanel> for Box<dyn State> {
Expand All @@ -289,6 +328,7 @@ impl From<TransactionsPanel> for Box<dyn State> {
}
}

#[derive(Debug)]
pub struct CreateRbfModal {
/// Transaction to replace.
tx: model::HistoryTransaction,
Expand Down
Loading

0 comments on commit f6925d8

Please sign in to comment.