Settings: its rows in five groups
CI / build (pull_request) Successful in 2m22s
Site / build (pull_request) Successful in 10s

Settings had grown to 21 rows in one list. It opens on five groups (This
device, Display, GNSS and radio, Network, System); Enter opens one, Back
returns to the groups, and Back from the groups leaves Settings.

SettingsMenu holds the groups and which one is open (host-tested: every
setting is in exactly one group). The guide, the how-tos, the README,
the scripts' messages and the firmware's own name the new paths, such as
"Settings > System > Firmware".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
2026-10-10 00:11:52 +02:00
co-authored by Claude Opus 5.5
parent 37335b4829
commit 0c52613b72
43 changed files with 305 additions and 98 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
namespace roro {
// Settings → Debug Console (ADR 0010): the switch, where to connect, and the token, which is shown
// Settings → System → Debug Console (ADR 0010): the switch, where to connect, and the token, which is shown
// here and nowhere else. Switching on makes a token if there's none; a new one, or one typed by
// hand, ends the connection of whoever holds the old one.
class DebugConsolePage {
+1 -1
View File
@@ -18,7 +18,7 @@
namespace roro {
// Settings → Firmware: the running version and its Probation, where to push Firmware Updates, the
// Settings → System → Firmware: the running version and its Probation, where to push Firmware Updates, the
// project's releases on Gitea (the latest, and the ten before it), and the Update Files on the SD
// card (in /updates) to install from.
class FirmwarePage {
+1 -1
View File
@@ -76,7 +76,7 @@ void GnssApp::draw(Canvas& c) {
c.drawString(held ? "GNSS is paused" : "GNSS is off", 4, area.y + 4);
c.setFont(&fonts::body);
c.setTextColor(theme::kMuted);
c.drawString(held ? "The LoRa radio is listening, and" : "Settings > GNSS turns it on.", 4, area.y + 22);
c.drawString(held ? "The LoRa radio is listening, and" : "On: Settings > GNSS and radio > GNSS", 4, area.y + 22);
if (held) c.drawString("Settings pauses GNSS for it.", 4, area.y + 22 + theme::kLineHeight);
return;
}
+1 -1
View File
@@ -11,7 +11,7 @@
namespace roro {
// Home screen: the visible Apps as a grid of icons, with the selected one's name under it, or as
// a list of names (Settings > Launcher). Select opens one. An App with something going on has a
// a list of names (Settings > Display > Launcher). Select opens one. An App with something going on has a
// dot on its tile (App::badge, issue #9).
class LauncherApp : public App {
public:
+27 -4
View File
@@ -15,7 +15,9 @@ using Row = SettingsMenu::Row;
void SettingsApp::onEnter() {
page_ = Page::Menu;
menu_.close();
list_.setCount(menu_.count());
list_.select(0);
}
void SettingsApp::warn(const std::string& text) {
@@ -56,7 +58,12 @@ bool SettingsApp::onMenuKey(const KeyEvent& e) {
if (menu_.kind(i) == Kind::Toggle) menu_.toggle(i);
return true;
case Key::Select: break;
default: return false; // Back leaves Settings
case Key::Back:
if (!menu_.close()) return false; // from the groups, Back leaves Settings
list_.setCount(menu_.count());
list_.select(menu_.groupIndex());
return true;
default: return false;
}
editingRow_ = i;
switch (menu_.kind(i)) {
@@ -72,6 +79,11 @@ bool SettingsApp::onMenuKey(const KeyEvent& e) {
break;
case Kind::Toggle: menu_.toggle(i); break;
case Kind::Slider: break;
case Kind::Group:
menu_.open(i);
list_.setCount(menu_.count());
list_.select(0);
break;
case Kind::Page:
switch (menu_.row(i)) {
case Row::Wifi:
@@ -194,16 +206,27 @@ std::vector<std::string> SettingsApp::aboutLines() const {
void SettingsApp::draw(Canvas& c) {
const auto& area = theme::kContent;
switch (page_) {
case Page::Menu:
case Page::Menu: {
// An open group has its name on a line of its own, over its rows.
theme::Rect rows = area;
if (menu_.inGroup()) {
c.setFont(&fonts::bold);
c.setTextDatum(top_left);
c.setTextColor(theme::kMuted);
c.drawString(menu_.title().c_str(), 4, area.y + 1);
rows.y += theme::kLineHeight;
rows.h -= theme::kLineHeight;
}
widgets::list(
c, list_, area, [this](int i) { return menu_.label(i); },
c, list_, rows, [this](int i) { return menu_.label(i); },
[this](int i) {
auto kind = menu_.kind(i);
if (kind == Kind::Page) return std::string(">");
if (kind == Kind::Page || kind == Kind::Group) return std::string(">");
if (kind == Kind::Slider) return "< " + menu_.value(i) + " >";
return menu_.value(i);
});
break;
}
case Page::Text:
c.setFont(&fonts::body);
c.setTextColor(theme::kMuted);
+1 -1
View File
@@ -35,7 +35,7 @@ struct SettingsAppDeps {
VpnService& vpn;
};
// Settings: every user-facing setting, plus the Wi-Fi, Firmware, Debug Console and About pages.
// Settings: every user-facing setting, in groups, plus the Wi-Fi, VPN, Firmware, Debug Console and About pages.
class SettingsApp : public App {
public:
explicit SettingsApp(const SettingsAppDeps& deps)
+1 -1
View File
@@ -16,7 +16,7 @@
namespace roro {
// Settings > VPN (issue #8): the switch, "Start with Wi-Fi", importing a `.conf` from the card
// Settings > Network > VPN (issue #8): the switch, "Start with Wi-Fi", importing a `.conf` from the card
// and forgetting it, and what the tunnel is doing. No key is ever on this page.
class VpnPage {
public:
+1 -1
View File
@@ -17,7 +17,7 @@
namespace roro {
// Settings → Wi-Fi: the On/Off switch, the connection and its details, DNS and NTP servers, and
// Settings → Network → Wi-Fi: the On/Off switch, the connection and its details, DNS and NTP servers, and
// the Saved Networks: adding one from a scan or by name (hidden), and each one's own page with its
// IP setting, Automatic or Fixed (docs/milestones/S1.md, Q113). Owned by the Settings App.
class WifiSettingsPage {