221 lines
7.7 KiB
Rust
221 lines
7.7 KiB
Rust
|
/*
|
||
|
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::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<T: Clone + Recordable> {
|
||
|
Original(Record<T>),
|
||
|
New(T),
|
||
|
Updated(Record<T>),
|
||
|
Deleted(Record<T>),
|
||
|
}
|
||
|
|
||
|
impl<T: Clone + emseries::Recordable> RecordState<T> {
|
||
|
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<T> {
|
||
|
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<RecordState<T>> {
|
||
|
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<T: Clone + emseries::Recordable> Deref for RecordState<T> {
|
||
|
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<App>,
|
||
|
pub date: chrono::NaiveDate,
|
||
|
weight: Arc<RwLock<Option<RecordState<ft_core::Weight>>>>,
|
||
|
steps: Arc<RwLock<Option<RecordState<ft_core::Steps>>>>,
|
||
|
records: Arc<RwLock<HashMap<RecordId, RecordState<TraxRecord>>>>,
|
||
|
}
|
||
|
|
||
|
impl DayDetailViewModel {
|
||
|
pub fn new(date: chrono::NaiveDate, records: Vec<Record<TraxRecord>>, app: App) -> Self {
|
||
|
let (weight_records, records): (Vec<Record<TraxRecord>>, Vec<Record<TraxRecord>>) =
|
||
|
records.into_iter().partition(|r| r.data.is_weight());
|
||
|
let (step_records, records): (Vec<Record<TraxRecord>>, Vec<Record<TraxRecord>>) =
|
||
|
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::<HashMap<RecordId, RecordState<TraxRecord>>>(),
|
||
|
)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn weight(&self) -> Option<si::Kilogram<f64>> {
|
||
|
match *self.weight.read().unwrap() {
|
||
|
Some(ref w) => Some((*w).weight),
|
||
|
None => None,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn set_weight(&self, new_weight: si::Kilogram<f64>) {
|
||
|
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<u32> {
|
||
|
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::<Vec<RecordState<TraxRecord>>>();
|
||
|
|
||
|
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!();
|
||
|
}
|
||
|
}
|