Warning screen, smarter license re-validation, version reporting

Warning screen
- New reusable WarningPage (word-wrapped message, ENTER acknowledges and
  continues to the screen you were about to enter).
- First use: pressing Play while the selected audio is still downloading.
  Dynamic Audio requires every file; a fixed track requires its own file.

License re-validation
- No longer re-verifies on every Home render. Checks now run only when the
  email/license key is entered or changed, on the manual option, at startup
  when already due, and hourly while valid.
- Invalid + unchanged credentials are never re-checked (persisted pair).
- Valid re-checks at most once per hour, anchored on a DB timestamp so an
  overdue check runs immediately after a power cycle.
- New system_kv keys track the checked pair, outcome, and last-check time.
- Added a System Settings row "Manually Validate License Key OK|Invalid"
  (OK green, Invalid red, label white) that runs a check immediately.

Portal / local integration
- License validation request now sends the app version as `appVersion`.
- The entered license key is mirrored to ~/.local/radiantwave/license.txt.

Misc
- "Please connect to WiFi" only shows when no WiFi is joined AND the internet
  is unreachable (fixes the NetworkManager-missing and Ethernet cases).
- Added unit tests for the audio-download warning, license scheduling policy,
  license request shape, and the new settings-menu entry.
This commit is contained in:
NeilClack
2026-10-14 10:58:40 -05:00
parent 9b57e48969
commit c3b3d6774a
21 changed files with 999 additions and 52 deletions
+65
View File
@@ -150,5 +150,70 @@ TEST(AudioDownloaderMd5, MissingFileReturnsEmpty) {
EXPECT_EQ(computeFileMd5(tempPath("_missing.bin")), "");
}
TEST(AudioDownloadWarning, DynamicAudioWarnsWhenAnyFileMissing) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = false;
snap.files = {
{"174Hz.wav", "174 Hz", true},
{"396Hz.wav", "396 Hz", false},
};
std::string message;
ASSERT_TRUE(buildAudioDownloadWarning(snap, true, "396Hz.wav", message));
EXPECT_NE(message.find("'Dynamic Audio'"), std::string::npos);
EXPECT_NE(message.find("these files"), std::string::npos);
}
TEST(AudioDownloadWarning, DynamicAudioSilentWhenAllAvailable) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = true;
snap.files = {{"174Hz.wav", "174 Hz", true}};
std::string message;
EXPECT_FALSE(buildAudioDownloadWarning(snap, true, "174Hz.wav", message));
}
TEST(AudioDownloadWarning, FixedTrackWarnsWhenItsFileMissing) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = false;
snap.files = {
{"174Hz.wav", "174 Hz", true},
{"OriginalAudio.wav", "Original Audio", false},
};
std::string message;
ASSERT_TRUE(buildAudioDownloadWarning(snap, false, "OriginalAudio.wav", message));
EXPECT_NE(message.find("'Original Audio'"), std::string::npos);
EXPECT_NE(message.find("this file"), std::string::npos);
}
TEST(AudioDownloadWarning, FixedTrackSilentWhenItsFilePresentEvenIfOthersMissing) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.allAvailable = false;
snap.files = {
{"174Hz.wav", "174 Hz", true},
{"396Hz.wav", "396 Hz", false},
};
std::string message;
EXPECT_FALSE(buildAudioDownloadWarning(snap, false, "174Hz.wav", message));
}
TEST(AudioDownloadWarning, UnknownSelectionNeverWarns) {
// No manifest fetched yet: the downloader has no evidence either way.
AudioDownloadSnapshot snap;
std::string message;
EXPECT_FALSE(buildAudioDownloadWarning(snap, false, "OriginalAudio.wav", message));
}
TEST(AudioDownloadWarning, FallsBackToFilenameForDisplayName) {
AudioDownloadSnapshot snap;
snap.manifestKnown = true;
snap.files = {{"Mystery.wav", "", false}};
std::string message;
ASSERT_TRUE(buildAudioDownloadWarning(snap, false, "Mystery.wav", message));
EXPECT_NE(message.find("'Mystery.wav'"), std::string::npos);
}
} // namespace
} // namespace radiantwave::audio