Implement "hide notes" and "show notes", retry on failed password and minor fixes

* implemented "hide notes"
* implemented "show notes" (internally "unhide")
* improved the title and metadata renderer to use enums and return values instead of callbacks
* added system to retry on password fails (adds `native-dialog` dep for convenience)
This commit is contained in:
Sofía Aritz 2023-03-02 22:40:36 +01:00
parent 19fe7a4ef5
commit fcdbfbc932
3 changed files with 98 additions and 10 deletions

View file

@ -14,4 +14,5 @@ chrono = "0.4"
serde_json = "1.0"
rand = "0.8"
pwbox = "0.5"
directories = "4.0"
directories = "4.0"
native-dialog = "0.6"

View file

@ -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;
}
}

View file

@ -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<Self> {
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<String>, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box<impl FnOnce(String)>)) {
enum RenderResult {
DeleteNote,
HideNote,
UnhideNote,
}
enum ButtonType {
Hide,
Unhide,
}
fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &NoteMetadata, show_delete_button: bool, show_hide_button: Option<ButtonType>) -> Option<RenderResult> {
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<String>, 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
}