From 18e7e4fe2f7adb22be10f50d2c36826f71a5bfe5 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 18 Jan 2024 09:00:08 -0500 Subject: [PATCH 1/3] Start setting up the day detail view model I've created the view model and added a getter function for the weight. I'm passing the view model now to the DayDetailView, DayDetail, and DayEdit. I'm starting to set up the Save function for the view model, draining all of the updated records and saving them. None of the components yet save any updates to the view model, so updated_records is always going to be empty until I figure that out. --- fitnesstrax/app/src/app_window.rs | 11 +- fitnesstrax/app/src/components/day.rs | 77 ++++++------ fitnesstrax/app/src/components/weight.rs | 8 +- fitnesstrax/app/src/views/day_detail_view.rs | 126 ++++++++++++------- fitnesstrax/app/src/views/mod.rs | 2 +- 5 files changed, 134 insertions(+), 90 deletions(-) diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 5fc3213..23b0492 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -16,7 +16,9 @@ You should have received a copy of the GNU General Public License along with Fit use crate::{ app::App, - views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView}, + views::{ + DayDetailView, DayDetailViewModel, HistoricalView, PlaceholderView, View, WelcomeView, + }, }; use adw::prelude::*; use chrono::{Duration, Local}; @@ -137,7 +139,12 @@ impl AppWindow { Rc::new(move |date, records| { let layout = gtk::Box::new(gtk::Orientation::Vertical, 0); layout.append(&adw::HeaderBar::new()); - layout.append(&DayDetailView::new(date, records, s.app.clone())); + // layout.append(&DayDetailView::new(date, records, s.app.clone())); + layout.append(&DayDetailView::new(DayDetailViewModel::new( + date, + records, + s.app.clone(), + ))); let page = &adw::NavigationPage::builder() .title(date.format("%Y-%m-%d").to_string()) .child(&layout) diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 1652d6c..9fa601f 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -16,7 +16,10 @@ You should have received a copy of the GNU General Public License along with Fit // use chrono::NaiveDate; // use ft_core::TraxRecord; -use crate::components::{ActionGroup, TimeDistanceView, Weight}; +use crate::{ + components::{ActionGroup, TimeDistanceView, Weight}, + views::DayDetailViewModel, +}; use emseries::Record; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; @@ -102,27 +105,14 @@ impl DaySummary { } } -pub struct DayDetailPrivate { - date: gtk::Label, - weight: RefCell>, -} +#[derive(Default)] +pub struct DayDetailPrivate {} #[glib::object_subclass] impl ObjectSubclass for DayDetailPrivate { const NAME: &'static str = "DayDetail"; type Type = DayDetail; type ParentType = gtk::Box; - - fn new() -> Self { - let date = gtk::Label::builder() - .css_classes(["daysummary-date"]) - .halign(gtk::Align::Start) - .build(); - Self { - date, - weight: RefCell::new(None), - } - } } impl ObjectImpl for DayDetailPrivate {} @@ -134,11 +124,7 @@ glib::wrapper! { } impl DayDetail { - pub fn new( - date: chrono::NaiveDate, - records: Vec>, - on_edit: OnEdit, - ) -> Self + pub fn new(view_model: DayDetailViewModel, on_edit: OnEdit) -> Self where OnEdit: Fn() + 'static, { @@ -148,7 +134,7 @@ impl DayDetail { s.append( &ActionGroup::builder() - .primary_action("Edit", Box::new(on_edit)) + .primary_action("Edit", Box::new(move || on_edit())) .build(), ); @@ -167,6 +153,7 @@ impl DayDetail { s.add_controller(click_controller); */ + /* let weight_record = records.iter().find_map(|record| match record { Record { id, @@ -174,13 +161,12 @@ impl DayDetail { } => Some((id.clone(), record.clone())), _ => None, }); + */ - let weight_view = match weight_record { - Some((id, data)) => Weight::new(Some(data.clone())), - None => Weight::new(None), - }; + let weight_view = Weight::new(view_model.weight()); s.append(&weight_view.widget()); + /* records.into_iter().for_each(|record| { let record_view = match record { Record { @@ -224,21 +210,20 @@ impl DayDetail { s.append(&record_view); } }); + */ s } } pub struct DayEditPrivate { - date: gtk::Label, - weight: Rc, + on_finished: RefCell>, } impl Default for DayEditPrivate { fn default() -> Self { Self { - date: gtk::Label::new(None), - weight: Rc::new(WeightEdit::new(None)), + on_finished: RefCell::new(Box::new(|| {})), } } } @@ -259,25 +244,20 @@ glib::wrapper! { } impl DayEdit { - pub fn new( - date: chrono::NaiveDate, - records: Vec>, - on_put_record: PutRecordFn, - on_update_record: UpdateRecordFn, - on_cancel: CancelFn, - ) -> Self + pub fn new(view_model: DayDetailViewModel, on_finished: OnFinished) -> Self where - PutRecordFn: Fn(ft_core::TraxRecord) + 'static, - UpdateRecordFn: Fn(Record) + 'static, - CancelFn: Fn() + 'static, + OnFinished: Fn() + 'static, { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_hexpand(true); + *s.imp().on_finished.borrow_mut() = Box::new(on_finished); + s.append( &ActionGroup::builder() .primary_action("Save", { + /* let s = s.clone(); let records = records.clone(); move || { @@ -309,11 +289,20 @@ impl DayEdit { } }; } + */ + let s = s.clone(); + move || { + s.finish(); + } + }) + .secondary_action("Cancel", { + let s = s.clone(); + move || s.finish() }) - .secondary_action("Cancel", on_cancel) .build(), ); + /* let weight_record = records.iter().find_map(|record| match record { Record { id, @@ -327,7 +316,13 @@ impl DayEdit { None => s.imp().weight.set_value(None), }; s.append(&s.imp().weight.widget()); + */ + s.append(&WeightEdit::new(view_model.weight()).widget()); s } + + fn finish(&self) { + (self.imp().on_finished.borrow())() + } } diff --git a/fitnesstrax/app/src/components/weight.rs b/fitnesstrax/app/src/components/weight.rs index 552b107..ddba3b5 100644 --- a/fitnesstrax/app/src/components/weight.rs +++ b/fitnesstrax/app/src/components/weight.rs @@ -29,14 +29,14 @@ pub struct Weight { } impl Weight { - pub fn new(weight: Option) -> Self { + pub fn new(weight: Option>) -> Self { let label = gtk::Label::builder() .css_classes(["card", "weight-view"]) .can_focus(true) .build(); match weight { - Some(w) => label.set_text(&format!("{:?}", w.weight)), + Some(w) => label.set_text(&format!("{:?}", w)), None => label.set_text("No weight recorded"), } @@ -54,11 +54,11 @@ pub struct WeightEdit { } impl WeightEdit { - pub fn new(weight: Option) -> Self { + pub fn new(weight: Option>) -> Self { Self { entry: TextEntry::new( "0 kg", - weight.map(|w| w.weight), + weight, |val: &si::Kilogram| val.to_string(), |v: &str| v.parse::().map(|w| w * si::KG).map_err(|_| ParseError), ), diff --git a/fitnesstrax/app/src/views/day_detail_view.rs b/fitnesstrax/app/src/views/day_detail_view.rs index c73d1f0..eab3611 100644 --- a/fitnesstrax/app/src/views/day_detail_view.rs +++ b/fitnesstrax/app/src/views/day_detail_view.rs @@ -18,18 +18,85 @@ use crate::{ app::App, components::{DayDetail, DayEdit, Singleton, SingletonImpl}, }; -use emseries::Record; +use dimensioned::si; +use emseries::{Record, RecordId}; use ft_core::TraxRecord; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::cell::RefCell; +use std::{ + cell::RefCell, + rc::Rc, + sync::{Arc, RwLock}, +}; + +#[derive(Default)] +struct DayDetailViewModelInner { + records: Vec>, + updated_records: Vec>, + new_records: Vec, + deleted_records: Vec, +} + +#[derive(Clone, Default)] +pub struct DayDetailViewModel { + app: Option, + pub date: chrono::NaiveDate, + inner: Arc>, +} + +impl DayDetailViewModel { + pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { + Self { + app: Some(app), + date, + inner: Arc::new(RwLock::new(DayDetailViewModelInner { + records, + updated_records: vec![], + new_records: vec![], + deleted_records: vec![], + })), + } + } + + pub fn weight(&self) -> Option> { + self.inner + .read() + .unwrap() + .records + .iter() + .find_map(|record| match record { + Record { + data: ft_core::TraxRecord::Weight(record), + .. + } => Some(record.weight.clone()), + _ => None, + }) + } + + pub fn save(&self) { + glib::spawn_future({ + let s = self.clone(); + async move { + if let Some(app) = s.app { + let updated_records = { + let mut data = s.inner.write().unwrap(); + data.updated_records + .drain(..) + .collect::>>() + }; + for record in updated_records.into_iter() { + let _ = app.update_record(record.clone()).await; + } + } + } + }); + } +} #[derive(Default)] pub struct DayDetailViewPrivate { - app: RefCell>, container: Singleton, - date: RefCell, - records: RefCell>>, + view_model: RefCell, } #[glib::object_subclass] @@ -49,62 +116,36 @@ glib::wrapper! { } impl DayDetailView { - pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { + pub fn new(view_model: DayDetailViewModel) -> Self { let s: Self = Object::builder().build(); - - *s.imp().date.borrow_mut() = date; - *s.imp().records.borrow_mut() = records; - *s.imp().app.borrow_mut() = Some(app); + *s.imp().view_model.borrow_mut() = view_model; s.append(&s.imp().container); - /* - s.imp() - .container - .swap(&DayDetail::new(date, records.clone(), { - let s = s.clone(); - let records = records.clone(); - move || { - s.imp().container.swap(&DayEdit::new( - date, - records, - s.on_put_record(), - // s.on_update_record(), - |_| {}, - )) - } - })); - */ - s.view(); s } fn view(&self) { - self.imp().container.swap(&DayDetail::new( - self.imp().date.borrow().clone(), - self.imp().records.borrow().clone(), - { + self.imp() + .container + .swap(&DayDetail::new(self.imp().view_model.borrow().clone(), { let s = self.clone(); move || s.edit() - }, - )); + })); } fn edit(&self) { - self.imp().container.swap(&DayEdit::new( - self.imp().date.borrow().clone(), - self.imp().records.borrow().clone(), - self.on_put_record(), - self.on_update_record(), - { + self.imp() + .container + .swap(&DayEdit::new(self.imp().view_model.borrow().clone(), { let s = self.clone(); move || s.view() - }, - )); + })); } + /* fn on_put_record(&self) -> Box { let s = self.clone(); let app = self.imp().app.clone(); @@ -161,4 +202,5 @@ impl DayDetailView { }); }) } + */ } diff --git a/fitnesstrax/app/src/views/mod.rs b/fitnesstrax/app/src/views/mod.rs index 9957823..4993459 100644 --- a/fitnesstrax/app/src/views/mod.rs +++ b/fitnesstrax/app/src/views/mod.rs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit use gtk::prelude::*; mod day_detail_view; -pub use day_detail_view::DayDetailView; +pub use day_detail_view::{DayDetailView, DayDetailViewModel}; mod historical_view; pub use historical_view::HistoricalView; -- 2.44.1 From 1fe318068b4396b2483a7cc646893da136eec34f Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 20 Jan 2024 11:16:31 -0500 Subject: [PATCH 2/3] Set up a view model for the day detail view --- .vscode/settings.json | 3 + fitnesstrax/app/src/app_window.rs | 5 +- fitnesstrax/app/src/components/day.rs | 70 ++---- fitnesstrax/app/src/components/text_entry.rs | 1 + fitnesstrax/app/src/components/weight.rs | 16 +- fitnesstrax/app/src/main.rs | 2 +- fitnesstrax/app/src/view_models/day_detail.rs | 220 ++++++++++++++++++ fitnesstrax/app/src/view_models/mod.rs | 18 ++ fitnesstrax/app/src/views/day_detail_view.rs | 126 +--------- fitnesstrax/app/src/views/mod.rs | 2 +- fitnesstrax/core/src/types.rs | 31 ++- 11 files changed, 310 insertions(+), 184 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 fitnesstrax/app/src/view_models/day_detail.rs create mode 100644 fitnesstrax/app/src/view_models/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4d9636b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.showUnlinkedFileNotification": false +} \ No newline at end of file diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 23b0492..9b230ea 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -16,9 +16,8 @@ You should have received a copy of the GNU General Public License along with Fit use crate::{ app::App, - views::{ - DayDetailView, DayDetailViewModel, HistoricalView, PlaceholderView, View, WelcomeView, - }, + view_models::DayDetailViewModel, + views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView}, }; use adw::prelude::*; use chrono::{Duration, Local}; diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index 9fa601f..f0fe13a 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License along with Fit // use chrono::NaiveDate; // use ft_core::TraxRecord; use crate::{ - components::{ActionGroup, TimeDistanceView, Weight}, - views::DayDetailViewModel, + components::{ActionGroup, Weight}, + view_models::DayDetailViewModel, }; use emseries::Record; use glib::Object; @@ -257,67 +257,33 @@ impl DayEdit { s.append( &ActionGroup::builder() .primary_action("Save", { - /* - let s = s.clone(); - let records = records.clone(); - move || { - let weight_record = records.iter().find_map(|record| match record { - Record { - id, - data: ft_core::TraxRecord::Weight(w), - } => Some((id, w)), - _ => None, - }); - - let weight = s.imp().weight.value(); - - if let Some(weight) = weight { - match weight_record { - Some((id, _)) => on_update_record(Record { - id: id.clone(), - data: ft_core::TraxRecord::Weight(ft_core::Weight { - date, - weight, - }), - }), - None => { - on_put_record(ft_core::TraxRecord::Weight(ft_core::Weight { - date, - weight, - })) - } - } - }; - } - */ let s = s.clone(); + let view_model = view_model.clone(); move || { + view_model.save(); s.finish(); } }) .secondary_action("Cancel", { let s = s.clone(); - move || s.finish() + let view_model = view_model.clone(); + move || { + view_model.revert(); + s.finish(); + } }) .build(), ); - /* - let weight_record = records.iter().find_map(|record| match record { - Record { - id, - data: ft_core::TraxRecord::Weight(record), - } => Some((id.clone(), record.clone())), - _ => None, - }); - - match weight_record { - Some((_id, data)) => s.imp().weight.set_value(Some(data.weight)), - None => s.imp().weight.set_value(None), - }; - s.append(&s.imp().weight.widget()); - */ - s.append(&WeightEdit::new(view_model.weight()).widget()); + s.append( + &WeightEdit::new(view_model.weight(), { + let view_model = view_model.clone(); + move |w| { + view_model.set_weight(w); + } + }) + .widget(), + ); s } diff --git a/fitnesstrax/app/src/components/text_entry.rs b/fitnesstrax/app/src/components/text_entry.rs index 996f1ed..20a8cb2 100644 --- a/fitnesstrax/app/src/components/text_entry.rs +++ b/fitnesstrax/app/src/components/text_entry.rs @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit use gtk::prelude::*; use std::{cell::RefCell, rc::Rc}; +#[derive(Clone, Debug)] pub struct ParseError; #[derive(Clone)] diff --git a/fitnesstrax/app/src/components/weight.rs b/fitnesstrax/app/src/components/weight.rs index ddba3b5..48bbf7f 100644 --- a/fitnesstrax/app/src/components/weight.rs +++ b/fitnesstrax/app/src/components/weight.rs @@ -54,13 +54,25 @@ pub struct WeightEdit { } impl WeightEdit { - pub fn new(weight: Option>) -> Self { + pub fn new(weight: Option>, on_update: OnUpdate) -> Self + where + OnUpdate: Fn(si::Kilogram) + 'static, + { Self { entry: TextEntry::new( "0 kg", weight, |val: &si::Kilogram| val.to_string(), - |v: &str| v.parse::().map(|w| w * si::KG).map_err(|_| ParseError), + move |v: &str| { + let new_weight = v.parse::().map(|w| w * si::KG).map_err(|_| ParseError); + match new_weight { + Ok(w) => { + on_update(w); + Ok(w) + } + Err(err) => Err(err), + } + }, ), } } diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index 0811941..1e86d60 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -18,12 +18,12 @@ mod app; mod app_window; mod components; mod types; +mod view_models; mod views; use adw::prelude::*; use app_window::AppWindow; use std::{env, path::PathBuf}; -use types::DayInterval; const APP_ID_DEV: &str = "com.luminescent-dreams.fitnesstrax.dev"; const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax"; diff --git a/fitnesstrax/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs new file mode 100644 index 0000000..5e0ecbd --- /dev/null +++ b/fitnesstrax/app/src/view_models/day_detail.rs @@ -0,0 +1,220 @@ +/* +Copyright 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 crate::app::App; +use dimensioned::si; +use emseries::{Record, RecordId, Recordable}; +use ft_core::TraxRecord; +use std::{ + collections::HashMap, + ops::Deref, + sync::{Arc, RwLock}, +}; + +#[derive(Clone, Debug)] +enum RecordState { + Original(Record), + New(T), + Updated(Record), + Deleted(Record), +} + +impl RecordState { + fn id(&self) -> Option<&RecordId> { + match self { + RecordState::Original(ref r) => Some(&r.id), + RecordState::New(ref r) => None, + RecordState::Updated(ref r) => Some(&r.id), + RecordState::Deleted(ref r) => Some(&r.id), + } + } + + fn with_value(self, value: T) -> RecordState { + match self { + RecordState::Original(r) => RecordState::Updated(Record { data: value, ..r }), + RecordState::New(_) => RecordState::New(value), + RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..r }), + RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..r }), + } + } + + fn with_delete(self) -> Option> { + match self { + RecordState::Original(r) => Some(RecordState::Deleted(r)), + RecordState::New(r) => None, + RecordState::Updated(r) => Some(RecordState::Deleted(r)), + RecordState::Deleted(r) => Some(RecordState::Deleted(r)), + } + } +} + +impl Deref for RecordState { + type Target = T; + fn deref(&self) -> &Self::Target { + match self { + RecordState::Original(ref r) => &r.data, + RecordState::New(ref r) => &r, + RecordState::Updated(ref r) => &r.data, + RecordState::Deleted(ref r) => &r.data, + } + } +} + +#[derive(Default)] +struct DayDetailViewModelInner {} + +#[derive(Clone, Default)] +pub struct DayDetailViewModel { + app: Option, + pub date: chrono::NaiveDate, + weight: Arc>>>, + steps: Arc>>>, + records: Arc>>>, +} + +impl DayDetailViewModel { + pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { + let (weight_records, records): (Vec>, Vec>) = + records.into_iter().partition(|r| r.data.is_weight()); + let (step_records, records): (Vec>, Vec>) = + records.into_iter().partition(|r| r.data.is_steps()); + Self { + app: Some(app), + date, + weight: Arc::new(RwLock::new( + weight_records + .first() + .and_then(|r| match r.data { + TraxRecord::Weight(ref w) => Some((r.id.clone(), w.clone())), + _ => None, + }) + .map(|(id, w)| RecordState::Original(Record { id, data: w })), + )), + steps: Arc::new(RwLock::new( + step_records + .first() + .and_then(|r| match r.data { + TraxRecord::Steps(ref w) => Some((r.id.clone(), w.clone())), + _ => None, + }) + .map(|(id, w)| RecordState::Original(Record { id, data: w })), + )), + + records: Arc::new(RwLock::new( + records + .into_iter() + .map(|r| (r.id.clone(), RecordState::Original(r))) + .collect::>>(), + )), + } + } + + pub fn weight(&self) -> Option> { + match *self.weight.read().unwrap() { + Some(ref w) => Some((*w).weight), + None => None, + } + } + + pub fn set_weight(&self, new_weight: si::Kilogram) { + let mut record = self.weight.write().unwrap(); + let new_record = match *record { + Some(ref rstate) => rstate.clone().with_value(ft_core::Weight { + date: self.date.clone(), + weight: new_weight, + }), + None => RecordState::New(ft_core::Weight { + date: self.date.clone(), + weight: new_weight, + }), + }; + *record = Some(new_record); + } + + pub fn steps(&self) -> Option { + match *self.steps.read().unwrap() { + Some(ref w) => Some((*w).count), + None => None, + } + } + + pub fn set_steps(&self, new_count: u32) { + let mut record = self.steps.write().unwrap(); + let new_record = match *record { + Some(ref rstate) => rstate.clone().with_value(ft_core::Steps { + date: self.date.clone(), + count: new_count, + }), + None => RecordState::New(ft_core::Steps { + date: self.date.clone(), + count: new_count, + }), + }; + *record = Some(new_record); + } + + pub fn save(&self) { + glib::spawn_future({ + let s = self.clone(); + async move { + if let Some(app) = s.app { + let weight_record = s.weight.read().unwrap().clone(); + match weight_record { + Some(RecordState::New(weight)) => { + let _ = app.put_record(TraxRecord::Weight(weight)).await; + } + Some(RecordState::Original(_)) => {} + Some(RecordState::Updated(weight)) => { + let _ = app + .update_record(Record { + id: weight.id, + data: TraxRecord::Weight(weight.data), + }) + .await; + } + Some(RecordState::Deleted(_)) => {} + None => {} + } + + let records = s + .records + .write() + .unwrap() + .drain() + .map(|(_, record)| record) + .collect::>>(); + + for record in records { + match record { + RecordState::New(data) => { + let _ = app.put_record(data).await; + } + RecordState::Original(_) => {} + RecordState::Updated(r) => { + let _ = app.update_record(r.clone()).await; + } + RecordState::Deleted(_) => unimplemented!(), + } + } + } + } + }); + } + + pub fn revert(&self) { + unimplemented!(); + } +} diff --git a/fitnesstrax/app/src/view_models/mod.rs b/fitnesstrax/app/src/view_models/mod.rs new file mode 100644 index 0000000..4e41990 --- /dev/null +++ b/fitnesstrax/app/src/view_models/mod.rs @@ -0,0 +1,18 @@ +/* +Copyright 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 . +*/ + +mod day_detail; +pub use day_detail::DayDetailViewModel; diff --git a/fitnesstrax/app/src/views/day_detail_view.rs b/fitnesstrax/app/src/views/day_detail_view.rs index eab3611..0b992bb 100644 --- a/fitnesstrax/app/src/views/day_detail_view.rs +++ b/fitnesstrax/app/src/views/day_detail_view.rs @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit use crate::{ app::App, components::{DayDetail, DayEdit, Singleton, SingletonImpl}, + view_models::DayDetailViewModel, }; use dimensioned::si; use emseries::{Record, RecordId}; @@ -25,74 +26,12 @@ use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{ cell::RefCell, + collections::HashMap, + ops::Deref, rc::Rc, sync::{Arc, RwLock}, }; -#[derive(Default)] -struct DayDetailViewModelInner { - records: Vec>, - updated_records: Vec>, - new_records: Vec, - deleted_records: Vec, -} - -#[derive(Clone, Default)] -pub struct DayDetailViewModel { - app: Option, - pub date: chrono::NaiveDate, - inner: Arc>, -} - -impl DayDetailViewModel { - pub fn new(date: chrono::NaiveDate, records: Vec>, app: App) -> Self { - Self { - app: Some(app), - date, - inner: Arc::new(RwLock::new(DayDetailViewModelInner { - records, - updated_records: vec![], - new_records: vec![], - deleted_records: vec![], - })), - } - } - - pub fn weight(&self) -> Option> { - self.inner - .read() - .unwrap() - .records - .iter() - .find_map(|record| match record { - Record { - data: ft_core::TraxRecord::Weight(record), - .. - } => Some(record.weight.clone()), - _ => None, - }) - } - - pub fn save(&self) { - glib::spawn_future({ - let s = self.clone(); - async move { - if let Some(app) = s.app { - let updated_records = { - let mut data = s.inner.write().unwrap(); - data.updated_records - .drain(..) - .collect::>>() - }; - for record in updated_records.into_iter() { - let _ = app.update_record(record.clone()).await; - } - } - } - }); - } -} - #[derive(Default)] pub struct DayDetailViewPrivate { container: Singleton, @@ -144,63 +83,4 @@ impl DayDetailView { move || s.view() })); } - - /* - fn on_put_record(&self) -> Box { - let s = self.clone(); - let app = self.imp().app.clone(); - Box::new(move |record| { - let s = s.clone(); - let app = app.clone(); - glib::spawn_future_local({ - async move { - match &*app.borrow() { - Some(app) => { - let id = app - .put_record(record.clone()) - .await - .expect("successful write"); - s.imp() - .records - .borrow_mut() - .push(Record { id, data: record }); - } - None => {} - } - s.view(); - } - }); - }) - } - - fn on_update_record(&self) -> Box)> { - let s = self.clone(); - let app = self.imp().app.clone(); - Box::new(move |updated_record| { - let app = app.clone(); - - let mut records = s.imp().records.borrow_mut(); - let idx = records.iter().position(|r| r.id == updated_record.id); - match idx { - Some(i) => records[i] = updated_record.clone(), - None => records.push(updated_record.clone()), - } - - glib::spawn_future_local({ - let s = s.clone(); - async move { - match &*app.borrow() { - Some(app) => { - let _ = app.update_record(updated_record).await; - } - None => { - println!("no app!"); - } - } - s.view(); - } - }); - }) - } - */ } diff --git a/fitnesstrax/app/src/views/mod.rs b/fitnesstrax/app/src/views/mod.rs index 4993459..9957823 100644 --- a/fitnesstrax/app/src/views/mod.rs +++ b/fitnesstrax/app/src/views/mod.rs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit use gtk::prelude::*; mod day_detail_view; -pub use day_detail_view::{DayDetailView, DayDetailViewModel}; +pub use day_detail_view::DayDetailView; mod historical_view; pub use historical_view::HistoricalView; diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index 401f4a8..f27fe18 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -22,6 +22,16 @@ pub struct Steps { pub count: u32, } +impl Recordable for Steps { + fn timestamp(&self) -> Timestamp { + Timestamp::Date(self.date.clone()) + } + + fn tags(&self) -> Vec { + vec![] + } +} + /// 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 @@ -54,6 +64,16 @@ pub struct Weight { pub weight: si::Kilogram, } +impl Recordable for Weight { + fn timestamp(&self) -> Timestamp { + Timestamp::Date(self.date.clone()) + } + + fn tags(&self) -> Vec { + vec![] + } +} + #[derive(Clone, Debug, PartialEq)] pub enum RecordType { BikeRide, @@ -96,6 +116,13 @@ impl TraxRecord { _ => false, } } + + pub fn is_steps(&self) -> bool { + match self { + TraxRecord::Steps(_) => true, + _ => false, + } + } } impl Recordable for TraxRecord { @@ -104,10 +131,10 @@ impl Recordable for TraxRecord { TraxRecord::BikeRide(rec) => Timestamp::DateTime(rec.datetime.clone()), TraxRecord::Row(rec) => Timestamp::DateTime(rec.datetime.clone()), TraxRecord::Run(rec) => Timestamp::DateTime(rec.datetime.clone()), - TraxRecord::Steps(rec) => Timestamp::Date(rec.date), + TraxRecord::Steps(rec) => rec.timestamp(), TraxRecord::Swim(rec) => Timestamp::DateTime(rec.datetime.clone()), TraxRecord::Walk(rec) => Timestamp::DateTime(rec.datetime.clone()), - TraxRecord::Weight(rec) => Timestamp::Date(rec.date), + TraxRecord::Weight(rec) => rec.timestamp(), } } -- 2.44.1 From 9bedb7a76c6d9c2029cae236162ab676644cd9f2 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 20 Jan 2024 14:35:10 -0500 Subject: [PATCH 3/3] Tons of linting and get tests running again --- emseries/src/series.rs | 2 +- emseries/src/types.rs | 29 +------------------ fitnesstrax/app/src/app.rs | 9 ++---- fitnesstrax/app/src/app_window.rs | 27 ++++------------- .../app/src/components/action_group.rs | 2 ++ fitnesstrax/app/src/components/day.rs | 18 ++++++++---- fitnesstrax/app/src/components/edit_view.rs | 22 -------------- fitnesstrax/app/src/components/mod.rs | 3 -- fitnesstrax/app/src/components/text_entry.rs | 24 ++++++++------- .../app/src/components/time_distance.rs | 5 ++-- fitnesstrax/app/src/components/weight.rs | 8 ++--- fitnesstrax/app/src/types.rs | 6 ++-- fitnesstrax/app/src/view_models/day_detail.rs | 23 +++++++-------- fitnesstrax/app/src/views/day_detail_view.rs | 12 +------- fitnesstrax/app/src/views/historical_view.rs | 16 ++++++---- fitnesstrax/app/src/views/placeholder_view.rs | 4 +-- fitnesstrax/app/src/views/welcome_view.rs | 2 +- fitnesstrax/core/src/legacy.rs | 6 ++-- fitnesstrax/core/src/types.rs | 27 +++++++---------- 19 files changed, 87 insertions(+), 158 deletions(-) delete mode 100644 fitnesstrax/app/src/components/edit_view.rs diff --git a/emseries/src/series.rs b/emseries/src/series.rs index ee964e7..310b3f7 100644 --- a/emseries/src/series.rs +++ b/emseries/src/series.rs @@ -108,7 +108,7 @@ where Ok(line_) => { match serde_json::from_str::>(line_.as_ref()) .map_err(EmseriesReadError::JSONParseError) - .and_then(|record| Record::try_from(record)) + .and_then(Record::try_from) { Ok(record) => records.insert(record.id.clone(), record.clone()), Err(EmseriesReadError::RecordDeleted(id)) => records.remove(&id), diff --git a/emseries/src/types.rs b/emseries/src/types.rs index 018a99b..3d4eb2e 100644 --- a/emseries/src/types.rs +++ b/emseries/src/types.rs @@ -11,9 +11,6 @@ You should have received a copy of the GNU General Public License along with Lum */ use chrono::{DateTime, FixedOffset, NaiveDate}; -use chrono_tz::UTC; -use serde::de::DeserializeOwned; -use serde::ser::Serialize; use std::{cmp::Ordering, fmt, io, str}; use thiserror::Error; use uuid::Uuid; @@ -93,33 +90,9 @@ impl str::FromStr for Timestamp { } } -/* -impl PartialEq for Timestamp { - fn eq(&self, other: &Timestamp) -> bool { - match (self, other) { - (Timestamp::DateTime(dt1), Timestamp::DateTime(dt2)) => { - dt1.with_timezone(&UTC) == dt2.with_timezone(&UTC) - } - // It's not clear to me what would make sense when I'm comparing a date and a - // timestamp. I'm going with a naive date comparison on the idea that what I'm wanting - // here is human scale, again. - (Timestamp::DateTime(dt1), Timestamp::Date(dt2)) => dt1.date_naive() == *dt2, - (Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => *dt1 == dt2.date_naive(), - (Timestamp::Date(dt1), Timestamp::Date(dt2)) => *dt1 == *dt2, - } - } -} -*/ - impl PartialOrd for Timestamp { fn partial_cmp(&self, other: &Timestamp) -> Option { - // Some(self.cmp(other)) - match (self, other) { - (Timestamp::DateTime(dt1), Timestamp::DateTime(dt2)) => dt1.partial_cmp(dt2), - (Timestamp::DateTime(dt1), Timestamp::Date(dt2)) => dt1.date_naive().partial_cmp(dt2), - (Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => dt1.partial_cmp(&dt2.date_naive()), - (Timestamp::Date(dt1), Timestamp::Date(dt2)) => dt1.partial_cmp(dt2), - } + Some(self.cmp(other)) } } diff --git a/fitnesstrax/app/src/app.rs b/fitnesstrax/app/src/app.rs index 94a6e00..8dfb882 100644 --- a/fitnesstrax/app/src/app.rs +++ b/fitnesstrax/app/src/app.rs @@ -14,7 +14,6 @@ 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::types::DayInterval; use chrono::NaiveDate; use emseries::{time_range, Record, RecordId, Series, Timestamp}; use ft_core::TraxRecord; @@ -52,12 +51,10 @@ impl App { .unwrap(), ); - let s = Self { + Self { runtime, database: Arc::new(RwLock::new(database)), - }; - - s + } } pub async fn records( @@ -76,7 +73,7 @@ impl App { Timestamp::Date(end), true, )) - .map(|record| record.clone()) + .cloned() .collect::>>(); Ok(records) } else { diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 9b230ea..f5f2d46 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -81,7 +81,7 @@ impl AppWindow { .orientation(gtk::Orientation::Vertical) .build(); - let initial_view = View::Placeholder(PlaceholderView::new().upcast()); + let initial_view = View::Placeholder(PlaceholderView::default().upcast()); layout.append(&initial_view.widget()); @@ -115,9 +115,10 @@ impl AppWindow { s.navigation.connect_popped({ let s = s.clone(); - move |_, _| match *s.current_view.borrow() { - View::Historical(_) => s.load_records(), - _ => {} + move |_, _| { + if let View::Historical(_) = *s.current_view.borrow() { + s.load_records(); + } } }); @@ -191,22 +192,4 @@ impl AppWindow { } }); } - - fn on_put_record(&self, record: TraxRecord) { - glib::spawn_future_local({ - let s = self.clone(); - async move { - s.app.put_record(record).await; - } - }); - } - - fn on_update_record(&self, record: Record) { - glib::spawn_future_local({ - let s = self.clone(); - async move { - s.app.update_record(record).await; - } - }); - } } diff --git a/fitnesstrax/app/src/components/action_group.rs b/fitnesstrax/app/src/components/action_group.rs index 4197edd..914adb6 100644 --- a/fitnesstrax/app/src/components/action_group.rs +++ b/fitnesstrax/app/src/components/action_group.rs @@ -14,6 +14,8 @@ General Public License for more details. You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . */ +//! ActionGroup and related structures + use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; diff --git a/fitnesstrax/app/src/components/day.rs b/fitnesstrax/app/src/components/day.rs index f0fe13a..631600e 100644 --- a/fitnesstrax/app/src/components/day.rs +++ b/fitnesstrax/app/src/components/day.rs @@ -23,7 +23,7 @@ use crate::{ use emseries::Record; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::{cell::RefCell, rc::Rc}; +use std::cell::RefCell; use super::weight::WeightEdit; @@ -60,8 +60,8 @@ glib::wrapper! { pub struct DaySummary(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } -impl DaySummary { - pub fn new() -> Self { +impl Default for DaySummary { + fn default() -> Self { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_css_classes(&["day-summary"]); @@ -70,6 +70,12 @@ impl DaySummary { s } +} + +impl DaySummary { + pub fn new() -> Self { + Self::default() + } pub fn set_data(&self, date: chrono::NaiveDate, records: Vec>) { self.imp() @@ -83,11 +89,11 @@ impl DaySummary { if let Some(Record { data: ft_core::TraxRecord::Weight(weight_record), .. - }) = records.iter().filter(|f| f.data.is_weight()).next() + }) = records.iter().find(|f| f.data.is_weight()) { let label = gtk::Label::builder() .halign(gtk::Align::Start) - .label(&format!("{}", weight_record.weight)) + .label(weight_record.weight.to_string()) .css_classes(["day-summary__weight"]) .build(); self.append(&label); @@ -134,7 +140,7 @@ impl DayDetail { s.append( &ActionGroup::builder() - .primary_action("Edit", Box::new(move || on_edit())) + .primary_action("Edit", Box::new(on_edit)) .build(), ); diff --git a/fitnesstrax/app/src/components/edit_view.rs b/fitnesstrax/app/src/components/edit_view.rs deleted file mode 100644 index f6f96a9..0000000 --- a/fitnesstrax/app/src/components/edit_view.rs +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2023, 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 . -*/ - -#[derive(Clone)] -pub enum EditView { - Unconfigured, - View(View), - Edit(Edit), -} diff --git a/fitnesstrax/app/src/components/mod.rs b/fitnesstrax/app/src/components/mod.rs index 75ecdd2..78e9b9b 100644 --- a/fitnesstrax/app/src/components/mod.rs +++ b/fitnesstrax/app/src/components/mod.rs @@ -20,9 +20,6 @@ pub use action_group::ActionGroup; mod day; pub use day::{DayDetail, DayEdit, DaySummary}; -mod edit_view; -pub use edit_view::EditView; - mod singleton; pub use singleton::{Singleton, SingletonImpl}; diff --git a/fitnesstrax/app/src/components/text_entry.rs b/fitnesstrax/app/src/components/text_entry.rs index 20a8cb2..12c0a6c 100644 --- a/fitnesstrax/app/src/components/text_entry.rs +++ b/fitnesstrax/app/src/components/text_entry.rs @@ -20,12 +20,16 @@ use std::{cell::RefCell, rc::Rc}; #[derive(Clone, Debug)] pub struct ParseError; +type Renderer = dyn Fn(&T) -> String; +type Parser = dyn Fn(&str) -> Result; + #[derive(Clone)] pub struct TextEntry { value: Rc>>, widget: gtk::Entry, - renderer: Rc String>>, - parser: Rc Result>>, + #[allow(unused)] + renderer: Rc>, + parser: Rc>, } impl std::fmt::Debug for TextEntry { @@ -46,16 +50,15 @@ impl TextEntry { V: Fn(&str) -> Result + 'static, { let widget = gtk::Entry::builder().placeholder_text(placeholder).build(); - match value { - Some(ref v) => widget.set_text(&renderer(&v)), - None => {} + if let Some(ref v) = value { + widget.set_text(&renderer(v)) } let s = Self { value: Rc::new(RefCell::new(value)), widget, - renderer: Rc::new(Box::new(renderer)), - parser: Rc::new(Box::new(parser)), + renderer: Rc::new(renderer), + parser: Rc::new(parser), }; s.widget.buffer().connect_text_notify({ @@ -84,19 +87,20 @@ impl TextEntry { } } + #[allow(unused)] pub fn value(&self) -> Option { let v = self.value.borrow().clone(); self.value.borrow().clone() } pub fn set_value(&self, value: Option) { - match value { - Some(ref v) => self.widget.set_text(&(self.renderer)(&v)), - None => {} + if let Some(ref v) = value { + self.widget.set_text(&(self.renderer)(v)) } *self.value.borrow_mut() = value; } + #[allow(unused)] pub fn grab_focus(&self) { self.widget.grab_focus(); } diff --git a/fitnesstrax/app/src/components/time_distance.rs b/fitnesstrax/app/src/components/time_distance.rs index 8a20c42..ac1ad9c 100644 --- a/fitnesstrax/app/src/components/time_distance.rs +++ b/fitnesstrax/app/src/components/time_distance.rs @@ -24,6 +24,7 @@ use std::cell::RefCell; #[derive(Default)] pub struct TimeDistanceViewPrivate { + #[allow(unused)] record: RefCell>, } @@ -53,7 +54,7 @@ impl TimeDistanceView { first_row.append( >k::Label::builder() .halign(gtk::Align::Start) - .label(&record.datetime.format("%H:%M").to_string()) + .label(record.datetime.format("%H:%M").to_string()) .build(), ); @@ -96,7 +97,7 @@ impl TimeDistanceView { .label( record .comments - .map(|comments| format!("{}", comments)) + .map(|comments| comments.to_string()) .unwrap_or("".to_owned()), ) .build(), diff --git a/fitnesstrax/app/src/components/weight.rs b/fitnesstrax/app/src/components/weight.rs index 48bbf7f..220b51e 100644 --- a/fitnesstrax/app/src/components/weight.rs +++ b/fitnesstrax/app/src/components/weight.rs @@ -14,12 +14,9 @@ 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, Singleton, TextEntry}; -use chrono::{Local, NaiveDate}; +use crate::components::{ParseError, TextEntry}; use dimensioned::si; -use glib::{object::ObjectRef, Object}; -use gtk::{prelude::*, subclass::prelude::*}; -use std::{borrow::Borrow, cell::RefCell}; +use gtk::prelude::*; #[derive(Default)] pub struct WeightViewPrivate {} @@ -77,6 +74,7 @@ impl WeightEdit { } } + #[allow(unused)] pub fn set_value(&self, value: Option>) { self.entry.set_value(value); } diff --git a/fitnesstrax/app/src/types.rs b/fitnesstrax/app/src/types.rs index 319e845..799f2d8 100644 --- a/fitnesstrax/app/src/types.rs +++ b/fitnesstrax/app/src/types.rs @@ -21,8 +21,8 @@ impl Default for DayInterval { impl DayInterval { pub fn days(&self) -> impl Iterator { DayIterator { - current: self.start.clone(), - end: self.end.clone(), + current: self.start, + end: self.end, } } } @@ -37,7 +37,7 @@ impl Iterator for DayIterator { fn next(&mut self) -> Option { if self.current <= self.end { - let val = self.current.clone(); + let val = self.current; self.current += Duration::days(1); Some(val) } else { diff --git a/fitnesstrax/app/src/view_models/day_detail.rs b/fitnesstrax/app/src/view_models/day_detail.rs index 5e0ecbd..f823d6f 100644 --- a/fitnesstrax/app/src/view_models/day_detail.rs +++ b/fitnesstrax/app/src/view_models/day_detail.rs @@ -29,10 +29,12 @@ enum RecordState { Original(Record), New(T), Updated(Record), + #[allow(unused)] Deleted(Record), } impl RecordState { + #[allow(unused)] fn id(&self) -> Option<&RecordId> { match self { RecordState::Original(ref r) => Some(&r.id), @@ -51,6 +53,7 @@ impl RecordState { } } + #[allow(unused)] fn with_delete(self) -> Option> { match self { RecordState::Original(r) => Some(RecordState::Deleted(r)), @@ -66,7 +69,7 @@ impl Deref for RecordState { fn deref(&self) -> &Self::Target { match self { RecordState::Original(ref r) => &r.data, - RecordState::New(ref r) => &r, + RecordState::New(ref r) => r, RecordState::Updated(ref r) => &r.data, RecordState::Deleted(ref r) => &r.data, } @@ -123,21 +126,18 @@ impl DayDetailViewModel { } pub fn weight(&self) -> Option> { - match *self.weight.read().unwrap() { - Some(ref w) => Some((*w).weight), - None => None, - } + (*self.weight.read().unwrap()).as_ref().map(|w| w.weight) } pub fn set_weight(&self, new_weight: si::Kilogram) { let mut record = self.weight.write().unwrap(); let new_record = match *record { Some(ref rstate) => rstate.clone().with_value(ft_core::Weight { - date: self.date.clone(), + date: self.date, weight: new_weight, }), None => RecordState::New(ft_core::Weight { - date: self.date.clone(), + date: self.date, weight: new_weight, }), }; @@ -145,21 +145,18 @@ impl DayDetailViewModel { } pub fn steps(&self) -> Option { - match *self.steps.read().unwrap() { - Some(ref w) => Some((*w).count), - None => None, - } + (*self.steps.read().unwrap()).as_ref().map(|w| w.count) } pub fn set_steps(&self, new_count: u32) { let mut record = self.steps.write().unwrap(); let new_record = match *record { Some(ref rstate) => rstate.clone().with_value(ft_core::Steps { - date: self.date.clone(), + date: self.date, count: new_count, }), None => RecordState::New(ft_core::Steps { - date: self.date.clone(), + date: self.date, count: new_count, }), }; diff --git a/fitnesstrax/app/src/views/day_detail_view.rs b/fitnesstrax/app/src/views/day_detail_view.rs index 0b992bb..ae213fe 100644 --- a/fitnesstrax/app/src/views/day_detail_view.rs +++ b/fitnesstrax/app/src/views/day_detail_view.rs @@ -15,22 +15,12 @@ You should have received a copy of the GNU General Public License along with Fit */ use crate::{ - app::App, components::{DayDetail, DayEdit, Singleton, SingletonImpl}, view_models::DayDetailViewModel, }; -use dimensioned::si; -use emseries::{Record, RecordId}; -use ft_core::TraxRecord; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::{ - cell::RefCell, - collections::HashMap, - ops::Deref, - rc::Rc, - sync::{Arc, RwLock}, -}; +use std::cell::RefCell; #[derive(Default)] pub struct DayDetailViewPrivate { diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index 20e074d..23bccad 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with Fit */ use crate::{components::DaySummary, types::DayInterval}; -use chrono::{Duration, Local, NaiveDate}; +use chrono::NaiveDate; use emseries::Record; use ft_core::TraxRecord; use glib::Object; @@ -161,7 +161,7 @@ impl DayRecords { } pub fn date(&self) -> chrono::NaiveDate { - self.imp().date.borrow().clone() + *self.imp().date.borrow() } pub fn records(&self) -> Vec> { @@ -204,11 +204,11 @@ impl GroupedRecords { self } - fn items<'a>(&'a self) -> impl Iterator + 'a { + fn items(&self) -> impl Iterator + '_ { self.interval.days().map(|date| { self.data .get(&date) - .map(|rec| rec.clone()) + .cloned() .unwrap_or(DayRecords::new(date, vec![])) }) } @@ -217,6 +217,7 @@ impl GroupedRecords { #[cfg(test)] mod test { use super::GroupedRecords; + use crate::types::DayInterval; use chrono::{FixedOffset, NaiveDate, TimeZone}; use dimensioned::si::{KG, M, S}; use emseries::{Record, RecordId}; @@ -272,7 +273,12 @@ mod test { }, ]; - let groups = GroupedRecords::from(records).0; + let groups = GroupedRecords::new(DayInterval { + start: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(), + end: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(), + }) + .with_data(records) + .data; assert_eq!(groups.len(), 3); } } diff --git a/fitnesstrax/app/src/views/placeholder_view.rs b/fitnesstrax/app/src/views/placeholder_view.rs index c3f68eb..94e8e50 100644 --- a/fitnesstrax/app/src/views/placeholder_view.rs +++ b/fitnesstrax/app/src/views/placeholder_view.rs @@ -38,8 +38,8 @@ glib::wrapper! { pub struct PlaceholderView(ObjectSubclass) @extends gtk::Box, gtk::Widget; } -impl PlaceholderView { - pub fn new() -> Self { +impl Default for PlaceholderView { + fn default() -> Self { let s: Self = Object::builder().build(); s } diff --git a/fitnesstrax/app/src/views/welcome_view.rs b/fitnesstrax/app/src/views/welcome_view.rs index 1f391a3..78a21a0 100644 --- a/fitnesstrax/app/src/views/welcome_view.rs +++ b/fitnesstrax/app/src/views/welcome_view.rs @@ -14,7 +14,7 @@ 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::{app::App, components::FileChooserRow}; +use crate::components::FileChooserRow; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::path::PathBuf; diff --git a/fitnesstrax/core/src/legacy.rs b/fitnesstrax/core/src/legacy.rs index 0a79713..9ab1ba3 100644 --- a/fitnesstrax/core/src/legacy.rs +++ b/fitnesstrax/core/src/legacy.rs @@ -1,23 +1,25 @@ -use crate::types; - #[cfg(test)] mod test { #[test] + #[ignore] fn read_a_legacy_set_rep_record() { unimplemented!() } #[test] + #[ignore] fn read_a_legacy_steps_record() { unimplemented!() } #[test] + #[ignore] fn read_a_legacy_time_distance_record() { unimplemented!() } #[test] + #[ignore] fn read_a_legacy_weight_record() { unimplemented!() } diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index f27fe18..b0959f7 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; /// SetRep represents workouts like pushups or situps, which involve doing a "set" of a number of /// actions, resting, and then doing another set. +#[allow(dead_code)] pub struct SetRep { /// I assume that a set/rep workout is only done once in a day. date: NaiveDate, @@ -24,7 +25,7 @@ pub struct Steps { impl Recordable for Steps { fn timestamp(&self) -> Timestamp { - Timestamp::Date(self.date.clone()) + Timestamp::Date(self.date) } fn tags(&self) -> Vec { @@ -66,7 +67,7 @@ pub struct Weight { impl Recordable for Weight { fn timestamp(&self) -> Timestamp { - Timestamp::Date(self.date.clone()) + Timestamp::Date(self.date) } fn tags(&self) -> Vec { @@ -111,29 +112,23 @@ impl TraxRecord { } pub fn is_weight(&self) -> bool { - match self { - TraxRecord::Weight(_) => true, - _ => false, - } + matches!(self, TraxRecord::Weight(_)) } pub fn is_steps(&self) -> bool { - match self { - TraxRecord::Steps(_) => true, - _ => false, - } + matches!(self, TraxRecord::Steps(_)) } } impl Recordable for TraxRecord { fn timestamp(&self) -> Timestamp { match self { - TraxRecord::BikeRide(rec) => Timestamp::DateTime(rec.datetime.clone()), - TraxRecord::Row(rec) => Timestamp::DateTime(rec.datetime.clone()), - TraxRecord::Run(rec) => Timestamp::DateTime(rec.datetime.clone()), + TraxRecord::BikeRide(rec) => Timestamp::DateTime(rec.datetime), + TraxRecord::Row(rec) => Timestamp::DateTime(rec.datetime), + TraxRecord::Run(rec) => Timestamp::DateTime(rec.datetime), TraxRecord::Steps(rec) => rec.timestamp(), - TraxRecord::Swim(rec) => Timestamp::DateTime(rec.datetime.clone()), - TraxRecord::Walk(rec) => Timestamp::DateTime(rec.datetime.clone()), + TraxRecord::Swim(rec) => Timestamp::DateTime(rec.datetime), + TraxRecord::Walk(rec) => Timestamp::DateTime(rec.datetime), TraxRecord::Weight(rec) => rec.timestamp(), } } @@ -162,6 +157,6 @@ mod test { let id = series.put(record.clone()).unwrap(); let record_ = series.get(&id).unwrap(); - assert_eq!(record_, record); + assert_eq!(record_.data, record); } } -- 2.44.1