From bd59a5128c05dcd550e91bbdd0cd9d5996a65586 Mon Sep 17 00:00:00 2001 From: Joris Date: Sun, 9 Jan 2022 09:43:21 +0100 Subject: Persist events to sqlite db --- src/app.rs | 151 +++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 113 insertions(+), 38 deletions(-) (limited to 'src/app.rs') diff --git a/src/app.rs b/src/app.rs index 5717c12..0eb2b1e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,15 +1,18 @@ use gtk4 as gtk; use async_channel::{Receiver, Sender}; -use chrono::{Datelike, NaiveDate, Weekday}; +use chrono::{Datelike, NaiveDate, NaiveTime, Weekday}; use gtk::gdk::Display; -use gtk::glib; use gtk::glib::signal::Inhibit; +use gtk::glib; use gtk::prelude::*; +use rusqlite::Connection; use std::future::Future; use std::rc::Rc; -use crate::model::event::{Event, Time}; +use crate::model::event; +use crate::model::event::Event; +use crate::db; /// Spawns a task on the default executor, without waiting for it to complete pub fn spawn(future: F) @@ -35,24 +38,25 @@ static MONTHES: [&str; 12] = [ "Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Aoû", "Sep", "Oct", "Nov", "Déc", ]; -pub fn run(events: Vec) { - let application = gtk::Application::new(Some("me.guyonvarch.calendar"), Default::default()); - application.connect_startup(move |app| build_ui(app, &events)); - application.run(); +pub fn run(conn: Connection) { + let conn = Rc::new(conn); + let app = gtk::Application::new(Some("me.guyonvarch.calendar"), Default::default()); + app.connect_startup(|_| load_style()); + app.connect_activate(move |app| build_ui(conn.clone(), app)); + app.run(); } -fn build_ui(app: >k::Application, events: &Vec) { - load_style(); +fn build_ui(conn: Rc, app: >k::Application) { let (tx, rx) = async_channel::unbounded(); - let app = App::new(app, tx.clone(), &events); - spawn(event_handler(rx, tx, app)) + let app = App::new(conn.clone(), app, tx.clone()); + spawn(event_handler(conn, rx, tx, app)) } -async fn event_handler(rx: Receiver, tx: Sender, mut app: App) { +async fn event_handler(conn: Rc, rx: Receiver, tx: Sender, mut app: App) { while let Ok(msg) = rx.recv().await { match msg { Msg::ShowAddForm { date } => { - spawn(add_event_dialog(tx.clone(), Rc::clone(&app.window), date)); + add_event_dialog(Rc::clone(&conn), tx.clone(), Rc::clone(&app.window), date).await; } Msg::AddEvent { event } => { let date = event.date.clone(); @@ -85,7 +89,7 @@ struct App { } impl App { - fn new(app: >k::Application, tx: Sender, events: &Vec) -> Self { + fn new(conn: Rc, app: >k::Application, tx: Sender) -> Self { let window = Rc::new( gtk::ApplicationWindow::builder() .application(app) @@ -107,6 +111,8 @@ impl App { let start_date = NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon); + + let events = db::list(&conn).unwrap_or(vec!()); show_days(tx, &grid, &start_date, &today, &events); window.connect_close_request(move |window| { @@ -196,7 +202,7 @@ fn day_entry( .iter() .filter(|e| e.date == *date) .collect::>(); - events.sort_by_key(|e| e.time); + events.sort_by_key(|e| e.start); if !events.is_empty() { vbox.append(&day_events(events)); @@ -263,34 +269,103 @@ fn day_events(events: Vec<&Event>) -> gtk::Box { vbox } -async fn add_event_dialog(tx: Sender, window: Rc, date: NaiveDate) { +static DATE_FORMAT: &str = "%d/%m/%Y"; + +async fn add_event_dialog(conn: Rc, tx: Sender, window: Rc, date: NaiveDate) { let dialog = gtk::Dialog::builder() .transient_for(&*window) .modal(true) - .title("Ajouter un évènement") + .title("Ajouter") + .css_classes(vec!["g-Form".to_string()]) .build(); let content_area = dialog.content_area(); - let label = gtk::Label::builder().label(&format!("{:?}", date)).build(); - content_area.append(&label); - - let entry = gtk::Entry::builder().build(); - content_area.append(&entry); - - dialog.add_buttons(&[ - ("Annuler", gtk::ResponseType::Cancel), - ("Créer", gtk::ResponseType::Ok), - ]); - - let answer = dialog.run_future().await; - if answer == gtk::ResponseType::Ok { - let event = Event { - date, - time: Time::AllDay, - name: entry.buffer().text(), - }; - - send_message(tx, Msg::AddEvent { event: event }); + + let vbox = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .build(); + vbox.add_css_class("g-Form__Inputs"); + content_area.append(&vbox); + + let name = entry(""); + vbox.append(&label("Événement")); + vbox.append(&name); + + let date = entry(&date.format(DATE_FORMAT).to_string()); + vbox.append(&label("Jour")); + vbox.append(&date); + + let start = entry(""); + vbox.append(&label("Début")); + vbox.append(&start); + + let end = entry(""); + vbox.append(&label("Fin")); + vbox.append(&end); + + let button = gtk::Button::with_label("Créer"); + vbox.append(&button); + button.connect_clicked(glib::clone!(@weak dialog => move |_| { + match validate_event(date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text()) { + Some(event) => { + match db::insert(&conn, &event) { + Ok(_) => { + send_message(tx.clone(), Msg::AddEvent { event: event }); + dialog.close() + }, + Err(_) => () + } + }, + None => () + } + })); + + dialog.run_future().await; +} + +fn entry(text: &str) -> gtk::Entry { + gtk::Entry::builder().text(text).margin_bottom(10).build() +} + +fn label(text: &str) -> gtk::Label { + gtk::Label::builder() + .label(text) + .halign(gtk::Align::Start) + .margin_bottom(5) + .build() +} + +fn validate_event(date: String, name: String, start: String, end: String) -> Option { + let start = validate_time(start)?; + let end = validate_time(end)?; + + match (start, end) { + (Some(s), Some(e)) if s > e => None?, + _ => (), + } + + Some(Event { + date: NaiveDate::parse_from_str(&date, DATE_FORMAT).ok()?, + name: validate_name(name)?, + start, + end, + }) +} + +fn validate_time(time: String) -> Option> { + let time = time.trim(); + if time.is_empty() { + Some(None) + } else { + event::parse_time(time).map(|t| Some(t)) + } +} + +fn validate_name(name: String) -> Option { + let name = name.trim(); + if name.is_empty() { + None + } else { + Some(name.to_string()) } - dialog.close(); } -- cgit v1.2.3