233 lines
7.1 KiB
Rust
233 lines
7.1 KiB
Rust
/*
|
|
Copyright 2023-2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
|
|
|
This file is part of FitnessTrax.
|
|
|
|
FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU
|
|
General Public License as published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
|
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
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/>.
|
|
*/
|
|
|
|
// use crate::components::{EditView, ParseError, TextEntry};
|
|
// use chrono::{Local, NaiveDate};
|
|
// use dimensioned::si;
|
|
use crate::{
|
|
components::{distance_field, duration_field, time_field},
|
|
types::{DistanceFormatter, DurationFormatter, FormatOption, TimeFormatter},
|
|
};
|
|
use dimensioned::si;
|
|
use ft_core::{TimeDistance, TimeDistanceActivity};
|
|
use glib::Object;
|
|
use gtk::{prelude::*, subclass::prelude::*};
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
pub fn time_distance_summary(
|
|
distance: DistanceFormatter,
|
|
duration: DurationFormatter,
|
|
) -> Option<gtk::Label> {
|
|
let text = match (*distance > si::M, *duration > si::S) {
|
|
(true, true) => Some(format!(
|
|
"{} of biking in {}",
|
|
distance.format(FormatOption::Full),
|
|
duration.format(FormatOption::Full)
|
|
)),
|
|
(true, false) => Some(format!("{} of biking", distance.format(FormatOption::Full))),
|
|
(false, true) => Some(format!("{} of biking", duration.format(FormatOption::Full))),
|
|
(false, false) => None,
|
|
};
|
|
|
|
text.map(|text| gtk::Label::new(Some(&text)))
|
|
}
|
|
|
|
pub fn time_distance_detail(record: ft_core::TimeDistance) -> gtk::Box {
|
|
let layout = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Vertical)
|
|
.hexpand(true)
|
|
.build();
|
|
let first_row = gtk::Box::builder().homogeneous(true).build();
|
|
|
|
first_row.append(
|
|
>k::Label::builder()
|
|
.halign(gtk::Align::Start)
|
|
.label(record.datetime.format("%H:%M").to_string())
|
|
.build(),
|
|
);
|
|
|
|
first_row.append(
|
|
>k::Label::builder()
|
|
.halign(gtk::Align::Start)
|
|
.label(format!("{:?}", record.activity))
|
|
.build(),
|
|
);
|
|
|
|
first_row.append(
|
|
>k::Label::builder()
|
|
.halign(gtk::Align::Start)
|
|
.label(
|
|
record
|
|
.distance
|
|
.map(|dist| DistanceFormatter::from(dist).format(FormatOption::Abbreviated))
|
|
.unwrap_or("".to_owned()),
|
|
)
|
|
.build(),
|
|
);
|
|
|
|
first_row.append(
|
|
>k::Label::builder()
|
|
.halign(gtk::Align::Start)
|
|
.label(
|
|
record
|
|
.duration
|
|
.map(|duration| {
|
|
DurationFormatter::from(duration).format(FormatOption::Abbreviated)
|
|
})
|
|
.unwrap_or("".to_owned()),
|
|
)
|
|
.build(),
|
|
);
|
|
|
|
layout.append(&first_row);
|
|
|
|
layout.append(
|
|
>k::Label::builder()
|
|
.halign(gtk::Align::Start)
|
|
.label(
|
|
record
|
|
.comments
|
|
.map(|comments| comments.to_string())
|
|
.unwrap_or("".to_owned()),
|
|
)
|
|
.build(),
|
|
);
|
|
|
|
layout
|
|
}
|
|
|
|
type OnUpdate = Rc<RefCell<Box<dyn Fn(TimeDistance)>>>;
|
|
|
|
pub struct TimeDistanceEditPrivate {
|
|
#[allow(unused)]
|
|
workout: RefCell<ft_core::TimeDistance>,
|
|
on_update: OnUpdate,
|
|
}
|
|
|
|
impl Default for TimeDistanceEditPrivate {
|
|
fn default() -> Self {
|
|
Self {
|
|
workout: RefCell::new(TimeDistance {
|
|
datetime: chrono::Utc::now().into(),
|
|
activity: TimeDistanceActivity::BikeRide,
|
|
duration: None,
|
|
distance: None,
|
|
comments: None,
|
|
}),
|
|
on_update: Rc::new(RefCell::new(Box::new(|_| {}))),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for TimeDistanceEditPrivate {
|
|
const NAME: &'static str = "TimeDistanceEdit";
|
|
type Type = TimeDistanceEdit;
|
|
type ParentType = gtk::Box;
|
|
}
|
|
|
|
impl ObjectImpl for TimeDistanceEditPrivate {}
|
|
impl WidgetImpl for TimeDistanceEditPrivate {}
|
|
impl BoxImpl for TimeDistanceEditPrivate {}
|
|
|
|
glib::wrapper! {
|
|
pub struct TimeDistanceEdit(ObjectSubclass<TimeDistanceEditPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
|
|
}
|
|
|
|
impl Default for TimeDistanceEdit {
|
|
fn default() -> Self {
|
|
let s: Self = Object::builder().build();
|
|
s.set_orientation(gtk::Orientation::Vertical);
|
|
s.set_hexpand(true);
|
|
s.set_css_classes(&["time-distance-edit"]);
|
|
|
|
s
|
|
}
|
|
}
|
|
|
|
impl TimeDistanceEdit {
|
|
pub fn new<OnUpdate>(workout: TimeDistance, on_update: OnUpdate) -> Self
|
|
where
|
|
OnUpdate: Fn(TimeDistance) + 'static,
|
|
{
|
|
let s = Self::default();
|
|
|
|
*s.imp().workout.borrow_mut() = workout.clone();
|
|
*s.imp().on_update.borrow_mut() = Box::new(on_update);
|
|
|
|
let details_row = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.build();
|
|
|
|
details_row.append(
|
|
&time_field(
|
|
Some(TimeFormatter::from(workout.datetime.naive_local().time())),
|
|
{
|
|
let s = s.clone();
|
|
move |t| s.update_time(t)
|
|
},
|
|
)
|
|
.widget(),
|
|
);
|
|
details_row.append(
|
|
&distance_field(workout.distance.map(DistanceFormatter::from), {
|
|
let s = s.clone();
|
|
move |d| s.update_distance(d)
|
|
})
|
|
.widget(),
|
|
);
|
|
details_row.append(
|
|
&duration_field(workout.duration.map(DurationFormatter::from), {
|
|
let s = s.clone();
|
|
move |d| s.update_duration(d)
|
|
})
|
|
.widget(),
|
|
);
|
|
s.append(&details_row);
|
|
s.append(>k::Entry::new());
|
|
|
|
s
|
|
}
|
|
|
|
fn update_time(&self, time: Option<TimeFormatter>) {
|
|
if let Some(time_formatter) = time {
|
|
let mut workout = self.imp().workout.borrow_mut();
|
|
let tz = workout.datetime.timezone();
|
|
let new_time = workout
|
|
.datetime
|
|
.date_naive()
|
|
.and_time(*time_formatter)
|
|
.and_local_timezone(tz)
|
|
.unwrap()
|
|
.fixed_offset();
|
|
workout.datetime = new_time;
|
|
(self.imp().on_update.borrow())(workout.clone());
|
|
}
|
|
}
|
|
|
|
fn update_distance(&self, distance: Option<DistanceFormatter>) {
|
|
let mut workout = self.imp().workout.borrow_mut();
|
|
workout.distance = distance.map(|d| *d);
|
|
(self.imp().on_update.borrow())(workout.clone());
|
|
}
|
|
|
|
fn update_duration(&self, duration: Option<DurationFormatter>) {
|
|
let mut workout = self.imp().workout.borrow_mut();
|
|
workout.duration = duration.map(|d| *d);
|
|
(self.imp().on_update.borrow())(workout.clone());
|
|
}
|
|
}
|