Merge #22: gui: Home and Receive panels
7f9fd84beead64ef8fe259f1174e01cbc0dd4cfa Add balance to panel home (edouard)
157d9b39333e9eae1b1fc231bc6abd2f6661bca6 Add coins to cache (edouard)
f32b4d52d2facf409d73b8e27a67d05e13255373 daemon: add list_coins method (edouard)
647b74ebcf789e8df7bf530e83258a3a38286be9 Add unit test for receive panel state (edouard)
ebc239733c3a508f08f5539a862d3029d45c753f Add sandbox and daemon mock to utils mod (edouard)
928294b32e2afa0ae4472817f8bbb1e477b8afdb Add receive panel (edouard)
1026d2b487c3f0d9955b93d1cf1b79a0a8ad94bf Add get_new_address to daemon trait (edouard)
2286a19e20cfde07d4c7eb4bdd3190abea3a09d1 Update minisafe master:#4a802890 (edouard)
Pull request description:
ACKs for top commit:
edouardparis:
Self ACK 7f9fd84beead64ef8fe259f1174e01cbc0dd4cfa
Tree-SHA512: b0c0b89540ede0f7f895197595af8b97fa515d5e4b6b7f3dcd4e29b02bfcb0a9ce338f3ee5644e486892ab132c456fca29ad4189a3f71f6420f535f99dbe77ba
This commit is contained in:
commit
f5387f7ed9
2
gui/Cargo.lock
generated
2
gui/Cargo.lock
generated
@ -1390,7 +1390,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
[[package]]
|
||||
name = "minisafe"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/revault/minisafe?branch=master#b548451292f4f497a5a837244b6432e473e6cd55"
|
||||
source = "git+https://github.com/revault/minisafe?branch=master#4a802890634f60d77e4da5a56f9189024ef99500"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"dirs",
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
use crate::daemon::model::Coin;
|
||||
|
||||
pub struct Cache {
|
||||
pub blockheight: i32,
|
||||
pub coins: Vec<Coin>,
|
||||
}
|
||||
|
||||
impl Default for Cache {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
blockheight: 0,
|
||||
coins: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Menu {
|
||||
Home,
|
||||
Receive,
|
||||
Settings,
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
use minisafe::config::Config as DaemonConfig;
|
||||
|
||||
use crate::app::{error::Error, view};
|
||||
use crate::{
|
||||
app::{error::Error, view},
|
||||
daemon::model::*,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
@ -10,4 +13,6 @@ pub enum Message {
|
||||
LoadDaemonConfig(Box<DaemonConfig>),
|
||||
DaemonConfigLoaded(Result<(), Error>),
|
||||
BlockHeight(Result<i32, Error>),
|
||||
ReceiveAddress(Result<bitcoin::Address, Error>),
|
||||
Coins(Result<Vec<Coin>, Error>),
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ pub use minisafe::config::Config as DaemonConfig;
|
||||
pub use config::Config;
|
||||
pub use message::Message;
|
||||
|
||||
use state::{Home, State};
|
||||
use state::{Home, ReceivePanel, State};
|
||||
|
||||
use crate::{
|
||||
app::{cache::Cache, error::Error, menu::Menu},
|
||||
@ -42,7 +42,7 @@ impl App {
|
||||
config: Config,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
) -> (App, Command<Message>) {
|
||||
let state: Box<dyn State> = Home {}.into();
|
||||
let state: Box<dyn State> = Home::new(&cache.coins).into();
|
||||
let cmd = state.load(daemon.clone());
|
||||
(
|
||||
Self {
|
||||
@ -62,7 +62,8 @@ impl App {
|
||||
state::SettingsState::new(self.daemon.config().clone(), self.daemon.is_external())
|
||||
.into()
|
||||
}
|
||||
menu::Menu::Home => Home {}.into(),
|
||||
menu::Menu::Home => Home::new(&self.cache.coins).into(),
|
||||
menu::Menu::Receive => ReceivePanel::default().into(),
|
||||
};
|
||||
self.state.load(self.daemon.clone())
|
||||
}
|
||||
@ -94,6 +95,18 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: Message) -> Command<Message> {
|
||||
// Update cache when values are passing by.
|
||||
// State will handle the error case.
|
||||
match &message {
|
||||
Message::Coins(Ok(coins)) => {
|
||||
self.cache.coins = coins.clone();
|
||||
}
|
||||
Message::BlockHeight(Ok(blockheight)) => {
|
||||
self.cache.blockheight = blockheight.clone();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
match message {
|
||||
Message::Tick => {
|
||||
let daemon = self.daemon.clone();
|
||||
@ -107,12 +120,6 @@ impl App {
|
||||
Message::BlockHeight,
|
||||
)
|
||||
}
|
||||
Message::BlockHeight(res) => {
|
||||
if let Ok(blockheight) = res {
|
||||
self.cache.blockheight = blockheight;
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
Message::LoadDaemonConfig(cfg) => {
|
||||
let res = self.load_daemon_config(*cfg);
|
||||
self.update(Message::DaemonConfigLoaded(res))
|
||||
|
||||
@ -2,20 +2,19 @@ mod settings;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitcoin::Amount;
|
||||
use iced::pure::{column, Element};
|
||||
use iced::{Command, Subscription};
|
||||
|
||||
use super::{cache::Cache, menu::Menu, message::Message, view};
|
||||
use iced::{widget::qr_code, Command, Subscription};
|
||||
|
||||
use super::{cache::Cache, error::Error, menu::Menu, message::Message, view};
|
||||
use crate::daemon::{model::Coin, Daemon};
|
||||
pub use settings::SettingsState;
|
||||
|
||||
use crate::daemon::Daemon;
|
||||
|
||||
pub trait State {
|
||||
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message>;
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Send + Sync>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
cache: &Cache,
|
||||
message: Message,
|
||||
) -> Command<Message>;
|
||||
@ -27,20 +26,44 @@ pub trait State {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Home {}
|
||||
pub struct Home {
|
||||
balance: Amount,
|
||||
}
|
||||
|
||||
impl Home {
|
||||
pub fn new(coins: &Vec<Coin>) -> Self {
|
||||
Self {
|
||||
balance: Amount::from_sat(coins.iter().map(|coin| coin.amount.as_sat()).sum()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State for Home {
|
||||
fn view<'a>(&self, _cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
view::dashboard(&Menu::Home, None, column())
|
||||
fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
view::dashboard(&Menu::Home, None, view::home::home_view(&self.balance))
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_daemon: Arc<dyn Daemon + Send + Sync>,
|
||||
_daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
_cache: &Cache,
|
||||
_message: Message,
|
||||
) -> Command<Message> {
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.list_coins()
|
||||
.map(|res| res.coins)
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::Coins,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Home> for Box<dyn State> {
|
||||
@ -48,3 +71,101 @@ impl From<Home> for Box<dyn State> {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ReceivePanel {
|
||||
address: Option<bitcoin::Address>,
|
||||
qr_code: Option<qr_code::State>,
|
||||
warning: Option<Error>,
|
||||
}
|
||||
|
||||
impl State for ReceivePanel {
|
||||
fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
if let Some(address) = &self.address {
|
||||
view::dashboard(
|
||||
&Menu::Receive,
|
||||
self.warning.as_ref(),
|
||||
view::receive::receive(address, self.qr_code.as_ref().unwrap()),
|
||||
)
|
||||
} else {
|
||||
view::dashboard(&Menu::Receive, self.warning.as_ref(), column())
|
||||
}
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
_daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
_cache: &Cache,
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
if let Message::ReceiveAddress(res) = message {
|
||||
match res {
|
||||
Ok(address) => {
|
||||
self.warning = None;
|
||||
self.qr_code = Some(qr_code::State::new(&address.to_qr_uri()).unwrap());
|
||||
self.address = Some(address);
|
||||
}
|
||||
Err(e) => self.warning = Some(e),
|
||||
}
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let daemon = daemon.clone();
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.get_new_address()
|
||||
.map(|res| res.address)
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::ReceiveAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReceivePanel> for Box<dyn State> {
|
||||
fn from(s: ReceivePanel) -> Box<dyn State> {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
app::cache::Cache,
|
||||
daemon::{
|
||||
client::{Minisafed, Request},
|
||||
model::*,
|
||||
},
|
||||
utils::{
|
||||
mock::{fake_daemon_config, Daemon},
|
||||
sandbox::Sandbox,
|
||||
},
|
||||
};
|
||||
|
||||
use bitcoin::Address;
|
||||
use serde_json::json;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_receive_panel() {
|
||||
let addr =
|
||||
Address::from_str("tb1qkldgvljmjpxrjq2ev5qxe8dvhn0dph9q85pwtfkjeanmwdue2akqj4twxj")
|
||||
.unwrap();
|
||||
let daemon = Daemon::new(vec![(
|
||||
Some(json!({"method": "getnewaddress", "params": Option::<Request>::None})),
|
||||
Ok(json!(GetAddressResult {
|
||||
address: addr.clone()
|
||||
})),
|
||||
)]);
|
||||
|
||||
let sandbox: Sandbox<ReceivePanel> = Sandbox::new(ReceivePanel::default());
|
||||
let client = Arc::new(Minisafed::new(daemon.run(), fake_daemon_config()));
|
||||
let sandbox = sandbox.load(client, &Cache::default()).await;
|
||||
|
||||
let panel = sandbox.state();
|
||||
assert_eq!(panel.address, Some(addr));
|
||||
}
|
||||
}
|
||||
|
||||
17
gui/src/app/view/home.rs
Normal file
17
gui/src/app/view/home.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use iced::{
|
||||
pure::{column, Element},
|
||||
Alignment,
|
||||
};
|
||||
|
||||
use crate::ui::component::text::*;
|
||||
|
||||
use super::message::Message;
|
||||
|
||||
pub fn home_view<'a>(balance: &'a bitcoin::Amount) -> Element<'a, Message> {
|
||||
column()
|
||||
.push(column().padding(40))
|
||||
.push(text(&format!("{} BTC", balance.as_btc())).bold().size(50))
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.into()
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
mod message;
|
||||
mod warning;
|
||||
|
||||
pub mod home;
|
||||
pub mod receive;
|
||||
pub mod settings;
|
||||
|
||||
pub use message::*;
|
||||
@ -14,7 +16,7 @@ use iced::{
|
||||
use crate::ui::{
|
||||
color,
|
||||
component::{button, separation, text::*},
|
||||
icon::{home_icon, settings_icon},
|
||||
icon::{home_icon, receive_icon, settings_icon},
|
||||
};
|
||||
|
||||
use crate::app::{error::Error, menu::Menu};
|
||||
@ -29,6 +31,17 @@ pub fn sidebar(menu: &Menu) -> widget::Container<Message> {
|
||||
.on_press(Message::Menu(Menu::Home))
|
||||
.width(iced::Length::Units(200))
|
||||
};
|
||||
|
||||
let receive_button = if *menu == Menu::Receive {
|
||||
button::primary(Some(receive_icon()), "Receive")
|
||||
.on_press(Message::Reload)
|
||||
.width(iced::Length::Units(200))
|
||||
} else {
|
||||
button::transparent(Some(receive_icon()), "Receive")
|
||||
.on_press(Message::Menu(Menu::Receive))
|
||||
.width(iced::Length::Units(200))
|
||||
};
|
||||
|
||||
let settings_button = if *menu == Menu::Settings {
|
||||
button::primary(Some(settings_icon()), "Settings")
|
||||
.on_press(Message::Menu(Menu::Settings))
|
||||
@ -51,6 +64,7 @@ pub fn sidebar(menu: &Menu) -> widget::Container<Message> {
|
||||
.spacing(10),
|
||||
)
|
||||
.push(home_button)
|
||||
.push(receive_button)
|
||||
.spacing(20)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
|
||||
32
gui/src/app/view/receive.rs
Normal file
32
gui/src/app/view/receive.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use iced::{
|
||||
pure::{column, row, widget::Button, Element},
|
||||
widget::qr_code::{self, QRCode},
|
||||
Alignment,
|
||||
};
|
||||
|
||||
use crate::ui::{
|
||||
component::{button, card, text::*},
|
||||
icon,
|
||||
};
|
||||
|
||||
use super::message::Message;
|
||||
|
||||
pub fn receive<'a>(address: &'a bitcoin::Address, qr: &'a qr_code::State) -> Element<'a, Message> {
|
||||
card::simple(
|
||||
column()
|
||||
.push(QRCode::new(qr).cell_size(10))
|
||||
.push(
|
||||
row()
|
||||
.push(text(&address.to_string()).small())
|
||||
.push(
|
||||
Button::new(icon::clipboard_icon())
|
||||
.on_press(Message::Clipboard(address.to_string()))
|
||||
.style(button::Style::TransparentBorder),
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
@ -62,6 +62,14 @@ impl<C: Client + Debug> Daemon for Minisafed<C> {
|
||||
fn get_info(&self) -> Result<GetInfoResult, DaemonError> {
|
||||
self.call("getinfo", Option::<Request>::None)
|
||||
}
|
||||
|
||||
fn get_new_address(&self) -> Result<GetAddressResult, DaemonError> {
|
||||
self.call("getnewaddress", Option::<Request>::None)
|
||||
}
|
||||
|
||||
fn list_coins(&self) -> Result<ListCoinsResult, DaemonError> {
|
||||
self.call("listcoins", Option::<Request>::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
|
||||
@ -69,4 +69,26 @@ impl Daemon for EmbeddedDaemon {
|
||||
.control
|
||||
.get_info())
|
||||
}
|
||||
|
||||
fn get_new_address(&self) -> Result<GetAddressResult, DaemonError> {
|
||||
Ok(self
|
||||
.handle
|
||||
.as_ref()
|
||||
.ok_or(DaemonError::NoAnswer)?
|
||||
.lock()
|
||||
.unwrap()
|
||||
.control
|
||||
.get_new_address())
|
||||
}
|
||||
|
||||
fn list_coins(&self) -> Result<ListCoinsResult, DaemonError> {
|
||||
Ok(self
|
||||
.handle
|
||||
.as_ref()
|
||||
.ok_or(DaemonError::NoAnswer)?
|
||||
.lock()
|
||||
.unwrap()
|
||||
.control
|
||||
.list_coins())
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,4 +45,8 @@ pub trait Daemon: Debug {
|
||||
fn stop(&mut self) -> Result<(), DaemonError>;
|
||||
|
||||
fn get_info(&self) -> Result<model::GetInfoResult, DaemonError>;
|
||||
|
||||
fn get_new_address(&self) -> Result<model::GetAddressResult, DaemonError>;
|
||||
|
||||
fn list_coins(&self) -> Result<model::ListCoinsResult, DaemonError>;
|
||||
}
|
||||
|
||||
@ -1 +1,3 @@
|
||||
pub use minisafe::commands::GetInfoResult;
|
||||
pub use minisafe::commands::{GetAddressResult, GetInfoResult, ListCoinsEntry, ListCoinsResult};
|
||||
|
||||
pub type Coin = ListCoinsEntry;
|
||||
|
||||
@ -3,3 +3,4 @@ pub mod daemon;
|
||||
pub mod installer;
|
||||
pub mod loader;
|
||||
pub mod ui;
|
||||
pub mod utils;
|
||||
|
||||
@ -39,7 +39,7 @@ enum Step {
|
||||
pub enum Message {
|
||||
Event(iced_native::Event),
|
||||
Syncing(Result<GetInfoResult, DaemonError>),
|
||||
Synced(GetInfoResult, Arc<dyn Daemon + Sync + Send>),
|
||||
Synced(GetInfoResult, Vec<Coin>, Arc<dyn Daemon + Sync + Send>),
|
||||
Started(Result<Arc<dyn Daemon + Sync + Send>, Error>),
|
||||
Loaded(Result<Arc<dyn Daemon + Sync + Send>, Error>),
|
||||
Failure(DaemonError),
|
||||
@ -118,9 +118,16 @@ impl Loader {
|
||||
Ok(info) => {
|
||||
if (info.sync - 1.0_f64).abs() < f64::EPSILON {
|
||||
let daemon = daemon.clone();
|
||||
return Command::perform(async move { (info, daemon) }, |res| {
|
||||
Message::Synced(res.0, res.1)
|
||||
});
|
||||
return Command::perform(
|
||||
async move {
|
||||
let coins = daemon
|
||||
.list_coins()
|
||||
.map(|res| res.coins)
|
||||
.unwrap_or_else(|_| Vec::new());
|
||||
(info, coins, daemon)
|
||||
},
|
||||
|res| Message::Synced(res.0, res.1, res.2),
|
||||
);
|
||||
} else {
|
||||
*progress = info.sync
|
||||
}
|
||||
|
||||
@ -161,9 +161,10 @@ impl Application for GUI {
|
||||
}
|
||||
}
|
||||
(State::Loader(loader), Message::Load(msg)) => {
|
||||
if let loader::Message::Synced(info, minisafed) = *msg {
|
||||
if let loader::Message::Synced(info, coins, minisafed) = *msg {
|
||||
let cache = Cache {
|
||||
blockheight: info.blockheight,
|
||||
coins,
|
||||
};
|
||||
|
||||
let (app, command) = App::new(cache, loader.gui_config.clone(), minisafed);
|
||||
|
||||
@ -50,7 +50,7 @@ pub fn connected_device_icon() -> Text {
|
||||
icon('\u{F350}')
|
||||
}
|
||||
|
||||
pub fn deposit_icon() -> Text {
|
||||
pub fn receive_icon() -> Text {
|
||||
icon('\u{F123}')
|
||||
}
|
||||
|
||||
|
||||
93
gui/src/utils/mock.rs
Normal file
93
gui/src/utils/mock.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::daemon::{client::Client, DaemonError};
|
||||
use minisafe::config::Config;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Mutex,
|
||||
};
|
||||
use std::thread;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DaemonClient {
|
||||
transport: Mutex<(Sender<Value>, Receiver<Result<Value, DaemonError>>)>,
|
||||
}
|
||||
|
||||
impl Client for DaemonClient {
|
||||
type Error = DaemonError;
|
||||
fn request<S: Serialize + Debug, D: DeserializeOwned + Debug>(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Option<S>,
|
||||
) -> Result<D, Self::Error> {
|
||||
let req = json!({"method": method, "params": params});
|
||||
let connection = self.transport.lock().expect("Failed to unlock");
|
||||
connection
|
||||
.0
|
||||
.send(req)
|
||||
.expect("Mock client failed to send request");
|
||||
connection
|
||||
.1
|
||||
.recv()
|
||||
.expect("Mock client failed to receive response")
|
||||
.map(|value| serde_json::from_value(value).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Daemon {
|
||||
requests: Vec<(Option<Value>, Result<Value, DaemonError>)>,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
pub fn new(requests: Vec<(Option<Value>, Result<Value, DaemonError>)>) -> Self {
|
||||
Self { requests }
|
||||
}
|
||||
|
||||
pub fn run(self) -> DaemonClient {
|
||||
let (client_sender, daemon_receiver) = channel();
|
||||
let (daemon_sender, client_receiver) = channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut requests = self.requests.into_iter();
|
||||
while let Ok(msg) = daemon_receiver.recv() {
|
||||
let request = requests
|
||||
.next()
|
||||
.expect("Mock Daemon must have all requests mocked in the right order");
|
||||
if let Some(body) = request.0 {
|
||||
assert_eq!(body, msg);
|
||||
}
|
||||
daemon_sender
|
||||
.send(request.1)
|
||||
.expect("Mock daemon failed to send response")
|
||||
}
|
||||
// close the daemon -> client channel after
|
||||
// the client -> daemon channel is closed.
|
||||
// (client -> daemon channel is closed when DaemonClient is dropped)
|
||||
drop(daemon_sender);
|
||||
// Readable with `cargo test -- --nocapture`
|
||||
println!("The daemon has stopped!");
|
||||
});
|
||||
|
||||
DaemonClient {
|
||||
transport: Mutex::new((client_sender, client_receiver)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fake_daemon_config() -> Config {
|
||||
toml::from_str(
|
||||
r#"
|
||||
data_dir = "/home/edouard/code/revault/demo/minisafe/datadir"
|
||||
main_descriptor = "wsh(or_d(pk(tpubDCbK3Ysvk8HjcF6mPyrgMu3KgLiaaP19RjKpNezd8GrbAbNg6v5BtWLaCt8FNm6QkLseopKLf5MNYQFtochDTKHdfgG6iqJ8cqnLNAwtXuP/*),and_v(v:pkh(tpubDDtb2WPYwEWw2WWDV7reLV348iJHw2HmhzvPysKKrJw3hYmvrd4jasyoioVPdKGQqjyaBMEvTn1HvHWDSVqQ6amyyxRZ5YjpPBBGjJ8yu8S/*),older(100))))#459t6xxr"
|
||||
|
||||
[bitcoin_config]
|
||||
network = "regtest"
|
||||
poll_interval_secs = 30
|
||||
|
||||
[bitcoind_config]
|
||||
addr = "127.0.0.1:9001"
|
||||
cookie_path = "/home/edouard/code/revault/demo/minisafe/regtest/bcdir1/regtest/.cookie"
|
||||
"#
|
||||
).unwrap()
|
||||
}
|
||||
5
gui/src/utils/mod.rs
Normal file
5
gui/src/utils/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[cfg(test)]
|
||||
pub mod sandbox;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod mock;
|
||||
51
gui/src/utils/sandbox.rs
Normal file
51
gui/src/utils/sandbox.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced_native::command::Action;
|
||||
|
||||
use crate::{
|
||||
app::{cache::Cache, message::Message, state::State},
|
||||
daemon::Daemon,
|
||||
};
|
||||
|
||||
pub struct Sandbox<S: State> {
|
||||
state: S,
|
||||
}
|
||||
|
||||
impl<S: State + Send + 'static> Sandbox<S> {
|
||||
pub fn new(state: S) -> Self {
|
||||
return Self { state };
|
||||
}
|
||||
|
||||
pub fn state(&self) -> &S {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
cache: &Cache,
|
||||
message: Message,
|
||||
) -> Self {
|
||||
let cmd = self.state.update(daemon.clone(), cache, message);
|
||||
for action in cmd.actions() {
|
||||
if let Action::Future(f) = action {
|
||||
let msg = f.await;
|
||||
let _cmd = self.state.update(daemon.clone(), cache, msg);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn load(mut self, daemon: Arc<dyn Daemon + Sync + Send>, cache: &Cache) -> Self {
|
||||
let cmd = self.state.load(daemon.clone());
|
||||
for action in cmd.actions() {
|
||||
if let Action::Future(f) = action {
|
||||
let msg = f.await;
|
||||
self = self.update(daemon.clone(), cache, msg).await;
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user