Skip to content

Commit

Permalink
gui: add subscription to export transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
pythcoiner committed Dec 17, 2024
1 parent c9de7dc commit 7920eea
Show file tree
Hide file tree
Showing 6 changed files with 616 additions and 0 deletions.
1 change: 1 addition & 0 deletions liana-gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ base64 = "0.21"
bitcoin_hashes = "0.12"
reqwest = { version = "0.11", default-features=false, features = ["json", "rustls-tls"] }
rust-ini = "0.19.0"
rfd = "0.15.1"


[target.'cfg(windows)'.dependencies]
Expand Down
1 change: 1 addition & 0 deletions liana-gui/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod settings;
pub mod state;
pub mod view;
pub mod wallet;
pub mod export;

mod error;

Expand Down
159 changes: 159 additions & 0 deletions liana-gui/src/app/state/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
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::{
message::Message,
view::{self, export::export_modal},
};
use crate::daemon::Daemon;
use crate::export::{self, get_path, ExportProgress};

#[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(handle) => {
self.handle = Some(handle);
self.state = ExportState::Progress(0.0);
}
ExportProgress::Progress(p) => {
if let ExportState::Progress(_) = self.state {
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;
} else {
log::warn!("no handle !!!!!!!!!!!!!!!!!!!!!!!!");
}
}

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(), Box::new(path.to_path_buf())),
export::export_subscription,
))
}
_ => None,
}
} else {
None
}
}
}
89 changes: 89 additions & 0 deletions liana-gui/src/app/view/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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::state::export::ExportState;
use crate::app::{state::export::ExportMessage, view::message::Message};
use crate::export::Error;

/// 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()
}
Loading

0 comments on commit 7920eea

Please sign in to comment.