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:
parent
19fe7a4ef5
commit
fcdbfbc932
3 changed files with 98 additions and 10 deletions
|
@ -14,4 +14,5 @@ chrono = "0.4"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
pwbox = "0.5"
|
pwbox = "0.5"
|
||||||
directories = "4.0"
|
directories = "4.0"
|
||||||
|
native-dialog = "0.6"
|
13
src/main.rs
13
src/main.rs
|
@ -13,6 +13,7 @@ use directories::ProjectDirs;
|
||||||
use eframe::{CreationContext, Frame};
|
use eframe::{CreationContext, Frame};
|
||||||
use egui::{Context, RichText, TextEdit, Widget};
|
use egui::{Context, RichText, TextEdit, Widget};
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use native_dialog::MessageDialog;
|
||||||
use simple_logger::SimpleLogger;
|
use simple_logger::SimpleLogger;
|
||||||
use crate::notes::{Note, NoteMetadata};
|
use crate::notes::{Note, NoteMetadata};
|
||||||
use crate::saving::Saving;
|
use crate::saving::Saving;
|
||||||
|
@ -100,7 +101,17 @@ impl eframe::App for App {
|
||||||
|
|
||||||
if !self.tested_password && self.password.is_some() {
|
if !self.tested_password && self.password.is_some() {
|
||||||
if let Some(note) = self.notes.get(0) {
|
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;
|
self.tested_password = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use egui::{RichText, Ui};
|
use egui::{RichText, Ui, WidgetText};
|
||||||
use pwbox::sodium::Sodium;
|
use pwbox::sodium::Sodium;
|
||||||
use pwbox::{ErasedPwBox, Eraser, Suite};
|
use pwbox::{ErasedPwBox, Eraser, Suite};
|
||||||
|
|
||||||
|
@ -25,6 +25,12 @@ pub enum Note {
|
||||||
title: String,
|
title: String,
|
||||||
metadata: NoteMetadata,
|
metadata: NoteMetadata,
|
||||||
text: String,
|
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> {
|
pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option<Self> {
|
||||||
let mut value = None;
|
let mut value = None;
|
||||||
ui.group(|ui| {
|
ui.group(|ui| {
|
||||||
match self {
|
match self {
|
||||||
Note::Encrypted { id, title, metadata, .. } => {
|
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() {
|
if ui.button("decrypt note").clicked() {
|
||||||
value = Some(self.decrypt(password).unwrap());
|
value = Some(self.decrypt(password).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Note::Decrypted { id, title, metadata, text, .. } => {
|
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);
|
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 {
|
match self {
|
||||||
Note::Encrypted { id, .. } => id.clone(),
|
Note::Encrypted { id, .. } => id.clone(),
|
||||||
Note::Decrypted { 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.horizontal(|ui| {
|
||||||
ui.label(RichText::new(title).size(14.0));
|
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();
|
ui.separator();
|
||||||
if show_delete_button {
|
if show_delete_button && ui.button("delete note").clicked() {
|
||||||
let (id, cb) = delete_info;
|
result = Some(RenderResult::DeleteNote);
|
||||||
if ui.button("delete note").clicked() {
|
}
|
||||||
cb(id);
|
|
||||||
|
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();
|
ui.separator();
|
||||||
|
result
|
||||||
}
|
}
|
Loading…
Reference in a new issue