/* 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), #[allow(unused)] Deleted(Record), } impl RecordState { #[allow(unused)] 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 }), } } #[allow(unused)] 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> { (*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, weight: new_weight, }), None => RecordState::New(ft_core::Weight { date: self.date, weight: new_weight, }), }; *record = Some(new_record); } pub fn steps(&self) -> Option { (*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, count: new_count, }), None => RecordState::New(ft_core::Steps { date: self.date, 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!(); } }