notedeck

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

account_login_view.rs (6976B)


      1 use crate::login_manager::AcquireKeyState;
      2 use crate::ui::onboarding::FollowPacksResponse;
      3 use crate::ui::{Preview, PreviewConfig};
      4 use egui::{
      5     Align, Button, Color32, Frame, InnerResponse, Layout, Margin, RichText, TextEdit, Vec2,
      6 };
      7 use egui_winit::clipboard::Clipboard;
      8 use enostr::Keypair;
      9 use notedeck::{fonts::get_font_size, tr, Localization, NotedeckTextStyle};
     10 use notedeck_ui::{
     11     app_images,
     12     context_menu::{input_context, PasteBehavior},
     13 };
     14 
     15 pub struct AccountLoginView<'a> {
     16     manager: &'a mut AcquireKeyState,
     17     clipboard: &'a mut Clipboard,
     18     i18n: &'a mut Localization,
     19 }
     20 
     21 pub enum AccountLoginResponse {
     22     CreatingNew,
     23     Onboarding(FollowPacksResponse),
     24     LoginWith(Keypair),
     25 }
     26 
     27 impl<'a> AccountLoginView<'a> {
     28     pub fn new(
     29         manager: &'a mut AcquireKeyState,
     30         clipboard: &'a mut Clipboard,
     31         i18n: &'a mut Localization,
     32     ) -> Self {
     33         AccountLoginView {
     34             manager,
     35             clipboard,
     36             i18n,
     37         }
     38     }
     39 
     40     pub fn ui(&mut self, ui: &mut egui::Ui) -> InnerResponse<Option<AccountLoginResponse>> {
     41         Frame::new().outer_margin(12.0).show(ui, |ui| self.show(ui))
     42     }
     43 
     44     fn show(&mut self, ui: &mut egui::Ui) -> Option<AccountLoginResponse> {
     45         ui.vertical(|ui| {
     46             ui.vertical_centered(|ui| {
     47                 ui.add_space(32.0);
     48                 ui.label(login_title_text(self.i18n));
     49             });
     50 
     51             ui.horizontal(|ui| {
     52                 ui.label(login_textedit_info_text(self.i18n));
     53             });
     54 
     55             ui.vertical_centered_justified(|ui| {
     56                 ui.horizontal(|ui| {
     57                     let available_width = ui.available_width();
     58                     let button_width = 32.0;
     59                     let text_edit_width = available_width - button_width;
     60 
     61                     let textedit_resp = ui.add_sized([text_edit_width, 40.0], login_textedit(self.manager, self.i18n));
     62                     input_context(ui, &textedit_resp, self.clipboard, self.manager.input_buffer(), PasteBehavior::Clear);
     63 
     64                     if eye_button(ui, self.manager.password_visible()).clicked() {
     65                         self.manager.toggle_password_visibility();
     66                     }
     67                 });
     68                 ui.with_layout(Layout::left_to_right(Align::TOP), |ui| {
     69                 let help_text_style = NotedeckTextStyle::Small;
     70                 ui.add(egui::Label::new(
     71                     RichText::new(tr!(self.i18n, "Enter your public key (npub), nostr address (e.g. {address}), or private key (nsec). You must enter your private key to be able to post, reply, etc.", "Instructions for entering Nostr credentials", address="vrod@damus.io"))
     72                         .text_style(help_text_style.text_style())
     73                         .size(get_font_size(ui.ctx(), &help_text_style)).color(ui.visuals().weak_text_color()),
     74                     ).wrap())
     75                 });
     76 
     77                 self.manager.loading_and_error_ui(ui, self.i18n);
     78 
     79                 if ui.add(login_button(self.i18n)).clicked() {
     80                     self.manager.apply_acquire();
     81                 }
     82             });
     83 
     84             ui.horizontal(|ui| {
     85                 ui.label(
     86                     RichText::new(tr!(self.i18n,"New to Nostr?", "Label asking if the user is new to Nostr. Underneath this label is a button to create an account."))
     87                         .color(ui.style().visuals.noninteractive().fg_stroke.color)
     88                         .text_style(NotedeckTextStyle::Body.text_style()),
     89                 );
     90 
     91                 if ui
     92                     .add(Button::new(RichText::new(tr!(self.i18n,"Create Account", "Button to create a new account"))).frame(false))
     93                     .clicked()
     94                 {
     95                     self.manager.should_create_new();
     96                 }
     97             });
     98         });
     99 
    100         if self.manager.check_for_create_new() {
    101             return Some(AccountLoginResponse::CreatingNew);
    102         }
    103 
    104         if let Some(keypair) = self.manager.get_login_keypair() {
    105             return Some(AccountLoginResponse::LoginWith(keypair.clone()));
    106         }
    107         None
    108     }
    109 }
    110 
    111 fn login_title_text(i18n: &mut Localization) -> RichText {
    112     RichText::new(tr!(i18n, "Login", "Login page title"))
    113         .text_style(NotedeckTextStyle::Heading2.text_style())
    114         .strong()
    115 }
    116 
    117 fn login_textedit_info_text(i18n: &mut Localization) -> RichText {
    118     RichText::new(tr!(i18n, "Enter your key", "Label for key input field. Key can be public key (npub), private key (nsec), or Nostr address (NIP-05)."))
    119         .strong()
    120         .text_style(NotedeckTextStyle::Body.text_style())
    121 }
    122 
    123 fn login_button(i18n: &mut Localization) -> Button<'static> {
    124     Button::new(
    125         RichText::new(tr!(i18n, "Login now — let's do this!", "Login button text"))
    126             .text_style(NotedeckTextStyle::Body.text_style())
    127             .strong(),
    128     )
    129     .fill(Color32::from_rgb(0xF8, 0x69, 0xB6)) // TODO: gradient
    130     .min_size(Vec2::new(0.0, 40.0))
    131 }
    132 
    133 fn login_textedit<'a>(
    134     manager: &'a mut AcquireKeyState,
    135     i18n: &'a mut Localization,
    136 ) -> TextEdit<'a> {
    137     let create_textedit = |text| {
    138         egui::TextEdit::singleline(text)
    139             .hint_text(
    140                 RichText::new(tr!(
    141                     i18n,
    142                     "Your key here...",
    143                     "Placeholder text for key input field"
    144                 ))
    145                 .text_style(NotedeckTextStyle::Body.text_style()),
    146             )
    147             .vertical_align(Align::Center)
    148             .min_size(Vec2::new(0.0, 40.0))
    149             .margin(Margin::same(12))
    150     };
    151 
    152     let is_visible = manager.password_visible();
    153     let mut text_edit = manager.get_acquire_textedit(create_textedit);
    154     if !is_visible {
    155         text_edit = text_edit.password(true);
    156     }
    157     text_edit
    158 }
    159 
    160 pub fn eye_button(ui: &mut egui::Ui, is_visible: bool) -> egui::Response {
    161     let is_dark_mode = ui.visuals().dark_mode;
    162     let icon = if is_visible && is_dark_mode {
    163         app_images::eye_dark_image()
    164     } else if is_visible {
    165         app_images::eye_light_image()
    166     } else if is_dark_mode {
    167         app_images::eye_slash_dark_image()
    168     } else {
    169         app_images::eye_slash_light_image()
    170     };
    171     ui.add(Button::image(icon).frame(false))
    172 }
    173 
    174 mod preview {
    175     use super::*;
    176     use notedeck::{App, AppContext, AppResponse};
    177 
    178     pub struct AccountLoginPreview {
    179         manager: AcquireKeyState,
    180     }
    181 
    182     impl App for AccountLoginPreview {
    183         fn update(&mut self, ctx: &mut AppContext<'_>, ui: &mut egui::Ui) -> AppResponse {
    184             AccountLoginView::new(&mut self.manager, ctx.clipboard, ctx.i18n).ui(ui);
    185 
    186             AppResponse::none()
    187         }
    188     }
    189 
    190     impl Preview for AccountLoginView<'_> {
    191         type Prev = AccountLoginPreview;
    192 
    193         fn preview(cfg: PreviewConfig) -> Self::Prev {
    194             let _ = cfg;
    195             let manager = AcquireKeyState::new();
    196             AccountLoginPreview { manager }
    197         }
    198     }
    199 }