[feature] Add animated community credits experience#4250
Conversation
There was a problem hiding this comment.
Really fun contribution — the demo-scene LCD credits are a delightful touch, and the engineering is careful: the network path is context-scoped and non-blocking, JSON parsing degrades to the built-in scroller on any failure, accessibility names/descriptions are set throughout, and it reuses PersistentDialog/trackPersistentDialog correctly. Build, CodeQL, a11y, and both mac/win checks are green, and the commit is signed. No correctness blockers — my notes are about placement and one design decision.
Would like fixed before merge
- ~575 lines of a self-contained widget + animation engine + network client are living in an anonymous namespace inside the menu-builder TU. Please lift it into its own
CommunityCreditsDialog.{h,cpp}— see inline.
Non-blocking notes
- Opening the dialog fires an unconditional POST to
api.opencollective.comwith no opt-out — worth a maintainer decision (inline). resources/c512w-friday.s3mis committed but referenced nowhere (not inresources.qrc, not in code) — only the rendered.wavis used at runtime. If it's kept purely for provenance, consider dropping it from the tree or noting its purpose, rather than shipping an unreferenced binary blob underresources/.
Polish
- The 16 ms animation timer spins whether or not the dialog is visible (inline).
🤖 aethersdr-agent · cost: $6.6713 · model: claude-opus-4-8
| qreal drift{0.0}; | ||
| }; | ||
|
|
||
| class CommunityCreditsCanvas final : public QWidget { |
There was a problem hiding this comment.
This whole block — CommunityCreditsCanvas, the aurora/starfield/scroller renderer, and fetchOpenCollectiveSupporters — is a ~575-line subsystem sitting in an anonymous namespace inside the menu-builder translation unit. It has no dependency on MainWindow internals until the button wiring at the call site, so it would be much easier to maintain (and much cheaper to recompile) as its own src/gui/CommunityCreditsDialog.{h,cpp} exposing a small factory/open entry point. MainWindow_Menus.cpp would then just construct and show it. Not a correctness issue — the engine/UI boundary check already passes — but a sizable altitude/maintainability concern for a menu TU.
There was a problem hiding this comment.
Thanks for a genuinely fun, well-crafted PR — the QPainter scene is clean, lambdas are correctly scoped to owner so there are no use-after-free traps across the network round-trip, the 7 s abort timer and the graceful "fall back to the generic scroller on any failure" path are exactly right, and geometry persistence goes through PersistentDialog rather than raw QSettings. The C++ is solid.
My reservations are about weight and scope, not correctness. The two concrete things worth resolving before merge are the ~8.4 MB of binary assets baked into the application, and a meta question that's really a maintainer call: whether a demo-scene credits screen with looping music and a live Open Collective fetch fits the character of a FlexRadio workstation. I'm posting COMMENT — the merge decision is yours.
Would like discussed before merge
resources/community-credits.wav(6.9 MB) +aurora-cutout.png(1.5 MB) are embedded into every build via the qrc — ~8.4 MB added to the binary for one easter-egg dialog. See inline for options.resources/c512w-friday.s3m(25 KB) is committed but is not inresources.qrc, so it never ships at runtime — confirm it's intentional source/provenance and not a leftover.
Non-blocking notes
- The dialog fires an outbound request to
api.opencollective.comevery time it opens (see inline) — fine since it's user-initiated and non-blocking, just flagging the offline/privacy angle. paintDxycpScrollernames a localexit(shadowsstd::exit) — cosmetic, ignore if you like.
🤖 aethersdr-agent · cost: $3.5215 · model: claude-opus-4-8
| <file alias="community-credits.wav">community-credits.wav</file> | ||
| </qresource> | ||
| <qresource prefix="/images"> | ||
| <file alias="aurora-cutout.png">aurora-cutout.png</file> |
There was a problem hiding this comment.
These two files embed ~8.4 MB into the AetherSDR binary itself (community-credits.wav is 6.9 MB, aurora-cutout.png is 1.5 MB), carried into every install for a single credits dialog.
Worth reconsidering: (a) a much smaller/compressed WAV — 0.08 volume looping background music doesn't need 24 kHz stereo PCM; even mono ~11 kHz would cut this by 3–4×; and/or (b) loading these from an on-disk resource dir instead of the qrc so they don't inflate the executable. If they stay in the qrc, please at least shrink the WAV — it's the bulk of the cost.
66fac3b to
02569cf
Compare
02569cf to
6f79923
Compare
ten9876
left a comment
There was a problem hiding this comment.
Good PR — clean isolation, the animation code is well-crafted, and the fallback path makes it fully usable offline. Two items worth fixing before merge, plus a couple of nits.
Medium — opacity(0.99) slow compositing path (inline comment on Contribute.cpp)
Medium — three unused includes left in MainWindow_Menus.cpp (inline comment on that file)
Minor — QSoundEffect silent failure has no user signal
QSoundEffect silently fails to open the audio device on some Linux PipeWire/ALSA configurations — play() returns but nothing plays. The dialog works fine muted, but the control bar still shows "Mute Music" when no music is playing. A one-line connect to QSoundEffect::statusChanged that hides or grays the track label and mute button on QSoundEffect::Error would make the failure visible. Could be a follow-up.
Nit — c512w-friday.s3m is dead weight at runtime
The S3M module is in resources/ for provenance but not embedded in resources.qrc — only the WAV is used at runtime. It travels in git history as a 65 KB binary blob that serves no build or runtime purpose. Consider moving it to docs/audio-sources/ or noting in a comment that it's source-only, so future contributors don't spend time wondering why it's there.
Nit — canvas raw pointer capture is safe but non-obvious
void fetchOpenCollectiveSupporters(QDialog* owner, CommunityCreditsCanvas* canvas)The lambda captures canvas by raw pointer. It's safe — canvas is a child of owner and both are destroyed together, and the QNetworkReply is deleted by its manager when owner closes, so the lambda never fires on a dangling pointer. But it's subtle enough to be worth a QPointer + null-check or a brief comment documenting the lifetime guarantee.
Generated by Claude Code
| viewport.center().x() - sceneSize.width() / 2.0, | ||
| viewport.center().y() - sceneSize.height() / 2.0, | ||
| sceneSize.width(), sceneSize.height()); | ||
|
|
There was a problem hiding this comment.
Medium — slow compositing path for no visual gain
painter.setOpacity(0.99);
painter.drawImage(sceneRect, m_radioImage);
painter.setOpacity(1.0);Any opacity value other than exactly 1.0 forces QPainter into a software compositing path — it reads back the destination pixels, blends, and writes, instead of blitting directly. On a 1040×680 dialog that's a ~4 MB read per frame at 60 fps.
The aurora is painted before the radio image and the PNG cutout has its own alpha channel where transparent, so the aurora already bleeds through the cutout at 1.0 opacity. Set this to 1.0 or add a comment explaining what the 0.99 is preserving that the alpha channel doesn't already give you.
Generated by Claude Code
There was a problem hiding this comment.
Nit — three leftover includes
+#include <QColor>
+#include <QJsonObject>
+#include <QNetworkRequest>All network and JSON handling moved into Contribute.cpp as intended, but these three headers were left behind. None are used directly in this TU after the refactor — QColor and QJsonObject don't appear in the remaining code, and QNetworkRequest was only needed by the fetch helper that's now in Contribute.cpp. Remove them so the file doesn't imply that menu setup does network I/O.
Generated by Claude Code
|
First-pass triage review (not exhaustive — surfacing blockers to keep this moving): Blockers
Secondary notes
Nice, self-contained extraction and the live-fetch-with-fallback design sidesteps stale hardcoded names. The music-after-close lifetime issue is the one thing I'd want fixed before merge. |
Summary
Adds a memorable, demo-scene-inspired community credits experience to the About dialog and gives users a direct path to support AetherSDR.
The new Play Community Credits... action opens a dedicated, persistent dialog built entirely with Qt. It places animated contributor credits inside the LCD of a high-resolution FLEX-8600 cutout, surrounds the radio with a procedural starfield and aurora, and plays a bundled Scream Tracker track while the credits run.
What changed
PersistentDialogthat follows the existing frameless-window and geometry-persistence behavior.Contribute.{h,cpp};MainWindow_Menus.cppretains only the About button and its one-line launcher.QPainterscene with:aurora.jpgsource is intentionally not part of this PR.Live supporter data
The dialog requests the 50 newest
BACKERmembers from Open Collective's GraphQL v2 API when it opens. Results are deduplicated case-insensitively and incognito, empty, or guest entries are represented as Anonymous Signal Booster.The network path is deliberately non-blocking:
Cross-platform implementation
The visual effects use QWidget/QPainter and a 16 ms Qt timer, so this does not introduce a new rendering backend or require 3D acceleration. LCD coordinates are defined in source-image space and scaled into the current dialog geometry, keeping animation clipped to the physical display at different sizes and DPI settings.
Music playback uses
QSoundEffectwith a resource-embedded 11.025 kHz mono PCM WAV. The original S3M is retained alongside the rendered track for source/provenance, while WAV playback avoids adding a module-player dependency or relying on platform-specific compressed-audio codecs. The lower sample rate reinforces the tracker-era character and reduces the WAV from 7,057,544 bytes to 1,621,098 bytes (77%). No new third-party library is introduced.Assets
resources/aurora-cutout.png— cleaned, alpha-transparent radio cutout used at runtime.resources/c512w-friday.s3m— source Scream Tracker 3 module.resources/community-credits.wav— 11.025 kHz mono, 16-bit PCM render used by Qt at runtime.docs/screenshots/community-credits.png— current macOS visual reference.resources/aurora.jpgis intentionally excluded.Validation
origin/main.AetherSDRsuccessfully on macOS withcmake --build build --target AetherSDR -j8.ContributeDialog, responsive radio/LCD composition, supporter scroller, music playback, mute/unmute toggle, close behavior, and Contribute control through the agent automation bridge.python3 tools/check_a11y.py; no findings were introduced by this change (the scanner reports eight pre-existing warnings in five unrelated files).python3 tools/check_engine_boundary.py --strict; no blocking findings.git diff origin/main...HEAD --checksuccessfully.aurora-cutout.pngand does not containaurora.jpg.Follow-up automation opportunities
The visual behavior is currently tested through app inspection. Useful bridge verbs for deterministic future coverage would be:
communityCredits.opencommunityCredits.statecommunityCredits.setMutedcommunityCredits.capturePhase(ms)👨🏼💻 Generated with OpenAI Codex (GPT-5.6 Sol) and tested by @jensenpat