/* Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com> 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 <https://www.gnu.org/licenses/>. */ use crate::{ app::App, components::{DateRangePicker, DaySummary}, types::DayInterval, view_models::DayDetailViewModel, }; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; /// The historical view will show a window into the main database. It will show some version of /// daily summaries, daily details, and will provide all functions the user may need for editing /// records. pub struct HistoricalViewPrivate { app: Rc<RefCell<Option<App>>>, list_view: gtk::ListView, date_range_picker: DateRangePicker, } #[glib::object_subclass] impl ObjectSubclass for HistoricalViewPrivate { const NAME: &'static str = "HistoricalView"; type Type = HistoricalView; type ParentType = gtk::Box; fn new() -> Self { let factory = gtk::SignalListItemFactory::new(); factory.connect_setup(move |_, list_item| { list_item .downcast_ref::<gtk::ListItem>() .expect("should be a ListItem") .set_child(Some(&DaySummary::new())); }); let date_range_picker = DateRangePicker::default(); let s = Self { app: Rc::new(RefCell::new(None)), list_view: gtk::ListView::builder() .factory(&factory) .single_click_activate(true) .show_separators(true) .build(), date_range_picker, }; factory.connect_bind({ let app = s.app.clone(); move |_, list_item| { let date = list_item .downcast_ref::<gtk::ListItem>() .expect("should be a ListItem") .item() .and_downcast::<Date>() .expect("should be a Date"); let summary = list_item .downcast_ref::<gtk::ListItem>() .expect("should be a ListItem") .child() .and_downcast::<DaySummary>() .expect("should be a DaySummary"); if let Some(app) = app.borrow().clone() { glib::spawn_future_local(async move { let view_model = DayDetailViewModel::new(date.date(), app.clone()) .await .unwrap(); summary.set_data(view_model); }); } } }); s } } impl ObjectImpl for HistoricalViewPrivate {} impl WidgetImpl for HistoricalViewPrivate {} impl BoxImpl for HistoricalViewPrivate {} glib::wrapper! { pub struct HistoricalView(ObjectSubclass<HistoricalViewPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } impl HistoricalView { pub fn new<SelectFn>(app: App, interval: DayInterval, on_select_day: Rc<SelectFn>) -> Self where SelectFn: Fn(chrono::NaiveDate) + 'static, { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_css_classes(&["historical"]); *s.imp().app.borrow_mut() = Some(app); s.imp().date_range_picker.connect_on_search({ let s = s.clone(); move |interval| s.set_interval(interval) }); s.set_interval(interval); s.imp().list_view.connect_activate({ let on_select_day = on_select_day.clone(); move |s, idx| { // This gets triggered whenever the user clicks on an item on the list. let item = s.model().unwrap().item(idx).unwrap(); let date = item.downcast_ref::<Date>().unwrap(); on_select_day(date.date()); } }); let scroller = gtk::ScrolledWindow::builder() .child(&s.imp().list_view) .hexpand(true) .vexpand(true) .hscrollbar_policy(gtk::PolicyType::Never) .build(); s.append(&s.imp().date_range_picker); s.append(&scroller); s } pub fn set_interval(&self, interval: DayInterval) { let mut model = gio::ListStore::new::<Date>(); let mut days = interval.days().map(Date::new).collect::<Vec<Date>>(); days.reverse(); model.extend(days.into_iter()); self.imp() .list_view .set_model(Some(>k::NoSelection::new(Some(model)))); self.imp().date_range_picker.set_interval(interval.start, interval.end); } } #[derive(Default)] pub struct DatePrivate { date: RefCell<chrono::NaiveDate>, } #[glib::object_subclass] impl ObjectSubclass for DatePrivate { const NAME: &'static str = "Date"; type Type = Date; } impl ObjectImpl for DatePrivate {} glib::wrapper! { pub struct Date(ObjectSubclass<DatePrivate>); } impl Date { pub fn new(date: chrono::NaiveDate) -> Self { let s: Self = Object::builder().build(); *s.imp().date.borrow_mut() = date; s } pub fn date(&self) -> chrono::NaiveDate { *self.imp().date.borrow() } }