notedeck

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

accounts.rs (6650B)


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