Show a summary of the day's biking stats when there is one

This commit is contained in:
Savanni D'Gerinel 2024-01-21 10:50:18 -05:00
parent f8d66bbb69
commit bbf07ef818
5 changed files with 109 additions and 4 deletions

View File

@ -17,9 +17,11 @@ You should have received a copy of the GNU General Public License along with Fit
// use chrono::NaiveDate; // use chrono::NaiveDate;
// use ft_core::TraxRecord; // use ft_core::TraxRecord;
use crate::{ use crate::{
components::{steps_editor, weight_editor, ActionGroup, Steps, Weight}, components::{steps_editor, time_distance_summary, weight_editor, ActionGroup, Steps, Weight},
view_models::DayDetailViewModel, view_models::DayDetailViewModel,
}; };
use dimensioned::si;
use ft_core::{RecordType, TraxRecord};
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*};
use std::cell::RefCell; use std::cell::RefCell;
@ -96,8 +98,10 @@ impl DaySummary {
label.set_label(&format!("{} steps", s.to_string())); label.set_label(&format!("{} steps", s.to_string()));
} }
row.append(&label); row.append(&label);
self.append(&row); self.append(&row);
let biking_summary = view_model.biking_summary();
time_distance_summary(biking_summary.0, biking_summary.1).map(|label| self.append(&label));
} }
} }

View File

@ -30,7 +30,7 @@ mod text_entry;
pub use text_entry::{ParseError, TextEntry}; pub use text_entry::{ParseError, TextEntry};
mod time_distance; mod time_distance;
pub use time_distance::TimeDistanceView; pub use time_distance::{time_distance_summary, TimeDistanceView};
mod weight; mod weight;
pub use weight::{weight_editor, Weight}; pub use weight::{weight_editor, Weight};

View File

@ -17,11 +17,33 @@ You should have received a copy of the GNU General Public License along with Fit
// use crate::components::{EditView, ParseError, TextEntry}; // use crate::components::{EditView, ParseError, TextEntry};
// use chrono::{Local, NaiveDate}; // use chrono::{Local, NaiveDate};
// use dimensioned::si; // use dimensioned::si;
use dimensioned::si;
use ft_core::{RecordType, TimeDistance}; use ft_core::{RecordType, TimeDistance};
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*};
use std::cell::RefCell; use std::cell::RefCell;
pub fn time_distance_summary(
distance: si::Meter<f64>,
duration: si::Second<f64>,
) -> Option<gtk::Label> {
let text = match (distance > si::M, duration > si::S) {
(true, true) => Some(format!(
"{} kilometers of biking in {} minutes",
distance.value_unsafe / 1000.,
duration.value_unsafe / 60.
)),
(true, false) => Some(format!(
"{} kilometers of biking",
distance.value_unsafe / 1000.
)),
(false, true) => Some(format!("{} seconds of biking", duration.value_unsafe / 60.)),
(false, false) => None,
};
text.map(|text| gtk::Label::new(Some(&text)))
}
#[derive(Default)] #[derive(Default)]
pub struct TimeDistanceViewPrivate { pub struct TimeDistanceViewPrivate {
#[allow(unused)] #[allow(unused)]

View File

@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
use crate::app::App; use crate::app::App;
use dimensioned::si; use dimensioned::si;
use emseries::{Record, RecordId, Recordable}; use emseries::{Record, RecordId, Recordable};
use ft_core::TraxRecord; use ft_core::{TimeDistance, TraxRecord};
use std::{ use std::{
collections::HashMap, collections::HashMap,
ops::Deref, ops::Deref,
@ -44,6 +44,15 @@ impl<T: Clone + emseries::Recordable> RecordState<T> {
} }
} }
fn data(&self) -> Option<&Record<T>> {
match self {
RecordState::Original(ref r) => Some(&r),
RecordState::New(ref r) => None,
RecordState::Updated(ref r) => Some(&r),
RecordState::Deleted(ref r) => Some(&r),
}
}
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 }),
@ -163,6 +172,40 @@ impl DayDetailViewModel {
*record = Some(new_record); *record = Some(new_record);
} }
pub fn biking_summary(&self) -> (si::Meter<f64>, si::Second<f64>) {
self.records.read().unwrap().iter().fold(
(0. * si::M, 0. * si::S),
|(acc_distance, acc_duration), (_, record)| match record.data() {
Some(Record {
data:
TraxRecord::BikeRide(TimeDistance {
distance, duration, ..
}),
..
}) => (
distance
.map(|distance| acc_distance + distance)
.unwrap_or(acc_distance),
(duration
.map(|duration| acc_duration + duration)
.unwrap_or(acc_duration)),
),
_ => (acc_distance, acc_duration),
},
)
}
pub fn records(&self) -> Vec<Record<TraxRecord>> {
let read_lock = self.records.read().unwrap();
read_lock
.iter()
.map(|(_, record_state)| record_state.data())
.filter_map(|r| r)
.cloned()
.collect::<Vec<Record<TraxRecord>>>()
}
pub fn save(&self) { pub fn save(&self) {
glib::spawn_future({ glib::spawn_future({
let s = self.clone(); let s = self.clone();
@ -233,3 +276,31 @@ impl DayDetailViewModel {
unimplemented!(); unimplemented!();
} }
} }
/*
struct SavedRecordIterator<'a> {
read_lock: RwLockReadGuard<'a, HashMap<RecordId, RecordState<TraxRecord>>>,
iter: Box<dyn Iterator<Item = &'a Record<TraxRecord>> + 'a>,
}
impl<'a> SavedRecordIterator<'a> {
fn new(records: Arc<RwLock<HashMap<RecordId, RecordState<TraxRecord>>>>) -> Self {
let read_lock = records.read().unwrap();
let iter = read_lock
.iter()
.map(|(_, record_state)| record_state.data())
.filter_map(|r| r);
Self {
read_lock,
iter: Box::new(iter),
}
}
}
impl<'a> Iterator for SavedRecordIterator<'a> {
type Item = &'a Record<TraxRecord>;
fn next(&mut self) -> Option<Self::Item> {
None
}
}
*/

View File

@ -118,6 +118,14 @@ impl TraxRecord {
pub fn is_steps(&self) -> bool { pub fn is_steps(&self) -> bool {
matches!(self, TraxRecord::Steps(_)) matches!(self, TraxRecord::Steps(_))
} }
pub fn is_bike_ride(&self) -> bool {
matches!(self, TraxRecord::BikeRide(_))
}
pub fn is_run(&self) -> bool {
matches!(self, TraxRecord::Run(_))
}
} }
impl Recordable for TraxRecord { impl Recordable for TraxRecord {