account_management.rs (8104B)
1 use crate::colors::PINK; 2 use crate::imgcache::ImageCache; 3 use crate::{ 4 account_manager::AccountManager, 5 route::{Route, Router}, 6 ui::{Preview, PreviewConfig, View}, 7 Damus, 8 }; 9 use egui::{Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, Vec2}; 10 use nostrdb::{Ndb, Transaction}; 11 12 use super::profile::preview::SimpleProfilePreview; 13 14 pub struct AccountsView<'a> { 15 ndb: &'a Ndb, 16 accounts: &'a AccountManager, 17 img_cache: &'a mut ImageCache, 18 } 19 20 #[derive(Clone, Debug)] 21 pub enum AccountsViewResponse { 22 SelectAccount(usize), 23 RemoveAccount(usize), 24 RouteToLogin, 25 } 26 27 #[derive(Debug)] 28 enum ProfilePreviewOp { 29 RemoveAccount, 30 SwitchTo, 31 } 32 33 impl<'a> AccountsView<'a> { 34 pub fn new(ndb: &'a Ndb, accounts: &'a AccountManager, img_cache: &'a mut ImageCache) -> Self { 35 AccountsView { 36 ndb, 37 accounts, 38 img_cache, 39 } 40 } 41 42 pub fn ui(&mut self, ui: &mut Ui) -> InnerResponse<Option<AccountsViewResponse>> { 43 Frame::none().outer_margin(12.0).show(ui, |ui| { 44 if let Some(resp) = Self::top_section_buttons_widget(ui).inner { 45 return Some(resp); 46 } 47 48 ui.add_space(8.0); 49 scroll_area() 50 .show(ui, |ui| { 51 Self::show_accounts(ui, self.accounts, self.ndb, self.img_cache) 52 }) 53 .inner 54 }) 55 } 56 57 fn show_accounts( 58 ui: &mut Ui, 59 account_manager: &AccountManager, 60 ndb: &Ndb, 61 img_cache: &mut ImageCache, 62 ) -> Option<AccountsViewResponse> { 63 let mut return_op: Option<AccountsViewResponse> = None; 64 ui.allocate_ui_with_layout( 65 Vec2::new(ui.available_size_before_wrap().x, 32.0), 66 Layout::top_down(egui::Align::Min), 67 |ui| { 68 let txn = if let Ok(txn) = Transaction::new(ndb) { 69 txn 70 } else { 71 return; 72 }; 73 74 for i in 0..account_manager.num_accounts() { 75 let account_pubkey = account_manager 76 .get_account(i) 77 .map(|account| account.pubkey.bytes()); 78 79 let account_pubkey = if let Some(pubkey) = account_pubkey { 80 pubkey 81 } else { 82 continue; 83 }; 84 85 let profile = ndb.get_profile_by_pubkey(&txn, account_pubkey).ok(); 86 let is_selected = 87 if let Some(selected) = account_manager.get_selected_account_index() { 88 i == selected 89 } else { 90 false 91 }; 92 93 let profile_peview_view = { 94 let width = ui.available_width(); 95 let preview = SimpleProfilePreview::new(profile.as_ref(), img_cache); 96 show_profile_card(ui, preview, width, is_selected) 97 }; 98 99 if let Some(op) = profile_peview_view { 100 return_op = Some(match op { 101 ProfilePreviewOp::SwitchTo => AccountsViewResponse::SelectAccount(i), 102 ProfilePreviewOp::RemoveAccount => { 103 AccountsViewResponse::RemoveAccount(i) 104 } 105 }); 106 } 107 } 108 }, 109 ); 110 return_op 111 } 112 113 fn top_section_buttons_widget( 114 ui: &mut egui::Ui, 115 ) -> InnerResponse<Option<AccountsViewResponse>> { 116 ui.allocate_ui_with_layout( 117 Vec2::new(ui.available_size_before_wrap().x, 32.0), 118 Layout::left_to_right(egui::Align::Center), 119 |ui| { 120 if ui.add(add_account_button()).clicked() { 121 Some(AccountsViewResponse::RouteToLogin) 122 } else { 123 None 124 } 125 }, 126 ) 127 } 128 } 129 130 fn show_profile_card( 131 ui: &mut egui::Ui, 132 preview: SimpleProfilePreview, 133 width: f32, 134 is_selected: bool, 135 ) -> Option<ProfilePreviewOp> { 136 let mut op: Option<ProfilePreviewOp> = None; 137 138 ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| { 139 Frame::none() 140 .show(ui, |ui| { 141 ui.horizontal(|ui| { 142 ui.add(preview); 143 144 ui.with_layout(Layout::right_to_left(Align::Center), |ui| { 145 if is_selected { 146 ui.add(selected_widget()); 147 } else { 148 if ui 149 .add(switch_button(ui.style().visuals.dark_mode)) 150 .clicked() 151 { 152 op = Some(ProfilePreviewOp::SwitchTo); 153 } 154 if ui.add(sign_out_button(ui)).clicked() { 155 op = Some(ProfilePreviewOp::RemoveAccount) 156 } 157 } 158 }); 159 }); 160 }) 161 .response 162 }); 163 ui.add_space(16.0); 164 op 165 } 166 167 fn scroll_area() -> ScrollArea { 168 egui::ScrollArea::vertical() 169 .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) 170 .auto_shrink([false; 2]) 171 } 172 173 fn add_account_button() -> Button<'static> { 174 let img_data = egui::include_image!("../../assets/icons/add_account_icon_4x.png"); 175 let img = Image::new(img_data).fit_to_exact_size(Vec2::new(48.0, 48.0)); 176 Button::image_and_text( 177 img, 178 RichText::new(" Add account") 179 .size(16.0) 180 // TODO: this color should not be hard coded. Find some way to add it to the visuals 181 .color(PINK), 182 ) 183 .frame(false) 184 } 185 186 fn sign_out_button(ui: &egui::Ui) -> egui::Button<'static> { 187 let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png"); 188 let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); 189 190 egui::Button::image_and_text( 191 img, 192 RichText::new("Sign out").color(ui.visuals().noninteractive().fg_stroke.color), 193 ) 194 .frame(false) 195 } 196 197 fn switch_button(dark_mode: bool) -> egui::Button<'static> { 198 let _ = dark_mode; 199 200 egui::Button::new("Switch").min_size(Vec2::new(76.0, 32.0)) 201 } 202 203 fn selected_widget() -> impl egui::Widget { 204 |ui: &mut egui::Ui| { 205 Frame::none() 206 .show(ui, |ui| { 207 ui.label(RichText::new("Selected").size(13.0).color(PINK)); 208 let img_data = egui::include_image!("../../assets/icons/select_icon_3x.png"); 209 let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0)); 210 ui.add(img); 211 }) 212 .response 213 } 214 } 215 216 // PREVIEWS 217 mod preview { 218 219 use super::*; 220 use crate::{account_manager::process_accounts_view_response, test_data}; 221 222 pub struct AccountsPreview { 223 app: Damus, 224 router: Router<Route>, 225 } 226 227 impl AccountsPreview { 228 fn new() -> Self { 229 let app = test_data::test_app(); 230 let router = Router::new(vec![Route::accounts()]); 231 232 AccountsPreview { app, router } 233 } 234 } 235 236 impl View for AccountsPreview { 237 fn ui(&mut self, ui: &mut egui::Ui) { 238 ui.add_space(24.0); 239 // TODO(jb55): maybe just use render_nav here so we can step through routes 240 if let Some(response) = 241 AccountsView::new(&self.app.ndb, &self.app.accounts, &mut self.app.img_cache) 242 .ui(ui) 243 .inner 244 { 245 process_accounts_view_response(self.app.accounts_mut(), response, &mut self.router); 246 } 247 } 248 } 249 250 impl<'a> Preview for AccountsView<'a> { 251 type Prev = AccountsPreview; 252 253 fn preview(_cfg: PreviewConfig) -> Self::Prev { 254 AccountsPreview::new() 255 } 256 } 257 }