Launcher: a grid of icons, with badges; the list stays as a Setting

The Launcher shows the Apps as a 4 x 3 grid of 32 x 32 icons, the
selected one's name under it (issue #9). The icons are the website's
own, doubled, and two drawn the same way for SSH and the Shell; they
are PNGs in assets/icons, turned into arrays by scripts/make_icons.py,
which CI checks. The selected tile is the website's: dark blue on cyan,
with notched corners.

- GridModel (lib/ui, host-tested) holds the selection and the scrolling.
- App::badge(): a dot on the tile for unread IRC messages, a Track
  recording, a Capture running, an SSH session open.
- Settings > Launcher: Grid or List.

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-09 23:14:37 +02:00
co-authored by Claude Opus 5.5
parent aadfd8cb37
commit e2326e113b
40 changed files with 907 additions and 24 deletions
+13 -11
View File
@@ -62,6 +62,7 @@
#include "safe_mode.h"
#include "settings.h"
#include "storage_paths.h"
#include "ui/icons.h"
#include "ui/notifier.h"
#include "ui/screen.h"
#include "scp_args.h"
@@ -230,19 +231,20 @@ void setup() {
apps = new AppManager(launcher);
launcher.setManager(*apps);
apps->registerApp({"irc", "IRC", false, new IrcApp(*irc, *clockService, bus)});
apps->registerApp({"wifi-tools", "Wi-Fi Tools", false, new WifiToolsApp(*wifi, *storageService, *clockService)});
apps->registerApp({"gnss", "GNSS", false, new GnssApp(*gnssService, settings)});
apps->registerApp({"gemini", "Gemini", false, new GeminiApp(*geminiService)});
apps->registerApp({"lora", "LoRa Scanner", false, new LoraScannerApp(*radioService, *loraCapture, settings, *clockService)});
apps->registerApp({"storage", "Storage", false, new StorageApp(*fileOps, *storageService, *clockService, *update, *power, bus, *new WebShare(*storageService, *fileOps, *wifi))});
apps->registerApp({"notes", "Notes", false, new NotesApp(*fileOps, *storageService, *clockService, *power)});
launcher.setSettings(settings);
apps->registerApp({"irc", "IRC", false, new IrcApp(*irc, *clockService, bus), icons::irc});
apps->registerApp({"wifi-tools", "Wi-Fi Tools", false, new WifiToolsApp(*wifi, *storageService, *clockService), icons::wifi_tools});
apps->registerApp({"gnss", "GNSS", false, new GnssApp(*gnssService, settings), icons::gnss});
apps->registerApp({"gemini", "Gemini", false, new GeminiApp(*geminiService), icons::gemini});
apps->registerApp({"lora", "LoRa Scanner", false, new LoraScannerApp(*radioService, *loraCapture, settings, *clockService), icons::lora});
apps->registerApp({"storage", "Storage", false, new StorageApp(*fileOps, *storageService, *clockService, *update, *power, bus, *new WebShare(*storageService, *fileOps, *wifi)), icons::storage});
apps->registerApp({"notes", "Notes", false, new NotesApp(*fileOps, *storageService, *clockService, *power), icons::notes});
sshService = new SshService(settings);
sshApp = new SshApp(*sshService, settings, *storageService);
scpService = new ScpService(settings, *storageService, *fileOps);
apps->registerApp({"ssh", "SSH", false, sshApp});
apps->registerApp({"ssh", "SSH", false, sshApp, icons::ssh});
shellApp = new ShellApp(shellRun, shellProbe, shellList, shellCount, helpText(), *apps);
apps->registerApp({"shell", "Shell", false, shellApp});
apps->registerApp({"shell", "Shell", false, shellApp, icons::shell});
shellApp->asking = [] { return scpService->asking(); };
shellApp->answer = [](const std::string& password) { scpService->answer(password); };
shellApp->abort = [] { scpService->cancel(); };
@@ -252,9 +254,9 @@ void setup() {
// A long note being rewritten (issue #47): the editor waits for the card, so it draws from there.
NoteEditor::onProgress = [](const std::string& name, int percent) { screen.renderUpdate("Saving", name, percent); };
apps->registerApp({"system", "System", false,
new SystemApp(*wifi, *battery, *storageService, *radioService, *gnssService, nvs)});
new SystemApp(*wifi, *battery, *storageService, *radioService, *gnssService, nvs), icons::system});
apps->registerApp({"settings", "Settings", false,
new SettingsApp({settings, bus, *apps, *battery, *storageService, *clockService, *wifi, *savedNetworks, *update, *vpnService})});
new SettingsApp({settings, bus, *apps, *battery, *storageService, *clockService, *wifi, *savedNetworks, *update, *vpnService}), icons::settings});
apps->registerApp({"demo", "Widget demo", true, new DemoApp(bus)});
apps->registerApp({"setup", "Setup", true, new SetupApp(settings, *apps)});