notedeck

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

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 }