diff options
| author | Joris | 2022-02-26 18:35:24 +0100 | 
|---|---|---|
| committer | Joris | 2022-02-26 18:35:24 +0100 | 
| commit | 2d80413609130f1c121dcae39a150a27dd9f02ea (patch) | |
| tree | a8c807b7e95d5049ea43a1757d292b5cb745524b /src/app | |
| parent | 1445e23a26c6581ad0c3f5b5016e47e95d224e9f (diff) | |
Show repeated events
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/app.rs | 21 | ||||
| -rw-r--r-- | src/app/calendar.rs | 44 | ||||
| -rw-r--r-- | src/app/form/mod.rs | 4 | ||||
| -rw-r--r-- | src/app/form/repetition.rs | 34 | ||||
| -rw-r--r-- | src/app/update.rs | 91 | 
5 files changed, 134 insertions, 60 deletions
| diff --git a/src/app/app.rs b/src/app/app.rs index b1ee395..58240af 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -1,7 +1,7 @@  use gtk4 as gtk;  use async_channel::Sender; -use chrono::{Datelike, NaiveDate, Weekday}; +use chrono::{Datelike, Duration, NaiveDate, Weekday};  use gtk::glib::signal::Inhibit;  use gtk::prelude::*;  use rusqlite::Connection; @@ -16,8 +16,10 @@ pub struct App {      pub window: Rc<gtk::ApplicationWindow>,      pub grid: gtk::Grid,      pub events: Vec<Event>, +    pub repeated_events: Vec<Event>,      pub today: NaiveDate,      pub start_date: NaiveDate, +    pub end_date: NaiveDate,      pub tx: Sender<Msg>,  } @@ -36,8 +38,19 @@ impl App {          let today = chrono::offset::Local::today().naive_utc();          let start_date =              NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon); -        let events = db::list(&conn).unwrap_or(vec![]); -        let grid = calendar::create(tx.clone(), &today, &start_date, &events); +        let end_date = start_date + Duration::days(7 * 4 - 1); + +        let events = db::list_non_repeated_between(&conn, start_date, end_date).unwrap_or(vec![]); +        let repeated_events = db::list_repeated(&conn).unwrap_or(vec![]); + +        let grid = calendar::create( +            tx.clone(), +            today, +            start_date, +            end_date, +            &events, +            &repeated_events, +        );          window.set_child(Some(&grid)); @@ -53,8 +66,10 @@ impl App {              window,              grid,              events, +            repeated_events,              today,              start_date, +            end_date,              tx,          }      } diff --git a/src/app/calendar.rs b/src/app/calendar.rs index fa4ebe6..11eb893 100644 --- a/src/app/calendar.rs +++ b/src/app/calendar.rs @@ -4,8 +4,9 @@ use async_channel::Sender;  use chrono::{Datelike, NaiveDate};  use gtk::glib;  use gtk::prelude::*; +use std::collections::HashMap; -use crate::{app::update, app::update::Msg, app::App, model::event::Event}; +use crate::{app::update, app::update::Msg, app::App, model::event, model::event::Event};  static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"];  static MONTHES: [&str; 12] = [ @@ -14,9 +15,11 @@ static MONTHES: [&str; 12] = [  pub fn create(      tx: Sender<Msg>, -    today: &NaiveDate, -    start_date: &NaiveDate, +    today: NaiveDate, +    start_date: NaiveDate, +    end_date: NaiveDate,      events: &Vec<Event>, +    repeated_events: &Vec<Event>,  ) -> gtk::Grid {      let grid = gtk::Grid::builder().build(); @@ -24,7 +27,8 @@ pub fn create(          grid.attach(&day_title(col), col, 0, 1, 1);      } -    attach_days(tx, &grid, &start_date, &today, &events); +    let repetitions = event::repetitions_between(repeated_events, start_date, end_date); +    attach_days(tx, &grid, start_date, today, events, &repetitions);      grid  } @@ -32,27 +36,34 @@ pub fn create(  fn attach_days(      tx: Sender<Msg>,      grid: >k::Grid, -    start_date: &NaiveDate, -    today: &NaiveDate, +    start_date: NaiveDate, +    today: NaiveDate,      events: &Vec<Event>, +    repetitions: &HashMap<NaiveDate, Vec<Event>>,  ) { -    let mut d = *start_date; +    let mut d = start_date;      for row in 1..5 {          for col in 0..7 { -            grid.attach(&day_entry(tx.clone(), &d, &today, &events), col, row, 1, 1); +            grid.attach( +                &day_entry(tx.clone(), d, today, events, repetitions), +                col, +                row, +                1, +                1, +            );              d = d.succ();          }      }  } -pub fn refresh_date(app: &App, date: NaiveDate) { +pub fn refresh_date(app: &App, date: NaiveDate, repetitions: &HashMap<NaiveDate, Vec<Event>>) {      let d = date.signed_duration_since(app.start_date).num_days();      let col = (d % 7) as i32;      let row = 1 + (d / 7) as i32;      app.grid.attach( -        &day_entry(app.tx.clone(), &date, &app.today, &app.events), +        &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions),          col,          row,          1, @@ -76,9 +87,10 @@ fn day_title(col: i32) -> gtk::Box {  pub fn day_entry(      tx: Sender<Msg>, -    date: &NaiveDate, -    today: &NaiveDate, +    date: NaiveDate, +    today: NaiveDate,      events: &Vec<Event>, +    repetitions: &HashMap<NaiveDate, Vec<Event>>,  ) -> gtk::ScrolledWindow {      let vbox = gtk::Box::builder()          .orientation(gtk::Orientation::Vertical) @@ -87,7 +99,7 @@ pub fn day_entry(      vbox.add_css_class("g-Calendar__Day");      let gesture = gtk::GestureClick::new(); -    gesture.connect_pressed(glib::clone!(@strong date, @strong tx => move |_, n, _, _| { +    gesture.connect_pressed(glib::clone!(@strong tx => move |_, n, _, _| {          if n == 2 {              update::send(tx.clone(), Msg::ShowAddForm { date });          } @@ -102,8 +114,10 @@ pub fn day_entry(      let mut events = events          .iter() -        .filter(|e| e.date == *date) +        .filter(|e| e.date == date)          .collect::<Vec<&Event>>(); +    let repeated_events = repetitions.get(&date).map(|e| e.clone()).unwrap_or(vec![]); +    events.extend(repeated_events.iter());      events.sort_by_key(|e| e.start);      if !events.is_empty() { @@ -120,7 +134,7 @@ pub fn day_entry(      scrolled_window  } -fn day_label(date: &NaiveDate) -> gtk::Label { +fn day_label(date: NaiveDate) -> gtk::Label {      let label = gtk::Label::builder()          .label(&format!(              "{} {}", diff --git a/src/app/form/mod.rs b/src/app/form/mod.rs index 5c60bc5..9cb6ba7 100644 --- a/src/app/form/mod.rs +++ b/src/app/form/mod.rs @@ -85,10 +85,10 @@ pub async fn show(app: &App, event: Event, is_new: bool) {                          update::send(tx.clone(), msg);                          dialog.close()                      }, -                    Err(err) => println!("Error when upserting event: {err}") +                    Err(err) => eprintln!("Error when upserting event: {err}")                  }              }, -            None => println!("Event is not valid: {event:?}") +            None => eprintln!("Event is not valid: {event:?}")          }      })); diff --git a/src/app/form/repetition.rs b/src/app/form/repetition.rs index ac56479..87c8d84 100644 --- a/src/app/form/repetition.rs +++ b/src/app/form/repetition.rs @@ -7,7 +7,7 @@ use crate::{      model::event::Event,      model::{          repetition, -        repetition::{MonthFrequency, Repetition}, +        repetition::{DayOfMonth, Repetition},      },  }; @@ -19,14 +19,14 @@ static WEEKDAYS: [Weekday; 7] = [Mon, Tue, Wed, Thu, Fri, Sat, Sun];  pub struct Model {      pub view: gtk::Box, -    pub no_radio: gtk::CheckButton, -    pub day_interval_radio: gtk::CheckButton, -    pub day_interval_entry: gtk::Entry, -    pub monthly_radio: gtk::CheckButton, -    pub monthly_entry: gtk::Entry, -    pub first_day_radio: gtk::CheckButton, -    pub first_day_dropdown: gtk::DropDown, -    pub yearly_radio: gtk::CheckButton, +    no_radio: gtk::CheckButton, +    day_interval_radio: gtk::CheckButton, +    day_interval_entry: gtk::Entry, +    monthly_radio: gtk::CheckButton, +    monthly_entry: gtk::Entry, +    first_day_radio: gtk::CheckButton, +    first_day_dropdown: gtk::DropDown, +    yearly_radio: gtk::CheckButton,  }  pub fn view(event: &Event) -> Model { @@ -44,7 +44,7 @@ pub fn view(event: &Event) -> Model {      view.append(&no_radio);      let default = match event.repetition { -        Some(Repetition::Daily { frequency }) => frequency.to_string(), +        Some(Repetition::Daily { period }) => period.to_string(),          _ => "".to_string(),      };      let day_interval_entry = gtk::Entry::builder().text(&default).build(); @@ -58,7 +58,7 @@ pub fn view(event: &Event) -> Model {      let default = match event.repetition {          Some(Repetition::Monthly { -            frequency: MonthFrequency::Day { day }, +            day: DayOfMonth::Day { day },          }) => day.to_string(),          _ => "".to_string(),      }; @@ -69,8 +69,8 @@ pub fn view(event: &Event) -> Model {      let (active, default) = match event.repetition {          Some(Repetition::Monthly { -            frequency: MonthFrequency::FirstDay { day }, -        }) => (true, day), +            day: DayOfMonth::Weekday { weekday }, +        }) => (true, weekday),          _ => (false, Mon),      };      let first_day_dropdown = gtk::DropDown::from_strings(&WEEKDAYS_STR); @@ -131,17 +131,17 @@ pub fn validate(model: &Model) -> Option<Repetition> {          None      } else if model.day_interval_radio.is_active() {          repetition::validate_day(&model.day_interval_entry.buffer().text()) -            .map(|d| Repetition::Daily { frequency: d }) +            .map(|d| Repetition::Daily { period: d })      } else if model.monthly_radio.is_active() {          repetition::validate_day(&model.monthly_entry.buffer().text()).map(|d| {              Repetition::Monthly { -                frequency: MonthFrequency::Day { day: d }, +                day: DayOfMonth::Day { day: d },              }          })      } else if model.first_day_radio.is_active() { -        let day = WEEKDAYS[model.first_day_dropdown.selected() as usize]; +        let weekday = WEEKDAYS[model.first_day_dropdown.selected() as usize];          Some(Repetition::Monthly { -            frequency: MonthFrequency::FirstDay { day }, +            day: DayOfMonth::Weekday { weekday },          })      } else if model.yearly_radio.is_active() {          Some(Repetition::Yearly) diff --git a/src/app/update.rs b/src/app/update.rs index 4e21050..4ef1eb1 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1,5 +1,6 @@  use async_channel::{Receiver, Sender};  use chrono::NaiveDate; +use std::collections::HashSet;  use crate::{      app::{calendar, form, utils, App}, @@ -30,31 +31,75 @@ pub async fn event_handler(rx: Receiver<Msg>, mut app: App) {                  form::show(&app, event, false).await;              }              Msg::AddEvent { new } => { -                let date = new.date.clone(); -                app.events.push(new); -                calendar::refresh_date(&app, date); +                let refresh_dates = add(&mut app, &new); +                refresh(&app, &refresh_dates)              }              Msg::UpdateEvent { old, new } => { -                let new_date = new.date.clone(); -                match app.events.iter().position(|e| e.id == new.id) { -                    Some(index) => { -                        app.events.remove(index); -                        app.events.push(new); -                        calendar::refresh_date(&app, new_date); -                        if old.date != new_date { -                            calendar::refresh_date(&app, old.date.clone()) -                        } -                    } -                    None => println!("Event not found when updating from {:?} to {:?}", old, new), -                } -            } -            Msg::DeleteEvent { event } => match app.events.iter().position(|e| e.id == event.id) { -                Some(index) => { -                    app.events.remove(index); -                    calendar::refresh_date(&app, event.date); -                } -                None => println!("Event not found when trying to delete {:?}", event), -            }, +                let refresh_dates_1 = remove(&mut app, &old); +                let refresh_dates_2 = add(&mut app, &new); +                refresh(&app, &refresh_dates_1.union(&refresh_dates_2).map(|d| *d).collect::<HashSet<NaiveDate>>()) +            } +            Msg::DeleteEvent { event } => { +                let refresh_dates = remove(&mut app, &event); +                refresh(&app, &refresh_dates) +            } +        } +    } +} + +/// Remove event and return dates  that should be refreshed. +fn remove(app: &mut App, event: &Event) -> HashSet<NaiveDate> { +    if event.repetition.is_some() { +        match app.repeated_events.iter().position(|e| e.id == event.id) { +            Some(index) => { +                app.repeated_events.remove(index); +                let mut dates = repetition_dates(&app, event); +                dates.insert(event.date); +                dates +            } +            None => { +                eprintln!("Event not found when trying to delete {:?}", event); +                HashSet::new() +            } +        } +    } else { +        match app.events.iter().position(|e| e.id == event.id) { +            Some(index) => { +                app.events.remove(index); +                HashSet::from([event.date]) +            } +            None => { +                eprintln!("Event not found when trying to delete {:?}", event); +                HashSet::new() +            }          }      }  } + +/// Add event and return dates that should be refreshed. +fn add(app: &mut App, event: &Event) -> HashSet<NaiveDate> { +    if event.repetition.is_some() { +        app.repeated_events.push(event.clone()); +        let mut dates = repetition_dates(&app, event); +        dates.insert(event.date); +        dates +    } else { +        app.events.push(event.clone()); +        HashSet::from([event.date]) +    } +} + +/// Repetition dates of a repetead event. +fn repetition_dates(app: &App, event: &Event) -> HashSet<NaiveDate> { +    let event_reps = event::repetitions_between(&vec!(event.clone()), app.start_date, app.end_date); +    HashSet::from_iter(event_reps.keys().map(|d| *d)) +} + +/// Refresh app for the given dates. +fn refresh(app: &App, dates: &HashSet<NaiveDate>) { +    let repetitions = event::repetitions_between(&app.repeated_events, app.start_date, app.end_date); + +    for date in dates { +        calendar::refresh_date(app, *date, &repetitions) +    } +} | 
