Commit Graph
972 Commits
Author SHA1 Message Date
austinried 06ca2da48d add frequently and recently played album and artist sorts
Song playCount/played are parsed and synced, then aggregated per album
(SUM/MAX) and per artist (through the album's artistId) after the song
sync. Albums and artists gain Frequent/Recent sorts with their own
indices; the options show only when the active source returned the
fields, and the aggregate only writes rows whose value changed.

The artist subqueries pin index_albums_sourceId_artistId: without it the
planner answers MAX(played) from index_albums_recent (sourceId, played),
which cannot apply the artistId equality, and scans every album once per
artist. The availability flows read the indexed album ranks rather than
scanning songs, which would re-run on every song-sync batch.
2026-10-04 00:30:05 +00:00
austinried a261612017 fade cover art over the letter placeholder and lift CJK initials 2026-10-04 00:30:05 +00:00
austinried 1128af165d address review: join the controller scope, keep DB closes where nothing leaks
PlaybackController.now exposes awaitStopped() so the tests that own its DB can join its scope before db.close(); close() stays cancel-only so AppModuleTest (Main dispatcher) cannot deadlock. Restores db.close() in the tests that have no leaking scope, closes the SyncManager AddSourceViewModelTest was leaking, replaces the duplicated poll with the existing awaitCoverArt helper, and trims the testLogging block to the one line that does anything.
2026-10-04 00:30:05 +00:00
austinried 84df71771e stop poisoning the unit suite from Room teardown races
The ThemeTest failure was cross-test contamination, not a theme bug. A Room invalidation-tracker query keeps running on a cancelled flow collector after the in-memory DB is closed or finalised, throws "no such table: sources", and kotlinx-coroutines-test records it and fails the next Compose test with UncaughtExceptionsBeforeTest. Which test it lands on is just ordering/load luck.

Close repositories the way DownloadRepository already did: cancel and join the scope, so Room work cannot outlive close. Stop closing the in-memory test DBs; the DB is dropped and collected once the test releases it, instead of being torn down under a pending query. Wait for the stored ref in offlineModeStillServesDownloadedArtwork instead of asserting once, since it was waiting on a different flow than the state coverArt reads. Print full stack traces for failed tests in CI.
2026-10-04 00:30:05 +00:00
austinried 2106d786cc drop the stale stable-pin rationale
Two nixpkgs pins are unremarkable and the version-specific reason rots; the flake and AGENTS no longer carry it.
2026-10-04 00:30:05 +00:00
austinried 592c522244 tidy the integration harness and its comments
Trim the explanatory comments to short quirk notes, drop the module headers, and fail cleanly: navidrome/gonic now kill their process if they never become ready and the entrypoint always tears down. The prune test waits for the removal to appear in the album list rather than for the scan flag. Correct the AGENTS token-auth note (lms answers 42, Nextcloud 41) and give CI more headroom.
2026-10-04 00:30:05 +00:00
austinried f79f26fed8 split the integration harness into server, api and util modules
tools/integration-test.nu is now just the entrypoint. The server lifecycle and fixture preparation live in tools/integration/servers.nu, the Subsonic request helpers in subsonic.nu, and shared waiting/port helpers in util.nu. No behaviour change; the assertions still live in the Kotlin tests.
2026-10-04 00:30:05 +00:00
austinried d635f3b9a0 cover lms and nextcloud music in the integration suite
Run the integration harness from its own devshell so the server packages stay out of the everyday shell. Add lms and a Nextcloud Music instance to the harness and extend the suite with auth-fallback, media, error, write round-trip, multi-source and prune tests.

The auth test caught that lms answers OpenSubsonic error 42 (not 41) when it refuses token auth, so the client now falls back on both codes.
2026-10-04 00:30:05 +00:00
austinried 2bea7fbb38 show the app's own GPL-3.0 licence and the bundled public suffix list
The app itself and the MPL-2.0 public-suffix data OkHttp embeds fall
outside the automatically collected Gradle dependency list, so add both
via the manual config. The Subtracks entry links to its source.
2026-10-04 00:30:05 +00:00
austinried d7cf0e2f4a attribute desugar_jdk_libs, which the licence collector cannot see
desugar_jdk_libs is bundled by core library desugaring rather than the
runtime classpath, so it never reached the generated licence list. Add it
plus its GPL-2.0-with-Classpath-Exception text via the plugin's manual
config directory.
2026-10-04 00:30:05 +00:00
austinried edb1eb8a68 generate third-party licenses at build and show them from settings
Add AboutLibraries, whose Android Gradle plugin collects the licence and
attribution of every dependency into a generated resource at build time,
and surface it under a new About section in Settings.
2026-10-04 00:30:05 +00:00
austinried 32d2988670 declare Subsonic API version 1.15.0
1.15.0 is the highest minor Airsonic and Airsonic-Advanced accept; they
reject a newer client with error 30. The OpenSubsonic response fields do
not depend on the declared version.
2026-10-04 00:30:05 +00:00
austinried fa5595db30 react to active source changes
The active source becomes one immutable id+source pair, so cover art and
stream/download URLs can no longer read a torn state mid-switch. The root
navigation watches the active source id and, on a real switch, closes the
overlays and pops an open album/artist/playlist detail back to the library.
2026-10-04 00:30:05 +00:00
austinried d4f3b54574 drop the process markers from the remaining ceiling comments 2026-10-04 00:30:05 +00:00
austinried a79e05184d drop the process marker from the artist paging comment 2026-10-04 00:30:05 +00:00
austinried 85d61d25d7 note the artist offset-paging ceiling 2026-10-04 00:30:05 +00:00
austinried 77c3959c48 share the artist song ordering and pin it with a test 2026-10-04 00:30:05 +00:00
austinried 336ff1a1d1 shuffle an artist across all of their albums 2026-10-04 00:30:05 +00:00
austinried 699b1dbb5d use the shared status bar scrim on the artist detail header 2026-10-04 00:30:05 +00:00
austinried afbbe29d6f lerp the artwork theme every frame instead of 24 quantized steps 2026-10-04 00:30:05 +00:00
austinried 430d2cf104 keep the artwork transition animating when the base scheme changes 2026-10-04 00:30:05 +00:00
austinried f33266113b colour letter placeholders by name and seed artwork themes from them 2026-10-04 00:30:05 +00:00
austinried 065bd99086 paint screen backgrounds from the theme instead of hardcoded black 2026-10-04 00:30:05 +00:00
austinried dff2fe58e0 default to the Material You dark scheme instead of black and white 2026-10-04 00:30:05 +00:00
austinried 9adb81aae7 drop the ripple on the now playing cover art 2026-10-04 00:30:05 +00:00
austinried f32c84bce7 address the second offline review: cancel guard, bounded reconcile 2026-10-04 00:30:05 +00:00
austinried 26e9b6cb27 cancel in-flight downloads when offline mode turns on 2026-10-04 00:30:05 +00:00
austinried ae4c500ae1 keep the artist sort index and bound the offline queue-view scan 2026-10-04 00:30:05 +00:00
austinried 0dff769d14 pluralize the album and song counts in the list views 2026-10-04 00:30:04 +00:00
austinried 669f1aff40 close offline detail screens when their downloads are gone 2026-10-04 00:30:04 +00:00
austinried 15e09d8097 address offline review: refresh, cursor, and cold-start fixes 2026-10-04 00:30:04 +00:00
austinried a36b310f95 show the downloaded song count on a playlist offline 2026-10-04 00:30:04 +00:00
austinried aa956668b1 skip non-downloaded tracks in offline shuffle and the queue view 2026-10-04 00:30:04 +00:00
austinried 554b7ef25d play an offline context by reference and filter its window 2026-10-04 00:30:04 +00:00
austinried 3e042cb380 download a song's playlist artwork too 2026-10-04 00:30:04 +00:00
austinried 410b13c920 count only downloaded albums under an artist when filtering 2026-10-04 00:30:04 +00:00
austinried 8a90635666 keep the playing context and window across an offline toggle 2026-10-04 00:30:04 +00:00
austinried 813ca00fba fix the offline queue mapping and cover it with tests 2026-10-04 00:30:04 +00:00
austinried 8cccee7065 add an offline mode that stays on the downloaded library 2026-10-04 00:30:04 +00:00
austinried 92637deb48 start the library on the stored tab instead of scrolling to it
The pager's settled-page collector reports its initial value, so a cold start
wrote Albums back before the stored tab had been read - racing the very setting
this branch added, able to pin the app to Albums and overwrite the choice. The
screen waits for the stored tab before composing now, so the pager latches the
right page and the initial report matches it, which also drops the spurious
scroll and the search clear that came with it. The report is guarded anyway.

The data layer no longer names a UI tab from a coincidentally same-named enum:
it returns the raw stored value and the view model owns the Albums fallback.
2026-10-04 00:30:04 +00:00
austinried cf92604e3a pin down that each tab keeps its own list query 2026-10-04 00:30:04 +00:00
austinried 270d749f0c remember the library tab across restarts
The selected tab lived in rememberSaveable, which survives a rotation or a
process-death restore but not a cold start, so the app always came back on
Albums. It goes through DataStore now, like the per-tab sort and filters, which
were already durable - landing on a different tab than the one you had filtered
is what made the filters look reset.
2026-10-04 00:30:04 +00:00
austinried 684cc5a287 close the test gaps a QA pass found, and de-flake the flaky ones
Verify the Subsonic token hash and salt freshness, and fall back to per-album
songs when the empty-search probe fails with an I/O error instead of
aborting the whole sync.

Add a real-server sync test that mirrors navidrome and gonic into Room and
reads the rows back, plus SyncManager coverage for the success, coalescing
and mid-sync-failure paths.

Cover the sort mapping and delete/ping ViewModels, the download engine's
request flags, the queue helpers, download prefs and migration data
preservation.

Drop the leaked collectors in the ViewModel/queue tests, document the
screenshot tests as render smoke tests, bind a real search term in the
trigram plan test, and assert a positive in-flight invariant alongside the
concurrency timing checks.
2026-10-04 00:30:04 +00:00
austinried 5cbe4c6839 tell the user when a sync fails
The failure was only recorded as SyncStatus.Failed, which nothing shows, so a
bad username or an unreachable server folded silently. It goes through the same
toast the rest of the data layer uses, with the server's own message.
2026-10-04 00:30:04 +00:00
austinried cdbecb9a9d drop unnecessary null checks flagged by the compiler 2026-10-04 00:30:04 +00:00
austinried 153122a552 fix scrobble double-count and missed plays found in review
Only treat a rewind as a new listen when it lands in the first few
seconds, so a small seek-back no longer re-arms and duplicates a
scrobble. Fall back to the player's resolved duration when the queue
item has none, and stop the position flow publishing a value for a
track the state has not caught up to. Submit off the collector so an
offline write cannot stall counting.
2026-10-04 00:30:04 +00:00
austinried 0379102e24 gate scrobbling behind a setting and funnel server writes through a sink
Scrobbling can be turned off in Settings (on by default). Now-playing,
scrobbles and stars all go through a ServerActionSink so a persistent
offline outbox can replace the network implementation later without
touching the callers.
2026-10-04 00:30:04 +00:00
austinried 29e5029b56 scrobble playback back to the server
Send a now-playing notification when a track starts playing and one
submission once it passes half its duration or four minutes, using the
Subsonic scrobble endpoint's submission and time parameters.
2026-10-04 00:30:04 +00:00
austinried d3425269ba Reapply "stop estimating a length for Ogg-based transcodes"
This reverts commit 2a3338be84cd728da799f549f144bc813611761a.
2026-10-04 00:30:04 +00:00
austinried c0d3ea767d Revert "retry a read that lands past a cold transcode"
This reverts commit 1aa92344ca02cd7a894cf8af4c9d06a9d49a853d.
2026-10-04 00:30:04 +00:00