Radiophonia 0.7.8 is a small release in feature-count terms, but it is exactly the kind of release that keeps an app pleasant to use: a compact UI ambiguity fixed, alpha Firestick support made less touch-centric, several silent error paths turned into useful diagnostics, and a Windows CI failure made boring again.
The headline user-visible change started as a simple question: the Now Playing page lists track history and play times, but not dates. Should it show a date under every time, or only when the day changes?
The answer ended up touching a shared widget, a test path, and the release pipeline.
Now Playing History: Context Without Headers
The Problem
Radiophonia has two history surfaces. The full History screen can afford date grouping because that page is built for browsing and filtering a long timeline. The Now Playing screen is different. It shows the current station’s recent songs below the player, so vertical space is already under pressure.
Showing only times works while everything is from today. It fails as soon as the list crosses midnight: late-night entries and just-after-midnight entries sit beside each other with no day context. Those are technically correct times, but without dates the user has to infer which entries are from today and which are from yesterday. Full-width date headers would solve the ambiguity, but they would also make the compact Now Playing list feel like the full History page.
The Solution
The shared history-list component now has a compact date-marker mode. When enabled, it marks only the first item for each date, rendering the marker below the time in the existing time column.
For entries in the current year the marker is short, such as 13 May. Older years include the year, such as 13 May 25. The Now Playing screen enables this mode; the full History screen keeps its existing date grouping.
That is the important part: the date is added where the ambiguity lives, not as a new layout concept. The list remains dense, selection still works, keyboard shortcuts still work, and the full history screen does not change.
The Lesson
Small UI changes are easiest to get wrong when they duplicate an existing surface. The tempting fix was “add a date label to the Now Playing row.” The better fix was “extend the shared history-list contract with a compact date-marker mode.” That keeps the behaviour testable in one place and avoids a one-off branch in the Now Playing screen.
Alpha Firestick Support: Touch Assumptions Exposed
The Problem
Firestick and Fire TV support is alpha in 0.7.8, and this release is the first pass at making the existing app feel usable from a remote. TV-style devices expose a different class of app bugs. A screen can be perfectly usable by touch and still feel broken with a D-pad. Focus needs to be visible. The selected pane needs a remote-friendly switch path. Text input needs to appear when a field receives focus from a remote, not only after a tap.
The password field was a good example. On a Firestick, focusing the field did not reliably summon the on-screen keyboard. The field existed, the focus existed, but the expected input path did not.
The Solution
0.7.8 adds alpha Firestick support by advertising optional Leanback/TV support metadata, including the TV banner, and improving focus treatment across station lists, navigation, buttons, and form fields. Focus highlights now appear during keyboard or remote navigation and fade after inactivity.
Pane switching also has an explicit remote path. The controller menu key, and F9 on keyboards, can switch panes without relying on touch.
For the account password field, focusing now explicitly requests Android text input and uses a password-friendly keyboard type while keeping the password hidden by default. This is a small fix, but it moves the control from “technically focusable” to “usable from the sofa.”
The Lesson
Alpha remote support is not just large tap targets. It is visible state, predictable focus movement, and an input path that does not assume a finger. A Flutter app can share most of its UI across phones, tablets, desktops, and TVs, but focus behaviour has to be designed as a first-class interaction model.
Silent Failures Are Still Bugs
The Problem
Several audio paths handled errors by effectively dropping them:
- audio session reactivation during connect-and-play
- local-track auto-advance
- auto-recording start calls
- proxy and player teardown during dispose
That is dangerous in an audio app because the symptom is often just “nothing plays” or “recording did not start.” Without a diagnostic trail, the next step is guesswork.
The Solution
The release adds debugPrint logging to those paths. This is intentionally modest. It is not a new telemetry system and it does not turn normal playback into a wall of logs. It just ensures that when a platform audio session refuses to reactivate, a local file fails during auto-advance, or a recording start fails because of permission or filesystem state, there is evidence in the device logs.
The stream proxy dispose path was tightened at the same time. AudioPlayerService.dispose() now stops the proxy before disposing it, so an active upstream HTTP connection and recording tee subscription are not left attached during teardown.
The Lesson
Swallowing an error is not the same as handling it. For app code that runs across Android, iOS, macOS, Windows, Linux, and web, some failures are inherently platform-dependent. You cannot make every platform failure impossible, but you can make it observable enough that the next diagnosis starts from a fact rather than a hunch.
The Sleep Timer Race
The Problem
The sleep timer fade-out used an unawaited loop of delayed volume writes. When the timer expired, Radiophonia stopped playback and restored the pre-fade volume. That looked correct on paper.
The race was in the delayed writes. A late fade tick could run after the restore and set the volume back toward zero. The next time the user resumed playback, the app looked normal but output was effectively muted until the user touched the volume slider.
The Solution
The volume fade now carries a generation counter. Starting a fade increments the generation; cancelling a fade increments it again. Each delayed step checks that it still belongs to the active generation before writing volume. If a newer fade or cancellation has superseded it, the old delayed step exits without touching the volume.
The sleep timer now cancels any in-flight fade before restoring the remembered volume on both manual cancellation and timer expiry.
The custom-duration dialog got a usability fix too. Typing a duration and pressing the dialog action now starts the countdown. The duration field also submits on Enter or the platform’s done action.
The Lesson
Unawaited delayed loops are state machines, whether they are written that way or not. If a later action needs to supersede them, they need cancellation semantics. A generation counter is simple, explicit, and enough for this case.
Windows: Paths, DLLs, and CI
The Problem
Two Windows issues in this cycle were the same kind of bug in different clothes: assuming one environment’s path behaviour applies everywhere.
Playlist files commonly use POSIX-style relative paths even on Windows. The resolver previously used native path normalisation, which could turn playlist paths into backslash-separated strings and break downstream equality checks.
The libVLC loader had the opposite shape. Production builds worked because the DLLs live beside the executable. Development and test builds failed because flutter run and flutter test use a different working layout, where the in-tree DLLs live under windows/vlc/.
The Solution
Playlist resolution now normalises playlist paths through p.posix, after converting backslashes to forward slashes. The result stays portable and dart:io File() accepts the path on Windows.
The VLC loader now probes several locations: the default DLL search path, the executable directory, an adjacent vlc directory, and windows/vlc/ relative to the working directory. When probing a non-default directory it preloads libvlccore.dll by absolute path before opening libvlc.dll, because Windows dependency resolution is based on the host process context, not the directory you wished it would use.
The CI Part
The release also exposed a Windows workflow failure around smtc_windows. The first visible error was not the root cause: the diagnostic cargo step failed because of PowerShell quoting, so the debug step could not debug anything.
That was fixed by switching to explicit argument arrays for cargo. Once the diagnostics actually ran, the real problem was the plugin workspace resolver. smtc_windows 1.1.0 uses edition 2021 but its workspace Cargo.toml omits resolver = "2", which newer Rust toolchains warn about or reject in this build path.
The workflow now reads .dart_tool/package_config.json to locate the exact package root, patches the package Cargo.toml if needed, and retries the Windows build once after cargo has warmed up. The same hardening went into both CI and release workflows.
The Lesson
Debug steps need the same engineering care as build steps. A diagnostic command that is fragile under PowerShell quoting is just another failure mode. The boring version, argument arrays and exact paths from package metadata, is the right version.
By the Numbers
- 1 shared history-list option for compact date markers
- 1 explicit fade cancellation path for the sleep timer
- 4 previously silent audio or recording failure paths now logged
- 4 VLC search locations for Windows dev/test/production layouts
- 2 workflows hardened for the Windows cargo resolver issue
- 8 CI lanes green before tagging: analyze, test, Windows, macOS, Linux, web, iOS, and Android
What This Release Reinforced
Small UX gaps are often design questions before they are code questions. A date marker sounds trivial until you decide where it belongs and how it should behave across the shared history component.
Remote input needs its own pass. Touch, keyboard, and D-pad navigation share widgets, but they do not share assumptions.
Silent failures are debt. They do not always crash the app, but they slow down every future investigation.
And release engineering is product work. A signed Windows artifact that builds reliably is part of the user experience, even if the user never sees the cargo resolver patch that made it possible.
Radiophonia 0.7.8 is available from radiophonia.app. Direct-download artifacts are also published on the public Radiophonia v0.7.8 release page.