Run transition bookkeeping under the queue mutation lock, retain detached playback when its final survivor is removed, and restore offline repeat wrapping without a forward survivor. Centralize detached survivor rebuilding and add regressions for each defect.
The near-black #120C12 behind the icon reads as black on dark home
screens, and the same fill backs the store icon, feature graphic and
README logo. Raise it to a plum #2D1C2D (L* 4.6 -> 13.1) so the
background is visible without costing the #F862F8 foreground its
contrast. The amber nightly background moves from #1A1200 to #2E2000 by
the same lightness step, so it stays amber instead of drifting to
brown-black.
Raster assets were recoloured from the committed originals; the vector
sources in images/ have no background and are unchanged.
Preparing the restored window on launch was meant to make the paused
session reachable from Bluetooth and Automate. The follow-up on the issue
shows the notification appears, but the car's play command still does not
reach the app until the phone is unlocked, and the speculative prepare
buffered up to 50 seconds of the restored track on every launch while
claiming the media controls whenever the app opened.
Removing the prepare also removes the error gate it needed, which could
hide genuine playback errors while paused.
Reverts #230.
A restore now prepares the saved window before any play gesture, so a
network failure at launch reached the error listener and showed a toast
even though the user had not asked to play. Only report playback errors
when playback was requested.
The new tests now wait for the prepare operation rather than the window
contents, which can be observed a poll before the prepare lands.
Restoring the saved queue left the player idle, so the media session had no
notification and Bluetooth or Automate saw no active player until playback
began. Prepare the restored window without autoplaying, which surfaces the
paused session when the app opens. A queue from another source stays stopped,
and an offline queue without downloads has no window to prepare.
Closesaustinried/subtracks#202
Robolectric resolves android-all-instrumented itself at test time with
MavenArtifactFetcher, whose default repository is repo1.maven.org. The
Gradle mirror in settings.gradle.kts never applied to that fetch, so the
CI egress IP still got HTTP 429 there.
That fetcher also stores the jar in ~/.m2/repository, outside the
~/.gradle/caches directory CI restores, so every ephemeral runner
downloaded it again. Point robolectric.dependency.repo.url at the same
mirror for every Test task and cache the Robolectric Maven repository in
both workflows, keyed by the Robolectric version and the configured SDK.
awaitActiveSourceId() watched the DAO's activeSourceId flow, which turns
non-null as soon as the source row is written. SourceRepository sets its
in-memory active source in a separate collector, so artistImage() could
still see null and the online URL assertion failed. Wait on the
repository's own activeSource readiness signal instead.
The downloaded statements only run with the filter on, so the
(? = 0 OR EXISTS (...)) guards and the CASE around the downloaded counts
were always true. Keep those statements direct and remove the unused bind.
SQLite gives a repeated named parameter one index, so rawQuery can pass the
SQL through unchanged and bind the distinct names in first-appearance order
instead of rewriting them to positional placeholders.
Use named arguments for the repository dispatch, since the adjacent
descending/downloaded booleans were easy to transpose, and add a repository
test that collects the downloaded album grid. Fix the query-timing labels so
the unqualified rows measure the same default path as before.
Rebase follow-up for the new SortNameMode preference: pass it into the
shared album/artist order functions so downloaded pages honour it too,
cover the new _plain indexes in the downloaded artist plan test, and add a
downloaded-mode ordering test.
Add tests for the downloaded artist and playlist paging sources now that
their observed entities are hand-picked, and assert the downloaded
search/starred filters directly. The downloaded artist plan test uses the
real select list and documents the computed album count's in-memory sort,
which the hand-written query also had.
Replace the thirty hand-written order variants with one query builder per
entity plus a default and a downloaded @RawQuery entry point. The
repository's sort dispatch trees and the duplicated positional filters in
LibraryPaging are gone, and the SQL now lives in multi-line strings with
named parameters.
The generated statements are identical to the previous ones modulo
whitespace: the downloaded variants keep the same CASE/download conditions,
and the observed entities match what Room inferred for the old queries. A
small helper rewrites the named parameters to RoomRawQuery's positional
form, including the repeated :sourceId bindings.
AGP 9 deprecates AndroidSourceDirectorySet.srcDir() in favour of the directories mutable set, which was the last source-set deprecation warning in the build.
The album id is the server id, so use it for the download, the local folder and the prune fixture instead of maintaining a separate remoteId mapping. The cache key moves to v3 because v2 may already hold folders named after the old local ids.
demo.subsonic.org no longer accepts the guest1 account, so the harness downloaded an XML error page and unzip failed before Gradle ran. Download the same three albums from demo.navidrome.org with the demo account, fetch each album's cover art into folder.jpg, and match the album title the files actually carry (Retroconnaissance, without the EP suffix). Bump the music cache key so stale copies from the old source are not reused.
Regenerate the store screenshots from the demo library. Only the artist
page changed meaningfully, so keep that capture, using Nine Inch Nails,
which has a biography and three albums.
The demo test now fetches the artist biography and passes artist art to
the home, library and search screens, so a regeneration keeps their art
instead of falling back to letter placeholders.
The captures now come from a 393x786 dp, 440 dpi phone: Pixel XL's
640x360 dp renders text far larger than a real phone shows it. That
phone renders 1080x2161, one pixel over the 2:1 limit both stores
enforce, so captures are written out at 1080x2160.
Bump versionCode to 14 and versionName to 3.1.0, and add the
changelogs/14.txt release notes covering ReplayGain, artist pages,
sort names, keep-screen-on and the fixes.
No tag: this only prepares the release.
Track info now lists the Artist field and the plural Artists field as
their own rows, hiding Artists when it would repeat the Artist value.
Keep one Artist sort row, also hidden when it matches the artist name.
The info rows do not depend on each other: a track with artists but no
artist sort keeps the artists row, and a track with an artist sort but
no artist name still shows the sort row. Pin both so a later label or
value change cannot start requiring both.
The hand-written playlist projection used by the Downloaded filter and
offline mode did not select the new owner and shared columns, so the
playlist Info dialog lost them there while unfiltered lists had them.
Select both columns and pin the filtered path in LibraryDaoTest.
Also share the artist-name extraction between the subtitle and the
plural label so duplicate or blank refs cannot make them disagree,
accept the XML boolean form "1" for the public flag, and strengthen the
date-adjacency and pluralisation tests.
Sort names and titles are only shown when they differ from the regular
name, so artists without a distinct sort name no longer repeat the name.
Track info uses plural labels for both the artists and their sort name
when the track has more than one artist, and singular labels otherwise.
Added and last played sit together in album info.
Playlists gain an Info dialog with owner, sharing, song count, length,
creation and update dates. Owner and the public flag are synced from
getPlaylists, so a schema migration adds both columns.
Size the offline button to 48dp and lift the cloud-off glyph by its
own off-centre so it sits in the middle of the circle. Give the active
filter dot the old FAB treatment: a 7dp dot with a ring in the button
colour, moved onto the top bar of the filter glyph.