mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-12 02:37:18 +02:00
refactor: abstract built-in providers into generic registry and unify platform bridge API
Replace hardcoded Tidal/Qobuz switch/case with builtInProviderSpec registry pattern. Unify searchTidalAll/searchQobuzAll into searchProviderAll, getDeezerMetadata/getTidalMetadata/getQobuzMetadata into getProviderMetadata, and parseDeezerUrl/parseQobuzUrl/parseTidalUrl into parseProviderUrl. Remove extension-specific getAlbum/Playlist/ArtistWithExtension in favor of generic getProviderMetadata routing. Extract provider UI helpers into provider_ui_utils.dart. Preserve track_number fallback for zero-value TrackNumber in album/playlist track lists.
This commit is contained in:
1 parent
e87f7a1177
commit
6895e45f2c
21 files changed
+1180
-1365
No files matched your search
@@ -176,83 +176,12 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
Future<void> _fetchTracks() async {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
if (widget.albumId.startsWith('deezer:')) {
|
||||
final deezerAlbumId = widget.albumId.replaceFirst('deezer:', '');
|
||||
final metadata = await PlatformBridge.getDeezerMetadata(
|
||||
final directProviderId = _directMetadataProviderId();
|
||||
if (directProviderId != null) {
|
||||
final metadata = await PlatformBridge.getProviderMetadata(
|
||||
directProviderId,
|
||||
'album',
|
||||
deezerAlbumId,
|
||||
);
|
||||
final trackList = metadata['track_list'] as List<dynamic>;
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId'])
|
||||
?.toString();
|
||||
final albumType = normalizeOptionalString(
|
||||
albumInfo?['album_type']?.toString(),
|
||||
);
|
||||
final totalTracks = albumInfo?['total_tracks'] as int?;
|
||||
final tracks = trackList
|
||||
.map(
|
||||
(t) => _parseTrack(
|
||||
t as Map<String, dynamic>,
|
||||
albumTypeFallback: albumType,
|
||||
totalTracksFallback: totalTracks,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
_AlbumCache.set(widget.albumId, tracks);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_tracks = tracks;
|
||||
_artistId = artistId;
|
||||
_albumType = albumType;
|
||||
_albumTotalTracks = totalTracks;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
} else if (widget.albumId.startsWith('qobuz:')) {
|
||||
final qobuzAlbumId = widget.albumId.replaceFirst('qobuz:', '');
|
||||
final metadata = await PlatformBridge.getQobuzMetadata(
|
||||
'album',
|
||||
qobuzAlbumId,
|
||||
);
|
||||
final trackList = metadata['track_list'] as List<dynamic>;
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
final artistId = (albumInfo?['artist_id'] ?? albumInfo?['artistId'])
|
||||
?.toString();
|
||||
final albumType = normalizeOptionalString(
|
||||
albumInfo?['album_type']?.toString(),
|
||||
);
|
||||
final totalTracks = albumInfo?['total_tracks'] as int?;
|
||||
final tracks = trackList
|
||||
.map(
|
||||
(t) => _parseTrack(
|
||||
t as Map<String, dynamic>,
|
||||
albumTypeFallback: albumType,
|
||||
totalTracksFallback: totalTracks,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
_AlbumCache.set(widget.albumId, tracks);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_tracks = tracks;
|
||||
_artistId = artistId;
|
||||
_albumType = albumType;
|
||||
_albumTotalTracks = totalTracks;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
} else if (widget.albumId.startsWith('tidal:')) {
|
||||
final tidalAlbumId = widget.albumId.replaceFirst('tidal:', '');
|
||||
final metadata = await PlatformBridge.getTidalMetadata(
|
||||
'album',
|
||||
tidalAlbumId,
|
||||
_metadataResourceId(directProviderId),
|
||||
);
|
||||
final trackList = metadata['track_list'] as List<dynamic>;
|
||||
final albumInfo = metadata['album_info'] as Map<String, dynamic>?;
|
||||
@@ -332,6 +261,24 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
String? _directMetadataProviderId() {
|
||||
if (widget.extensionId != null && widget.extensionId!.isNotEmpty) {
|
||||
return widget.extensionId;
|
||||
}
|
||||
if (widget.albumId.startsWith('deezer:')) return 'deezer';
|
||||
if (widget.albumId.startsWith('qobuz:')) return 'qobuz';
|
||||
if (widget.albumId.startsWith('tidal:')) return 'tidal';
|
||||
return null;
|
||||
}
|
||||
|
||||
String _metadataResourceId(String providerId) {
|
||||
final prefixed = '$providerId:';
|
||||
if (widget.albumId.startsWith(prefixed)) {
|
||||
return widget.albumId.substring(prefixed.length);
|
||||
}
|
||||
return widget.albumId;
|
||||
}
|
||||
|
||||
Track _parseTrack(
|
||||
Map<String, dynamic> data, {
|
||||
String? albumTypeFallback,
|
||||
@@ -366,12 +313,7 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
|
||||
}
|
||||
|
||||
String? _recommendedDownloadService() {
|
||||
if (widget.extensionId != null && widget.extensionId!.isNotEmpty) {
|
||||
return widget.extensionId;
|
||||
}
|
||||
if (widget.albumId.startsWith('tidal:')) return 'tidal';
|
||||
if (widget.albumId.startsWith('qobuz:')) return 'qobuz';
|
||||
return null;
|
||||
return _directMetadataProviderId();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+33
-114
@@ -154,14 +154,27 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
}
|
||||
|
||||
String? _recommendedDownloadService() {
|
||||
return _directMetadataProviderId();
|
||||
}
|
||||
|
||||
String? _directMetadataProviderId() {
|
||||
if (widget.extensionId != null && widget.extensionId!.isNotEmpty) {
|
||||
return widget.extensionId;
|
||||
}
|
||||
if (widget.artistId.startsWith('tidal:')) return 'tidal';
|
||||
if (widget.artistId.startsWith('deezer:')) return 'deezer';
|
||||
if (widget.artistId.startsWith('qobuz:')) return 'qobuz';
|
||||
if (widget.artistId.startsWith('tidal:')) return 'tidal';
|
||||
return null;
|
||||
}
|
||||
|
||||
String _metadataResourceId(String providerId) {
|
||||
final prefixed = '$providerId:';
|
||||
if (widget.artistId.startsWith(prefixed)) {
|
||||
return widget.artistId.substring(prefixed.length);
|
||||
}
|
||||
return widget.artistId;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -250,51 +263,13 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
String? headerImage;
|
||||
int? listeners;
|
||||
|
||||
if (widget.artistId.startsWith('deezer:')) {
|
||||
final deezerArtistId = widget.artistId.replaceFirst('deezer:', '');
|
||||
final metadata = await PlatformBridge.getDeezerMetadata(
|
||||
if (_directMetadataProviderId() != null) {
|
||||
final providerId = _directMetadataProviderId()!;
|
||||
final artistData = await PlatformBridge.getProviderMetadata(
|
||||
providerId,
|
||||
'artist',
|
||||
deezerArtistId,
|
||||
_metadataResourceId(providerId),
|
||||
);
|
||||
final albumsList = metadata['albums'] as List<dynamic>;
|
||||
albums = albumsList
|
||||
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
|
||||
.toList();
|
||||
} else if (widget.artistId.startsWith('qobuz:')) {
|
||||
final qobuzArtistId = widget.artistId.replaceFirst('qobuz:', '');
|
||||
final metadata = await PlatformBridge.getQobuzMetadata(
|
||||
'artist',
|
||||
qobuzArtistId,
|
||||
);
|
||||
final albumsList = metadata['albums'] as List<dynamic>;
|
||||
albums = albumsList
|
||||
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
|
||||
.toList();
|
||||
final artistInfo = metadata['artist_info'] as Map<String, dynamic>?;
|
||||
headerImage = artistInfo?['images'] as String?;
|
||||
} else if (widget.artistId.startsWith('tidal:')) {
|
||||
final tidalArtistId = widget.artistId.replaceFirst('tidal:', '');
|
||||
final metadata = await PlatformBridge.getTidalMetadata(
|
||||
'artist',
|
||||
tidalArtistId,
|
||||
);
|
||||
final albumsList = metadata['albums'] as List<dynamic>;
|
||||
albums = albumsList
|
||||
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
|
||||
.toList();
|
||||
final artistInfo = metadata['artist_info'] as Map<String, dynamic>?;
|
||||
headerImage = artistInfo?['images'] as String?;
|
||||
} else if (widget.extensionId != null && widget.extensionId!.isNotEmpty) {
|
||||
final result = await PlatformBridge.getArtistWithExtension(
|
||||
widget.extensionId!,
|
||||
widget.artistId,
|
||||
);
|
||||
|
||||
if (result == null) {
|
||||
throw Exception('Failed to load artist from extension');
|
||||
}
|
||||
|
||||
final artistData = result;
|
||||
final albumsList = artistData['albums'] as List<dynamic>? ?? [];
|
||||
albums = albumsList
|
||||
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
|
||||
@@ -314,11 +289,16 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
final artistInfo = artistData['artist_info'] as Map<String, dynamic>?;
|
||||
headerImage =
|
||||
artistInfo?['images'] as String? ??
|
||||
artistInfo?['header_image'] as String? ??
|
||||
artistInfo?['cover_url'] as String? ??
|
||||
artistData['header_image'] as String? ??
|
||||
artistData['cover_url'] as String? ??
|
||||
artistData['image_url'] as String?;
|
||||
listeners = artistData['listeners'] as int?;
|
||||
listeners =
|
||||
artistInfo?['listeners'] as int? ?? artistData['listeners'] as int?;
|
||||
} else {
|
||||
final url = 'https://open.spotify.com/artist/${widget.artistId}';
|
||||
final result = await PlatformBridge.handleURLWithExtension(url);
|
||||
@@ -1051,42 +1031,16 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
}
|
||||
|
||||
Future<List<Track>> _fetchAlbumTracks(ArtistAlbum album) async {
|
||||
if (album.providerId != null && album.providerId!.isNotEmpty) {
|
||||
final result = await PlatformBridge.getAlbumWithExtension(
|
||||
album.providerId!,
|
||||
album.id,
|
||||
);
|
||||
if (result != null && result['tracks'] != null) {
|
||||
final tracksList = result['tracks'] as List<dynamic>;
|
||||
final parsedTracks = tracksList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>, album: album))
|
||||
.toList();
|
||||
return parsedTracks;
|
||||
}
|
||||
} else if (album.id.startsWith('deezer:')) {
|
||||
final deezerId = album.id.replaceFirst('deezer:', '');
|
||||
final metadata = await PlatformBridge.getDeezerMetadata(
|
||||
final providerId = album.providerId;
|
||||
if (providerId != null && providerId.isNotEmpty) {
|
||||
final resourceId = album.id.startsWith('$providerId:')
|
||||
? album.id.substring(providerId.length + 1)
|
||||
: album.id;
|
||||
final metadata = await PlatformBridge.getProviderMetadata(
|
||||
providerId,
|
||||
'album',
|
||||
deezerId,
|
||||
resourceId,
|
||||
);
|
||||
if (metadata['tracks'] != null) {
|
||||
final tracksList = metadata['tracks'] as List<dynamic>;
|
||||
return tracksList
|
||||
.map((t) => _parseTrackFromDeezer(t as Map<String, dynamic>, album))
|
||||
.toList();
|
||||
}
|
||||
} else if (album.id.startsWith('qobuz:')) {
|
||||
final qobuzId = album.id.replaceFirst('qobuz:', '');
|
||||
final metadata = await PlatformBridge.getQobuzMetadata('album', qobuzId);
|
||||
if (metadata['track_list'] != null) {
|
||||
final tracksList = metadata['track_list'] as List<dynamic>;
|
||||
return tracksList
|
||||
.map((t) => _parseTrack(t as Map<String, dynamic>, album: album))
|
||||
.toList();
|
||||
}
|
||||
} else if (album.id.startsWith('tidal:')) {
|
||||
final tidalId = album.id.replaceFirst('tidal:', '');
|
||||
final metadata = await PlatformBridge.getTidalMetadata('album', tidalId);
|
||||
if (metadata['track_list'] != null) {
|
||||
final tracksList = metadata['track_list'] as List<dynamic>;
|
||||
return tracksList
|
||||
@@ -1106,41 +1060,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
return [];
|
||||
}
|
||||
|
||||
Track _parseTrackFromDeezer(Map<String, dynamic> data, ArtistAlbum album) {
|
||||
int durationMs = 0;
|
||||
final durationValue = data['duration'];
|
||||
final artistData = data['artist'];
|
||||
final artistName = artistData is Map<String, dynamic>
|
||||
? (artistData['name'] as String? ?? widget.artistName)
|
||||
: (artistData?.toString() ?? widget.artistName);
|
||||
if (durationValue is int) {
|
||||
durationMs = durationValue * 1000; // Deezer returns seconds
|
||||
} else if (durationValue is double) {
|
||||
durationMs = (durationValue * 1000).toInt();
|
||||
}
|
||||
|
||||
return Track(
|
||||
id: 'deezer:${data['id']}',
|
||||
name: (data['title'] ?? data['name'] ?? '').toString(),
|
||||
artistName: artistName,
|
||||
albumName: album.name,
|
||||
albumArtist: null,
|
||||
artistId: widget.artistId,
|
||||
albumId: album.id.isNotEmpty ? album.id : null,
|
||||
coverUrl: album.coverUrl,
|
||||
isrc: data['isrc']?.toString(),
|
||||
duration: (durationMs / 1000).round(),
|
||||
trackNumber:
|
||||
data['track_position'] as int? ?? data['track_number'] as int?,
|
||||
discNumber: data['disk_number'] as int? ?? data['disc_number'] as int?,
|
||||
totalDiscs: data['total_discs'] as int?,
|
||||
releaseDate: album.releaseDate,
|
||||
albumType: album.albumType,
|
||||
totalTracks: album.totalTracks,
|
||||
composer: data['composer']?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(
|
||||
BuildContext context,
|
||||
ColorScheme colorScheme, {
|
||||
|
||||
+71
-104
@@ -31,6 +31,7 @@ import 'package:spotiflac_android/widgets/download_service_picker.dart';
|
||||
import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/utils/clickable_metadata.dart';
|
||||
import 'package:spotiflac_android/utils/provider_ui_utils.dart';
|
||||
|
||||
class HomeTab extends ConsumerStatefulWidget {
|
||||
const HomeTab({super.key});
|
||||
@@ -481,13 +482,14 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
final explicit = explicitSearchProvider?.trim();
|
||||
if (explicit != null &&
|
||||
explicit.isNotEmpty &&
|
||||
(_builtInSearchProviders.contains(explicit) ||
|
||||
(isBuiltInSearchProvider(explicit) ||
|
||||
extensions.any(
|
||||
(ext) => ext.enabled && ext.hasCustomSearch && ext.id == explicit,
|
||||
))) {
|
||||
return explicit;
|
||||
}
|
||||
return _defaultSearchExtension(extensions)?.id ?? 'tidal';
|
||||
return _defaultSearchExtension(extensions)?.id ??
|
||||
defaultBuiltInSearchProviderId;
|
||||
}
|
||||
|
||||
String? _sanitizeSearchFilterForProvider(
|
||||
@@ -503,7 +505,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
if (currentSearchProvider == null ||
|
||||
currentSearchProvider.isEmpty ||
|
||||
_builtInSearchProviders.contains(currentSearchProvider)) {
|
||||
isBuiltInSearchProvider(currentSearchProvider)) {
|
||||
switch (canonicalFilter) {
|
||||
case 'track':
|
||||
case 'artist':
|
||||
@@ -695,8 +697,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
|
||||
if (searchProvider == null || searchProvider.isEmpty) return false;
|
||||
|
||||
// Built-in providers (tidal, qobuz) also support live search
|
||||
if (_builtInSearchProviders.contains(searchProvider)) return true;
|
||||
if (isBuiltInSearchProvider(searchProvider)) return true;
|
||||
|
||||
final extension = extState.extensions
|
||||
.where((e) => e.id == searchProvider && e.enabled)
|
||||
@@ -755,8 +756,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
}
|
||||
}
|
||||
|
||||
static const _builtInSearchProviders = {'tidal', 'qobuz'};
|
||||
|
||||
Future<void> _performSearch(String query, {String? filterOverride}) async {
|
||||
final settings = ref.read(settingsProvider);
|
||||
final extState = ref.read(extensionProvider);
|
||||
@@ -795,8 +794,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
_invalidateSearchSortCaches();
|
||||
|
||||
final isBuiltInProvider =
|
||||
searchProvider != null &&
|
||||
_builtInSearchProviders.contains(searchProvider);
|
||||
searchProvider != null && isBuiltInSearchProvider(searchProvider);
|
||||
|
||||
final isExtensionEnabled =
|
||||
searchProvider != null &&
|
||||
@@ -3347,11 +3345,9 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
}
|
||||
|
||||
if (searchProvider != null && searchProvider.isNotEmpty) {
|
||||
if (searchProvider == 'tidal') {
|
||||
return 'Search with Tidal...';
|
||||
}
|
||||
if (searchProvider == 'qobuz') {
|
||||
return 'Search with Qobuz...';
|
||||
final builtIn = builtInProviderSpecForId(searchProvider);
|
||||
if (builtIn != null && builtIn.supportsSearch) {
|
||||
return 'Search with ${builtIn.displayName}...';
|
||||
}
|
||||
|
||||
final ext = extState.extensions
|
||||
@@ -3566,16 +3562,19 @@ class _SearchProviderDropdown extends ConsumerWidget {
|
||||
final searchProviders = extensions
|
||||
.where((ext) => ext.enabled && ext.hasCustomSearch)
|
||||
.toList();
|
||||
final builtInProviders = builtInSearchProviderSpecs;
|
||||
final primarySearchExtension = _defaultSearchExtension(searchProviders);
|
||||
final defaultProviderTarget =
|
||||
primarySearchExtension?.displayName ?? 'Tidal';
|
||||
primarySearchExtension?.displayName ??
|
||||
defaultBuiltInSearchProviderDisplayName ??
|
||||
context.l10n.extensionDefaultProvider;
|
||||
final defaultProviderLabel =
|
||||
'${context.l10n.extensionsHomeFeedAuto} ($defaultProviderTarget)';
|
||||
final defaultProviderIconPath = primarySearchExtension?.iconPath;
|
||||
final currentProvider =
|
||||
rawCurrentProvider != null &&
|
||||
rawCurrentProvider.isNotEmpty &&
|
||||
({'tidal', 'qobuz'}.contains(rawCurrentProvider) ||
|
||||
(isBuiltInSearchProvider(rawCurrentProvider) ||
|
||||
searchProviders.any((e) => e.id == rawCurrentProvider))
|
||||
? rawCurrentProvider
|
||||
: null;
|
||||
@@ -3587,9 +3586,8 @@ class _SearchProviderDropdown extends ConsumerWidget {
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
const builtInProviders = {'tidal', 'qobuz'};
|
||||
final isBuiltInProvider =
|
||||
currentProvider != null && builtInProviders.contains(currentProvider);
|
||||
currentProvider != null && isBuiltInSearchProvider(currentProvider);
|
||||
|
||||
IconData displayIcon = Icons.search;
|
||||
String? iconPath;
|
||||
@@ -3612,7 +3610,7 @@ class _SearchProviderDropdown extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
} else if (isBuiltInProvider) {
|
||||
displayIcon = Icons.music_note;
|
||||
displayIcon = resolveProviderIcon(currentProvider);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
@@ -3679,58 +3677,33 @@ class _SearchProviderDropdown extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'tidal',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.music_note,
|
||||
size: 20,
|
||||
color: currentProvider == 'tidal'
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Tidal',
|
||||
style: TextStyle(
|
||||
fontWeight: currentProvider == 'tidal'
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
...builtInProviders.map(
|
||||
(provider) => PopupMenuItem<String>(
|
||||
value: provider.id,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
resolveProviderIcon(provider.id),
|
||||
size: 20,
|
||||
color: currentProvider == provider.id
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
provider.displayName,
|
||||
style: TextStyle(
|
||||
fontWeight: currentProvider == provider.id
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (currentProvider == 'tidal')
|
||||
Icon(Icons.check, size: 18, color: colorScheme.primary),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'qobuz',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.music_note,
|
||||
size: 20,
|
||||
color: currentProvider == 'qobuz'
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Qobuz',
|
||||
style: TextStyle(
|
||||
fontWeight: currentProvider == 'qobuz'
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (currentProvider == 'qobuz')
|
||||
Icon(Icons.check, size: 18, color: colorScheme.primary),
|
||||
],
|
||||
if (currentProvider == provider.id)
|
||||
Icon(Icons.check, size: 18, color: colorScheme.primary),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (searchProviders.isNotEmpty) const PopupMenuDivider(),
|
||||
@@ -4627,21 +4600,17 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await PlatformBridge.getAlbumWithExtension(
|
||||
final result = await PlatformBridge.getProviderMetadata(
|
||||
widget.extensionId,
|
||||
'album',
|
||||
widget.albumId,
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
||||
if (result == null) {
|
||||
setState(() {
|
||||
_error = context.l10n.errorLoadAlbum;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final trackList = result['tracks'] as List<dynamic>?;
|
||||
final albumInfo = result['album_info'] as Map<String, dynamic>? ?? result;
|
||||
final trackList =
|
||||
result['track_list'] as List<dynamic>? ??
|
||||
result['tracks'] as List<dynamic>?;
|
||||
if (trackList == null) {
|
||||
setState(() {
|
||||
_error = context.l10n.errorNoTracksFound;
|
||||
@@ -4650,12 +4619,15 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
final artistId = (result['artist_id'] ?? result['artistId'])?.toString();
|
||||
final artistName = result['artists'] as String?;
|
||||
final artistId = (albumInfo['artist_id'] ?? albumInfo['artistId'])
|
||||
?.toString();
|
||||
final artistName = (albumInfo['artists'] ?? albumInfo['artist'])
|
||||
?.toString();
|
||||
final albumType =
|
||||
normalizeOptionalString(result['album_type']?.toString()) ??
|
||||
normalizeOptionalString(albumInfo['album_type']?.toString()) ??
|
||||
_albumType;
|
||||
final totalTracks = result['total_tracks'] as int? ?? _albumTotalTracks;
|
||||
final totalTracks =
|
||||
albumInfo['total_tracks'] as int? ?? _albumTotalTracks;
|
||||
final tracks = trackList
|
||||
.map(
|
||||
(t) => _parseTrack(
|
||||
@@ -4818,21 +4790,16 @@ class _ExtensionPlaylistScreenState
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await PlatformBridge.getPlaylistWithExtension(
|
||||
final result = await PlatformBridge.getProviderMetadata(
|
||||
widget.extensionId,
|
||||
'playlist',
|
||||
widget.playlistId,
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
||||
if (result == null) {
|
||||
setState(() {
|
||||
_error = context.l10n.errorLoadPlaylist;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final trackList = result['tracks'] as List<dynamic>?;
|
||||
final trackList =
|
||||
result['track_list'] as List<dynamic>? ??
|
||||
result['tracks'] as List<dynamic>?;
|
||||
if (trackList == null) {
|
||||
setState(() {
|
||||
_error = context.l10n.errorNoTracksFound;
|
||||
@@ -4975,20 +4942,15 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen> {
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await PlatformBridge.getArtistWithExtension(
|
||||
final result = await PlatformBridge.getProviderMetadata(
|
||||
widget.extensionId,
|
||||
'artist',
|
||||
widget.artistId,
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
||||
if (result == null) {
|
||||
setState(() {
|
||||
_error = context.l10n.errorLoadArtist;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final artistInfo =
|
||||
result['artist_info'] as Map<String, dynamic>? ?? result;
|
||||
final albumList = result['albums'] as List<dynamic>?;
|
||||
final albums =
|
||||
albumList
|
||||
@@ -5004,8 +4966,13 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
final headerImage = result['header_image'] as String?;
|
||||
final listeners = result['listeners'] as int?;
|
||||
final headerImage =
|
||||
artistInfo['images'] as String? ??
|
||||
artistInfo['header_image'] as String? ??
|
||||
artistInfo['cover_url'] as String? ??
|
||||
result['header_image'] as String?;
|
||||
final listeners =
|
||||
artistInfo['listeners'] as int? ?? result['listeners'] as int?;
|
||||
|
||||
setState(() {
|
||||
_albums = albums;
|
||||
|
||||
@@ -51,6 +51,21 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
String get _playlistName => _resolvedPlaylistName ?? widget.playlistName;
|
||||
String? get _coverUrl => _resolvedCoverUrl ?? widget.coverUrl;
|
||||
|
||||
String? _metadataProviderId(String playlistId) {
|
||||
if (playlistId.startsWith('deezer:')) return 'deezer';
|
||||
if (playlistId.startsWith('qobuz:')) return 'qobuz';
|
||||
if (playlistId.startsWith('tidal:')) return 'tidal';
|
||||
return null;
|
||||
}
|
||||
|
||||
String _metadataResourceId(String providerId, String playlistId) {
|
||||
final prefixed = '$providerId:';
|
||||
if (playlistId.startsWith(prefixed)) {
|
||||
return playlistId.substring(prefixed.length);
|
||||
}
|
||||
return playlistId;
|
||||
}
|
||||
|
||||
String? _recommendedDownloadService() {
|
||||
final explicit = widget.recommendedService;
|
||||
if (explicit != null && explicit.isNotEmpty) {
|
||||
@@ -99,17 +114,19 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
try {
|
||||
String playlistId = widget.playlistId!;
|
||||
late final Map<String, dynamic> result;
|
||||
if (playlistId.startsWith('deezer:')) {
|
||||
playlistId = playlistId.substring(7);
|
||||
result = await PlatformBridge.getDeezerMetadata('playlist', playlistId);
|
||||
} else if (playlistId.startsWith('qobuz:')) {
|
||||
playlistId = playlistId.substring(6);
|
||||
result = await PlatformBridge.getQobuzMetadata('playlist', playlistId);
|
||||
} else if (playlistId.startsWith('tidal:')) {
|
||||
playlistId = playlistId.substring(6);
|
||||
result = await PlatformBridge.getTidalMetadata('playlist', playlistId);
|
||||
final providerId = _metadataProviderId(playlistId);
|
||||
if (providerId != null) {
|
||||
result = await PlatformBridge.getProviderMetadata(
|
||||
providerId,
|
||||
'playlist',
|
||||
_metadataResourceId(providerId, playlistId),
|
||||
);
|
||||
} else {
|
||||
result = await PlatformBridge.getDeezerMetadata('playlist', playlistId);
|
||||
result = await PlatformBridge.getProviderMetadata(
|
||||
'deezer',
|
||||
'playlist',
|
||||
playlistId,
|
||||
);
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/app_bar_layout.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/provider_ui_utils.dart';
|
||||
import 'package:spotiflac_android/screens/settings/lyrics_provider_priority_page.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
@@ -24,7 +25,6 @@ class DownloadSettingsPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
static const _builtInServices = ['tidal', 'qobuz'];
|
||||
static const _songLinkRegions = [
|
||||
'AD',
|
||||
'AE',
|
||||
@@ -300,7 +300,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final topPadding = normalizedHeaderTopPadding(context);
|
||||
|
||||
final isBuiltInService = _builtInServices.contains(settings.defaultService);
|
||||
final isBuiltInService = isBuiltInDownloadProvider(settings.defaultService);
|
||||
final isTidalService = settings.defaultService == 'tidal';
|
||||
|
||||
return PopScope(
|
||||
@@ -2053,13 +2053,13 @@ class _ServiceSelector extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final extState = ref.watch(extensionProvider);
|
||||
final builtInServiceIds = ['tidal', 'qobuz'];
|
||||
final builtInProviders = builtInDownloadProviderSpecs;
|
||||
|
||||
final extensionProviders = extState.extensions
|
||||
.where((e) => e.enabled && e.hasDownloadProvider)
|
||||
.toList();
|
||||
|
||||
final isExtensionService = !builtInServiceIds.contains(currentService);
|
||||
final isExtensionService = !isBuiltInDownloadProvider(currentService);
|
||||
final isCurrentExtensionEnabled = isExtensionService
|
||||
? extensionProviders.any((e) => e.id == currentService)
|
||||
: true;
|
||||
@@ -2070,25 +2070,17 @@ class _ServiceSelector extends ConsumerWidget {
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _ServiceChip(
|
||||
icon: Icons.music_note,
|
||||
label: 'Tidal',
|
||||
isSelected: effectiveService == 'tidal',
|
||||
onTap: () => onChanged('tidal'),
|
||||
for (final provider in builtInProviders)
|
||||
_ServiceChip(
|
||||
icon: resolveProviderIcon(provider.id),
|
||||
label: provider.displayName,
|
||||
isSelected: effectiveService == provider.id,
|
||||
onTap: () => onChanged(provider.id),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _ServiceChip(
|
||||
icon: Icons.album,
|
||||
label: 'Qobuz',
|
||||
isSelected: effectiveService == 'qobuz',
|
||||
onTap: () => onChanged('qobuz'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (extensionProviders.isNotEmpty) ...[
|
||||
|
||||
@@ -660,26 +660,27 @@ class _DownloadFallbackItem extends ConsumerWidget {
|
||||
class _SearchProviderSelector extends ConsumerWidget {
|
||||
const _SearchProviderSelector();
|
||||
|
||||
static const _builtInProviders = {'tidal': 'Tidal', 'qobuz': 'Qobuz'};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final extState = ref.watch(extensionProvider);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final builtInProviders = builtInSearchProviderSpecs;
|
||||
|
||||
final searchProviders = extState.extensions
|
||||
.where((e) => e.enabled && e.hasCustomSearch)
|
||||
.toList();
|
||||
|
||||
final hasAnyProvider =
|
||||
searchProviders.isNotEmpty || _builtInProviders.isNotEmpty;
|
||||
searchProviders.isNotEmpty || builtInProviders.isNotEmpty;
|
||||
|
||||
String currentProviderName = context.l10n.extensionDefaultProvider;
|
||||
if (settings.searchProvider != null &&
|
||||
settings.searchProvider!.isNotEmpty) {
|
||||
if (_builtInProviders.containsKey(settings.searchProvider)) {
|
||||
currentProviderName = _builtInProviders[settings.searchProvider]!;
|
||||
if (isBuiltInSearchProvider(settings.searchProvider)) {
|
||||
currentProviderName = resolveProviderDisplayName(
|
||||
settings.searchProvider!,
|
||||
);
|
||||
} else {
|
||||
final ext = searchProviders
|
||||
.where((e) => e.id == settings.searchProvider)
|
||||
@@ -754,6 +755,7 @@ class _SearchProviderSelector extends ConsumerWidget {
|
||||
List<Extension> searchProviders,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final builtInProviders = builtInSearchProviderSpecs;
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
@@ -800,18 +802,20 @@ class _SearchProviderSelector extends ConsumerWidget {
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
..._builtInProviders.entries.map(
|
||||
(entry) => ListTile(
|
||||
...builtInProviders.map(
|
||||
(provider) => ListTile(
|
||||
leading: Icon(Icons.search, color: colorScheme.tertiary),
|
||||
title: Text(entry.value),
|
||||
subtitle: Text(ctx.l10n.extensionsSearchWith(entry.value)),
|
||||
trailing: settings.searchProvider == entry.key
|
||||
title: Text(provider.displayName),
|
||||
subtitle: Text(
|
||||
ctx.l10n.extensionsSearchWith(provider.displayName),
|
||||
),
|
||||
trailing: settings.searchProvider == provider.id
|
||||
? Icon(Icons.check_circle, color: colorScheme.primary)
|
||||
: Icon(Icons.circle_outlined, color: colorScheme.outline),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSearchProvider(entry.key);
|
||||
.setSearchProvider(provider.id);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/provider_ui_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
|
||||
|
||||
class MetadataProviderPriorityPage extends ConsumerStatefulWidget {
|
||||
@@ -220,36 +221,34 @@ class _MetadataProviderItem extends StatelessWidget {
|
||||
BuildContext context,
|
||||
String provider,
|
||||
) {
|
||||
switch (provider) {
|
||||
case 'deezer':
|
||||
return _MetadataProviderInfo(
|
||||
name: 'Deezer',
|
||||
icon: Icons.album,
|
||||
description: context.l10n.providerExtension,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
case 'qobuz':
|
||||
return _MetadataProviderInfo(
|
||||
name: 'Qobuz',
|
||||
icon: Icons.library_music,
|
||||
description: context.l10n.providerBuiltIn,
|
||||
isBuiltIn: true,
|
||||
);
|
||||
case 'tidal':
|
||||
return _MetadataProviderInfo(
|
||||
name: 'Tidal',
|
||||
icon: Icons.music_note,
|
||||
description: context.l10n.providerBuiltIn,
|
||||
isBuiltIn: true,
|
||||
);
|
||||
default:
|
||||
return _MetadataProviderInfo(
|
||||
name: provider,
|
||||
icon: Icons.extension,
|
||||
description: context.l10n.providerExtension,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
final builtIn = builtInProviderSpecForId(provider);
|
||||
if (builtIn != null) {
|
||||
return _MetadataProviderInfo(
|
||||
name: builtIn.displayName,
|
||||
icon: resolveProviderIcon(
|
||||
provider,
|
||||
builtInDefaultIcon: Icons.library_music,
|
||||
),
|
||||
description: context.l10n.providerBuiltIn,
|
||||
isBuiltIn: true,
|
||||
);
|
||||
}
|
||||
|
||||
if (provider == 'deezer') {
|
||||
return _MetadataProviderInfo(
|
||||
name: 'Deezer',
|
||||
icon: Icons.album,
|
||||
description: context.l10n.providerExtension,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
}
|
||||
|
||||
return _MetadataProviderInfo(
|
||||
name: provider,
|
||||
icon: Icons.extension,
|
||||
description: context.l10n.providerExtension,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/utils/app_bar_layout.dart';
|
||||
import 'package:spotiflac_android/utils/artist_utils.dart';
|
||||
import 'package:spotiflac_android/utils/provider_ui_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
class OptionsSettingsPage extends ConsumerWidget {
|
||||
@@ -717,8 +718,6 @@ class _ChannelChip extends StatelessWidget {
|
||||
class _MetadataSourceSelector extends ConsumerWidget {
|
||||
const _MetadataSourceSelector();
|
||||
|
||||
static const _builtInProviders = {'tidal': 'Tidal', 'qobuz': 'Qobuz'};
|
||||
|
||||
Extension? _defaultSearchExtension(List<Extension> extensions) {
|
||||
return extensions
|
||||
.where(
|
||||
@@ -738,12 +737,15 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final extState = ref.watch(extensionProvider);
|
||||
final builtInProviders = builtInSearchProviderSpecs;
|
||||
|
||||
final rawSearchProvider = settings.searchProvider?.trim() ?? '';
|
||||
final isValidBuiltIn = _builtInProviders.containsKey(rawSearchProvider);
|
||||
final isValidBuiltIn = isBuiltInSearchProvider(rawSearchProvider);
|
||||
final primarySearchExtension = _defaultSearchExtension(extState.extensions);
|
||||
final defaultProviderTarget =
|
||||
primarySearchExtension?.displayName ?? 'Tidal';
|
||||
primarySearchExtension?.displayName ??
|
||||
defaultBuiltInSearchProviderDisplayName ??
|
||||
context.l10n.extensionDefaultProvider;
|
||||
final defaultProviderLabel =
|
||||
'${context.l10n.extensionsHomeFeedAuto} ($defaultProviderTarget)';
|
||||
final searchProvider =
|
||||
@@ -754,7 +756,7 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
)
|
||||
? rawSearchProvider
|
||||
: '';
|
||||
final isBuiltIn = _builtInProviders.containsKey(searchProvider);
|
||||
final isBuiltIn = isBuiltInSearchProvider(searchProvider);
|
||||
|
||||
Extension? activeExtension;
|
||||
if (searchProvider.isNotEmpty && !isBuiltIn) {
|
||||
@@ -766,7 +768,7 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
|
||||
String subtitle;
|
||||
if (isBuiltIn) {
|
||||
subtitle = 'Using ${_builtInProviders[searchProvider]}';
|
||||
subtitle = 'Using ${resolveProviderDisplayName(searchProvider)}';
|
||||
} else if (activeExtension != null) {
|
||||
subtitle = context.l10n.optionsUsingExtension(
|
||||
activeExtension.displayName,
|
||||
@@ -796,48 +798,34 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _SourceChip(
|
||||
icon: Icons.graphic_eq,
|
||||
label: defaultProviderLabel,
|
||||
isSelected: searchProvider.isEmpty,
|
||||
onTap: () {
|
||||
if (hasNonDefaultProvider) {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSearchProvider(null);
|
||||
}
|
||||
},
|
||||
),
|
||||
_SourceChip(
|
||||
icon: Icons.graphic_eq,
|
||||
label: defaultProviderLabel,
|
||||
isSelected: searchProvider.isEmpty,
|
||||
onTap: () {
|
||||
if (hasNonDefaultProvider) {
|
||||
ref.read(settingsProvider.notifier).setSearchProvider(null);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _SourceChip(
|
||||
icon: Icons.waves,
|
||||
label: 'Tidal',
|
||||
isSelected: searchProvider == 'tidal',
|
||||
for (final provider in builtInProviders)
|
||||
_SourceChip(
|
||||
icon: resolveProviderIcon(
|
||||
provider.id,
|
||||
tidalIcon: Icons.waves,
|
||||
),
|
||||
label: provider.displayName,
|
||||
isSelected: searchProvider == provider.id,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSearchProvider('tidal');
|
||||
.setSearchProvider(provider.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _SourceChip(
|
||||
icon: Icons.album,
|
||||
label: 'Qobuz',
|
||||
isSelected: searchProvider == 'qobuz',
|
||||
onTap: () {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSearchProvider('qobuz');
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (activeExtension != null) ...[
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/app_bar_layout.dart';
|
||||
import 'package:spotiflac_android/utils/provider_ui_utils.dart';
|
||||
|
||||
class ProviderPriorityPage extends ConsumerStatefulWidget {
|
||||
const ProviderPriorityPage({super.key});
|
||||
@@ -325,28 +326,28 @@ class _ProviderItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
_ProviderInfo _getProviderInfo(String provider) {
|
||||
switch (provider) {
|
||||
case 'tidal':
|
||||
return _ProviderInfo(
|
||||
name: 'Tidal',
|
||||
icon: Icons.music_note,
|
||||
isBuiltIn: true,
|
||||
);
|
||||
case 'qobuz':
|
||||
return _ProviderInfo(name: 'Qobuz', icon: Icons.album, isBuiltIn: true);
|
||||
case 'deezer':
|
||||
return _ProviderInfo(
|
||||
name: 'Deezer',
|
||||
icon: Icons.graphic_eq,
|
||||
isBuiltIn: true,
|
||||
);
|
||||
default:
|
||||
return _ProviderInfo(
|
||||
name: provider,
|
||||
icon: Icons.extension,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
final builtIn = builtInProviderSpecForId(provider);
|
||||
if (builtIn != null) {
|
||||
return _ProviderInfo(
|
||||
name: builtIn.displayName,
|
||||
icon: resolveProviderIcon(provider),
|
||||
isBuiltIn: true,
|
||||
);
|
||||
}
|
||||
|
||||
if (provider == 'deezer') {
|
||||
return _ProviderInfo(
|
||||
name: 'Deezer',
|
||||
icon: Icons.graphic_eq,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
}
|
||||
|
||||
return _ProviderInfo(
|
||||
name: provider,
|
||||
icon: Icons.extension,
|
||||
isBuiltIn: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5040,7 +5040,8 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
|
||||
final deezerId = _extractRawDeezerTrackIdFromValue(sourceTrackId);
|
||||
if (deezerId != null) {
|
||||
final deezerTrack = await PlatformBridge.getDeezerMetadata(
|
||||
final deezerTrack = await PlatformBridge.getProviderMetadata(
|
||||
'deezer',
|
||||
'track',
|
||||
deezerId,
|
||||
);
|
||||
@@ -5291,7 +5292,8 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
(enriched['isrc'] ?? '').trim().isEmpty &&
|
||||
deezerId != null) {
|
||||
try {
|
||||
final deezerMeta = await PlatformBridge.getDeezerMetadata(
|
||||
final deezerMeta = await PlatformBridge.getProviderMetadata(
|
||||
'deezer',
|
||||
'track',
|
||||
deezerId,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user