//! What the app says about itself, and the one preference that lives beside it. //! //! //! //! The version and the platform are host facts, resolved at startup by the //! thing that can resolve them and held on [`AppState`]: a host fact a //! described screen needs is a host fact the app puts in `S` while it still has //! a handle to ask. //! //! [`About::platform`](crate::state::About::platform) is `std::env::consts`, so //! it says `linux aarch64` rather than a browser's guess at `Linux`. //! //! # Why the preference is not a `user_config` key //! //! "Check for updates on launch" is in `preferences.json` rather than the //! database, and `commands::preferences` says why: it is read before the //! database is open, and a launch-time check cannot wait for a connection. The //! described handler reads and writes that file through //! [`AppState::config_dir`](crate::state::AppState::config_dir), which is the //! path resolved at startup for exactly this. //! //! A host that would not give up a config directory leaves `config_dir` as //! `None`. The section then shows the default and refuses the write with a //! complaint, rather than inventing a path and writing somewhere nobody reads. //! //! # What is not here //! //! **The biometric app-lock row.** It is offered only where the OS can answer //! the prompt, which is a live question to a mobile platform rather than a fact //! a startup can resolve and hold: biometry can be enrolled and unenrolled //! while the app runs. Offering a toggle that cannot work is worse than not //! offering one. Rebuilding the row means rebuilding the plugin, command and //! preference behind it. //! //! **The Keyboard Shortcuts button.** There is no described help screen to open //! and no chrome binding to open it with; see [`super::super::shell`], and //! quasicoherent `858be2a6` for the container an overlay needs. //! //! **Re-run the first-run welcome.** Nothing describes it. use quasi_declare::declare; use quasi_router::screen::Choice; use quasi_router::{Action, RouteError}; use crate::commands::Preferences; use crate::state::AppState; /// The key the update-check toggle sends under. pub(super) const UPDATE_CHECK: &str = "update_check_on_launch"; /// The seven identity rows, in the order the shipped screen listed them. /// /// Named members rather than a tuple, for `policy`'s reason: a description /// names what it draws, and `.1` is not a name. struct Fact { /// What the row is called. label: &'static str, /// What it says. value: &'static str, } /// The five that are the same on every machine. const FIXED: &[Fact] = &[ Fact { label: "Publisher", value: "Make Creative, LLC", }, Fact { label: "License", value: "PolyForm Noncommercial 1.0.0", }, Fact { label: "Contact", value: "info@makenot.work", }, Fact { label: "Source", value: "makenot.work", }, Fact { label: "Privacy", value: "makenot.work/policy", }, ]; /// Which option the update-check picker opens on. /// /// A supplier because the choice is a branch on a `bool` and the form admits no /// expression in an argument. It hands back a `&'static str`, which keeps it /// out of the population. fn update_choice(prefs: &Preferences) -> &'static str { if prefs.update_check_on_launch { "enabled" } else { "disabled" } } declare! { /// The facts, then the preference. /// /// A list of rows rather than a `dl`, because a description has no word for /// a definition list and does not need one: each row is a label and a /// value, which is what `meta` already is. The renderer decides whether /// that draws as two columns. /// /// An On/Off choice rather than a toggle kind, which is how every other /// boolean on this screen is said (`plan_nudges`, `review_nudges`). One /// shape for one question, and the renderer decides whether that draws as a /// switch. pub(super) shape pane(state: &AppState) -> Vec; let prefs = read(state); section "About GoingsOn"; text "Tasks, email, calendar, contacts."; list { // The two the host resolved at startup, then the five that are the same // wherever it runs. row "Version" { meta state.about.version.clone(); } row "Platform" { meta state.about.platform.clone(); } for fact in FIXED { row fact.label { meta fact.value; } } } field Select UPDATE_CHECK "Check for updates on launch" { option Choice::new("enabled", "Enabled (default)"); option Choice::new("disabled", "Disabled"); value update_choice(&prefs); hint "When a new signed release is available, a banner appears in the app. \ Install is always user-initiated."; writes Action::post("/settings/about/update-check"); } } /// The preferences as they stand, or the defaults. fn read(state: &AppState) -> Preferences { state .config_dir .as_ref() .map_or_else(Preferences::default, |dir| { crate::commands::load_at(&crate::commands::path_in(dir)) }) } /// Write the update-check preference. /// /// Answers a complaint rather than a fault when there is nowhere to write: /// a host with no config directory is a host this app cannot remember anything /// on, and that is worth saying to the person who just pressed the switch. pub(super) fn write(state: &AppState, on: bool) -> Result<&'static str, RouteError> { let Some(dir) = state.config_dir.as_ref() else { return Err(RouteError::internal( "This machine did not give the app a config directory, so the setting cannot be saved.", )); }; let path = crate::commands::path_in(dir); let mut prefs = crate::commands::load_at(&path); prefs.update_check_on_launch = on; crate::commands::save_at(&path, &prefs) .map_err(|error| RouteError::internal(error.to_string()))?; Ok(if on { "Update checks on." } else { "Update checks off." }) } #[cfg(test)] mod tests;