accounts.rs (6338B)
1 use crate::colors::PINK; 2 use egui::{ 3 Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, UiBuilder, Vec2, 4 }; 5 use nostrdb::{Ndb, Transaction}; 6 use notedeck::{Accounts, ImageCache}; 7 8 use super::profile::preview::SimpleProfilePreview; 9 10 pub struct AccountsView<'a> { 11 ndb: &'a Ndb, 12 accounts: &'a Accounts, 13 img_cache: &'a mut ImageCache, 14 } 15 16 #[derive(Clone, Debug)] 17 pub enum AccountsViewResponse { 18 SelectAccount(usize), 19 RemoveAccount(usize), 20 RouteToLogin, 21 } 22 23 #[derive(Debug)] 24 enum ProfilePreviewAction { 25 RemoveAccount, 26 SwitchTo, 27 } 28 29 impl<'a> AccountsView<'a> { 30 pub fn new(ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut ImageCache) -> Self { 31 AccountsView { 32 ndb, 33 accounts, 34 img_cache, 35 } 36 } 37 38 pub fn ui(&mut self, ui: &mut Ui) -> InnerResponse<Option<AccountsViewResponse>> { 39 Frame::none().outer_margin(12.0).show(ui, |ui| { 40 if let Some(resp) = Self::top_section_buttons_widget(ui).inner { 41 return Some(resp); 42 } 43 44 ui.add_space(8.0); 45 scroll_area() 46 .show(ui, |ui| { 47 Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache) 48 }) 49 .inner 50 }) 51 } 52 53 fn show_accounts( 54 ui: &mut Ui, 55 accounts: &Accounts, 56 ndb: &Ndb, 57 img_cache: &mut ImageCache, 58 ) -> Option<AccountsViewResponse> { 59 let mut return_op: Option<AccountsViewResponse> = None; 60 ui.allocate_ui_with_layout( 61 Vec2::new(ui.available_size_before_wrap().x, 32.0), 62 Layout::top_down(egui::Align::Min), 63 |ui| { 64 let txn = if let Ok(txn) = Transaction::new(ndb) { 65 txn 66 } else { 67 return; 68 }; 69 70 for i in 0..accounts.num_accounts() { 71 let (account_pubkey, has_nsec) = match accounts.get_account(i) { 72 Some(acc) => (acc.pubkey.bytes(), acc.secret_key.is_some()), 73 None => continue, 74 }; 75 76 let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok(); 77 let is_selected = if let Some(selected) = accounts.get_selected_account_index() 78 { 79 i == selected 80 } else { 81 false 82 }; 83 84 let profile_peview_view = { 85 let max_size = egui::vec2(ui.available_width(), 77.0); 86 let resp = ui.allocate_response(max_size, egui::Sense::click()); 87 ui.allocate_new_ui(UiBuilder::new().max_rect(resp.rect), |ui| { 88 let preview = 89 SimpleProfilePreview::new(profile.as_ref(), img_cache, has_nsec); 90 show_profile_card(ui, preview, max_size, is_selected, resp) 91 }) 92 .inner 93 }; 94 95 if let Some(op) = profile_peview_view { 96 return_op = Some(match op { 97 ProfilePreviewAction::SwitchTo => { 98 AccountsViewResponse::SelectAccount(i) 99 } 100 ProfilePreviewAction::RemoveAccount => { 101 AccountsViewResponse::RemoveAccount(i) 102 } 103 }); 104 } 105 } 106 }, 107 ); 108 return_op 109 } 110 111 fn top_section_buttons_widget( 112 ui: &mut egui::Ui, 113 ) -> InnerResponse<Option<AccountsViewResponse>> { 114 ui.allocate_ui_with_layout( 115 Vec2::new(ui.available_size_before_wrap().x, 32.0), 116 Layout::left_to_right(egui::Align::Center), 117 |ui| { 118 if ui.add(add_account_button()).clicked() { 119 Some(AccountsViewResponse::RouteToLogin) 120 } else { 121 None 122 } 123 }, 124 ) 125 } 126 } 127 128 fn show_profile_card( 129 ui: &mut egui::Ui, 130 preview: SimpleProfilePreview, 131 max_size: egui::Vec2, 132 is_selected: bool, 133 card_resp: egui::Response, 134 ) -> Option<ProfilePreviewAction> { 135 let mut op: Option<ProfilePreviewAction> = None; 136 137 ui.add_sized(max_size, |ui: &mut egui::Ui| { 138 let mut frame = Frame::none(); 139 if is_selected || card_resp.hovered() { 140 frame = frame.fill(ui.visuals().noninteractive().weak_bg_fill); 141 } 142 if is_selected { 143 frame = frame.stroke(ui.visuals().noninteractive().fg_stroke); 144 } 145 frame 146 .rounding(8.0) 147 .inner_margin(8.0) 148 .show(ui, |ui| { 149 ui.horizontal(|ui| { 150 ui.add(preview); 151 152 ui.with_layout(Layout::right_to_left(Align::Center), |ui| { 153 if card_resp.clicked() { 154 op = Some(ProfilePreviewAction::SwitchTo); 155 } 156 if ui 157 .add_sized(egui::Vec2::new(84.0, 32.0), sign_out_button()) 158 .clicked() 159 { 160 op = Some(ProfilePreviewAction::RemoveAccount) 161 } 162 }); 163 }); 164 }) 165 .response 166 }); 167 ui.add_space(8.0); 168 op 169 } 170 171 fn scroll_area() -> ScrollArea { 172 egui::ScrollArea::vertical() 173 .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) 174 .auto_shrink([false; 2]) 175 } 176 177 fn add_account_button() -> Button<'static> { 178 let img_data = egui::include_image!("../../../../assets/icons/add_account_icon_4x.png"); 179 let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0)); 180 Button::image_and_text( 181 img, 182 RichText::new(" Add account") 183 .size(16.0) 184 // TODO: this color should not be hard coded. Find some way to add it to the visuals 185 .color(PINK), 186 ) 187 .frame(false) 188 } 189 190 fn sign_out_button() -> egui::Button<'static> { 191 egui::Button::new(RichText::new("Sign out")) 192 }