Compare commits

..

2 Commits

Author SHA1 Message Date
Savanni D'Gerinel 1d6155d9e5 Finish the update and delete view model functions 2024-02-07 09:29:08 -05:00
Savanni D'Gerinel a8bf540517 Remove test that steps and weights are honored correctly
Step and weight records may be presented in any order. Any test that
tries to enforce that one gets presented before teh other can't cannot
succeed. So, I've removed that test and instead put in a warning that
will appear when the view model gets loaded.
2024-02-07 08:28:38 -05:00
1 changed files with 91 additions and 56 deletions

View File

@ -30,7 +30,6 @@ enum RecordState<T: Clone + Recordable> {
Original(Record<T>), Original(Record<T>),
New(T), New(T),
Updated(Record<T>), Updated(Record<T>),
#[allow(unused)]
Deleted(Record<T>), Deleted(Record<T>),
} }
@ -45,6 +44,15 @@ impl<T: Clone + emseries::Recordable> RecordState<T> {
} }
} }
fn exists(&self) -> bool {
match self {
RecordState::Original(ref r) => true,
RecordState::New(ref r) => true,
RecordState::Updated(ref r) => true,
RecordState::Deleted(ref r) => false,
}
}
fn with_value(self, value: T) -> RecordState<T> { fn with_value(self, value: T) -> RecordState<T> {
match self { match self {
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..r }), RecordState::Original(r) => RecordState::Updated(Record { data: value, ..r }),
@ -110,6 +118,14 @@ impl DayDetailViewModel {
.partition(|r| r.data.is_weight()); .partition(|r| r.data.is_weight());
let (step_records, records): (Vec<Record<TraxRecord>>, Vec<Record<TraxRecord>>) = let (step_records, records): (Vec<Record<TraxRecord>>, Vec<Record<TraxRecord>>) =
records.into_iter().partition(|r| r.data.is_steps()); records.into_iter().partition(|r| r.data.is_steps());
if weight_records.len() > 1 {
eprintln!("warning: multiple weight records found for {}. This is unsupported and the one presented is unpredictable.", date.format("%Y-%m-%d"));
}
if step_records.len() > 1 {
eprintln!("warning: multiple step records found for {}. This is unsupported and the one presented is unpredictable.", date.format("%Y-%m-%d"));
}
Ok(Self { Ok(Self {
provider: Arc::new(provider), provider: Arc::new(provider),
date, date,
@ -203,52 +219,66 @@ impl DayDetailViewModel {
let id = workout.id.clone(); let id = workout.id.clone();
let data = workout.data.clone(); let data = workout.data.clone();
self.records let mut record_set = self.records.write().unwrap();
.write() if let Some(record_state) = record_set.get(&id).clone() {
.unwrap() let updated_state = match **record_state {
.entry(id) TraxRecord::BikeRide(_) => {
.and_modify(|r| match **r { Some(record_state.clone().with_value(TraxRecord::BikeRide(data)))
TraxRecord::BikeRide(ref mut v) => *v = data, }
TraxRecord::Row(ref mut v) => *v = data, TraxRecord::Row(_) => Some(record_state.clone().with_value(TraxRecord::Row(data))),
TraxRecord::Run(ref mut v) => *v = data, TraxRecord::Run(_) => Some(record_state.clone().with_value(TraxRecord::Run(data))),
TraxRecord::Swim(ref mut v) => *v = data, TraxRecord::Swim(_) => {
TraxRecord::Walk(ref mut v) => *v = data, Some(record_state.clone().with_value(TraxRecord::Swim(data)))
_ => {} }
}); TraxRecord::Walk(_) => {
Some(record_state.clone().with_value(TraxRecord::Walk(data)))
}
_ => None,
};
if let Some(updated_state) = updated_state {
record_set.insert(id, updated_state);
}
}
} }
pub fn time_distance_records(&self, type_: RecordType) -> Vec<Record<TimeDistance>> { pub fn time_distance_records(
unimplemented!("time_distance_records")
}
pub fn time_distance_summary(
&self, &self,
type_: TimeDistanceWorkoutType, type_: TimeDistanceWorkoutType,
) -> (si::Meter<f64>, si::Second<f64>) { ) -> Vec<Record<TimeDistance>> {
self.records self.records
.read() .read()
.unwrap() .unwrap()
.iter() .iter()
.filter(|(_, record)| record.exists())
.filter(|(_, workout_state)| workout_state.is_time_distance_type(type_)) .filter(|(_, workout_state)| workout_state.is_time_distance_type(type_))
.filter_map(|(id, record_state)| match **record_state { .filter_map(|(id, record_state)| match **record_state {
TraxRecord::BikeRide(ref workout) TraxRecord::BikeRide(ref workout)
| TraxRecord::Row(ref workout) | TraxRecord::Row(ref workout)
| TraxRecord::Run(ref workout) | TraxRecord::Run(ref workout)
| TraxRecord::Swim(ref workout) | TraxRecord::Swim(ref workout)
| TraxRecord::Walk(ref workout) => Some(workout), | TraxRecord::Walk(ref workout) => Some(Record {
id: id.clone(),
data: workout.clone(),
}),
_ => None, _ => None,
}) })
.fold((0. * si::M, 0. * si::S), |(distance, duration), workout| { .collect()
println!("folding workout: {:?}", workout); }
match (workout.distance, workout.duration) {
(Some(distance_), Some(duration_)) => { pub fn time_distance_summary(
(distance + distance_, duration + duration_) &self,
} type_: TimeDistanceWorkoutType,
(Some(distance_), None) => (distance + distance_, duration), ) -> (si::Meter<f64>, si::Second<f64>) {
(None, Some(duration_)) => (distance, duration + duration_), self.time_distance_records(type_).into_iter().fold(
(None, None) => (distance, duration), (0. * si::M, 0. * si::S),
} |(distance, duration), workout| match (workout.data.distance, workout.data.duration) {
}) (Some(distance_), Some(duration_)) => (distance + distance_, duration + duration_),
(Some(distance_), None) => (distance + distance_, duration),
(None, Some(duration_)) => (distance, duration + duration_),
(None, None) => (distance, duration),
},
)
} }
fn get_record(&self, id: &RecordId) -> Option<Record<TraxRecord>> { fn get_record(&self, id: &RecordId) -> Option<Record<TraxRecord>> {
@ -263,7 +293,17 @@ impl DayDetailViewModel {
} }
pub fn remove_record(&self, id: RecordId) { pub fn remove_record(&self, id: RecordId) {
unimplemented!("remove_record") let mut record_set = self.records.write().unwrap();
let updated_record = match record_set.remove(&id) {
Some(RecordState::Original(r)) => Some(RecordState::Deleted(r)),
Some(RecordState::New(_)) => None,
Some(RecordState::Updated(r)) => Some(RecordState::Deleted(r)),
Some(RecordState::Deleted(r)) => Some(RecordState::Deleted(r)),
None => None,
};
if let Some(updated_record) = updated_record {
record_set.insert(id, updated_record);
}
} }
pub fn save(&self) { pub fn save(&self) {
@ -331,7 +371,9 @@ impl DayDetailViewModel {
RecordState::Updated(r) => { RecordState::Updated(r) => {
let _ = self.provider.update_record(r.clone()).await; let _ = self.provider.update_record(r.clone()).await;
} }
RecordState::Deleted(_) => unimplemented!(), RecordState::Deleted(r) => {
let _ = self.provider.delete_record(r.id).await;
}
} }
} }
} }
@ -405,11 +447,19 @@ mod test {
} }
async fn update_record(&self, record: Record<TraxRecord>) -> Result<(), WriteError> { async fn update_record(&self, record: Record<TraxRecord>) -> Result<(), WriteError> {
Err(WriteError::NoDatabase) println!("updated record: {:?}", record);
self.updated_records.write().unwrap().push(record.clone());
self.records
.write()
.unwrap()
.insert(record.id.clone(), record);
Ok(())
} }
async fn delete_record(&self, id: RecordId) -> Result<(), WriteError> { async fn delete_record(&self, id: RecordId) -> Result<(), WriteError> {
Err(WriteError::NoDatabase) self.deleted_records.write().unwrap().push(id.clone());
let _ = self.records.write().unwrap().remove(&id);
Ok(())
} }
} }
@ -454,20 +504,6 @@ mod test {
count: 2500, count: 2500,
}), }),
}, },
Record {
id: RecordId::default(),
data: TraxRecord::Weight(ft_core::Weight {
date: oct_13.clone(),
weight: 91. * si::KG,
}),
},
Record {
id: RecordId::default(),
data: TraxRecord::Steps(ft_core::Steps {
date: oct_13.clone(),
count: 2750,
}),
},
Record { Record {
id: RecordId::default(), id: RecordId::default(),
data: TraxRecord::BikeRide(ft_core::TimeDistance { data: TraxRecord::BikeRide(ft_core::TimeDistance {
@ -566,15 +602,16 @@ mod test {
} }
#[tokio::test] #[tokio::test]
#[ignore]
async fn it_can_update_an_existing_record() { async fn it_can_update_an_existing_record() {
let (view_model, provider) = create_view_model().await; let (view_model, provider) = create_view_model().await;
let mut workout = view_model let mut workout = view_model
.time_distance_records(RecordType::BikeRide) .time_distance_records(TimeDistanceWorkoutType::BikeRide)
.first() .first()
.cloned() .cloned()
.unwrap(); .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_time_distance(workout.clone());
@ -583,7 +620,7 @@ mod test {
(15000. * si::M, 1800. * si::S) (15000. * si::M, 1800. * si::S)
); );
view_model.save(); view_model.async_save().await;
assert_eq!(provider.put_records.read().unwrap().len(), 0); assert_eq!(provider.put_records.read().unwrap().len(), 0);
assert_eq!(provider.updated_records.read().unwrap().len(), 1); assert_eq!(provider.updated_records.read().unwrap().len(), 1);
@ -591,7 +628,6 @@ mod test {
} }
#[tokio::test] #[tokio::test]
#[ignore]
async fn it_can_remove_a_new_record() { async fn it_can_remove_a_new_record() {
let (view_model, provider) = create_empty_view_model().await; let (view_model, provider) = create_empty_view_model().await;
assert_eq!( assert_eq!(
@ -609,11 +645,10 @@ mod test {
} }
#[tokio::test] #[tokio::test]
#[ignore]
async fn it_can_delete_an_existing_record() { async fn it_can_delete_an_existing_record() {
let (view_model, provider) = create_view_model().await; let (view_model, provider) = create_view_model().await;
let mut workout = view_model let mut workout = view_model
.time_distance_records(RecordType::BikeRide) .time_distance_records(TimeDistanceWorkoutType::BikeRide)
.first() .first()
.cloned() .cloned()
.unwrap(); .unwrap();
@ -623,7 +658,7 @@ mod test {
view_model.time_distance_summary(TimeDistanceWorkoutType::BikeRide), view_model.time_distance_summary(TimeDistanceWorkoutType::BikeRide),
(0. * si::M, 0. * si::S) (0. * si::M, 0. * si::S)
); );
view_model.save(); view_model.async_save().await;
assert_eq!(provider.put_records.read().unwrap().len(), 0); assert_eq!(provider.put_records.read().unwrap().len(), 0);
assert_eq!(provider.updated_records.read().unwrap().len(), 0); assert_eq!(provider.updated_records.read().unwrap().len(), 0);