Compare commits

...

2 Commits

Author SHA1 Message Date
Savanni D'Gerinel 4acf034b8d Clean up warnings and remove printlns 2024-02-08 19:01:56 -05:00
Savanni D'Gerinel 1aff203afc Reload data when the user saves on the DayEdit panel
This required some big overhauls. The view model no longer takes records. It only takes the date that it is responsible for, and it will ask the database for records pertaining to that date. This means that once the view model has saved all of its records, it can simply reload those records from the database. This has the effect that as soon as the user moves from DayEdit back to DayDetail, all of the interesting information has been repopulated.
2024-02-08 19:01:56 -05:00
10 changed files with 78 additions and 128 deletions

View File

@ -201,7 +201,7 @@ mod test {
impl Recordable for WeightRecord {
fn timestamp(&self) -> Timestamp {
Timestamp::Date(self.date.clone())
Timestamp::Date(self.date)
}
fn tags(&self) -> Vec<String> {

View File

@ -20,7 +20,7 @@ extern crate emseries;
#[cfg(test)]
mod test {
use chrono::{format::Fixed, prelude::*};
use chrono::{prelude::*};
use chrono_tz::Etc::UTC;
use dimensioned::si::{Kilogram, Meter, Second, M, S};
@ -42,7 +42,7 @@ mod test {
impl Recordable for BikeTrip {
fn timestamp(&self) -> Timestamp {
Timestamp::DateTime(self.datetime.clone())
Timestamp::DateTime(self.datetime)
}
fn tags(&self) -> Vec<String> {
Vec::new()
@ -99,7 +99,7 @@ mod test {
]
}
fn run_test<T>(test: T) -> ()
fn run_test<T>(test: T)
where
T: FnOnce(tempfile::TempPath),
{
@ -108,7 +108,7 @@ mod test {
test(tmp_path);
}
fn run<T>(test: T) -> ()
fn run<T>(test: T)
where
T: FnOnce(Series<BikeTrip>),
{
@ -280,8 +280,7 @@ mod test {
UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
)
.into(),
),
true,
),
|l, r| l.timestamp().cmp(&r.timestamp()),

View File

@ -95,6 +95,20 @@ impl App {
.await
.unwrap()
}
pub async fn get_record(&self, id: RecordId) -> Result<Option<Record<TraxRecord>>, AppError> {
let db = self.database.clone();
self.runtime
.spawn_blocking(move || {
if let Some(ref db) = *db.read().unwrap() {
Ok(db.get(&id))
} else {
Err(AppError::NoDatabase)
}
})
.await
.unwrap()
}
}
#[async_trait]

View File

@ -99,10 +99,6 @@ impl AppWindow {
window.set_content(Some(&navigation));
window.present();
let gesture = gtk::GestureClick::new();
gesture.connect_released(|_, _, _, _| println!("detected gesture"));
layout.add_controller(gesture);
let s = Self {
app: ft_app,
layout,
@ -135,14 +131,15 @@ impl AppWindow {
}
fn show_historical_view(&self, interval: DayInterval) {
let view = View::Historical(HistoricalView::new(self.app.clone(), interval, {
let on_select_day = {
let s = self.clone();
Rc::new(move |date| {
move |date| {
let s = s.clone();
glib::spawn_future_local(async move {
let view_model = DayDetailViewModel::new(date, s.app.clone()).await.unwrap();
let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
layout.append(&adw::HeaderBar::new());
let view_model = DayDetailViewModel::new(date, s.app.clone()).await.unwrap();
// layout.append(&DayDetailView::new(date, records, s.app.clone()));
layout.append(&DayDetailView::new(view_model));
let page = &adw::NavigationPage::builder()
.title(date.format("%Y-%m-%d").to_string())
@ -150,8 +147,14 @@ impl AppWindow {
.build();
s.navigation.push(page);
});
})
}));
}
};
let view = View::Historical(HistoricalView::new(
self.app.clone(),
interval,
Rc::new(on_select_day),
));
self.swap_main(view);
}

View File

@ -148,31 +148,6 @@ impl DayDetail {
.build(),
);
/*
let click_controller = gtk::GestureClick::new();
click_controller.connect_released({
let s = s.clone();
move |_, _, _, _| {
println!("clicked outside of focusable entity");
if let Some(widget) = s.focus_child().and_downcast_ref::<WeightView>() {
println!("focused child is the weight view");
widget.blur();
}
}
});
s.add_controller(click_controller);
*/
/*
let weight_record = records.iter().find_map(|record| match record {
Record {
id,
data: ft_core::TraxRecord::Weight(record),
} => Some((id.clone(), record.clone())),
_ => None,
});
*/
let top_row = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.build();
@ -272,11 +247,23 @@ impl DayEdit {
}
fn finish(&self) {
(self.imp().on_finished.borrow())()
glib::spawn_future_local({
let s = self.clone();
async move {
let view_model = {
let view_model = s.imp().view_model.borrow();
view_model
.as_ref()
.expect("DayEdit has not been initialized with the view model")
.clone()
};
let _ = view_model.async_save().await;
(s.imp().on_finished.borrow())()
}
});
}
fn add_row(&self, workout: Record<TraxRecord>) {
println!("adding a row for {:?}", workout);
let workout_rows = self.imp().workout_rows.borrow();
#[allow(clippy::single_match)]
@ -308,11 +295,7 @@ fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup
ActionGroup::builder()
.primary_action("Save", {
let s = s.clone();
let view_model = view_model.clone();
move || {
view_model.save();
s.finish();
}
move || s.finish()
})
.secondary_action("Cancel", {
let s = s.clone();

View File

@ -11,7 +11,8 @@ FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY W
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/>.
You should have received a copy of the GNU General Public License along with FitnessTrax. If not,
see <https://www.gnu.org/licenses/>.
*/
mod action_group;

View File

@ -25,7 +25,7 @@ use dimensioned::si;
use ft_core::{TimeDistance, TimeDistanceActivity};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, rc::Rc};
use std::{rc::Rc, cell::RefCell};
pub fn time_distance_summary(
distance: DistanceFormatter,

View File

@ -256,14 +256,6 @@ impl From<si::Second<f64>> for DurationFormatter {
}
}
/*
fn take_digits(s: String) -> String {
s.chars()
.take_while(|t| t.is_ascii_digit())
.collect::<String>()
}
*/
#[cfg(test)]
mod test {
use super::*;

View File

@ -120,51 +120,15 @@ impl DayDetailViewModel {
date: chrono::NaiveDate,
provider: impl RecordProvider + 'static,
) -> Result<Self, ReadError> {
let (weight_records, records): (Vec<Record<TraxRecord>>, Vec<Record<TraxRecord>>) =
provider
.records(date, date)
.await?
.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());
if weight_records.len() > 1 {
eprintln!("warning: multiple weight records found for {}. This is unsupported and the one presented is unpredictable.", date.format("%Y-%m-%d"));
}
if step_records.len() > 1 {
eprintln!("warning: multiple step records found for {}. This is unsupported and the one presented is unpredictable.", date.format("%Y-%m-%d"));
}
Ok(Self {
let s = Self {
provider: Arc::new(provider),
date,
weight: Arc::new(RwLock::new(
weight_records
.first()
.and_then(|r| match r.data {
TraxRecord::Weight(ref w) => Some((r.id, 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, w.clone())),
_ => None,
})
.map(|(id, w)| RecordState::Original(Record { id, data: w })),
)),
records: Arc::new(RwLock::new(
records
.into_iter()
.map(|r| (r.id, RecordState::Original(r)))
.collect::<HashMap<RecordId, RecordState<TraxRecord>>>(),
)),
})
weight: Arc::new(RwLock::new(None)),
steps: Arc::new(RwLock::new(None)),
records: Arc::new(RwLock::new(HashMap::new())),
};
s.populate_records().await;
Ok(s)
}
pub fn weight(&self) -> Option<si::Kilogram<f64>> {
@ -266,12 +230,10 @@ impl DayDetailViewModel {
}
pub fn update_record(&self, update: Record<TraxRecord>) {
println!("updating a record: {:?}", update);
let mut records = self.records.write().unwrap();
records
.entry(update.id)
.and_modify(|record| record.set_value(update.data));
println!("record updated: {:?}", records.get(&update.id));
}
pub fn records(&self) -> Vec<Record<TraxRecord>> {
@ -377,6 +339,7 @@ impl DayDetailViewModel {
}
}
}
self.populate_records().await;
}
pub async fn revert(&self) {

View File

@ -18,8 +18,6 @@ use crate::{
app::App, components::DaySummary, types::DayInterval, view_models::DayDetailViewModel,
};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, rc::Rc};
@ -60,31 +58,28 @@ impl ObjectSubclass for HistoricalViewPrivate {
factory.connect_bind({
let app = s.app.clone();
move |_, list_item| {
let app = app.clone();
let list_item = list_item.clone();
glib::spawn_future_local(async move {
let date = list_item
.downcast_ref::<gtk::ListItem>()
.expect("should be a ListItem")
.item()
.and_downcast::<Date>()
.expect("should be a DaySummary");
let date = list_item
.downcast_ref::<gtk::ListItem>()
.expect("should be a ListItem")
.item()
.and_downcast::<Date>()
.expect("should be a Date");
let summary = list_item
.downcast_ref::<gtk::ListItem>()
.expect("should be a ListItem")
.child()
.and_downcast::<DaySummary>()
.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() {
glib::spawn_future_local(async move {
let view_model =
DayDetailViewModel::new(date.date(), app).await.unwrap();
summary.set_data(view_model);
});
}
});
if let Some(app) = app.borrow().clone() {
glib::spawn_future_local(async move {
let view_model = DayDetailViewModel::new(date.date(), app.clone())
.await
.unwrap();
summary.set_data(view_model);
});
}
}
});