Render and be able to edit bike rides (and sorta other time distance workouts) #169
|
@ -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