Commit Graph
319 Commits
Author SHA1 Message Date
austinried 46ab8f66c4 open now playing when the media notification is tapped 2026-10-04 00:30:07 +00:00
austinried 6eb23445f1 limit the home downloaded-songs preview in SQL 2026-10-04 00:30:07 +00:00
austinried 3e260aa170 address review: dedupe and trim genres, add a starred queue reference, fix the offline source switch 2026-10-04 00:30:06 +00:00
austinried 4779bfc88b keep playback running when offline mode is toggled, and swap the recently-added header icon 2026-10-04 00:30:06 +00:00
austinried 2061ebafca keep playback running when rebuilding the offline window, and order downloads newest-first 2026-10-04 00:30:06 +00:00
austinried 8848e951e4 play genres and downloads as lazy queue references instead of expanding every song 2026-10-04 00:30:06 +00:00
austinried adbd3b8d8e normalize every song's genres into song_genres and simplify the genre queries 2026-10-04 00:30:06 +00:00
austinried 3df16826e6 home: paginate the offline lists and add More to each downloaded section 2026-10-04 00:30:06 +00:00
austinried c44987ff31 home: swap to downloaded sections in offline mode 2026-10-04 00:30:06 +00:00
austinried d9aa18c37a home: play the whole genre list from a genre song row 2026-10-04 00:30:06 +00:00
austinried f62e8363de home: drop On repeat, restore starred, give artist lists the detail column 2026-10-04 00:30:06 +00:00
austinried e36a0e3de5 home: add an On repeat song row and move recently starred below genres 2026-10-04 00:30:06 +00:00
austinried b0a31d0291 home: show album more screens as lists with a section-relevant detail column 2026-10-04 00:30:06 +00:00
austinried ff236836a2 home polish: genres by most played in a wrapping flow, tighter album corners, spaced left-aligned artists 2026-10-04 00:30:06 +00:00
austinried 796e5e2827 queue the whole starred list from both starred song lists 2026-10-04 00:30:06 +00:00
austinried 3affe7e049 play the starred list from the home row, and reorder the home sections 2026-10-04 00:30:06 +00:00
austinried 82b62b8d9d fix the home tab text colour against the dark theme 2026-10-04 00:30:06 +00:00
austinried 378d6a3b47 add a home tab with local recommendation rows 2026-10-04 00:30:06 +00:00
austinried 26f58b7502 address review: share the test helpers, guard AppModuleTest cleanup
Use cancelAndJoinBlocking in the repo tests and AppModuleTest instead of hand-rolled runBlocking joins, put AppModuleTest's cleanup in a finally so a failed join cannot leave Main overridden and Koin started, and collapse the five polling await copies into one awaitUntil. upcomingItem now reads the queue snapshot under the lock but does the Room read outside it.
2026-10-04 00:30:06 +00:00
austinried 88b53821bd let tests own the worker coroutine scopes
The repositories and the playback controller each created a hidden SupervisorJob scope and exposed close() only so tests could join it before closing the DB; production never called close(). Inject the scope instead and have each test cancel and join the scope it passes, so no test-only teardown lives in the app. Koin now provides the app/io/playback scopes.

Also read upcomingItem under the controller lock, and give the wall-clock test waits one shared 30s budget instead of 5s per file: a loaded runner could starve the controller's single thread past the old deadline.
2026-10-04 00:30:06 +00:00
austinried 18dc929218 store multiple OpenSubsonic song genres
OpenSubsonic servers return a per-song <genres> array that was dropped,
leaving every song with only its first (or joined) legacy genre. Parse it
and store the ordered list in a song_genres table, keeping the single
genre column as the fallback for base Subsonic servers.

Album genres are derived by unioning the album's song genres, falling
back to the single song genre, so album-level genre never trusts the
per-server album genres[] behaviour.

Schema 21 -> 22, with parser, sync, unit and integration coverage.
2026-10-04 00:30:06 +00:00
austinried 010aa6d122 stop prefetching the next track's full artwork 2026-10-04 00:30:05 +00:00
austinried 4d4eb7b758 address review: count a play even when its scrobble fails
A submitted scrobble now counts locally regardless of the HTTP result
(offline already did), so a transient server error no longer drops the
play from the sorts. Last-played uses MAX(existing, at) so a backwards
clock step cannot move an album or artist back down the list.
2026-10-04 00:30:05 +00:00
austinried d3aaeca405 count a play locally when it is scrobbled
A submitted scrobble now bumps the song's playCount/played and its album
and artist ranks, so the play sorts move without waiting for a sync. The
sync then overwrites them with the server's own numbers, so the
increment cannot double-count.
2026-10-04 00:30:05 +00:00
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 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 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 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 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 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 dff2fe58e0 default to the Material You dark scheme instead of black and white 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 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