diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index 3507182..a3111a7 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Fit mod ui; use adw::prelude::*; -use emseries::Series; +use emseries::{EmseriesReadError, Series}; use ft_core::TraxRecord; use gio::resources_lookup_data; use glib::Object; @@ -36,6 +36,14 @@ const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax"; const RESOURCE_BASE_PATH: &str = "/com/luminescent-dreams/fitnesstrax/"; +/// A set of events that can occur at the global application level. These events should represent +/// significant state changes that should go through a central dispatcher. +enum Events { + DatabaseChanged(Series), +} +// Note that I have not yet figured out the communication channel or how the central dispatcher +// should work. There's a dance between the App and the AppWindow that I haven't figured out yet. + /// The real, headless application. This is where all of the logic will reside. #[derive(Clone)] struct App { @@ -50,6 +58,34 @@ impl App { } } +pub struct PlaceholderViewPrivate {} + +#[glib::object_subclass] +impl ObjectSubclass for PlaceholderViewPrivate { + const NAME: &'static str = "PlaceholderView"; + type Type = PlaceholderView; + type ParentType = gtk::Box; + + fn new() -> Self { + Self {} + } +} + +impl ObjectImpl for PlaceholderViewPrivate {} +impl WidgetImpl for PlaceholderViewPrivate {} +impl BoxImpl for PlaceholderViewPrivate {} + +glib::wrapper! { + pub struct PlaceholderView(ObjectSubclass) @extends gtk::Box, gtk::Widget; +} + +impl PlaceholderView { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + s + } +} + /// This is the view to show if the application has not yet been configured. It will walk the user /// through the most critical setup steps so that we can move on to the other views in the app. pub struct WelcomeViewPrivate {} @@ -74,7 +110,7 @@ glib::wrapper! { } impl WelcomeView { - pub fn new(on_save: F) -> Self + pub fn new(on_save: Box) -> Self where F: Fn(PathBuf) + 'static, { @@ -111,7 +147,7 @@ impl WelcomeView { content.append(>k::Label::new(Some("Welcome to FitnessTrax. The application has not yet been configured, so I will walk you through that. Let's start out by selecting your database."))); content.append(&db_row); - let on_save = Box::new(on_save); + let on_save = on_save; save_button.connect_clicked({ move |_| { if let Some(path) = db_row.path() { @@ -166,11 +202,12 @@ impl HistoricalView { /// The application window, or the main window, is the main user interface for the app. Almost /// everything occurs here. +#[derive(Clone)] struct AppWindow { app: App, window: adw::ApplicationWindow, layout: gtk::Box, - current_view: RefCell, + current_view: Rc>, } impl AppWindow { @@ -187,12 +224,6 @@ impl AppWindow { .height_request(600) .build(); - let current_view = if app.database.read().unwrap().is_none() { - WelcomeView::new(&|_| {}).upcast() - } else { - HistoricalView::new().upcast() - }; - let stylesheet = String::from_utf8( resources_lookup_data( &format!("{}style.css", RESOURCE_BASE_PATH), @@ -216,18 +247,77 @@ impl AppWindow { let layout = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); + + let initial_view = PlaceholderView::new(); + layout.append(&header); - layout.append(¤t_view); + layout.append(&initial_view); window.set_content(Some(&layout)); window.present(); let s = Self { - app, + app: app.clone(), window, layout, - current_view: RefCell::new(current_view), + current_view: Rc::new(RefCell::new(initial_view.upcast())), }; + + let initial_view = if app.database.read().unwrap().is_none() { + WelcomeView::new({ + let app = app.clone(); + let s = s.clone(); + Box::new(move |path: PathBuf| { + // The user has selected a path. Perhaps the path is new, perhaps it already + // exists. + // + // If the file exists already, attempt to read it. Fail if that doesn't work. + // A should show to the user something that indicates that the file exists but is + // not already a database. + // + // If the file does not exist, create a new one. Again, show the user an error if + // some kind of error occurs. + if path.exists() { + let db = Series::open(&path); + match db { + Ok(db) => { + *app.database.write().unwrap() = Some(db); + s.change_view(HistoricalView::new().upcast()); + } + Err(EmseriesReadError::UUIDParseError(_)) => { + println!("Invalid UUID detected in the file") + } + Err(EmseriesReadError::JSONParseError(_)) => { + println!("The file cannot be parsed and may not be a database") + } + Err(EmseriesReadError::IOError(err)) => { + println!("The file cannot be read: {}", err) + } + } + } else { + let db = Series::open(&path); + match db { + Ok(db) => { + *app.database.write().unwrap() = Some(db); + s.change_view(HistoricalView::new().upcast()); + } + Err(EmseriesReadError::IOError(err)) => { + println!("The file cannot be read: {}", err) + } + _ => unreachable!( + "other error types should not be possible when creating a new DB" + ), + } + } + }) + }) + .upcast() + } else { + HistoricalView::new().upcast() + }; + + s.change_view(initial_view); + s }