Files
SpotiFLAC-Mobile/lib/utils/provider_resource_ids.dart
T
Abelardo Ramirez bed95efb29 fix(playlists): thread the source provider id through to recent playlists
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.
2026-08-11 17:29:41 +07:00

38 lines
1.5 KiB
Dart

import 'package:spotiflac_android/constants/music_services.dart';
/// Maps a legacy prefixed resource id (e.g. "deezer:123") to its provider id,
/// or null when the value carries no known provider prefix.
String? legacyProviderIdFromResourceId(String value) {
if (value.startsWith('deezer:')) return MusicServices.deezer;
if (value.startsWith('qobuz:')) return MusicServices.qobuz;
if (value.startsWith('tidal:')) return MusicServices.tidal;
if (value.startsWith('spotify:')) return MusicServices.spotify;
return null;
}
/// Strips a leading "provider:" prefix from a resource id, if present.
String stripPrefixedResourceId(String value) {
final colonIndex = value.indexOf(':');
if (colonIndex <= 0 || colonIndex == value.length - 1) {
return value;
}
return value.substring(colonIndex + 1);
}
/// Resolves the metadata provider id to fetch a resource from, preferring a
/// caller-supplied [knownProviderId] (e.g. from a recent-access entry that
/// already recorded its source) over guessing from [resourceId]'s shape.
///
/// [legacyProviderIdFromResourceId] only recognizes legacy-prefixed ids
/// ("deezer:...", "spotify:...", etc.); anything else - including a plain,
/// unprefixed id from a provider that never used that scheme - resolves to
/// null unless the caller already knows the provider.
String? resolvePreferredMetadataProviderId(
String? knownProviderId,
String resourceId,
) {
final known = knownProviderId?.trim();
if (known != null && known.isNotEmpty) return known;
return legacyProviderIdFromResourceId(resourceId);
}