diff --git a/fitnesstrax/app/src/components/date_field.rs b/fitnesstrax/app/src/components/date_field.rs index 7b56330..dafab5d 100644 --- a/fitnesstrax/app/src/components/date_field.rs +++ b/fitnesstrax/app/src/components/date_field.rs @@ -126,15 +126,18 @@ impl DateField { s.append(&s.imp().month.widget()); s.append(&s.imp().day.widget()); - s.imp().year.set_value(Some(date.year())); - s.imp().month.set_value(Some(date.month())); - s.imp().day.set_value(Some(date.day())); - - *s.imp().date.borrow_mut() = date; + s.set_date(date); s } + pub fn set_date(&self, date: chrono::NaiveDate) { + self.imp().year.set_value(Some(date.year())); + self.imp().month.set_value(Some(date.month())); + self.imp().day.set_value(Some(date.day())); + + *self.imp().date.borrow_mut() = date; + } pub fn date(&self) -> chrono::NaiveDate { self.imp().date.borrow().clone() diff --git a/fitnesstrax/app/src/components/date_range.rs b/fitnesstrax/app/src/components/date_range.rs new file mode 100644 index 0000000..ebad6aa --- /dev/null +++ b/fitnesstrax/app/src/components/date_range.rs @@ -0,0 +1,168 @@ +/* +Copyright 2024, Savanni D'Gerinel + +This file is part of FitnessTrax. + +FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . +*/ + +use crate::{ + app::App, + components::{DateField, DaySummary}, + types::DayInterval, + view_models::DayDetailViewModel, +}; +use chrono::{Duration, Local, Months}; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, rc::Rc}; + +type OnSearch = dyn Fn(DayInterval) + 'static; + +pub struct DateRangePickerPrivate { + start: DateField, + end: DateField, + + on_search: RefCell>, +} + +#[glib::object_subclass] +impl ObjectSubclass for DateRangePickerPrivate { + const NAME: &'static str = "DateRangePicker"; + type Type = DateRangePicker; + type ParentType = gtk::Box; + + fn new() -> Self { + let default_date = Local::now().date_naive(); + Self { + start: DateField::new(default_date), + end: DateField::new(default_date), + on_search: RefCell::new(Box::new(|_| {})), + } + } +} + +impl ObjectImpl for DateRangePickerPrivate {} +impl WidgetImpl for DateRangePickerPrivate {} +impl BoxImpl for DateRangePickerPrivate {} + +glib::wrapper! { + pub struct DateRangePicker(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl DateRangePicker { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + s.set_orientation(gtk::Orientation::Vertical); + + let search_button = gtk::Button::with_label("Search"); + search_button.connect_clicked({ + let s = s.clone(); + move |_| (s.imp().on_search.borrow())(s.interval()) + }); + + let last_week_button = gtk::Button::builder().label("last week").build(); + last_week_button.connect_clicked({ + let s = s.clone(); + move |_| { + let end = Local::now().date_naive(); + let start = end - Duration::days(7); + s.set_interval(start, end); + (s.imp().on_search.borrow())(s.interval()); + } + }); + + let two_weeks_button = gtk::Button::builder().label("last two weeks").build(); + two_weeks_button.connect_clicked({ + let s = s.clone(); + move |_| { + let end = Local::now().date_naive(); + let start = end - Duration::days(14); + s.set_interval(start, end); + (s.imp().on_search.borrow())(s.interval()); + } + }); + + let last_month_button = gtk::Button::builder().label("last month").build(); + last_month_button.connect_clicked({ + let s = s.clone(); + move |_| { + let end = Local::now().date_naive(); + let start = end - Months::new(1); + s.set_interval(start, end); + (s.imp().on_search.borrow())(s.interval()); + } + }); + + let six_months_button = gtk::Button::builder().label("last six months").build(); + six_months_button.connect_clicked({ + let s = s.clone(); + move |_| { + let end = Local::now().date_naive(); + let start = end - Months::new(6); + s.set_interval(start, end); + (s.imp().on_search.borrow())(s.interval()); + } + }); + + let last_year_button = gtk::Button::builder().label("last year").build(); + last_year_button.connect_clicked({ + let s = s.clone(); + move |_| { + let end = Local::now().date_naive(); + let start = end - Months::new(12); + s.set_interval(start, end); + (s.imp().on_search.borrow())(s.interval()); + } + }); + + let date_row = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .build(); + date_row.append(&s.imp().start); + date_row.append(>k::Label::new(Some("to"))); + date_row.append(&s.imp().end); + date_row.append(&search_button); + + let quick_picker = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .build(); + quick_picker.append(&last_week_button); + quick_picker.append(&two_weeks_button); + quick_picker.append(&last_month_button); + quick_picker.append(&six_months_button); + quick_picker.append(&last_year_button); + + s.append(&date_row); + s.append(&quick_picker); + + s + } + + pub fn connect_on_search(&self, f: OnSearch) + where + OnSearch: Fn(DayInterval) + 'static, + { + *self.imp().on_search.borrow_mut() = Box::new(f); + } + + fn set_interval(&self, start: chrono::NaiveDate, end: chrono::NaiveDate) { + self.imp().start.set_date(start); + self.imp().end.set_date(end); + } + + fn interval(&self) -> DayInterval { + DayInterval { + start: self.imp().start.date(), + end: self.imp().end.date(), + } + } +} diff --git a/fitnesstrax/app/src/components/mod.rs b/fitnesstrax/app/src/components/mod.rs index addb82f..d08f670 100644 --- a/fitnesstrax/app/src/components/mod.rs +++ b/fitnesstrax/app/src/components/mod.rs @@ -24,6 +24,9 @@ pub use day::{DayDetail, DayEdit, DaySummary}; mod date_field; pub use date_field::DateField; +mod date_range; +pub use date_range::DateRangePicker; + mod singleton; pub use singleton::{Singleton, SingletonImpl}; diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index 7bda8ea..bfb60e1 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -16,11 +16,10 @@ You should have received a copy of the GNU General Public License along with Fit use crate::{ app::App, - components::{DateField, DaySummary}, + components::{DateRangePicker, DaySummary}, types::DayInterval, view_models::DayDetailViewModel, }; - use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; @@ -109,39 +108,12 @@ impl HistoricalView { *s.imp().app.borrow_mut() = Some(app); - let start_date = DateField::new(interval.start); - let end_date = DateField::new(interval.end); - let search_button = gtk::Button::with_label("Search"); - search_button.connect_clicked({ + let date_range_picker = DateRangePicker::new(); + date_range_picker.connect_on_search({ let s = s.clone(); - let start_date = start_date.clone(); - let end_date = end_date.clone(); - move |_| { - let interval = DayInterval { - start: start_date.date(), - end: end_date.date(), - }; - s.set_interval(interval); - } + move |interval| s.set_interval(interval) }); - let date_row = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .build(); - date_row.append(&start_date); - date_row.append(>k::Label::new(Some("to"))); - date_row.append(&end_date); - date_row.append(&search_button); - - let quick_picker = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .build(); - quick_picker.append(>k::Button::builder().label("last week").build()); - quick_picker.append(>k::Button::builder().label("last two weeks").build()); - quick_picker.append(>k::Button::builder().label("last month").build()); - quick_picker.append(>k::Button::builder().label("last six months").build()); - quick_picker.append(>k::Button::builder().label("last year").build()); - let mut model = gio::ListStore::new::(); model.extend(interval.days().map(Date::new)); s.imp() @@ -158,8 +130,7 @@ impl HistoricalView { } }); - s.append(&date_row); - s.append(&quick_picker); + s.append(&date_range_picker); s.append(&s.imp().list_view); s @@ -172,10 +143,6 @@ impl HistoricalView { .list_view .set_model(Some(>k::NoSelection::new(Some(model)))); } - - pub fn time_window(&self) -> DayInterval { - self.imp().time_window.borrow().clone() - } } #[derive(Default)]