use std::{ env, }; use chrono::{ DateTime, Local, SecondsFormat, }; use fltk::{ app, button, frame, prelude::*, text, window::Window, }; use tokio::{ fs, io::AsyncWriteExt, time::{ Duration, interval_at, }, }; mod config; mod error; mod offset; use config::Config; use error::Error; #[tokio::main] async fn main () -> Result <(), Error> { let config = Config::from_args (env::args ())?; let (fltk_tx, fltk_rx) = app::channel:: (); let app = app::App::default (); tokio::spawn (async move { let mut i = interval_at ( offset::get_next_tick ( config.interval_secs, config.offset_secs, ).into (), Duration::from_secs (config.interval_secs), ); i.set_missed_tick_behavior (tokio::time::MissedTickBehavior::Skip); loop { i.tick ().await; eprintln! ("3SAHNQ43 Popping up"); fltk_tx.send (Message::PopUp); } }); let mut gui = Gui::new (config, fltk_tx); while app.wait () { let msg = match fltk_rx.recv () { Some (x) => x, None => continue, }; match msg { Message::Submit => { if let Err (e) = gui.submit ().await { eprintln! ("DVW4SBNB Error while submitting: {:?}", e); } }, Message::PopUp => { if let Err (e) = gui.pop_up () { eprintln! ("5BWNNQT6 Error while popping up: {:?}", e); } }, } } Ok (()) } #[derive (Clone, Copy)] enum Message { PopUp, Submit, } #[derive (serde::Serialize)] struct JournalLine { text: String, time_popped_up: Option , time_submitted: String, } struct Gui { time_popped_up: Option >, editor: text::TextEditor, status: frame::Frame, wind: Window, } impl Gui { fn new (config: Config, fltk_tx: app::Sender ) -> Self { let mut wind = Window::new (100, 100, 640, 480, "Annoying Journal"); wind.make_resizable (true); let mut buffer = text::TextBuffer::default (); buffer.set_text (&config.prompt); let mut display = text::TextDisplay::new (0, 0, 640, 50, ""); display.set_buffer (Some (buffer)); let mut editor = text::TextEditor::new (0, 50, 640, 480 - 50 - 50, ""); editor.set_buffer (Some (text::TextBuffer::default ())); editor.set_tab_nav (true); let mut but = button::ReturnButton::new (640 - 100, 480 - 50, 100, 50, "Submit"); but.emit (fltk_tx, Message::Submit); let status = frame::Frame::new (0, 480 - 50, 640 - 100, 50, ""); wind.set_label ("ANNOYING JOURNAL"); wind.end (); wind.show (); let mut that = Self { time_popped_up: Some (Local::now ()), editor, status, wind, }; that.refresh_status (); that } fn refresh_status (&mut self) { let version = option_env! ("CARGO_PKG_VERSION").unwrap_or ("(???)"); let time_popped_up = self.time_popped_up .map (|x| x.to_rfc3339_opts (SecondsFormat::Secs, true)) .unwrap_or_else (|| "(???)".to_string ()); self.status.set_label (&format! ("v{}, popped up at {}", version, time_popped_up)); } fn pop_up (&mut self) -> Result <(), Error> { if self.time_popped_up.is_some () { eprintln! ("O4U6E36V Ignoring pop-up, already popped up"); return Ok (()); } self.time_popped_up = Some (Local::now ()); self.refresh_status (); self.wind.set_label ("ANNOYING JOURNAL"); self.wind.show (); self.editor.take_focus ()?; Ok (()) } async fn submit (&mut self) -> Result <(), Error> { let buffer = match self.editor.buffer () { None => return Err (Error::EditorHasNoBuffer), Some (x) => x, }; let jl = JournalLine { text: buffer.text (), time_popped_up: self.time_popped_up.map (|x| x.to_rfc3339_opts (SecondsFormat::Secs, true)), time_submitted: Local::now ().to_rfc3339_opts (SecondsFormat::Secs, true), }; let s = serde_json::to_string (&jl)?; fs::create_dir_all ("annoying_journal").await?; let mut f = fs::OpenOptions::new () .append (true) .create (true) .open ("annoying_journal/journal.jsonl").await?; f.write_all (s.as_bytes ()).await?; f.write_all (b"\n").await?; println! ("{}", s); self.editor.set_buffer (text::TextBuffer::default ()); self.wind.iconize (); self.time_popped_up = None; self.wind.set_label ("annoying journal"); Ok (()) } }