diff --git a/emseries/src/series.rs b/emseries/src/series.rs index 310b3f7..0e6441b 100644 --- a/emseries/src/series.rs +++ b/emseries/src/series.rs @@ -110,7 +110,7 @@ where .map_err(EmseriesReadError::JSONParseError) .and_then(Record::try_from) { - Ok(record) => records.insert(record.id.clone(), record.clone()), + Ok(record) => records.insert(record.id, record.clone()), Err(EmseriesReadError::RecordDeleted(id)) => records.remove(&id), Err(err) => return Err(err), }; @@ -124,19 +124,16 @@ where /// Put a new record into the database. A unique id will be assigned to the record and /// returned. pub fn put(&mut self, entry: T) -> Result { - let uuid = RecordId::default(); - let record = Record { - id: uuid.clone(), - data: entry, - }; + let id = RecordId::default(); + let record = Record { id, data: entry }; self.update(record)?; - Ok(uuid) + Ok(id) } /// Update an existing record. The [RecordId] of the record passed into this function must match /// the [RecordId] of a record already in the database. pub fn update(&mut self, record: Record) -> Result<(), EmseriesWriteError> { - self.records.insert(record.id.clone(), record.clone()); + self.records.insert(record.id, record.clone()); let write_res = match serde_json::to_string(&RecordOnDisk { id: record.id, data: Some(record.data), @@ -166,7 +163,7 @@ where self.records.remove(uuid); let rec: RecordOnDisk = RecordOnDisk { - id: uuid.clone(), + id: *uuid, data: None, }; match serde_json::to_string(&rec) { diff --git a/emseries/src/types.rs b/emseries/src/types.rs index a094830..e10298c 100644 --- a/emseries/src/types.rs +++ b/emseries/src/types.rs @@ -166,6 +166,17 @@ impl Record { pub fn timestamp(&self) -> Timestamp { self.data.timestamp() } + + pub fn map(self, map: Map) -> Record + where + Map: Fn(T) -> U, + U: Clone + Recordable, + { + Record { + id: self.id, + data: map(self.data), + } + } } #[cfg(test)] diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 0edcd5d..b024912 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -23,12 +23,13 @@ use crate::{ types::WeightFormatter, view_models::DayDetailViewModel, }; -use ft_core::TimeDistanceActivity; +use emseries::Record; +use ft_core::{TimeDistanceActivity, TraxRecord}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::cell::RefCell; +use std::{cell::RefCell, rc::Rc}; -use super::time_distance_detail; +use super::{time_distance::TimeDistanceEdit, time_distance_detail}; pub struct DaySummaryPrivate { date: gtk::Label, @@ -237,9 +238,15 @@ impl DayEdit { *s.imp().on_finished.borrow_mut() = Box::new(on_finished); *s.imp().view_model.borrow_mut() = Some(view_model.clone()); + let workout_buttons = workout_buttons(view_model.clone(), { + let s = s.clone(); + move |workout| s.add_row(workout) + }); + s.append(&control_buttons(&s, &view_model)); s.append(&weight_and_steps_row(&view_model)); - s.append(&workout_buttons()); + s.append(&*s.imp().workout_rows.borrow()); + s.append(&workout_buttons); s } @@ -247,6 +254,17 @@ impl DayEdit { fn finish(&self) { (self.imp().on_finished.borrow())() } + + fn add_row(&self, workout: Record) { + println!("add_row: {:?}", 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, |_| {})), + _ => {} + } + } } fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup { @@ -303,24 +321,37 @@ fn weight_and_steps_row(view_model: &DayDetailViewModel) -> gtk::Box { row } -fn workout_buttons() -> gtk::Box { - let sunrise_button = gtk::Button::builder() - .icon_name("daytime-sunrise-symbolic") - .width_request(64) - .height_request(64) - .build(); - +fn workout_buttons(view_model: DayDetailViewModel, add_row: AddRow) -> gtk::Box +where + AddRow: Fn(Record) + 'static, +{ + let add_row = Rc::new(add_row); let walking_button = gtk::Button::builder() .icon_name("walking2-symbolic") .width_request(64) .height_request(64) .build(); + walking_button.connect_clicked({ + let view_model = view_model.clone(); + let add_row = add_row.clone(); + move |_| { + let workout = view_model.new_time_distance(TimeDistanceActivity::Walking); + add_row(workout.map(TraxRecord::TimeDistance)); + } + }); let running_button = gtk::Button::builder() .icon_name("running-symbolic") .width_request(64) .height_request(64) .build(); + running_button.connect_clicked({ + let view_model = view_model.clone(); + move |_| { + let workout = view_model.new_time_distance(TimeDistanceActivity::Running); + add_row(workout.map(TraxRecord::TimeDistance)); + } + }); let layout = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) @@ -328,7 +359,6 @@ fn workout_buttons() -> gtk::Box { let row = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .build(); - row.append(&sunrise_button); row.append(&walking_button); row.append(&running_button); layout.append(&row); diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index 3771556..bda7248 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with Fit // use chrono::{Local, NaiveDate}; // use dimensioned::si; use dimensioned::si; +use ft_core::TimeDistance; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::cell::RefCell; @@ -130,6 +131,7 @@ impl Default for TimeDistanceEdit { fn default() -> Self { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Horizontal); + s.set_hexpand(true); s.set_css_classes(&["time-distance-edit"]); s @@ -137,12 +139,18 @@ impl Default for TimeDistanceEdit { } impl TimeDistanceEdit { - #[allow(unused)] - fn empty(_on_update: OnUpdate) -> Self + pub fn new(record: TimeDistance, _on_update: OnUpdate) -> Self where OnUpdate: Fn(&ft_core::TimeDistance), { - Self::default() + println!("new TimeDistanceEdit"); + let s = Self::default(); + + s.append(>k::Label::new(Some( + record.datetime.format("%H:%M").to_string().as_ref(), + ))); + + s } /* diff --git a/fitnesstrax/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs index 2751abe..7339168 100644 --- a/fitnesstrax/app/src/view_models/day_detail.rs +++ b/fitnesstrax/app/src/view_models/day_detail.rs @@ -30,7 +30,7 @@ use std::{ #[derive(Clone, Debug)] enum RecordState { Original(Record), - New(T), + New(Record), Updated(Record), Deleted(Record), } @@ -57,19 +57,13 @@ impl RecordState { fn set_value(&mut self, value: T) { *self = match self { - RecordState::Original(r) => RecordState::Updated(Record { - id: r.id.clone(), - data: value, - }), - RecordState::New(_) => RecordState::New(value), - RecordState::Updated(r) => RecordState::Updated(Record { - id: r.id.clone(), - data: value, - }), - RecordState::Deleted(r) => RecordState::Updated(Record { - id: r.id.clone(), + RecordState::Original(r) => RecordState::Updated(Record { data: value, ..*r }), + RecordState::New(_) => RecordState::New(Record { + id: RecordId::default(), data: value, }), + RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..*r }), + RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..*r }), }; } @@ -94,7 +88,7 @@ impl Deref for RecordState { fn deref(&self) -> &Self::Target { match self { RecordState::Original(ref r) => &r.data, - RecordState::New(ref r) => r, + RecordState::New(ref r) => &r.data, RecordState::Updated(ref r) => &r.data, RecordState::Deleted(ref r) => &r.data, } @@ -105,7 +99,7 @@ impl std::ops::DerefMut for RecordState { fn deref_mut(&mut self) -> &mut Self::Target { match self { RecordState::Original(ref mut r) => &mut r.data, - RecordState::New(ref mut r) => r, + RecordState::New(ref mut r) => &mut r.data, RecordState::Updated(ref mut r) => &mut r.data, RecordState::Deleted(ref mut r) => &mut r.data, } @@ -149,7 +143,7 @@ impl DayDetailViewModel { weight_records .first() .and_then(|r| match r.data { - TraxRecord::Weight(ref w) => Some((r.id.clone(), w.clone())), + TraxRecord::Weight(ref w) => Some((r.id, w.clone())), _ => None, }) .map(|(id, w)| RecordState::Original(Record { id, data: w })), @@ -158,7 +152,7 @@ impl DayDetailViewModel { step_records .first() .and_then(|r| match r.data { - TraxRecord::Steps(ref w) => Some((r.id.clone(), w.clone())), + TraxRecord::Steps(ref w) => Some((r.id, w.clone())), _ => None, }) .map(|(id, w)| RecordState::Original(Record { id, data: w })), @@ -167,7 +161,7 @@ impl DayDetailViewModel { records: Arc::new(RwLock::new( records .into_iter() - .map(|r| (r.id.clone(), RecordState::Original(r))) + .map(|r| (r.id, RecordState::Original(r))) .collect::>>(), )), }) @@ -184,9 +178,12 @@ impl DayDetailViewModel { date: self.date, weight: new_weight, }), - None => RecordState::New(ft_core::Weight { - date: self.date, - weight: new_weight, + None => RecordState::New(Record { + id: RecordId::default(), + data: ft_core::Weight { + date: self.date, + weight: new_weight, + }, }), }; *record = Some(new_record); @@ -203,9 +200,12 @@ impl DayDetailViewModel { date: self.date, count: new_count, }), - None => RecordState::New(ft_core::Steps { - date: self.date, - count: new_count, + None => RecordState::New(Record { + id: RecordId::default(), + data: ft_core::Steps { + date: self.date, + count: new_count, + }, }), }; *record = Some(new_record); @@ -224,7 +224,7 @@ impl DayDetailViewModel { self.records .write() .unwrap() - .insert(id.clone(), RecordState::New(tr)); + .insert(id, RecordState::New(Record { id, data: tr })); println!( "records after new_time_distance: {:?}", self.records.read().unwrap() @@ -233,11 +233,10 @@ impl DayDetailViewModel { } pub fn update_time_distance(&self, workout: Record) { - let id = workout.id.clone(); let data = workout.data.clone(); let mut record_set = self.records.write().unwrap(); - record_set.entry(id).and_modify(|record_state| { + record_set.entry(workout.id).and_modify(|record_state| { record_state.set_value(TraxRecord::TimeDistance(data)); }); } @@ -250,7 +249,7 @@ impl DayDetailViewModel { .filter(|(_, record)| record.exists()) .filter_map(|(id, record_state)| match **record_state { TraxRecord::TimeDistance(ref workout) => Some(Record { - id: id.clone(), + id: *id, data: workout.clone(), }), _ => None, @@ -283,7 +282,7 @@ impl DayDetailViewModel { fn get_record(&self, id: &RecordId) -> Option> { let record_set = self.records.read().unwrap(); record_set.get(id).map(|record| Record { - id: id.clone(), + id: *id, data: (**record).clone(), }) } @@ -303,18 +302,19 @@ impl DayDetailViewModel { } pub fn save(&self) { - glib::spawn_future({ - let s = self.clone(); - async move { s.async_save().await } - }); + let s = self.clone(); + + glib::spawn_future(async move { s.async_save().await }); } pub async fn async_save(&self) { - println!("async_save"); let weight_record = self.weight.read().unwrap().clone(); match weight_record { Some(RecordState::New(data)) => { - let _ = self.provider.put_record(TraxRecord::Weight(data)).await; + let _ = self + .provider + .put_record(TraxRecord::Weight(data.data)) + .await; } Some(RecordState::Original(_)) => {} Some(RecordState::Updated(weight)) => { @@ -333,7 +333,7 @@ impl DayDetailViewModel { let steps_record = self.steps.read().unwrap().clone(); match steps_record { Some(RecordState::New(data)) => { - let _ = self.provider.put_record(TraxRecord::Steps(data)).await; + let _ = self.provider.put_record(TraxRecord::Steps(data.data)).await; } Some(RecordState::Original(_)) => {} Some(RecordState::Updated(steps)) => { @@ -361,7 +361,7 @@ impl DayDetailViewModel { println!("saving record: {:?}", record); match record { RecordState::New(data) => { - let _ = self.provider.put_record(data).await; + let _ = self.provider.put_record(data.data).await; } RecordState::Original(_) => {} RecordState::Updated(r) => { @@ -389,7 +389,7 @@ impl DayDetailViewModel { *self.weight.write().unwrap() = weight_records .first() .and_then(|r| match r.data { - TraxRecord::Weight(ref w) => Some((r.id.clone(), w.clone())), + TraxRecord::Weight(ref w) => Some((r.id, w.clone())), _ => None, }) .map(|(id, w)| RecordState::Original(Record { id, data: w })); @@ -397,14 +397,14 @@ impl DayDetailViewModel { *self.steps.write().unwrap() = step_records .first() .and_then(|r| match r.data { - TraxRecord::Steps(ref w) => Some((r.id.clone(), w.clone())), + TraxRecord::Steps(ref w) => Some((r.id, w.clone())), _ => None, }) .map(|(id, w)| RecordState::Original(Record { id, data: w })); *self.records.write().unwrap() = records .into_iter() - .map(|r| (r.id.clone(), RecordState::Original(r))) + .map(|r| (r.id, RecordState::Original(r))) .collect::>>(); } } @@ -430,7 +430,7 @@ mod test { fn new(records: Vec>) -> Self { let record_map = records .into_iter() - .map(|r| (r.id.clone(), r)) + .map(|r| (r.id, r)) .collect::>>(); Self { records: Arc::new(RwLock::new(record_map)), @@ -464,26 +464,23 @@ mod test { async fn put_record(&self, record: TraxRecord) -> Result { let id = RecordId::default(); let record = Record { - id: id.clone(), + id: id, data: record, }; self.put_records.write().unwrap().push(record.clone()); - self.records.write().unwrap().insert(id.clone(), record); + self.records.write().unwrap().insert(id, record); Ok(id) } async fn update_record(&self, record: Record) -> Result<(), WriteError> { println!("updated record: {:?}", record); self.updated_records.write().unwrap().push(record.clone()); - self.records - .write() - .unwrap() - .insert(record.id.clone(), record); + self.records.write().unwrap().insert(record.id, record); Ok(()) } async fn delete_record(&self, id: RecordId) -> Result<(), WriteError> { - self.deleted_records.write().unwrap().push(id.clone()); + self.deleted_records.write().unwrap().push(id); let _ = self.records.write().unwrap().remove(&id); Ok(()) } diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index c50d4eb..a48da32 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -134,18 +134,6 @@ impl TraxRecord { }) ) } - - /* - pub fn is_time_distance_type(&self, type_: TimeDistanceActivity) -> bool { - match type_ { - TimeDistanceWorkoutType::BikeRide => matches!(self, TraxRecord::BikeRide(_)), - TimeDistanceWorkoutType::Row => matches!(self, TraxRecord::Row(_)), - TimeDistanceWorkoutType::Run => matches!(self, TraxRecord::Run(_)), - TimeDistanceWorkoutType::Swim => matches!(self, TraxRecord::Swim(_)), - TimeDistanceWorkoutType::Walk => matches!(self, TraxRecord::Walk(_)), - } - } - */ } impl Recordable for TraxRecord {