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