From 0dd0a5f7cc24a94e017ac617e17577543a377821 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 18 Dec 2023 20:04:55 -0500 Subject: [PATCH 1/7] Set up some of the content of the welcome view --- fitnesstrax/app/src/main.rs | 44 +++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index dcb3748..e337a81 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -46,12 +46,12 @@ impl App { /// 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 UnconfiguredViewPrivate {} +pub struct WelcomeViewPrivate {} #[glib::object_subclass] -impl ObjectSubclass for UnconfiguredViewPrivate { - const NAME: &'static str = "UnconfiguredView"; - type Type = UnconfiguredView; +impl ObjectSubclass for WelcomeViewPrivate { + const NAME: &'static str = "WelcomeView"; + type Type = WelcomeView; type ParentType = gtk::Box; fn new() -> Self { @@ -59,24 +59,46 @@ impl ObjectSubclass for UnconfiguredViewPrivate { } } -impl ObjectImpl for UnconfiguredViewPrivate {} -impl WidgetImpl for UnconfiguredViewPrivate {} -impl BoxImpl for UnconfiguredViewPrivate {} +impl ObjectImpl for WelcomeViewPrivate {} +impl WidgetImpl for WelcomeViewPrivate {} +impl BoxImpl for WelcomeViewPrivate {} glib::wrapper! { - pub struct UnconfiguredView(ObjectSubclass) @extends gtk::Box, gtk::Widget; + pub struct WelcomeView(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; } -impl UnconfiguredView { +impl WelcomeView { pub fn new() -> Self { let s: Self = Object::builder().build(); + s.set_orientation(gtk::Orientation::Vertical); // Replace this with the welcome screen that we set up in the fitnesstrax/unconfigured-page // branch. let label = gtk::Label::builder() - .label("Database is not configured.") + .label("Welcome to FitnessTrax") .build(); + s.append(&label); + + s.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."))); + + // The database selection row should be a box that shows a default database path, along with a + // button that triggers a file chooser dialog. Once the dialog returns, the box should be + // updated to reflect the chosen path. + let db_row = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .build(); + db_row.append( + >k::Label::builder() + .label("No Path Selected") + .hexpand(true) + .build(), + ); + db_row.append(>k::Button::builder().label("Select Database").build()); + + s.append(&db_row); + s.append(>k::Button::builder().label("Save Settings").build()); + s } } @@ -141,7 +163,7 @@ impl AppWindow { .build(); let current_view = if app.database.read().unwrap().is_none() { - UnconfiguredView::new().upcast() + WelcomeView::new().upcast() } else { HistoricalView::new().upcast() }; -- 2.44.1 From 38db3d6780b25de87802490f44ccade3c7c578a7 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 18 Dec 2023 21:14:08 -0500 Subject: [PATCH 2/7] Elaborate upon and format the welcome dialog --- fitnesstrax/app/Cargo.toml | 2 +- fitnesstrax/app/src/main.rs | 34 +++++++------ fitnesstrax/app/src/ui/mod.rs | 91 +++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 fitnesstrax/app/src/ui/mod.rs diff --git a/fitnesstrax/app/Cargo.toml b/fitnesstrax/app/Cargo.toml index 470ecea..66f08ef 100644 --- a/fitnesstrax/app/Cargo.toml +++ b/fitnesstrax/app/Cargo.toml @@ -11,7 +11,7 @@ emseries = { path = "../../emseries" } ft-core = { path = "../core" } gio = { version = "0.18" } glib = { version = "0.18" } -gtk = { version = "0.7", package = "gtk4", features = [ "v4_8" ] } +gtk = { version = "0.7", package = "gtk4", features = [ "v4_10" ] } tokio = { version = "1.34", features = [ "full" ] } [build-dependencies] diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index e337a81..c67024e 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -13,6 +13,9 @@ General Public License for more details. You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see . */ + +mod ui; + use adw::prelude::*; use emseries::Series; use ft_core::TraxRecord; @@ -24,6 +27,7 @@ use std::{ env, sync::{Arc, RwLock}, }; +use ui::FileChooserRow; const APP_ID_DEV: &str = "com.luminescent-dreams.fitnesstrax.dev"; const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax"; @@ -71,32 +75,32 @@ impl WelcomeView { pub fn new() -> Self { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); + s.set_css_classes(&["modal"]); // Replace this with the welcome screen that we set up in the fitnesstrax/unconfigured-page // branch. - let label = gtk::Label::builder() + let title = gtk::Label::builder() .label("Welcome to FitnessTrax") + .css_classes(["modal-title"]) + .build(); + s.append(&title); + + let content = gtk::Box::builder() + .css_classes(["model-content"]) + .orientation(gtk::Orientation::Vertical) + .vexpand(true) .build(); - s.append(&label); - - s.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(>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."))); // The database selection row should be a box that shows a default database path, along with a // button that triggers a file chooser dialog. Once the dialog returns, the box should be // updated to reflect the chosen path. - let db_row = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .build(); - db_row.append( - >k::Label::builder() - .label("No Path Selected") - .hexpand(true) - .build(), - ); - db_row.append(>k::Button::builder().label("Select Database").build()); + let db_row = FileChooserRow::new(); - s.append(&db_row); + content.append(&db_row); + + s.append(&content); s.append(>k::Button::builder().label("Save Settings").build()); s diff --git a/fitnesstrax/app/src/ui/mod.rs b/fitnesstrax/app/src/ui/mod.rs new file mode 100644 index 0000000..662d1e1 --- /dev/null +++ b/fitnesstrax/app/src/ui/mod.rs @@ -0,0 +1,91 @@ +/* +Copyright 2023, 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 glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, path::PathBuf}; + +pub struct FileChooserRowPrivate { + path: RefCell>, + label: gtk::Label, +} + +#[glib::object_subclass] +impl ObjectSubclass for FileChooserRowPrivate { + const NAME: &'static str = "FileChooser"; + type Type = FileChooserRow; + type ParentType = gtk::Box; + + fn new() -> Self { + Self { + path: RefCell::new(None), + label: gtk::Label::builder().hexpand(true).build(), + } + } +} + +impl ObjectImpl for FileChooserRowPrivate {} +impl WidgetImpl for FileChooserRowPrivate {} +impl BoxImpl for FileChooserRowPrivate {} + +glib::wrapper! { + pub struct FileChooserRow(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl FileChooserRow { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + + s.set_orientation(gtk::Orientation::Horizontal); + + // The database selection row should be a box that shows a default database path, along with a + // button that triggers a file chooser dialog. Once the dialog returns, the box should be + // updated to reflect the chosen path. + s.imp().label.set_text("No database selected"); + + let db_file_chooser_button = gtk::Button::builder().label("Select Database").build(); + db_file_chooser_button.connect_clicked({ + let s = s.clone(); + move |_| { + let no_window: Option<>k::Window> = None; + let not_cancellable: Option<&gio::Cancellable> = None; + let s = s.clone(); + gtk::FileDialog::builder().build().open( + no_window, + not_cancellable, + move |file_id| match file_id { + Ok(file_id) => match file_id.path() { + Some(path) => { + s.imp().label.set_text(path.to_str().unwrap()); + *s.imp().path.borrow_mut() = Some(path); + } + None => { + *s.imp().path.borrow_mut() = None; + s.imp().label.set_text("No database selected"); + } + }, + Err(err) => println!("file opening failed: {}", err), + }, + ); + } + }); + + s.append(&s.imp().label); + s.append(&db_file_chooser_button); + + s + } +} -- 2.44.1 From 104ffc5782939fd77187348d11dec6c889b244fb Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 19 Dec 2023 00:31:36 -0500 Subject: [PATCH 3/7] Set up callbacks to make the save button sensitive to the file selection --- fitnesstrax/app/src/main.rs | 33 +++++++++++++++++++++++++++------ fitnesstrax/app/src/ui/mod.rs | 18 ++++++++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index c67024e..3eb3bc9 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -25,6 +25,8 @@ use gtk::{subclass::prelude::*, STYLE_PROVIDER_PRIORITY_USER}; use std::{ cell::RefCell, env, + path::PathBuf, + rc::Rc, sync::{Arc, RwLock}, }; use ui::FileChooserRow; @@ -72,7 +74,10 @@ glib::wrapper! { } impl WelcomeView { - pub fn new() -> Self { + pub fn new(on_save: F) -> Self + where + F: Fn(PathBuf) + 'static, + { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); s.set_css_classes(&["modal"]); @@ -83,7 +88,6 @@ impl WelcomeView { .label("Welcome to FitnessTrax") .css_classes(["modal-title"]) .build(); - s.append(&title); let content = gtk::Box::builder() .css_classes(["model-content"]) @@ -91,17 +95,34 @@ impl WelcomeView { .vexpand(true) .build(); - 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."))); + let save_button = gtk::Button::builder() + .label("Save Settings") + .sensitive(false) + .build(); // The database selection row should be a box that shows a default database path, along with a // button that triggers a file chooser dialog. Once the dialog returns, the box should be // updated to reflect the chosen path. - let db_row = FileChooserRow::new(); + let db_row = FileChooserRow::new({ + let save_button = save_button.clone(); + move |_| save_button.set_sensitive(true) + }); + 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); + save_button.connect_clicked({ + move |_| { + if let Some(path) = db_row.path() { + on_save(path) + } + } + }); + + s.append(&title); s.append(&content); - s.append(>k::Button::builder().label("Save Settings").build()); + s.append(&save_button); s } @@ -167,7 +188,7 @@ impl AppWindow { .build(); let current_view = if app.database.read().unwrap().is_none() { - WelcomeView::new().upcast() + WelcomeView::new(&|_| {}).upcast() } else { HistoricalView::new().upcast() }; diff --git a/fitnesstrax/app/src/ui/mod.rs b/fitnesstrax/app/src/ui/mod.rs index 662d1e1..fef79fb 100644 --- a/fitnesstrax/app/src/ui/mod.rs +++ b/fitnesstrax/app/src/ui/mod.rs @@ -16,7 +16,11 @@ You should have received a copy of the GNU General Public License along with Fit use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; -use std::{cell::RefCell, path::PathBuf}; +use std::{ + cell::RefCell, + path::{Path, PathBuf}, + rc::Rc, +}; pub struct FileChooserRowPrivate { path: RefCell>, @@ -46,7 +50,10 @@ glib::wrapper! { } impl FileChooserRow { - pub fn new() -> Self { + pub fn new(on_selected: F) -> Self + where + F: Fn(PathBuf) + 'static, + { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Horizontal); @@ -57,12 +64,14 @@ impl FileChooserRow { s.imp().label.set_text("No database selected"); let db_file_chooser_button = gtk::Button::builder().label("Select Database").build(); + let on_selected = Rc::new(Box::new(on_selected)); db_file_chooser_button.connect_clicked({ let s = s.clone(); move |_| { let no_window: Option<>k::Window> = None; let not_cancellable: Option<&gio::Cancellable> = None; let s = s.clone(); + let on_selected = on_selected.clone(); gtk::FileDialog::builder().build().open( no_window, not_cancellable, @@ -70,6 +79,7 @@ impl FileChooserRow { Ok(file_id) => match file_id.path() { Some(path) => { s.imp().label.set_text(path.to_str().unwrap()); + on_selected(path.clone()); *s.imp().path.borrow_mut() = Some(path); } None => { @@ -88,4 +98,8 @@ impl FileChooserRow { s } + + pub fn path(&self) -> Option { + self.imp().path.borrow().clone() + } } -- 2.44.1 From db188ea75ab19516345e5d83afae07c08b172bb0 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 19 Dec 2023 00:37:51 -0500 Subject: [PATCH 4/7] Allow the user to create a new file --- fitnesstrax/app/src/ui/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fitnesstrax/app/src/ui/mod.rs b/fitnesstrax/app/src/ui/mod.rs index fef79fb..2327823 100644 --- a/fitnesstrax/app/src/ui/mod.rs +++ b/fitnesstrax/app/src/ui/mod.rs @@ -72,7 +72,7 @@ impl FileChooserRow { let not_cancellable: Option<&gio::Cancellable> = None; let s = s.clone(); let on_selected = on_selected.clone(); - gtk::FileDialog::builder().build().open( + gtk::FileDialog::builder().build().save( no_window, not_cancellable, move |file_id| match file_id { -- 2.44.1 From beedeba8dc1d060c658039bc4c0ac2797e565a78 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 19 Dec 2023 10:10:02 -0500 Subject: [PATCH 5/7] Style the welcome screen --- fitnesstrax/app/resources/style.css | 21 +++++++++ fitnesstrax/app/src/main.rs | 4 +- fitnesstrax/app/src/ui/mod.rs | 66 ++++++++++++++++++++--------- 3 files changed, 69 insertions(+), 22 deletions(-) diff --git a/fitnesstrax/app/resources/style.css b/fitnesstrax/app/resources/style.css index e69de29..314fd86 100644 --- a/fitnesstrax/app/resources/style.css +++ b/fitnesstrax/app/resources/style.css @@ -0,0 +1,21 @@ +.welcome { + margin: 64px; +} + +.welcome-title { + font-size: larger; + padding: 8px; +} + +.welcome-content { + padding: 8px; +} + +.welcome-footer { +} + +.dialog-row { + margin: 8px 0px 8px 0px; + padding: 8px; +} + diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index 3eb3bc9..3507182 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -80,13 +80,13 @@ impl WelcomeView { { let s: Self = Object::builder().build(); s.set_orientation(gtk::Orientation::Vertical); - s.set_css_classes(&["modal"]); + s.set_css_classes(&["welcome"]); // Replace this with the welcome screen that we set up in the fitnesstrax/unconfigured-page // branch. let title = gtk::Label::builder() .label("Welcome to FitnessTrax") - .css_classes(["modal-title"]) + .css_classes(["welcome-title"]) .build(); let content = gtk::Box::builder() diff --git a/fitnesstrax/app/src/ui/mod.rs b/fitnesstrax/app/src/ui/mod.rs index 2327823..14f1243 100644 --- a/fitnesstrax/app/src/ui/mod.rs +++ b/fitnesstrax/app/src/ui/mod.rs @@ -56,45 +56,71 @@ impl FileChooserRow { { let s: Self = Object::builder().build(); + s.set_css_classes(&["dialog-row", "card"]); s.set_orientation(gtk::Orientation::Horizontal); + s.set_spacing(8); // The database selection row should be a box that shows a default database path, along with a // button that triggers a file chooser dialog. Once the dialog returns, the box should be // updated to reflect the chosen path. s.imp().label.set_text("No database selected"); - let db_file_chooser_button = gtk::Button::builder().label("Select Database").build(); let on_selected = Rc::new(Box::new(on_selected)); - db_file_chooser_button.connect_clicked({ + + let import_button = gtk::Button::builder().label("Import a Database").build(); + + let handle_file_selection = Rc::new(Box::new({ let s = s.clone(); + let on_selected = on_selected.clone(); + move |file_id: Result| match file_id { + Ok(file_id) => match file_id.path() { + Some(path) => { + s.imp().label.set_text(path.to_str().unwrap()); + on_selected(path.clone()); + *s.imp().path.borrow_mut() = Some(path); + } + None => { + *s.imp().path.borrow_mut() = None; + s.imp().label.set_text("No database selected"); + } + }, + Err(err) => println!("file opening failed: {}", err), + } + })); + + import_button.connect_clicked({ + let handle_file_selection = handle_file_selection.clone(); move |_| { let no_window: Option<>k::Window> = None; let not_cancellable: Option<&gio::Cancellable> = None; - let s = s.clone(); - let on_selected = on_selected.clone(); - gtk::FileDialog::builder().build().save( + let handle_file_selection = handle_file_selection.clone(); + gtk::FileDialog::builder().build().open( no_window, not_cancellable, - move |file_id| match file_id { - Ok(file_id) => match file_id.path() { - Some(path) => { - s.imp().label.set_text(path.to_str().unwrap()); - on_selected(path.clone()); - *s.imp().path.borrow_mut() = Some(path); - } - None => { - *s.imp().path.borrow_mut() = None; - s.imp().label.set_text("No database selected"); - } - }, - Err(err) => println!("file opening failed: {}", err), - }, + move |file_id| handle_file_selection(file_id), ); } }); + let new_button = gtk::Button::builder().label("Create Database").build(); + new_button.connect_clicked({ + let handle_file_selection = handle_file_selection.clone(); + move |_| { + let no_window: Option<>k::Window> = None; + let not_cancellable: Option<&gio::Cancellable> = None; + let handle_file_selection = handle_file_selection.clone(); + gtk::FileDialog::builder().build().save( + no_window, + not_cancellable, + move |file_id| handle_file_selection(file_id), + ); + } + }); + + s.imp().label.set_halign(gtk::Align::Start); s.append(&s.imp().label); - s.append(&db_file_chooser_button); + s.append(&import_button); + s.append(&new_button); s } -- 2.44.1 From 50268ffadcabb673290a0f499e0ef83f6efa6bac Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 19 Dec 2023 10:46:53 -0500 Subject: [PATCH 6/7] Actually be able to open the database --- fitnesstrax/app/src/main.rs | 116 ++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 13 deletions(-) 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 } -- 2.44.1 From 87994012fa5f97b77938148ff0c848fd19211a98 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 19 Dec 2023 10:59:33 -0500 Subject: [PATCH 7/7] Save the database path to settings and attempt to open the database on start --- fitnesstrax/app/src/main.rs | 60 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/fitnesstrax/app/src/main.rs b/fitnesstrax/app/src/main.rs index a3111a7..1eb412e 100644 --- a/fitnesstrax/app/src/main.rs +++ b/fitnesstrax/app/src/main.rs @@ -25,7 +25,7 @@ use gtk::{subclass::prelude::*, STYLE_PROVIDER_PRIORITY_USER}; use std::{ cell::RefCell, env, - path::PathBuf, + path::{Path, PathBuf}, rc::Rc, sync::{Arc, RwLock}, }; @@ -47,14 +47,32 @@ enum Events { /// The real, headless application. This is where all of the logic will reside. #[derive(Clone)] struct App { + settings: gio::Settings, database: Arc>>>, } impl App { - pub fn new() -> Self { - Self { + pub fn new(settings: gio::Settings) -> Self { + let s = Self { + settings, database: Arc::new(RwLock::new(None)), + }; + + if !s.settings.string("series-path").is_empty() { + let path = PathBuf::from(s.settings.string("series-path")); + let db = Series::open(path).unwrap(); + *s.database.write().unwrap() = Some(db); } + + s + } + + pub fn open_db(&self, path: &Path) { + let db = Series::open(path).unwrap(); + *self.database.write().unwrap() = Some(db); + self.settings + .set_string("series-path", path.to_str().unwrap()) + .unwrap(); } } @@ -277,38 +295,8 @@ impl AppWindow { // // 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" - ), - } - } + app.open_db(&path); + s.change_view(HistoricalView::new().upcast()); }) }) .upcast() @@ -352,7 +340,7 @@ fn main() { println!("database path: {}", settings.string("series-path")); - let app = App::new(); + let app = App::new(settings); /* let runtime = tokio::runtime::Builder::new_multi_thread() -- 2.44.1