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.
This commit is contained in:
Savanni D'Gerinel 2024-01-18 09:00:08 -05:00
parent 1c2c4982a1
commit 18e7e4fe2f
5 changed files with 134 additions and 90 deletions

View File

@ -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)

View File

@ -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<Option<gtk::Label>>,
}
#[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<OnEdit>(
date: chrono::NaiveDate,
records: Vec<Record<ft_core::TraxRecord>>,
on_edit: OnEdit,
) -> Self
pub fn new<OnEdit>(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<WeightEdit>,
on_finished: RefCell<Box<dyn Fn()>>,
}
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<PutRecordFn, UpdateRecordFn, CancelFn>(
date: chrono::NaiveDate,
records: Vec<Record<ft_core::TraxRecord>>,
on_put_record: PutRecordFn,
on_update_record: UpdateRecordFn,
on_cancel: CancelFn,
) -> Self
pub fn new<OnFinished>(view_model: DayDetailViewModel, on_finished: OnFinished) -> Self
where
PutRecordFn: Fn(ft_core::TraxRecord) + 'static,
UpdateRecordFn: Fn(Record<ft_core::TraxRecord>) + '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())()
}
}

View File

@ -29,14 +29,14 @@ pub struct Weight {
}
impl Weight {
pub fn new(weight: Option<ft_core::Weight>) -> Self {
pub fn new(weight: Option<si::Kilogram<f64>>) -> 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<ft_core::Weight>) -> Self {
pub fn new(weight: Option<si::Kilogram<f64>>) -> Self {
Self {
entry: TextEntry::new(
"0 kg",
weight.map(|w| w.weight),
weight,
|val: &si::Kilogram<f64>| val.to_string(),
|v: &str| v.parse::<f64>().map(|w| w * si::KG).map_err(|_| ParseError),
),

View File

@ -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<Record<TraxRecord>>,
updated_records: Vec<Record<TraxRecord>>,
new_records: Vec<TraxRecord>,
deleted_records: Vec<RecordId>,
}
#[derive(Clone, Default)]
pub struct DayDetailViewModel {
app: Option<App>,
pub date: chrono::NaiveDate,
inner: Arc<RwLock<DayDetailViewModelInner>>,
}
impl DayDetailViewModel {
pub fn new(date: chrono::NaiveDate, records: Vec<Record<TraxRecord>>, 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<si::Kilogram<f64>> {
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::<Vec<Record<TraxRecord>>>()
};
for record in updated_records.into_iter() {
let _ = app.update_record(record.clone()).await;
}
}
}
});
}
}
#[derive(Default)]
pub struct DayDetailViewPrivate {
app: RefCell<Option<App>>,
container: Singleton,
date: RefCell<chrono::NaiveDate>,
records: RefCell<Vec<Record<TraxRecord>>>,
view_model: RefCell<DayDetailViewModel>,
}
#[glib::object_subclass]
@ -49,62 +116,36 @@ glib::wrapper! {
}
impl DayDetailView {
pub fn new(date: chrono::NaiveDate, records: Vec<Record<TraxRecord>>, 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<dyn Fn(ft_core::TraxRecord)> {
let s = self.clone();
let app = self.imp().app.clone();
@ -161,4 +202,5 @@ impl DayDetailView {
});
})
}
*/
}

View File

@ -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;