Cleared 5 errors + 1 warning + 1 info from CI flutter analyze on 5114a81:
- prefetcher.dart: Riverpod listener callbacks used (_, _) for two
placeholder params — Dart 3 enforces unique names. Changed to (_, __).
- prefetcher.dart: Riverpod 3's AsyncValue exposes `.value` (nullable)
not `.valueOrNull`. Three call sites updated.
- sync_controller.dart: doc comment had `?since=<cursor>` → analyzer
warned about unintended HTML. Wrapped in backticks with `{cursor}`.
- sync_controller.dart: delete loop over heterogeneous Table list
inferred as List<Table>; drift's delete() expects TableInfo. Unrolled
to explicit per-table deletes.
- audio_cache_manager_test.dart: db.into(...).insertAll([...]) doesn't
exist on InsertStatement — only insert/insertOnConflictUpdate. Used
db.batch((b) => b.insertAll(table, [...])) instead, in two test cases.
- audio_handler.dart: LockCachingAudioSource is marked experimental in
just_audio. Added // ignore: experimental_member_use — operator
acknowledged the experimental status during brainstorming and we're
the project; CI's --fatal-infos would otherwise gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_buildAudioSource is now async and cache-aware:
1. Cache hit → AudioSource.uri(file://path)
2. Cache miss with manager → LockCachingAudioSource (cache-as-you-play)
3. No manager configured → plain AudioSource.uri (legacy fallback)
playerActionsProvider.playTracks now passes audioCacheManager into
configure() alongside coverCache. setQueueFromTracks awaits the source
build (Future.wait over the track list).
Out of scope: registering an index row when LockCachingAudioSource
finishes downloading (no clean hook from just_audio). Prefetcher /
Download buttons cover the index path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small data-layer additions for the upcoming track-actions menu:
- PlaylistsApi.appendTracks(playlistId, trackIds) wraps
POST /api/playlists/{id}/tracks for the "Add to playlist" action.
- audio_handler gains playNext (insertAudioSource at currentIndex+1)
and enqueue (addAudioSource) — both also push the audio_service
queue notifier so the queue-screen UI stays in sync.
- The AudioSource construction was extracted into a private
_buildAudioSource helper so setQueueFromTracks / playNext / enqueue
share one source-building path.
- PlayerActions exposes playNext / enqueue for menu use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audio handler accepts an AlbumCoverCache via configure() and uses it
to populate MediaItem.artUri with a file:// URI to a locally cached
album cover. Lock screen, Bluetooth car displays, Wear OS, and CarPlay
now show the album cover for the currently playing track instead of
the system's generic music icon.
Flow:
- _toMediaItem stashes album_id in MediaItem.extras
- After setQueueFromTracks pushes the queue + initial mediaItem, fires
_loadArtForCurrentItem async (doesn't block playback)
- Subscribes to _player.currentIndexStream so track advances trigger
the same loader for the new current item
- _loadArtForCurrentItem early-returns if cache is null, no current
item, no album_id, or artUri already set; otherwise calls
cache.getOrFetch and pushes an updated MediaItem with artUri set
- Race guard: if the user skipped to another track while the fetch was
in flight, the result is discarded
Closes the visible "generic music icon on lock screen" gap operator
flagged when first-testing on a real device.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues from on-device testing against prod HTTPS:
1. ServerImage was resolving cover URLs correctly
(https://minstrel.fabledsword.com/api/albums/.../cover) but the server
returned 401 because Image.network doesn't carry the session token
automatically the way the browser sends cookies. Forwards the stored
session token as an Authorization: Bearer header. No-op when no token
is present.
2. The "Cleartext HTTP traffic to 127.0.0.1" audio error reproduced even
after the previous defensive check landed, which means the URL handed
to ExoPlayer has a valid scheme+host (just the wrong host). The check
only catches scheme-less URLs, so it didn't fire. Added debugPrint
logging at configure() and setQueueFromTracks() time to show the
actual baseUrl + per-track resolved URL on the next reproduction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover-art Image.network calls were passing server-relative URLs
(/api/albums/<id>/cover) straight to NetworkImage, which interprets
"no scheme" as file:/// and crashes with "No host specified in URI".
Same root cause regardless of HTTPS or HTTP server.
ServerImage wraps Image.network with a Riverpod read of
serverUrlProvider and prefixes the configured base URL. Absolute URLs
(e.g. discover screen's Lidarr image_urls) pass through unchanged.
Three call sites updated: album_card, artist_card, playlists_list_screen.
discover_screen left as-is — its row.imageUrl is already absolute (Lidarr
returns full URLs from MusicBrainz / Spotify metadata) and it has a
meaningful errorBuilder that ServerImage doesn't expose.
Also adds a defensive check in audio_handler.setQueueFromTracks: if
the constructed stream URL ends up scheme-less, throw a StateError
naming baseUrl + track.streamUrl + track.id instead of letting it
fall through to ExoPlayer which surfaces a confusing "Cleartext HTTP
traffic to 127.0.0.1 not permitted" error (Android's URL parser
defaults a scheme-less URI to localhost). User reported this exact
confusing error against an HTTPS prod server; the better message
will pinpoint where the empty baseUrl comes from on next reproduction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. lib/app.dart + lib/shared/routing.dart — buildRouter takes a Ref but was
being called from a ConsumerWidget's build() with a WidgetRef. Add a
routerProvider; the widget watches it instead of constructing the
router from its own ref. Real bug — would have crashed compile in
slice 1, just never compiled until CI ran.
2. lib/player/audio_handler.dart — override of AudioHandler.seek used
`p` for the Duration; rename to `position` to match the base class
(avoid_renaming_method_parameters lint).
3. lib/theme/theme_extension.dart — fromTokens() returned a non-const
constructor; all inputs are const so make the call const too
(prefer_const_constructors).
4. test/library/home_screen_test.dart — same const-constructor lint on
the HomeData test fixture.
5. test/smoke_test.dart — drop the unused
`import 'package:flutter/material.dart'`.
These are exactly the kind of issues the no-in-task-tests rule shifted
to CI; this is the first run that actually exercises the analyzer
against the slice 1 code.
audio_service runs the handler in a background isolate; UI watches
playbackState/mediaItem/queue streams through Riverpod. AndroidManifest
gets the foreground-service media-playback permission; Info.plist gets
UIBackgroundModes=audio. Bearer token attached to stream URLs via
just_audio's per-source headers.