Render and be able to edit bike rides (and sorta other time distance workouts) #169
|
@ -23,7 +23,7 @@ use crate::{
|
|||
types::WeightFormatter,
|
||||
view_models::DayDetailViewModel,
|
||||
};
|
||||
use emseries::Record;
|
||||
use emseries::{Record, RecordId};
|
||||
use ft_core::{TimeDistanceActivity, TraxRecord};
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
|
@ -256,15 +256,32 @@ impl DayEdit {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
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 {
|
||||
|
|
|
@ -50,7 +50,7 @@ where
|
|||
"0",
|
||||
value,
|
||||
|v| format!("{}", v),
|
||||
move |v| v.parse::<u32>().map_err(|_| ParseError),
|
||||
|v| v.parse::<u32>().map_err(|_| ParseError),
|
||||
on_update,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ use crate::types::{
|
|||
use gtk::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
type Parser<T> = dyn Fn(&str) -> Result<T, ParseError>;
|
||||
type OnUpdate<T> = dyn Fn(Option<T>);
|
||||
pub type Parser<T> = dyn Fn(&str) -> Result<T, ParseError>;
|
||||
pub type OnUpdate<T> = dyn Fn(Option<T>);
|
||||
|
||||
#[derive(Clone)]
|
||||
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 chrono::{Local, NaiveDate};
|
||||
// use dimensioned::si;
|
||||
use crate::{
|
||||
components::{distance_field, duration_field, time_field},
|
||||
types::{DistanceFormatter, DurationFormatter, TimeFormatter},
|
||||
};
|
||||
use dimensioned::si;
|
||||
use ft_core::TimeDistance;
|
||||
use ft_core::{TimeDistance, TimeDistanceActivity};
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub fn time_distance_summary(
|
||||
distance: si::Meter<f64>,
|
||||
|
@ -106,10 +110,27 @@ pub fn time_distance_detail(record: ft_core::TimeDistance) -> gtk::Box {
|
|||
layout
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
type OnUpdate = Rc<RefCell<Box<dyn Fn(TimeDistance)>>>;
|
||||
|
||||
pub struct TimeDistanceEditPrivate {
|
||||
#[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]
|
||||
|
@ -130,7 +151,7 @@ glib::wrapper! {
|
|||
impl Default for TimeDistanceEdit {
|
||||
fn default() -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
s.set_orientation(gtk::Orientation::Horizontal);
|
||||
s.set_orientation(gtk::Orientation::Vertical);
|
||||
s.set_hexpand(true);
|
||||
s.set_css_classes(&["time-distance-edit"]);
|
||||
|
||||
|
@ -139,23 +160,62 @@ impl Default for TimeDistanceEdit {
|
|||
}
|
||||
|
||||
impl TimeDistanceEdit {
|
||||
pub fn new<OnUpdate>(record: TimeDistance, _on_update: OnUpdate) -> Self
|
||||
pub fn new<OnUpdate>(workout: TimeDistance, on_update: OnUpdate) -> Self
|
||||
where
|
||||
OnUpdate: Fn(&ft_core::TimeDistance),
|
||||
OnUpdate: Fn(TimeDistance) + 'static,
|
||||
{
|
||||
println!("new TimeDistanceEdit");
|
||||
let s = Self::default();
|
||||
|
||||
s.append(>k::Label::new(Some(
|
||||
record.datetime.format("%H:%M").to_string().as_ref(),
|
||||
)));
|
||||
*s.imp().workout.borrow_mut() = workout.clone();
|
||||
*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
|
||||
}
|
||||
|
||||
/*
|
||||
fn with_record<OnUpdate>(type_: ft_core::RecordType, record: ft_core::TimeDistance, on_update: OnUpdate) -> Self
|
||||
where OnUpdate: Fn(&ft_core::RecordType, &ft_core::TimeDistance) {
|
||||
fn update_time(&self, _time: Option<TimeFormatter>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
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) {
|
||||
*self = match self {
|
||||
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||
RecordState::New(_) => RecordState::New(Record {
|
||||
id: RecordId::default(),
|
||||
data: value,
|
||||
}),
|
||||
RecordState::New(r) => RecordState::New(Record { data: value, ..*r }),
|
||||
RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||
RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||
};
|
||||
|
@ -225,22 +222,9 @@ impl DayDetailViewModel {
|
|||
.write()
|
||||
.unwrap()
|
||||
.insert(id, RecordState::New(Record { id, data: tr }));
|
||||
println!(
|
||||
"records after new_time_distance: {:?}",
|
||||
self.records.read().unwrap()
|
||||
);
|
||||
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>> {
|
||||
self.records
|
||||
.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)]
|
||||
fn get_record(&self, id: &RecordId) -> Option<Record<TraxRecord>> {
|
||||
let record_set = self.records.read().unwrap();
|
||||
|
@ -604,11 +606,8 @@ mod test {
|
|||
|
||||
let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide);
|
||||
record.data.duration = Some(60. * si::S);
|
||||
view_model.update_time_distance(record.clone());
|
||||
let record = Record {
|
||||
id: record.id,
|
||||
data: TraxRecord::TimeDistance(record.data),
|
||||
};
|
||||
let record = record.map(TraxRecord::TimeDistance);
|
||||
view_model.update_record(record.clone());
|
||||
assert_eq!(view_model.get_record(&record.id), Some(record));
|
||||
assert_eq!(
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
|
@ -630,10 +629,8 @@ mod test {
|
|||
let (view_model, provider) = create_view_model().await;
|
||||
let mut workout = view_model.time_distance_records().first().cloned().unwrap();
|
||||
|
||||
println!("found record: {:?}", workout);
|
||||
|
||||
workout.data.duration = Some(1800. * si::S);
|
||||
view_model.update_time_distance(workout.clone());
|
||||
view_model.update_record(workout.map(TraxRecord::TimeDistance));
|
||||
|
||||
assert_eq!(
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
|
|
Loading…
Reference in New Issue