diff --git a/app/src/app.rs b/app/src/app.rs index 486c6e0..82b700a 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -13,17 +13,17 @@ use ratatui::{ use crate::{ api::{ - blockchain_monitoring::mock::BlockchainMonitoringApiMock, + blockchain_monitoring::{mock::BlockchainMonitoringApiMock, BlockchainMonitoringApiT}, cache_utils::ModePlan, - coin_price::{cache::Cache as CoinPriceApiCache, mock::CoinPriceApiMock, CoinPriceApi}, + coin_price::{ + cache::Cache as CoinPriceApiCache, mock::CoinPriceApiMock, CoinPriceApi, CoinPriceApiT, + }, common::{Account, Network}, - ledger::{cache::Cache as LedgerApiCache, mock::LedgerApiMock, Device, DeviceInfo}, - }, - screen::{ - asset::Model as AssetScreen, deposit::Model as DepositScreen, - device_selection::Model as DeviceSelectionScreen, portfolio::Model as PortfolioScreen, - OutgoingMessage, Screen, ScreenName, + ledger::{ + cache::Cache as LedgerApiCache, mock::LedgerApiMock, Device, DeviceInfo, LedgerApiT, + }, }, + screen::{OutgoingMessage, Screen, ScreenName}, }; pub struct App { @@ -38,6 +38,18 @@ pub(crate) struct StateRegistry { _phantom: PhantomData<()>, } +pub(crate) struct ApiRegistry +where + L: LedgerApiT, + C: CoinPriceApiT, + M: BlockchainMonitoringApiT, +{ + pub ledger_api: L, + pub coin_price_api: C, + pub blockchain_monitoring_api: M, + _phantom: PhantomData<()>, +} + impl StateRegistry { fn new() -> StateRegistry { StateRegistry { @@ -71,15 +83,39 @@ impl App { async fn main_loop(&mut self, mut terminal: Terminal) { let mut state = Some(StateRegistry::new()); + let api_registry = { + let ledger_api = LedgerApiMock::new(10, 3); + let mut ledger_api = block_on(LedgerApiCache::new(ledger_api)); + ledger_api.set_all_modes(ModePlan::Transparent); + + let _coin_price_api = CoinPriceApiMock::new(); + let coin_price_api = CoinPriceApi::new("https://data-api.binance.vision"); + let mut coin_price_api = block_on(CoinPriceApiCache::new(coin_price_api)); + coin_price_api.set_all_modes(ModePlan::TimedOut(Duration::from_secs(5))); + + let blockchain_monitoring_api = BlockchainMonitoringApiMock::new(4); + + ApiRegistry { + ledger_api, + coin_price_api, + blockchain_monitoring_api, + _phantom: PhantomData, + } + }; + + let mut api_registry = Some(api_registry); + loop { let screen = self .screens .last() .expect("At least one screen should be present"); - let screen = create_screen(*screen); - let (new_state, msg) = Self::screen_loop(screen, &mut terminal, state.take().unwrap()); + let screen = Screen::new(*screen, state.take().unwrap(), api_registry.take().unwrap()); + + let (new_state, new_api_registry, msg) = Self::screen_loop(screen, &mut terminal); state = Some(new_state); + api_registry = Some(new_api_registry); match msg { OutgoingMessage::Exit => { @@ -97,13 +133,10 @@ impl App { } } - fn screen_loop( - mut screen: Box, + fn screen_loop( + mut screen: Screen, terminal: &mut Terminal, - state: StateRegistry, - ) -> (StateRegistry, OutgoingMessage) { - screen.construct(state); - + ) -> (StateRegistry, ApiRegistry, OutgoingMessage) { loop { terminal.draw(|frame| screen.render(frame)).unwrap(); @@ -114,33 +147,9 @@ impl App { let msg = screen.tick(event); if let Some(msg) = msg { - let state = screen.deconstruct(); - return (state, msg); + let (state, api_registry) = screen.deconstruct(); + return (state, api_registry, msg); } } } } - -fn create_screen(screen: ScreenName) -> Box { - let ledger_api = LedgerApiMock::new(10, 3); - let mut ledger_api = block_on(LedgerApiCache::new(ledger_api)); - ledger_api.set_all_modes(ModePlan::Transparent); - - let _coin_price_api = CoinPriceApiMock::new(); - let coin_price_api = CoinPriceApi::new("https://data-api.binance.vision"); - let mut coin_price_api = block_on(CoinPriceApiCache::new(coin_price_api)); - coin_price_api.set_all_modes(ModePlan::TimedOut(Duration::from_secs(5))); - - let blockchain_monitoring_api = BlockchainMonitoringApiMock::new(4); - - match screen { - ScreenName::Portfolio => Box::from(PortfolioScreen::new( - ledger_api, - coin_price_api, - blockchain_monitoring_api, - )), - ScreenName::DeviceSelection => Box::from(DeviceSelectionScreen::new(ledger_api)), - ScreenName::Asset => Box::from(AssetScreen::new(coin_price_api, blockchain_monitoring_api)), - ScreenName::Deposit => Box::from(DepositScreen::new()), - } -} diff --git a/app/src/screen/asset/controller.rs b/app/src/screen/asset/controller.rs index e48b319..5b8b908 100644 --- a/app/src/screen/asset/controller.rs +++ b/app/src/screen/asset/controller.rs @@ -1,15 +1,18 @@ use ratatui::crossterm::event::{Event, KeyCode}; use crate::{ - api::{blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT}, + api::{ + blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, + ledger::LedgerApiT, + }, screen::{EventExt, OutgoingMessage, ScreenName}, }; use super::{Model, TimePeriod}; -pub(super) fn process_input( +pub(super) fn process_input( event: &Event, - model: &mut Model, + model: &mut Model, ) -> Option { if event.is_key_pressed(KeyCode::Char('q')) { return Some(OutgoingMessage::Exit); @@ -28,9 +31,9 @@ pub(super) fn process_input( None } -fn process_time_interval_selection( +fn process_time_interval_selection( event: &Event, - model: &mut Model, + model: &mut Model, ) { match () { () if event.is_key_pressed(KeyCode::Char('d')) => { diff --git a/app/src/screen/asset/mod.rs b/app/src/screen/asset/mod.rs index 0a5323b..86b9bfd 100644 --- a/app/src/screen/asset/mod.rs +++ b/app/src/screen/asset/mod.rs @@ -3,14 +3,15 @@ use ratatui::{crossterm::event::Event, Frame}; use rust_decimal::Decimal; use strum::EnumIter; -use super::{OutgoingMessage, Screen}; +use super::{OutgoingMessage, ScreenT}; use crate::{ api::{ blockchain_monitoring::{BlockchainMonitoringApiT, TransactionInfo, TransactionUid}, coin_price::{Coin, CoinPriceApiT, TimePeriod as ApiTimePeriod}, common::Network, + ledger::LedgerApiT, }, - app::StateRegistry, + app::{ApiRegistry, StateRegistry}, }; mod controller; @@ -18,15 +19,13 @@ mod view; const DEFAULT_SELECTED_TIME_PERIOD: TimePeriod = TimePeriod::Day; -pub struct Model { - coin_price_api: C, - blockchain_monitoring_api: M, - +pub struct Model { coin_price_history: Option>, transactions: Option>, selected_time_period: TimePeriod, - state: Option, + state: StateRegistry, + apis: ApiRegistry, } #[derive(Clone, Copy, PartialEq, Eq, Hash, EnumIter)] @@ -40,27 +39,10 @@ enum TimePeriod { type PriceHistoryPoint = Decimal; -impl Model { - pub fn new(coin_price_api: C, blockchain_monitoring_api: M) -> Self { - Self { - coin_price_api, - blockchain_monitoring_api, - - coin_price_history: Default::default(), - transactions: Default::default(), - selected_time_period: DEFAULT_SELECTED_TIME_PERIOD, - - state: None, - } - } - +impl Model { fn tick_logic(&mut self) { - let state = self + let (selected_network, selected_account) = self .state - .as_ref() - .expect("Construct should be called at the start of window lifetime"); - - let (selected_network, selected_account) = state .selected_account .as_ref() .expect("Selected account should be present in state"); // TODO: Enforce this rule at `app` level? @@ -78,7 +60,7 @@ impl Model { TimePeriod::All => ApiTimePeriod::All, }; - self.coin_price_history = block_on(self.coin_price_api.get_price_history( + self.coin_price_history = block_on(self.apis.coin_price_api.get_price_history( coin, Coin::USDT, time_period, @@ -86,7 +68,8 @@ impl Model { // TODO: Don't make requests to API each tick. let tx_list = block_on( - self.blockchain_monitoring_api + self.apis + .blockchain_monitoring_api .get_transactions(*selected_network, selected_account), ); let txs = tx_list @@ -95,7 +78,8 @@ impl Model { ( tx_uid.clone(), block_on( - self.blockchain_monitoring_api + self.apis + .blockchain_monitoring_api .get_transaction_info(*selected_network, &tx_uid), ), ) @@ -106,9 +90,18 @@ impl Model { } } -impl Screen for Model { - fn construct(&mut self, state: StateRegistry) { - self.state = Some(state); +impl ScreenT + for Model +{ + fn construct(state: StateRegistry, api_registry: ApiRegistry) -> Self { + Self { + coin_price_history: Default::default(), + transactions: Default::default(), + selected_time_period: DEFAULT_SELECTED_TIME_PERIOD, + + state, + apis: api_registry, + } } fn render(&self, frame: &mut Frame<'_>) { @@ -121,8 +114,7 @@ impl Screen for Model { controller::process_input(event.as_ref()?, self) } - fn deconstruct(self: Box) -> StateRegistry { - self.state - .expect("Construct should be called at the start of window lifetime") + fn deconstruct(self) -> (StateRegistry, ApiRegistry) { + (self.state, self.apis) } } diff --git a/app/src/screen/asset/view.rs b/app/src/screen/asset/view.rs index 43d6c67..9ca3d01 100644 --- a/app/src/screen/asset/view.rs +++ b/app/src/screen/asset/view.rs @@ -14,14 +14,15 @@ use crate::{ api::{ blockchain_monitoring::{BlockchainMonitoringApiT, TransactionType}, coin_price::CoinPriceApiT, + ledger::LedgerApiT, }, screen::common::network_symbol, }; use super::{Model, TimePeriod}; -pub(super) fn render( - model: &Model, +pub(super) fn render( + model: &Model, frame: &mut Frame<'_>, ) { let area = frame.size(); @@ -45,8 +46,8 @@ pub(super) fn render( render_tx_list(model, frame, inner_txs_list_area); } -fn render_price_chart( - model: &Model, +fn render_price_chart( + model: &Model, frame: &mut Frame<'_>, area: Rect, ) { @@ -106,8 +107,8 @@ fn render_price_chart( frame.render_widget(chart, area); } -fn render_tx_list( - model: &Model, +fn render_tx_list( + model: &Model, frame: &mut Frame<'_>, area: Rect, ) { @@ -121,12 +122,8 @@ fn render_tx_list( return; } - let state = model + let (selected_account_network, selected_account) = model .state - .as_ref() - .expect("Construct should be called at the start of window lifetime"); - - let (selected_account_network, selected_account) = state .selected_account .as_ref() .expect("Selected account should be present in state"); // TODO: Enforce this rule at `app` level? diff --git a/app/src/screen/deposit/controller.rs b/app/src/screen/deposit/controller.rs index de1acd6..85d7db9 100644 --- a/app/src/screen/deposit/controller.rs +++ b/app/src/screen/deposit/controller.rs @@ -4,9 +4,18 @@ use copypasta::{ClipboardContext, ClipboardProvider}; use ratatui::crossterm::event::{Event, KeyCode}; use super::Model; -use crate::screen::{EventExt, OutgoingMessage}; - -pub(super) fn process_input(event: &Event, model: &mut Model) -> Option { +use crate::{ + api::{ + blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, + ledger::LedgerApiT, + }, + screen::{EventExt, OutgoingMessage}, +}; + +pub(super) fn process_input( + event: &Event, + model: &mut Model, +) -> Option { if event.is_key_pressed(KeyCode::Char('q')) { return Some(OutgoingMessage::Exit); } @@ -18,12 +27,8 @@ pub(super) fn process_input(event: &Event, model: &mut Model) -> Option { last_address_copy: Option, - state: Option, + state: StateRegistry, + apis: ApiRegistry, } -impl Model { - pub fn new() -> Self { +impl ScreenT + for Model +{ + fn construct(state: StateRegistry, api_registry: ApiRegistry) -> Self { Self { last_address_copy: None, - state: None, - } - } -} -impl Screen for Model { - fn construct(&mut self, state: StateRegistry) { - self.state = Some(state); + state, + apis: api_registry, + } } fn render(&self, frame: &mut Frame<'_>) { @@ -36,8 +41,7 @@ impl Screen for Model { controller::process_input(event.as_ref()?, self) } - fn deconstruct(self: Box) -> StateRegistry { - self.state - .expect("Construct should be called at the start of window lifetime") + fn deconstruct(self) -> (StateRegistry, ApiRegistry) { + (self.state, self.apis) } } diff --git a/app/src/screen/deposit/view.rs b/app/src/screen/deposit/view.rs index 7e095c0..cb8d768 100644 --- a/app/src/screen/deposit/view.rs +++ b/app/src/screen/deposit/view.rs @@ -10,17 +10,20 @@ use ratatui::{ Frame, }; +use crate::api::{ + blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, ledger::LedgerApiT, +}; + use super::Model; const DISPLAY_COPIED_TEXT_FOR: Duration = Duration::from_secs(2); -pub(super) fn render(model: &Model, frame: &mut Frame<'_>) { - let state = model +pub(super) fn render( + model: &Model, + frame: &mut Frame<'_>, +) { + let pubkey = model .state - .as_ref() - .expect("Construct should be called at the start of window lifetime"); - - let pubkey = state .selected_account .as_ref() .expect("Selected account should be present in state") // TODO: Enforce this rule at `app` level? diff --git a/app/src/screen/device_selection/controller.rs b/app/src/screen/device_selection/controller.rs index 76c2ae7..0c578ed 100644 --- a/app/src/screen/device_selection/controller.rs +++ b/app/src/screen/device_selection/controller.rs @@ -1,14 +1,17 @@ use ratatui::crossterm::event::{Event, KeyCode}; use crate::{ - api::ledger::LedgerApiT, + api::{ + blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, + ledger::LedgerApiT, + }, screen::{EventExt, OutgoingMessage, ScreenName}, }; use super::Model; -pub(super) fn process_input( - model: &mut Model, +pub(super) fn process_input( + model: &mut Model, event: &Event, ) -> Option { if event.is_key_pressed(KeyCode::Down) && !model.devices.is_empty() { @@ -30,11 +33,7 @@ pub(super) fn process_input( if event.is_key_pressed(KeyCode::Enter) { if let Some(device_idx) = model.selected_device { let (device, info) = model.devices[device_idx].clone(); - model - .state - .as_mut() - .expect("Construct should be called at the start of window lifetime") - .active_device = Some((device, info)); + model.state.active_device = Some((device, info)); // TODO: Add mechanism to return one window back. return Some(OutgoingMessage::SwitchScreen(ScreenName::Portfolio)); } diff --git a/app/src/screen/device_selection/mod.rs b/app/src/screen/device_selection/mod.rs index 4aebf4c..603a3ef 100644 --- a/app/src/screen/device_selection/mod.rs +++ b/app/src/screen/device_selection/mod.rs @@ -3,10 +3,14 @@ use std::time::{Duration, Instant}; use futures::executor::block_on; use ratatui::{crossterm::event::Event, Frame}; -use super::{OutgoingMessage, Screen}; +use super::{OutgoingMessage, ScreenT}; use crate::{ - api::ledger::{Device, DeviceInfo, LedgerApiT}, - app::StateRegistry, + api::{ + blockchain_monitoring::BlockchainMonitoringApiT, + coin_price::CoinPriceApiT, + ledger::{Device, DeviceInfo, LedgerApiT}, + }, + app::{ApiRegistry, StateRegistry}, }; mod controller; @@ -14,34 +18,23 @@ mod view; const DEVICE_POLL_PERIOD: Duration = Duration::from_secs(2); -pub struct Model { - ledger_api: L, - +pub struct Model { devices: Vec<(Device, DeviceInfo)>, previous_poll: Instant, selected_device: Option, - state: Option, + state: StateRegistry, + apis: ApiRegistry, } -impl Model { - pub fn new(ledger_api: L) -> Self { - Self { - devices: vec![], - previous_poll: Instant::now() - DEVICE_POLL_PERIOD, - selected_device: None, - state: None, - ledger_api, - } - } - +impl Model { fn tick_logic(&mut self) { if self.previous_poll.elapsed() >= DEVICE_POLL_PERIOD { - let devices = block_on(self.ledger_api.discover_devices()); + let devices = block_on(self.apis.ledger_api.discover_devices()); let mut devices_with_info = Vec::with_capacity(devices.len()); for device in devices { - let info = block_on(self.ledger_api.get_device_info(&device)); + let info = block_on(self.apis.ledger_api.get_device_info(&device)); if let Some(info) = info { devices_with_info.push((device, info)); } @@ -64,9 +57,18 @@ impl Model { } } -impl Screen for Model { - fn construct(&mut self, state: StateRegistry) { - self.state = Some(state); +impl ScreenT + for Model +{ + fn construct(state: StateRegistry, api_registry: ApiRegistry) -> Self { + Self { + devices: vec![], + previous_poll: Instant::now() - DEVICE_POLL_PERIOD, + selected_device: None, + + state, + apis: api_registry, + } } fn render(&self, frame: &mut Frame<'_>) { @@ -79,8 +81,7 @@ impl Screen for Model { controller::process_input(self, event.as_ref()?) } - fn deconstruct(self: Box) -> StateRegistry { - self.state - .expect("Construct should be called at the start of window lifetime") + fn deconstruct(self) -> (StateRegistry, ApiRegistry) { + (self.state, self.apis) } } diff --git a/app/src/screen/device_selection/view.rs b/app/src/screen/device_selection/view.rs index 22c18ea..72fbc0e 100644 --- a/app/src/screen/device_selection/view.rs +++ b/app/src/screen/device_selection/view.rs @@ -7,9 +7,14 @@ use ratatui::{ }; use super::Model; -use crate::api::ledger::LedgerApiT; +use crate::api::{ + blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, ledger::LedgerApiT, +}; -pub(super) fn render(model: &Model, frame: &mut Frame<'_>) { +pub(super) fn render( + model: &Model, + frame: &mut Frame<'_>, +) { let area = frame.size(); let list_block = Block::new() diff --git a/app/src/screen/mod.rs b/app/src/screen/mod.rs index eec9d9b..ac16f6f 100644 --- a/app/src/screen/mod.rs +++ b/app/src/screen/mod.rs @@ -3,7 +3,13 @@ use ratatui::{ Frame, }; -use crate::app::StateRegistry; +use crate::{ + api::{ + blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, + ledger::LedgerApiT, + }, + app::{ApiRegistry, StateRegistry}, +}; pub mod asset; mod common; @@ -11,14 +17,68 @@ pub mod deposit; pub mod device_selection; pub mod portfolio; -pub trait Screen { - // TODO: Make it into unified constructor. - fn construct(&mut self, state: StateRegistry); +pub enum Screen { + Asset(asset::Model), + Deposit(deposit::Model), + DeviceSelection(device_selection::Model), + Portfolio(portfolio::Model), +} + +impl Screen { + pub fn new( + name: ScreenName, + state_registry: StateRegistry, + api_registry: ApiRegistry, + ) -> Self { + match name { + ScreenName::Asset => Self::Asset(asset::Model::construct(state_registry, api_registry)), + ScreenName::Deposit => { + Self::Deposit(deposit::Model::construct(state_registry, api_registry)) + } + ScreenName::DeviceSelection => Self::DeviceSelection( + device_selection::Model::construct(state_registry, api_registry), + ), + ScreenName::Portfolio => { + Self::Portfolio(portfolio::Model::construct(state_registry, api_registry)) + } + } + } + + pub fn render(&self, frame: &mut Frame<'_>) { + match self { + Self::Asset(screen) => screen.render(frame), + Self::Deposit(screen) => screen.render(frame), + Self::DeviceSelection(screen) => screen.render(frame), + Self::Portfolio(screen) => screen.render(frame), + } + } + + pub fn tick(&mut self, event: Option) -> Option { + match self { + Self::Asset(screen) => screen.tick(event), + Self::Deposit(screen) => screen.tick(event), + Self::DeviceSelection(screen) => screen.tick(event), + Self::Portfolio(screen) => screen.tick(event), + } + } + + pub fn deconstruct(self) -> (StateRegistry, ApiRegistry) { + match self { + Self::Asset(screen) => screen.deconstruct(), + Self::Deposit(screen) => screen.deconstruct(), + Self::DeviceSelection(screen) => screen.deconstruct(), + Self::Portfolio(screen) => screen.deconstruct(), + } + } +} + +trait ScreenT { + fn construct(state: StateRegistry, api_registry: ApiRegistry) -> Self; fn render(&self, frame: &mut Frame<'_>); fn tick(&mut self, event: Option) -> Option; - fn deconstruct(self: Box) -> StateRegistry; + fn deconstruct(self) -> (StateRegistry, ApiRegistry); } pub enum OutgoingMessage { diff --git a/app/src/screen/portfolio/controller.rs b/app/src/screen/portfolio/controller.rs index 9730c9f..d20b0ab 100644 --- a/app/src/screen/portfolio/controller.rs +++ b/app/src/screen/portfolio/controller.rs @@ -15,28 +15,25 @@ pub(super) fn process_input, event: &Event, ) -> Option { - if let Some(state) = model.state.as_mut() { - if let Some(accounts) = state.device_accounts.as_ref() { - if event.is_key_pressed(KeyCode::Enter) { - if let Some((selected_network_idx, selected_account_idx)) = model.selected_account { - let (selected_network, accounts) = &accounts[selected_network_idx]; - let selected_account = accounts[selected_account_idx].clone(); + if let Some(accounts) = model.state.device_accounts.as_ref() { + if event.is_key_pressed(KeyCode::Enter) { + if let Some((selected_network_idx, selected_account_idx)) = model.selected_account { + let (selected_network, accounts) = &accounts[selected_network_idx]; + let selected_account = accounts[selected_account_idx].clone(); - state.selected_account = Some((*selected_network, selected_account)); + model.state.selected_account = Some((*selected_network, selected_account)); - return Some(OutgoingMessage::SwitchScreen(ScreenName::Asset)); - } + return Some(OutgoingMessage::SwitchScreen(ScreenName::Asset)); } - - let accounts_per_network: Vec<_> = accounts - .iter() - .map(|(_, accounts)| { - NonZeroUsize::new(accounts.len()) - .expect("No accounts for provided network found") - }) - .collect(); - process_table_navigation(model, event, &accounts_per_network); } + + let accounts_per_network: Vec<_> = accounts + .iter() + .map(|(_, accounts)| { + NonZeroUsize::new(accounts.len()).expect("No accounts for provided network found") + }) + .collect(); + process_table_navigation(model, event, &accounts_per_network); } if event.is_key_pressed(KeyCode::Char('d')) { diff --git a/app/src/screen/portfolio/mod.rs b/app/src/screen/portfolio/mod.rs index b86ce0e..223cc1f 100644 --- a/app/src/screen/portfolio/mod.rs +++ b/app/src/screen/portfolio/mod.rs @@ -4,7 +4,7 @@ use futures::executor::block_on; use ratatui::{crossterm::event::Event, Frame}; use rust_decimal::Decimal; -use super::{OutgoingMessage, Screen}; +use super::{OutgoingMessage, ScreenT}; use crate::{ api::{ blockchain_monitoring::BlockchainMonitoringApiT, @@ -12,58 +12,40 @@ use crate::{ common::{Account, Network}, ledger::LedgerApiT, }, - app::StateRegistry, + app::{ApiRegistry, StateRegistry}, }; mod controller; mod view; pub struct Model { - ledger_api: L, - coin_price_api: C, - blockchain_monitoring_api: M, - selected_account: Option<(NetworkIdx, AccountIdx)>, // TODO: Store it in API cache. coin_prices: HashMap>, balances: HashMap<(Network, Account), Decimal>, - state: Option, + state: StateRegistry, + apis: ApiRegistry, } type AccountIdx = usize; type NetworkIdx = usize; impl Model { - pub fn new(ledger_api: L, coin_price_api: C, blockchain_monitoring_api: M) -> Self { - Self { - ledger_api, - coin_price_api, - blockchain_monitoring_api, - - selected_account: None, - coin_prices: Default::default(), - balances: Default::default(), - state: None, - } - } - fn tick_logic(&mut self) { - let state = self - .state - .as_mut() - .expect("Construct should be called at the start of window lifetime"); - - if state.device_accounts.is_none() { - if let Some((active_device, _)) = state.active_device.as_ref() { + if self.state.device_accounts.is_none() { + if let Some((active_device, _)) = self.state.active_device.as_ref() { // TODO: Load at startup from config and add only on user request. // TODO: Filter accounts based on balance. - state.device_accounts = Some( + self.state.device_accounts = Some( [Network::Bitcoin, Network::Ethereum] .into_iter() .filter_map(|network| { - let accounts = - block_on(self.ledger_api.discover_accounts(active_device, network)); + let accounts = block_on( + self.apis + .ledger_api + .discover_accounts(active_device, network), + ); if accounts.is_empty() { None @@ -88,20 +70,21 @@ impl Model Model Screen for Model { - fn construct(&mut self, state: StateRegistry) { - self.state = Some(state); +impl ScreenT + for Model +{ + fn construct(state: StateRegistry, api_registry: ApiRegistry) -> Self { + Self { + selected_account: None, + coin_prices: Default::default(), + balances: Default::default(), + + state, + apis: api_registry, + } } fn render(&self, frame: &mut Frame<'_>) { @@ -126,8 +118,7 @@ impl Screen for Mo controller::process_input(self, event.as_ref()?) } - fn deconstruct(self: Box) -> StateRegistry { - self.state - .expect("Construct should be called at the start of window lifetime") + fn deconstruct(self) -> (StateRegistry, ApiRegistry) { + (self.state, self.apis) } } diff --git a/app/src/screen/portfolio/view.rs b/app/src/screen/portfolio/view.rs index 627379a..db39b88 100644 --- a/app/src/screen/portfolio/view.rs +++ b/app/src/screen/portfolio/view.rs @@ -26,12 +26,7 @@ pub(super) fn render, frame: &mut Frame<'_>, ) { - let model_state = model - .state - .as_ref() - .expect("Construct should be called at the start of window lifetime"); - - if let Some(accounts) = model_state.device_accounts.as_ref() { + if let Some(accounts) = model.state.device_accounts.as_ref() { render_account_table(model, frame, accounts); } else { // TODO: Process case when device is connected but accounts haven't been loaded yet.