use anyhow::Result;
use gtk::{
gio::{self, prelude::*},
glib::{self, subclass::prelude::*},
};
use std::convert::TryInto;
mod imp {
use crate::{core::Database, model::Activity};
use gtk::subclass::prelude::*;
use gtk::{
gio,
glib::{self, subclass, Cast, StaticType},
};
use std::{
cell::RefCell,
convert::{TryFrom, TryInto},
};
#[derive(Debug, Default)]
pub struct ModelActivityMut {
pub vec: Vec<Activity>,
}
#[derive(Debug, Default)]
pub struct ModelActivity {
pub database: Database,
pub inner: RefCell<ModelActivityMut>,
}
#[glib::object_subclass]
impl ObjectSubclass for ModelActivity {
const NAME: &'static str = "HealthModelActivity";
type ParentType = glib::Object;
type Instance = subclass::basic::InstanceStruct<Self>;
type Class = subclass::basic::ClassStruct<Self>;
type Type = super::ModelActivity;
type Interfaces = (gio::ListModel,);
}
impl ObjectImpl for ModelActivity {}
impl ListModelImpl for ModelActivity {
fn item_type(&self) -> glib::Type {
Activity::static_type()
}
fn n_items(&self) -> u32 {
self.inner.borrow().vec.len().try_into().unwrap()
}
fn item(&self, position: u32) -> Option<glib::Object> {
self.inner
.borrow()
.vec
.get(usize::try_from(position).unwrap())
.map(|o| o.clone().upcast())
}
}
}
glib::wrapper! {
pub struct ModelActivity(ObjectSubclass<imp::ModelActivity>) @implements gio::ListModel;
}
impl Default for ModelActivity {
fn default() -> Self {
Self::new()
}
}
impl ModelActivity {
pub fn is_empty(&self) -> bool {
self.imp().inner.borrow().vec.is_empty()
}
pub fn new() -> Self {
glib::Object::new()
}
pub async fn reload(&self) -> Result<()> {
let imp = self.imp();
let previous_size = { imp.inner.borrow().vec.len() };
let new_vec = imp.database.activities().await?;
{
imp.inner.borrow_mut().vec = new_vec;
}
self.items_changed(
0,
previous_size.try_into().unwrap(),
imp.inner.borrow().vec.len().try_into().unwrap(),
);
Ok(())
}
pub async fn activity_present(&self) -> bool {
self.imp().database.has_activities().await.unwrap_or(false)
}
}
#[cfg(test)]
mod test {
use super::ModelActivity;
use crate::{
core::Database,
model::{Activity, ActivityType},
prelude::*,
};
use gtk::{glib, prelude::*};
#[test]
fn new() {
ModelActivity::new();
}
#[gtk::test]
fn is_empty() {
let data_dir = tempfile::tempdir().unwrap();
Database::set_instance(
Database::new_with_store_path(data_dir.path().to_path_buf()).unwrap(),
);
let a = ModelActivity::new();
assert!(a.is_empty());
glib::clone!(@weak a => async move {
a.reload().await.unwrap();
assert!(a.is_empty());
assert_eq!(a.n_items(), 0);
assert_eq!(a.item(0), None);
let activity = Activity::builder()
.activity_type(ActivityType::Walking)
.duration(glib::TimeSpan::from_minutes(1))
.date(glib::DateTime::from_unix_utc(1_000_000_000).unwrap())
.build();
Database::instance().save_activity(activity.clone()).await.unwrap();
a.reload().await.unwrap();
assert!(!a.is_empty());
assert_eq!(a.n_items(), 1);
let new_act = a.item(0).unwrap().downcast::<Activity>().unwrap();
assert_eq!(activity.activity_type(), new_act.activity_type());
assert_eq!(activity.duration(), new_act.duration());
assert_eq!(activity.date(), new_act.date());
assert_eq!(a.item(1), None);
})
.block();
}
}