From af8f9b024434abff24bb9ca58712e94738edd50f Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 24 Dec 2023 19:13:49 -0500 Subject: [PATCH] Generate some random data and feed it into hte historical view --- Cargo.lock | 1 + fitnesstrax/app/Cargo.toml | 1 + fitnesstrax/app/src/app_window.rs | 39 ++++++++- fitnesstrax/app/src/components/day.rs | 2 +- fitnesstrax/app/src/views/historical_view.rs | 89 +++++++++++++++++--- fitnesstrax/core/src/lib.rs | 2 +- fitnesstrax/core/src/types.rs | 12 +-- 7 files changed, 125 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cef9966..c2c4b54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,6 +1022,7 @@ version = "0.1.0" dependencies = [ "async-channel", "chrono", + "chrono-tz", "dimensioned 0.8.0", "emseries", "ft-core", diff --git a/fitnesstrax/app/Cargo.toml b/fitnesstrax/app/Cargo.toml index b35999e..bfb1eca 100644 --- a/fitnesstrax/app/Cargo.toml +++ b/fitnesstrax/app/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] } async-channel = { version = "2.1" } chrono = { version = "0.4" } +chrono-tz = { version = "0.8" } dimensioned = { version = "0.8", features = [ "serde" ] } emseries = { path = "../../emseries" } ft-core = { path = "../core" } diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 4268277..0326a22 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -20,6 +20,11 @@ use crate::{ }; use adw::prelude::*; use async_channel::Sender; +use chrono::{NaiveDate, TimeZone}; +use chrono_tz::America::Anchorage; +use dimensioned::si::{KG, M, S}; +use emseries::DateTimeTz; +use ft_core::{Steps, TimeDistance, TraxRecord, Weight}; use gio::resources_lookup_data; use gtk::STYLE_PROVIDER_PRIORITY_USER; use std::path::PathBuf; @@ -144,7 +149,39 @@ impl AppWindow { }) .upcast(), ), - ViewName::Historical => View::Historical(HistoricalView::new(vec![]).upcast()), + ViewName::Historical => View::Historical( + HistoricalView::new(vec![ + TraxRecord::Steps(Steps { + date: NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(), + count: 1500, + }), + TraxRecord::Weight(Weight { + date: NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(), + weight: 85. * KG, + }), + TraxRecord::Weight(Weight { + date: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(), + weight: 86. * KG, + }), + TraxRecord::BikeRide(TimeDistance { + datetime: DateTimeTz( + Anchorage.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap(), + ), + distance: Some(1000. * M), + duration: Some(150. * S), + comments: Some("Test Comments".to_owned()), + }), + TraxRecord::BikeRide(TimeDistance { + datetime: DateTimeTz( + Anchorage.with_ymd_and_hms(2019, 6, 15, 23, 0, 0).unwrap(), + ), + distance: Some(1000. * M), + duration: Some(150. * S), + comments: Some("Test Comments".to_owned()), + }), + ]) + .upcast(), + ), } } } diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 0899262..1026a20 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -69,7 +69,7 @@ impl DaySummary { pub fn set_data(&self, date: chrono::NaiveDate, records: Vec) { self.imp() .date - .set_text(&date.format("%y-%m-%d").to_string()); + .set_text(&date.format("%Y-%m-%d").to_string()); if let Some(ref weight_label) = *self.imp().weight.borrow() { self.remove(weight_label); diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index 07ace8d..c3d1cf0 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -15,11 +15,11 @@ You should have received a copy of the GNU General Public License along with Fit */ use crate::components::DaySummary; -use dimensioned::si::KG; -use ft_core::{TraxRecord, Weight}; +use emseries::{Recordable, Timestamp}; +use ft_core::TraxRecord; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::cell::RefCell; +use std::{cell::RefCell, collections::HashMap}; /// The historical view will show a window into the main database. It will show some version of /// daily summaries, daily details, and will provide all functions the user may need for editing @@ -46,20 +46,14 @@ glib::wrapper! { } impl HistoricalView { - pub fn new(_records: Vec) -> Self { + pub fn new(records: Vec) -> Self { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); - let day_records: Vec = vec![DayRecords::new( - chrono::NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(), - vec![TraxRecord::Weight(Weight { - date: chrono::NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(), - weight: 100. * KG, - })], - )]; + let day_records: GroupedRecords = GroupedRecords::from(records); let model = gio::ListStore::new::(); - model.extend_from_slice(&day_records); + model.extend_from_slice(&day_records.0); let factory = gtk::SignalListItemFactory::new(); factory.connect_setup(move |_, list_item| { @@ -142,4 +136,75 @@ impl DayRecords { pub fn records(&self) -> Vec { self.imp().records.borrow().clone() } + + pub fn add_record(&self, record: TraxRecord) { + self.imp().records.borrow_mut().push(record); + } +} + +struct GroupedRecords(Vec); + +impl From> for GroupedRecords { + fn from(records: Vec) -> GroupedRecords { + GroupedRecords( + records + .into_iter() + .fold(HashMap::new(), |mut acc, rec| { + let date = match rec.timestamp() { + Timestamp::DateTime(dtz) => dtz.0.date_naive(), + Timestamp::Date(date) => date, + }; + acc.entry(date) + .and_modify(|entry: &mut DayRecords| (*entry).add_record(rec.clone())) + .or_insert(DayRecords::new(date, vec![rec])); + acc + }) + .values() + .cloned() + .collect::>(), + ) + } +} + +#[cfg(test)] +mod test { + use super::GroupedRecords; + use chrono::{NaiveDate, TimeZone}; + use chrono_tz::America::Anchorage; + use dimensioned::si::{KG, M, S}; + use emseries::DateTimeTz; + use ft_core::{Steps, TimeDistance, TraxRecord, Weight}; + + #[test] + fn groups_records() { + let records = vec![ + TraxRecord::Steps(Steps { + date: NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(), + count: 1500, + }), + TraxRecord::Weight(Weight { + date: NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(), + weight: 85. * KG, + }), + TraxRecord::Weight(Weight { + date: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(), + weight: 86. * KG, + }), + TraxRecord::BikeRide(TimeDistance { + datetime: DateTimeTz(Anchorage.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap()), + distance: Some(1000. * M), + duration: Some(150. * S), + comments: Some("Test Comments".to_owned()), + }), + TraxRecord::BikeRide(TimeDistance { + datetime: DateTimeTz(Anchorage.with_ymd_and_hms(2019, 6, 15, 23, 0, 0).unwrap()), + distance: Some(1000. * M), + duration: Some(150. * S), + comments: Some("Test Comments".to_owned()), + }), + ]; + + let groups = GroupedRecords::from(records).0; + assert_eq!(groups.len(), 3); + } } diff --git a/fitnesstrax/core/src/lib.rs b/fitnesstrax/core/src/lib.rs index b0c9d9d..d454169 100644 --- a/fitnesstrax/core/src/lib.rs +++ b/fitnesstrax/core/src/lib.rs @@ -4,4 +4,4 @@ use emseries::DateTimeTz; mod legacy; mod types; -pub use types::{TraxRecord, Weight}; +pub use types::{Steps, TimeDistance, TraxRecord, Weight}; diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index ca526d4..4de0da4 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -18,8 +18,8 @@ pub struct SetRep { /// The number of steps one takes in a single day. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Steps { - date: NaiveDate, - count: u32, + pub date: NaiveDate, + pub count: u32, } /// TimeDistance represents workouts characterized by a duration and a distance travelled. These @@ -34,14 +34,14 @@ pub struct TimeDistance { /// if one moved two timezones to the east. This is kind of nonsensical from a human /// perspective, so the DateTimeTz keeps track of the precise time in UTC, but also the /// timezone in which the event was recorded. - datetime: DateTimeTz, + pub datetime: DateTimeTz, /// The distance travelled. This is optional because such a workout makes sense even without /// the distance. - distance: Option>, + pub distance: Option>, /// The duration of the workout, which is also optional. Some people may keep track of the /// amount of distance travelled without tracking the duration. - duration: Option>, - comments: Option, + pub duration: Option>, + pub comments: Option, } /// A singular daily weight measurement. Weight changes slowly enough that it seems unlikely to