monorepo/fitnesstrax/app/src/view_models/day_detail.rs

221 lines
7.7 KiB
Rust
Raw Normal View History

/*
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!();
}
}