/* Copyright 2023-2024, Savanni D'Gerinel 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 . */ // 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 { 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>>; pub struct TimeDistanceEditPrivate { #[allow(unused)] workout: RefCell, 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) @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(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) { 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) { 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) { let mut workout = self.imp().workout.borrow_mut(); workout.duration = duration.map(|d| *d); (self.imp().on_update.borrow())(workout.clone()); } }