notedeck

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

account_manager.rs (7307B)


      1 use std::cmp::Ordering;
      2 
      3 use enostr::{FilledKeypair, FullKeypair, Keypair};
      4 use nostrdb::Ndb;
      5 use serde::{Deserialize, Serialize};
      6 
      7 use crate::{
      8     column::Columns,
      9     imgcache::ImageCache,
     10     login_manager::AcquireKeyState,
     11     route::{Route, Router},
     12     storage::{KeyStorageResponse, KeyStorageType},
     13     ui::{
     14         account_login_view::{AccountLoginResponse, AccountLoginView},
     15         account_management::{AccountsView, AccountsViewResponse},
     16     },
     17     unknowns::SingleUnkIdAction,
     18 };
     19 use tracing::{error, info};
     20 
     21 pub use crate::user_account::UserAccount;
     22 
     23 /// The interface for managing the user's accounts.
     24 /// Represents all user-facing operations related to account management.
     25 pub struct AccountManager {
     26     currently_selected_account: Option<usize>,
     27     accounts: Vec<UserAccount>,
     28     key_store: KeyStorageType,
     29 }
     30 
     31 // TODO(jb55): move to accounts/route.rs
     32 pub enum AccountsRouteResponse {
     33     Accounts(AccountsViewResponse),
     34     AddAccount(AccountLoginResponse),
     35 }
     36 
     37 #[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
     38 pub enum AccountsRoute {
     39     Accounts,
     40     AddAccount,
     41 }
     42 
     43 /// Render account management views from a route
     44 #[allow(clippy::too_many_arguments)]
     45 pub fn render_accounts_route(
     46     ui: &mut egui::Ui,
     47     ndb: &Ndb,
     48     col: usize,
     49     columns: &mut Columns,
     50     img_cache: &mut ImageCache,
     51     accounts: &mut AccountManager,
     52     login_state: &mut AcquireKeyState,
     53     route: AccountsRoute,
     54 ) -> SingleUnkIdAction {
     55     let router = columns.column_mut(col).router_mut();
     56     let resp = match route {
     57         AccountsRoute::Accounts => AccountsView::new(ndb, accounts, img_cache)
     58             .ui(ui)
     59             .inner
     60             .map(AccountsRouteResponse::Accounts),
     61 
     62         AccountsRoute::AddAccount => AccountLoginView::new(login_state)
     63             .ui(ui)
     64             .inner
     65             .map(AccountsRouteResponse::AddAccount),
     66     };
     67 
     68     if let Some(resp) = resp {
     69         match resp {
     70             AccountsRouteResponse::Accounts(response) => {
     71                 process_accounts_view_response(accounts, response, router);
     72                 SingleUnkIdAction::no_action()
     73             }
     74             AccountsRouteResponse::AddAccount(response) => {
     75                 let action = process_login_view_response(accounts, response);
     76                 *login_state = Default::default();
     77                 router.go_back();
     78                 action
     79             }
     80         }
     81     } else {
     82         SingleUnkIdAction::no_action()
     83     }
     84 }
     85 
     86 pub fn process_accounts_view_response(
     87     manager: &mut AccountManager,
     88     response: AccountsViewResponse,
     89     router: &mut Router<Route>,
     90 ) {
     91     match response {
     92         AccountsViewResponse::RemoveAccount(index) => {
     93             manager.remove_account(index);
     94         }
     95         AccountsViewResponse::SelectAccount(index) => {
     96             manager.select_account(index);
     97         }
     98         AccountsViewResponse::RouteToLogin => {
     99             router.route_to(Route::add_account());
    100         }
    101     }
    102 }
    103 
    104 impl AccountManager {
    105     pub fn new(key_store: KeyStorageType) -> Self {
    106         let accounts = if let KeyStorageResponse::ReceivedResult(res) = key_store.get_keys() {
    107             res.unwrap_or_default()
    108         } else {
    109             Vec::new()
    110         };
    111 
    112         let currently_selected_account = get_selected_index(&accounts, &key_store);
    113         AccountManager {
    114             currently_selected_account,
    115             accounts,
    116             key_store,
    117         }
    118     }
    119 
    120     pub fn get_accounts(&self) -> &Vec<UserAccount> {
    121         &self.accounts
    122     }
    123 
    124     pub fn get_account(&self, ind: usize) -> Option<&UserAccount> {
    125         self.accounts.get(ind)
    126     }
    127 
    128     pub fn find_account(&self, pk: &[u8; 32]) -> Option<&UserAccount> {
    129         self.accounts.iter().find(|acc| acc.pubkey.bytes() == pk)
    130     }
    131 
    132     pub fn remove_account(&mut self, index: usize) {
    133         if let Some(account) = self.accounts.get(index) {
    134             let _ = self.key_store.remove_key(account);
    135             self.accounts.remove(index);
    136 
    137             if let Some(selected_index) = self.currently_selected_account {
    138                 match selected_index.cmp(&index) {
    139                     Ordering::Greater => {
    140                         self.select_account(selected_index - 1);
    141                     }
    142                     Ordering::Equal => {
    143                         self.clear_selected_account();
    144                     }
    145                     Ordering::Less => {}
    146                 }
    147             }
    148         }
    149     }
    150 
    151     pub fn has_account_pubkey(&self, pubkey: &[u8; 32]) -> bool {
    152         for account in &self.accounts {
    153             if account.pubkey.bytes() == pubkey {
    154                 return true;
    155             }
    156         }
    157 
    158         false
    159     }
    160 
    161     #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
    162     pub fn add_account(&mut self, account: Keypair) -> SingleUnkIdAction {
    163         if self.has_account_pubkey(account.pubkey.bytes()) {
    164             info!("already have account, not adding {}", account.pubkey);
    165             return SingleUnkIdAction::pubkey(account.pubkey);
    166         }
    167 
    168         let _ = self.key_store.add_key(&account);
    169         let pk = account.pubkey;
    170         self.accounts.push(account);
    171         SingleUnkIdAction::pubkey(pk)
    172     }
    173 
    174     pub fn num_accounts(&self) -> usize {
    175         self.accounts.len()
    176     }
    177 
    178     pub fn get_selected_account_index(&self) -> Option<usize> {
    179         self.currently_selected_account
    180     }
    181 
    182     pub fn selected_or_first_nsec(&self) -> Option<FilledKeypair<'_>> {
    183         self.get_selected_account()
    184             .and_then(|kp| kp.to_full())
    185             .or_else(|| self.accounts.iter().find_map(|a| a.to_full()))
    186     }
    187 
    188     pub fn get_selected_account(&self) -> Option<&UserAccount> {
    189         if let Some(account_index) = self.currently_selected_account {
    190             if let Some(account) = self.get_account(account_index) {
    191                 Some(account)
    192             } else {
    193                 None
    194             }
    195         } else {
    196             None
    197         }
    198     }
    199 
    200     pub fn select_account(&mut self, index: usize) {
    201         if let Some(account) = self.accounts.get(index) {
    202             self.currently_selected_account = Some(index);
    203             self.key_store.select_key(Some(account.pubkey));
    204         }
    205     }
    206 
    207     pub fn clear_selected_account(&mut self) {
    208         self.currently_selected_account = None;
    209         self.key_store.select_key(None);
    210     }
    211 }
    212 
    213 fn get_selected_index(accounts: &[UserAccount], keystore: &KeyStorageType) -> Option<usize> {
    214     match keystore.get_selected_key() {
    215         KeyStorageResponse::ReceivedResult(Ok(Some(pubkey))) => {
    216             return accounts.iter().position(|account| account.pubkey == pubkey);
    217         }
    218 
    219         KeyStorageResponse::ReceivedResult(Err(e)) => error!("Error getting selected key: {}", e),
    220         KeyStorageResponse::Waiting | KeyStorageResponse::ReceivedResult(Ok(None)) => {}
    221     };
    222 
    223     None
    224 }
    225 
    226 pub fn process_login_view_response(
    227     manager: &mut AccountManager,
    228     response: AccountLoginResponse,
    229 ) -> SingleUnkIdAction {
    230     let r = match response {
    231         AccountLoginResponse::CreateNew => {
    232             manager.add_account(FullKeypair::generate().to_keypair())
    233         }
    234         AccountLoginResponse::LoginWith(keypair) => manager.add_account(keypair),
    235     };
    236     manager.select_account(manager.num_accounts() - 1);
    237     r
    238 }