Render and be able to edit bike rides (and sorta other time distance workouts) #169

Merged
savanni merged 13 commits from fitnesstrax/time-distance-workout into main 2024-02-09 00:05:26 +00:00
5 changed files with 69 additions and 104 deletions
Showing only changes of commit 1aff203afc - Show all commits

View File

@ -95,6 +95,20 @@ impl App {
.await .await
.unwrap() .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] #[async_trait]

View File

@ -135,14 +135,15 @@ impl AppWindow {
} }
fn show_historical_view(&self, interval: DayInterval) { 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(); let s = self.clone();
Rc::new(move |date| { move |date| {
let s = s.clone(); let s = s.clone();
glib::spawn_future_local(async move { 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); let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
layout.append(&adw::HeaderBar::new()); 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)); layout.append(&DayDetailView::new(view_model));
let page = &adw::NavigationPage::builder() let page = &adw::NavigationPage::builder()
.title(date.format("%Y-%m-%d").to_string()) .title(date.format("%Y-%m-%d").to_string())
@ -150,8 +151,14 @@ impl AppWindow {
.build(); .build();
s.navigation.push(page); s.navigation.push(page);
}); });
}) }
})); };
let view = View::Historical(HistoricalView::new(
self.app.clone(),
interval,
Rc::new(on_select_day),
));
self.swap_main(view); self.swap_main(view);
} }

View File

@ -148,31 +148,6 @@ impl DayDetail {
.build(), .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() let top_row = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal) .orientation(gtk::Orientation::Horizontal)
.build(); .build();
@ -272,7 +247,20 @@ impl DayEdit {
} }
fn finish(&self) { 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>) { fn add_row(&self, workout: Record<TraxRecord>) {
@ -308,11 +296,7 @@ fn control_buttons(s: &DayEdit, view_model: &DayDetailViewModel) -> ActionGroup
ActionGroup::builder() ActionGroup::builder()
.primary_action("Save", { .primary_action("Save", {
let s = s.clone(); let s = s.clone();
let view_model = view_model.clone(); move || s.finish()
move || {
view_model.save();
s.finish();
}
}) })
.secondary_action("Cancel", { .secondary_action("Cancel", {
let s = s.clone(); let s = s.clone();

View File

@ -120,51 +120,15 @@ impl DayDetailViewModel {
date: chrono::NaiveDate, date: chrono::NaiveDate,
provider: impl RecordProvider + 'static, provider: impl RecordProvider + 'static,
) -> Result<Self, ReadError> { ) -> Result<Self, ReadError> {
let (weight_records, records): (Vec<Record<TraxRecord>>, Vec<Record<TraxRecord>>) = let s = Self {
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 {
provider: Arc::new(provider), provider: Arc::new(provider),
date, date,
weight: Arc::new(RwLock::new( weight: Arc::new(RwLock::new(None)),
weight_records steps: Arc::new(RwLock::new(None)),
.first() records: Arc::new(RwLock::new(HashMap::new())),
.and_then(|r| match r.data { };
TraxRecord::Weight(ref w) => Some((r.id, w.clone())), s.populate_records().await;
_ => None, Ok(s)
})
.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>>>(),
)),
})
} }
pub fn weight(&self) -> Option<si::Kilogram<f64>> { pub fn weight(&self) -> Option<si::Kilogram<f64>> {
@ -377,6 +341,7 @@ impl DayDetailViewModel {
} }
} }
} }
self.populate_records().await;
} }
pub async fn revert(&self) { pub async fn revert(&self) {

View File

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