notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 10d6d740b837427c687c68f95702351184ad78f8
parent d092f5c23e7843da4919531f10a31a71f2a09b82
Author: kernelkind <kernelkind@gmail.com>
Date:   Thu, 26 Jun 2025 14:30:26 -0400

migrate accounts to be referenced through pks instead of indices

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck/src/account/accounts.rs | 375+++++++++++++++++++++++--------------------------------------------------------
Mcrates/notedeck/src/app.rs | 9+++------
Mcrates/notedeck/src/context.rs | 4++--
Mcrates/notedeck/src/wallet.rs | 10++++------
Mcrates/notedeck_columns/src/accounts/mod.rs | 32++++++++++++++++----------------
Mcrates/notedeck_columns/src/actionbar.rs | 6++----
Mcrates/notedeck_columns/src/app.rs | 9++++-----
Mcrates/notedeck_columns/src/nav.rs | 36++++++++++++++++--------------------
Mcrates/notedeck_columns/src/timeline/route.rs | 2+-
Mcrates/notedeck_columns/src/ui/accounts.rs | 27++++++++++-----------------
Mcrates/notedeck_columns/src/ui/wallet.rs | 2+-
11 files changed, 168 insertions(+), 344 deletions(-)

diff --git a/crates/notedeck/src/account/accounts.rs b/crates/notedeck/src/account/accounts.rs @@ -1,11 +1,11 @@ use tracing::{debug, error, info}; +use crate::account::cache::AccountCache; use crate::account::mute::AccountMutedData; use crate::account::relay::{AccountRelayData, RelayDefaults}; -use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UnknownIds, UserAccount}; +use crate::{AccountStorage, MuteFun, RelaySpec, SingleUnkIdAction, UserAccount}; use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool}; use nostrdb::{Ndb, Note, Transaction}; -use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet}; // TODO: remove this @@ -14,13 +14,11 @@ use std::sync::Arc; /// The interface for managing the user's accounts. /// Represents all user-facing operations related to account management. pub struct Accounts { - currently_selected_account: Option<usize>, - accounts: Vec<UserAccount>, + pub cache: AccountCache, key_store: Option<AccountStorage>, account_data: BTreeMap<[u8; 32], AccountData>, relay_defaults: RelayDefaults, needs_relay_config: bool, - fallback: Pubkey, } impl Accounts { @@ -29,21 +27,21 @@ impl Accounts { forced_relays: Vec<String>, fallback: Pubkey, ) -> Self { - let accounts = match &key_store { - Some(keystore) => match keystore.get_accounts() { - Ok(k) => k, + let (mut cache, _) = AccountCache::new(UserAccount::new(Keypair::only_pubkey(fallback))); + if let Some(keystore) = &key_store { + match keystore.get_accounts() { + Ok(accounts) => { + for account in accounts { + cache.add(account); + } + } Err(e) => { tracing::error!("could not get keys: {e}"); - Vec::new() } - }, - None => Vec::new(), - }; - - let currently_selected_account = if let Some(key_store) = &key_store { - get_selected_index(&accounts, key_store) - } else { - None + } + if let Some(selected) = keystore.get_selected_key().ok().flatten() { + cache.select(selected); + } }; let account_data = BTreeMap::new(); @@ -51,69 +49,22 @@ impl Accounts { let relay_defaults = RelayDefaults::new(forced_relays); Accounts { - currently_selected_account, - accounts, + cache, key_store, account_data, relay_defaults, needs_relay_config: true, - fallback, } } - pub fn get_accounts(&self) -> &Vec<UserAccount> { - &self.accounts - } - - pub fn get_account(&self, ind: usize) -> Option<&UserAccount> { - self.accounts.get(ind) - } - - pub fn find_account(&self, pk: &[u8; 32]) -> Option<&UserAccount> { - self.accounts - .iter() - .find(|acc| acc.key.pubkey.bytes() == pk) - } - - pub fn find_account_mut(&mut self, pk: &[u8; 32]) -> Option<&mut UserAccount> { - self.accounts - .iter_mut() - .find(|acc| acc.key.pubkey.bytes() == pk) - } - - pub fn with_fallback(&mut self, fallback: Pubkey) { - self.fallback = fallback; - } - - pub fn remove_account(&mut self, index: usize) { - if let Some(account) = self.accounts.get(index) { - if let Some(key_store) = &self.key_store { - if let Err(e) = key_store.remove_key(&account.key) { - tracing::error!("Could not remove account at index {index}: {e}"); - } - } - - self.accounts.remove(index); + pub fn remove_account(&mut self, pk: &Pubkey) { + let Some(removed) = self.cache.remove(pk) else { + return; + }; - if let Some(selected_index) = self.currently_selected_account { - match selected_index.cmp(&index) { - Ordering::Greater => { - self.select_account(selected_index - 1); - } - Ordering::Equal => { - if self.accounts.is_empty() { - // If no accounts remain, clear the selection - self.clear_selected_account(); - } else if index >= self.accounts.len() { - // If the removed account was the last one, select the new last account - self.select_account(self.accounts.len() - 1); - } else { - // Otherwise, select the account at the same position - self.select_account(index); - } - } - Ordering::Less => {} - } + if let Some(key_store) = &self.key_store { + if let Err(e) = key_store.remove_key(&removed.key) { + tracing::error!("Could not remove account {pk}: {e}"); } } } @@ -122,74 +73,44 @@ impl Accounts { self.needs_relay_config = true; } - fn contains_account(&self, pubkey: &[u8; 32]) -> Option<ContainsAccount> { - for (index, account) in self.accounts.iter().enumerate() { - let has_pubkey = account.key.pubkey.bytes() == pubkey; - let has_nsec = account.key.secret_key.is_some(); - if has_pubkey { - return Some(ContainsAccount { has_nsec, index }); - } - } - - None - } - pub fn contains_full_kp(&self, pubkey: &enostr::Pubkey) -> bool { - if let Some(contains) = self.contains_account(pubkey.bytes()) { - contains.has_nsec - } else { - false - } + self.cache + .get(pubkey) + .is_some_and(|u| u.key.secret_key.is_some()) } #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"] - pub fn add_account(&mut self, key: Keypair) -> Option<AddAccountResponse> { - let pubkey = key.pubkey; - let switch_to_index = if let Some(contains_acc) = self.contains_account(pubkey.bytes()) { - if key.secret_key.is_some() && !contains_acc.has_nsec { - info!( - "user provided nsec, but we already have npub {}. Upgrading to nsec", - pubkey - ); - - if let Some(key_store) = &self.key_store { - if let Err(e) = key_store.write_account(&UserAccount::new(key.clone())) { - tracing::error!("Could not add key for {:?}: {e}", key.pubkey); - } - } - - self.accounts[contains_acc.index].key = key; - } else { - info!("already have account, not adding {}", pubkey); + pub fn add_account(&mut self, kp: Keypair) -> Option<AddAccountResponse> { + let acc = if let Some(acc) = self.cache.get_mut(&kp.pubkey) { + if kp.secret_key.is_none() || acc.key.secret_key.is_some() { + tracing::info!("Already have account, not adding"); + return None; } - contains_acc.index + + acc.key = kp.clone(); + AccType::Acc(&*acc) } else { - info!("adding new account {}", pubkey); - if let Some(key_store) = &self.key_store { - if let Err(e) = key_store.write_account(&UserAccount::new(key.clone())) { - tracing::error!("Could not add key for {:?}: {e}", key.pubkey); - } - } - self.accounts.push(UserAccount::new(key)); - self.accounts.len() - 1 + AccType::Entry(self.cache.add(UserAccount::new(kp.clone()))) }; + if let Some(key_store) = &self.key_store { + if let Err(e) = key_store.write_account(acc.get_acc()) { + tracing::error!("Could not add key for {:?}: {e}", kp.pubkey); + } + } + Some(AddAccountResponse { - switch_to: switch_to_index, - unk_id_action: SingleUnkIdAction::pubkey(pubkey), + switch_to: kp.pubkey, + unk_id_action: SingleUnkIdAction::pubkey(kp.pubkey), }) } /// Update the `UserAccount` via callback and save the result to disk. /// return true if the update was successful pub fn update_current_account(&mut self, update: impl FnOnce(&mut UserAccount)) -> bool { - { - let Some(cur_account) = self.get_selected_account_mut() else { - return false; - }; + let cur_account = self.get_selected_account_mut(); - update(cur_account); - } + update(cur_account); let cur_acc = self.get_selected_account(); @@ -205,19 +126,8 @@ impl Accounts { true } - pub fn num_accounts(&self) -> usize { - self.accounts.len() - } - - pub fn get_selected_account_index(&self) -> Option<usize> { - self.currently_selected_account - } - - pub fn selected_or_first_nsec(&self) -> Option<FilledKeypair<'_>> { - self.get_selected_account() - .key - .to_full() - .or_else(|| self.accounts.iter().find_map(|a| a.key.to_full())) + pub fn selected_filled(&self) -> Option<FilledKeypair<'_>> { + self.get_selected_account().key.to_full() } /// Get the selected account's pubkey as bytes. Common operation so @@ -231,30 +141,15 @@ impl Accounts { } pub fn get_selected_account(&self) -> &UserAccount { - self.currently_selected_account - .and_then(|i| self.get_account(i)) - // NOTE: yeah, this is incorrect but we just need to seperate out the changes in smaller commits - .unwrap() + self.cache.selected() } pub fn selected_account_has_wallet(&self) -> bool { self.get_selected_account().wallet.is_some() } - pub fn get_selected_account_mut(&mut self) -> Option<&mut UserAccount> { - self.currently_selected_account - .map(|i| self.accounts.get_mut(i))? - } - - pub fn get_account_mut_optimized(&mut self, pk: &[u8; 32]) -> Option<&mut UserAccount> { - if let Some(ind) = self.currently_selected_account { - if ind < self.accounts.len() { - return Some(&mut self.accounts[ind]); - } - } - self.accounts - .iter_mut() - .find(|acc| acc.key.pubkey.bytes() == pk) + pub fn get_selected_account_mut(&mut self) -> &mut UserAccount { + self.cache.selected_mut() } pub fn get_selected_account_data(&mut self) -> Option<&mut AccountData> { @@ -262,37 +157,21 @@ impl Accounts { self.account_data.get_mut(&account_pubkey) } - pub fn select_account(&mut self, index: usize) { - if let Some(account) = self.accounts.get(index) { - self.currently_selected_account = Some(index); + pub fn select_account(&mut self, pk: &Pubkey) { + if self.cache.select(*pk) { if let Some(key_store) = &self.key_store { - if let Err(e) = key_store.select_key(Some(account.key.pubkey)) { - tracing::error!("Could not select key {:?}: {e}", account.key.pubkey); + if let Err(e) = key_store.select_key(Some(*pk)) { + tracing::error!("Could not select key {:?}: {e}", pk); } } } } - pub fn clear_selected_account(&mut self) { - self.currently_selected_account = None; - if let Some(key_store) = &self.key_store { - if let Err(e) = key_store.select_key(None) { - tracing::error!("Could not select None key: {e}"); - } - } - } - pub fn mutefun(&self) -> Box<MuteFun> { - if let Some(index) = self.currently_selected_account { - if let Some(account) = self.accounts.get(index) { - let pubkey = account.key.pubkey.bytes(); - if let Some(account_data) = self.account_data.get(pubkey) { - let muted = Arc::clone(&account_data.muted.muted); - return Box::new(move |note: &Note, thread: &[u8; 32]| { - muted.is_muted(note, thread) - }); - } - } + let pubkey = self.cache.selected().key.pubkey.bytes(); + if let Some(account_data) = self.account_data.get(pubkey) { + let muted = Arc::clone(&account_data.muted.muted); + return Box::new(move |note: &Note, thread: &[u8; 32]| muted.is_muted(note, thread)); } Box::new(|_: &Note, _: &[u8; 32]| false) } @@ -320,14 +199,14 @@ impl Accounts { // which have still data but are no longer in our account list (removed). fn delta_accounts(&self) -> (Vec<[u8; 32]>, Vec<[u8; 32]>) { let mut added = Vec::new(); - for pubkey in self.accounts.iter().map(|a| a.key.pubkey.bytes()) { + for pubkey in (&self.cache).into_iter().map(|(pk, _)| pk.bytes()) { if !self.account_data.contains_key(pubkey) { added.push(*pubkey); } } let mut removed = Vec::new(); for pubkey in self.account_data.keys() { - if self.contains_account(pubkey).is_none() { + if self.cache.get_bytes(pubkey).is_none() { removed.push(*pubkey); } } @@ -351,13 +230,6 @@ impl Accounts { self.account_data.remove(pubkey); } - fn handle_no_accounts(&mut self, unknown_ids: &mut UnknownIds, ndb: &Ndb, txn: &Transaction) { - if let Some(resp) = self.add_account(Keypair::new(self.fallback, None)) { - resp.unk_id_action.process_action(unknown_ids, ndb, txn); - } - self.select_account(self.num_accounts() - 1); - } - fn poll_for_updates(&mut self, ndb: &Ndb) -> bool { let mut changed = false; for (pubkey, data) in &mut self.account_data { @@ -396,8 +268,7 @@ impl Accounts { ) { debug!( "updating relay configuration for currently selected {:?}", - self.currently_selected_account - .map(|i| hex::encode(self.accounts.get(i).unwrap().key.pubkey.bytes())) + self.cache.selected().key.pubkey.hex() ); // If forced relays are set use them only @@ -444,13 +315,7 @@ impl Accounts { debug!("current relays: {:?}", pool.urls()); } - pub fn update( - &mut self, - ndb: &mut Ndb, - pool: &mut RelayPool, - ctx: &egui::Context, - unknown_ids: &mut UnknownIds, - ) { + pub fn update(&mut self, ndb: &mut Ndb, pool: &mut RelayPool, ctx: &egui::Context) { // IMPORTANT - This function is called in the UI update loop, // make sure it is fast when idle @@ -463,8 +328,11 @@ impl Accounts { }; // Do we need to deactivate any existing account subs? - for (ndx, account) in self.accounts.iter().enumerate() { - if Some(ndx) != self.currently_selected_account { + + let selected = self.cache.selected(); + + for (pk, account) in &self.cache { + if *pk != selected.key.pubkey { // this account is not currently selected if let Some(data) = self.account_data.get_mut(account.key.pubkey.bytes()) { if data.relay.sub.is_some() { @@ -490,10 +358,6 @@ impl Accounts { need_reconfig = true; } - if self.accounts.is_empty() { - let txn = Transaction::new(ndb).unwrap(); - self.handle_no_accounts(unknown_ids, ndb, &txn); - } // Did any accounts receive updates (ie NIP-65 relay lists) need_reconfig = self.poll_for_updates(ndb) || need_reconfig; @@ -516,16 +380,8 @@ impl Accounts { } } - pub fn get_full<'a>(&'a self, pubkey: &[u8; 32]) -> Option<FilledKeypair<'a>> { - if let Some(contains) = self.contains_account(pubkey) { - if contains.has_nsec { - if let Some(kp) = self.get_account(contains.index) { - return kp.key.to_full(); - } - } - } - - None + pub fn get_full<'a>(&'a self, pubkey: &Pubkey) -> Option<FilledKeypair<'a>> { + self.cache.get(pubkey).and_then(|r| r.key.to_full()) } fn modify_advertised_relays( @@ -539,42 +395,35 @@ impl Accounts { RelayAction::Add => info!("add advertised relay \"{}\"", relay_url), RelayAction::Remove => info!("remove advertised relay \"{}\"", relay_url), } - match self.currently_selected_account { - None => error!("no account is currently selected."), - Some(index) => match self.accounts.get(index) { - None => error!("selected account index {} is out of range.", index), - Some(keypair) => { - let key_bytes: [u8; 32] = *keypair.key.pubkey.bytes(); - match self.account_data.get_mut(&key_bytes) { - None => error!("no account data found for the provided key."), - Some(account_data) => { - let advertised = &mut account_data.relay.advertised; - if advertised.is_empty() { - // If the selected account has no advertised relays, - // initialize with the bootstrapping set. - advertised - .extend(self.relay_defaults.bootstrap_relays.iter().cloned()); - } - match action { - RelayAction::Add => { - advertised.insert(RelaySpec::new(relay_url, false, false)); - } - RelayAction::Remove => { - advertised.remove(&RelaySpec::new(relay_url, false, false)); - } - } - self.needs_relay_config = true; - - // If we have the secret key publish the NIP-65 relay list - if let Some(secretkey) = &keypair.key.secret_key { - account_data - .relay - .publish_nip65_relays(&secretkey.to_secret_bytes(), pool); - } - } + + let selected = self.cache.selected(); + let key_bytes: [u8; 32] = *self.cache.selected().key.pubkey.bytes(); + match self.account_data.get_mut(&key_bytes) { + None => error!("no account data found for the provided key."), + Some(account_data) => { + let advertised = &mut account_data.relay.advertised; + if advertised.is_empty() { + // If the selected account has no advertised relays, + // initialize with the bootstrapping set. + advertised.extend(self.relay_defaults.bootstrap_relays.iter().cloned()); + } + match action { + RelayAction::Add => { + advertised.insert(RelaySpec::new(relay_url, false, false)); + } + RelayAction::Remove => { + advertised.remove(&RelaySpec::new(relay_url, false, false)); } } - }, + self.needs_relay_config = true; + + // If we have the secret key publish the NIP-65 relay list + if let Some(secretkey) = &selected.key.secret_key { + account_data + .relay + .publish_nip65_relays(&secretkey.to_secret_bytes(), pool); + } + } } } @@ -587,29 +436,23 @@ impl Accounts { } } -enum RelayAction { - Add, - Remove, +enum AccType<'a> { + Entry(hashbrown::hash_map::OccupiedEntry<'a, Pubkey, UserAccount>), + Acc(&'a UserAccount), } -fn get_selected_index(accounts: &[UserAccount], keystore: &AccountStorage) -> Option<usize> { - match keystore.get_selected_key() { - Ok(Some(pubkey)) => { - return accounts - .iter() - .position(|account| account.key.pubkey == pubkey); +impl<'a> AccType<'a> { + fn get_acc(&'a self) -> &'a UserAccount { + match self { + AccType::Entry(occupied_entry) => occupied_entry.get(), + AccType::Acc(user_account) => user_account, } - Ok(None) => {} - Err(e) => error!("Error getting selected key: {}", e), - }; - - None + } } -#[derive(Default)] -pub struct ContainsAccount { - pub has_nsec: bool, - pub index: usize, +enum RelayAction { + Add, + Remove, } pub struct AccountData { @@ -618,6 +461,6 @@ pub struct AccountData { } pub struct AddAccountResponse { - pub switch_to: usize, + pub switch_to: Pubkey, pub unk_id_action: SingleUnkIdAction, } diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs @@ -95,8 +95,7 @@ impl eframe::App for Notedeck { .on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage); // handle account updates - self.accounts - .update(&mut self.ndb, &mut self.pool, ctx, &mut self.unknown_ids); + self.accounts.update(&mut self.ndb, &mut self.pool, ctx); self.zaps .process(&mut self.accounts, &mut self.global_wallet, &self.ndb); @@ -179,8 +178,6 @@ impl Notedeck { let mut accounts = Accounts::new(keystore, parsed_args.relays.clone(), FALLBACK_PUBKEY()); - let num_keys = parsed_args.keys.len(); - let mut unknown_ids = UnknownIds::default(); let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); @@ -195,8 +192,8 @@ impl Notedeck { } } - if num_keys != 0 { - accounts.select_account(0); + if let Some(first) = parsed_args.keys.first() { + accounts.select_account(&first.pubkey); } // AccountManager will setup the pool on first update diff --git a/crates/notedeck/src/context.rs b/crates/notedeck/src/context.rs @@ -1,6 +1,6 @@ use crate::{ - frame_history::FrameHistory, wallet::GlobalWallet, zaps::Zaps, Accounts, Args, DataPath, - Images, JobPool, NoteCache, ThemeHandler, UnknownIds, + account::accounts::Accounts, frame_history::FrameHistory, wallet::GlobalWallet, zaps::Zaps, + Args, DataPath, Images, JobPool, NoteCache, ThemeHandler, UnknownIds, }; use egui_winit::clipboard::Clipboard; diff --git a/crates/notedeck/src/wallet.rs b/crates/notedeck/src/wallet.rs @@ -11,13 +11,13 @@ use tokio::sync::RwLock; use crate::{zaps::UserZapMsats, Accounts, DataPath, DefaultZapMsats, TokenHandler}; pub fn get_wallet_for<'a>( - accounts: &'a mut Accounts, + accounts: &'a Accounts, global_wallet: &'a mut GlobalWallet, account_pk: &'a [u8; 32], ) -> Option<&'a ZapWallet> { - let cur_acc = accounts.get_account_mut_optimized(account_pk)?; + let cur_acc = accounts.cache.get_bytes(account_pk)?; - if let Some(wallet) = &mut cur_acc.wallet { + if let Some(wallet) = &cur_acc.wallet { return Some(wallet); } @@ -28,9 +28,7 @@ pub fn get_current_wallet<'a>( accounts: &'a mut Accounts, global_wallet: &'a mut GlobalWallet, ) -> Option<&'a mut ZapWallet> { - let Some(acc) = accounts.get_selected_account_mut() else { - return global_wallet.wallet.as_mut(); - }; + let acc = accounts.get_selected_account_mut(); let Some(wallet) = &mut acc.wallet else { return global_wallet.wallet.as_mut(); diff --git a/crates/notedeck_columns/src/accounts/mod.rs b/crates/notedeck_columns/src/accounts/mod.rs @@ -1,4 +1,4 @@ -use enostr::FullKeypair; +use enostr::{FullKeypair, Pubkey}; use nostrdb::{Ndb, Transaction}; use notedeck::{Accounts, Images, SingleUnkIdAction, UnknownIds}; @@ -35,11 +35,11 @@ pub struct SwitchAccountAction { pub source_column: usize, /// The account to switch to - pub switch_to: usize, + pub switch_to: Pubkey, } impl SwitchAccountAction { - pub fn new(source_column: usize, switch_to: usize) -> Self { + pub fn new(source_column: usize, switch_to: Pubkey) -> Self { SwitchAccountAction { source_column, switch_to, @@ -50,7 +50,7 @@ impl SwitchAccountAction { #[derive(Debug)] pub enum AccountsAction { Switch(SwitchAccountAction), - Remove(usize), + Remove(Pubkey), } #[must_use = "You must call process_login_action on this to handle unknown ids"] @@ -120,24 +120,24 @@ pub fn process_accounts_view_response( let router = get_active_columns_mut(accounts, decks) .column_mut(col) .router_mut(); - let mut selection = None; + let mut action = None; match response { - AccountsViewResponse::RemoveAccount(index) => { - let acc_sel = AccountsAction::Remove(index); - info!("account selection: {:?}", acc_sel); - selection = Some(acc_sel); + AccountsViewResponse::RemoveAccount(pk_to_remove) => { + let cur_action = AccountsAction::Remove(pk_to_remove); + info!("account selection: {:?}", action); + action = Some(cur_action); } - AccountsViewResponse::SelectAccount(index) => { - let acc_sel = AccountsAction::Switch(SwitchAccountAction::new(col, index)); + AccountsViewResponse::SelectAccount(new_pk) => { + let acc_sel = AccountsAction::Switch(SwitchAccountAction::new(col, new_pk)); info!("account selection: {:?}", acc_sel); - selection = Some(acc_sel); + action = Some(acc_sel); } AccountsViewResponse::RouteToLogin => { router.route_to(Route::add_account()); } } accounts.needs_relay_config(); - selection + action } pub fn process_login_view_response( @@ -160,13 +160,13 @@ pub fn process_login_view_response( decks.add_deck_default(pubkey); - if let Some(resp) = r { + if let Some(action) = r { AddAccountAction { accounts_action: Some(AccountsAction::Switch(SwitchAccountAction { source_column: col, - switch_to: resp.switch_to, + switch_to: action.switch_to, })), - unk_id_action: resp.unk_id_action, + unk_id_action: action.unk_id_action, } } else { AddAccountAction { diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs @@ -97,10 +97,8 @@ fn execute_note_action( NoteAction::Quote(note_id) => { router_action = Some(RouterAction::route_to(Route::quote(note_id))); } - NoteAction::Zap(zap_action) => 's: { - let Some(cur_acc) = accounts.get_selected_account_mut() else { - break 's; - }; + NoteAction::Zap(zap_action) => { + let cur_acc = accounts.get_selected_account_mut(); let sender = cur_acc.key.pubkey; diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -428,8 +428,8 @@ impl Damus { } else { info!("DecksCache: creating new with demo configuration"); let mut cache = DecksCache::new_with_demo_config(&mut timeline_cache, ctx); - for account in ctx.accounts.get_accounts() { - cache.add_deck_default(account.key.pubkey); + for (pk, _) in &ctx.accounts.cache { + cache.add_deck_default(*pk); } set_demo(&mut cache, ctx.ndb, ctx.accounts, ctx.unknown_ids); @@ -445,8 +445,6 @@ impl Damus { let jobs = JobsCache::default(); - ctx.accounts.with_fallback(FALLBACK_PUBKEY()); - let threads = Threads::default(); Self { @@ -770,13 +768,14 @@ pub fn set_demo( accounts: &mut Accounts, unk_ids: &mut UnknownIds, ) { + let fallback = decks_cache.get_fallback_pubkey(); let txn = Transaction::new(ndb).expect("txn"); if let Some(resp) = accounts.add_account(Keypair::only_pubkey(*decks_cache.get_fallback_pubkey())) { resp.unk_id_action.process_action(unk_ids, ndb, &txn); } - accounts.select_account(accounts.num_accounts() - 1); + accounts.select_account(fallback); } fn columns_to_decks_cache(cols: Columns, key: &[u8; 32]) -> DecksCache { diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs @@ -78,14 +78,14 @@ impl SwitchingAction { match &self { SwitchingAction::Accounts(account_action) => match account_action { AccountsAction::Switch(switch_action) => { - ctx.accounts.select_account(switch_action.switch_to); + ctx.accounts.select_account(&switch_action.switch_to); // pop nav after switch get_active_columns_mut(ctx.accounts, decks_cache) .column_mut(switch_action.source_column) .router_mut() .go_back(); } - AccountsAction::Remove(index) => ctx.accounts.remove_account(*index), + AccountsAction::Remove(to_remove) => ctx.accounts.remove_account(to_remove), }, SwitchingAction::Columns(columns_action) => match *columns_action { ColumnsAction::Remove(index) => { @@ -481,7 +481,7 @@ fn render_nav_body( }; let id = egui::Id::new(("post", col, note.key().unwrap())); - let poster = ctx.accounts.selected_or_first_nsec()?; + let poster = ctx.accounts.selected_filled()?; let action = { let draft = app.drafts.reply_mut(note.id()); @@ -519,7 +519,7 @@ fn render_nav_body( let id = egui::Id::new(("post", col, note.key().unwrap())); - let poster = ctx.accounts.selected_or_first_nsec()?; + let poster = ctx.accounts.selected_filled()?; let draft = app.drafts.quote_mut(note.id()); let response = egui::ScrollArea::vertical() @@ -656,7 +656,7 @@ fn render_nav_body( } Route::EditProfile(pubkey) => { let mut action = None; - if let Some(kp) = ctx.accounts.get_full(pubkey.bytes()) { + if let Some(kp) = ctx.accounts.get_full(pubkey) { let state = app .view_state .pubkey_to_profile_state @@ -686,15 +686,16 @@ fn render_nav_body( Route::Wallet(wallet_type) => { let state = match wallet_type { notedeck::WalletType::Auto => 's: { - if let Some(cur_acc) = ctx.accounts.get_selected_account_mut() { - if let Some(wallet) = &mut cur_acc.wallet { - let default_zap_state = get_default_zap_state(&mut wallet.default_zap); - break 's WalletState::Wallet { - wallet: &mut wallet.wallet, - default_zap_state, - can_create_local_wallet: false, - }; - } + if let Some(cur_acc_wallet) = + &mut ctx.accounts.get_selected_account_mut().wallet + { + let default_zap_state = + get_default_zap_state(&mut cur_acc_wallet.default_zap); + break 's WalletState::Wallet { + wallet: &mut cur_acc_wallet.wallet, + default_zap_state, + can_create_local_wallet: false, + }; } let Some(wallet) = &mut ctx.global_wallet.wallet else { @@ -712,12 +713,7 @@ fn render_nav_body( } } notedeck::WalletType::Local => 's: { - let Some(cur_acc) = ctx.accounts.get_selected_account_mut() else { - break 's WalletState::NoWallet { - state: &mut ctx.global_wallet.ui_state, - show_local_only: false, - }; - }; + let cur_acc = ctx.accounts.get_selected_account_mut(); let Some(wallet) = &mut cur_acc.wallet else { break 's WalletState::NoWallet { state: &mut ctx.global_wallet.ui_state, diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs @@ -131,7 +131,7 @@ pub fn render_profile_route( if let Some(action) = action { match action { ui::profile::ProfileViewAction::EditProfile => accounts - .get_full(pubkey.bytes()) + .get_full(pubkey) .map(|kp| RenderNavAction::ProfileAction(ProfileAction::Edit(kp.to_full()))), ui::profile::ProfileViewAction::Note(note_action) => { Some(RenderNavAction::NoteAction(note_action)) diff --git a/crates/notedeck_columns/src/ui/accounts.rs b/crates/notedeck_columns/src/ui/accounts.rs @@ -1,6 +1,7 @@ use egui::{ Align, Button, Frame, InnerResponse, Layout, RichText, ScrollArea, Ui, UiBuilder, Vec2, }; +use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; use notedeck::{Accounts, Images}; use notedeck_ui::colors::PINK; @@ -16,8 +17,8 @@ pub struct AccountsView<'a> { #[derive(Clone, Debug)] pub enum AccountsViewResponse { - SelectAccount(usize), - RemoveAccount(usize), + SelectAccount(Pubkey), + RemoveAccount(Pubkey), RouteToLogin, } @@ -68,19 +69,11 @@ impl<'a> AccountsView<'a> { return; }; - for i in 0..accounts.num_accounts() { - let (account_pubkey, has_nsec) = match accounts.get_account(i) { - Some(acc) => (acc.key.pubkey.bytes(), acc.key.secret_key.is_some()), - None => continue, - }; - - let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok(); - let is_selected = if let Some(selected) = accounts.get_selected_account_index() - { - i == selected - } else { - false - }; + let selected = accounts.cache.selected(); + for (pk, account) in &accounts.cache { + let profile = ndb.get_profile_by_pubkey(&txn, pk).ok(); + let is_selected = *pk == selected.key.pubkey; + let has_nsec = account.key.secret_key.is_some(); let profile_peview_view = { let max_size = egui::vec2(ui.available_width(), 77.0); @@ -96,10 +89,10 @@ impl<'a> AccountsView<'a> { if let Some(op) = profile_peview_view { return_op = Some(match op { ProfilePreviewAction::SwitchTo => { - AccountsViewResponse::SelectAccount(i) + AccountsViewResponse::SelectAccount(*pk) } ProfilePreviewAction::RemoveAccount => { - AccountsViewResponse::RemoveAccount(i) + AccountsViewResponse::RemoveAccount(*pk) } }); } diff --git a/crates/notedeck_columns/src/ui/wallet.rs b/crates/notedeck_columns/src/ui/wallet.rs @@ -63,7 +63,7 @@ impl WalletAction { let ui_state = &mut global_wallet.ui_state; if ui_state.for_local_only { ui_state.for_local_only = false; - let cur_acc = accounts.get_selected_account_mut()?; + let cur_acc = accounts.get_selected_account_mut(); if cur_acc.wallet.is_some() { return None;