Compare commits
3 Commits
1cfd62c44f
...
2fb8728856
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 2fb8728856 | |
Savanni D'Gerinel | a7d43ef184 | |
Savanni D'Gerinel | 9727d35116 |
70
Cargo.nix
70
Cargo.nix
|
@ -655,6 +655,32 @@ rec {
|
|||
};
|
||||
resolvedDefaultFeatures = [ "default" "std" ];
|
||||
};
|
||||
"async-trait" = rec {
|
||||
crateName = "async-trait";
|
||||
version = "0.1.77";
|
||||
edition = "2021";
|
||||
sha256 = "1adf1jh2yg39rkpmqjqyr9xyd6849p0d95425i6imgbhx0syx069";
|
||||
procMacro = true;
|
||||
authors = [
|
||||
"David Tolnay <dtolnay@gmail.com>"
|
||||
];
|
||||
dependencies = [
|
||||
{
|
||||
name = "proc-macro2";
|
||||
packageId = "proc-macro2";
|
||||
}
|
||||
{
|
||||
name = "quote";
|
||||
packageId = "quote";
|
||||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" "visit-mut" ];
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
"atoi" = rec {
|
||||
crateName = "atoi";
|
||||
version = "2.0.0";
|
||||
|
@ -1487,7 +1513,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" ];
|
||||
}
|
||||
];
|
||||
|
@ -2394,7 +2420,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
}
|
||||
];
|
||||
features = {
|
||||
|
@ -2972,6 +2998,10 @@ rec {
|
|||
name = "async-channel";
|
||||
packageId = "async-channel";
|
||||
}
|
||||
{
|
||||
name = "async-trait";
|
||||
packageId = "async-trait";
|
||||
}
|
||||
{
|
||||
name = "chrono";
|
||||
packageId = "chrono";
|
||||
|
@ -3576,7 +3606,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" ];
|
||||
}
|
||||
];
|
||||
|
@ -4341,7 +4371,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" ];
|
||||
}
|
||||
];
|
||||
|
@ -7690,7 +7720,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" ];
|
||||
}
|
||||
];
|
||||
|
@ -8187,7 +8217,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" "visit-mut" ];
|
||||
}
|
||||
];
|
||||
|
@ -8529,9 +8559,9 @@ rec {
|
|||
};
|
||||
"proc-macro2" = rec {
|
||||
crateName = "proc-macro2";
|
||||
version = "1.0.70";
|
||||
version = "1.0.78";
|
||||
edition = "2021";
|
||||
sha256 = "0fzxg3dkrjy101vv5b6llc8mh74xz1vhhsaiwrn68kzvynxqy9rr";
|
||||
sha256 = "1bjak27pqdn4f4ih1c9nr3manzyavsgqmf76ygw9k76q8pb2lhp2";
|
||||
authors = [
|
||||
"David Tolnay <dtolnay@gmail.com>"
|
||||
"Alex Crichton <alex@alexcrichton.com>"
|
||||
|
@ -8665,9 +8695,9 @@ rec {
|
|||
};
|
||||
"quote" = rec {
|
||||
crateName = "quote";
|
||||
version = "1.0.33";
|
||||
version = "1.0.35";
|
||||
edition = "2018";
|
||||
sha256 = "1biw54hbbr12wdwjac55z1m2x2rylciw83qnjn564a3096jgqrsj";
|
||||
sha256 = "1vv8r2ncaz4pqdr78x7f138ka595sp2ncr1sa2plm4zxbsmwj7i9";
|
||||
authors = [
|
||||
"David Tolnay <dtolnay@gmail.com>"
|
||||
];
|
||||
|
@ -10269,7 +10299,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
}
|
||||
];
|
||||
features = {
|
||||
|
@ -11748,11 +11778,11 @@ rec {
|
|||
};
|
||||
resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "full" "parsing" "printing" "proc-macro" "quote" ];
|
||||
};
|
||||
"syn 2.0.41" = rec {
|
||||
"syn 2.0.48" = rec {
|
||||
crateName = "syn";
|
||||
version = "2.0.41";
|
||||
version = "2.0.48";
|
||||
edition = "2021";
|
||||
sha256 = "0sg2lzkwbwbm229p3kx1yxai43hkc0s1wmk6g47bzhvw8y6b5j24";
|
||||
sha256 = "0gqgfygmrxmp8q32lia9p294kdd501ybn6kn2h4gqza0irik2d8g";
|
||||
authors = [
|
||||
"David Tolnay <dtolnay@gmail.com>"
|
||||
];
|
||||
|
@ -11990,7 +12020,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -12406,7 +12436,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" ];
|
||||
}
|
||||
];
|
||||
|
@ -12820,7 +12850,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
usesDefaultFeatures = false;
|
||||
features = [ "full" "parsing" "printing" "visit-mut" "clone-impls" "extra-traits" "proc-macro" ];
|
||||
}
|
||||
|
@ -13814,7 +13844,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "full" ];
|
||||
}
|
||||
{
|
||||
|
@ -13904,7 +13934,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
features = [ "visit" "full" ];
|
||||
}
|
||||
{
|
||||
|
@ -15388,7 +15418,7 @@ rec {
|
|||
}
|
||||
{
|
||||
name = "syn";
|
||||
packageId = "syn 2.0.41";
|
||||
packageId = "syn 2.0.48";
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ impl RecordProvider for App {
|
|||
.map_err(|_| WriteError::Unhandled)
|
||||
}
|
||||
|
||||
async fn delete_record(&self, id: RecordId) -> Result<(), WriteError> {
|
||||
async fn delete_record(&self, _id: RecordId) -> Result<(), WriteError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,14 @@ You should have received a copy of the GNU General Public License along with Fit
|
|||
*/
|
||||
|
||||
use crate::{
|
||||
app::{App, RecordProvider},
|
||||
app::App,
|
||||
types::DayInterval,
|
||||
view_models::DayDetailViewModel,
|
||||
views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use chrono::{Duration, Local};
|
||||
use emseries::Record;
|
||||
use ft_core::TraxRecord;
|
||||
|
||||
use gio::resources_lookup_data;
|
||||
use gtk::STYLE_PROVIDER_PRIORITY_USER;
|
||||
use std::{cell::RefCell, path::PathBuf, rc::Rc};
|
||||
|
@ -126,6 +125,7 @@ impl AppWindow {
|
|||
s
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn show_welcome_view(&self) {
|
||||
let view = View::Welcome(WelcomeView::new({
|
||||
let s = self.clone();
|
||||
|
@ -178,6 +178,7 @@ impl AppWindow {
|
|||
self.layout.append(¤t_widget.widget());
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn on_apply_config(&self, path: PathBuf) {
|
||||
glib::spawn_future_local({
|
||||
let s = self.clone();
|
||||
|
|
|
@ -223,6 +223,7 @@ impl DayDetail {
|
|||
|
||||
pub struct DayEditPrivate {
|
||||
on_finished: RefCell<Box<dyn Fn()>>,
|
||||
#[allow(unused)]
|
||||
workout_rows: RefCell<gtk::Box>,
|
||||
view_model: RefCell<Option<DayDetailViewModel>>,
|
||||
}
|
||||
|
|
|
@ -106,6 +106,7 @@ impl<T: Clone + std::fmt::Debug + 'static> TextEntry<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn time_field<OnUpdate>(
|
||||
value: Option<TimeFormatter>,
|
||||
on_update: OnUpdate,
|
||||
|
@ -122,6 +123,7 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn distance_field<OnUpdate>(
|
||||
value: Option<DistanceFormatter>,
|
||||
on_update: OnUpdate,
|
||||
|
@ -138,6 +140,7 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn duration_field<OnUpdate>(
|
||||
value: Option<DurationFormatter>,
|
||||
on_update: OnUpdate,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
||||
Copyright 2023-2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
||||
|
||||
This file is part of FitnessTrax.
|
||||
|
||||
|
@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
|
|||
// use crate::components::{EditView, ParseError, TextEntry};
|
||||
// use chrono::{Local, NaiveDate};
|
||||
// use dimensioned::si;
|
||||
use ft_core::{RecordType, TimeDistance};
|
||||
use ft_core::TimeDistance;
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use std::cell::RefCell;
|
||||
|
@ -44,7 +44,7 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl TimeDistanceView {
|
||||
pub fn new(type_: RecordType, record: TimeDistance) -> Self {
|
||||
pub fn new(record: TimeDistance) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
s.set_orientation(gtk::Orientation::Vertical);
|
||||
s.set_hexpand(true);
|
||||
|
@ -58,12 +58,14 @@ impl TimeDistanceView {
|
|||
.build(),
|
||||
);
|
||||
|
||||
/*
|
||||
first_row.append(
|
||||
>k::Label::builder()
|
||||
.halign(gtk::Align::Start)
|
||||
.label(format!("{:?}", type_))
|
||||
.build(),
|
||||
);
|
||||
*/
|
||||
|
||||
first_row.append(
|
||||
>k::Label::builder()
|
||||
|
|
|
@ -15,7 +15,6 @@ You should have received a copy of the GNU General Public License along with Fit
|
|||
*/
|
||||
|
||||
use crate::{
|
||||
components::TextEntry,
|
||||
types::{FormatOption, WeightFormatter},
|
||||
};
|
||||
use gtk::prelude::*;
|
||||
|
|
|
@ -53,6 +53,7 @@ impl Iterator for DayIterator {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum FormatOption {
|
||||
Abbreviated,
|
||||
#[allow(unused)]
|
||||
Full,
|
||||
}
|
||||
|
||||
|
@ -60,6 +61,7 @@ pub enum FormatOption {
|
|||
pub struct TimeFormatter(chrono::NaiveTime);
|
||||
|
||||
impl TimeFormatter {
|
||||
#[allow(unused)]
|
||||
pub fn format(&self, option: FormatOption) -> String {
|
||||
match option {
|
||||
FormatOption::Abbreviated => self.0.format("%H:%M"),
|
||||
|
@ -68,6 +70,7 @@ impl TimeFormatter {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn parse(s: &str) -> Result<TimeFormatter, ParseError> {
|
||||
let parts = s
|
||||
.split(':')
|
||||
|
@ -104,6 +107,7 @@ impl From<chrono::NaiveTime> for TimeFormatter {
|
|||
pub struct WeightFormatter(si::Kilogram<f64>);
|
||||
|
||||
impl WeightFormatter {
|
||||
#[allow(unused)]
|
||||
pub fn format(&self, option: FormatOption) -> String {
|
||||
match option {
|
||||
FormatOption::Abbreviated => format!("{} kg", self.0.value_unsafe),
|
||||
|
@ -111,6 +115,7 @@ impl WeightFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn parse(s: &str) -> Result<WeightFormatter, ParseError> {
|
||||
s.parse::<f64>()
|
||||
.map(|w| WeightFormatter(w * si::KG))
|
||||
|
@ -149,6 +154,7 @@ impl From<si::Kilogram<f64>> for WeightFormatter {
|
|||
pub struct DistanceFormatter(si::Meter<f64>);
|
||||
|
||||
impl DistanceFormatter {
|
||||
#[allow(unused)]
|
||||
pub fn format(&self, option: FormatOption) -> String {
|
||||
match option {
|
||||
FormatOption::Abbreviated => format!("{} km", self.0.value_unsafe / 1000.),
|
||||
|
@ -156,6 +162,7 @@ impl DistanceFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn parse(s: &str) -> Result<DistanceFormatter, ParseError> {
|
||||
let value = s.parse::<f64>().map_err(|_| ParseError)?;
|
||||
Ok(DistanceFormatter(value * 1000. * si::M))
|
||||
|
@ -193,6 +200,7 @@ impl From<si::Meter<f64>> for DistanceFormatter {
|
|||
pub struct DurationFormatter(si::Second<f64>);
|
||||
|
||||
impl DurationFormatter {
|
||||
#[allow(unused)]
|
||||
pub fn format(&self, option: FormatOption) -> String {
|
||||
let (hours, minutes) = self.hours_and_minutes();
|
||||
let (h, m) = match option {
|
||||
|
@ -206,11 +214,13 @@ impl DurationFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn parse(s: &str) -> Result<DurationFormatter, ParseError> {
|
||||
let value = s.parse::<f64>().map_err(|_| ParseError)?;
|
||||
Ok(DurationFormatter(value * 60. * si::S))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn hours_and_minutes(&self) -> (i64, i64) {
|
||||
let minutes: i64 = (self.0.value_unsafe / 60.).round() as i64;
|
||||
let hours: i64 = minutes / 60;
|
||||
|
|
|
@ -14,11 +14,13 @@ General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::app::{ReadError, RecordProvider, WriteError};
|
||||
#[allow(unused_imports)]
|
||||
use chrono::NaiveDate;
|
||||
use dimensioned::si;
|
||||
use emseries::{Record, RecordId, Recordable};
|
||||
use ft_core::{RecordType, TimeDistance, TimeDistanceWorkoutType, TraxRecord};
|
||||
use ft_core::{TimeDistance, TimeDistanceActivity, TraxRecord};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Deref,
|
||||
|
@ -46,20 +48,34 @@ impl<T: Clone + emseries::Recordable> RecordState<T> {
|
|||
|
||||
fn exists(&self) -> bool {
|
||||
match self {
|
||||
RecordState::Original(ref r) => true,
|
||||
RecordState::New(ref r) => true,
|
||||
RecordState::Updated(ref r) => true,
|
||||
RecordState::Deleted(ref r) => false,
|
||||
RecordState::Original(_) => true,
|
||||
RecordState::New(_) => true,
|
||||
RecordState::Updated(_) => true,
|
||||
RecordState::Deleted(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_value(self, value: T) -> RecordState<T> {
|
||||
match self {
|
||||
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..r }),
|
||||
fn set_value(&mut self, value: T) {
|
||||
*self = match self {
|
||||
RecordState::Original(r) => RecordState::Updated(Record {
|
||||
id: r.id.clone(),
|
||||
data: value,
|
||||
}),
|
||||
RecordState::New(_) => RecordState::New(value),
|
||||
RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..r }),
|
||||
RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..r }),
|
||||
}
|
||||
RecordState::Updated(r) => RecordState::Updated(Record {
|
||||
id: r.id.clone(),
|
||||
data: value,
|
||||
}),
|
||||
RecordState::Deleted(r) => RecordState::Updated(Record {
|
||||
id: r.id.clone(),
|
||||
data: value,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
fn with_value(mut self, value: T) -> RecordState<T> {
|
||||
self.set_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -158,7 +174,7 @@ impl DayDetailViewModel {
|
|||
}
|
||||
|
||||
pub fn weight(&self) -> Option<si::Kilogram<f64>> {
|
||||
(*self.weight.read().unwrap()).as_ref().map(|w| (*w).weight)
|
||||
(*self.weight.read().unwrap()).as_ref().map(|w| w.weight)
|
||||
}
|
||||
|
||||
pub fn set_weight(&self, new_weight: si::Kilogram<f64>) {
|
||||
|
@ -195,15 +211,16 @@ impl DayDetailViewModel {
|
|||
*record = Some(new_record);
|
||||
}
|
||||
|
||||
pub fn new_time_distance(&self, type_: TimeDistanceWorkoutType) -> Record<TimeDistance> {
|
||||
pub fn new_time_distance(&self, activity: TimeDistanceActivity) -> Record<TimeDistance> {
|
||||
let id = RecordId::default();
|
||||
let workout = TimeDistance {
|
||||
datetime: chrono::Local::now().into(),
|
||||
activity,
|
||||
distance: None,
|
||||
duration: None,
|
||||
comments: None,
|
||||
};
|
||||
let tr = TraxRecord::from_time_distance(type_, workout.clone());
|
||||
let tr = TraxRecord::from(workout.clone());
|
||||
self.records
|
||||
.write()
|
||||
.unwrap()
|
||||
|
@ -220,44 +237,19 @@ impl DayDetailViewModel {
|
|||
let data = workout.data.clone();
|
||||
|
||||
let mut record_set = self.records.write().unwrap();
|
||||
if let Some(record_state) = record_set.get(&id).clone() {
|
||||
let updated_state = match **record_state {
|
||||
TraxRecord::BikeRide(_) => {
|
||||
Some(record_state.clone().with_value(TraxRecord::BikeRide(data)))
|
||||
}
|
||||
TraxRecord::Row(_) => Some(record_state.clone().with_value(TraxRecord::Row(data))),
|
||||
TraxRecord::Run(_) => Some(record_state.clone().with_value(TraxRecord::Run(data))),
|
||||
TraxRecord::Swim(_) => {
|
||||
Some(record_state.clone().with_value(TraxRecord::Swim(data)))
|
||||
}
|
||||
TraxRecord::Walk(_) => {
|
||||
Some(record_state.clone().with_value(TraxRecord::Walk(data)))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(updated_state) = updated_state {
|
||||
record_set.insert(id, updated_state);
|
||||
}
|
||||
}
|
||||
record_set.entry(id).and_modify(|record_state| {
|
||||
record_state.set_value(TraxRecord::TimeDistance(data));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn time_distance_records(
|
||||
&self,
|
||||
type_: TimeDistanceWorkoutType,
|
||||
) -> Vec<Record<TimeDistance>> {
|
||||
pub fn time_distance_records(&self) -> Vec<Record<TimeDistance>> {
|
||||
self.records
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|(_, record)| record.exists())
|
||||
.filter(|(_, workout_state)| workout_state.is_time_distance_type(type_))
|
||||
.filter_map(|(id, record_state)| match **record_state {
|
||||
TraxRecord::BikeRide(ref workout)
|
||||
| TraxRecord::Row(ref workout)
|
||||
| TraxRecord::Run(ref workout)
|
||||
| TraxRecord::Swim(ref workout)
|
||||
| TraxRecord::Walk(ref workout) => Some(Record {
|
||||
TraxRecord::TimeDistance(ref workout) => Some(Record {
|
||||
id: id.clone(),
|
||||
data: workout.clone(),
|
||||
}),
|
||||
|
@ -268,28 +260,32 @@ impl DayDetailViewModel {
|
|||
|
||||
pub fn time_distance_summary(
|
||||
&self,
|
||||
type_: TimeDistanceWorkoutType,
|
||||
activity: TimeDistanceActivity,
|
||||
) -> (si::Meter<f64>, si::Second<f64>) {
|
||||
self.time_distance_records(type_).into_iter().fold(
|
||||
(0. * si::M, 0. * si::S),
|
||||
|(distance, duration), workout| match (workout.data.distance, workout.data.duration) {
|
||||
(Some(distance_), Some(duration_)) => (distance + distance_, duration + duration_),
|
||||
(Some(distance_), None) => (distance + distance_, duration),
|
||||
(None, Some(duration_)) => (distance, duration + duration_),
|
||||
(None, None) => (distance, duration),
|
||||
},
|
||||
)
|
||||
self.time_distance_records()
|
||||
.into_iter()
|
||||
.filter(|rec| rec.data.activity == activity)
|
||||
.fold(
|
||||
(0. * si::M, 0. * si::S),
|
||||
|(distance, duration), workout| match (workout.data.distance, workout.data.duration)
|
||||
{
|
||||
(Some(distance_), Some(duration_)) => {
|
||||
(distance + distance_, duration + duration_)
|
||||
}
|
||||
(Some(distance_), None) => (distance + distance_, duration),
|
||||
(None, Some(duration_)) => (distance, duration + duration_),
|
||||
(None, None) => (distance, duration),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn get_record(&self, id: &RecordId) -> Option<Record<TraxRecord>> {
|
||||
let record_set = self.records.read().unwrap();
|
||||
match record_set.get(&id) {
|
||||
Some(record) => Some(Record {
|
||||
id: id.clone(),
|
||||
data: (**record).clone(),
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
record_set.get(id).map(|record| Record {
|
||||
id: id.clone(),
|
||||
data: (**record).clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_record(&self, id: RecordId) {
|
||||
|
@ -486,28 +482,29 @@ mod test {
|
|||
Record {
|
||||
id: RecordId::default(),
|
||||
data: TraxRecord::Weight(ft_core::Weight {
|
||||
date: oct_12.clone(),
|
||||
date: oct_12,
|
||||
weight: 93. * si::KG,
|
||||
}),
|
||||
},
|
||||
Record {
|
||||
id: RecordId::default(),
|
||||
data: TraxRecord::Weight(ft_core::Weight {
|
||||
date: oct_13.clone(),
|
||||
date: oct_13,
|
||||
weight: 95. * si::KG,
|
||||
}),
|
||||
},
|
||||
Record {
|
||||
id: RecordId::default(),
|
||||
data: TraxRecord::Steps(ft_core::Steps {
|
||||
date: oct_13.clone(),
|
||||
date: oct_13,
|
||||
count: 2500,
|
||||
}),
|
||||
},
|
||||
Record {
|
||||
id: RecordId::default(),
|
||||
data: TraxRecord::BikeRide(ft_core::TimeDistance {
|
||||
data: TraxRecord::TimeDistance(ft_core::TimeDistance {
|
||||
datetime: oct_13_am.clone(),
|
||||
activity: TimeDistanceActivity::BikeRide,
|
||||
distance: Some(15000. * si::M),
|
||||
duration: Some(3600. * si::S),
|
||||
comments: Some("somecomments present".to_owned()),
|
||||
|
@ -522,7 +519,7 @@ mod test {
|
|||
|
||||
#[tokio::test]
|
||||
async fn it_honors_only_the_first_weight_and_step_record() {
|
||||
let (view_model, provider) = create_view_model().await;
|
||||
let (view_model, _provider) = create_view_model().await;
|
||||
assert_eq!(view_model.weight(), Some(95. * si::KG));
|
||||
assert_eq!(view_model.steps(), Some(2500));
|
||||
}
|
||||
|
@ -557,11 +554,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(TimeDistanceWorkoutType::BikeRide),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
(0. * si::M, 0. * si::S)
|
||||
);
|
||||
|
||||
let mut record = view_model.new_time_distance(TimeDistanceWorkoutType::BikeRide);
|
||||
let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide);
|
||||
record.data.duration = Some(60. * si::S);
|
||||
view_model.async_save().await;
|
||||
|
||||
|
@ -574,24 +571,24 @@ 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(TimeDistanceWorkoutType::BikeRide),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
(0. * si::M, 0. * si::S)
|
||||
);
|
||||
|
||||
let mut record = view_model.new_time_distance(TimeDistanceWorkoutType::BikeRide);
|
||||
let mut record = view_model.new_time_distance(TimeDistanceActivity::BikeRide);
|
||||
record.data.duration = Some(60. * si::S);
|
||||
view_model.update_time_distance(record.clone());
|
||||
let record = Record {
|
||||
id: record.id,
|
||||
data: TraxRecord::BikeRide(record.data),
|
||||
data: TraxRecord::TimeDistance(record.data),
|
||||
};
|
||||
assert_eq!(view_model.get_record(&record.id), Some(record));
|
||||
assert_eq!(
|
||||
view_model.time_distance_summary(TimeDistanceWorkoutType::BikeRide),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
(0. * si::M, 60. * si::S)
|
||||
);
|
||||
assert_eq!(
|
||||
view_model.time_distance_summary(TimeDistanceWorkoutType::Run),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::Running),
|
||||
(0. * si::M, 0. * si::S)
|
||||
);
|
||||
view_model.async_save().await;
|
||||
|
@ -604,11 +601,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn it_can_update_an_existing_record() {
|
||||
let (view_model, provider) = create_view_model().await;
|
||||
let mut workout = view_model
|
||||
.time_distance_records(TimeDistanceWorkoutType::BikeRide)
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap();
|
||||
let mut workout = view_model.time_distance_records().first().cloned().unwrap();
|
||||
|
||||
println!("found record: {:?}", workout);
|
||||
|
||||
|
@ -616,7 +609,7 @@ mod test {
|
|||
view_model.update_time_distance(workout.clone());
|
||||
|
||||
assert_eq!(
|
||||
view_model.time_distance_summary(TimeDistanceWorkoutType::BikeRide),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
(15000. * si::M, 1800. * si::S)
|
||||
);
|
||||
|
||||
|
@ -631,11 +624,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(TimeDistanceWorkoutType::BikeRide),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
(0. * si::M, 0. * si::S)
|
||||
);
|
||||
|
||||
let record = view_model.new_time_distance(TimeDistanceWorkoutType::BikeRide);
|
||||
let record = view_model.new_time_distance(TimeDistanceActivity::BikeRide);
|
||||
view_model.remove_record(record.id);
|
||||
view_model.save();
|
||||
|
||||
|
@ -647,15 +640,11 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn it_can_delete_an_existing_record() {
|
||||
let (view_model, provider) = create_view_model().await;
|
||||
let mut workout = view_model
|
||||
.time_distance_records(TimeDistanceWorkoutType::BikeRide)
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap();
|
||||
let mut workout = view_model.time_distance_records().first().cloned().unwrap();
|
||||
|
||||
view_model.remove_record(workout.id);
|
||||
assert_eq!(
|
||||
view_model.time_distance_summary(TimeDistanceWorkoutType::BikeRide),
|
||||
view_model.time_distance_summary(TimeDistanceActivity::BikeRide),
|
||||
(0. * si::M, 0. * si::S)
|
||||
);
|
||||
view_model.async_save().await;
|
||||
|
|
|
@ -17,12 +17,12 @@ You should have received a copy of the GNU General Public License along with Fit
|
|||
use crate::{
|
||||
app::App, components::DaySummary, types::DayInterval, view_models::DayDetailViewModel,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use emseries::Record;
|
||||
use ft_core::TraxRecord;
|
||||
|
||||
|
||||
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// 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
|
||||
|
|
|
@ -30,6 +30,7 @@ pub use welcome_view::WelcomeView;
|
|||
|
||||
pub enum View {
|
||||
Placeholder(PlaceholderView),
|
||||
#[allow(unused)]
|
||||
Welcome(WelcomeView),
|
||||
Historical(HistoricalView),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mod legacy;
|
||||
|
||||
mod types;
|
||||
pub use types::{RecordType, Steps, TimeDistance, TimeDistanceWorkoutType, TraxRecord, Weight};
|
||||
pub use types::{Steps, TimeDistance, TimeDistanceActivity, TraxRecord, Weight};
|
||||
|
|
|
@ -33,6 +33,15 @@ impl Recordable for Steps {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TimeDistanceActivity {
|
||||
BikeRide,
|
||||
Running,
|
||||
Rowing,
|
||||
Swimming,
|
||||
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
|
||||
|
@ -48,6 +57,8 @@ pub struct TimeDistance {
|
|||
/// in the database, but we can still get a Naive Date from the DateTime, which will still read
|
||||
/// as the original day.
|
||||
pub datetime: DateTime<FixedOffset>,
|
||||
/// The activity
|
||||
pub activity: TimeDistanceActivity,
|
||||
/// The distance travelled. This is optional because such a workout makes sense even without
|
||||
/// the distance.
|
||||
pub distance: Option<si::Meter<f64>>,
|
||||
|
@ -85,61 +96,15 @@ impl Recordable for Weight {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum RecordType {
|
||||
BikeRide,
|
||||
Row,
|
||||
Run,
|
||||
Steps,
|
||||
Swim,
|
||||
Walk,
|
||||
Weight,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TimeDistanceWorkoutType {
|
||||
BikeRide,
|
||||
Row,
|
||||
Run,
|
||||
Swim,
|
||||
Walk,
|
||||
}
|
||||
|
||||
/// The unified data structure for all records that are part of the app.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TraxRecord {
|
||||
BikeRide(TimeDistance),
|
||||
Row(TimeDistance),
|
||||
Run(TimeDistance),
|
||||
TimeDistance(TimeDistance),
|
||||
Steps(Steps),
|
||||
Swim(TimeDistance),
|
||||
Walk(TimeDistance),
|
||||
Weight(Weight),
|
||||
}
|
||||
|
||||
impl TraxRecord {
|
||||
pub fn from_time_distance(type_: TimeDistanceWorkoutType, workout: TimeDistance) -> Self {
|
||||
match type_ {
|
||||
TimeDistanceWorkoutType::BikeRide => Self::BikeRide(workout),
|
||||
TimeDistanceWorkoutType::Run => Self::Run(workout),
|
||||
TimeDistanceWorkoutType::Row => Self::Row(workout),
|
||||
TimeDistanceWorkoutType::Swim => Self::Swim(workout),
|
||||
TimeDistanceWorkoutType::Walk => Self::Walk(workout),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workout_type(&self) -> RecordType {
|
||||
match self {
|
||||
TraxRecord::BikeRide(_) => RecordType::BikeRide,
|
||||
TraxRecord::Row(_) => RecordType::Row,
|
||||
TraxRecord::Run(_) => RecordType::Run,
|
||||
TraxRecord::Steps(_) => RecordType::Steps,
|
||||
TraxRecord::Swim(_) => RecordType::Swim,
|
||||
TraxRecord::Walk(_) => RecordType::Walk,
|
||||
TraxRecord::Weight(_) => RecordType::Weight,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_weight(&self) -> bool {
|
||||
matches!(self, TraxRecord::Weight(_))
|
||||
}
|
||||
|
@ -151,15 +116,27 @@ impl TraxRecord {
|
|||
pub fn is_time_distance(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
TraxRecord::BikeRide(_)
|
||||
| TraxRecord::Row(_)
|
||||
| TraxRecord::Run(_)
|
||||
| TraxRecord::Swim(_)
|
||||
| TraxRecord::Walk(_)
|
||||
TraxRecord::TimeDistance(TimeDistance {
|
||||
activity: TimeDistanceActivity::BikeRide,
|
||||
..
|
||||
}) | TraxRecord::TimeDistance(TimeDistance {
|
||||
activity: TimeDistanceActivity::Running,
|
||||
..
|
||||
}) | TraxRecord::TimeDistance(TimeDistance {
|
||||
activity: TimeDistanceActivity::Rowing,
|
||||
..
|
||||
}) | TraxRecord::TimeDistance(TimeDistance {
|
||||
activity: TimeDistanceActivity::Swimming,
|
||||
..
|
||||
}) | TraxRecord::TimeDistance(TimeDistance {
|
||||
activity: TimeDistanceActivity::Walking,
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_time_distance_type(&self, type_: TimeDistanceWorkoutType) -> bool {
|
||||
/*
|
||||
pub fn is_time_distance_type(&self, type_: TimeDistanceActivity) -> bool {
|
||||
match type_ {
|
||||
TimeDistanceWorkoutType::BikeRide => matches!(self, TraxRecord::BikeRide(_)),
|
||||
TimeDistanceWorkoutType::Row => matches!(self, TraxRecord::Row(_)),
|
||||
|
@ -168,17 +145,14 @@ impl TraxRecord {
|
|||
TimeDistanceWorkoutType::Walk => matches!(self, TraxRecord::Walk(_)),
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl Recordable for TraxRecord {
|
||||
fn timestamp(&self) -> Timestamp {
|
||||
match self {
|
||||
TraxRecord::BikeRide(rec) => Timestamp::DateTime(rec.datetime),
|
||||
TraxRecord::Row(rec) => Timestamp::DateTime(rec.datetime),
|
||||
TraxRecord::Run(rec) => Timestamp::DateTime(rec.datetime),
|
||||
TraxRecord::TimeDistance(rec) => Timestamp::DateTime(rec.datetime),
|
||||
TraxRecord::Steps(rec) => rec.timestamp(),
|
||||
TraxRecord::Swim(rec) => Timestamp::DateTime(rec.datetime),
|
||||
TraxRecord::Walk(rec) => Timestamp::DateTime(rec.datetime),
|
||||
TraxRecord::Weight(rec) => rec.timestamp(),
|
||||
}
|
||||
}
|
||||
|
@ -188,6 +162,12 @@ impl Recordable for TraxRecord {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TimeDistance> for TraxRecord {
|
||||
fn from(td: TimeDistance) -> Self {
|
||||
Self::TimeDistance(td)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
Loading…
Reference in New Issue