/* 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 . */ mod app; mod app_window; mod components; mod views; use adw::prelude::*; use app_window::AppWindow; use std::{env, path::PathBuf}; const APP_ID_DEV: &str = "com.luminescent-dreams.fitnesstrax.dev"; const APP_ID_PROD: &str = "com.luminescent-dreams.fitnesstrax"; const RESOURCE_BASE_PATH: &str = "/com/luminescent-dreams/fitnesstrax/"; fn main() { // I still don't fully understand gio resources. resources_register_include! is convenient // because I don't have to deal with filesystem locations at runtime. However, I think other // GTK applications do that rather than compiling the resources directly into the app. So, I'm // unclear as to how I want to handle this. gio::resources_register_include!("com.luminescent-dreams.fitnesstrax.gresource") .expect("to register resources"); let app_id = if std::env::var_os("ENV") == Some("dev".into()) { APP_ID_DEV } else { APP_ID_PROD }; let settings = gio::Settings::new(app_id); let app = app::App::new({ let path = settings.string("series-path"); if path.is_empty() { None } else { Some(PathBuf::from(path)) } }); let adw_app = adw::Application::builder() .application_id(app_id) .resource_base_path(RESOURCE_BASE_PATH) .build(); let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); adw_app.connect_activate(move |adw_app| { // These channels are used to send messages to the UI. Anything that needs to send a // message to the UI will send it via `ui_tx`. We will have one single process that owns // `ui_rx`. That process will read messages coming in and send them to [AppWindow] for proper // processing. // // The core app will usually only send messages in response to a request, but this channel // can also be used to tell the UI that something happened in the background, such as // detecting a watch, detecting new tracks to import, and so forth. let (ui_tx, ui_rx) = async_channel::unbounded::(); // These channels are used for communicating with the app. Already I can see that a lot of // different event handlers will need copies of app_tx in order to send requests into the // UI. let (app_tx, app_rx) = async_channel::unbounded::(); let window = AppWindow::new(app_id, RESOURCE_BASE_PATH, adw_app, app_tx.clone()); // Spawn a future where the UI will receive messages for the app window. Previously, this // would have been done by creating a glib::MainContext::channel(), but that has been // deprecated since gtk 4.10 in favor of using `async_channel`. glib::spawn_future_local(async move { // The app requests data to start with. This kicks everything off. The response from // the app will cause the window to be updated shortly. let _ = app_tx.send(app::AppInvocation::RequestRecords).await; while let Ok(response) = ui_rx.recv().await { window.process_response(response); } }); // The tokio runtime starts up here and will handle all of the asynchronous operations that // the application needs to do. Messages arrive on `app_rx` and responses will be sent via // `ui_tx`. runtime.spawn({ let app = app.clone(); async move { while let Ok(invocation) = app_rx.recv().await { let response = app.process_invocation(invocation).await; let _ = ui_tx.send(response).await; } } }); }); let args: Vec = env::args().collect(); ApplicationExtManual::run_with_args(&adw_app, &args); }