notedeck

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

commit a927c56870fa898944561b8b75bdee25d038ae81
parent 343d3dc569f6ca9ac7c97ab01027b84d403b71d0
Author: kernelkind <kernelkind@gmail.com>
Date:   Mon,  1 Apr 2024 11:04:48 -0400

Create account login panel

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Aassets/Logo-Gradient-2x.png | 0
Aassets/Welcome to Nostrdeck 2x.png | 0
Asrc/account_login_view.rs | 286+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/colors.rs | 10++++++++++
Msrc/lib.rs | 1+
5 files changed, 297 insertions(+), 0 deletions(-)

diff --git a/assets/Logo-Gradient-2x.png b/assets/Logo-Gradient-2x.png Binary files differ. diff --git a/assets/Welcome to Nostrdeck 2x.png b/assets/Welcome to Nostrdeck 2x.png Binary files differ. diff --git a/src/account_login_view.rs b/src/account_login_view.rs @@ -0,0 +1,286 @@ +use crate::colors::{ + ALMOST_WHITE, DARKER_BG, DARK_BG_1, DARK_ISH_BG, GRAY_SECONDARY, RED_700, SEMI_DARKER_BG, WHITE, +}; +use crate::key_parsing::{perform_key_retrieval, LoginError}; +use crate::login_manager::LoginManager; +use egui::{ + epaint::Shadow, Align, Align2, Button, Color32, Frame, Id, LayerId, Margin, Pos2, Rect, + RichText, Rounding, Ui, Vec2, Window, +}; + +pub struct AccountLoginView<'a> { + ctx: &'a egui::Context, + manager: &'a mut LoginManager, + generate_y_intercept: Option<f32>, +} + +impl<'a> AccountLoginView<'a> { + pub fn new(ctx: &'a egui::Context, manager: &'a mut LoginManager) -> Self { + AccountLoginView { + ctx, + manager, + generate_y_intercept: None, + } + } + pub fn panel(&mut self) { + let frame = egui::CentralPanel::default().frame(Frame { + inner_margin: Margin::same(0.0), + fill: DARKER_BG, + ..Default::default() + }); + + let screen_width = self.ctx.screen_rect().max.x; + let screen_height = self.ctx.screen_rect().max.y; + + frame.show(self.ctx, |ui| { + let title_layer = LayerId::new(egui::Order::Background, Id::new("Title layer")); + + let mut top_panel_height: Option<f32> = None; + ui.with_layer_id(title_layer, |ui| { + egui::TopBottomPanel::top("Top") + .resizable(false) + .default_height(340.0) + .frame(Frame::none()) + .show_separator_line(false) + .show_inside(ui, |ui| { + top_panel_height = Some(ui.available_rect_before_wrap().bottom()); + self.top_title_area(ui); + }); + }); + + egui::TopBottomPanel::bottom("Bottom") + .resizable(false) + .frame(Frame::none()) + .show_separator_line(false) + .show_inside(ui, |ui| { + self.window(ui, top_panel_height.unwrap_or(0.0)); + }); + + let top_rect = Rect { + min: Pos2::ZERO, + max: Pos2::new( + screen_width, + self.generate_y_intercept.unwrap_or(screen_height * 0.5), + ), + }; + + ui.painter_at(top_rect) + .with_layer_id(LayerId::background()) + .rect_filled(top_rect, Rounding::ZERO, DARK_ISH_BG); + }); + } + + fn window(&mut self, ui: &mut Ui, top_panel_height: f32) { + let needed_height_over_top = (self.ctx.screen_rect().bottom() / 2.0) - 230.0; + let y_offset = if top_panel_height > needed_height_over_top { + top_panel_height - needed_height_over_top + } else { + 0.0 + }; + Window::new("Account login") + .movable(false) + .constrain(true) + .collapsible(false) + .drag_to_scroll(false) + .title_bar(false) + .resizable(false) + .anchor(Align2::CENTER_CENTER, [0f32, y_offset]) + .max_width(538.0) + .frame( + egui::Frame::default() + .fill(DARK_ISH_BG) + .rounding(egui::Rounding::from(32f32)) + .stroke(egui::Stroke::new(1.0, DARK_BG_1)) + .shadow(Shadow { + offset: [0.0, 8.0].into(), + blur: 24.0, + spread: 0.0, + color: egui::Color32::from_rgba_unmultiplied(0x6D, 0x6D, 0x6D, 0x14), + }), + ) + .show(ui.ctx(), |ui| { + ui.vertical_centered(|ui| { + ui.add_space(40.0); + ui.label( + RichText::new("Login") + .size(24f32) + .color(WHITE) + .strong() + .line_height(Some(36f32)), + ); + + ui.add_space(16f32); + + ui.label( + RichText::new("Enter your private key to start using Notedeck") + .size(13f32) + .color(GRAY_SECONDARY) + .line_height(Some(19.5)), + ); + + ui.add_space(24.0); + + Frame::none() + .outer_margin(Margin::symmetric(48.0, 0.0)) + .show(ui, |ui| { + self.login_form(ui); + }); + + ui.add_space(32.0); + + let y_margin: f32 = 24.0; + let generate_frame = egui::Frame::default() + .fill(Color32::from_rgb(0x26, 0x26, 0x26)) // TODO: gradient + .rounding(egui::Rounding::from(32f32)) + .stroke(egui::Stroke::new(1.0, DARK_BG_1)) + .inner_margin(Margin::symmetric(48.0, y_margin)); + + generate_frame.show(ui, |ui| { + self.generate_y_intercept = + Some(ui.available_rect_before_wrap().top() - y_margin); + self.generate_group(ui); + }); + }); + }); + } + + fn top_title_area(&mut self, ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + let logo_gradient_data = egui::include_image!("../assets/Logo-Gradient-2x.png"); + ui.add(egui::Image::new(logo_gradient_data).max_width(232.0)); + + ui.add_space(48.0); + + let welcome_data = egui::include_image!("../assets/Welcome to Nostrdeck 2x.png"); + ui.add(egui::Image::new(welcome_data).max_width(528.0)); + + ui.add_space(12.0); + + // ui.label( + // RichText::new("Welcome to Nostrdeck") + // .size(48.0) + // .strong() + // .line_height(Some(72.0)), + // ); + ui.label( + RichText::new("The best alternative to tweetDeck built in nostr protocol") + .size(24.0) + .line_height(Some(36.0)) + .color(ALMOST_WHITE), + ); + }); + } + + fn login_form(&mut self, ui: &mut egui::Ui) { + ui.vertical_centered_justified(|ui| { + ui.horizontal(|ui| { + ui.label( + RichText::new("Enter your key") + .color(WHITE) + .strong() + .line_height(Some(19.5f32)) + .size(13f32), + ); + }); + + ui.add_space(8f32); + + ui.add( + egui::TextEdit::singleline(&mut self.manager.login_key) + .hint_text( + RichText::new("Your key here...") + .size(13f32) + .line_height(Some(19.5f32)) + .color(GRAY_SECONDARY), + ) + .margin(Margin::symmetric(12.0, 12.0)) + .min_size(Vec2::new(440.0, 40.0)) + .vertical_align(Align::Center), + ); + + ui.add_space(8.0); + + ui.vertical_centered(|ui| { + if self.manager.promise.is_some() { + ui.add(egui::Spinner::new()); + } + }); + + if let Some(error_key) = &self.manager.key_on_error { + if self.manager.login_key != *error_key { + self.manager.error = None; + self.manager.key_on_error = None; + } + } + if let Some(err) = &self.manager.error { + ui.horizontal(|ui| { + let error_label = match err { + LoginError::InvalidKey => { + egui::Label::new(RichText::new("Invalid key.").color(RED_700)) + } + LoginError::Nip05Failed(e) => { + egui::Label::new(RichText::new(e).color(RED_700)) + } + }; + ui.add(error_label.truncate(true)); + }); + } + + ui.add_space(8.0); + + let login_button = Button::new( + RichText::new("Login now — let's do this!") + .line_height(Some(16.25f32)) + .strong() + .size(13f32) + .color(WHITE), + ) + .rounding(Rounding::same(8f32)) + .min_size(Vec2::new(442.0, 40.0)) + .fill(Color32::from_rgb(0xF8, 0x69, 0xB6)); // TODO: gradient + + if ui.add(login_button).clicked() { + self.manager.promise = Some(perform_key_retrieval(&self.manager.login_key)); + } + }); + } + + fn generate_group(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.label( + RichText::new("New in nostr?") + .size(20f32) + .line_height(Some(30f32)) + .color(ALMOST_WHITE), + ); + + ui.label( + RichText::new(" — we got you!") + .size(20f32) + .line_height(Some(30f32)) + .color(GRAY_SECONDARY), + ); + }); + + ui.add_space(6.0); + + ui.horizontal(|ui| { + ui.label( + RichText::new("Quickly generate your keys. Make sure you save them safely.") + .color(GRAY_SECONDARY) + .size(13f32) + .line_height(Some(19.5f32)), + ); + }); + + ui.add_space(16.0); + + let generate_button = Button::new(RichText::new("Generate keys").color(WHITE)) + .fill(SEMI_DARKER_BG) + .min_size(Vec2::new(442.0, 40.0)) + .rounding(Rounding::same(8.0)); + if ui.add(generate_button).clicked() { + // TODO: keygen + } + } +} diff --git a/src/colors.rs b/src/colors.rs @@ -2,3 +2,13 @@ use egui::Color32; pub const PURPLE: Color32 = Color32::from_rgb(0xCC, 0x43, 0xC5); pub const DARK_BG: Color32 = egui::Color32::from_rgb(40, 44, 52); +pub const GRAY_SECONDARY: Color32 = Color32::from_rgb(0x8A, 0x8A, 0x8A); +pub const WHITE: Color32 = Color32::from_rgb(0xFF, 0xFF, 0xFF); +pub const ALMOST_WHITE: Color32 = Color32::from_rgb(0xFA, 0xFA, 0xFA); +pub const RED_700: Color32 = Color32::from_rgb(0xC7, 0x37, 0x5A); + +// BACKGROUNDS +pub const SEMI_DARKER_BG: Color32 = Color32::from_rgb(0x39, 0x39, 0x39); +pub const DARKER_BG: Color32 = Color32::from_rgb(0x1E, 0x1E, 0x1E); +pub const DARK_BG_1: Color32 = Color32::from_rgb(0x2C, 0x2C, 0x2C); +pub const DARK_ISH_BG: Color32 = egui::Color32::from_rgb(0x22, 0x22, 0x22); diff --git a/src/lib.rs b/src/lib.rs @@ -20,6 +20,7 @@ mod colors; mod profile; mod key_parsing; mod login_manager; +mod account_login_view; #[cfg(test)] #[macro_use]