From 18e7e4fe2f7adb22be10f50d2c36826f71a5bfe5 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 18 Jan 2024 09:00:08 -0500 Subject: [PATCH] Start setting up the day detail view model I've created the view model and added a getter function for the weight. I'm passing the view model now to the DayDetailView, DayDetail, and DayEdit. I'm starting to set up the Save function for the view model, draining all of the updated records and saving them. None of the components yet save any updates to the view model, so updated_records is always going to be empty until I figure that out. --- fitnesstrax/app/src/app_window.rs | 11 +- fitnesstrax/app/src/components/day.rs | 77 ++++++------ fitnesstrax/app/src/components/weight.rs | 8 +- fitnesstrax/app/src/views/day_detail_view.rs | 126 ++++++++++++------- fitnesstrax/app/src/views/mod.rs | 2 +- 5 files changed, 134 insertions(+), 90 deletions(-) diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 5fc3213..23b0492 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -16,7 +16,9 @@ You should have received a copy of the GNU General Public License along with Fit use crate::{ app::App, - views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView}, + views::{ + DayDetailView, DayDetailViewModel, HistoricalView, PlaceholderView, View, WelcomeView, + }, }; use adw::prelude::*; use chrono::{Duration, Local}; @@ -137,7 +139,12 @@ impl AppWindow { 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(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) diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 1652d6c..9fa601f 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -16,7 +16,10 @@ You should have received a copy of the GNU General Public License along with Fit // use chrono::NaiveDate; // use ft_core::TraxRecord; -use crate::components::{ActionGroup, TimeDistanceView, Weight}; +use crate::{ + components::{ActionGroup, TimeDistanceView, Weight}, + views::DayDetailViewModel, +}; use emseries::Record; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; @@ -102,27 +105,14 @@ impl DaySummary { } } -pub struct DayDetailPrivate { - date: gtk::Label, - weight: RefCell>, -} +#[derive(Default)] +pub struct DayDetailPrivate {} #[glib::object_subclass] impl ObjectSubclass for DayDetailPrivate { const NAME: &'static str = "DayDetail"; type Type = DayDetail; type ParentType = gtk::Box; - - fn new() -> Self { - let date = gtk::Label::builder() - .css_classes(["daysummary-date"]) - .halign(gtk::Align::Start) - .build(); - Self { - date, - weight: RefCell::new(None), - } - } } impl ObjectImpl for DayDetailPrivate {} @@ -134,11 +124,7 @@ glib::wrapper! { } impl DayDetail { - pub fn new( - date: chrono::NaiveDate, - records: Vec>, - on_edit: OnEdit, - ) -> Self + pub fn new(view_model: DayDetailViewModel, on_edit: OnEdit) -> Self where OnEdit: Fn() + 'static, { @@ -148,7 +134,7 @@ impl DayDetail { s.append( &ActionGroup::builder() - .primary_action("Edit", Box::new(on_edit)) + .primary_action("Edit", Box::new(move || on_edit())) .build(), ); @@ -167,6 +153,7 @@ impl DayDetail { s.add_controller(click_controller); */ + /* let weight_record = records.iter().find_map(|record| match record { Record { id, @@ -174,13 +161,12 @@ impl DayDetail { } => Some((id.clone(), record.clone())), _ => None, }); + */ - let weight_view = match weight_record { - Some((id, data)) => Weight::new(Some(data.clone())), - None => Weight::new(None), - }; + let weight_view = Weight::new(view_model.weight()); s.append(&weight_view.widget()); + /* records.into_iter().for_each(|record| { let record_view = match record { Record { @@ -224,21 +210,20 @@ impl DayDetail { s.append(&record_view); } }); + */ s } } pub struct DayEditPrivate { - date: gtk::Label, - weight: Rc, + on_finished: RefCell>, } impl Default for DayEditPrivate { fn default() -> Self { Self { - date: gtk::Label::new(None), - weight: Rc::new(WeightEdit::new(None)), + on_finished: RefCell::new(Box::new(|| {})), } } } @@ -259,25 +244,20 @@ glib::wrapper! { } impl DayEdit { - pub fn new( - date: chrono::NaiveDate, - records: Vec>, - on_put_record: PutRecordFn, - on_update_record: UpdateRecordFn, - on_cancel: CancelFn, - ) -> Self + pub fn new(view_model: DayDetailViewModel, on_finished: OnFinished) -> Self where - PutRecordFn: Fn(ft_core::TraxRecord) + 'static, - UpdateRecordFn: Fn(Record) + 'static, - CancelFn: Fn() + 'static, + OnFinished: Fn() + 'static, { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_hexpand(true); + *s.imp().on_finished.borrow_mut() = Box::new(on_finished); + s.append( &ActionGroup::builder() .primary_action("Save", { + /* let s = s.clone(); let records = records.clone(); move || { @@ -309,11 +289,20 @@ impl DayEdit { } }; } + */ + let s = s.clone(); + move || { + s.finish(); + } + }) + .secondary_action("Cancel", { + let s = s.clone(); + move || s.finish() }) - .secondary_action("Cancel", on_cancel) .build(), ); + /* let weight_record = records.iter().find_map(|record| match record { Record { id, @@ -327,7 +316,13 @@ impl DayEdit { None => s.imp().weight.set_value(None), }; s.append(&s.imp().weight.widget()); + */ + s.append(&WeightEdit::new(view_model.weight()).widget()); s } + + fn finish(&self) { + (self.imp().on_finished.borrow())() + } } diff --git a/fitnesstrax/app/src/components/weight.rs b/fitnesstrax/app/src/components/weight.rs index 552b107..ddba3b5 100644 --- a/fitnesstrax/app/src/components/weight.rs +++ b/fitnesstrax/app/src/components/weight.rs @@ -29,14 +29,14 @@ pub struct Weight { } impl Weight { - pub fn new(weight: Option) -> Self { + pub fn new(weight: Option>) -> Self { let label = gtk::Label::builder() .css_classes(["card", "weight-view"]) .can_focus(true) .build(); match weight { - Some(w) => label.set_text(&format!("{:?}", w.weight)), + Some(w) => label.set_text(&format!("{:?}", w)), None => label.set_text("No weight recorded"), } @@ -54,11 +54,11 @@ pub struct WeightEdit { } impl WeightEdit { - pub fn new(weight: Option) -> Self { + pub fn new(weight: Option>) -> Self { Self { entry: TextEntry::new( "0 kg", - weight.map(|w| w.weight), + weight, |val: &si::Kilogram| val.to_string(), |v: &str| v.parse::().map(|w| w * si::KG).map_err(|_| ParseError), ), diff --git a/fitnesstrax/app/src/views/day_detail_view.rs b/fitnesstrax/app/src/views/day_detail_view.rs index c73d1f0..eab3611 100644 --- a/fitnesstrax/app/src/views/day_detail_view.rs +++ b/fitnesstrax/app/src/views/day_detail_view.rs @@ -18,18 +18,85 @@ use crate::{ app::App, components::{DayDetail, DayEdit, Singleton, SingletonImpl}, }; -use emseries::Record; +use dimensioned::si; +use emseries::{Record, RecordId}; use ft_core::TraxRecord; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::cell::RefCell; +use std::{ + cell::RefCell, + rc::Rc, + sync::{Arc, RwLock}, +}; + +#[derive(Default)] +struct DayDetailViewModelInner { + records: Vec>, + updated_records: Vec>, + new_records: Vec, + deleted_records: Vec, +} + +#[derive(Clone, Default)] +pub struct DayDetailViewModel { + app: Option, + pub date: chrono::NaiveDate, + inner: Arc>, +} + +impl DayDetailViewModel { + pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { + Self { + app: Some(app), + date, + inner: Arc::new(RwLock::new(DayDetailViewModelInner { + records, + updated_records: vec![], + new_records: vec![], + deleted_records: vec![], + })), + } + } + + pub fn weight(&self) -> Option> { + self.inner + .read() + .unwrap() + .records + .iter() + .find_map(|record| match record { + Record { + data: ft_core::TraxRecord::Weight(record), + .. + } => Some(record.weight.clone()), + _ => None, + }) + } + + pub fn save(&self) { + glib::spawn_future({ + let s = self.clone(); + async move { + if let Some(app) = s.app { + let updated_records = { + let mut data = s.inner.write().unwrap(); + data.updated_records + .drain(..) + .collect::>>() + }; + for record in updated_records.into_iter() { + let _ = app.update_record(record.clone()).await; + } + } + } + }); + } +} #[derive(Default)] pub struct DayDetailViewPrivate { - app: RefCell>, container: Singleton, - date: RefCell, - records: RefCell>>, + view_model: RefCell, } #[glib::object_subclass] @@ -49,62 +116,36 @@ glib::wrapper! { } impl DayDetailView { - pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { + pub fn new(view_model: DayDetailViewModel) -> Self { let s: Self = Object::builder().build(); - - *s.imp().date.borrow_mut() = date; - *s.imp().records.borrow_mut() = records; - *s.imp().app.borrow_mut() = Some(app); + *s.imp().view_model.borrow_mut() = view_model; s.append(&s.imp().container); - /* - s.imp() - .container - .swap(&DayDetail::new(date, records.clone(), { - let s = s.clone(); - let records = records.clone(); - move || { - s.imp().container.swap(&DayEdit::new( - date, - records, - s.on_put_record(), - // s.on_update_record(), - |_| {}, - )) - } - })); - */ - s.view(); s } fn view(&self) { - self.imp().container.swap(&DayDetail::new( - self.imp().date.borrow().clone(), - self.imp().records.borrow().clone(), - { + self.imp() + .container + .swap(&DayDetail::new(self.imp().view_model.borrow().clone(), { let s = self.clone(); move || s.edit() - }, - )); + })); } fn edit(&self) { - self.imp().container.swap(&DayEdit::new( - self.imp().date.borrow().clone(), - self.imp().records.borrow().clone(), - self.on_put_record(), - self.on_update_record(), - { + self.imp() + .container + .swap(&DayEdit::new(self.imp().view_model.borrow().clone(), { let s = self.clone(); move || s.view() - }, - )); + })); } + /* fn on_put_record(&self) -> Box { let s = self.clone(); let app = self.imp().app.clone(); @@ -161,4 +202,5 @@ impl DayDetailView { }); }) } + */ } diff --git a/fitnesstrax/app/src/views/mod.rs b/fitnesstrax/app/src/views/mod.rs index 9957823..4993459 100644 --- a/fitnesstrax/app/src/views/mod.rs +++ b/fitnesstrax/app/src/views/mod.rs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit use gtk::prelude::*; mod day_detail_view; -pub use day_detail_view::DayDetailView; +pub use day_detail_view::{DayDetailView, DayDetailViewModel}; mod historical_view; pub use historical_view::HistoricalView;