Commit Graph
962 Commits
Author SHA1 Message Date
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
austinried dd6b0ea00c retry a read that lands past a cold transcode
A transcoded stream is chunked with no length, so the app declares an upper-bound
length, and on the first request the server is still producing the stream: a seek
near that bound lands past the data and comes back as POSITION_OUT_OF_RANGE
(Navidrome answers 416 until it has cached the transcode). That first request is
what warms the cache, so give the load one retry after a second: the retry sees a
real Content-Length and the stream plays and seeks. Further out-of-range errors
are given up on rather than looped.
2026-10-04 00:30:04 +00:00
austinried 9a999498ab show a bitrate the codecs do not report, and name LATM AAC
A decoder often gives no bitrate, so the row vanished; for a downloaded file the
size over the track length fills it in. The AAC mime Android and media3 use is
audio/mp4a-latm, which read as "MP4A-LATM" rather than AAC.
2026-10-04 00:30:04 +00:00
austinried 7b43b73592 centre the info dialog's rows so the smaller labels line up with their values 2026-10-04 00:30:04 +00:00
austinried 76f3b39162 Revert "stop estimating a length for Ogg-based transcodes"
This reverts commit aeb08bf628cc98f81bd3084bce1a2a5da0a2e762.
2026-10-04 00:30:04 +00:00
austinried 73d38ddf3e even out the hero's side buttons around the play controls
The download and more blocks were different widths, so the play/shuffle pill
sat off centre and the download hugged its edge closer than the more button.
Both now take the same inset.
2026-10-04 00:30:04 +00:00
austinried b6a2f89d6f stop estimating a length for Ogg-based transcodes
Streaming Opus from Navidrome failed the first play with a read out of range,
then worked. Ogg seeks by binary-searching for the last page from the end of the
file, and the length the app estimates for a transcode overshoots the real
output, so that search reads past the data and the server answers 416. Opus,
Ogg and Vorbis now get no estimate: seeking falls back to what is buffered, but
playback is not scuppered by a length we cannot know. Downloads were unaffected
because they never carried the estimate.
2026-10-04 00:30:04 +00:00
austinried c3f4969911 tighten the info dialog's label column so the values get the room 2026-10-04 00:30:04 +00:00
austinried 2459d7cb93 replace only the queue items a delete actually changed
Deleting any download re-set the whole queue window, which makes the player
re-prepare the current item and audibly pauses. It now rebuilds only the items
whose local file appeared or went away, comparing what the window reports with
what the repository can resolve, and falls back to the full reload when the
window no longer lines up.
2026-10-04 00:30:04 +00:00
austinried eccf14aa58 name the codec, not the container, and tidy the info rows
MediaExtractor reports audio/raw for some containers it decodes itself, so a
downloaded FLAC showed "Raw" while the same stream showed FLAC. The file's own
header is now the source of the format when the extractor gives nothing useful,
covering FLAC, MP3, AAC/ALAC, Ogg, Vorbis, Opus, WAV and WebM - and for the
containers, the codec inside them (a WebM is Opus or Vorbis, not "WebM"),
matching what media3 reports for the stream.

The info dialog's label column is a fixed width in the accent colour at a
smaller size, so the values line up. The quality rows read format then bitrate,
the manage row loses its subtitle, and the switches get explicit off colours,
since the themed defaults left the knob invisible against some backgrounds.
2026-10-04 00:30:04 +00:00
austinried caf8369924 let the user allow downloads on mobile data
Downloads asked the platform for an unmetered network and nothing more, so
there was no way to run them on mobile. A setting under Downloads now decides,
defaulting to Wi-Fi only, and it also governs roaming: the platform permits
roaming by default, which would have contradicted the Wi-Fi-only choice.

The flag rides the enqueue request like the download quality does, cached in the
source repository so the poller does not read the preference per row.
2026-10-04 00:30:03 +00:00