Compare commits
2 Commits
3a716ee546
...
39acfe7950
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 39acfe7950 | |
Savanni D'Gerinel | d0cce4ee58 |
|
@ -120,7 +120,7 @@ pub trait Recordable {
|
||||||
/// Uniquely identifies a record.
|
/// Uniquely identifies a record.
|
||||||
///
|
///
|
||||||
/// This is a wrapper around a basic uuid with some extra convenience methods.
|
/// This is a wrapper around a basic uuid with some extra convenience methods.
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct RecordId(Uuid);
|
pub struct RecordId(Uuid);
|
||||||
|
|
||||||
impl Default for RecordId {
|
impl Default for RecordId {
|
||||||
|
|
|
@ -23,13 +23,13 @@ use crate::{
|
||||||
},
|
},
|
||||||
view_models::DayDetailViewModel,
|
view_models::DayDetailViewModel,
|
||||||
};
|
};
|
||||||
use dimensioned::si;
|
use emseries::Record;
|
||||||
use ft_core::{RecordType, TraxRecord};
|
use ft_core::{RecordType, TraxRecord};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use std::cell::RefCell;
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use super::time_distance_detail;
|
use super::{time_distance::TimeDistanceEdit, time_distance_detail};
|
||||||
|
|
||||||
pub struct DaySummaryPrivate {
|
pub struct DaySummaryPrivate {
|
||||||
date: gtk::Label,
|
date: gtk::Label,
|
||||||
|
@ -198,12 +198,19 @@ impl DayDetail {
|
||||||
|
|
||||||
pub struct DayEditPrivate {
|
pub struct DayEditPrivate {
|
||||||
on_finished: RefCell<Box<dyn Fn()>>,
|
on_finished: RefCell<Box<dyn Fn()>>,
|
||||||
|
workout_rows: RefCell<gtk::Box>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DayEditPrivate {
|
impl Default for DayEditPrivate {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
on_finished: RefCell::new(Box::new(|| {})),
|
on_finished: RefCell::new(Box::new(|| {})),
|
||||||
|
workout_rows: RefCell::new(
|
||||||
|
gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.hexpand(true)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,9 +240,15 @@ impl DayEdit {
|
||||||
s.set_hexpand(true);
|
s.set_hexpand(true);
|
||||||
*s.imp().on_finished.borrow_mut() = Box::new(on_finished);
|
*s.imp().on_finished.borrow_mut() = Box::new(on_finished);
|
||||||
|
|
||||||
|
let workout_buttons = workout_buttons(view_model.clone(), {
|
||||||
|
let s = s.clone();
|
||||||
|
move |workout| s.add_row(workout)
|
||||||
|
});
|
||||||
|
|
||||||
s.append(&control_buttons(&s, &view_model));
|
s.append(&control_buttons(&s, &view_model));
|
||||||
s.append(&weight_and_steps_row(&view_model));
|
s.append(&weight_and_steps_row(&view_model));
|
||||||
s.append(&workout_buttons());
|
s.append(&*s.imp().workout_rows.borrow());
|
||||||
|
s.append(&workout_buttons);
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
@ -243,6 +256,24 @@ impl DayEdit {
|
||||||
fn finish(&self) {
|
fn finish(&self) {
|
||||||
(self.imp().on_finished.borrow())()
|
(self.imp().on_finished.borrow())()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_row(&self, workout: Record<TraxRecord>) {
|
||||||
|
println!("add_row: {:?}", workout);
|
||||||
|
let workout_rows = self.imp().workout_rows.borrow();
|
||||||
|
|
||||||
|
let workout_type = workout.data.workout_type();
|
||||||
|
|
||||||
|
match workout.data {
|
||||||
|
TraxRecord::BikeRide(w)
|
||||||
|
| TraxRecord::Row(w)
|
||||||
|
| TraxRecord::Swim(w)
|
||||||
|
| TraxRecord::Run(w)
|
||||||
|
| TraxRecord::Walk(w) => {
|
||||||
|
workout_rows.append(&TimeDistanceEdit::new(workout_type, w, |_, _| {}))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup {
|
fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup {
|
||||||
|
@ -291,24 +322,37 @@ fn weight_and_steps_row(view_model: &DayDetailViewModel) -> gtk::Box {
|
||||||
row
|
row
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workout_buttons() -> gtk::Box {
|
fn workout_buttons<AddRow>(view_model: DayDetailViewModel, add_row: AddRow) -> gtk::Box
|
||||||
let sunrise_button = gtk::Button::builder()
|
where
|
||||||
.icon_name("daytime-sunrise-symbolic")
|
AddRow: Fn(Record<TraxRecord>) + 'static,
|
||||||
.width_request(64)
|
{
|
||||||
.height_request(64)
|
let add_row = Rc::new(add_row);
|
||||||
.build();
|
|
||||||
|
|
||||||
let walking_button = gtk::Button::builder()
|
let walking_button = gtk::Button::builder()
|
||||||
.icon_name("walking2-symbolic")
|
.icon_name("walking2-symbolic")
|
||||||
.width_request(64)
|
.width_request(64)
|
||||||
.height_request(64)
|
.height_request(64)
|
||||||
.build();
|
.build();
|
||||||
|
walking_button.connect_clicked({
|
||||||
|
let view_model = view_model.clone();
|
||||||
|
let add_row = add_row.clone();
|
||||||
|
move |_| {
|
||||||
|
let workout = view_model.new_record(RecordType::Walk);
|
||||||
|
&add_row(workout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let running_button = gtk::Button::builder()
|
let running_button = gtk::Button::builder()
|
||||||
.icon_name("running-symbolic")
|
.icon_name("running-symbolic")
|
||||||
.width_request(64)
|
.width_request(64)
|
||||||
.height_request(64)
|
.height_request(64)
|
||||||
.build();
|
.build();
|
||||||
|
running_button.connect_clicked({
|
||||||
|
let view_model = view_model.clone();
|
||||||
|
move |_| {
|
||||||
|
let workout = view_model.new_record(RecordType::Walk);
|
||||||
|
add_row(workout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let layout = gtk::Box::builder()
|
let layout = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Vertical)
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
@ -316,7 +360,6 @@ fn workout_buttons() -> gtk::Box {
|
||||||
let row = gtk::Box::builder()
|
let row = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
.build();
|
.build();
|
||||||
row.append(&sunrise_button);
|
|
||||||
row.append(&walking_button);
|
row.append(&walking_button);
|
||||||
row.append(&running_button);
|
row.append(&running_button);
|
||||||
layout.append(&row);
|
layout.append(&row);
|
||||||
|
|
|
@ -132,6 +132,7 @@ impl Default for TimeDistanceEdit {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_orientation(gtk::Orientation::Horizontal);
|
s.set_orientation(gtk::Orientation::Horizontal);
|
||||||
|
s.set_hexpand(true);
|
||||||
s.set_css_classes(&["time-distance-edit"]);
|
s.set_css_classes(&["time-distance-edit"]);
|
||||||
|
|
||||||
s
|
s
|
||||||
|
@ -139,12 +140,17 @@ impl Default for TimeDistanceEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimeDistanceEdit {
|
impl TimeDistanceEdit {
|
||||||
fn empty<OnUpdate>(on_update: OnUpdate) -> Self
|
pub fn new<OnUpdate>(type_: RecordType, record: TimeDistance, on_update: OnUpdate) -> Self
|
||||||
where
|
where
|
||||||
OnUpdate: Fn(&ft_core::RecordType, &ft_core::TimeDistance),
|
OnUpdate: Fn(&ft_core::RecordType, &ft_core::TimeDistance),
|
||||||
{
|
{
|
||||||
|
println!("new TimeDistanceEdit");
|
||||||
let s = Self::default();
|
let s = Self::default();
|
||||||
|
|
||||||
|
s.append(>k::Label::new(Some(
|
||||||
|
record.datetime.format("%H:%M").to_string().as_ref(),
|
||||||
|
)));
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use dimensioned::si;
|
use dimensioned::si;
|
||||||
use emseries::{Record, RecordId, Recordable};
|
use emseries::{Record, RecordId, Recordable};
|
||||||
use ft_core::{TimeDistance, TraxRecord};
|
use ft_core::{RecordType, TimeDistance, TraxRecord};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
|
@ -27,7 +27,7 @@ use std::{
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum RecordState<T: Clone + Recordable> {
|
enum RecordState<T: Clone + Recordable> {
|
||||||
Original(Record<T>),
|
Original(Record<T>),
|
||||||
New(T),
|
New(Record<T>),
|
||||||
Updated(Record<T>),
|
Updated(Record<T>),
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
Deleted(Record<T>),
|
Deleted(Record<T>),
|
||||||
|
@ -53,13 +53,21 @@ impl<T: Clone + emseries::Recordable> RecordState<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_value(self, value: T) -> RecordState<T> {
|
fn set_value(&mut self, value: T) {
|
||||||
match self {
|
*self = match self {
|
||||||
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..r }),
|
RecordState::Original(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||||
RecordState::New(_) => RecordState::New(value),
|
RecordState::New(_) => RecordState::New(Record {
|
||||||
RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..r }),
|
id: RecordId::default(),
|
||||||
RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..r }),
|
data: value,
|
||||||
|
}),
|
||||||
|
RecordState::Updated(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||||
|
RecordState::Deleted(r) => RecordState::Updated(Record { data: value, ..*r }),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_value(mut self, value: T) -> RecordState<T> {
|
||||||
|
self.set_value(value);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -78,7 +86,7 @@ impl<T: Clone + emseries::Recordable> Deref for RecordState<T> {
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
match self {
|
match self {
|
||||||
RecordState::Original(ref r) => &r.data,
|
RecordState::Original(ref r) => &r.data,
|
||||||
RecordState::New(ref r) => r,
|
RecordState::New(ref r) => &r.data,
|
||||||
RecordState::Updated(ref r) => &r.data,
|
RecordState::Updated(ref r) => &r.data,
|
||||||
RecordState::Deleted(ref r) => &r.data,
|
RecordState::Deleted(ref r) => &r.data,
|
||||||
}
|
}
|
||||||
|
@ -126,9 +134,12 @@ impl DayDetailViewModel {
|
||||||
date: self.date,
|
date: self.date,
|
||||||
weight: new_weight,
|
weight: new_weight,
|
||||||
}),
|
}),
|
||||||
None => RecordState::New(ft_core::Weight {
|
None => RecordState::New(Record {
|
||||||
|
id: RecordId::default(),
|
||||||
|
data: ft_core::Weight {
|
||||||
date: self.date,
|
date: self.date,
|
||||||
weight: new_weight,
|
weight: new_weight,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
*record = Some(new_record);
|
*record = Some(new_record);
|
||||||
|
@ -145,9 +156,12 @@ impl DayDetailViewModel {
|
||||||
date: self.date,
|
date: self.date,
|
||||||
count: new_count,
|
count: new_count,
|
||||||
}),
|
}),
|
||||||
None => RecordState::New(ft_core::Steps {
|
None => RecordState::New(Record {
|
||||||
|
id: RecordId::default(),
|
||||||
|
data: ft_core::Steps {
|
||||||
date: self.date,
|
date: self.date,
|
||||||
count: new_count,
|
count: new_count,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
*record = Some(new_record);
|
*record = Some(new_record);
|
||||||
|
@ -177,6 +191,25 @@ impl DayDetailViewModel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_record(&self, type_: RecordType) -> Record<TraxRecord> {
|
||||||
|
let new_record = Record {
|
||||||
|
id: RecordId::default(),
|
||||||
|
data: ft_core::TraxRecord::new(type_, chrono::Local::now().into()),
|
||||||
|
};
|
||||||
|
self.records
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert(new_record.id.clone(), RecordState::New(new_record.clone()));
|
||||||
|
new_record
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_record(&self, update: Record<TraxRecord>) {
|
||||||
|
let mut records = self.records.write().unwrap();
|
||||||
|
records
|
||||||
|
.entry(update.id)
|
||||||
|
.and_modify(|mut record| record.set_value(update.data));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn records(&self) -> Vec<Record<TraxRecord>> {
|
pub fn records(&self) -> Vec<Record<TraxRecord>> {
|
||||||
let read_lock = self.records.read().unwrap();
|
let read_lock = self.records.read().unwrap();
|
||||||
read_lock
|
read_lock
|
||||||
|
@ -194,8 +227,8 @@ impl DayDetailViewModel {
|
||||||
if let Some(app) = s.app {
|
if let Some(app) = s.app {
|
||||||
let weight_record = s.weight.read().unwrap().clone();
|
let weight_record = s.weight.read().unwrap().clone();
|
||||||
match weight_record {
|
match weight_record {
|
||||||
Some(RecordState::New(weight)) => {
|
Some(RecordState::New(Record { data, .. })) => {
|
||||||
let _ = app.put_record(TraxRecord::Weight(weight)).await;
|
let _ = app.put_record(TraxRecord::Weight(data)).await;
|
||||||
}
|
}
|
||||||
Some(RecordState::Original(_)) => {}
|
Some(RecordState::Original(_)) => {}
|
||||||
Some(RecordState::Updated(weight)) => {
|
Some(RecordState::Updated(weight)) => {
|
||||||
|
@ -212,8 +245,8 @@ impl DayDetailViewModel {
|
||||||
|
|
||||||
let steps_record = s.steps.read().unwrap().clone();
|
let steps_record = s.steps.read().unwrap().clone();
|
||||||
match steps_record {
|
match steps_record {
|
||||||
Some(RecordState::New(steps)) => {
|
Some(RecordState::New(Record { data, .. })) => {
|
||||||
let _ = app.put_record(TraxRecord::Steps(steps)).await;
|
let _ = app.put_record(TraxRecord::Steps(data)).await;
|
||||||
}
|
}
|
||||||
Some(RecordState::Original(_)) => {}
|
Some(RecordState::Original(_)) => {}
|
||||||
Some(RecordState::Updated(steps)) => {
|
Some(RecordState::Updated(steps)) => {
|
||||||
|
@ -238,7 +271,7 @@ impl DayDetailViewModel {
|
||||||
|
|
||||||
for record in records {
|
for record in records {
|
||||||
match record {
|
match record {
|
||||||
RecordState::New(data) => {
|
RecordState::New(Record { data, .. }) => {
|
||||||
let _ = app.put_record(data).await;
|
let _ = app.put_record(data).await;
|
||||||
}
|
}
|
||||||
RecordState::Original(_) => {}
|
RecordState::Original(_) => {}
|
||||||
|
|
|
@ -57,6 +57,17 @@ pub struct TimeDistance {
|
||||||
pub comments: Option<String>,
|
pub comments: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TimeDistance {
|
||||||
|
pub fn new(time: DateTime<FixedOffset>) -> Self {
|
||||||
|
Self {
|
||||||
|
datetime: time,
|
||||||
|
distance: None,
|
||||||
|
duration: None,
|
||||||
|
comments: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A singular daily weight measurement. Weight changes slowly enough that it seems unlikely to
|
/// A singular daily weight measurement. Weight changes slowly enough that it seems unlikely to
|
||||||
/// need to track more than a single weight in a day.
|
/// need to track more than a single weight in a day.
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -99,6 +110,18 @@ pub enum TraxRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TraxRecord {
|
impl TraxRecord {
|
||||||
|
pub fn new(type_: RecordType, time: DateTime<FixedOffset>) -> TraxRecord {
|
||||||
|
match type_ {
|
||||||
|
RecordType::BikeRide => TraxRecord::BikeRide(TimeDistance::new(time)),
|
||||||
|
RecordType::Row => TraxRecord::Row(TimeDistance::new(time)),
|
||||||
|
RecordType::Run => TraxRecord::Run(TimeDistance::new(time)),
|
||||||
|
RecordType::Steps => unimplemented!(),
|
||||||
|
RecordType::Swim => unimplemented!(),
|
||||||
|
RecordType::Walk => TraxRecord::Walk(TimeDistance::new(time)),
|
||||||
|
RecordType::Weight => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn workout_type(&self) -> RecordType {
|
pub fn workout_type(&self) -> RecordType {
|
||||||
match self {
|
match self {
|
||||||
TraxRecord::BikeRide(_) => RecordType::BikeRide,
|
TraxRecord::BikeRide(_) => RecordType::BikeRide,
|
||||||
|
|
Loading…
Reference in New Issue