notedeck

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

account_login_view.rs (12291B)


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