Commit Graph
708 Commits
Author SHA1 Message Date
austinried c262e58a96 logging: fix SQL timing and upsert-conflict false alarms
Room's statement cache resets a statement when returning it to the cache, so starting the timer at reset counted idle time in the cache and reported normal queries as tens of seconds. Start timing on the first step instead, and finish on the completing step. Also downgrade Room @Upsert uniqueness-conflict errors (its insert-then-catch-then-update control flow) from ERROR to DEBUG, and log successful HTTP requests at VERBOSE so debug logs aren't flooded by cover-art requests.
2026-10-09 07:58:55 +00:00
austinried eb5019ea5e logging: use Share logs and restore its Flutter translations
Align the Settings label with the old Flutter wording and port the 19 existing settingsAboutShareLogs translations. The subtitle was new text with no prior translation, so drop it rather than ship an untranslated string.
2026-10-09 07:58:55 +00:00
austinried 8fe26a36b1 logging: make the writer total and harden export
Never let a log-write or export failure reach the uncaught handler: wrap the async writer and crash path, synchronize export with rotation, and tolerate vanished files. Redact the Logcat stack-trace copy, stop leaking rows=0 on writes, shut down the previous writer on re-init, and drop unused API. Strengthen tests for the batched write path, rotation count and throwable redaction.
2026-10-09 07:58:55 +00:00
austinried 7a890094fc logging: add file-based logging and a Send logs option
Capture logs to rotating app-private files with Subsonic credential and URL basic-auth redaction, mirror to Logcat, and capture uncaught crashes. Log slow SQL (>100ms), SQL errors, migrations and DB lifecycle via a decorating SQLiteDriver. Add a Send logs row in Settings that zips the logs and opens the share sheet through a FileProvider.
2026-10-09 07:58:54 +00:00
austinried 32360fac08 artwork: keep the cover request stable across recompositions
CoverArt keyed its remember blocks on the whole CoverArtRef, whose url is
freshly salted on every call. A recomposition therefore built a new
ImageRequest, Coil restarted the load and dropped the current drawable, so
the image flashed back to the letter placeholder before resolving from
cache. Key the request and the load-state remembers on the ref's stable
cacheKey instead.
2026-10-09 07:03:49 +00:00
austinried 206ac8988f net: include the Android release in the user agent 2026-10-09 06:33:22 +00:00
austinried 4fad6a78ec net: use the lowercase subtracks name in the user agent 2026-10-09 06:33:22 +00:00
austinried 2439368e4e net: pin the user agent value and test the shared client
Build the shared OkHttpClient through UserAgent.httpClient() so the unit test exercises the same client the app uses, assert the exact 'Subtracks (Android; <version>)' value rather than the property it writes, and run the test under the Android runner.
2026-10-09 06:33:22 +00:00
austinried 92c6f6945f net: use the AppName (platform; version) user agent form 2026-10-09 06:33:22 +00:00
austinried bcd7bbf23a net: send the subtracks user agent on every request
Only SubsonicClient tagged requests with 'subtracks/android'; playback (Media3 OkHttpDataSource), downloads (DownloadManager) and artwork (Coil/OkHttpArtworkFetcher) fell back to the OkHttp or platform default. Add a single UserAgent with the app version, apply it to the shared OkHttp client, the streaming data source factory and the DownloadManager request, and drop SubsonicClient's now-redundant header.
2026-10-09 06:33:22 +00:00
austinried 293561d81f settings: guard the About links and align the support label 2026-10-08 17:56:34 +00:00
austinried 2de31735f7 settings: use leading icons for the About rows and drop the heart emoji 2026-10-08 17:19:18 +00:00
austinried 327aa08763 settings: restore the About translations from the Flutter app 2026-10-08 17:19:18 +00:00
austinried 8aee86fc60 settings: restore the version, homepage and support links in About 2026-10-08 17:19:18 +00:00
austinried 05de10c912 clear the build warnings and fail on new ones
A clean build now emits no warnings of its own, and Kotlin
`allWarningsAsErrors` plus lint `warningsAsErrors` stop new ones from
sneaking back in.

- Kotlin: give SearchFilter's @StringRes parameter an explicit @param:
  target; use safe calls for File.parentFile in StringResourcesTest.
- Packaging: keep debug symbols for the prebuilt .so files that have no
  symbol table, which is what the "unable to strip" warning was about.
- Lint: move `modifier` to the first optional parameter across the Compose
  screens, use mutableIntStateOf for int state, read Resources via
  LocalResources, use the KTX Bitmap.get/String.toUri, drop the unused
  ic_action_name and ic_stat_name bitmaps and the dead -v24 qualifier on
  the notification vector, fix the pt-BR "album" accents and the unused
  Vietnamese/Thai plural quantities, and suppress the intentional
  cleartext/user-certificate, backup, exported-service and
  plural-candidate findings. Disable the version-update nags, which the
  dependency-update process owns.

The AboutLibraries plugin still resolves the variant classpaths during
configuration, so AGP keeps warning about that one; upstream in
mikepenz/AboutLibraries#1369.
2026-10-08 10:27:09 +00:00
austinried 174d79eeb7 artists: keep the label-to-count gap the same across artist types
The album count sits in the star/more button row, so the row is 48dp tall
only when those buttons are present, shifting the count for album artists
versus credited track artists. Pin the row to that height either way.
2026-10-04 01:49:55 +00:00
austinried 299457d8fc artists: tighten the header and drop the dead star/more on track artists
Lift the type label to the artist name's inset and centre the album count
on the star/more row. A credited track artist has no artists row, so its
star is disabled and more does nothing; hide both there.
2026-10-04 01:49:55 +00:00
austinried fd2e1cfd86 artists: show a credited track artist's name and type on their page
A track artist without an artists row had no name on the artist page, so
the header and the placeholder letter were blank. Derive the name from the
songs that credit them, and label the page 'Album artist' when they lead an
album or 'Artist' when they are only credited on tracks. The hero art keeps
the letter placeholder when there is no artist image.
2026-10-04 01:49:55 +00:00
austinried 2b08ae3b63 artists: browse and download the tracks a guest artist is credited on
A song's artist can differ from its album artist (compilations, guest
tracks). The artist page was built only from albums where the artist is
the album artist, so opening such an artist showed a blank page. Attribute
a song to both its album artist and its credited track artist across the
artist album list, shuffle queue, download status and the artists list
filter/badges, and show the listed album count on the detail page.
2026-10-04 01:49:55 +00:00
austinried d1ec74ad1e Add monochrome launcher icon layer for themed icons 2026-10-04 01:24:16 +00:00
austinried dbb6745b7b address review: replay one dismissal so a recreated screen still sees it
A notification tap can fire the dismissal before the route's collector
subscribes (activity recreated with a restored options sheet), and a
replay-zero broadcast drops it. Replaying one is a no-op on default state
and the new ContextMenuHost test guards it.
2026-10-04 00:56:53 +00:00
austinried af6979459b address review: signal transient dismissal with an event flow, not an int
An Int counter was already rejected for the now-playing launch signal; use
a broadcast Flow on the shared ContextMenuHost and collect it, so the
dismissal is an event rather than an ever-increasing number.
2026-10-04 00:56:53 +00:00
austinried 1b83a5ff93 now playing: dismiss transient UI when the notification opens it
Tapping the media notification opened now playing over whatever was on
screen, leaving the item context menu, the library options sheet, the
keyboard and any open dialog above it. On a now-playing request, clear
focus, dismiss the shared menus and close each route's dialog state.
2026-10-04 00:56:53 +00:00
austinried 9f2e14a9f9 Re-notify replay on every connectivity change, not just availability flips
conflate rather than distinctUntilChanged: switching between two
available networks (WiFi LAN server -> cellular -> WiFi) kept the signal
true, so the queue never drained until another trigger. Emit on each
callback instead, and drop the sink combine's distinctUntilChanged so a
repeated ready edge still flushes. Simplify flush to reuse the safe drain.
Add a regression test that replays on two true emissions with no false
between.
2026-10-04 00:37:29 +00:00
austinried 6662bdd514 Replay pending actions when connectivity is restored
A raw network drop and recovery never flipped the offline flag, so the
queue only drained on the next sink op, an offline-mode toggle or a
restart. Observe the default network's availability and use it, together
with the persisted offline preference, to trigger a replay. This signal
only drains pending actions; it never changes offline mode.
2026-10-04 00:37:29 +00:00
austinried f20937c83e Harden the pending-action queue after review
- Order direct stars against the queue so a successful online unstar is no
  longer overwritten by an older queued star, and serialize all queue
  mutations under one lock.
- Treat a SubsonicException with a negative code as a transport failure:
  queue it in setStar and keep it on flush instead of dropping it.
- Make coalescing atomic, keep the offline collector alive across errors,
  and drain from the persisted offline flag so a queued action flushes on
  a cold start that is already online.
- Give ServerActionSink.flush a default body and drop the no-op overrides.
2026-10-04 00:37:29 +00:00
austinried 0e70268129 Queue stars and scrobbles while the server is unreachable
Actions that cannot be delivered are persisted in a pending_actions
table and replayed once the server is reachable again. Stars coalesce so
only the latest state per item is sent, and replay happens on the next
successful action, when offline mode is turned off, or before a sync.
2026-10-04 00:37:29 +00:00
austinried 5d9d9e5d7e brand: refresh the app icon, logo and feature graphic 2026-10-04 00:30:08 +00:00
austinried 8e6c1593dc metadata: say Subsonic-compatible and use lowercase subtracks
Drop the "API" from the server compatibility wording, and lowercase the brand
in the title, description, old changelog and the app name to match the logo.
2026-10-04 00:30:08 +00:00
austinried 836d0636d1 search: tighten the filter bar's vertical padding 2026-10-04 00:30:08 +00:00
austinried f6e087b6ab search: list playlists before artists, albums and songs
Order the result sections smallest-first and apply the review fixes: pad
the filter bar for the navigation bar, keep at least one filter enabled,
hide the clear button on an empty field, drop the unused autoFocus and
bar-modifier parameters, and test the filter mask and section order.
2026-10-04 00:30:08 +00:00
austinried 5d81c2dc41 library: match the list options search rounding to the search field 2026-10-04 00:30:07 +00:00
austinried f54ea54566 search: outline the shared search field to match the list options entry 2026-10-04 00:30:07 +00:00
austinried 3bef469acc search: add a type filter bar and share the search field
Add a filter chip bar above the keyboard to toggle songs, albums, artists
and playlists independently, all on by default and scrolling when it
overflows. Extract one compact 48dp search field and use it in both the
cross-library screen and the library tabs.
2026-10-04 00:30:07 +00:00
austinried 12ad62813e search: localize the cross-library search strings 2026-10-04 00:30:07 +00:00
austinried b25b9deacd search: address review findings
Prefix result keys per type so ids shared across songs, albums, artists
and playlists cannot collide and crash the list; add a loading state so
a query in flight no longer reads as no results; gate on code points to
match SQLite's length(); bind the three-character floor to one constant;
label the search field for TalkBack; and drop the vacuous trigram-plan
test. Cancel the ViewModel scope in its test teardown so it cannot leak
into later Robolectric tests.
2026-10-04 00:30:07 +00:00
austinried c1081b4ae7 search: require three characters and play songs in album context
Drop the library-wide instr fallback: below three characters the
cross-type search now returns nothing instead of scanning every table,
and the screen asks for more input. Lock in that a song result plays its
album context rather than a search queue.
2026-10-04 00:30:07 +00:00
austinried 5a3d5ded78 search: expose cross-library search from a home FAB
Add FTS5-backed search across songs, albums, artists and playlists,
reachable from a search FAB on the Home tab. Terms of three characters
or more run through the trigram indices; shorter terms fall back to the
instr scan, matching the per-tab filters. Results are grouped by type
and route into detail screens or song playback.
2026-10-04 00:30:07 +00:00
austinried 98a3d336a8 port the library tab labels from the old plural names
Albums/Artists/Playlists were new keys because the old Flutter strings
were plurals, so their translations went unused. Take the `other` form of
resourcesAlbumName/resourcesArtistName/resourcesPlaylistName into the three
plain-string resources for the 26-27 locales that translated them. Arabic
`resources_album_name` and Hungarian `resources_playlist_name` stay English
because their upstream `other` form is empty.
2026-10-04 00:30:07 +00:00
austinried 699a6e86f5 address review: localize connection errors, tighten i18n guard, drop dead resources
- AddSourceViewModel resolves SubsonicException.uiMessage too, so malformed/
  HTTP/truncated responses show localized text instead of the raw parser
  message; add a regression test.
- StringResourcesTest now requires an 'other' quantity in every plural and
  asserts exact placeholder (index and conversion) equality, both of which
  could previously pass while crashing getQuantityString at runtime.
- Map the pre-enum song_downloads.error sentences back to their DownloadError
  so existing failed rows keep their specific message.
- Localize the media-session star/unstar button label.
- Remove 12 unreferenced resources (queue name, filter year, sync_failed, and
  the unused codec_* strings; codec labels stay in AudioEncoding).
- Make buildTree's fallback arguments required; only tests used the defaults.
2026-10-04 00:30:07 +00:00
austinried 3162ac07d5 localize transient messages and the download notification
Route user-facing messages produced below the UI through a typed UiMessage
(string-resource id plus format args) instead of pre-built English strings:
showMessage, SyncStatus.Failed, playback errors and download toasts now
resolve in the current locale at the UI edge. Subsonic and page-cap
exceptions carry a UiMessage alongside their developer message, and the
download progress notification is formatted from resources.

Internal invariants and log tags stay plain strings.
2026-10-04 00:30:07 +00:00
austinried a1e45380cc leave the fixed ID3v1 genre names untranslated 2026-10-04 00:30:07 +00:00
austinried 24757bda3f localize user-facing text into string resources
Move every exposed UI string into res/values/strings.xml (+ plurals) and
read it via stringResource/pluralStringResource or getString, with 27
locale files carried over from the Flutter app's Weblate translations.
Only strings whose English is unchanged reuse an old translation; reworded
ones get fresh keys.

Persisted download failures become a DownloadError enum mapped to string
resources (stored by name, unknown legacy values degrade to Failed), the
per-app locale config is generated by AGP, and StringResourcesTest guards
locale keys and placeholders.
2026-10-04 00:30:07 +00:00
austinried 88cd3764bf align the swipe peek with the skip and harden its edges
Resolve previousItem/upcomingItem through the same offline download scan the transport uses, so the peeking art is the track the swipe will actually play. Clear a frozen strip when a fallback swipe fires, prefer the dragged direction over an opposite fling, and simplify the hand-back and ratio prefetch.
2026-10-04 00:30:07 +00:00
austinried 73add672e1 keep the previous artwork visible and stop the neighbour flashing mid-swipe
Swipe-back now goes to the previous track (previousTrack) instead of restarting, so the previous peek stays visible past the restart threshold. The frozen strip only hands back once the live neighbour props match it, so a stale neighbour can no longer flash while the slide settles.
2026-10-04 00:30:07 +00:00
austinried b4539b9ba5 peek the adjacent artwork while swiping the now playing screen
Resolve the previous/next queue items and render a three-slot cover strip that follows the drag, so the neighbouring cover slides in from the side on release. The strip is rotated optimistically at commit and hands back to live state once the controller reports the new track (or after a grace period), so a prop update mid-animation cannot desync it. Ratio prefetch and layered cover loading keep the incoming art from snapping as it loads.
2026-10-04 00:30:07 +00:00
austinried 317a8f4e09 swipe between tracks on the now playing screen
Left/right drags on the cover art skip to the next/previous track, mirroring the transport buttons; the cover follows the finger and settles back on release. Also drops the ripple on the title/artist block.
2026-10-04 00:30:07 +00:00
austinried 4ddbefe7cd downloads: address review on the cancel/progress rows
Name the cancel and delete buttons after the row so screen readers can
tell them apart, show an indeterminate bar when a running track has no
known total, and drop the no-op media refresh on cancel. Trim the dead
size alias and a redundant guard, and cover a queued row with no engine
id plus a failed track in the progress denominator.
2026-10-04 00:30:07 +00:00
austinried 196733f9a7 downloads: base album/artist progress on completed tracks
The aggregate summed bytes over only the currently active songs, so it
reset each time a track finished and the next one started. Count completed
tracks (plus the fraction of each running track) over the node's full track
count instead, so it climbs one track at a time.
2026-10-04 00:30:07 +00:00
austinried 5e074db834 downloads: show progress and cancel on album and artist rows too
A node with any active download now shows the aggregate progress bar and
cancels all its active downloads instead of offering delete; a node with
only finished downloads keeps the trash.
2026-10-04 00:30:07 +00:00