app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit ea9d77ee32abd692ce36c2facc6d1e9a8142f9e0
parent 7f181b47a59fd214ca52f86c4e461bd52aefa422
Author: triesap <tyson@radroots.org>
Date:   Sat, 21 Mar 2026 16:58:34 +0000

android: add device-reset action for local state

- expose `Reset This Device` from the android backend in this slice
- remove all local identities and the android accounts file before returning the app to setup
- add android tests for clearing every local identity and deleting the accounts file
- keep the change scoped to the android launcher crate with the existing shared home reset contract

Diffstat:
Mcrates/android/src/lib.rs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+), 0 deletions(-)

diff --git a/crates/android/src/lib.rs b/crates/android/src/lib.rs @@ -18,6 +18,8 @@ use radroots_nostr_accounts::prelude::RadrootsNostrAccountRecord; use radroots_nostr_accounts::prelude::RadrootsNostrAccountsManager; #[cfg(any(target_os = "android", test))] use radroots_nostr_accounts::prelude::RadrootsNostrSelectedAccountStatus; +#[cfg(any(target_os = "android", test))] +use std::path::Path; #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; @@ -99,6 +101,36 @@ impl RadrootsAppBackend for AndroidBackend { Ok(None) } } + + fn home_reset_action_state(&self) -> Option<HomeActionState> { + #[cfg(target_os = "android")] + { + return Some(HomeActionState { + label: "Reset This Device".to_owned(), + enabled: true, + pending: false, + }); + } + + #[cfg(not(target_os = "android"))] + { + None + } + } + + fn request_home_reset_action(&self) -> Result<Option<IdentityGateState>, String> { + #[cfg(target_os = "android")] + { + let manager = Self::accounts_manager()?; + let accounts_path = storage::accounts_path()?; + return Self::reset_local_device_state(&manager, accounts_path.as_path()).map(Some); + } + + #[cfg(not(target_os = "android"))] + { + Ok(None) + } + } } #[cfg(any(target_os = "android", test))] @@ -179,6 +211,43 @@ impl AndroidBackend { .map_err(|source| source.to_string())?; Self::identity_state_from_manager(manager) } + + fn remove_all_local_identities( + manager: &RadrootsNostrAccountsManager, + ) -> Result<IdentityGateState, String> { + let account_ids = manager + .list_accounts() + .map_err(|source| source.to_string())? + .into_iter() + .map(|record| record.account_id) + .collect::<Vec<_>>(); + + for account_id in account_ids { + manager + .remove_account(&account_id) + .map_err(|source| source.to_string())?; + } + + Self::identity_state_from_manager(manager) + } + + fn remove_accounts_file_if_present(accounts_path: &Path) -> Result<(), String> { + match std::fs::remove_file(accounts_path) { + Ok(()) => Ok(()), + Err(source) if source.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(source) => Err(format!("failed to remove android accounts file: {source}")), + } + } + + #[cfg(target_os = "android")] + fn reset_local_device_state( + manager: &RadrootsNostrAccountsManager, + accounts_path: &Path, + ) -> Result<IdentityGateState, String> { + let state = Self::remove_all_local_identities(manager)?; + Self::remove_accounts_file_if_present(accounts_path)?; + Ok(state) + } } #[cfg(any(target_os = "android", test))] @@ -323,4 +392,40 @@ mod tests { None ); } + + #[test] + fn remove_all_local_identities_clears_every_account() { + let manager = RadrootsNostrAccountsManager::new_in_memory(); + + manager + .generate_identity(Some("first".into()), true) + .expect("generate first"); + manager + .generate_identity(Some("second".into()), false) + .expect("generate second"); + + let state = AndroidBackend::remove_all_local_identities(&manager).expect("reset state"); + + assert_eq!(state, IdentityGateState::Missing); + assert_eq!(manager.list_accounts().expect("list accounts").len(), 0); + assert_eq!(manager.selected_account_id().expect("selected"), None); + } + + #[test] + fn remove_accounts_file_if_present_deletes_existing_file() { + let unique = format!( + "radroots-android-reset-{}-{}.json", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system time") + .as_nanos() + ); + let path = std::env::temp_dir().join(unique); + std::fs::write(&path, b"{}").expect("write accounts file"); + + AndroidBackend::remove_accounts_file_if_present(path.as_path()).expect("remove file"); + + assert!(!path.exists()); + } }