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;