notedeck

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

accounts.rs (6338B)


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