account_switcher.rs (8826B)
1 use crate::{ 2 account_manager::UserAccount, colors::PINK, profile::DisplayName, route::Route, 3 ui::profile_preview_controller, Damus, Result, 4 }; 5 6 use nostrdb::Ndb; 7 8 use egui::{ 9 Align, Button, Color32, Frame, Id, Image, Layout, Margin, RichText, Rounding, ScrollArea, 10 Sense, Vec2, 11 }; 12 13 use super::profile::preview::SimpleProfilePreview; 14 15 pub struct AccountSelectionWidget {} 16 17 enum AccountSelectAction { 18 RemoveAccount { _index: usize }, 19 SelectAccount { _index: usize }, 20 OpenAccountManagement, 21 } 22 23 #[derive(Default)] 24 struct AccountSelectResponse { 25 action: Option<AccountSelectAction>, 26 } 27 28 impl AccountSelectionWidget { 29 pub fn ui(app: &mut Damus, ui: &mut egui::Ui) { 30 if !app.show_account_switcher { 31 return; 32 } 33 34 if app.is_mobile() { 35 Self::show_mobile(ui); 36 } else { 37 account_switcher_window(&mut app.show_account_switcher.clone()).show( 38 ui.ctx(), 39 |ui: &mut egui::Ui| { 40 let (account_selection_response, response) = Self::show(app, ui); 41 if let Some(action) = account_selection_response.action { 42 Self::perform_action(app, action); 43 } 44 response 45 }, 46 ); 47 } 48 } 49 50 fn perform_action(app: &mut Damus, action: AccountSelectAction) { 51 match action { 52 AccountSelectAction::RemoveAccount { _index } => { 53 app.account_manager.remove_account(_index) 54 } 55 AccountSelectAction::SelectAccount { _index } => { 56 app.show_account_switcher = false; 57 app.account_manager.select_account(_index); 58 } 59 AccountSelectAction::OpenAccountManagement => { 60 app.show_account_switcher = false; 61 app.global_nav.push(Route::ManageAccount); 62 app.show_global_popup = true; 63 } 64 } 65 } 66 67 fn show(app: &mut Damus, ui: &mut egui::Ui) -> (AccountSelectResponse, egui::Response) { 68 let mut res = AccountSelectResponse::default(); 69 let mut selected_index = app.account_manager.get_selected_account_index(); 70 71 let response = Frame::none() 72 .outer_margin(8.0) 73 .show(ui, |ui| { 74 res = top_section_widget(ui); 75 76 scroll_area().show(ui, |ui| { 77 if let Some(_index) = Self::show_accounts(app, ui) { 78 selected_index = Some(_index); 79 res.action = Some(AccountSelectAction::SelectAccount { _index }); 80 } 81 }); 82 ui.add_space(8.0); 83 ui.add(add_account_button()); 84 85 if let Some(_index) = selected_index { 86 if let Some(account) = app.account_manager.get_account(_index) { 87 ui.add_space(8.0); 88 if Self::handle_sign_out(&app.ndb, ui, account) { 89 res.action = Some(AccountSelectAction::RemoveAccount { _index }) 90 } 91 } 92 } 93 94 ui.add_space(8.0); 95 }) 96 .response; 97 98 (res, response) 99 } 100 101 fn handle_sign_out(ndb: &Ndb, ui: &mut egui::Ui, account: &UserAccount) -> bool { 102 if let Ok(response) = Self::sign_out_button(ndb, ui, account) { 103 response.clicked() 104 } else { 105 false 106 } 107 } 108 109 fn show_mobile(ui: &mut egui::Ui) -> egui::Response { 110 let _ = ui; 111 todo!() 112 } 113 114 fn show_accounts(app: &mut Damus, ui: &mut egui::Ui) -> Option<usize> { 115 profile_preview_controller::view_profile_previews(app, ui, account_switcher_card_ui) 116 } 117 118 fn sign_out_button( 119 ndb: &Ndb, 120 ui: &mut egui::Ui, 121 account: &UserAccount, 122 ) -> Result<egui::Response> { 123 profile_preview_controller::show_with_nickname( 124 ndb, 125 ui, 126 account.pubkey.bytes(), 127 |ui: &mut egui::Ui, username: &DisplayName| { 128 let img_data = egui::include_image!("../../assets/icons/signout_icon_4x.png"); 129 let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); 130 let button = egui::Button::image_and_text( 131 img, 132 RichText::new(format!(" Sign out @{}", username.username())) 133 .color(PINK) 134 .size(16.0), 135 ) 136 .frame(false); 137 138 ui.add(button) 139 }, 140 ) 141 } 142 } 143 144 fn account_switcher_card_ui( 145 ui: &mut egui::Ui, 146 preview: SimpleProfilePreview, 147 width: f32, 148 is_selected: bool, 149 index: usize, 150 ) -> bool { 151 let resp = ui.add_sized(Vec2::new(width, 50.0), |ui: &mut egui::Ui| { 152 Frame::none() 153 .show(ui, |ui| { 154 ui.add_space(8.0); 155 ui.horizontal(|ui| { 156 if is_selected { 157 Frame::none() 158 .rounding(Rounding::same(8.0)) 159 .inner_margin(Margin::same(8.0)) 160 .fill(Color32::from_rgb(0x45, 0x1B, 0x59)) 161 .show(ui, |ui| { 162 ui.add(preview); 163 ui.with_layout(Layout::right_to_left(Align::Center), |ui| { 164 ui.add(selection_widget()); 165 }); 166 }); 167 } else { 168 ui.add_space(8.0); 169 ui.add(preview); 170 } 171 }); 172 }) 173 .response 174 }); 175 176 ui.interact(resp.rect, Id::new(index), Sense::click()) 177 .clicked() 178 } 179 180 fn account_switcher_window(open: &'_ mut bool) -> egui::Window<'_> { 181 egui::Window::new("account switcher") 182 .title_bar(false) 183 .collapsible(false) 184 .anchor(egui::Align2::LEFT_BOTTOM, Vec2::new(4.0, -44.0)) 185 .fixed_size(Vec2::new(360.0, 406.0)) 186 .open(open) 187 .movable(false) 188 } 189 190 fn selection_widget() -> impl egui::Widget { 191 |ui: &mut egui::Ui| { 192 let img_data: egui::ImageSource = 193 egui::include_image!("../../assets/icons/select_icon_3x.png"); 194 let img = Image::new(img_data).max_size(Vec2::new(16.0, 16.0)); 195 ui.add(img) 196 } 197 } 198 199 fn top_section_widget(ui: &mut egui::Ui) -> AccountSelectResponse { 200 ui.horizontal(|ui| { 201 let mut resp = AccountSelectResponse::default(); 202 203 ui.allocate_ui_with_layout( 204 Vec2::new(ui.available_size_before_wrap().x, 32.0), 205 Layout::left_to_right(egui::Align::Center), 206 |ui| ui.add(account_switcher_title()), 207 ); 208 209 ui.allocate_ui_with_layout( 210 Vec2::new(ui.available_size_before_wrap().x, 32.0), 211 Layout::right_to_left(egui::Align::Center), 212 |ui| { 213 if ui.add(manage_accounts_button()).clicked() { 214 resp.action = Some(AccountSelectAction::OpenAccountManagement); 215 } 216 }, 217 ); 218 219 resp 220 }) 221 .inner 222 } 223 224 fn manage_accounts_button() -> egui::Button<'static> { 225 Button::new(RichText::new("Manage").color(PINK).size(16.0)).frame(false) 226 } 227 228 fn account_switcher_title() -> impl egui::Widget { 229 |ui: &mut egui::Ui| ui.label(RichText::new("Account switcher").size(20.0).strong()) 230 } 231 232 fn scroll_area() -> ScrollArea { 233 egui::ScrollArea::vertical() 234 .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) 235 .auto_shrink([false; 2]) 236 } 237 238 fn add_account_button() -> egui::Button<'static> { 239 let img_data = egui::include_image!("../../assets/icons/plus_icon_4x.png"); 240 let img = Image::new(img_data).fit_to_exact_size(Vec2::new(16.0, 16.0)); 241 Button::image_and_text(img, RichText::new(" Add account").size(16.0).color(PINK)).frame(false) 242 } 243 244 mod previews { 245 246 use crate::{ 247 test_data, 248 ui::{Preview, PreviewConfig, View}, 249 Damus, 250 }; 251 252 use super::AccountSelectionWidget; 253 254 pub struct AccountSelectionPreview { 255 app: Damus, 256 } 257 258 impl AccountSelectionPreview { 259 fn new(is_mobile: bool) -> Self { 260 let app = test_data::test_app(is_mobile); 261 AccountSelectionPreview { app } 262 } 263 } 264 265 impl View for AccountSelectionPreview { 266 fn ui(&mut self, ui: &mut egui::Ui) { 267 AccountSelectionWidget::ui(&mut self.app, ui); 268 } 269 } 270 271 impl Preview for AccountSelectionWidget { 272 type Prev = AccountSelectionPreview; 273 274 fn preview(cfg: PreviewConfig) -> Self::Prev { 275 AccountSelectionPreview::new(cfg.is_mobile) 276 } 277 } 278 }