diff --git a/Cargo.toml b/Cargo.toml index 2f645e0..9e73892 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,5 @@ chrono = "0.4" serde_json = "1.0" rand = "0.8" pwbox = "0.5" -directories = "4.0" \ No newline at end of file +directories = "4.0" +native-dialog = "0.6" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3fd6e49..eaf1ae3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use directories::ProjectDirs; use eframe::{CreationContext, Frame}; use egui::{Context, RichText, TextEdit, Widget}; use log::info; +use native_dialog::MessageDialog; use simple_logger::SimpleLogger; use crate::notes::{Note, NoteMetadata}; use crate::saving::Saving; @@ -100,7 +101,17 @@ impl eframe::App for App { if !self.tested_password && self.password.is_some() { if let Some(note) = self.notes.get(0) { - note.decrypt(self.password.as_ref().unwrap()).unwrap(); + if note.decrypt(self.password.as_ref().unwrap()).is_err() { + let _ = MessageDialog::new() + .set_title("invalid password") + .set_text("failed to verify the password against an existing note. please try again") + .show_alert(); + + self.password = None; + self.tested_password = false; + return; + } + self.tested_password = true; } } diff --git a/src/notes/mod.rs b/src/notes/mod.rs index 83607c8..adbc0d1 100644 --- a/src/notes/mod.rs +++ b/src/notes/mod.rs @@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize}; use std::collections::HashMap; use std::time::SystemTime; use chrono::{DateTime, Utc}; -use egui::{RichText, Ui}; +use egui::{RichText, Ui, WidgetText}; use pwbox::sodium::Sodium; use pwbox::{ErasedPwBox, Eraser, Suite}; @@ -25,6 +25,12 @@ pub enum Note { title: String, metadata: NoteMetadata, text: String, + }, + Hidden { + id: String, + title: String, + metadata: NoteMetadata, + text: String, } } @@ -73,20 +79,64 @@ impl Note { } } + pub fn hide(self) -> Self { + match self { + Note::Decrypted { id, title, metadata, text } => { + Self::Hidden { id, title, metadata, text } + } + _ => unreachable!() + } + } + + pub fn unhide(self) -> Self { + match self { + Note::Hidden { id, title, metadata, text } => { + Self::Decrypted { id, title, metadata, text } + } + _ => unreachable!() + } + } + pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option { let mut value = None; ui.group(|ui| { match self { Note::Encrypted { id, title, metadata, .. } => { - render_title_and_metadata(ui, title, metadata, false, (id.clone(), Box::new(cb))); + let result = render_title_and_metadata(ui, title, metadata, false, None); + if let Some(action) = result { + match action { + RenderResult::DeleteNote => cb(id.to_string()), + _ => unreachable!() + } + } if ui.button("decrypt note").clicked() { value = Some(self.decrypt(password).unwrap()); } } Note::Decrypted { id, title, metadata, text, .. } => { - render_title_and_metadata(ui, title, metadata, true, (id.clone(), Box::new(cb))); + let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Hide)); + if let Some(action) = result { + match action { + RenderResult::DeleteNote => cb(id.to_string()), + RenderResult::HideNote => value = Some(self.clone().hide()), + _ => unreachable!() + } + } + ui.label(text); } + Note::Hidden { id, title, metadata, .. } => { + let result = render_title_and_metadata(ui, title, metadata, true, Some(ButtonType::Unhide)); + if let Some(action) = result { + match action { + RenderResult::DeleteNote => cb(id.to_string()), + RenderResult::UnhideNote => value = Some(self.clone().unhide()), + _ => unreachable!() + } + } + + ui.label(WidgetText::from("note is decrypted, but hidden").italics()); + } }; }); @@ -97,11 +147,24 @@ impl Note { match self { Note::Encrypted { id, .. } => id.clone(), Note::Decrypted { id, .. } => id.clone(), + Note::Hidden { id, .. } => id.clone(), } } } -fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box)) { +enum RenderResult { + DeleteNote, + HideNote, + UnhideNote, +} + +enum ButtonType { + Hide, + Unhide, +} + +fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &NoteMetadata, show_delete_button: bool, show_hide_button: Option) -> Option { + let mut result = None; ui.horizontal(|ui| { ui.label(RichText::new(title).size(14.0)); @@ -117,13 +180,26 @@ fn render_title_and_metadata(ui: &mut Ui, title: impl Into, metadata: &N } ui.separator(); - if show_delete_button { - let (id, cb) = delete_info; - if ui.button("delete note").clicked() { - cb(id); + if show_delete_button && ui.button("delete note").clicked() { + result = Some(RenderResult::DeleteNote); + } + + if let Some(button_type) = show_hide_button { + match button_type { + ButtonType::Hide => { + if ui.button("hide note").clicked() { + result = Some(RenderResult::HideNote) + } + } + ButtonType::Unhide => { + if ui.button("show note").clicked() { + result = Some(RenderResult::UnhideNote) + } + } } } }); ui.separator(); + result } \ No newline at end of file