diff --git a/fitnesstrax/app/src/app.rs b/fitnesstrax/app/src/app.rs index 8dfb882..1bfbb1e 100644 --- a/fitnesstrax/app/src/app.rs +++ b/fitnesstrax/app/src/app.rs @@ -57,6 +57,20 @@ impl App { } } + pub async fn get_record(&self, id: RecordId) -> Result>, AppError> { + let db = self.database.clone(); + self.runtime + .spawn_blocking(move || { + if let Some(ref db) = *db.read().unwrap() { + Ok(db.get(&id)) + } else { + Err(AppError::NoDatabase) + } + }) + .await + .unwrap() + } + pub async fn records( &self, start: NaiveDate, diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 69218f8..6a1b5e6 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with Fit use crate::{ app::App, + types::DayInterval, view_models::DayDetailViewModel, views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView}, }; @@ -133,25 +134,34 @@ impl AppWindow { self.swap_main(view); } - fn show_historical_view(&self, records: Vec>) { - let view = View::Historical(HistoricalView::new(self.app.clone(), records, { + fn show_historical_view(&self, start_date: chrono::NaiveDate, end_date: chrono::NaiveDate) { + let on_select_day = { let s = self.clone(); - Rc::new(move |date, records| { - let layout = gtk::Box::new(gtk::Orientation::Vertical, 0); - layout.append(&adw::HeaderBar::new()); - // layout.append(&DayDetailView::new(date, records, s.app.clone())); - layout.append(&DayDetailView::new(DayDetailViewModel::new( - date, - records, - s.app.clone(), - ))); - let page = &adw::NavigationPage::builder() - .title(date.format("%Y-%m-%d").to_string()) - .child(&layout) - .build(); - s.navigation.push(page); - }) - })); + move |date, records| { + let s = s.clone(); + glib::spawn_future_local(async move { + let view_model = DayDetailViewModel::new(date, s.app.clone()).await; + let layout = gtk::Box::new(gtk::Orientation::Vertical, 0); + layout.append(&adw::HeaderBar::new()); + // layout.append(&DayDetailView::new(date, records, s.app.clone())); + layout.append(&DayDetailView::new(view_model)); + let page = &adw::NavigationPage::builder() + .title(date.format("%Y-%m-%d").to_string()) + .child(&layout) + .build(); + s.navigation.push(page); + }); + } + }; + + let view = View::Historical(HistoricalView::new( + self.app.clone(), + DayInterval { + start: start_date, + end: end_date, + }, + Rc::new(on_select_day), + )); self.swap_main(view); } @@ -162,7 +172,7 @@ impl AppWindow { let end = Local::now().date_naive(); let start = end - Duration::days(7); match s.app.records(start, end).await { - Ok(records) => s.show_historical_view(records), + Ok(records) => s.show_historical_view(start, end), Err(_) => s.show_welcome_view(), } } diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index c64d01a..ef9944c 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -140,31 +140,6 @@ impl DayDetail { .build(), ); - /* - let click_controller = gtk::GestureClick::new(); - click_controller.connect_released({ - let s = s.clone(); - move |_, _, _, _| { - println!("clicked outside of focusable entity"); - if let Some(widget) = s.focus_child().and_downcast_ref::() { - println!("focused child is the weight view"); - widget.blur(); - } - } - }); - s.add_controller(click_controller); - */ - - /* - let weight_record = records.iter().find_map(|record| match record { - Record { - id, - data: ft_core::TraxRecord::Weight(record), - } => Some((id.clone(), record.clone())), - _ => None, - }); - */ - let top_row = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); @@ -281,7 +256,17 @@ impl DayEdit { } fn finish(&self) { - (self.imp().on_finished.borrow())() + glib::spawn_future_local({ + let s = self.clone(); + async move { + let view_model = s.imp().view_model.borrow(); + let view_model = view_model + .as_ref() + .expect("DayEdit has not been initialized with the view model"); + let _ = view_model.save().await; + (s.imp().on_finished.borrow())() + } + }); } fn add_row(&self, workout: Record) { @@ -333,10 +318,7 @@ fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup .primary_action("Save", { let s = s.clone(); let view_model = view_model.clone(); - move || { - view_model.save(); - s.finish(); - } + move || s.finish() }) .secondary_action("Cancel", { let s = s.clone(); diff --git a/fitnesstrax/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs index 64195eb..7d938b9 100644 --- a/fitnesstrax/app/src/view_models/day_detail.rs +++ b/fitnesstrax/app/src/view_models/day_detail.rs @@ -100,12 +100,10 @@ pub struct DayDetailViewModel { weight: Arc>>>, steps: Arc>>>, records: Arc>>>, - - original_records: Vec>, } impl DayDetailViewModel { - pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { + pub async fn new(date: chrono::NaiveDate, app: App) -> Self { let s = Self { app, date, @@ -113,10 +111,8 @@ impl DayDetailViewModel { weight: Arc::new(RwLock::new(None)), steps: Arc::new(RwLock::new(None)), records: Arc::new(RwLock::new(HashMap::new())), - - original_records: records, }; - s.populate_records(); + s.populate_records().await; s } @@ -223,7 +219,7 @@ impl DayDetailViewModel { .collect::>>() } - pub fn save(&self) { + pub fn save(&self) -> glib::JoinHandle<()> { glib::spawn_future({ let s = self.clone(); async move { @@ -286,16 +282,18 @@ impl DayDetailViewModel { RecordState::Deleted(_) => unimplemented!(), } } + + s.populate_records().await; } - }); + }) } pub fn revert(&self) { self.populate_records(); } - fn populate_records(&self) { - let records = self.original_records.clone(); + async fn populate_records(&self) { + let records = self.app.records(self.date, self.date).await.unwrap(); let (weight_records, records): (Vec>, Vec>) = records.into_iter().partition(|r| r.data.is_weight()); diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index b388769..164dc85 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -60,12 +60,12 @@ impl ObjectSubclass for HistoricalViewPrivate { factory.connect_bind({ let app = s.app.clone(); move |_, list_item| { - let records = list_item + let date = list_item .downcast_ref::() .expect("should be a ListItem") .item() - .and_downcast::() - .expect("should be a DaySummary"); + .and_downcast::() + .expect("should be a Date"); let summary = list_item .downcast_ref::() @@ -75,11 +75,14 @@ impl ObjectSubclass for HistoricalViewPrivate { .expect("should be a DaySummary"); if let Some(app) = app.borrow().clone() { - summary.set_data(DayDetailViewModel::new( - records.date(), - records.records(), - app.clone(), - )); + let _ = glib::spawn_future_local(async move { + println!( + "setting up a DayDetailViewModel for {}", + date.date().format("%Y-%m-%d") + ); + let view_model = DayDetailViewModel::new(date.date(), app.clone()).await; + summary.set_data(view_model); + }); } } }); @@ -97,11 +100,7 @@ glib::wrapper! { } impl HistoricalView { - pub fn new( - app: App, - records: Vec>, - on_select_day: Rc, - ) -> Self + pub fn new(app: App, interval: DayInterval, on_select_day: Rc) -> Self where SelectFn: Fn(chrono::NaiveDate, Vec>) + 'static, { @@ -111,11 +110,8 @@ impl HistoricalView { *s.imp().app.borrow_mut() = Some(app); - let grouped_records = - GroupedRecords::new((*s.imp().time_window.borrow()).clone()).with_data(records); - - let mut model = gio::ListStore::new::(); - model.extend(grouped_records.items()); + let mut model = gio::ListStore::new::(); + model.extend(interval.days().map(|d| Date::new(d))); s.imp() .list_view .set_model(Some(>k::NoSelection::new(Some(model)))); @@ -125,8 +121,8 @@ impl HistoricalView { 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 records = item.downcast_ref::().unwrap(); - on_select_day(records.date(), records.records()); + let date = item.downcast_ref::().unwrap(); + on_select_day(date.date(), vec![]); } }); @@ -135,101 +131,37 @@ impl HistoricalView { s } - pub fn set_records(&self, records: Vec>) { - println!("set_records: {:?}", records); - let grouped_records = - GroupedRecords::new((self.imp().time_window.borrow()).clone()).with_data(records); - let mut model = gio::ListStore::new::(); - model.extend(grouped_records.items()); - self.imp() - .list_view - .set_model(Some(>k::NoSelection::new(Some(model)))); - } - pub fn time_window(&self) -> DayInterval { self.imp().time_window.borrow().clone() } } #[derive(Default)] -pub struct DayRecordsPrivate { +pub struct DatePrivate { date: RefCell, - records: RefCell>>, } #[glib::object_subclass] -impl ObjectSubclass for DayRecordsPrivate { - const NAME: &'static str = "DayRecords"; - type Type = DayRecords; +impl ObjectSubclass for DatePrivate { + const NAME: &'static str = "Date"; + type Type = Date; } -impl ObjectImpl for DayRecordsPrivate {} +impl ObjectImpl for DatePrivate {} glib::wrapper! { - pub struct DayRecords(ObjectSubclass); + pub struct Date(ObjectSubclass); } -impl DayRecords { - pub fn new(date: chrono::NaiveDate, records: Vec>) -> Self { +impl Date { + pub fn new(date: chrono::NaiveDate) -> Self { let s: Self = Object::builder().build(); - *s.imp().date.borrow_mut() = date; - *s.imp().records.borrow_mut() = records; - s } pub fn date(&self) -> chrono::NaiveDate { - *self.imp().date.borrow() - } - - pub fn records(&self) -> Vec> { - self.imp().records.borrow().clone() - } - - pub fn add_record(&self, record: Record) { - self.imp().records.borrow_mut().push(record); - } -} - -// This isn't feeling quite right. DayRecords is a glib object, but I'm not sure that I want to -// really be passing that around. It seems not generic enough. I feel like this whole grouped -// records thing can be made more generic. -struct GroupedRecords { - interval: DayInterval, - data: HashMap, -} - -impl GroupedRecords { - fn new(interval: DayInterval) -> Self { - let mut s = Self { - interval: interval.clone(), - data: HashMap::new(), - }; - interval.days().for_each(|date| { - let _ = s.data.insert(date, DayRecords::new(date, vec![])); - }); - s - } - - fn with_data(mut self, records: Vec>) -> Self { - records.into_iter().for_each(|record| { - self.data - .entry(record.date()) - .and_modify(|entry: &mut DayRecords| (*entry).add_record(record.clone())) - .or_insert(DayRecords::new(record.date(), vec![record])); - }); - - self - } - - fn items(&self) -> impl Iterator + '_ { - self.interval.days().map(|date| { - self.data - .get(&date) - .cloned() - .unwrap_or(DayRecords::new(date, vec![])) - }) + self.imp().date.borrow().clone() } }