notedeck

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

account_login_view.rs (12086B)


      1 use crate::app_style::NotedeckTextStyle;
      2 use crate::key_parsing::{perform_key_retrieval, LoginError};
      3 use crate::login_manager::LoginManager;
      4 use crate::ui;
      5 use crate::ui::{Preview, View};
      6 use egui::{
      7     Align, Align2, Button, Color32, Frame, Id, LayerId, Margin, Pos2, Rect, RichText, Rounding, Ui,
      8     Vec2, Window,
      9 };
     10 use egui::{Image, TextBuffer, TextEdit};
     11 
     12 pub struct AccountLoginView<'a> {
     13     manager: &'a mut LoginManager,
     14     generate_y_intercept: Option<f32>,
     15 }
     16 
     17 impl<'a> View for AccountLoginView<'a> {
     18     fn ui(&mut self, ui: &mut egui::Ui) {
     19         if ui::is_mobile(ui.ctx()) {
     20             self.show_mobile(ui);
     21         } else {
     22             self.show(ui);
     23         }
     24     }
     25 }
     26 
     27 impl<'a> AccountLoginView<'a> {
     28     pub fn new(manager: &'a mut LoginManager) -> Self {
     29         AccountLoginView {
     30             manager,
     31             generate_y_intercept: None,
     32         }
     33     }
     34 
     35     fn show(&mut self, ui: &mut egui::Ui) -> egui::Response {
     36         let screen_width = ui.ctx().screen_rect().max.x;
     37         let screen_height = ui.ctx().screen_rect().max.y;
     38 
     39         let title_layer = LayerId::new(egui::Order::Background, Id::new("Title layer"));
     40 
     41         let mut top_panel_height: Option<f32> = None;
     42         ui.with_layer_id(title_layer, |ui| {
     43             egui::TopBottomPanel::top("Top")
     44                 .resizable(false)
     45                 .default_height(340.0)
     46                 .frame(Frame::none())
     47                 .show_separator_line(false)
     48                 .show_inside(ui, |ui| {
     49                     top_panel_height = Some(ui.available_rect_before_wrap().bottom());
     50                     self.top_title_area(ui);
     51                 });
     52         });
     53 
     54         egui::TopBottomPanel::bottom("Bottom")
     55             .resizable(false)
     56             .frame(Frame::none())
     57             .show_separator_line(false)
     58             .show_inside(ui, |ui| {
     59                 self.window(ui, top_panel_height.unwrap_or(0.0));
     60             });
     61 
     62         let top_rect = Rect {
     63             min: Pos2::ZERO,
     64             max: Pos2::new(
     65                 screen_width,
     66                 self.generate_y_intercept.unwrap_or(screen_height * 0.5),
     67             ),
     68         };
     69 
     70         let top_background_color = ui.visuals().noninteractive().bg_fill;
     71         ui.painter_at(top_rect)
     72             .with_layer_id(LayerId::background())
     73             .rect_filled(top_rect, Rounding::ZERO, top_background_color);
     74 
     75         egui::CentralPanel::default()
     76             .show(ui.ctx(), |_ui: &mut egui::Ui| {})
     77             .response
     78     }
     79 
     80     fn mobile_ui(&mut self, ui: &mut egui::Ui) -> egui::Response {
     81         ui.vertical(|ui| {
     82             ui.vertical_centered(|ui| {
     83                 ui.add(logo_unformatted().max_width(256.0));
     84                 ui.add_space(64.0);
     85                 ui.label(login_info_text());
     86                 ui.add_space(32.0);
     87                 ui.label(login_title_text());
     88             });
     89 
     90             ui.horizontal(|ui| {
     91                 ui.label(login_textedit_info_text());
     92             });
     93 
     94             ui.vertical_centered_justified(|ui| {
     95                 ui.add(login_textedit(&mut self.manager.login_key));
     96 
     97                 if ui.add(login_button()).clicked() {
     98                     self.manager.promise = Some(perform_key_retrieval(&self.manager.login_key));
     99                 }
    100             });
    101 
    102             ui.horizontal(|ui| {
    103                 ui.label(
    104                     RichText::new("New to Nostr?")
    105                         .color(ui.style().visuals.noninteractive().fg_stroke.color)
    106                         .text_style(NotedeckTextStyle::Body.text_style()),
    107                 );
    108 
    109                 if ui
    110                     .add(Button::new(RichText::new("Create Account")).frame(false))
    111                     .clicked()
    112                 {
    113                     // TODO: navigate to 'create account' screen
    114                 }
    115             });
    116         })
    117         .response
    118     }
    119 
    120     pub fn show_mobile(&mut self, ui: &mut egui::Ui) -> egui::Response {
    121         egui::CentralPanel::default()
    122             .show(ui.ctx(), |_| {
    123                 Window::new("Login")
    124                     .movable(true)
    125                     .constrain(true)
    126                     .collapsible(false)
    127                     .drag_to_scroll(false)
    128                     .title_bar(false)
    129                     .resizable(false)
    130                     .anchor(Align2::CENTER_CENTER, [0.0, 0.0])
    131                     .frame(Frame::central_panel(&ui.ctx().style()))
    132                     .max_width(ui.ctx().screen_rect().width() - 32.0) // margin
    133                     .show(ui.ctx(), |ui| self.mobile_ui(ui));
    134             })
    135             .response
    136     }
    137 
    138     fn window(&mut self, ui: &mut Ui, top_panel_height: f32) {
    139         let needed_height_over_top = (ui.ctx().screen_rect().bottom() / 2.0) - 230.0;
    140         let y_offset = if top_panel_height > needed_height_over_top {
    141             top_panel_height - needed_height_over_top
    142         } else {
    143             0.0
    144         };
    145         Window::new("Account login")
    146             .movable(false)
    147             .constrain(true)
    148             .collapsible(false)
    149             .drag_to_scroll(false)
    150             .title_bar(false)
    151             .resizable(false)
    152             .anchor(Align2::CENTER_CENTER, [0f32, y_offset])
    153             .max_width(538.0)
    154             .frame(egui::Frame::window(ui.style()).inner_margin(Margin::ZERO))
    155             .show(ui.ctx(), |ui| {
    156                 ui.vertical_centered(|ui| {
    157                     ui.add_space(40.0);
    158 
    159                     ui.label(login_title_text());
    160 
    161                     ui.add_space(16f32);
    162 
    163                     ui.label(login_window_info_text(ui));
    164 
    165                     ui.add_space(24.0);
    166 
    167                     Frame::none()
    168                         .outer_margin(Margin::symmetric(48.0, 0.0))
    169                         .show(ui, |ui| {
    170                             self.login_form(ui);
    171                         });
    172 
    173                     ui.add_space(32.0);
    174 
    175                     let y_margin: f32 = 24.0;
    176                     let generate_frame = egui::Frame::default()
    177                         .fill(ui.style().noninteractive().bg_fill) // TODO: gradient
    178                         .rounding(ui.style().visuals.window_rounding)
    179                         .stroke(ui.style().noninteractive().bg_stroke)
    180                         .inner_margin(Margin::symmetric(48.0, y_margin));
    181 
    182                     generate_frame.show(ui, |ui| {
    183                         self.generate_y_intercept =
    184                             Some(ui.available_rect_before_wrap().top() - y_margin);
    185                         self.generate_group(ui);
    186                     });
    187                 });
    188             });
    189     }
    190 
    191     fn top_title_area(&mut self, ui: &mut egui::Ui) {
    192         ui.vertical_centered(|ui| {
    193             ui.add(logo_unformatted().max_width(232.0));
    194 
    195             ui.add_space(48.0);
    196 
    197             let welcome_data = egui::include_image!("../assets/Welcome to Nostrdeck 2x.png");
    198             ui.add(egui::Image::new(welcome_data).max_width(528.0));
    199 
    200             ui.add_space(12.0);
    201 
    202             // ui.label(
    203             //     RichText::new("Welcome to Nostrdeck")
    204             //         .size(48.0)
    205             //         .strong()
    206             //         .line_height(Some(72.0)),
    207             // );
    208             ui.label(login_info_text());
    209         });
    210     }
    211 
    212     fn login_form(&mut self, ui: &mut egui::Ui) {
    213         ui.vertical_centered_justified(|ui| {
    214             ui.horizontal(|ui| {
    215                 ui.label(login_textedit_info_text());
    216             });
    217 
    218             ui.add_space(8f32);
    219 
    220             ui.add(login_textedit(&mut self.manager.login_key).min_size(Vec2::new(440.0, 40.0)));
    221 
    222             ui.add_space(8.0);
    223 
    224             ui.vertical_centered(|ui| {
    225                 if self.manager.promise.is_some() {
    226                     ui.add(egui::Spinner::new());
    227                 }
    228             });
    229 
    230             if let Some(error_key) = &self.manager.key_on_error {
    231                 if self.manager.login_key != *error_key {
    232                     self.manager.error = None;
    233                     self.manager.key_on_error = None;
    234                 }
    235             }
    236             if let Some(err) = &self.manager.error {
    237                 ui.horizontal(|ui| {
    238                     let error_label = match err {
    239                         LoginError::InvalidKey => egui::Label::new(
    240                             RichText::new("Invalid key.").color(ui.visuals().error_fg_color),
    241                         ),
    242                         LoginError::Nip05Failed(e) => {
    243                             egui::Label::new(RichText::new(e).color(ui.visuals().error_fg_color))
    244                         }
    245                     };
    246                     ui.add(error_label.truncate(true));
    247                 });
    248             }
    249 
    250             ui.add_space(8.0);
    251 
    252             let login_button = login_button().min_size(Vec2::new(442.0, 40.0));
    253 
    254             if ui.add(login_button).clicked() {
    255                 self.manager.promise = Some(perform_key_retrieval(&self.manager.login_key));
    256             }
    257         });
    258     }
    259 
    260     fn generate_group(&mut self, ui: &mut egui::Ui) {
    261         ui.horizontal(|ui| {
    262             ui.label(
    263                 RichText::new("New in nostr?").text_style(NotedeckTextStyle::Heading3.text_style()),
    264             );
    265 
    266             ui.label(
    267                 RichText::new(" — we got you!")
    268                     .text_style(NotedeckTextStyle::Heading3.text_style())
    269                     .color(ui.visuals().noninteractive().fg_stroke.color),
    270             );
    271         });
    272 
    273         ui.add_space(6.0);
    274 
    275         ui.horizontal(|ui| {
    276             ui.label(generate_info_text().color(ui.visuals().noninteractive().fg_stroke.color));
    277         });
    278 
    279         ui.add_space(16.0);
    280 
    281         let generate_button = generate_keys_button().min_size(Vec2::new(442.0, 40.0));
    282         if ui.add(generate_button).clicked() {
    283             // TODO: keygen
    284         }
    285     }
    286 }
    287 
    288 fn login_title_text() -> RichText {
    289     RichText::new("Login")
    290         .text_style(NotedeckTextStyle::Heading2.text_style())
    291         .strong()
    292 }
    293 
    294 fn login_info_text() -> RichText {
    295     RichText::new("The best alternative to tweetDeck built in nostr protocol")
    296         .text_style(NotedeckTextStyle::Heading3.text_style())
    297 }
    298 
    299 fn login_window_info_text(ui: &Ui) -> RichText {
    300     RichText::new("Enter your private key to start using Notedeck")
    301         .text_style(NotedeckTextStyle::Body.text_style())
    302         .color(ui.visuals().noninteractive().fg_stroke.color)
    303 }
    304 
    305 fn login_textedit_info_text() -> RichText {
    306     RichText::new("Enter your key")
    307         .strong()
    308         .text_style(NotedeckTextStyle::Body.text_style())
    309 }
    310 
    311 fn logo_unformatted() -> Image<'static> {
    312     let logo_gradient_data = egui::include_image!("../assets/Logo-Gradient-2x.png");
    313     return egui::Image::new(logo_gradient_data);
    314 }
    315 
    316 fn generate_info_text() -> RichText {
    317     RichText::new("Quickly generate your keys. Make sure you save them safely.")
    318         .text_style(NotedeckTextStyle::Body.text_style())
    319 }
    320 
    321 fn generate_keys_button() -> Button<'static> {
    322     Button::new(RichText::new("Generate keys").text_style(NotedeckTextStyle::Body.text_style()))
    323 }
    324 
    325 fn login_button() -> Button<'static> {
    326     Button::new(
    327         RichText::new("Login now — let's do this!")
    328             .text_style(NotedeckTextStyle::Body.text_style())
    329             .strong(),
    330     )
    331     .fill(Color32::from_rgb(0xF8, 0x69, 0xB6)) // TODO: gradient
    332     .min_size(Vec2::new(0.0, 40.0))
    333 }
    334 
    335 fn login_textedit(text: &mut dyn TextBuffer) -> TextEdit {
    336     egui::TextEdit::singleline(text)
    337         .hint_text(
    338             RichText::new("Your key here...").text_style(NotedeckTextStyle::Body.text_style()),
    339         )
    340         .vertical_align(Align::Center)
    341         .min_size(Vec2::new(0.0, 40.0))
    342         .margin(Margin::same(12.0))
    343 }
    344 
    345 pub struct AccountLoginPreview {
    346     manager: LoginManager,
    347 }
    348 
    349 impl View for AccountLoginPreview {
    350     fn ui(&mut self, ui: &mut egui::Ui) {
    351         AccountLoginView::new(&mut self.manager).ui(ui);
    352     }
    353 }
    354 
    355 impl<'a> Preview for AccountLoginView<'a> {
    356     type Prev = AccountLoginPreview;
    357 
    358     fn preview() -> Self::Prev {
    359         let manager = LoginManager::new();
    360         AccountLoginPreview { manager }
    361     }
    362 }