Add loader error message and retry button
Refac notification::warning component to use a Collapse instead of Tooltip. Add a retry button to start again Liana. close #134
This commit is contained in:
parent
d6e6c67d1d
commit
17669547ed
@ -3,7 +3,7 @@ use liana::config::ConfigError;
|
||||
use std::convert::From;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Config(String),
|
||||
Daemon(DaemonError),
|
||||
|
||||
@ -44,7 +44,7 @@ impl std::fmt::Display for WarningMessage {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn warn<'a, T: 'a>(error: Option<&Error>) -> widget::Container<'a, T> {
|
||||
pub fn warn<'a, T: 'a + Clone>(error: Option<&Error>) -> widget::Container<'a, T> {
|
||||
if let Some(w) = error {
|
||||
let message: WarningMessage = w.into();
|
||||
notification::warning(message.to_string(), w.to_string()).width(Length::Fill)
|
||||
|
||||
@ -22,8 +22,8 @@ impl EmbeddedDaemon {
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<(), DaemonError> {
|
||||
let handle = DaemonHandle::start_default(self.config.clone())
|
||||
.map_err(|e| DaemonError::Start(e.to_string()))?;
|
||||
let handle =
|
||||
DaemonHandle::start_default(self.config.clone()).map_err(DaemonError::Start)?;
|
||||
self.handle = Some(RwLock::new(handle));
|
||||
Ok(())
|
||||
}
|
||||
@ -45,8 +45,7 @@ impl Daemon for EmbeddedDaemon {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let next =
|
||||
DaemonHandle::start_default(cfg).map_err(|e| DaemonError::Start(e.to_string()))?;
|
||||
let next = DaemonHandle::start_default(cfg).map_err(DaemonError::Start)?;
|
||||
self.handle.take().unwrap().into_inner().unwrap().shutdown();
|
||||
self.handle = Some(RwLock::new(next));
|
||||
Ok(())
|
||||
|
||||
@ -9,9 +9,10 @@ use std::io::ErrorKind;
|
||||
use liana::{
|
||||
config::Config,
|
||||
miniscript::bitcoin::{util::psbt::Psbt, Address, OutPoint, Txid},
|
||||
StartupError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum DaemonError {
|
||||
/// Something was wrong with the request.
|
||||
Rpc(i32, String),
|
||||
@ -22,7 +23,7 @@ pub enum DaemonError {
|
||||
/// No response.
|
||||
NoAnswer,
|
||||
// Error at start up.
|
||||
Start(String),
|
||||
Start(StartupError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DaemonError {
|
||||
|
||||
@ -4,21 +4,27 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::{
|
||||
widget::{Column, Text},
|
||||
widget::{Column, Container, ProgressBar},
|
||||
Element,
|
||||
};
|
||||
use iced::{Alignment, Command, Subscription};
|
||||
use iced::{Alignment, Command, Length, Subscription};
|
||||
use iced_native::{window, Event};
|
||||
use log::{debug, info};
|
||||
|
||||
use liana::{
|
||||
config::{Config, ConfigError},
|
||||
miniscript::bitcoin,
|
||||
StartupError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::config::{default_datadir, Config as GUIConfig},
|
||||
daemon::{client, embedded::EmbeddedDaemon, model::*, Daemon, DaemonError},
|
||||
ui::{
|
||||
component::{button, notification, text::*},
|
||||
icon,
|
||||
util::Collection,
|
||||
},
|
||||
};
|
||||
|
||||
type Lianad = client::Lianad<client::jsonrpc::JsonRPCClient>;
|
||||
@ -27,11 +33,12 @@ pub struct Loader {
|
||||
pub gui_config: GUIConfig,
|
||||
pub daemon_started: bool,
|
||||
|
||||
daemon_config: Config,
|
||||
should_exit: bool,
|
||||
step: Step,
|
||||
}
|
||||
|
||||
enum Step {
|
||||
pub enum Step {
|
||||
Connecting,
|
||||
StartingDaemon,
|
||||
Syncing {
|
||||
@ -43,6 +50,7 @@ enum Step {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
View(ViewMessage),
|
||||
Event(iced_native::Event),
|
||||
Syncing(Result<GetInfoResult, DaemonError>),
|
||||
Synced(
|
||||
@ -65,6 +73,7 @@ impl Loader {
|
||||
.unwrap();
|
||||
(
|
||||
Loader {
|
||||
daemon_config: daemon_config.clone(),
|
||||
gui_config,
|
||||
step: Step::Connecting,
|
||||
should_exit: false,
|
||||
@ -177,6 +186,11 @@ impl Loader {
|
||||
|
||||
pub fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::View(ViewMessage::Retry) => {
|
||||
let (loader, cmd) = Self::new(self.gui_config.clone(), self.daemon_config.clone());
|
||||
*self = loader;
|
||||
cmd
|
||||
}
|
||||
Message::Started(res) => self.on_start(res),
|
||||
Message::Loaded(res) => self.on_load(res),
|
||||
Message::Syncing(res) => self.on_sync(res),
|
||||
@ -201,23 +215,78 @@ impl Loader {
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
match &self.step {
|
||||
Step::StartingDaemon => cover(Text::new("Starting daemon...")),
|
||||
Step::Connecting => cover(Text::new("Connecting to daemon...")),
|
||||
Step::Syncing { progress, .. } => cover(Text::new(format!("Syncing... {}%", progress))),
|
||||
Step::Error(error) => cover(Text::new(format!("Error: {}", error))),
|
||||
}
|
||||
view(&self.step).map(Message::View)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cover<'a, T: 'a, C: Into<Element<'a, T>>>(content: C) -> Element<'a, T> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ViewMessage {
|
||||
Retry,
|
||||
}
|
||||
|
||||
pub fn view(step: &Step) -> Element<ViewMessage> {
|
||||
match &step {
|
||||
Step::StartingDaemon => cover(
|
||||
None,
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.push(ProgressBar::new(0.0..=1.0, 0.0).width(Length::Fill))
|
||||
.push(text("Starting daemon...")),
|
||||
),
|
||||
Step::Connecting => cover(
|
||||
None,
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.push(ProgressBar::new(0.0..=1.0, 0.0).width(Length::Fill))
|
||||
.push(text("Connecting to daemon...")),
|
||||
),
|
||||
Step::Syncing { progress, .. } => cover(
|
||||
None,
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.push(ProgressBar::new(0.0..=1.0, *progress as f32).width(Length::Fill))
|
||||
.push(text("Syncing the wallet with the blockchain...")),
|
||||
),
|
||||
Step::Error(error) => cover(
|
||||
Some(("Error while starting the internal daemon", error)),
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.push(icon::plug_icon().size(100).width(Length::Units(300)))
|
||||
.push(
|
||||
if matches!(
|
||||
error.as_ref(),
|
||||
Error::Daemon(DaemonError::Start(StartupError::Bitcoind(_)))
|
||||
) {
|
||||
text("Liana failed to start, please check if bitcoind is running")
|
||||
} else {
|
||||
text("Liana failed to start")
|
||||
},
|
||||
)
|
||||
.push(
|
||||
button::primary(None, "Retry")
|
||||
.width(Length::Units(200))
|
||||
.on_press(ViewMessage::Retry),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cover<'a, T: 'a + Clone, C: Into<Element<'a, T>>>(
|
||||
warn: Option<(&'static str, &Error)>,
|
||||
content: C,
|
||||
) -> Element<'a, T> {
|
||||
Column::new()
|
||||
.push(content)
|
||||
.width(iced::Length::Fill)
|
||||
.height(iced::Length::Fill)
|
||||
.padding(50)
|
||||
.spacing(50)
|
||||
.align_items(Alignment::Center)
|
||||
.push_maybe(warn.map(|w| notification::warning(w.0.to_string(), w.1.to_string())))
|
||||
.push(
|
||||
Container::new(content)
|
||||
.width(iced::Length::Fill)
|
||||
.height(iced::Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.padding(50),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -239,8 +308,7 @@ async fn connect(
|
||||
pub async fn start_daemon(config_path: PathBuf) -> Result<Arc<dyn Daemon + Sync + Send>, Error> {
|
||||
debug!("starting liana daemon");
|
||||
|
||||
let config = Config::from_file(Some(config_path))
|
||||
.map_err(|e| DaemonError::Start(format!("Error parsing config: {}", e)))?;
|
||||
let config = Config::from_file(Some(config_path)).map_err(Error::Config)?;
|
||||
|
||||
let mut daemon = EmbeddedDaemon::new(config);
|
||||
daemon.start()?;
|
||||
|
||||
@ -112,7 +112,7 @@ impl button::StyleSheet for Style {
|
||||
},
|
||||
Style::Transparent => button::Appearance {
|
||||
shadow_offset: Vector::default(),
|
||||
background: color::BACKGROUND.into(),
|
||||
background: Color::TRANSPARENT.into(),
|
||||
border_radius: 10.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
|
||||
@ -1,23 +1,52 @@
|
||||
use crate::ui::{color, icon};
|
||||
use crate::ui::{
|
||||
color,
|
||||
component::{button, collapse, text::*},
|
||||
icon,
|
||||
};
|
||||
use iced::{
|
||||
widget::{container, tooltip, Container, Row, Text, Tooltip},
|
||||
Length,
|
||||
widget::{container, Button, Container, Row},
|
||||
Alignment, Element, Length,
|
||||
};
|
||||
|
||||
pub fn warning<'a, T: 'a>(message: String, error: String) -> Container<'a, T> {
|
||||
Container::new(Container::new(
|
||||
Tooltip::new(
|
||||
Row::new()
|
||||
.push(icon::warning_icon())
|
||||
.push(Text::new(message))
|
||||
.spacing(20),
|
||||
error,
|
||||
tooltip::Position::Bottom,
|
||||
)
|
||||
.style(TooltipWarningStyle),
|
||||
))
|
||||
pub fn warning<'a, T: 'a + Clone>(message: String, error: String) -> Container<'a, T> {
|
||||
let message_clone = message.clone();
|
||||
Container::new(Container::new(collapse::Collapse::new(
|
||||
move || {
|
||||
Button::new(
|
||||
Row::new()
|
||||
.push(
|
||||
Container::new(text(message_clone.to_string()).small().bold())
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(text("Learn more").small().bold())
|
||||
.push(icon::collapse_icon()),
|
||||
),
|
||||
)
|
||||
.style(button::Style::Transparent.into())
|
||||
},
|
||||
move || {
|
||||
Button::new(
|
||||
Row::new()
|
||||
.push(
|
||||
Container::new(text(message.to_owned()).small().bold()).width(Length::Fill),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(text("Learn more").small().bold())
|
||||
.push(icon::collapsed_icon()),
|
||||
),
|
||||
)
|
||||
.style(button::Style::Transparent.into())
|
||||
},
|
||||
move || Element::<'a, T>::from(text(error.to_owned()).small()),
|
||||
)))
|
||||
.padding(15)
|
||||
.center_x()
|
||||
.style(WarningStyle)
|
||||
.width(Length::Fill)
|
||||
}
|
||||
@ -47,29 +76,3 @@ impl From<WarningStyle> for iced::theme::Container {
|
||||
iced::theme::Container::Custom(i.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TooltipWarningStyle;
|
||||
impl container::StyleSheet for TooltipWarningStyle {
|
||||
type Style = iced::Theme;
|
||||
fn appearance(&self, _style: &Self::Style) -> container::Appearance {
|
||||
container::Appearance {
|
||||
border_radius: 0.0,
|
||||
border_width: 1.0,
|
||||
text_color: color::WARNING.into(),
|
||||
background: color::FOREGROUND.into(),
|
||||
border_color: color::WARNING,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TooltipWarningStyle> for Box<dyn container::StyleSheet<Style = iced::Theme>> {
|
||||
fn from(s: TooltipWarningStyle) -> Box<dyn container::StyleSheet<Style = iced::Theme>> {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TooltipWarningStyle> for iced::theme::Container {
|
||||
fn from(i: TooltipWarningStyle) -> iced::theme::Container {
|
||||
iced::theme::Container::Custom(i.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,10 @@ fn icon(unicode: char) -> Text<'static> {
|
||||
.size(20)
|
||||
}
|
||||
|
||||
pub fn plug_icon() -> Text<'static> {
|
||||
icon('\u{F4F6}')
|
||||
}
|
||||
|
||||
pub fn reload_icon() -> Text<'static> {
|
||||
icon('\u{F130}')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user