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:
parent
1c2c4982a1
commit
18e7e4fe2f
|
@ -16,7 +16,9 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView},
|
views::{
|
||||||
|
DayDetailView, DayDetailViewModel, HistoricalView, PlaceholderView, View, WelcomeView,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use chrono::{Duration, Local};
|
use chrono::{Duration, Local};
|
||||||
|
@ -137,7 +139,12 @@ impl AppWindow {
|
||||||
Rc::new(move |date, records| {
|
Rc::new(move |date, records| {
|
||||||
let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
layout.append(&adw::HeaderBar::new());
|
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()
|
let page = &adw::NavigationPage::builder()
|
||||||
.title(date.format("%Y-%m-%d").to_string())
|
.title(date.format("%Y-%m-%d").to_string())
|
||||||
.child(&layout)
|
.child(&layout)
|
||||||
|
|
|
@ -16,7 +16,10 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||||
|
|
||||||
// use chrono::NaiveDate;
|
// use chrono::NaiveDate;
|
||||||
// use ft_core::TraxRecord;
|
// use ft_core::TraxRecord;
|
||||||
use crate::components::{ActionGroup, TimeDistanceView, Weight};
|
use crate::{
|
||||||
|
components::{ActionGroup, TimeDistanceView, Weight},
|
||||||
|
views::DayDetailViewModel,
|
||||||
|
};
|
||||||
use emseries::Record;
|
use emseries::Record;
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
@ -102,27 +105,14 @@ impl DaySummary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DayDetailPrivate {
|
#[derive(Default)]
|
||||||
date: gtk::Label,
|
pub struct DayDetailPrivate {}
|
||||||
weight: RefCell<Option<gtk::Label>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for DayDetailPrivate {
|
impl ObjectSubclass for DayDetailPrivate {
|
||||||
const NAME: &'static str = "DayDetail";
|
const NAME: &'static str = "DayDetail";
|
||||||
type Type = DayDetail;
|
type Type = DayDetail;
|
||||||
type ParentType = gtk::Box;
|
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 {}
|
impl ObjectImpl for DayDetailPrivate {}
|
||||||
|
@ -134,11 +124,7 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DayDetail {
|
impl DayDetail {
|
||||||
pub fn new<OnEdit>(
|
pub fn new<OnEdit>(view_model: DayDetailViewModel, on_edit: OnEdit) -> Self
|
||||||
date: chrono::NaiveDate,
|
|
||||||
records: Vec<Record<ft_core::TraxRecord>>,
|
|
||||||
on_edit: OnEdit,
|
|
||||||
) -> Self
|
|
||||||
where
|
where
|
||||||
OnEdit: Fn() + 'static,
|
OnEdit: Fn() + 'static,
|
||||||
{
|
{
|
||||||
|
@ -148,7 +134,7 @@ impl DayDetail {
|
||||||
|
|
||||||
s.append(
|
s.append(
|
||||||
&ActionGroup::builder()
|
&ActionGroup::builder()
|
||||||
.primary_action("Edit", Box::new(on_edit))
|
.primary_action("Edit", Box::new(move || on_edit()))
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -167,6 +153,7 @@ impl DayDetail {
|
||||||
s.add_controller(click_controller);
|
s.add_controller(click_controller);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
let weight_record = records.iter().find_map(|record| match record {
|
let weight_record = records.iter().find_map(|record| match record {
|
||||||
Record {
|
Record {
|
||||||
id,
|
id,
|
||||||
|
@ -174,13 +161,12 @@ impl DayDetail {
|
||||||
} => Some((id.clone(), record.clone())),
|
} => Some((id.clone(), record.clone())),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
let weight_view = match weight_record {
|
let weight_view = Weight::new(view_model.weight());
|
||||||
Some((id, data)) => Weight::new(Some(data.clone())),
|
|
||||||
None => Weight::new(None),
|
|
||||||
};
|
|
||||||
s.append(&weight_view.widget());
|
s.append(&weight_view.widget());
|
||||||
|
|
||||||
|
/*
|
||||||
records.into_iter().for_each(|record| {
|
records.into_iter().for_each(|record| {
|
||||||
let record_view = match record {
|
let record_view = match record {
|
||||||
Record {
|
Record {
|
||||||
|
@ -224,21 +210,20 @@ impl DayDetail {
|
||||||
s.append(&record_view);
|
s.append(&record_view);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DayEditPrivate {
|
pub struct DayEditPrivate {
|
||||||
date: gtk::Label,
|
on_finished: RefCell<Box<dyn Fn()>>,
|
||||||
weight: Rc<WeightEdit>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DayEditPrivate {
|
impl Default for DayEditPrivate {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
date: gtk::Label::new(None),
|
on_finished: RefCell::new(Box::new(|| {})),
|
||||||
weight: Rc::new(WeightEdit::new(None)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,25 +244,20 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DayEdit {
|
impl DayEdit {
|
||||||
pub fn new<PutRecordFn, UpdateRecordFn, CancelFn>(
|
pub fn new<OnFinished>(view_model: DayDetailViewModel, on_finished: OnFinished) -> Self
|
||||||
date: chrono::NaiveDate,
|
|
||||||
records: Vec<Record<ft_core::TraxRecord>>,
|
|
||||||
on_put_record: PutRecordFn,
|
|
||||||
on_update_record: UpdateRecordFn,
|
|
||||||
on_cancel: CancelFn,
|
|
||||||
) -> Self
|
|
||||||
where
|
where
|
||||||
PutRecordFn: Fn(ft_core::TraxRecord) + 'static,
|
OnFinished: Fn() + 'static,
|
||||||
UpdateRecordFn: Fn(Record<ft_core::TraxRecord>) + 'static,
|
|
||||||
CancelFn: Fn() + 'static,
|
|
||||||
{
|
{
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_orientation(gtk::Orientation::Vertical);
|
s.set_orientation(gtk::Orientation::Vertical);
|
||||||
s.set_hexpand(true);
|
s.set_hexpand(true);
|
||||||
|
|
||||||
|
*s.imp().on_finished.borrow_mut() = Box::new(on_finished);
|
||||||
|
|
||||||
s.append(
|
s.append(
|
||||||
&ActionGroup::builder()
|
&ActionGroup::builder()
|
||||||
.primary_action("Save", {
|
.primary_action("Save", {
|
||||||
|
/*
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
let records = records.clone();
|
let records = records.clone();
|
||||||
move || {
|
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(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
let weight_record = records.iter().find_map(|record| match record {
|
let weight_record = records.iter().find_map(|record| match record {
|
||||||
Record {
|
Record {
|
||||||
id,
|
id,
|
||||||
|
@ -327,7 +316,13 @@ impl DayEdit {
|
||||||
None => s.imp().weight.set_value(None),
|
None => s.imp().weight.set_value(None),
|
||||||
};
|
};
|
||||||
s.append(&s.imp().weight.widget());
|
s.append(&s.imp().weight.widget());
|
||||||
|
*/
|
||||||
|
s.append(&WeightEdit::new(view_model.weight()).widget());
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish(&self) {
|
||||||
|
(self.imp().on_finished.borrow())()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,14 +29,14 @@ pub struct Weight {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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()
|
let label = gtk::Label::builder()
|
||||||
.css_classes(["card", "weight-view"])
|
.css_classes(["card", "weight-view"])
|
||||||
.can_focus(true)
|
.can_focus(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
match weight {
|
match weight {
|
||||||
Some(w) => label.set_text(&format!("{:?}", w.weight)),
|
Some(w) => label.set_text(&format!("{:?}", w)),
|
||||||
None => label.set_text("No weight recorded"),
|
None => label.set_text("No weight recorded"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,11 +54,11 @@ pub struct WeightEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WeightEdit {
|
impl WeightEdit {
|
||||||
pub fn new(weight: Option<ft_core::Weight>) -> Self {
|
pub fn new(weight: Option<si::Kilogram<f64>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entry: TextEntry::new(
|
entry: TextEntry::new(
|
||||||
"0 kg",
|
"0 kg",
|
||||||
weight.map(|w| w.weight),
|
weight,
|
||||||
|val: &si::Kilogram<f64>| val.to_string(),
|
|val: &si::Kilogram<f64>| val.to_string(),
|
||||||
|v: &str| v.parse::<f64>().map(|w| w * si::KG).map_err(|_| ParseError),
|
|v: &str| v.parse::<f64>().map(|w| w * si::KG).map_err(|_| ParseError),
|
||||||
),
|
),
|
||||||
|
|
|
@ -18,18 +18,85 @@ use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
components::{DayDetail, DayEdit, Singleton, SingletonImpl},
|
components::{DayDetail, DayEdit, Singleton, SingletonImpl},
|
||||||
};
|
};
|
||||||
use emseries::Record;
|
use dimensioned::si;
|
||||||
|
use emseries::{Record, RecordId};
|
||||||
use ft_core::TraxRecord;
|
use ft_core::TraxRecord;
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
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)]
|
#[derive(Default)]
|
||||||
pub struct DayDetailViewPrivate {
|
pub struct DayDetailViewPrivate {
|
||||||
app: RefCell<Option<App>>,
|
|
||||||
container: Singleton,
|
container: Singleton,
|
||||||
date: RefCell<chrono::NaiveDate>,
|
view_model: RefCell<DayDetailViewModel>,
|
||||||
records: RefCell<Vec<Record<TraxRecord>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -49,62 +116,36 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DayDetailView {
|
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();
|
let s: Self = Object::builder().build();
|
||||||
|
*s.imp().view_model.borrow_mut() = view_model;
|
||||||
*s.imp().date.borrow_mut() = date;
|
|
||||||
*s.imp().records.borrow_mut() = records;
|
|
||||||
*s.imp().app.borrow_mut() = Some(app);
|
|
||||||
|
|
||||||
s.append(&s.imp().container);
|
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.view();
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) {
|
fn view(&self) {
|
||||||
self.imp().container.swap(&DayDetail::new(
|
self.imp()
|
||||||
self.imp().date.borrow().clone(),
|
.container
|
||||||
self.imp().records.borrow().clone(),
|
.swap(&DayDetail::new(self.imp().view_model.borrow().clone(), {
|
||||||
{
|
|
||||||
let s = self.clone();
|
let s = self.clone();
|
||||||
move || s.edit()
|
move || s.edit()
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit(&self) {
|
fn edit(&self) {
|
||||||
self.imp().container.swap(&DayEdit::new(
|
self.imp()
|
||||||
self.imp().date.borrow().clone(),
|
.container
|
||||||
self.imp().records.borrow().clone(),
|
.swap(&DayEdit::new(self.imp().view_model.borrow().clone(), {
|
||||||
self.on_put_record(),
|
|
||||||
self.on_update_record(),
|
|
||||||
{
|
|
||||||
let s = self.clone();
|
let s = self.clone();
|
||||||
move || s.view()
|
move || s.view()
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
fn on_put_record(&self) -> Box<dyn Fn(ft_core::TraxRecord)> {
|
fn on_put_record(&self) -> Box<dyn Fn(ft_core::TraxRecord)> {
|
||||||
let s = self.clone();
|
let s = self.clone();
|
||||||
let app = self.imp().app.clone();
|
let app = self.imp().app.clone();
|
||||||
|
@ -161,4 +202,5 @@ impl DayDetailView {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
mod day_detail_view;
|
mod day_detail_view;
|
||||||
pub use day_detail_view::DayDetailView;
|
pub use day_detail_view::{DayDetailView, DayDetailViewModel};
|
||||||
|
|
||||||
mod historical_view;
|
mod historical_view;
|
||||||
pub use historical_view::HistoricalView;
|
pub use historical_view::HistoricalView;
|
||||||
|
|
Loading…
Reference in New Issue