notedeck

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

accounts.rs (7221B)


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