2023-02-27 15:43:33 +00:00
|
|
|
#![cfg_attr(
|
|
|
|
all(
|
|
|
|
target_os = "windows",
|
|
|
|
not(debug_assertions),
|
|
|
|
),
|
|
|
|
windows_subsystem = "windows"
|
|
|
|
)]
|
2023-02-27 14:31:17 +00:00
|
|
|
|
2023-02-27 14:20:01 +00:00
|
|
|
use std::collections::{VecDeque};
|
|
|
|
use std::fs;
|
|
|
|
use std::time::SystemTime;
|
|
|
|
use directories::ProjectDirs;
|
2023-02-27 17:57:20 +00:00
|
|
|
use eframe::{CreationContext, Frame};
|
2023-03-05 23:03:41 +00:00
|
|
|
use egui::{Color32, Context, RichText, TextEdit, Widget, WidgetText};
|
2023-02-27 14:20:01 +00:00
|
|
|
use log::info;
|
2023-03-02 21:40:36 +00:00
|
|
|
use native_dialog::MessageDialog;
|
2023-02-27 14:20:01 +00:00
|
|
|
use simple_logger::SimpleLogger;
|
|
|
|
use crate::notes::{Note, NoteMetadata};
|
2023-03-05 23:03:41 +00:00
|
|
|
use crate::password::{check_password, entropy, generate_new_password};
|
2023-02-27 14:20:01 +00:00
|
|
|
use crate::saving::Saving;
|
|
|
|
|
|
|
|
mod notes;
|
|
|
|
mod saving;
|
2023-03-05 16:35:04 +00:00
|
|
|
mod password;
|
2023-02-27 14:20:01 +00:00
|
|
|
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
enum CurrentMode {
|
|
|
|
View,
|
|
|
|
Compose,
|
|
|
|
PasswordInput,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct App {
|
|
|
|
notes: Vec<Note>,
|
2023-02-27 15:43:33 +00:00
|
|
|
update_notes_next: bool,
|
2023-02-27 14:20:01 +00:00
|
|
|
password: Option<String>,
|
|
|
|
tested_password: bool,
|
|
|
|
mode: CurrentMode,
|
|
|
|
saving: Saving,
|
|
|
|
|
|
|
|
password_buffer: String,
|
|
|
|
|
|
|
|
title_buffer: String,
|
|
|
|
text_buffer: String,
|
|
|
|
}
|
|
|
|
|
2023-02-27 17:57:20 +00:00
|
|
|
impl App {
|
|
|
|
fn new(_cc: &CreationContext<'_>, notes: Vec<Note>, saving: Saving) -> Self {
|
|
|
|
Self {
|
|
|
|
password: None,
|
|
|
|
update_notes_next: false,
|
|
|
|
tested_password: false,
|
|
|
|
notes,
|
|
|
|
mode: CurrentMode::PasswordInput,
|
|
|
|
saving,
|
|
|
|
|
|
|
|
password_buffer: String::new(),
|
|
|
|
|
|
|
|
title_buffer: String::new(),
|
|
|
|
text_buffer: String::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:20:01 +00:00
|
|
|
fn main() {
|
|
|
|
SimpleLogger::new().init().expect("failed to initialize logger");
|
|
|
|
|
|
|
|
info!("starting the UI");
|
|
|
|
|
|
|
|
let options = eframe::NativeOptions {
|
|
|
|
initial_window_size: Some(egui::vec2(640.0, 640.0)),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let project_directories = ProjectDirs::from("com", "sofiaritz", "notes")
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let data_directory = project_directories
|
|
|
|
.data_dir();
|
|
|
|
info!("data directory: {:?}", data_directory);
|
|
|
|
|
|
|
|
fs::create_dir_all(data_directory).unwrap();
|
|
|
|
info!("created all intermediate directories");
|
|
|
|
|
|
|
|
let saving = Saving::new(data_directory);
|
|
|
|
let notes = saving.read_notes().unwrap();
|
|
|
|
info!("successfully read initial notes");
|
|
|
|
|
|
|
|
eframe::run_native(
|
|
|
|
"notes",
|
|
|
|
options,
|
2023-02-27 17:57:20 +00:00
|
|
|
Box::new(move |cc| Box::new(App::new(cc, notes, saving)))
|
2023-02-27 14:20:01 +00:00
|
|
|
).expect("failed to run native");
|
|
|
|
|
|
|
|
info!("shutdown");
|
|
|
|
}
|
|
|
|
|
|
|
|
impl eframe::App for App {
|
|
|
|
fn update(&mut self, ctx: &Context, _frame: &mut Frame) {
|
|
|
|
if self.password.is_none() {
|
|
|
|
self.mode = CurrentMode::PasswordInput;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.tested_password && self.password.is_some() {
|
|
|
|
if let Some(note) = self.notes.get(0) {
|
2023-03-05 16:35:04 +00:00
|
|
|
if let Ok(password) = check_password(self.password.as_ref().unwrap(), note) {
|
|
|
|
self.password = Some(password);
|
|
|
|
} else {
|
2023-03-02 21:40:36 +00:00
|
|
|
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;
|
|
|
|
}
|
2023-03-05 16:35:04 +00:00
|
|
|
} else {
|
|
|
|
self.password = Some(generate_new_password(self.password.as_ref().unwrap()))
|
2023-02-27 14:20:01 +00:00
|
|
|
}
|
2023-03-05 16:35:04 +00:00
|
|
|
|
|
|
|
self.tested_password = true;
|
2023-02-27 14:20:01 +00:00
|
|
|
}
|
|
|
|
|
2023-02-27 15:43:33 +00:00
|
|
|
if self.update_notes_next {
|
|
|
|
self.notes = self.saving.read_notes().unwrap();
|
|
|
|
self.update_notes_next = false;
|
|
|
|
info!("reloaded notes");
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:20:01 +00:00
|
|
|
match self.mode {
|
|
|
|
CurrentMode::View => {
|
|
|
|
let password = self.password.as_ref().unwrap();
|
|
|
|
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
|
|
ui.heading("notes");
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
ui.label("the simple note taking app that uses encryption by default");
|
|
|
|
ui.add_space(7.5);
|
|
|
|
if ui.button("want to add one note?").clicked() {
|
|
|
|
self.mode = CurrentMode::Compose;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
ui.separator();
|
|
|
|
|
2023-02-27 16:16:04 +00:00
|
|
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
|
|
for note in &mut self.notes {
|
|
|
|
let render_result = note.render(ui, password, |id| {
|
|
|
|
let _ = &self.saving.delete_note(&id).unwrap();
|
|
|
|
info!("note with id {} was successfully deleted", id);
|
|
|
|
|
|
|
|
self.update_notes_next = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(new_note) = render_result {
|
|
|
|
*note = new_note;
|
|
|
|
}
|
|
|
|
ui.add_space(10.0);
|
2023-02-27 14:20:01 +00:00
|
|
|
}
|
2023-02-27 16:16:04 +00:00
|
|
|
});
|
2023-02-27 14:20:01 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
CurrentMode::Compose => {
|
|
|
|
let password = self.password.as_ref().unwrap();
|
|
|
|
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
|
|
ui.heading("notes");
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
ui.label("the simple note taking app that uses encryption by default");
|
|
|
|
ui.add_space(7.5);
|
|
|
|
if ui.button("return to view mode?").clicked() {
|
|
|
|
self.title_buffer = String::new();
|
|
|
|
self.text_buffer = String::new();
|
|
|
|
self.mode = CurrentMode::View;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
ui.separator();
|
|
|
|
|
2023-02-27 16:16:04 +00:00
|
|
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
|
|
ui.label("title:");
|
|
|
|
ui.text_edit_singleline(&mut self.title_buffer);
|
|
|
|
|
|
|
|
ui.label("text:");
|
|
|
|
ui.text_edit_multiline(&mut self.text_buffer);
|
|
|
|
|
|
|
|
ui.add_space(10.0);
|
|
|
|
if ui.button("add note").clicked() {
|
|
|
|
let note = Note::Decrypted {
|
|
|
|
id: SystemTime::now()
|
|
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
|
|
.unwrap()
|
|
|
|
.as_millis()
|
|
|
|
.to_string(),
|
|
|
|
title: self.title_buffer.clone(),
|
|
|
|
metadata: NoteMetadata {
|
|
|
|
date: SystemTime::now(),
|
|
|
|
arbitrary: None,
|
|
|
|
},
|
|
|
|
text: self.text_buffer.clone(),
|
|
|
|
};
|
|
|
|
|
|
|
|
self.saving.save_note(note.clone(), password.to_string()).unwrap();
|
|
|
|
|
|
|
|
let mut vec_deque = VecDeque::from(self.notes.clone());
|
|
|
|
vec_deque.push_front(note);
|
|
|
|
|
|
|
|
self.notes = Vec::from(vec_deque);
|
|
|
|
|
|
|
|
self.title_buffer = String::new();
|
|
|
|
self.text_buffer = String::new();
|
|
|
|
self.mode = CurrentMode::View;
|
|
|
|
}
|
|
|
|
});
|
2023-02-27 14:20:01 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
CurrentMode::PasswordInput => {
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
|
|
ui.heading("notes");
|
|
|
|
ui.label("the simple note taking app that uses encryption by default");
|
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
ui.label("you need to put your password to access the notes");
|
|
|
|
ui.label(RichText::new("after starting the app, the password will be tested against the latest note").italics());
|
|
|
|
|
|
|
|
ui.add_space(10.0);
|
2023-03-05 23:03:41 +00:00
|
|
|
ui.horizontal(|ui| {
|
|
|
|
TextEdit::singleline(&mut self.password_buffer).password(true).ui(ui);
|
2023-03-05 23:30:51 +00:00
|
|
|
let text = if self.password_buffer.len() < 12 {
|
|
|
|
WidgetText::from(format!("length: {}", self.password_buffer.len()))
|
|
|
|
.color(Color32::from_rgb(240, 5, 5))
|
|
|
|
} else {
|
|
|
|
WidgetText::from(format!("length: {}", self.password_buffer.len()))
|
|
|
|
.color(Color32::from_rgb(144, 238, 144))
|
|
|
|
};
|
|
|
|
ui.label(text);
|
|
|
|
|
|
|
|
let entropy = entropy(&self.password_buffer)
|
|
|
|
.or(Some(0.0))
|
|
|
|
.unwrap();
|
|
|
|
let text = if entropy < 35_f64 {
|
|
|
|
WidgetText::from(format!("entropy: {:.2}", entropy))
|
|
|
|
.color(Color32::from_rgb(240, 5, 5))
|
|
|
|
} else if entropy < 60_f64 {
|
|
|
|
WidgetText::from(format!("entropy: {:.2}", entropy))
|
|
|
|
.color(Color32::from_rgb(220, 88, 42))
|
|
|
|
} else {
|
|
|
|
WidgetText::from(format!("entropy: {:.2}", entropy))
|
|
|
|
.color(Color32::from_rgb(144, 238, 144))
|
|
|
|
};
|
|
|
|
|
|
|
|
ui.label(text);
|
2023-03-05 23:03:41 +00:00
|
|
|
});
|
2023-02-27 14:36:35 +00:00
|
|
|
ui.add_space(7.5);
|
2023-02-27 14:20:01 +00:00
|
|
|
|
|
|
|
if ui.button("start app").clicked() {
|
|
|
|
self.password = Some(self.password_buffer.clone());
|
|
|
|
self.password_buffer = String::new();
|
|
|
|
self.mode = CurrentMode::View;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|