gui: add import/export features in settings
This commit is contained in:
parent
f1b62074d3
commit
f41de56e38
@ -49,3 +49,9 @@ pub enum Message {
|
||||
RbfModal(Box<HistoryTransaction>, bool, Result<HashSet<Txid>, Error>),
|
||||
Export(ImportExportMessage),
|
||||
}
|
||||
|
||||
impl From<ImportExportMessage> for Message {
|
||||
fn from(value: ImportExportMessage) -> Self {
|
||||
Message::View(view::Message::ImportExport(value))
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@ impl Panels {
|
||||
data_dir: PathBuf,
|
||||
daemon_backend: DaemonBackend,
|
||||
internal_bitcoind: Option<&Bitcoind>,
|
||||
config: Arc<Config>,
|
||||
) -> Panels {
|
||||
Self {
|
||||
current: Menu::Home,
|
||||
@ -93,6 +94,7 @@ impl Panels {
|
||||
wallet.clone(),
|
||||
daemon_backend,
|
||||
internal_bitcoind.is_some(),
|
||||
config.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -132,7 +134,7 @@ impl Panels {
|
||||
|
||||
pub struct App {
|
||||
cache: Cache,
|
||||
config: Config,
|
||||
config: Arc<Config>,
|
||||
wallet: Arc<Wallet>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
@ -149,12 +151,14 @@ impl App {
|
||||
data_dir: PathBuf,
|
||||
internal_bitcoind: Option<Bitcoind>,
|
||||
) -> (App, Task<Message>) {
|
||||
let config = Arc::new(config);
|
||||
let mut panels = Panels::new(
|
||||
&cache,
|
||||
wallet.clone(),
|
||||
data_dir,
|
||||
daemon.backend(),
|
||||
internal_bitcoind.as_ref(),
|
||||
config.clone(),
|
||||
);
|
||||
let cmd = panels.home.reload(daemon.clone(), wallet.clone());
|
||||
(
|
||||
|
||||
@ -8,10 +8,7 @@ use liana_ui::{component::modal::Modal, widget::Element};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
self,
|
||||
view::{self, export::export_modal},
|
||||
},
|
||||
app::view::{self, export::export_modal},
|
||||
daemon::Daemon,
|
||||
export::{self, get_path, ImportExportMessage, ImportExportState, ImportExportType, Progress},
|
||||
};
|
||||
@ -58,16 +55,20 @@ impl ExportModal {
|
||||
ImportExportType::ImportPsbt => "psbt.psbt".into(),
|
||||
ImportExportType::ImportDescriptor => "descriptor.descriptor".into(),
|
||||
ImportExportType::ExportLabels => format!("liana-labels-{date}.csv"),
|
||||
ImportExportType::ExportBackup(_) => format!("liana-backup-{date}.json"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch(&self) -> Task<app::message::Message> {
|
||||
Task::perform(get_path(self.default_filename()), |m| {
|
||||
app::message::Message::View(view::Message::ImportExport(ImportExportMessage::Path(m)))
|
||||
pub fn launch<M: From<ImportExportMessage> + Send + 'static>(&self) -> Task<M> {
|
||||
Task::perform(get_path(self.default_filename()), move |m| {
|
||||
ImportExportMessage::Path(m).into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: ImportExportMessage) -> Task<app::message::Message> {
|
||||
pub fn update<M: From<ImportExportMessage> + Send + 'static>(
|
||||
&mut self,
|
||||
message: ImportExportMessage,
|
||||
) -> Task<M> {
|
||||
match message {
|
||||
ImportExportMessage::Progress(m) => match m {
|
||||
Progress::Started(handle) => {
|
||||
@ -106,11 +107,7 @@ impl ExportModal {
|
||||
self.path = Some(path);
|
||||
self.start();
|
||||
} else {
|
||||
return Task::perform(async {}, |_| {
|
||||
app::message::Message::View(view::Message::ImportExport(
|
||||
ImportExportMessage::Close,
|
||||
))
|
||||
});
|
||||
return Task::perform(async {}, |_| ImportExportMessage::Close.into());
|
||||
}
|
||||
}
|
||||
ImportExportMessage::Close | ImportExportMessage::Open => { /* unreachable */ }
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod coins;
|
||||
mod export;
|
||||
pub mod export;
|
||||
mod label;
|
||||
mod psbt;
|
||||
mod psbts;
|
||||
|
||||
@ -20,16 +20,22 @@ use crate::{
|
||||
state::State,
|
||||
view::{self},
|
||||
wallet::Wallet,
|
||||
Config,
|
||||
},
|
||||
backup::Backup,
|
||||
daemon::{Daemon, DaemonBackend},
|
||||
export::{ImportExportMessage, ImportExportType},
|
||||
};
|
||||
|
||||
use super::export::ExportModal;
|
||||
|
||||
pub struct SettingsState {
|
||||
data_dir: PathBuf,
|
||||
wallet: Arc<Wallet>,
|
||||
setting: Option<Box<dyn State>>,
|
||||
daemon_backend: DaemonBackend,
|
||||
internal_bitcoind: bool,
|
||||
config: Arc<Config>,
|
||||
}
|
||||
|
||||
impl SettingsState {
|
||||
@ -38,6 +44,7 @@ impl SettingsState {
|
||||
wallet: Arc<Wallet>,
|
||||
daemon_backend: DaemonBackend,
|
||||
internal_bitcoind: bool,
|
||||
config: Arc<Config>,
|
||||
) -> Self {
|
||||
Self {
|
||||
data_dir,
|
||||
@ -45,6 +52,7 @@ impl SettingsState {
|
||||
setting: None,
|
||||
daemon_backend,
|
||||
internal_bitcoind,
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,6 +87,12 @@ impl State for SettingsState {
|
||||
self.setting = Some(BackendSettingsState::new().into());
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ImportExportSection)) => {
|
||||
self.setting = Some(
|
||||
ImportExportSettingsState::new(self.wallet.clone(), self.config.clone()).into(),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::AboutSection)) => {
|
||||
self.setting = Some(AboutSettingsState::default().into());
|
||||
let wallet = self.wallet.clone();
|
||||
@ -89,7 +103,12 @@ impl State for SettingsState {
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::EditWalletSettings)) => {
|
||||
self.setting = Some(
|
||||
WalletSettingsState::new(self.data_dir.clone(), self.wallet.clone()).into(),
|
||||
WalletSettingsState::new(
|
||||
self.data_dir.clone(),
|
||||
self.wallet.clone(),
|
||||
self.config.clone(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
let wallet = self.wallet.clone();
|
||||
self.setting
|
||||
@ -145,6 +164,139 @@ impl From<SettingsState> for Box<dyn State> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImportExportSettingsState {
|
||||
warning: Option<Error>,
|
||||
modal: Option<ExportModal>,
|
||||
wallet: Arc<Wallet>,
|
||||
config: Arc<Config>,
|
||||
}
|
||||
|
||||
impl ImportExportSettingsState {
|
||||
pub fn new(wallet: Arc<Wallet>, config: Arc<Config>) -> Self {
|
||||
Self {
|
||||
warning: None,
|
||||
modal: None,
|
||||
wallet,
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! launch {
|
||||
($s:ident, $m: ident) => {
|
||||
let launch = $m.launch();
|
||||
$s.modal = Some($m);
|
||||
return launch
|
||||
};
|
||||
}
|
||||
|
||||
impl State for ImportExportSettingsState {
|
||||
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
let content = view::settings::import_export(cache, self.warning.as_ref());
|
||||
if let Some(modal) = &self.modal {
|
||||
modal.view(content)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> iced::Subscription<Message> {
|
||||
if let Some(modal) = &self.modal {
|
||||
if let Some(sub) = modal.subscription() {
|
||||
return sub.map(|m| {
|
||||
Message::View(view::Message::Settings(
|
||||
view::SettingsMessage::ImportExport(ImportExportMessage::Progress(m)),
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
iced::Subscription::none()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
cache: &Cache,
|
||||
message: Message,
|
||||
) -> Task<Message> {
|
||||
match message {
|
||||
Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => {
|
||||
self.modal = None;
|
||||
}
|
||||
Message::View(view::Message::ImportExport(m)) => {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
return modal.update(m);
|
||||
};
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ImportExport(m))) => {
|
||||
if let Some(modal) = self.modal.as_mut() {
|
||||
return modal.update(m);
|
||||
};
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportDescriptor)) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(
|
||||
daemon,
|
||||
ImportExportType::Descriptor(self.wallet.main_descriptor.clone()),
|
||||
);
|
||||
launch!(self, modal);
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportTransactions)) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(daemon, ImportExportType::Transactions);
|
||||
launch!(self, modal);
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportLabels)) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(daemon, ImportExportType::ExportLabels);
|
||||
launch!(self, modal);
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportWallet)) => {
|
||||
if self.modal.is_none() {
|
||||
let datadir = cache.datadir_path.clone();
|
||||
let network = cache.network;
|
||||
let config = self.config.clone();
|
||||
let wallet = self.wallet.clone();
|
||||
let daemon = daemon.clone();
|
||||
return Task::perform(
|
||||
async move {
|
||||
let backup =
|
||||
Backup::from_app(datadir, network, config, wallet, daemon).await;
|
||||
let backup = backup.unwrap();
|
||||
serde_json::to_string_pretty(&backup).unwrap()
|
||||
// TODO: do not unwrap, return an error message instead
|
||||
},
|
||||
|s| {
|
||||
Message::View(view::Message::Settings(
|
||||
view::SettingsMessage::ExportBackup(s),
|
||||
))
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(backup))) => {
|
||||
let modal = ExportModal::new(daemon, ImportExportType::ExportBackup(backup));
|
||||
launch!(self, modal);
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ImportWallet)) => {
|
||||
// TODO:
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImportExportSettingsState> for Box<dyn State> {
|
||||
fn from(s: ImportExportSettingsState) -> Box<dyn State> {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AboutSettingsState {
|
||||
daemon_version: Option<String>,
|
||||
|
||||
@ -17,34 +17,57 @@ use liana_ui::{
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
cache::Cache, error::Error, message::Message, settings, state::State, view, wallet::Wallet,
|
||||
cache::Cache,
|
||||
error::Error,
|
||||
message::Message,
|
||||
settings,
|
||||
state::{export::ExportModal, State},
|
||||
view,
|
||||
wallet::Wallet,
|
||||
Config,
|
||||
},
|
||||
backup::Backup,
|
||||
daemon::{Daemon, DaemonBackend},
|
||||
export::{ImportExportMessage, ImportExportType},
|
||||
hw::{HardwareWallet, HardwareWalletConfig, HardwareWallets},
|
||||
};
|
||||
|
||||
enum Modal {
|
||||
None,
|
||||
RegisterWallet(RegisterWalletModal),
|
||||
ImportExport(ExportModal),
|
||||
}
|
||||
|
||||
impl Modal {
|
||||
fn is_none(&self) -> bool {
|
||||
matches!(self, Modal::None)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WalletSettingsState {
|
||||
data_dir: PathBuf,
|
||||
warning: Option<Error>,
|
||||
descriptor: LianaDescriptor,
|
||||
keys_aliases: Vec<(Fingerprint, form::Value<String>)>,
|
||||
wallet: Arc<Wallet>,
|
||||
modal: Option<RegisterWalletModal>,
|
||||
modal: Modal,
|
||||
processing: bool,
|
||||
updated: bool,
|
||||
config: Arc<Config>,
|
||||
}
|
||||
|
||||
impl WalletSettingsState {
|
||||
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>) -> Self {
|
||||
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>, config: Arc<Config>) -> Self {
|
||||
WalletSettingsState {
|
||||
data_dir,
|
||||
descriptor: wallet.main_descriptor.clone(),
|
||||
keys_aliases: Self::keys_aliases(&wallet),
|
||||
wallet,
|
||||
warning: None,
|
||||
modal: None,
|
||||
modal: Modal::None,
|
||||
processing: false,
|
||||
updated: false,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,20 +109,31 @@ impl State for WalletSettingsState {
|
||||
self.processing,
|
||||
self.updated,
|
||||
);
|
||||
if let Some(m) = &self.modal {
|
||||
modal::Modal::new(content, m.view())
|
||||
|
||||
match &self.modal {
|
||||
Modal::None => content,
|
||||
Modal::RegisterWallet(m) => modal::Modal::new(content, m.view())
|
||||
.on_blur(Some(view::Message::Close))
|
||||
.into()
|
||||
} else {
|
||||
content
|
||||
.into(),
|
||||
Modal::ImportExport(m) => m.view(content),
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
if let Some(modal) = &self.modal {
|
||||
modal.subscription()
|
||||
} else {
|
||||
Subscription::none()
|
||||
match &self.modal {
|
||||
Modal::None => Subscription::none(),
|
||||
Modal::RegisterWallet(modal) => modal.subscription(),
|
||||
Modal::ImportExport(modal) => {
|
||||
if let Some(sub) = modal.subscription() {
|
||||
sub.map(|m| {
|
||||
Message::View(view::Message::Settings(
|
||||
view::SettingsMessage::ImportExport(ImportExportMessage::Progress(m)),
|
||||
))
|
||||
})
|
||||
} else {
|
||||
Subscription::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +146,7 @@ impl State for WalletSettingsState {
|
||||
match message {
|
||||
Message::WalletUpdated(res) => {
|
||||
self.processing = false;
|
||||
if let Some(modal) = &mut self.modal {
|
||||
if let Modal::RegisterWallet(modal) = &mut self.modal {
|
||||
modal.update(daemon, cache, Message::WalletUpdated(res))
|
||||
} else {
|
||||
match res {
|
||||
@ -139,7 +173,7 @@ impl State for WalletSettingsState {
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::Save)) => {
|
||||
self.modal = None;
|
||||
self.modal = Modal::None;
|
||||
self.processing = true;
|
||||
self.updated = false;
|
||||
Task::perform(
|
||||
@ -157,22 +191,93 @@ impl State for WalletSettingsState {
|
||||
)
|
||||
}
|
||||
Message::View(view::Message::Close) => {
|
||||
self.modal = None;
|
||||
self.modal = Modal::None;
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::RegisterWallet)) => {
|
||||
self.modal = Some(RegisterWalletModal::new(
|
||||
self.modal = Modal::RegisterWallet(RegisterWalletModal::new(
|
||||
self.data_dir.clone(),
|
||||
self.wallet.clone(),
|
||||
cache.network,
|
||||
));
|
||||
Task::none()
|
||||
}
|
||||
_ => self
|
||||
.modal
|
||||
.as_mut()
|
||||
.map(|m| m.update(daemon, cache, message))
|
||||
.unwrap_or_else(Task::none),
|
||||
|
||||
Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => {
|
||||
if let Modal::ImportExport(_) = &self.modal {
|
||||
self.modal = Modal::None;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::ImportExport(m)) => {
|
||||
if let Modal::ImportExport(modal) = &mut self.modal {
|
||||
modal.update(m)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ImportExport(m))) => {
|
||||
if let Modal::ImportExport(modal) = &mut self.modal {
|
||||
modal.update(m)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportWallet)) => {
|
||||
if self.modal.is_none() {
|
||||
let datadir = cache.datadir_path.clone();
|
||||
let network = cache.network;
|
||||
let config = self.config.clone();
|
||||
let wallet = self.wallet.clone();
|
||||
let daemon = daemon.clone();
|
||||
Task::perform(
|
||||
async move {
|
||||
let backup =
|
||||
Backup::from_app(datadir, network, config, wallet, daemon).await;
|
||||
let backup = backup.unwrap();
|
||||
serde_json::to_string_pretty(&backup).unwrap()
|
||||
// TODO: do not unwrap, return an error message instead
|
||||
},
|
||||
|s| {
|
||||
Message::View(view::Message::Settings(
|
||||
view::SettingsMessage::ExportBackup(s),
|
||||
))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(backup))) => {
|
||||
if self.modal.is_none() {
|
||||
let modal =
|
||||
// ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup));
|
||||
ExportModal::new(daemon, ImportExportType::ExportBackup(backup));
|
||||
let launch = modal.launch();
|
||||
self.modal = Modal::ImportExport(modal);
|
||||
launch
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::ImportWallet)) => {
|
||||
if self.modal.is_none() {
|
||||
let modal = ExportModal::new(
|
||||
// Some(daemon),
|
||||
daemon,
|
||||
ImportExportType::Descriptor(self.wallet.main_descriptor.clone()),
|
||||
);
|
||||
let launch = modal.launch();
|
||||
self.modal = Modal::ImportExport(modal);
|
||||
launch
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
_ => match &mut self.modal {
|
||||
Modal::RegisterWallet(m) => m.update(daemon, cache, message),
|
||||
_ => Task::none(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -287,7 +287,7 @@ impl State for TransactionsPanel {
|
||||
TransactionsModal::CreateRbf(modal) => modal.update(daemon, _cache, message),
|
||||
TransactionsModal::Export(modal) => {
|
||||
if let Message::View(view::Message::ImportExport(m)) = msg {
|
||||
modal.update(m.clone())
|
||||
modal.update::<Message>(m.clone())
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ pub fn export_modal<'a>(
|
||||
card::simple(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(Container::new(h4_bold(format!("Export {export_type}"))).width(Length::Fill))
|
||||
.push(Container::new(h4_bold(export_type)).width(Length::Fill))
|
||||
.push(Space::with_height(Length::Fill))
|
||||
.push(progress_bar_row)
|
||||
.push(Space::with_height(Length::Fill))
|
||||
|
||||
@ -70,9 +70,17 @@ pub enum SettingsMessage {
|
||||
BitcoindSettings(SettingsEditMessage),
|
||||
ElectrumSettings(SettingsEditMessage),
|
||||
RescanSettings(SettingsEditMessage),
|
||||
ImportExport(ImportExportMessage),
|
||||
EditRemoteBackendSettings,
|
||||
RemoteBackendSettings(RemoteBackendSettingsMessage),
|
||||
EditWalletSettings,
|
||||
ImportExportSection,
|
||||
ExportDescriptor,
|
||||
ExportTransactions,
|
||||
ExportLabels,
|
||||
ExportWallet,
|
||||
ExportBackup(String),
|
||||
ImportWallet,
|
||||
AboutSection,
|
||||
RegisterWallet,
|
||||
FingerprintAliasEdited(Fingerprint, String),
|
||||
|
||||
@ -106,6 +106,13 @@ pub fn list(cache: &Cache, is_remote_backend: bool) -> Element<Message> {
|
||||
Message::Settings(SettingsMessage::EditWalletSettings),
|
||||
);
|
||||
|
||||
let import_export = settings_section(
|
||||
"Import/export",
|
||||
None,
|
||||
icon::wallet_icon(),
|
||||
Message::Settings(SettingsMessage::ImportExportSection),
|
||||
);
|
||||
|
||||
let recovery = settings_section(
|
||||
"Recovery",
|
||||
Some("In case of loss of the main key, the recovery key can move the funds after a certain time."),
|
||||
@ -130,6 +137,7 @@ pub fn list(cache: &Cache, is_remote_backend: bool) -> Element<Message> {
|
||||
.push(header)
|
||||
.push(if !is_remote_backend { node } else { backend })
|
||||
.push(wallet)
|
||||
.push(import_export)
|
||||
.push(recovery)
|
||||
.push(about),
|
||||
)
|
||||
@ -153,6 +161,60 @@ pub fn bitcoind_settings<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn import_export<'a>(cache: &'a Cache, warning: Option<&Error>) -> Element<'a, Message> {
|
||||
let header = header("Import/Export", SettingsMessage::ImportExportSection);
|
||||
|
||||
let export_descriptor = settings_section(
|
||||
"Export descriptor",
|
||||
None,
|
||||
icon::wallet_icon(),
|
||||
Message::Settings(SettingsMessage::ExportDescriptor),
|
||||
);
|
||||
|
||||
let export_transactions = settings_section(
|
||||
"Export transactions",
|
||||
None,
|
||||
icon::wallet_icon(),
|
||||
Message::Settings(SettingsMessage::ExportTransactions),
|
||||
);
|
||||
|
||||
let export_labels = settings_section(
|
||||
"Export labels",
|
||||
None,
|
||||
icon::wallet_icon(),
|
||||
Message::Settings(SettingsMessage::ExportLabels),
|
||||
);
|
||||
|
||||
let export_wallet = settings_section(
|
||||
"Back Up Wallet",
|
||||
None,
|
||||
icon::wallet_icon(),
|
||||
Message::Settings(SettingsMessage::ExportWallet),
|
||||
);
|
||||
|
||||
let import_wallet = settings_section(
|
||||
"Restore wallet",
|
||||
None,
|
||||
icon::wallet_icon(),
|
||||
Message::Settings(SettingsMessage::ImportWallet),
|
||||
);
|
||||
|
||||
dashboard(
|
||||
&Menu::Settings,
|
||||
cache,
|
||||
warning,
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.push(header)
|
||||
.push(export_descriptor)
|
||||
.push(export_transactions)
|
||||
.push(export_labels)
|
||||
.push(export_wallet)
|
||||
.push(import_wallet)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn about_section<'a>(
|
||||
cache: &'a Cache,
|
||||
warning: Option<&Error>,
|
||||
@ -856,6 +918,18 @@ pub fn wallet_settings<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
let header = header("Wallet", SettingsMessage::EditWalletSettings);
|
||||
|
||||
let import_export = Row::new()
|
||||
.push(
|
||||
button::secondary(Some(icon::wallet_icon()), "Backup")
|
||||
.on_press(Message::Settings(SettingsMessage::ExportWallet)),
|
||||
)
|
||||
.push(Space::with_width(10))
|
||||
.push(
|
||||
button::secondary(Some(icon::wallet_icon()), "Restore")
|
||||
.on_press(Message::Settings(SettingsMessage::ImportWallet)),
|
||||
)
|
||||
.push(Space::with_width(Length::Fill));
|
||||
|
||||
let descr = card::simple(
|
||||
Column::new()
|
||||
.push(text("Wallet descriptor:").bold())
|
||||
@ -943,6 +1017,7 @@ pub fn wallet_settings<'a>(
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.push(header)
|
||||
.push(import_export)
|
||||
.push(descr)
|
||||
.push(
|
||||
card::simple(display_policy(
|
||||
|
||||
@ -46,6 +46,7 @@ pub struct Backup {
|
||||
pub proprietary: serde_json::Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
DescriptorMissing,
|
||||
NotSingleWallet,
|
||||
@ -148,7 +149,7 @@ impl Backup {
|
||||
pub async fn from_app(
|
||||
datadir: PathBuf,
|
||||
network: Network,
|
||||
config: Config,
|
||||
config: Arc<Config>,
|
||||
wallet: Arc<Wallet>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
) -> Result<Self, Error> {
|
||||
@ -165,7 +166,7 @@ impl Backup {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(config) = serde_json::to_value(config) {
|
||||
if let Ok(config) = serde_json::to_value((*config).clone()) {
|
||||
proprietary.insert(CONFIG_KEY.to_string(), config);
|
||||
}
|
||||
|
||||
|
||||
@ -135,6 +135,7 @@ pub enum Error {
|
||||
pub enum ImportExportType {
|
||||
Transactions,
|
||||
ExportPsbt(String),
|
||||
ExportBackup(String),
|
||||
Descriptor(LianaDescriptor),
|
||||
ExportLabels,
|
||||
ImportPsbt,
|
||||
@ -218,11 +219,12 @@ impl Export {
|
||||
) {
|
||||
match export_type {
|
||||
ImportExportType::Transactions => export_transactions(sender, daemon, path).await,
|
||||
ImportExportType::ExportPsbt(psbt) => export_psbt(sender, path, psbt),
|
||||
ImportExportType::ExportPsbt(str) => export_string(sender, path, str),
|
||||
ImportExportType::Descriptor(descriptor) => export_descriptor(sender, path, descriptor),
|
||||
ImportExportType::ExportLabels => export_labels(sender, daemon, path).await,
|
||||
ImportExportType::ImportPsbt => import_psbt(sender, path),
|
||||
ImportExportType::ImportDescriptor => import_descriptor(sender, path),
|
||||
ImportExportType::ExportBackup(str) => export_string(sender, path, str),
|
||||
};
|
||||
}
|
||||
|
||||
@ -497,7 +499,7 @@ pub fn export_descriptor(sender: Sender<Progress>, path: PathBuf, descriptor: Li
|
||||
send_progress!(sender, Ended);
|
||||
}
|
||||
|
||||
pub fn export_psbt(sender: Sender<Progress>, path: PathBuf, psbt: String) {
|
||||
pub fn export_string(sender: Sender<Progress>, path: PathBuf, psbt: String) {
|
||||
let mut file = open_file!(path, sender);
|
||||
|
||||
if let Err(e) = file.write_all(psbt.as_bytes()) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user