Skip to main content

max / goingson

6.1 KB · 179 lines History Blame Raw
1 //! What the app says about itself, and the one preference that lives beside it.
2 //!
3 //! <!-- wiki: quasi-overview -->
4 //!
5 //! The version and the platform are host facts, resolved at startup by the
6 //! thing that can resolve them and held on [`AppState`]: a host fact a
7 //! described screen needs is a host fact the app puts in `S` while it still has
8 //! a handle to ask.
9 //!
10 //! [`About::platform`](crate::state::About::platform) is `std::env::consts`, so
11 //! it says `linux aarch64` rather than a browser's guess at `Linux`.
12 //!
13 //! # Why the preference is not a `user_config` key
14 //!
15 //! "Check for updates on launch" is in `preferences.json` rather than the
16 //! database, and `commands::preferences` says why: it is read before the
17 //! database is open, and a launch-time check cannot wait for a connection. The
18 //! described handler reads and writes that file through
19 //! [`AppState::config_dir`](crate::state::AppState::config_dir), which is the
20 //! path resolved at startup for exactly this.
21 //!
22 //! A host that would not give up a config directory leaves `config_dir` as
23 //! `None`. The section then shows the default and refuses the write with a
24 //! complaint, rather than inventing a path and writing somewhere nobody reads.
25 //!
26 //! # What is not here
27 //!
28 //! **The biometric app-lock row.** It is offered only where the OS can answer
29 //! the prompt, which is a live question to a mobile platform rather than a fact
30 //! a startup can resolve and hold: biometry can be enrolled and unenrolled
31 //! while the app runs. Offering a toggle that cannot work is worse than not
32 //! offering one. Rebuilding the row means rebuilding the plugin, command and
33 //! preference behind it.
34 //!
35 //! **The Keyboard Shortcuts button.** There is no described help screen to open
36 //! and no chrome binding to open it with; see [`super::super::shell`], and
37 //! quasicoherent `858be2a6` for the container an overlay needs.
38 //!
39 //! **Re-run the first-run welcome.** Nothing describes it.
40
41 use quasi_declare::declare;
42 use quasi_router::screen::Choice;
43 use quasi_router::{Action, RouteError};
44
45 use crate::commands::Preferences;
46 use crate::state::AppState;
47
48 /// The key the update-check toggle sends under.
49 pub(super) const UPDATE_CHECK: &str = "update_check_on_launch";
50
51 /// The seven identity rows, in the order the shipped screen listed them.
52 ///
53 /// Named members rather than a tuple, for `policy`'s reason: a description
54 /// names what it draws, and `.1` is not a name.
55 struct Fact {
56 /// What the row is called.
57 label: &'static str,
58 /// What it says.
59 value: &'static str,
60 }
61
62 /// The five that are the same on every machine.
63 const FIXED: &[Fact] = &[
64 Fact {
65 label: "Publisher",
66 value: "Make Creative, LLC",
67 },
68 Fact {
69 label: "License",
70 value: "PolyForm Noncommercial 1.0.0",
71 },
72 Fact {
73 label: "Contact",
74 value: "info@makenot.work",
75 },
76 Fact {
77 label: "Source",
78 value: "makenot.work",
79 },
80 Fact {
81 label: "Privacy",
82 value: "makenot.work/policy",
83 },
84 ];
85
86 /// Which option the update-check picker opens on.
87 ///
88 /// A supplier because the choice is a branch on a `bool` and the form admits no
89 /// expression in an argument. It hands back a `&'static str`, which keeps it
90 /// out of the population.
91 fn update_choice(prefs: &Preferences) -> &'static str {
92 if prefs.update_check_on_launch {
93 "enabled"
94 } else {
95 "disabled"
96 }
97 }
98
99 declare! {
100 /// The facts, then the preference.
101 ///
102 /// A list of rows rather than a `dl`, because a description has no word for
103 /// a definition list and does not need one: each row is a label and a
104 /// value, which is what `meta` already is. The renderer decides whether
105 /// that draws as two columns.
106 ///
107 /// An On/Off choice rather than a toggle kind, which is how every other
108 /// boolean on this screen is said (`plan_nudges`, `review_nudges`). One
109 /// shape for one question, and the renderer decides whether that draws as a
110 /// switch.
111 pub(super) shape pane(state: &AppState) -> Vec<Node>;
112
113 let prefs = read(state);
114
115 section "About GoingsOn";
116 text "Tasks, email, calendar, contacts.";
117
118 list {
119 // The two the host resolved at startup, then the five that are the same
120 // wherever it runs.
121 row "Version" {
122 meta state.about.version.clone();
123 }
124 row "Platform" {
125 meta state.about.platform.clone();
126 }
127 for fact in FIXED {
128 row fact.label {
129 meta fact.value;
130 }
131 }
132 }
133
134 field Select UPDATE_CHECK "Check for updates on launch" {
135 option Choice::new("enabled", "Enabled (default)");
136 option Choice::new("disabled", "Disabled");
137 value update_choice(&prefs);
138 hint "When a new signed release is available, a banner appears in the app. \
139 Install is always user-initiated.";
140 writes Action::post("/settings/about/update-check");
141 }
142 }
143
144 /// The preferences as they stand, or the defaults.
145 fn read(state: &AppState) -> Preferences {
146 state
147 .config_dir
148 .as_ref()
149 .map_or_else(Preferences::default, |dir| {
150 crate::commands::load_at(&crate::commands::path_in(dir))
151 })
152 }
153
154 /// Write the update-check preference.
155 ///
156 /// Answers a complaint rather than a fault when there is nowhere to write:
157 /// a host with no config directory is a host this app cannot remember anything
158 /// on, and that is worth saying to the person who just pressed the switch.
159 pub(super) fn write(state: &AppState, on: bool) -> Result<&'static str, RouteError> {
160 let Some(dir) = state.config_dir.as_ref() else {
161 return Err(RouteError::internal(
162 "This machine did not give the app a config directory, so the setting cannot be saved.",
163 ));
164 };
165 let path = crate::commands::path_in(dir);
166 let mut prefs = crate::commands::load_at(&path);
167 prefs.update_check_on_launch = on;
168 crate::commands::save_at(&path, &prefs)
169 .map_err(|error| RouteError::internal(error.to_string()))?;
170 Ok(if on {
171 "Update checks on."
172 } else {
173 "Update checks off."
174 })
175 }
176
177 #[cfg(test)]
178 mod tests;
179