Save new time/distance records
This sets up a bunch of callbacks. We're starting to get into Callback Hell, where there are things that need knowledge that I really don't want them to have. However, edit fields for TimeDistanceEdit now propogate data back into the view model, which is then able to save the results.
This commit is contained in:
parent
2c42c35dfe
commit
73052a0694
|
@ -23,7 +23,7 @@ use crate::{
|
||||||
types::WeightFormatter,
|
types::WeightFormatter,
|
||||||
view_models::DayDetailViewModel,
|
view_models::DayDetailViewModel,
|
||||||
};
|
};
|
||||||
use emseries::Record;
|
use emseries::{Record, RecordId};
|
||||||
use ft_core::{TimeDistanceActivity, TraxRecord};
|
use ft_core::{TimeDistanceActivity, TraxRecord};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
@ -256,15 +256,32 @@ impl DayEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_row(&self, workout: Record<TraxRecord>) {
|
fn add_row(&self, workout: Record<TraxRecord>) {
|
||||||
println!("add_row: {:?}", workout);
|
println!("adding a row for {:?}", workout);
|
||||||
let workout_rows = self.imp().workout_rows.borrow();
|
let workout_rows = self.imp().workout_rows.borrow();
|
||||||
|
|
||||||
#[allow(clippy::single_match)]
|
#[allow(clippy::single_match)]
|
||||||
match workout.data {
|
match workout.data {
|
||||||
TraxRecord::TimeDistance(r) => workout_rows.append(&TimeDistanceEdit::new(r, |_| {})),
|
TraxRecord::TimeDistance(r) => workout_rows.append(&TimeDistanceEdit::new(r, {
|
||||||
|
let s = self.clone();
|
||||||
|
move |data| {
|
||||||
|
println!("update workout callback on workout: {:?}", workout.id);
|
||||||
|
s.update_workout(workout.id, data)
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_workout(&self, id: RecordId, data: ft_core::TimeDistance) {
|
||||||
|
if let Some(ref view_model) = *self.imp().view_model.borrow() {
|
||||||
|
let record = Record {
|
||||||
|
id,
|
||||||
|
data: TraxRecord::TimeDistance(data),
|
||||||
|
};
|
||||||
|
view_model.update_record(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup {
|
fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup {
|
||||||
|
|
|
@ -50,7 +50,7 @@ where
|
||||||
"0",
|
"0",
|
||||||
value,
|
value,
|
||||||
|v| format!("{}", v),
|
|v| format!("{}", v),
|
||||||
move |v| v.parse::<u32>().map_err(|_| ParseError),
|
|v| v.parse::<u32>().map_err(|_| ParseError),
|
||||||
on_update,
|
on_update,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ use crate::types::{
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
type Parser<T> = dyn Fn(&str) -> Result<T, ParseError>;
|
pub type Parser<T> = dyn Fn(&str) -> Result<T, ParseError>;
|
||||||
type OnUpdate<T> = dyn Fn(Option<T>);
|
pub type OnUpdate<T> = dyn Fn(Option<T>);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TextEntry<T: Clone + std::fmt::Debug> {
|
pub struct TextEntry<T: Clone + std::fmt::Debug> {
|
||||||
|
|
|
@ -17,11 +17,15 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||||
// use crate::components::{EditView, ParseError, TextEntry};
|
// use crate::components::{EditView, ParseError, TextEntry};
|
||||||
// use chrono::{Local, NaiveDate};
|
// use chrono::{Local, NaiveDate};
|
||||||
// use dimensioned::si;
|
// use dimensioned::si;
|
||||||
|
use crate::{
|
||||||
|
components::{distance_field, duration_field, time_field},
|
||||||
|
types::{DistanceFormatter, DurationFormatter, TimeFormatter},
|
||||||
|
};
|
||||||
use dimensioned::si;
|
use dimensioned::si;
|
||||||
use ft_core::TimeDistance;
|
use ft_core::{TimeDistance, TimeDistanceActivity};
|
||||||
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};
|
||||||
|
|
||||||
pub fn time_distance_summary(
|
pub fn time_distance_summary(
|
||||||
distance: si::Meter<f64>,
|
distance: si::Meter<f64>,
|
||||||
|
@ -106,10 +110,27 @@ pub fn time_distance_detail(record: ft_core::TimeDistance) -> gtk::Box {
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
type OnUpdate = Rc<RefCell<Box<dyn Fn(TimeDistance)>>>;
|
||||||
|
|
||||||
pub struct TimeDistanceEditPrivate {
|
pub struct TimeDistanceEditPrivate {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
record: RefCell<Option<ft_core::TimeDistance>>,
|
workout: RefCell<ft_core::TimeDistance>,
|
||||||
|
on_update: OnUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TimeDistanceEditPrivate {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
workout: RefCell::new(TimeDistance {
|
||||||
|
datetime: chrono::Utc::now().into(),
|
||||||
|
activity: TimeDistanceActivity::BikeRide,
|
||||||
|
duration: None,
|
||||||
|
distance: None,
|
||||||
|
comments: None,
|
||||||
|
}),
|
||||||
|
on_update: Rc::new(RefCell::new(Box::new(|_| {}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -130,7 +151,7 @@ glib::wrapper! {
|
||||||
impl Default for TimeDistanceEdit {
|
impl Default for TimeDistanceEdit {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_orientation(gtk::Orientation::Horizontal);
|
s.set_orientation(gtk::Orientation::Vertical);
|
||||||
s.set_hexpand(true);
|
s.set_hexpand(true);
|
||||||
s.set_css_classes(&["time-distance-edit"]);
|
s.set_css_classes(&["time-distance-edit"]);
|
||||||
|
|
||||||
|
@ -139,23 +160,62 @@ impl Default for TimeDistanceEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimeDistanceEdit {
|
impl TimeDistanceEdit {
|
||||||
pub fn new<OnUpdate>(record: TimeDistance, _on_update: OnUpdate) -> Self
|
pub fn new<OnUpdate>(workout: TimeDistance, on_update: OnUpdate) -> Self
|
||||||
where
|
where
|
||||||
OnUpdate: Fn(&ft_core::TimeDistance),
|
OnUpdate: Fn(TimeDistance) + 'static,
|
||||||
{
|
{
|
||||||
println!("new TimeDistanceEdit");
|
|
||||||
let s = Self::default();
|
let s = Self::default();
|
||||||
|
|
||||||
s.append(>k::Label::new(Some(
|
*s.imp().workout.borrow_mut() = workout.clone();
|
||||||
record.datetime.format("%H:%M").to_string().as_ref(),
|
*s.imp().on_update.borrow_mut() = Box::new(on_update);
|
||||||
)));
|
|
||||||
|
let details_row = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
details_row.append(
|
||||||
|
&time_field(
|
||||||
|
Some(TimeFormatter::from(workout.datetime.naive_local().time())),
|
||||||
|
{
|
||||||
|
let s = s.clone();
|
||||||
|
move |t| s.update_time(t)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.widget(),
|
||||||
|
);
|
||||||
|
details_row.append(
|
||||||
|
&distance_field(workout.distance.map(DistanceFormatter::from), {
|
||||||
|
let s = s.clone();
|
||||||
|
move |d| s.update_distance(d)
|
||||||
|
})
|
||||||
|
.widget(),
|
||||||
|
);
|
||||||
|
details_row.append(
|
||||||
|
&duration_field(workout.duration.map(DurationFormatter::from), {
|
||||||
|
let s = s.clone();
|
||||||
|
move |d| s.update_duration(d)
|
||||||
|
})
|
||||||
|
.widget(),
|
||||||
|
);
|
||||||
|
s.append(&details_row);
|
||||||
|
s.append(>k::Entry::new());
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
fn update_time(&self, _time: Option<TimeFormatter>) {
|
||||||
fn with_record<OnUpdate>(type_: ft_core::RecordType, record: ft_core::TimeDistance, on_update: OnUpdate) -> Self
|
unimplemented!()
|
||||||
where OnUpdate: Fn(&ft_core::RecordType, &ft_core::TimeDistance) {
|
}
|
||||||
|
|
||||||
|
fn update_distance(&self, distance: Option<DistanceFormatter>) {
|
||||||
|
let mut workout = self.imp().workout.borrow_mut();
|
||||||
|
workout.distance = distance.map(|d| *d);
|
||||||
|
(self.imp().on_update.borrow())(workout.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_duration(&self, duration: Option<DurationFormatter>) {
|
||||||
|
let mut workout = self.imp().workout.borrow_mut();
|
||||||
|
workout.duration = duration.map(|d| *d);
|
||||||
|
(self.imp().on_update.borrow())(workout.clone());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,7 @@ impl<T: Clone + emseries::Recordable> RecordState<T> {
|
||||||
fn set_value(&mut self, value: T) {
|
fn set_value(&mut self, value: T) {
|
||||||
*self = match self {
|
*self = match self {
|
||||||
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..*r }),
|
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||||
RecordState::New(_) => RecordState::New(Record {
|
RecordState::New(r) => RecordState::New(Record { data: value, ..*r }),
|
||||||
id: RecordId::default(),
|
|
||||||
data: value,
|
|
||||||
}),
|
|
||||||
RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..*r }),
|
RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||||
RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..*r }),
|
RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||||
};
|
};
|
||||||
|
@ -225,22 +222,9 @@ impl DayDetailViewModel {
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert(id, RecordState::New(Record { id, data: tr }));
|
.insert(id, RecordState::New(Record { id, data: tr }));
|
||||||
println!(
|
|
||||||
"records after new_time_distance: {:?}",
|
|
||||||
self.records.read().unwrap()
|
|
||||||
);
|
|
||||||
Record { id, data: workout }
|
Record { id, data: workout }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_time_distance(&self, workout: Record<TimeDistance>) {
|
|
||||||
let data = workout.data.clone();
|
|
||||||
|
|
||||||
let mut record_set = self.records.write().unwrap();
|
|
||||||
record_set.entry(workout.id).and_modify(|record_state| {
|
|
||||||
record_state.set_value(TraxRecord::TimeDistance(data));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn time_distance_records(&self) -> Vec<Record<TimeDistance>> {
|
pub fn time_distance_records(&self) -> Vec<Record<TimeDistance>> {
|
||||||
self.records
|
self.records
|
||||||
.read()
|
.read()
|
||||||
|
@ -278,6 +262,24 @@ impl DayDetailViewModel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_record(&self, update: Record<TraxRecord>) {
|
||||||
|
println!("updating a record: {:?}", update);
|
||||||
|
let mut records = self.records.write().unwrap();
|
||||||
|
records
|
||||||
|
.entry(update.id)
|
||||||
|
.and_modify(|record| record.set_value(update.data));
|
||||||
|
println!("record updated: {:?}", records.get(&update.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn records(&self) -> Vec<Record<TraxRecord>> {
|
||||||
|
let read_lock = self.records.read().unwrap();
|
||||||
|
read_lock
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, record_state)| record_state.data())
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<Record<TraxRecord>>>()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn get_record(&self, id: &RecordId) -> Option<Record<TraxRecord>> {
|
fn get_record(&self, id: &RecordId) -> Option<Record<TraxRecord>> {
|
||||||
let record_set = self.records.read().unwrap();
|
let record_set = self.records.read().unwrap();
|
||||||
|
@ -604,11 +606,8 @@ mod test {
|
||||||
|
|
||||||
let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide);
|
let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide);
|
||||||
record.data.duration = Some(60. * si::S);
|
record.data.duration = Some(60. * si::S);
|
||||||
view_model.update_time_distance(record.clone());
|
let record = record.map(TraxRecord::TimeDistance);
|
||||||
let record = Record {
|
view_model.update_record(record.clone());
|
||||||
id: record.id,
|
|
||||||
data: TraxRecord::TimeDistance(record.data),
|
|
||||||
};
|
|
||||||
assert_eq!(view_model.get_record(&record.id), Some(record));
|
assert_eq!(view_model.get_record(&record.id), Some(record));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||||
|
@ -630,10 +629,8 @@ mod test {
|
||||||
let (view_model, provider) = create_view_model().await;
|
let (view_model, provider) = create_view_model().await;
|
||||||
let mut workout = view_model.time_distance_records().first().cloned().unwrap();
|
let mut workout = view_model.time_distance_records().first().cloned().unwrap();
|
||||||
|
|
||||||
println!("found record: {:?}", workout);
|
|
||||||
|
|
||||||
workout.data.duration = Some(1800. * si::S);
|
workout.data.duration = Some(1800. * si::S);
|
||||||
view_model.update_time_distance(workout.clone());
|
view_model.update_record(workout.map(TraxRecord::TimeDistance));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||||
|
|
Loading…
Reference in New Issue