Compare commits
No commits in common. "7ec48ded5d044b05a4db2d6ddc8a46a976dbcc16" and "9bedb7a76c6d9c2029cae236162ab676644cd9f2" have entirely different histories.
7ec48ded5d
...
9bedb7a76c
|
@ -11,7 +11,8 @@
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome__footer {}
|
.welcome__footer {
|
||||||
|
}
|
||||||
|
|
||||||
.historical {
|
.historical {
|
||||||
margin: 32px;
|
margin: 32px;
|
||||||
|
@ -36,7 +37,3 @@
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-view {
|
|
||||||
padding: 8px;
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
|
@ -134,7 +134,7 @@ impl AppWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_historical_view(&self, records: Vec<Record<TraxRecord>>) {
|
fn show_historical_view(&self, records: Vec<Record<TraxRecord>>) {
|
||||||
let view = View::Historical(HistoricalView::new(self.app.clone(), records, {
|
let view = View::Historical(HistoricalView::new(records, {
|
||||||
let s = self.clone();
|
let s = self.clone();
|
||||||
Rc::new(move |date, records| {
|
Rc::new(move |date, records| {
|
||||||
let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
|
|
|
@ -17,15 +17,19 @@ 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::{ActionGroup, Weight},
|
||||||
view_models::DayDetailViewModel,
|
view_models::DayDetailViewModel,
|
||||||
};
|
};
|
||||||
|
use emseries::Record;
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use super::weight::WeightEdit;
|
||||||
|
|
||||||
pub struct DaySummaryPrivate {
|
pub struct DaySummaryPrivate {
|
||||||
date: gtk::Label,
|
date: gtk::Label,
|
||||||
|
weight: RefCell<Option<gtk::Label>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -39,7 +43,10 @@ impl ObjectSubclass for DaySummaryPrivate {
|
||||||
.css_classes(["day-summary__date"])
|
.css_classes(["day-summary__date"])
|
||||||
.halign(gtk::Align::Start)
|
.halign(gtk::Align::Start)
|
||||||
.build();
|
.build();
|
||||||
Self { date }
|
Self {
|
||||||
|
date,
|
||||||
|
weight: RefCell::new(None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,34 +77,37 @@ impl DaySummary {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_data(&self, view_model: DayDetailViewModel) {
|
pub fn set_data(&self, date: chrono::NaiveDate, records: Vec<Record<ft_core::TraxRecord>>) {
|
||||||
self.imp()
|
self.imp()
|
||||||
.date
|
.date
|
||||||
.set_text(&view_model.date.format("%Y-%m-%d").to_string());
|
.set_text(&date.format("%Y-%m-%d").to_string());
|
||||||
|
|
||||||
let row = gtk::Box::builder().build();
|
if let Some(ref weight_label) = *self.imp().weight.borrow() {
|
||||||
|
self.remove(weight_label);
|
||||||
let label = gtk::Label::builder()
|
|
||||||
.halign(gtk::Align::Start)
|
|
||||||
.css_classes(["day-summary__weight"])
|
|
||||||
.build();
|
|
||||||
if let Some(w) = view_model.weight() {
|
|
||||||
label.set_label(&w.to_string())
|
|
||||||
}
|
}
|
||||||
row.append(&label);
|
|
||||||
|
|
||||||
self.append(&label);
|
if let Some(Record {
|
||||||
|
data: ft_core::TraxRecord::Weight(weight_record),
|
||||||
let label = gtk::Label::builder()
|
..
|
||||||
.halign(gtk::Align::Start)
|
}) = records.iter().find(|f| f.data.is_weight())
|
||||||
.css_classes(["day-summary__weight"])
|
{
|
||||||
.build();
|
let label = gtk::Label::builder()
|
||||||
if let Some(s) = view_model.steps() {
|
.halign(gtk::Align::Start)
|
||||||
label.set_label(&format!("{} steps", s.to_string()));
|
.label(weight_record.weight.to_string())
|
||||||
|
.css_classes(["day-summary__weight"])
|
||||||
|
.build();
|
||||||
|
self.append(&label);
|
||||||
|
*self.imp().weight.borrow_mut() = Some(label);
|
||||||
}
|
}
|
||||||
row.append(&label);
|
|
||||||
|
|
||||||
self.append(&row);
|
/*
|
||||||
|
self.append(
|
||||||
|
>k::Label::builder()
|
||||||
|
.halign(gtk::Align::Start)
|
||||||
|
.label("15km of biking in 60 minutes")
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,16 +169,8 @@ impl DayDetail {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let top_row = gtk::Box::builder()
|
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
|
||||||
.build();
|
|
||||||
let weight_view = Weight::new(view_model.weight());
|
let weight_view = Weight::new(view_model.weight());
|
||||||
top_row.append(&weight_view.widget());
|
s.append(&weight_view.widget());
|
||||||
|
|
||||||
let steps_view = Steps::new(view_model.steps());
|
|
||||||
top_row.append(&steps_view.widget());
|
|
||||||
|
|
||||||
s.append(&top_row);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
records.into_iter().for_each(|record| {
|
records.into_iter().for_each(|record| {
|
||||||
|
@ -279,11 +281,8 @@ impl DayEdit {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let top_row = gtk::Box::builder()
|
s.append(
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
&WeightEdit::new(view_model.weight(), {
|
||||||
.build();
|
|
||||||
top_row.append(
|
|
||||||
&weight_editor(view_model.weight(), {
|
|
||||||
let view_model = view_model.clone();
|
let view_model = view_model.clone();
|
||||||
move |w| {
|
move |w| {
|
||||||
view_model.set_weight(w);
|
view_model.set_weight(w);
|
||||||
|
@ -292,15 +291,6 @@ impl DayEdit {
|
||||||
.widget(),
|
.widget(),
|
||||||
);
|
);
|
||||||
|
|
||||||
top_row.append(
|
|
||||||
&steps_editor(view_model.steps(), {
|
|
||||||
let view_model = view_model.clone();
|
|
||||||
move |s| view_model.set_steps(s)
|
|
||||||
})
|
|
||||||
.widget(),
|
|
||||||
);
|
|
||||||
s.append(&top_row);
|
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,6 @@ pub use day::{DayDetail, DayEdit, DaySummary};
|
||||||
mod singleton;
|
mod singleton;
|
||||||
pub use singleton::{Singleton, SingletonImpl};
|
pub use singleton::{Singleton, SingletonImpl};
|
||||||
|
|
||||||
mod steps;
|
|
||||||
pub use steps::{steps_editor, Steps};
|
|
||||||
|
|
||||||
mod text_entry;
|
mod text_entry;
|
||||||
pub use text_entry::{ParseError, TextEntry};
|
pub use text_entry::{ParseError, TextEntry};
|
||||||
|
|
||||||
|
@ -33,7 +30,7 @@ mod time_distance;
|
||||||
pub use time_distance::TimeDistanceView;
|
pub use time_distance::TimeDistanceView;
|
||||||
|
|
||||||
mod weight;
|
mod weight;
|
||||||
pub use weight::{weight_editor, Weight};
|
pub use weight::Weight;
|
||||||
|
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
|
||||||
|
|
||||||
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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use crate::components::{ParseError, TextEntry};
|
|
||||||
use gtk::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Steps {
|
|
||||||
label: gtk::Label,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Steps {
|
|
||||||
pub fn new(steps: Option<u32>) -> Self {
|
|
||||||
let label = gtk::Label::builder()
|
|
||||||
.css_classes(["card", "step-view"])
|
|
||||||
.can_focus(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
match steps {
|
|
||||||
Some(s) => label.set_text(&format!("{}", s)),
|
|
||||||
None => label.set_text("No steps recorded"),
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { label }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn widget(&self) -> gtk::Widget {
|
|
||||||
self.label.clone().upcast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn steps_editor<OnUpdate>(value: Option<u32>, on_update: OnUpdate) -> TextEntry<u32>
|
|
||||||
where
|
|
||||||
OnUpdate: Fn(u32) + 'static,
|
|
||||||
{
|
|
||||||
TextEntry::new(
|
|
||||||
"0",
|
|
||||||
value,
|
|
||||||
|v| format!("{}", v),
|
|
||||||
move |v| match v.parse::<u32>() {
|
|
||||||
Ok(val) => {
|
|
||||||
on_update(val);
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
Err(_) => Err(ParseError),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -18,6 +18,9 @@ use crate::components::{ParseError, TextEntry};
|
||||||
use dimensioned::si;
|
use dimensioned::si;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct WeightViewPrivate {}
|
||||||
|
|
||||||
pub struct Weight {
|
pub struct Weight {
|
||||||
label: gtk::Label,
|
label: gtk::Label,
|
||||||
}
|
}
|
||||||
|
@ -42,26 +45,45 @@ impl Weight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weight_editor<OnUpdate>(
|
#[derive(Debug)]
|
||||||
weight: Option<si::Kilogram<f64>>,
|
pub struct WeightEdit {
|
||||||
on_update: OnUpdate,
|
entry: TextEntry<si::Kilogram<f64>>,
|
||||||
) -> TextEntry<si::Kilogram<f64>>
|
}
|
||||||
where
|
|
||||||
OnUpdate: Fn(si::Kilogram<f64>) + 'static,
|
impl WeightEdit {
|
||||||
{
|
pub fn new<OnUpdate>(weight: Option<si::Kilogram<f64>>, on_update: OnUpdate) -> Self
|
||||||
TextEntry::new(
|
where
|
||||||
"0 kg",
|
OnUpdate: Fn(si::Kilogram<f64>) + 'static,
|
||||||
weight,
|
{
|
||||||
|val: &si::Kilogram<f64>| val.to_string(),
|
Self {
|
||||||
move |v: &str| {
|
entry: TextEntry::new(
|
||||||
let new_weight = v.parse::<f64>().map(|w| w * si::KG).map_err(|_| ParseError);
|
"0 kg",
|
||||||
match new_weight {
|
weight,
|
||||||
Ok(w) => {
|
|val: &si::Kilogram<f64>| val.to_string(),
|
||||||
on_update(w);
|
move |v: &str| {
|
||||||
Ok(w)
|
let new_weight = v.parse::<f64>().map(|w| w * si::KG).map_err(|_| ParseError);
|
||||||
}
|
match new_weight {
|
||||||
Err(err) => Err(err),
|
Ok(w) => {
|
||||||
}
|
on_update(w);
|
||||||
},
|
Ok(w)
|
||||||
)
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn set_value(&self, value: Option<si::Kilogram<f64>>) {
|
||||||
|
self.entry.set_value(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> Option<si::Kilogram<f64>> {
|
||||||
|
self.entry.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(&self) -> gtk::Widget {
|
||||||
|
self.entry.widget()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,24 +186,6 @@ impl DayDetailViewModel {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let steps_record = s.steps.read().unwrap().clone();
|
|
||||||
match steps_record {
|
|
||||||
Some(RecordState::New(steps)) => {
|
|
||||||
let _ = app.put_record(TraxRecord::Steps(steps)).await;
|
|
||||||
}
|
|
||||||
Some(RecordState::Original(_)) => {}
|
|
||||||
Some(RecordState::Updated(steps)) => {
|
|
||||||
let _ = app
|
|
||||||
.update_record(Record {
|
|
||||||
id: steps.id,
|
|
||||||
data: TraxRecord::Steps(steps.data),
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(RecordState::Deleted(_)) => {}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let records = s
|
let records = s
|
||||||
.records
|
.records
|
||||||
.write()
|
.write()
|
||||||
|
|
|
@ -14,9 +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 <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{
|
use crate::{components::DaySummary, types::DayInterval};
|
||||||
app::App, components::DaySummary, types::DayInterval, view_models::DayDetailViewModel,
|
|
||||||
};
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use emseries::Record;
|
use emseries::Record;
|
||||||
use ft_core::TraxRecord;
|
use ft_core::TraxRecord;
|
||||||
|
@ -28,8 +26,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
/// daily summaries, daily details, and will provide all functions the user may need for editing
|
/// daily summaries, daily details, and will provide all functions the user may need for editing
|
||||||
/// records.
|
/// records.
|
||||||
pub struct HistoricalViewPrivate {
|
pub struct HistoricalViewPrivate {
|
||||||
app: Rc<RefCell<Option<App>>>,
|
time_window: RefCell<DayInterval>,
|
||||||
time_window: Rc<RefCell<DayInterval>>,
|
|
||||||
list_view: gtk::ListView,
|
list_view: gtk::ListView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,42 +45,31 @@ impl ObjectSubclass for HistoricalViewPrivate {
|
||||||
.set_child(Some(&DaySummary::new()));
|
.set_child(Some(&DaySummary::new()));
|
||||||
});
|
});
|
||||||
|
|
||||||
let s = Self {
|
factory.connect_bind(move |_, list_item| {
|
||||||
app: Rc::new(RefCell::new(None)),
|
let records = list_item
|
||||||
time_window: Rc::new(RefCell::new(DayInterval::default())),
|
.downcast_ref::<gtk::ListItem>()
|
||||||
|
.expect("should be a ListItem")
|
||||||
|
.item()
|
||||||
|
.and_downcast::<DayRecords>()
|
||||||
|
.expect("should be a DaySummary");
|
||||||
|
|
||||||
|
let summary = list_item
|
||||||
|
.downcast_ref::<gtk::ListItem>()
|
||||||
|
.expect("should be a ListItem")
|
||||||
|
.child()
|
||||||
|
.and_downcast::<DaySummary>()
|
||||||
|
.expect("should be a DaySummary");
|
||||||
|
|
||||||
|
summary.set_data(records.date(), records.records());
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
time_window: RefCell::new(DayInterval::default()),
|
||||||
list_view: gtk::ListView::builder()
|
list_view: gtk::ListView::builder()
|
||||||
.factory(&factory)
|
.factory(&factory)
|
||||||
.single_click_activate(true)
|
.single_click_activate(true)
|
||||||
.build(),
|
.build(),
|
||||||
};
|
}
|
||||||
factory.connect_bind({
|
|
||||||
let app = s.app.clone();
|
|
||||||
move |_, list_item| {
|
|
||||||
let records = list_item
|
|
||||||
.downcast_ref::<gtk::ListItem>()
|
|
||||||
.expect("should be a ListItem")
|
|
||||||
.item()
|
|
||||||
.and_downcast::<DayRecords>()
|
|
||||||
.expect("should be a DaySummary");
|
|
||||||
|
|
||||||
let summary = list_item
|
|
||||||
.downcast_ref::<gtk::ListItem>()
|
|
||||||
.expect("should be a ListItem")
|
|
||||||
.child()
|
|
||||||
.and_downcast::<DaySummary>()
|
|
||||||
.expect("should be a DaySummary");
|
|
||||||
|
|
||||||
if let Some(app) = app.borrow().clone() {
|
|
||||||
summary.set_data(DayDetailViewModel::new(
|
|
||||||
records.date(),
|
|
||||||
records.records(),
|
|
||||||
app.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,11 +82,7 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoricalView {
|
impl HistoricalView {
|
||||||
pub fn new<SelectFn>(
|
pub fn new<SelectFn>(records: Vec<Record<TraxRecord>>, on_select_day: Rc<SelectFn>) -> Self
|
||||||
app: App,
|
|
||||||
records: Vec<Record<TraxRecord>>,
|
|
||||||
on_select_day: Rc<SelectFn>,
|
|
||||||
) -> Self
|
|
||||||
where
|
where
|
||||||
SelectFn: Fn(chrono::NaiveDate, Vec<Record<TraxRecord>>) + 'static,
|
SelectFn: Fn(chrono::NaiveDate, Vec<Record<TraxRecord>>) + 'static,
|
||||||
{
|
{
|
||||||
|
@ -108,8 +90,6 @@ impl HistoricalView {
|
||||||
s.set_orientation(gtk::Orientation::Vertical);
|
s.set_orientation(gtk::Orientation::Vertical);
|
||||||
s.set_css_classes(&["historical"]);
|
s.set_css_classes(&["historical"]);
|
||||||
|
|
||||||
*s.imp().app.borrow_mut() = Some(app);
|
|
||||||
|
|
||||||
let grouped_records =
|
let grouped_records =
|
||||||
GroupedRecords::new((*s.imp().time_window.borrow()).clone()).with_data(records);
|
GroupedRecords::new((*s.imp().time_window.borrow()).clone()).with_data(records);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue