Compare commits
2 Commits
ea867812bc
...
4acf034b8d
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 4acf034b8d | |
Savanni D'Gerinel | 1aff203afc |
|
@ -201,7 +201,7 @@ mod test {
|
||||||
|
|
||||||
impl Recordable for WeightRecord {
|
impl Recordable for WeightRecord {
|
||||||
fn timestamp(&self) -> Timestamp {
|
fn timestamp(&self) -> Timestamp {
|
||||||
Timestamp::Date(self.date.clone())
|
Timestamp::Date(self.date)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tags(&self) -> Vec<String> {
|
fn tags(&self) -> Vec<String> {
|
||||||
|
|
|
@ -20,7 +20,7 @@ extern crate emseries;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use chrono::{format::Fixed, prelude::*};
|
use chrono::{prelude::*};
|
||||||
use chrono_tz::Etc::UTC;
|
use chrono_tz::Etc::UTC;
|
||||||
use dimensioned::si::{Kilogram, Meter, Second, M, S};
|
use dimensioned::si::{Kilogram, Meter, Second, M, S};
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ mod test {
|
||||||
|
|
||||||
impl Recordable for BikeTrip {
|
impl Recordable for BikeTrip {
|
||||||
fn timestamp(&self) -> Timestamp {
|
fn timestamp(&self) -> Timestamp {
|
||||||
Timestamp::DateTime(self.datetime.clone())
|
Timestamp::DateTime(self.datetime)
|
||||||
}
|
}
|
||||||
fn tags(&self) -> Vec<String> {
|
fn tags(&self) -> Vec<String> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
@ -99,7 +99,7 @@ mod test {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_test<T>(test: T) -> ()
|
fn run_test<T>(test: T)
|
||||||
where
|
where
|
||||||
T: FnOnce(tempfile::TempPath),
|
T: FnOnce(tempfile::TempPath),
|
||||||
{
|
{
|
||||||
|
@ -108,7 +108,7 @@ mod test {
|
||||||
test(tmp_path);
|
test(tmp_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<T>(test: T) -> ()
|
fn run<T>(test: T)
|
||||||
where
|
where
|
||||||
T: FnOnce(Series<BikeTrip>),
|
T: FnOnce(Series<BikeTrip>),
|
||||||
{
|
{
|
||||||
|
@ -280,8 +280,7 @@ mod test {
|
||||||
UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
|
UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
|
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
|
||||||
)
|
),
|
||||||
.into(),
|
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|l, r| l.timestamp().cmp(&r.timestamp()),
|
|l, r| l.timestamp().cmp(&r.timestamp()),
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -99,10 +99,6 @@ impl AppWindow {
|
||||||
window.set_content(Some(&navigation));
|
window.set_content(Some(&navigation));
|
||||||
window.present();
|
window.present();
|
||||||
|
|
||||||
let gesture = gtk::GestureClick::new();
|
|
||||||
gesture.connect_released(|_, _, _, _| println!("detected gesture"));
|
|
||||||
layout.add_controller(gesture);
|
|
||||||
|
|
||||||
let s = Self {
|
let s = Self {
|
||||||
app: ft_app,
|
app: ft_app,
|
||||||
layout,
|
layout,
|
||||||
|
@ -135,14 +131,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 +147,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,11 +247,23 @@ 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>) {
|
||||||
println!("adding a row for {:?}", workout);
|
|
||||||
let workout_rows = self.imp().workout_rows.borrow();
|
let workout_rows = self.imp().workout_rows.borrow();
|
||||||
|
|
||||||
#[allow(clippy::single_match)]
|
#[allow(clippy::single_match)]
|
||||||
|
@ -308,11 +295,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();
|
||||||
|
|
|
@ -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
|
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
General Public License for more details.
|
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;
|
mod action_group;
|
||||||
|
|
|
@ -25,7 +25,7 @@ use dimensioned::si;
|
||||||
use ft_core::{TimeDistance, TimeDistanceActivity};
|
use ft_core::{TimeDistance, TimeDistanceActivity};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
pub fn time_distance_summary(
|
pub fn time_distance_summary(
|
||||||
distance: DistanceFormatter,
|
distance: DistanceFormatter,
|
||||||
|
|
|
@ -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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -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>> {
|
||||||
|
@ -266,12 +230,10 @@ impl DayDetailViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_record(&self, update: Record<TraxRecord>) {
|
pub fn update_record(&self, update: Record<TraxRecord>) {
|
||||||
println!("updating a record: {:?}", update);
|
|
||||||
let mut records = self.records.write().unwrap();
|
let mut records = self.records.write().unwrap();
|
||||||
records
|
records
|
||||||
.entry(update.id)
|
.entry(update.id)
|
||||||
.and_modify(|record| record.set_value(update.data));
|
.and_modify(|record| record.set_value(update.data));
|
||||||
println!("record updated: {:?}", records.get(&update.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn records(&self) -> Vec<Record<TraxRecord>> {
|
pub fn records(&self) -> Vec<Record<TraxRecord>> {
|
||||||
|
@ -377,6 +339,7 @@ impl DayDetailViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.populate_records().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn revert(&self) {
|
pub async fn revert(&self) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue