Add a selectable Mornye theme with shared glass controls, dialogs, sheets, notifications, and collection headers while retaining Material components.
Update player artwork, lyrics transitions and highlighting, synchronized rolling time labels, volume controls, and compact navigation. Unify metadata, Library, search, and extension surfaces; preserve restored artwork and report extension load failures.
Validation: full Flutter analyzer clean; 713 tests passed and 4 skipped; pinned Dart formatting and diff checks passed; signed ARM64 and ARM32 release APKs built and verified.
Fixes#368.
Reopening a Spotify playlist from "recent access" showed no tracks,
while the first view (right after pasting the URL) worked fine.
Root cause traced across two layers:
1. Go: ExtURLHandleResult (the parsed shape of an extension's handleUrl()
return value) never captured a top-level `id` for the handled
resource. Track/album/artist results carry their own id inside their
nested metadata, but a plain playlist result has no such object, so
its id was silently dropped everywhere from the goja parser through
to the JSON the Dart side receives.
2. Dart: TrackState had nowhere to put that id even if it existed
(only playlistName), so recording a "recent" playlist access stored
the playlist's *name* as if it were its id. Reopening it later fed
that name into PlaylistScreen's provider-guessing logic
(legacyProviderIdFromResourceId, which only recognizes legacy
"provider:id" prefixes), which naturally failed and fell through to
a hardcoded Deezer metadata fetch using a Spotify playlist's name as
the resource id - guaranteed to return nothing.
Fix, matching the "generic API, not per-provider checks" architecture
in CONTRIBUTING.md:
- go_backend/extension_provider_wrapper.go: add ExtURLHandleResult.ID.
- go_backend/extension_goja_convert.go: parse it from the handler's
return value.
- go_backend/exports_extensions.go: surface it in the JSON response.
- lib/providers/track_provider.dart: add TrackState.playlistId,
populated from the response's `id` field for playlist results.
- lib/screens/home_tab.dart: record the real playlist id (falling back
to the name only if a provider never supplies one) and pass both the
id and the already-known provider id forward to PlaylistScreen.
- lib/screens/playlist_screen.dart: PlaylistScreen gains a
metadataProviderId param that takes priority over guessing from the
id's shape.
- lib/screens/home_tab_recent.dart: pass the recent-access entry's
stored providerId through when reopening a playlist.
- lib/utils/provider_resource_ids.dart: extract the
known-id-vs-guessed-id preference into a small, directly testable
resolvePreferredMetadataProviderId helper.
Note: this fixes the common case (an extension already reported its
own id as the source provider for the URL it handled). If a provider
never supplies an id for its handleUrl() playlist result, playlistId
stays null and behavior is unchanged from before this fix - no
regression, just not a complete fix for that narrower case, since
that would require changes in extension-side JS code outside this
repo.
Tests:
- go_backend/extension_goja_convert_url_handle_test.go: the new ID
field round-trips from a handler's return value, and stays empty
when the handler doesn't supply one.
- test/provider_resource_ids_test.dart: resolvePreferredMetadataProviderId
prefers a known provider id, falls back to the legacy-prefix guess,
treats a blank known id as unknown, and returns null for the
unprefixed-id-with-no-known-provider case from #368 itself.
Verification: go build/vet/test and flutter analyze/test all green.