Finish removing the previous record grouping

Now that the DayDetailViewModel knows to retrieve its own records, the grouping functions, and passing groups of records around, no longer make sens.
This commit is contained in:
Savanni D'Gerinel 2024-02-01 10:08:18 -05:00
parent c1e797f3ae
commit 96317f5692
2 changed files with 25 additions and 159 deletions

View File

@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with Fit
use crate::{ use crate::{
app::App, app::App,
types::DayInterval,
view_models::DayDetailViewModel, view_models::DayDetailViewModel,
views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView}, views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView},
}; };
@ -133,10 +134,10 @@ impl AppWindow {
self.swap_main(view); self.swap_main(view);
} }
fn show_historical_view(&self, records: Vec<Record<TraxRecord>>) { fn show_historical_view(&self, interval: DayInterval) {
let view = View::Historical(HistoricalView::new(self.app.clone(), records, { let view = View::Historical(HistoricalView::new(self.app.clone(), interval, {
let s = self.clone(); let s = self.clone();
Rc::new(move |date, _records| { Rc::new(move |date| {
let s = s.clone(); let s = s.clone();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
let layout = gtk::Box::new(gtk::Orientation::Vertical, 0); let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
@ -160,10 +161,7 @@ impl AppWindow {
async move { async move {
let end = Local::now().date_naive(); let end = Local::now().date_naive();
let start = end - Duration::days(7); let start = end - Duration::days(7);
match s.app.records(start, end).await { s.show_historical_view(DayInterval { start, end });
Ok(records) => s.show_historical_view(records),
Err(_) => s.show_welcome_view(),
}
} }
}); });
} }

View File

@ -62,11 +62,11 @@ impl ObjectSubclass for HistoricalViewPrivate {
let app = app.clone(); let app = app.clone();
let list_item = list_item.clone(); let list_item = list_item.clone();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
let records = list_item let date = list_item
.downcast_ref::<gtk::ListItem>() .downcast_ref::<gtk::ListItem>()
.expect("should be a ListItem") .expect("should be a ListItem")
.item() .item()
.and_downcast::<DayRecords>() .and_downcast::<Date>()
.expect("should be a DaySummary"); .expect("should be a DaySummary");
let summary = list_item let summary = list_item
@ -77,7 +77,7 @@ impl ObjectSubclass for HistoricalViewPrivate {
.expect("should be a DaySummary"); .expect("should be a DaySummary");
if let Some(app) = app.borrow().clone() { if let Some(app) = app.borrow().clone() {
let view_model = DayDetailViewModel::new(records.date(), app.clone()).await; let view_model = DayDetailViewModel::new(date.date(), app.clone()).await;
summary.set_data(view_model); summary.set_data(view_model);
} }
}); });
@ -97,13 +97,9 @@ glib::wrapper! {
} }
impl HistoricalView { impl HistoricalView {
pub fn new<SelectFn>( pub fn new<SelectFn>(app: App, interval: DayInterval, on_select_day: Rc<SelectFn>) -> Self
app: App,
records: Vec<Record<TraxRecord>>,
on_select_day: Rc<SelectFn>,
) -> Self
where where
SelectFn: Fn(chrono::NaiveDate, Vec<Record<TraxRecord>>) + 'static, SelectFn: Fn(chrono::NaiveDate) + 'static,
{ {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
s.set_orientation(gtk::Orientation::Vertical); s.set_orientation(gtk::Orientation::Vertical);
@ -111,11 +107,8 @@ impl HistoricalView {
*s.imp().app.borrow_mut() = Some(app); *s.imp().app.borrow_mut() = Some(app);
let grouped_records = let mut model = gio::ListStore::new::<Date>();
GroupedRecords::new((*s.imp().time_window.borrow()).clone()).with_data(records); model.extend(interval.days().map(Date::new));
let mut model = gio::ListStore::new::<DayRecords>();
model.extend(grouped_records.items());
s.imp() s.imp()
.list_view .list_view
.set_model(Some(&gtk::NoSelection::new(Some(model)))); .set_model(Some(&gtk::NoSelection::new(Some(model))));
@ -127,8 +120,8 @@ impl HistoricalView {
// actually want to do here is to open a modal dialog that shows all of the details of // actually want to do here is to open a modal dialog that shows all of the details of
// the day and which allows the user to edit items within that dialog. // the day and which allows the user to edit items within that dialog.
let item = s.model().unwrap().item(idx).unwrap(); let item = s.model().unwrap().item(idx).unwrap();
let records = item.downcast_ref::<DayRecords>().unwrap(); let date = item.downcast_ref::<Date>().unwrap();
on_select_day(records.date(), records.records()); on_select_day(date.date());
} }
}); });
@ -137,12 +130,9 @@ impl HistoricalView {
s s
} }
pub fn set_records(&self, records: Vec<Record<TraxRecord>>) { pub fn set_intervals(&self, interval: DayInterval) {
println!("set_records: {:?}", records); let mut model = gio::ListStore::new::<Date>();
let grouped_records = model.extend(interval.days().map(Date::new));
GroupedRecords::new((self.imp().time_window.borrow()).clone()).with_data(records);
let mut model = gio::ListStore::new::<DayRecords>();
model.extend(grouped_records.items());
self.imp() self.imp()
.list_view .list_view
.set_model(Some(&gtk::NoSelection::new(Some(model)))); .set_model(Some(&gtk::NoSelection::new(Some(model))));
@ -154,152 +144,30 @@ impl HistoricalView {
} }
#[derive(Default)] #[derive(Default)]
pub struct DayRecordsPrivate { pub struct DatePrivate {
date: RefCell<chrono::NaiveDate>, date: RefCell<chrono::NaiveDate>,
records: RefCell<Vec<Record<TraxRecord>>>,
} }
#[glib::object_subclass] #[glib::object_subclass]
impl ObjectSubclass for DayRecordsPrivate { impl ObjectSubclass for DatePrivate {
const NAME: &'static str = "DayRecords"; const NAME: &'static str = "Date";
type Type = DayRecords; type Type = Date;
} }
impl ObjectImpl for DayRecordsPrivate {} impl ObjectImpl for DatePrivate {}
glib::wrapper! { glib::wrapper! {
pub struct DayRecords(ObjectSubclass<DayRecordsPrivate>); pub struct Date(ObjectSubclass<DatePrivate>);
} }
impl DayRecords { impl Date {
pub fn new(date: chrono::NaiveDate, records: Vec<Record<TraxRecord>>) -> Self { pub fn new(date: chrono::NaiveDate) -> Self {
let s: Self = Object::builder().build(); let s: Self = Object::builder().build();
*s.imp().date.borrow_mut() = date; *s.imp().date.borrow_mut() = date;
*s.imp().records.borrow_mut() = records;
s s
} }
pub fn date(&self) -> chrono::NaiveDate { pub fn date(&self) -> chrono::NaiveDate {
*self.imp().date.borrow() *self.imp().date.borrow()
} }
pub fn records(&self) -> Vec<Record<TraxRecord>> {
self.imp().records.borrow().clone()
}
pub fn add_record(&self, record: Record<TraxRecord>) {
self.imp().records.borrow_mut().push(record);
}
}
// This isn't feeling quite right. DayRecords is a glib object, but I'm not sure that I want to
// really be passing that around. It seems not generic enough. I feel like this whole grouped
// records thing can be made more generic.
struct GroupedRecords {
interval: DayInterval,
data: HashMap<NaiveDate, DayRecords>,
}
impl GroupedRecords {
fn new(interval: DayInterval) -> Self {
let mut s = Self {
interval: interval.clone(),
data: HashMap::new(),
};
interval.days().for_each(|date| {
let _ = s.data.insert(date, DayRecords::new(date, vec![]));
});
s
}
fn with_data(mut self, records: Vec<Record<TraxRecord>>) -> Self {
records.into_iter().for_each(|record| {
self.data
.entry(record.date())
.and_modify(|entry: &mut DayRecords| (*entry).add_record(record.clone()))
.or_insert(DayRecords::new(record.date(), vec![record]));
});
self
}
fn items(&self) -> impl Iterator<Item = DayRecords> + '_ {
self.interval.days().map(|date| {
self.data
.get(&date)
.cloned()
.unwrap_or(DayRecords::new(date, vec![]))
})
}
}
#[cfg(test)]
mod test {
use super::GroupedRecords;
use crate::types::DayInterval;
use chrono::{FixedOffset, NaiveDate, TimeZone};
use dimensioned::si::{KG, M, S};
use emseries::{Record, RecordId};
use ft_core::{Steps, TimeDistance, TraxRecord, Weight};
#[test]
fn groups_records() {
let records = vec![
Record {
id: RecordId::default(),
data: TraxRecord::Steps(Steps {
date: NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(),
count: 1500,
}),
},
Record {
id: RecordId::default(),
data: TraxRecord::Weight(Weight {
date: NaiveDate::from_ymd_opt(2023, 10, 13).unwrap(),
weight: 85. * KG,
}),
},
Record {
id: RecordId::default(),
data: TraxRecord::Weight(Weight {
date: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(),
weight: 86. * KG,
}),
},
Record {
id: RecordId::default(),
data: TraxRecord::BikeRide(TimeDistance {
datetime: FixedOffset::west_opt(10 * 60 * 60)
.unwrap()
.with_ymd_and_hms(2019, 6, 15, 12, 0, 0)
.unwrap(),
distance: Some(1000. * M),
duration: Some(150. * S),
comments: Some("Test Comments".to_owned()),
}),
},
Record {
id: RecordId::default(),
data: TraxRecord::BikeRide(TimeDistance {
datetime: FixedOffset::west_opt(10 * 60 * 60)
.unwrap()
.with_ymd_and_hms(2019, 6, 15, 23, 0, 0)
.unwrap(),
distance: Some(1000. * M),
duration: Some(150. * S),
comments: Some("Test Comments".to_owned()),
}),
},
];
let groups = GroupedRecords::new(DayInterval {
start: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(),
end: NaiveDate::from_ymd_opt(2023, 10, 14).unwrap(),
})
.with_data(records)
.data;
assert_eq!(groups.len(), 3);
}
} }