notedeck

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

accounts.rs (7598B)


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