/* 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 chrono::NaiveDate; // use ft_core::TraxRecord; use crate::components::{TimeDistanceView, Weight}; use emseries::Record; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::cell::RefCell; use super::weight::WeightEdit; pub struct DaySummaryPrivate { date: gtk::Label, weight: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for DaySummaryPrivate { const NAME: &'static str = "DaySummary"; type Type = DaySummary; type ParentType = gtk::Box; fn new() -> Self { let date = gtk::Label::builder() .css_classes(["day-summary__date"]) .halign(gtk::Align::Start) .build(); Self { date, weight: RefCell::new(None), } } } impl ObjectImpl for DaySummaryPrivate {} impl WidgetImpl for DaySummaryPrivate {} impl BoxImpl for DaySummaryPrivate {} glib::wrapper! { /// The DaySummary displays one day's activities in a narrative style. This is meant to give /// an overall feel of everything that happened during the day without going into details. pub struct DaySummary(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } impl DaySummary { pub fn new() -> Self { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_css_classes(&["day-summary"]); s.append(&s.imp().date); s } pub fn set_data(&self, date: chrono::NaiveDate, records: Vec>) { self.imp() .date .set_text(&date.format("%Y-%m-%d").to_string()); if let Some(ref weight_label) = *self.imp().weight.borrow() { self.remove(weight_label); } if let Some(Record { data: ft_core::TraxRecord::Weight(weight_record), .. }) = records.iter().filter(|f| f.data.is_weight()).next() { let label = gtk::Label::builder() .halign(gtk::Align::Start) .label(&format!("{}", weight_record.weight)) .css_classes(["day-summary__weight"]) .build(); self.append(&label); *self.imp().weight.borrow_mut() = Some(label); } /* self.append( >k::Label::builder() .halign(gtk::Align::Start) .label("15km of biking in 60 minutes") .build(), ); */ } } #[derive(Default)] struct CommandRowPrivate; #[glib::object_subclass] impl ObjectSubclass for CommandRowPrivate { const NAME: &'static str = "DayDetailCommandRow"; type Type = CommandRow; type ParentType = gtk::Box; } impl ObjectImpl for CommandRowPrivate {} impl WidgetImpl for CommandRowPrivate {} impl BoxImpl for CommandRowPrivate {} glib::wrapper! { struct CommandRow(ObjectSubclass) @extends gtk::Box, gtk::Widget; } impl CommandRow { fn new(on_edit: OnEdit) -> Self where OnEdit: Fn() + 'static, { let s: Self = Object::builder().build(); s.set_halign(gtk::Align::End); let edit_button = gtk::Button::builder().label("Edit").build(); edit_button.connect_clicked(move |_| on_edit()); s.append(&edit_button); s } } pub struct DayDetailPrivate { date: gtk::Label, weight: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for DayDetailPrivate { const NAME: &'static str = "DayDetail"; type Type = DayDetail; type ParentType = gtk::Box; fn new() -> Self { let date = gtk::Label::builder() .css_classes(["daysummary-date"]) .halign(gtk::Align::Start) .build(); Self { date, weight: RefCell::new(None), } } } impl ObjectImpl for DayDetailPrivate {} impl WidgetImpl for DayDetailPrivate {} impl BoxImpl for DayDetailPrivate {} glib::wrapper! { pub struct DayDetail(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } impl DayDetail { pub fn new( date: chrono::NaiveDate, records: Vec>, on_edit: OnEdit, ) -> Self where OnEdit: Fn() + 'static, { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_hexpand(true); s.append(&CommandRow::new(on_edit)); /* let click_controller = gtk::GestureClick::new(); click_controller.connect_released({ let s = s.clone(); move |_, _, _, _| { println!("clicked outside of focusable entity"); if let Some(widget) = s.focus_child().and_downcast_ref::() { println!("focused child is the weight view"); widget.blur(); } } }); s.add_controller(click_controller); */ let weight_record = records.iter().find_map(|record| match record { Record { id, data: ft_core::TraxRecord::Weight(record), } => Some((id.clone(), record.clone())), _ => None, }); let weight_view = match weight_record { Some((id, data)) => Weight::new(Some(data.clone())), None => Weight::new(None), }; s.append(&weight_view.widget()); records.into_iter().for_each(|record| { let record_view = match record { Record { data: ft_core::TraxRecord::BikeRide(record), .. } => Some( TimeDistanceView::new(ft_core::RecordType::BikeRide, record) .upcast::(), ), Record { data: ft_core::TraxRecord::Row(record), .. } => Some( TimeDistanceView::new(ft_core::RecordType::Row, record).upcast::(), ), Record { data: ft_core::TraxRecord::Run(record), .. } => Some( TimeDistanceView::new(ft_core::RecordType::Row, record).upcast::(), ), Record { data: ft_core::TraxRecord::Swim(record), .. } => Some( TimeDistanceView::new(ft_core::RecordType::Row, record).upcast::(), ), Record { data: ft_core::TraxRecord::Walk(record), .. } => Some( TimeDistanceView::new(ft_core::RecordType::Row, record).upcast::(), ), _ => None, }; if let Some(record_view) = record_view { record_view.add_css_class("day-detail"); record_view.set_halign(gtk::Align::Start); s.append(&record_view); } }); s } } pub struct DayEditPrivate { date: gtk::Label, weight: WeightEdit, } impl Default for DayEditPrivate { fn default() -> Self { Self { date: gtk::Label::new(None), weight: WeightEdit::new(None), } } } #[glib::object_subclass] impl ObjectSubclass for DayEditPrivate { const NAME: &'static str = "DayEdit"; type Type = DayEdit; type ParentType = gtk::Box; } impl ObjectImpl for DayEditPrivate {} impl WidgetImpl for DayEditPrivate {} impl BoxImpl for DayEditPrivate {} glib::wrapper! { pub struct DayEdit(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } impl DayEdit { pub fn new( date: chrono::NaiveDate, records: Vec>, on_put_record: PutRecordFn, on_update_record: UpdateRecordFn, ) -> Self where PutRecordFn: Fn(ft_core::TraxRecord) + 'static, UpdateRecordFn: Fn(Record) + 'static, { let s: Self = Object::builder().build(); /*, move |weight| { on_update_record(Record { id: id.clone(), data: ft_core::TraxRecord::Weight(ft_core::Weight { date, weight }), }) }*/ /*, move |weight| { on_put_record(ft_core::TraxRecord::Weight(ft_core::Weight { date, weight, })); }*/ let weight_record = records.iter().find_map(|record| match record { Record { id, data: ft_core::TraxRecord::Weight(record), } => Some((id.clone(), record.clone())), _ => None, }); let weight_view = match weight_record { Some((_id, data)) => WeightEdit::new(Some(data.clone())), None => WeightEdit::new(None), }; s.append(&weight_view.widget()); s } }