diff --git a/fitnesstrax/app/src/app_window.rs b/fitnesstrax/app/src/app_window.rs index 0326a22..d03c53c 100644 --- a/fitnesstrax/app/src/app_window.rs +++ b/fitnesstrax/app/src/app_window.rs @@ -20,10 +20,8 @@ use crate::{ }; use adw::prelude::*; use async_channel::Sender; -use chrono::{NaiveDate, TimeZone}; -use chrono_tz::America::Anchorage; +use chrono::{FixedOffset, NaiveDate, TimeZone}; use dimensioned::si::{KG, M, S}; -use emseries::DateTimeTz; use ft_core::{Steps, TimeDistance, TraxRecord, Weight}; use gio::resources_lookup_data; use gtk::STYLE_PROVIDER_PRIORITY_USER; @@ -164,17 +162,19 @@ impl AppWindow { weight: 86. * KG, }), TraxRecord::BikeRide(TimeDistance { - datetime: DateTimeTz( - Anchorage.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap(), - ), + 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()), }), TraxRecord::BikeRide(TimeDistance { - datetime: DateTimeTz( - Anchorage.with_ymd_and_hms(2019, 6, 15, 23, 0, 0).unwrap(), - ), + 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()), diff --git a/fitnesstrax/app/src/ui/mod.rs b/fitnesstrax/app/src/ui/mod.rs new file mode 100644 index 0000000..3a0fe05 --- /dev/null +++ b/fitnesstrax/app/src/ui/mod.rs @@ -0,0 +1,2 @@ +mod modal; +pub use modal::{welcome_modal, Modal}; diff --git a/fitnesstrax/app/src/ui/modal.rs b/fitnesstrax/app/src/ui/modal.rs new file mode 100644 index 0000000..a8ce309 --- /dev/null +++ b/fitnesstrax/app/src/ui/modal.rs @@ -0,0 +1,75 @@ +//! The Modal is a reusable component with a title, arbitrary content, and up to three action +//! buttons. It does not itself enforce being a modal, but is meant to become a child of an Overlay +//! component. +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::cell::RefCell; + +pub struct ModalPrivate { + title: gtk::Label, + content: RefCell, + primary_action: gtk::Button, + secondary_action: Option, + tertiary_action: Option, +} + +#[glib::object_subclass] +impl ObjectSubclass for ModalPrivate { + const NAME: &'static str = "Modal"; + type Type = Modal; + type ParentType = gtk::Box; + + fn new() -> Self { + let title = gtk::Label::builder().label("Modal").build(); + let content = gtk::Box::new(gtk::Orientation::Vertical, 0); + let actions = gtk::Box::new(gtk::Orientation::Horizontal, 0); + + Self { + title, + content: RefCell::new(content.upcast()), + primary_action: gtk::Button::new(), + secondary_action: None, + tertiary_action: None, + } + } +} + +impl ObjectImpl for ModalPrivate {} +impl WidgetImpl for ModalPrivate {} +impl BoxImpl for ModalPrivate {} + +glib::wrapper! { + pub struct Modal(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} + +impl Modal { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + s.append(&s.imp().title); + s.append(&*s.imp().content.borrow()); + // s.append(&s.imp().actions); + + s + } + + pub fn set_title(&self, text: &str) { + self.imp().title.set_text(text); + } + + pub fn set_content(&self, content: gtk::Widget) { + self.remove(&*self.imp().content.borrow()); + self.insert_child_after(&content, Some(&self.imp().title)); + *self.imp().content.borrow_mut() = content; + } +} + +/// The welcome modal is the first thing the user will see when FitnessTrax starts up if the +/// database has not been configured yet. +/// +/// This is a [Modal] component with all of the welcome content. +pub fn welcome_modal() -> Modal { + let modal = Modal::new(); + modal.set_title("Welcome to FitnessTrax"); + modal +} diff --git a/fitnesstrax/app/src/views/historical_view.rs b/fitnesstrax/app/src/views/historical_view.rs index c3d1cf0..2f14d52 100644 --- a/fitnesstrax/app/src/views/historical_view.rs +++ b/fitnesstrax/app/src/views/historical_view.rs @@ -151,7 +151,7 @@ impl From> for GroupedRecords { .into_iter() .fold(HashMap::new(), |mut acc, rec| { let date = match rec.timestamp() { - Timestamp::DateTime(dtz) => dtz.0.date_naive(), + Timestamp::DateTime(dtz) => dtz.date_naive(), Timestamp::Date(date) => date, }; acc.entry(date) diff --git a/fitnesstrax/core/src/lib.rs b/fitnesstrax/core/src/lib.rs index d454169..fcdbecd 100644 --- a/fitnesstrax/core/src/lib.rs +++ b/fitnesstrax/core/src/lib.rs @@ -1,6 +1,5 @@ use chrono::NaiveDate; use dimensioned::si; -use emseries::DateTimeTz; mod legacy; mod types; diff --git a/fitnesstrax/core/src/types.rs b/fitnesstrax/core/src/types.rs index 4de0da4..dd507fc 100644 --- a/fitnesstrax/core/src/types.rs +++ b/fitnesstrax/core/src/types.rs @@ -1,6 +1,6 @@ -use chrono::NaiveDate; +use chrono::{DateTime, FixedOffset, NaiveDate}; use dimensioned::si; -use emseries::{DateTimeTz, Recordable, Timestamp}; +use emseries::{Recordable, Timestamp}; use serde::{Deserialize, Serialize}; /// SetRep represents workouts like pushups or situps, which involve doing a "set" of a number of @@ -31,10 +31,8 @@ pub struct Steps { pub struct TimeDistance { /// The precise time (and the relevant timezone) of the workout. One of the edge cases that I /// account for is that a ride which occurred at 11pm in one timezone would then count as 1am - /// if one moved two timezones to the east. This is kind of nonsensical from a human - /// perspective, so the DateTimeTz keeps track of the precise time in UTC, but also the - /// timezone in which the event was recorded. - pub datetime: DateTimeTz, + /// if one moved two timezones to the east. + pub datetime: DateTime, /// The distance travelled. This is optional because such a workout makes sense even without /// the distance. pub distance: Option>,