aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorJoris2022-02-26 18:35:24 +0100
committerJoris2022-02-26 18:35:24 +0100
commit2d80413609130f1c121dcae39a150a27dd9f02ea (patch)
treea8c807b7e95d5049ea43a1757d292b5cb745524b /src/app
parent1445e23a26c6581ad0c3f5b5016e47e95d224e9f (diff)
Show repeated events
Diffstat (limited to 'src/app')
-rw-r--r--src/app/app.rs21
-rw-r--r--src/app/calendar.rs44
-rw-r--r--src/app/form/mod.rs4
-rw-r--r--src/app/form/repetition.rs34
-rw-r--r--src/app/update.rs91
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: &gtk::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)
+ }
+}