diff --git a/src/random_password.rs b/src/app.rs similarity index 79% rename from src/random_password.rs rename to src/app.rs index bb0b07e..afee73b 100644 --- a/src/random_password.rs +++ b/src/app.rs @@ -1,36 +1,40 @@ use rand::{distributions::Uniform, thread_rng, Rng}; -use crate::constants::SPECIAL_CHARACTERS; - -#[derive(Copy, Clone)] -pub struct RandomPasswordOptions { - pub length: u8, - pub include_lowercase: bool, - pub include_uppercase: bool, - pub include_numbers: bool, - pub include_special_characters: bool, -} +use crate::{ + constants::SPECIAL_CHARACTERS, + types::{PasswordGeneratorOptions, RandomPassword}, +}; -impl Default for RandomPasswordOptions { +impl Default for PasswordGeneratorOptions { fn default() -> Self { Self { - length: 10, include_lowercase: true, - include_uppercase: true, include_numbers: true, include_special_characters: true, + include_uppercase: true, + length: 10, } } } -pub struct RandomPassword { - pub characters: Vec, +pub struct PasswordGenerator { + pub options: PasswordGeneratorOptions, pub password: String, - pub range: Uniform, + pub recent_passwords: Vec, +} + +impl Default for PasswordGenerator { + fn default() -> Self { + Self { + options: PasswordGeneratorOptions::default(), + password: RandomPassword::new(PasswordGeneratorOptions::default()).password, + recent_passwords: Vec::with_capacity(10), + } + } } impl RandomPassword { - pub fn new(options: RandomPasswordOptions) -> Self { + pub fn new(options: PasswordGeneratorOptions) -> Self { let mut password = String::new(); let mut characters: Vec = Vec::with_capacity(86); @@ -72,11 +76,11 @@ impl RandomPassword { #[cfg(test)] mod tests { - use super::{RandomPassword, RandomPasswordOptions}; + use super::{PasswordGeneratorOptions, RandomPassword}; #[test] fn it_generates_lowercase_only_password() { - let options = RandomPasswordOptions { + let options = PasswordGeneratorOptions { include_lowercase: true, include_uppercase: false, include_numbers: false, @@ -93,7 +97,7 @@ mod tests { #[test] fn it_generates_uppercase_only_password() { - let options = RandomPasswordOptions { + let options = PasswordGeneratorOptions { include_lowercase: false, include_uppercase: true, include_numbers: false, @@ -110,7 +114,7 @@ mod tests { #[test] fn it_generates_numbers_only_password() { - let options = RandomPasswordOptions { + let options = PasswordGeneratorOptions { include_lowercase: false, include_uppercase: false, include_numbers: true, @@ -127,7 +131,7 @@ mod tests { #[test] fn it_generates_special_characters_only_password() { - let options = RandomPasswordOptions { + let options = PasswordGeneratorOptions { include_lowercase: false, include_uppercase: false, include_numbers: false, @@ -144,7 +148,7 @@ mod tests { #[test] fn it_generates_lower_upper_numbers_special_characters_password() { - let options = RandomPasswordOptions::default(); + let options = PasswordGeneratorOptions::default(); let random_password = RandomPassword::new(options); @@ -157,9 +161,9 @@ mod tests { #[test] fn it_generates_random_password_with_length() { - let options = RandomPasswordOptions { + let options = PasswordGeneratorOptions { length: 30, - ..RandomPasswordOptions::default() + ..PasswordGeneratorOptions::default() }; let random_password = RandomPassword::new(options); diff --git a/src/constants.rs b/src/constants.rs index 1487064..324a833 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,2 +1,9 @@ +/// Application id and name +pub const APP_ID: &str = "password-generator"; +pub const APP_NAME: &str = "Password Generator"; + +/// Maximum password length pub const MAX_PASSWORD_LENGTH: u8 = 50; + +/// Special characters for random password generation pub const SPECIAL_CHARACTERS: [char; 14] = ['!', '@', '#', '$', '%', '&', '^', '&', '(', ')', '[', ']', '-', '+']; diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..22997e8 --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,91 @@ +use copypasta::{ClipboardContext, ClipboardProvider}; +use eframe::{ + egui::{CentralPanel, Context, RichText, Slider}, + App, Frame, +}; +use serde_json::{from_str, json}; +use std::time::Duration; + +use crate::{app::PasswordGenerator, constants::MAX_PASSWORD_LENGTH, types::RandomPassword}; + +impl App for PasswordGenerator { + fn auto_save_interval(&self) -> Duration { + Duration::from_secs(5) + } + + fn save(&mut self, storage: &mut dyn eframe::Storage) { + let mut passwords_to_add: Vec = Vec::with_capacity(10); + + passwords_to_add.push(self.password.clone()); + + let mut recent_passwords = match storage.get_string("recent_passwords") { + Some(passwords) => { + let passwords: Vec<_> = from_str(passwords.as_str()).unwrap_or_default(); + + passwords_to_add.extend(passwords); + + passwords_to_add + } + None => passwords_to_add, + }; + + recent_passwords.sort(); + + recent_passwords.dedup(); + + storage.set_string("recent_passwords", json!(recent_passwords).to_string()); + } + + fn update(&mut self, ctx: &Context, _frame: &mut Frame) { + CentralPanel::default().show(ctx, |ui| { + ui.label(RichText::new(self.password.to_string()).size(18.0)); + + ui.add(Slider::new(&mut self.options.length, 1..=MAX_PASSWORD_LENGTH).text("Length")); + + ui.checkbox(&mut self.options.include_lowercase, "Include Lowercase"); + ui.checkbox(&mut self.options.include_uppercase, "Include Uppercase"); + ui.checkbox(&mut self.options.include_numbers, "Include Numbers"); + ui.checkbox( + &mut self.options.include_special_characters, + "Include Special Characters", + ); + + let checkbox_options = [ + self.options.include_lowercase, + self.options.include_uppercase, + self.options.include_numbers, + self.options.include_special_characters, + ]; + + if ui.button(RichText::new("Generate")).clicked() { + if !checkbox_options.iter().any(|&x| x) { + return self.password = String::from("At least one of checkboxes should be checked"); + } + + self.password = RandomPassword::new(self.options).password; + + if self.recent_passwords.len() >= 10 { + self.recent_passwords.pop(); + } + + self.recent_passwords.push(self.password.clone()); + } + + if ui.button(RichText::new("Copy")).clicked() { + let mut context = ClipboardContext::new().unwrap(); + + let _copied = context.set_contents(self.password.to_string()); + } + + ui.separator(); + + ui.heading("Recent Passwords"); + + ui.vertical(|ui| { + for password in self.recent_passwords.iter().rev() { + ui.label(RichText::new(password.to_string()).size(14.0)); + } + }); + }); + } +} diff --git a/src/main.rs b/src/main.rs index a3fdabd..b4733c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,30 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -mod constants; -mod random_password; - -use constants::MAX_PASSWORD_LENGTH; -use random_password::{RandomPassword, RandomPasswordOptions}; +use eframe::{egui::ViewportBuilder, run_native, Error, NativeOptions}; +use serde_json::from_str; -use std::time::Duration; +mod app; +mod constants; +mod gui; +mod types; -use copypasta::{ClipboardContext, ClipboardProvider}; -use eframe::{ - egui::{CentralPanel, Context, RichText, Slider, ViewportBuilder}, - run_native, App, Error, Frame, NativeOptions, +use crate::{ + app::PasswordGenerator, + constants::{APP_ID, APP_NAME}, }; -use serde_json::{from_str, json}; fn main() -> Result<(), Error> { let options = NativeOptions { centered: true, follow_system_theme: true, viewport: ViewportBuilder::default() - .with_app_id("password-generator") + .with_app_id(APP_ID) .with_inner_size([300.0, 200.0]), ..Default::default() }; run_native( - "Password Generator", + APP_NAME, options, Box::new(|creation_context| { let storage = match creation_context.storage { @@ -46,100 +44,3 @@ fn main() -> Result<(), Error> { }), ) } -struct PasswordGenerator { - options: RandomPasswordOptions, - password: String, - recent_passwords: Vec, -} - -impl Default for PasswordGenerator { - fn default() -> Self { - Self { - options: RandomPasswordOptions::default(), - password: RandomPassword::new(RandomPasswordOptions::default()).password, - recent_passwords: Vec::with_capacity(10), - } - } -} - -impl App for PasswordGenerator { - fn save(&mut self, storage: &mut dyn eframe::Storage) { - let mut passwords_to_add: Vec = Vec::with_capacity(10); - - passwords_to_add.push(self.password.clone()); - - let mut recent_passwords = match storage.get_string("recent_passwords") { - Some(passwords) => { - let passwords: Vec<_> = from_str(passwords.as_str()).unwrap_or_default(); - - passwords_to_add.extend(passwords); - - passwords_to_add - } - None => passwords_to_add, - }; - - recent_passwords.sort(); - - recent_passwords.dedup(); - - storage.set_string("recent_passwords", json!(recent_passwords).to_string()); - } - - fn update(&mut self, ctx: &Context, _frame: &mut Frame) { - CentralPanel::default().show(ctx, |ui| { - ui.label(RichText::new(self.password.to_string()).size(18.0)); - - ui.add(Slider::new(&mut self.options.length, 1..=MAX_PASSWORD_LENGTH).text("Length")); - - ui.checkbox(&mut self.options.include_lowercase, "Include Lowercase"); - ui.checkbox(&mut self.options.include_uppercase, "Include Uppercase"); - ui.checkbox(&mut self.options.include_numbers, "Include Numbers"); - ui.checkbox( - &mut self.options.include_special_characters, - "Include Special Characters", - ); - - let checkbox_options = [ - self.options.include_lowercase, - self.options.include_uppercase, - self.options.include_numbers, - self.options.include_special_characters, - ]; - - if ui.button(RichText::new("Generate")).clicked() { - if !checkbox_options.iter().any(|&x| x) { - return self.password = String::from("At least one of checkboxes should be checked"); - } - - self.password = RandomPassword::new(self.options).password; - - if self.recent_passwords.len() >= 10 { - self.recent_passwords.pop(); - } - - self.recent_passwords.push(self.password.clone()); - } - - if ui.button(RichText::new("Copy")).clicked() { - let mut context = ClipboardContext::new().unwrap(); - - let _copied = context.set_contents(self.password.to_string()); - } - - ui.separator(); - - ui.heading("Recent Passwords"); - - ui.vertical(|ui| { - for password in self.recent_passwords.iter().rev() { - ui.label(RichText::new(password.to_string()).size(14.0)); - } - }); - }); - } - - fn auto_save_interval(&self) -> std::time::Duration { - Duration::from_secs(5) - } -} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..59a1791 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,16 @@ +use rand::distributions::Uniform; + +#[derive(Copy, Clone)] +pub struct PasswordGeneratorOptions { + pub include_lowercase: bool, + pub include_numbers: bool, + pub include_special_characters: bool, + pub include_uppercase: bool, + pub length: u8, +} + +pub struct RandomPassword { + pub characters: Vec, + pub password: String, + pub range: Uniform, +}