Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 120 additions & 102 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::backend_task::core::CoreItem;
use crate::backend_task::{BackendTask, BackendTaskSuccessResult};
use crate::components::core_zmq_listener::{CoreZMQListener, ZMQMessage};
use crate::context::AppContext;
use crate::context::connection_status::ConnectionStatus;
use crate::database::Database;
use crate::logging::initialize_logger;
use crate::model::settings::Settings;
Expand Down Expand Up @@ -65,6 +66,7 @@ pub struct AppState {
pub selected_main_screen: RootScreenType,
pub screen_stack: Vec<Screen>,
pub chosen_network: Network,
pub connection_status: Arc<ConnectionStatus>,
pub mainnet_app_context: Arc<AppContext>,
pub testnet_app_context: Option<Arc<AppContext>>,
pub devnet_app_context: Option<Arc<AppContext>>,
Expand Down Expand Up @@ -182,11 +184,13 @@ impl AppState {
let onboarding_completed = settings.onboarding_completed;

let subtasks = Arc::new(TaskManager::new());
let connection_status = Arc::new(ConnectionStatus::new());
let mainnet_app_context = match AppContext::new(
Network::Dash,
db.clone(),
password_info.clone(),
subtasks.clone(),
connection_status.clone(),
) {
Some(context) => context,
None => {
Expand All @@ -201,18 +205,21 @@ impl AppState {
db.clone(),
password_info.clone(),
subtasks.clone(),
connection_status.clone(),
);
let devnet_app_context = AppContext::new(
Network::Devnet,
db.clone(),
password_info.clone(),
subtasks.clone(),
connection_status.clone(),
);
let local_app_context = AppContext::new(
Network::Regtest,
db.clone(),
password_info,
subtasks.clone(),
connection_status.clone(),
);

// load fonts
Expand Down Expand Up @@ -604,6 +611,7 @@ impl AppState {
selected_main_screen,
screen_stack: vec![],
chosen_network,
connection_status,
mainnet_app_context,
testnet_app_context,
devnet_app_context,
Expand Down Expand Up @@ -738,6 +746,8 @@ impl AppState {
for screen in self.main_screens.values_mut() {
screen.change_context(app_context.clone())
}

self.connection_status.reset();
}

pub fn visible_screen_mut(&mut self) -> &mut Screen {
Expand Down Expand Up @@ -789,14 +799,14 @@ impl App for AppState {
// Apply Dash theme with user preference
crate::ui::theme::apply_theme(ctx, self.theme_preference);

if let Ok(event) = self.current_app_context().rx_zmq_status.try_recv()
&& let Ok(mut status) = self.current_app_context().zmq_connection_status.lock()
{
*status = event;
}
let active_context = self.current_app_context().clone();

// Poll the receiver for any new task results
while let Ok(task_result) = self.task_result_receiver.try_recv() {
active_context
.connection_status()
.handle_task_result(&task_result, active_context.network);

// Handle the result on the main thread
match task_result {
TaskResult::Success(message) => {
Expand Down Expand Up @@ -984,112 +994,120 @@ impl App for AppState {
}

// Show welcome screen if onboarding not completed
let action = if self.show_welcome_screen {
if let Some(welcome_screen) = &mut self.welcome_screen {
welcome_screen.ui(ctx)
} else {
AppAction::None
}
let mut actions = Vec::new();
if self.show_welcome_screen
&& let Some(welcome_screen) = &mut self.welcome_screen
{
actions.push(welcome_screen.ui(ctx));
} else {
self.visible_screen_mut().ui(ctx)
actions.push(self.visible_screen_mut().ui(ctx));
};

match action {
AppAction::AddScreen(screen) => self.screen_stack.push(screen),
AppAction::None => {}
AppAction::Refresh => self.visible_screen_mut().refresh(),
AppAction::PopScreen => {
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
// Schedule connection status refresh
actions.push(
active_context
.connection_status()
.trigger_refresh(active_context.as_ref()),
);

for action in actions {
match action {
AppAction::None => {}
AppAction::AddScreen(screen) => self.screen_stack.push(screen),
AppAction::Refresh => self.visible_screen_mut().refresh(),
AppAction::PopScreen => {
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
}
}
}
AppAction::PopScreenAndRefresh => {
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
AppAction::PopScreenAndRefresh => {
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
}
if let Some(screen) = self.screen_stack.last_mut() {
screen.refresh();
} else {
self.active_root_screen_mut().refresh_on_arrival();
}
}
if let Some(screen) = self.screen_stack.last_mut() {
screen.refresh();
} else {
AppAction::GoToMainScreen => {
self.screen_stack = vec![];
self.active_root_screen_mut().refresh_on_arrival();
}
}
AppAction::GoToMainScreen => {
self.screen_stack = vec![];
self.active_root_screen_mut().refresh_on_arrival();
}
AppAction::BackendTask(task) => {
self.handle_backend_task(task);
}
AppAction::BackendTasks(tasks, mode) => {
self.handle_backend_tasks(tasks, mode);
}
AppAction::SetMainScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
}
AppAction::SetMainScreenThenGoToMainScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
self.screen_stack = vec![];
}
AppAction::SetMainScreenThenPopScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
AppAction::BackendTask(task) => {
self.handle_backend_task(task);
}
}
AppAction::SwitchNetwork(network) => {
self.change_network(network);
self.current_app_context()
.update_settings(RootScreenType::RootScreenNetworkChooser)
.ok();
}
AppAction::PopThenAddScreenToMainScreen(root_screen_type, screen) => {
self.screen_stack = vec![screen];
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
}
AppAction::Custom(_) => {}
AppAction::OnboardingComplete {
main_screen,
add_screen,
} => {
self.show_welcome_screen = false;
self.welcome_screen = None;
self.selected_main_screen = main_screen;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context().update_settings(main_screen).ok();
// If there's an additional screen to push, create and push it
if let Some(screen_type) = add_screen {
let screen = screen_type.create_screen(self.current_app_context());
self.screen_stack.push(screen);
AppAction::BackendTasks(tasks, mode) => {
self.handle_backend_tasks(tasks, mode);
}
// Start SPV sync after onboarding completes (if auto-start is enabled and developer mode is on)
// TODO: SPV auto-start is gated behind developer mode while SPV is in development.
// Remove the is_developer_mode() check once SPV is production-ready.
let current_context = self.current_app_context();
let auto_start_spv = current_context.db.get_auto_start_spv().unwrap_or(false);
if auto_start_spv
&& current_context.is_developer_mode()
&& current_context.core_backend_mode() == crate::spv::CoreBackendMode::Spv
{
if let Err(e) = current_context.start_spv() {
tracing::warn!("Failed to start SPV sync after onboarding: {}", e);
} else {
tracing::info!("SPV sync started after onboarding");
AppAction::SetMainScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
}
AppAction::SetMainScreenThenGoToMainScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
self.screen_stack = vec![];
}
AppAction::SetMainScreenThenPopScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
}
}
AppAction::SwitchNetwork(network) => {
self.change_network(network);
self.current_app_context()
.update_settings(RootScreenType::RootScreenNetworkChooser)
.ok();
}
AppAction::PopThenAddScreenToMainScreen(root_screen_type, screen) => {
self.screen_stack = vec![screen];
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
}
AppAction::Custom(_) => {}
Comment thread
lklimek marked this conversation as resolved.
AppAction::OnboardingComplete {
main_screen,
add_screen,
} => {
self.show_welcome_screen = false;
self.welcome_screen = None;
self.selected_main_screen = main_screen;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context().update_settings(main_screen).ok();
// If there's an additional screen to push, create and push it
if let Some(screen_type) = add_screen {
let screen = screen_type.create_screen(self.current_app_context());
self.screen_stack.push(screen);
}
// Start SPV sync after onboarding completes (if auto-start is enabled and developer mode is on)
// TODO: SPV auto-start is gated behind developer mode while SPV is in development.
// Remove the is_developer_mode() check once SPV is production-ready.
let current_context = self.current_app_context();
let auto_start_spv = current_context.db.get_auto_start_spv().unwrap_or(false);
if auto_start_spv
&& current_context.is_developer_mode()
&& current_context.core_backend_mode() == crate::spv::CoreBackendMode::Spv
{
if let Err(e) = current_context.start_spv() {
tracing::warn!("Failed to start SPV sync after onboarding: {}", e);
} else {
tracing::info!("SPV sync started after onboarding");
}
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::app_dir::core_cookie_path;
use crate::backend_task::contested_names::ScheduledDPNSVote;
use crate::components::core_zmq_listener::ZMQConnectionEvent;
pub mod connection_status;
use crate::config::{Config, NetworkConfig};
use crate::context_provider::Provider as RpcProvider;
use crate::context_provider_spv::SpvProvider;
Expand All @@ -22,6 +23,7 @@ use crate::ui::RootScreenType;
use crate::ui::tokens::tokens_screen::{IdentityTokenBalance, IdentityTokenIdentifier};
use crate::utils::tasks::TaskManager;
use bincode::config;
use connection_status::ConnectionStatus;
use crossbeam_channel::{Receiver, Sender};
use dash_sdk::Sdk;
use dash_sdk::dashcore_rpc::dashcore::{InstantLock, Transaction};
Expand Down Expand Up @@ -75,7 +77,6 @@ pub struct AppContext {
pub(crate) config: Arc<RwLock<NetworkConfig>>,
pub(crate) rx_zmq_status: Receiver<ZMQConnectionEvent>,
pub(crate) sx_zmq_status: Sender<ZMQConnectionEvent>,
pub(crate) zmq_connection_status: Mutex<ZMQConnectionEvent>,
pub(crate) dpns_contract: Arc<DataContract>,
pub(crate) withdraws_contract: Arc<DataContract>,
pub(crate) dashpay_contract: Arc<DataContract>,
Expand All @@ -100,6 +101,8 @@ pub struct AppContext {
pub(crate) subtasks: Arc<TaskManager>,
pub(crate) spv_manager: Arc<SpvManager>,
core_backend_mode: AtomicU8,
/// Tracks the connection status to currently active network
pub(crate) connection_status: Arc<ConnectionStatus>,
/// Pending wallet selection - set after creating/importing a wallet
/// so the wallet screen can auto-select the new wallet
pub(crate) pending_wallet_selection: Mutex<Option<WalletSeedHash>>,
Expand All @@ -118,6 +121,7 @@ impl AppContext {
db: Arc<Database>,
password_info: Option<PasswordInfo>,
subtasks: Arc<TaskManager>,
connection_status: Arc<ConnectionStatus>,
) -> Option<Arc<Self>> {
let config = match Config::load() {
Ok(config) => config,
Expand Down Expand Up @@ -270,12 +274,12 @@ impl AppContext {
single_key_wallets: RwLock::new(single_key_wallets),
password_info,
transactions_waiting_for_finality: Mutex::new(BTreeMap::new()),
zmq_connection_status: Mutex::new(ZMQConnectionEvent::Disconnected),
animate,
cached_settings: RwLock::new(None),
subtasks,
spv_manager,
core_backend_mode: AtomicU8::new(saved_core_backend_mode),
connection_status,
pending_wallet_selection: Mutex::new(None),
selected_wallet_hash: Mutex::new(selected_wallet_hash),
selected_single_key_hash: Mutex::new(selected_single_key_hash),
Expand Down Expand Up @@ -349,6 +353,10 @@ impl AppContext {
self.core_backend_mode.load(Ordering::Relaxed).into()
}

pub fn connection_status(&self) -> &ConnectionStatus {
&self.connection_status
}

pub fn set_core_backend_mode(self: &Arc<Self>, mode: CoreBackendMode) {
self.core_backend_mode
.store(mode.as_u8(), Ordering::Relaxed);
Expand Down
Loading
Loading