Show a summary of the day's biking stats when there is one
This commit is contained in:
parent
772188b470
commit
279810f7d7
|
@ -17,9 +17,13 @@ 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_field, ActionGroup, Steps, WeightLabel},
|
components::{
|
||||||
|
steps_editor, time_distance_summary, weight_field, ActionGroup, Steps, WeightLabel,
|
||||||
|
},
|
||||||
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 +100,10 @@ impl DaySummary {
|
||||||
label.set_label(&format!("{} steps", s));
|
label.set_label(&format!("{} steps", s));
|
||||||
}
|
}
|
||||||
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ mod text_entry;
|
||||||
pub use text_entry::{weight_field, TextEntry};
|
pub use text_entry::{weight_field, 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::WeightLabel;
|
pub use weight::WeightLabel;
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -15,8 +15,9 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{app::App, types::WeightFormatter};
|
use crate::{app::App, types::WeightFormatter};
|
||||||
|
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,
|
||||||
|
@ -43,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 }),
|
||||||
|
@ -164,6 +174,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();
|
||||||
|
@ -234,3 +278,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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue