diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs
index 289f1a5..6c26f00 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);
+ }
}
}
}
@@ -383,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 7b800c1..6ad5c3a 100644
--- a/fitnesstrax/app/src/components/time_distance.rs
+++ b/fitnesstrax/app/src/components/time_distance.rs
@@ -14,31 +14,38 @@ 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::{rc::Rc, cell::RefCell};
+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,
};
@@ -122,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,
@@ -182,6 +189,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();
@@ -202,8 +210,26 @@ 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_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) {
@@ -217,4 +243,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/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs
index a8f3c08..ab379d2 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,
@@ -499,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()),
@@ -549,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;
@@ -566,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!(
@@ -599,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)
);
@@ -614,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();
@@ -634,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/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..d2ef95c 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};
@@ -35,13 +51,21 @@ impl Recordable for Steps {
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum TimeDistanceActivity {
- BikeRide,
+ Biking,
Running,
Rowing,
Swimming,
Walking,
}
+pub const TIME_DISTANCE_ACTIVITIES: [TimeDistanceActivity; 5] = [
+ TimeDistanceActivity::Biking,
+ 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
@@ -117,7 +141,7 @@ impl TraxRecord {
matches!(
self,
TraxRecord::TimeDistance(TimeDistance {
- activity: TimeDistanceActivity::BikeRide,
+ activity: TimeDistanceActivity::Biking,
..
}) | TraxRecord::TimeDistance(TimeDistance {
activity: TimeDistanceActivity::Running,