From 38b1e62b60843a94d5f62062562a01c6cd4ee001 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 8 Feb 2024 21:58:22 -0500 Subject: [PATCH 1/5] Add the ability to edit the time on a time_distance record --- .../app/src/components/time_distance.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index 7b800c1..e42698f 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -25,7 +25,7 @@ use dimensioned::si; use ft_core::{TimeDistance, TimeDistanceActivity}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::{rc::Rc, cell::RefCell}; +use std::{cell::RefCell, rc::Rc}; pub fn time_distance_summary( distance: DistanceFormatter, @@ -202,8 +202,20 @@ impl TimeDistanceEdit { s } - fn update_time(&self, _time: Option) { - unimplemented!() + fn update_time(&self, time: Option) { + if let Some(time_formatter) = time { + let mut workout = self.imp().workout.borrow_mut(); + let tz = workout.datetime.timezone(); + let new_time = workout + .datetime + .date_naive() + .and_time(*time_formatter) + .and_local_timezone(tz) + .unwrap() + .fixed_offset(); + workout.datetime = new_time; + (self.imp().on_update.borrow())(workout.clone()); + } } fn update_distance(&self, distance: Option) { -- 2.44.1 From 6394d89331fedd189a1c579efd33f320bcc39f6b Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 8 Feb 2024 22:11:46 -0500 Subject: [PATCH 2/5] Create new records with the date of the view model --- fitnesstrax/app/src/view_models/day_detail.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fitnesstrax/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs index a8f3c08..5e19de3 100644 --- a/fitnesstrax/app/src/view_models/day_detail.rs +++ b/fitnesstrax/app/src/view_models/day_detail.rs @@ -176,9 +176,20 @@ impl DayDetailViewModel { } pub fn new_time_distance(&self, activity: TimeDistanceActivity) -> Record { + let now = chrono::Local::now(); + let base_time = now.time(); + let tz = now.timezone(); + let datetime = self + .date + .clone() + .and_time(base_time) + .and_local_timezone(tz) + .unwrap() + .into(); + let id = RecordId::default(); let workout = TimeDistance { - datetime: chrono::Local::now().into(), + datetime, activity, distance: None, duration: None, -- 2.44.1 From b5c42e3ac307958117115a605b46c4038bc552b9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 9 Feb 2024 08:17:10 -0500 Subject: [PATCH 3/5] Add the activity type selector to the time-distance widget --- .../app/src/components/time_distance.rs | 37 +++++++++++++++++-- fitnesstrax/core/src/lib.rs | 4 +- fitnesstrax/core/src/types.rs | 24 ++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index e42698f..b9698f3 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -14,15 +14,12 @@ General Public License for more details. You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . */ -// 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, FormatOption, TimeFormatter}, }; use dimensioned::si; -use ft_core::{TimeDistance, TimeDistanceActivity}; +use ft_core::{TimeDistance, TimeDistanceActivity, TIME_DISTANCE_ACTIVITIES}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; @@ -182,6 +179,7 @@ impl TimeDistanceEdit { ) .widget(), ); + details_row.append(&s.activity_menu(workout.activity)); details_row.append( &distance_field(workout.distance.map(DistanceFormatter::from), { let s = s.clone(); @@ -218,6 +216,12 @@ impl TimeDistanceEdit { } } + fn update_workout_type(&self, type_: TimeDistanceActivity) { + let mut workout = self.imp().workout.borrow_mut(); + workout.activity = type_; + (self.imp().on_update.borrow())(workout.clone()) + } + fn update_distance(&self, distance: Option) { let mut workout = self.imp().workout.borrow_mut(); workout.distance = distance.map(|d| *d); @@ -229,4 +233,29 @@ impl TimeDistanceEdit { workout.duration = duration.map(|d| *d); (self.imp().on_update.borrow())(workout.clone()); } + + fn activity_menu(&self, selected: TimeDistanceActivity) -> gtk::DropDown { + let options = TIME_DISTANCE_ACTIVITIES + .iter() + .map(|item| format!("{:?}", item)) + .collect::>(); + + let options = options.iter().map(|o| o.as_ref()).collect::>(); + + let selected_idx = TIME_DISTANCE_ACTIVITIES + .iter() + .position(|&v| v == selected) + .unwrap_or(0); + + let menu = gtk::DropDown::from_strings(&options); + menu.set_selected(selected_idx as u32); + menu.connect_selected_item_notify({ + let s = self.clone(); + move |menu| { + let new_item = TIME_DISTANCE_ACTIVITIES[menu.selected() as usize]; + s.update_workout_type(new_item); + } + }); + menu + } } diff --git a/fitnesstrax/core/src/lib.rs b/fitnesstrax/core/src/lib.rs index 89895ad..f579628 100644 --- a/fitnesstrax/core/src/lib.rs +++ b/fitnesstrax/core/src/lib.rs @@ -1,4 +1,6 @@ mod legacy; mod types; -pub use types::{Steps, TimeDistance, TimeDistanceActivity, TraxRecord, Weight}; +pub use types::{ + Steps, TimeDistance, TimeDistanceActivity, TraxRecord, Weight, TIME_DISTANCE_ACTIVITIES, +}; diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index a48da32..5602a8e 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -1,3 +1,19 @@ +/* +Copyright 2023-2024, Savanni D'Gerinel + +This file is part of FitnessTrax. + +FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . +*/ + use chrono::{DateTime, FixedOffset, NaiveDate}; use dimensioned::si; use emseries::{Recordable, Timestamp}; @@ -42,6 +58,14 @@ pub enum TimeDistanceActivity { Walking, } +pub const TIME_DISTANCE_ACTIVITIES: [TimeDistanceActivity; 5] = [ + TimeDistanceActivity::BikeRide, + TimeDistanceActivity::Rowing, + TimeDistanceActivity::Running, + TimeDistanceActivity::Swimming, + TimeDistanceActivity::Walking, +]; + /// TimeDistance represents workouts characterized by a duration and a distance travelled. These /// sorts of workouts can occur many times a day, depending on how one records things. I might /// record a single 30-km workout if I go on a long-distanec ride. Or I might record multiple 5km -- 2.44.1 From 291dc32fe5050f35b3d877dcfddb0fb69f9dcca4 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 9 Feb 2024 08:22:12 -0500 Subject: [PATCH 4/5] Show summaries of all time-distance workouts --- fitnesstrax/app/src/components/day.rs | 17 ++++++++++------- fitnesstrax/app/src/components/time_distance.rs | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 289f1a5..3068945 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -24,7 +24,7 @@ use crate::{ view_models::DayDetailViewModel, }; use emseries::{Record, RecordId}; -use ft_core::{TimeDistanceActivity, TraxRecord}; +use ft_core::{TimeDistanceActivity, TraxRecord, TIME_DISTANCE_ACTIVITIES}; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; @@ -105,12 +105,15 @@ impl DaySummary { row.append(&label); self.append(&row); - let biking_summary = view_model.time_distance_summary(TimeDistanceActivity::BikeRide); - if let Some(label) = time_distance_summary( - DistanceFormatter::from(biking_summary.0), - DurationFormatter::from(biking_summary.1), - ) { - self.append(&label); + for activity in TIME_DISTANCE_ACTIVITIES { + let summary = view_model.time_distance_summary(activity); + if let Some(label) = time_distance_summary( + activity, + DistanceFormatter::from(summary.0), + DurationFormatter::from(summary.1), + ) { + self.append(&label); + } } } } diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index b9698f3..5c74e06 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -25,17 +25,27 @@ use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; pub fn time_distance_summary( + activity: TimeDistanceActivity, distance: DistanceFormatter, duration: DurationFormatter, ) -> Option { let text = match (*distance > si::M, *duration > si::S) { (true, true) => Some(format!( - "{} of biking in {}", + "{} of {:?} in {}", distance.format(FormatOption::Full), + activity, duration.format(FormatOption::Full) )), - (true, false) => Some(format!("{} of biking", distance.format(FormatOption::Full))), - (false, true) => Some(format!("{} of biking", duration.format(FormatOption::Full))), + (true, false) => Some(format!( + "{} of {:?}", + distance.format(FormatOption::Full), + activity + )), + (false, true) => Some(format!( + "{} of {:?}", + duration.format(FormatOption::Full), + activity + )), (false, false) => None, }; -- 2.44.1 From 73a5ab89a3f9510ed233f144c43f095ea7875620 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 9 Feb 2024 08:24:01 -0500 Subject: [PATCH 5/5] Rename BikeRide to Biking This just generally feels more comfortable, especially when put in conjuction with the other activity types --- fitnesstrax/app/src/components/day.rs | 2 +- .../app/src/components/time_distance.rs | 2 +- fitnesstrax/app/src/view_models/day_detail.rs | 20 +++++++++---------- fitnesstrax/core/src/types.rs | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 3068945..6c26f00 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -386,7 +386,7 @@ where biking_button.connect_clicked({ let view_model = view_model.clone(); move |_| { - let workout = view_model.new_time_distance(TimeDistanceActivity::BikeRide); + let workout = view_model.new_time_distance(TimeDistanceActivity::Biking); add_row(workout.map(TraxRecord::TimeDistance)); } }); diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index 5c74e06..6ad5c3a 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -129,7 +129,7 @@ impl Default for TimeDistanceEditPrivate { Self { workout: RefCell::new(TimeDistance { datetime: chrono::Utc::now().into(), - activity: TimeDistanceActivity::BikeRide, + activity: TimeDistanceActivity::Biking, duration: None, distance: None, comments: None, diff --git a/fitnesstrax/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs index 5e19de3..ab379d2 100644 --- a/fitnesstrax/app/src/view_models/day_detail.rs +++ b/fitnesstrax/app/src/view_models/day_detail.rs @@ -510,7 +510,7 @@ mod test { id: RecordId::default(), data: TraxRecord::TimeDistance(ft_core::TimeDistance { datetime: oct_13_am.clone(), - activity: TimeDistanceActivity::BikeRide, + activity: TimeDistanceActivity::Biking, distance: Some(15000. * si::M), duration: Some(3600. * si::S), comments: Some("somecomments present".to_owned()), @@ -560,11 +560,11 @@ mod test { async fn it_can_construct_new_records() { let (view_model, provider) = create_empty_view_model().await; assert_eq!( - view_model.time_distance_summary(TimeDistanceActivity::BikeRide), + view_model.time_distance_summary(TimeDistanceActivity::Biking), (0. * si::M, 0. * si::S) ); - let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide); + let mut record = view_model.new_time_distance(TimeDistanceActivity::Biking); record.data.duration = Some(60. * si::S); view_model.async_save().await; @@ -577,17 +577,17 @@ mod test { async fn it_can_update_a_new_record_before_saving() { let (view_model, provider) = create_empty_view_model().await; assert_eq!( - view_model.time_distance_summary(TimeDistanceActivity::BikeRide), + view_model.time_distance_summary(TimeDistanceActivity::Biking), (0. * si::M, 0. * si::S) ); - let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide); + let mut record = view_model.new_time_distance(TimeDistanceActivity::Biking); record.data.duration = Some(60. * si::S); 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), + view_model.time_distance_summary(TimeDistanceActivity::Biking), (0. * si::M, 60. * si::S) ); assert_eq!( @@ -610,7 +610,7 @@ mod test { view_model.update_record(workout.map(TraxRecord::TimeDistance)); assert_eq!( - view_model.time_distance_summary(TimeDistanceActivity::BikeRide), + view_model.time_distance_summary(TimeDistanceActivity::Biking), (15000. * si::M, 1800. * si::S) ); @@ -625,11 +625,11 @@ mod test { async fn it_can_remove_a_new_record() { let (view_model, provider) = create_empty_view_model().await; assert_eq!( - view_model.time_distance_summary(TimeDistanceActivity::BikeRide), + view_model.time_distance_summary(TimeDistanceActivity::Biking), (0. * si::M, 0. * si::S) ); - let record = view_model.new_time_distance(TimeDistanceActivity::BikeRide); + let record = view_model.new_time_distance(TimeDistanceActivity::Biking); view_model.remove_record(record.id); view_model.save(); @@ -645,7 +645,7 @@ mod test { view_model.remove_record(workout.id); assert_eq!( - view_model.time_distance_summary(TimeDistanceActivity::BikeRide), + view_model.time_distance_summary(TimeDistanceActivity::Biking), (0. * si::M, 0. * si::S) ); view_model.async_save().await; diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index 5602a8e..d2ef95c 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -51,7 +51,7 @@ impl Recordable for Steps { #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum TimeDistanceActivity { - BikeRide, + Biking, Running, Rowing, Swimming, @@ -59,7 +59,7 @@ pub enum TimeDistanceActivity { } pub const TIME_DISTANCE_ACTIVITIES: [TimeDistanceActivity; 5] = [ - TimeDistanceActivity::BikeRide, + TimeDistanceActivity::Biking, TimeDistanceActivity::Rowing, TimeDistanceActivity::Running, TimeDistanceActivity::Swimming, @@ -141,7 +141,7 @@ impl TraxRecord { matches!( self, TraxRecord::TimeDistance(TimeDistance { - activity: TimeDistanceActivity::BikeRide, + activity: TimeDistanceActivity::Biking, .. }) | TraxRecord::TimeDistance(TimeDistance { activity: TimeDistanceActivity::Running, -- 2.44.1