refactor: remove Qobuz built-in provider and delete qobuz.go

Delete the entire Qobuz downloader implementation (qobuz.go, qobuz_test.go)
including all API clients, search, metadata, download, and track matching
code. Empty the builtInProviderRegistry now that all built-in providers are
retired. Remove Qobuz-specific exports (SearchQobuzAll, GetQobuzMetadata,
ParseQobuzURLExport) and the downloadWithBuiltInQobuz adapter. Stub out
PreWarmTrackCache and cache management since no built-in providers remain.
Move qobuz cover upgrade regex to cover.go. Update Dart screens, providers,
and localization strings for the provider-agnostic UI.
This commit is contained in:
zarzet
2026-04-18 23:32:16 +07:00
parent 8f2ca33e87
commit 803cd2de96
37 changed files with 331 additions and 3794 deletions
+8 -8
View File
@@ -376,10 +376,10 @@ abstract class AppLocalizations {
/// **'Choose which tab opens first for new search results.'**
String get optionsDefaultSearchTabSubtitle;
/// Hint to switch back to built-in providers
/// Hint to switch away from the current provider selection
///
/// In en, this message translates to:
/// **'Tap Deezer or Spotify to switch back from extension'**
/// **'Choose another provider below to stop using the current extension'**
String get optionsSwitchBack;
/// Auto-retry with other services
@@ -406,10 +406,10 @@ abstract class AppLocalizations {
/// **'Extensions will be tried first'**
String get optionsUseExtensionProvidersOn;
/// Status when extension providers disabled
/// Status when extension providers are disabled
///
/// In en, this message translates to:
/// **'Using built-in providers only'**
/// **'Extensions are turned off'**
String get optionsUseExtensionProvidersOff;
/// Embed lyrics in audio files
@@ -5073,16 +5073,16 @@ abstract class AppLocalizations {
/// **'Off: strict HTTPS certificate validation (recommended)'**
String get downloadNetworkCompatibilityModeDisabled;
/// Hint shown instead of Ask-quality subtitle when no built-in service selected
/// Hint shown instead of Ask-quality subtitle when the selected provider does not expose built-in quality controls
///
/// In en, this message translates to:
/// **'Select a built-in service to enable'**
/// **'Select a compatible download provider to enable quality options'**
String get downloadSelectServiceToEnable;
/// Info hint when non-Tidal/Qobuz service is selected
/// Info hint when the selected provider does not expose built-in quality controls
///
/// In en, this message translates to:
/// **'Select Tidal or Qobuz above to configure quality'**
/// **'Select a compatible provider above to configure quality'**
String get downloadSelectTidalQobuz;
/// Subtitle for Embed Lyrics when Embed Metadata is disabled
+2 -2
View File
@@ -2952,11 +2952,11 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+4 -4
View File
@@ -143,7 +143,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
'Choose another provider below to stop using the current extension';
@override
String get optionsAutoFallback => 'Auto Fallback';
@@ -159,7 +159,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get optionsUseExtensionProvidersOn => 'Extensions will be tried first';
@override
String get optionsUseExtensionProvidersOff => 'Using built-in providers only';
String get optionsUseExtensionProvidersOff => 'Extensions are turned off';
@override
String get optionsEmbedLyrics => 'Embed Lyrics';
@@ -2920,11 +2920,11 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2920,11 +2920,11 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2921,11 +2921,11 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2919,11 +2919,11 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+4 -5
View File
@@ -145,7 +145,7 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get optionsSwitchBack =>
'Ketuk Deezer atau Spotify untuk beralih dari ekstensi';
'Pilih provider lain di bawah untuk berhenti memakai ekstensi saat ini';
@override
String get optionsAutoFallback => 'Cadangan Otomatis';
@@ -162,8 +162,7 @@ class AppLocalizationsId extends AppLocalizations {
'Ekstensi akan dicoba terlebih dahulu';
@override
String get optionsUseExtensionProvidersOff =>
'Hanya menggunakan provider bawaan';
String get optionsUseExtensionProvidersOff => 'Ekstensi dimatikan';
@override
String get optionsEmbedLyrics => 'Sematkan Lirik';
@@ -2930,11 +2929,11 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+4 -5
View File
@@ -142,8 +142,7 @@ class AppLocalizationsJa extends AppLocalizations {
'Choose which tab opens first for new search results.';
@override
String get optionsSwitchBack =>
'Tap Deezer or Spotify to switch back from extension';
String get optionsSwitchBack => '現在の拡張の使用をやめるには、下から別のプロバイダーを選択してください';
@override
String get optionsAutoFallback => 'Auto Fallback';
@@ -159,7 +158,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get optionsUseExtensionProvidersOn => '最初に拡張で試みます';
@override
String get optionsUseExtensionProvidersOff => '内蔵のプロバイダーのみを使用する';
String get optionsUseExtensionProvidersOff => '拡張はオフです';
@override
String get optionsEmbedLyrics => '歌詞を埋め込む';
@@ -2906,11 +2905,11 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2899,11 +2899,11 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2919,11 +2919,11 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2920,11 +2920,11 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2979,11 +2979,11 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+4 -5
View File
@@ -146,7 +146,7 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get optionsSwitchBack =>
'Eklentiden çıkıp varsayılana dönmek için Deezer veya Spotify\'a dokunun';
'Geçerli eklentiyi bırakmak için aşağıdan başka bir sağlayıcı seçin';
@override
String get optionsAutoFallback => 'Otomatik Geçiş';
@@ -163,8 +163,7 @@ class AppLocalizationsTr extends AppLocalizations {
'İndirme için önce eklentiler denenecek';
@override
String get optionsUseExtensionProvidersOff =>
'Sadece yerleşik sağlayıcılar kullanılıyor';
String get optionsUseExtensionProvidersOff => 'Eklentiler kapalı';
@override
String get optionsEmbedLyrics => 'Şarkı Sözlerini Gömer';
@@ -2979,11 +2978,11 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Seçenekleri açmak için yerleşik bir sağlayıcı seçin';
'Kalite seçeneklerini açmak için uyumlu bir indirme sağlayıcısı seçin';
@override
String get downloadSelectTidalQobuz =>
'Kaliteyi ayarlamak için lütfen yukarıdan Tidal veya Qobuz seçin';
'Kaliteyi ayarlamak için yukarıdan uyumlu bir sağlayıcı seçin';
@override
String get downloadEmbedLyricsDisabled =>
+2 -2
View File
@@ -2920,11 +2920,11 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get downloadSelectServiceToEnable =>
'Select a built-in service to enable';
'Select a compatible download provider to enable quality options';
@override
String get downloadSelectTidalQobuz =>
'Select Tidal or Qobuz above to configure quality';
'Select a compatible provider above to configure quality';
@override
String get downloadEmbedLyricsDisabled =>
+8 -8
View File
@@ -174,9 +174,9 @@
"@optionsDefaultSearchTabSubtitle": {
"description": "Subtitle for the preferred default search tab setting"
},
"optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension",
"optionsSwitchBack": "Choose another provider below to stop using the current extension",
"@optionsSwitchBack": {
"description": "Hint to switch back to built-in providers"
"description": "Hint to switch away from the current provider selection"
},
"optionsAutoFallback": "Auto Fallback",
"@optionsAutoFallback": {
@@ -194,9 +194,9 @@
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
"optionsUseExtensionProvidersOff": "Using built-in providers only",
"optionsUseExtensionProvidersOff": "Extensions are turned off",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
"description": "Status when extension providers are disabled"
},
"optionsEmbedLyrics": "Embed Lyrics",
"@optionsEmbedLyrics": {
@@ -3867,14 +3867,14 @@
"@downloadNetworkCompatibilityModeDisabled": {
"description": "Subtitle when network compatibility mode is disabled"
},
"downloadSelectServiceToEnable": "Select a built-in service to enable",
"downloadSelectServiceToEnable": "Select a compatible download provider to enable quality options",
"@downloadSelectServiceToEnable": {
"description": "Hint shown instead of Ask-quality subtitle when no built-in service selected"
"description": "Hint shown instead of Ask-quality subtitle when the selected provider does not expose built-in quality controls"
},
"downloadSelectTidalQobuz": "Select Tidal or Qobuz above to configure quality",
"downloadSelectTidalQobuz": "Select a compatible provider above to configure quality",
"@downloadSelectTidalQobuz": {
"description": "Info hint when non-Tidal/Qobuz service is selected"
"description": "Info hint when the selected provider does not expose built-in quality controls"
},
"downloadEmbedLyricsDisabled": "Disabled while Embed Metadata is turned off",
"@downloadEmbedLyricsDisabled": {
+4 -4
View File
@@ -158,9 +158,9 @@
"@optionsDefaultSearchTabSubtitle": {
"description": "Subtitle for the preferred default search tab setting"
},
"optionsSwitchBack": "Ketuk Deezer atau Spotify untuk beralih dari ekstensi",
"optionsSwitchBack": "Pilih provider lain di bawah untuk berhenti memakai ekstensi saat ini",
"@optionsSwitchBack": {
"description": "Hint to switch back to built-in providers"
"description": "Hint to switch away from the current provider selection"
},
"optionsAutoFallback": "Cadangan Otomatis",
"@optionsAutoFallback": {
@@ -178,9 +178,9 @@
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
"optionsUseExtensionProvidersOff": "Hanya menggunakan provider bawaan",
"optionsUseExtensionProvidersOff": "Ekstensi dimatikan",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
"description": "Status when extension providers are disabled"
},
"optionsEmbedLyrics": "Sematkan Lirik",
"@optionsEmbedLyrics": {
+5 -5
View File
@@ -150,9 +150,9 @@
}
}
},
"optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension",
"optionsSwitchBack": "現在の拡張の使用をやめるには、下から別のプロバイダーを選択してください",
"@optionsSwitchBack": {
"description": "Hint to switch back to built-in providers"
"description": "Hint to switch away from the current provider selection"
},
"optionsAutoFallback": "Auto Fallback",
"@optionsAutoFallback": {
@@ -170,9 +170,9 @@
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
"optionsUseExtensionProvidersOff": "内蔵のプロバイダーのみを使用する",
"optionsUseExtensionProvidersOff": "拡張はオフです",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
"description": "Status when extension providers are disabled"
},
"optionsEmbedLyrics": "歌詞を埋め込む",
"@optionsEmbedLyrics": {
@@ -3198,4 +3198,4 @@
"@downloadUseAlbumArtistForFoldersTrackSubtitle": {
"description": "Subtitle when Track Artist is used for folder naming"
}
}
}
+8 -8
View File
@@ -150,9 +150,9 @@
}
}
},
"optionsSwitchBack": "Eklentiden çıkıp varsayılana dönmek için Deezer veya Spotify'a dokunun",
"optionsSwitchBack": "Geçerli eklentiyi bırakmak için aşağıdan başka bir sağlayıcı seçin",
"@optionsSwitchBack": {
"description": "Hint to switch back to built-in providers"
"description": "Hint to switch away from the current provider selection"
},
"optionsAutoFallback": "Otomatik Geçiş",
"@optionsAutoFallback": {
@@ -170,9 +170,9 @@
"@optionsUseExtensionProvidersOn": {
"description": "Status when extension providers enabled"
},
"optionsUseExtensionProvidersOff": "Sadece yerleşik sağlayıcılar kullanılıyor",
"optionsUseExtensionProvidersOff": "Eklentiler kapalı",
"@optionsUseExtensionProvidersOff": {
"description": "Status when extension providers disabled"
"description": "Status when extension providers are disabled"
},
"optionsEmbedLyrics": "Şarkı Sözlerini Gömer",
"@optionsEmbedLyrics": {
@@ -3689,13 +3689,13 @@
"@downloadNetworkCompatibilityModeDisabled": {
"description": "Subtitle when network compatibility mode is disabled"
},
"downloadSelectServiceToEnable": "Seçenekleri açmak için yerleşik bir sağlayıcı seçin",
"downloadSelectServiceToEnable": "Kalite seçeneklerini açmak için uyumlu bir indirme sağlayıcısı seçin",
"@downloadSelectServiceToEnable": {
"description": "Hint shown instead of Ask-quality subtitle when no built-in service selected"
"description": "Hint shown instead of Ask-quality subtitle when the selected provider does not expose built-in quality controls"
},
"downloadSelectTidalQobuz": "Kaliteyi ayarlamak için lütfen yukarıdan Tidal veya Qobuz seçin",
"downloadSelectTidalQobuz": "Kaliteyi ayarlamak için yukarıdan uyumlu bir sağlayıcı seçin",
"@downloadSelectTidalQobuz": {
"description": "Info hint when non-Tidal/Qobuz service is selected"
"description": "Info hint when the selected provider does not expose built-in quality controls"
},
"downloadEmbedLyricsDisabled": "Şarkı Verilerini Dosyaya Gömme ayarı kapalıyken kullanılamaz",
"@downloadEmbedLyricsDisabled": {
+1 -1
View File
@@ -4815,7 +4815,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
String payloadTidalId = '';
if (trackToDownload.id.startsWith('qobuz:')) {
payloadQobuzId = trackToDownload.id.substring(6);
if (item.service == 'qobuz') {
if (_usesBuiltInCompatibleDownloadProvider(item.service, 'qobuz')) {
payloadSpotifyId = '';
}
}
+79
View File
@@ -288,6 +288,85 @@ List<String> get builtInDownloadProviderIds => List<String>.unmodifiable(
builtInDownloadProviderSpecs.map((provider) => provider.id),
);
String resolveEffectiveDownloadService(
String requestedService,
ExtensionState extensionState,
) {
final normalizedRequested = requestedService.trim().toLowerCase();
final builtInDownloadIds = extensionState.builtInProviders
.where((provider) => provider.supportsDownload)
.map((provider) => provider.id.trim().toLowerCase())
.where((providerId) => providerId.isNotEmpty)
.toSet();
final enabledDownloadExtensions = extensionState.extensions
.where((ext) => ext.enabled && ext.hasDownloadProvider)
.toList(growable: false);
if (normalizedRequested.isNotEmpty) {
if (builtInDownloadIds.contains(normalizedRequested)) {
return normalizedRequested;
}
final matchingExtension = enabledDownloadExtensions
.where((ext) => ext.id.trim().toLowerCase() == normalizedRequested)
.firstOrNull;
if (matchingExtension != null) {
return matchingExtension.id;
}
final replacementExtension = enabledDownloadExtensions
.where(
(ext) => ext.replacesBuiltInProviders.contains(normalizedRequested),
)
.firstOrNull;
if (replacementExtension != null) {
return replacementExtension.id;
}
}
const preferredBuiltInOrder = ['tidal', 'qobuz', 'deezer'];
for (final builtInId in preferredBuiltInOrder) {
final replacement = enabledDownloadExtensions
.where((ext) => ext.replacesBuiltInProviders.contains(builtInId))
.firstOrNull;
if (replacement != null) {
return replacement.id;
}
if (builtInDownloadIds.contains(builtInId)) {
return builtInId;
}
}
return enabledDownloadExtensions.firstOrNull?.id ??
extensionState.builtInProviders
.where((provider) => provider.supportsDownload)
.map((provider) => provider.id)
.firstOrNull ??
'';
}
bool isDeezerCompatibleDownloadService(
String service,
ExtensionState extensionState,
) {
final normalizedService = service.trim().toLowerCase();
if (normalizedService.isEmpty) {
return false;
}
if (normalizedService == 'deezer') {
return true;
}
return extensionState.extensions.any(
(ext) =>
ext.enabled &&
ext.hasDownloadProvider &&
ext.id.trim().toLowerCase() == normalizedService &&
ext.replacesBuiltInProviders.contains('deezer'),
);
}
bool isBuiltInSearchProvider(String? providerId) =>
builtInProviderSpecForId(providerId)?.supportsSearch ?? false;
-1
View File
@@ -599,7 +599,6 @@ class TrackNotifier extends Notifier<TrackState> {
if (effectiveBuiltInProvider == null || effectiveBuiltInProvider.isEmpty) {
state = TrackState(
isLoading: false,
error: 'No active search provider available',
hasSearchText: state.hasSearchText,
isShowingRecentAccess: state.isShowingRecentAccess,
selectedSearchFilter: currentFilter,
+25 -2
View File
@@ -5,6 +5,7 @@ import 'package:spotiflac_android/services/cover_cache_manager.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/providers/recent_access_provider.dart';
import 'package:spotiflac_android/providers/local_library_provider.dart';
@@ -644,9 +645,20 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addToQueue(track, settings.defaultService);
.addToQueue(track, service);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
);
@@ -716,9 +728,20 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addMultipleToQueue(tracksToQueue, settings.defaultService);
.addMultipleToQueue(tracksToQueue, service);
_showQueuedSnackbar(context, tracksToQueue.length, skippedCount);
}
}
+13 -1
View File
@@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
import 'package:spotiflac_android/services/cover_cache_manager.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/providers/track_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
@@ -1627,7 +1628,18 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
return;
}
enqueue(settings.defaultService);
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
enqueue(service);
}
Widget _buildAlbumSection(
+40 -3
View File
@@ -1004,9 +1004,20 @@ class _HomeTabState extends ConsumerState<HomeTab>
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addToQueue(track, settings.defaultService);
.addToQueue(track, service);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.snackbarAddedToQueue(track.name)),
@@ -1227,9 +1238,24 @@ class _HomeTabState extends ConsumerState<HomeTab>
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(this.context).showSnackBar(
SnackBar(
content: Text(this.context.l10n.extensionsNoDownloadProvider),
),
);
}
return;
}
ref
.read(downloadQueueProvider.notifier)
.addMultipleToQueue(tracksToQueue, settings.defaultService);
.addMultipleToQueue(tracksToQueue, service);
if (mounted) {
ScaffoldMessenger.of(this.context).showSnackBar(
SnackBar(
@@ -2139,9 +2165,20 @@ class _HomeTabState extends ConsumerState<HomeTab>
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addToQueue(track, settings.defaultService);
.addToQueue(track, service);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
);
+25 -2
View File
@@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/providers/library_collections_provider.dart';
import 'package:spotiflac_android/providers/playback_provider.dart';
import 'package:spotiflac_android/providers/local_library_provider.dart';
@@ -214,12 +215,23 @@ class _LibraryTracksFolderScreenState
void _downloadSelected(List<CollectionTrackEntry> entries) {
final settings = ref.read(settingsProvider);
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
final queueNotifier = ref.read(downloadQueueProvider.notifier);
var count = 0;
for (final entry in entries) {
if (!_selectedKeys.contains(entry.key)) continue;
queueNotifier.addToQueue(entry.track, settings.defaultService);
queueNotifier.addToQueue(entry.track, service);
count++;
}
@@ -1349,9 +1361,20 @@ class _CollectionTrackTile extends ConsumerWidget {
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addToQueue(track, settings.defaultService);
.addToQueue(track, service);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
);
+8
View File
@@ -965,10 +965,18 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
);
final targetService = LocalTrackRedownloadService.preferredFlacService(
settings,
extensionState,
);
if (targetService.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
final targetQuality =
LocalTrackRedownloadService.preferredFlacQualityForService(
targetService,
extensionState,
);
final matchedTracks = <Track>[];
+13 -1
View File
@@ -6,6 +6,7 @@ import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/providers/library_collections_provider.dart';
import 'package:spotiflac_android/utils/file_access.dart';
import 'package:spotiflac_android/utils/image_cache_utils.dart';
@@ -495,11 +496,22 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
},
);
} else {
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addToQueue(
track,
settings.defaultService,
service,
playlistName: _playlistName,
);
ScaffoldMessenger.of(context).showSnackBar(
+23 -1
View File
@@ -1845,10 +1845,24 @@ class _QueueTabState extends ConsumerState<QueueTab> {
if (confirmed != true || !context.mounted) return;
final settings = ref.read(settingsProvider);
final extensionState = ref.read(extensionProvider);
final queueNotifier = ref.read(downloadQueueProvider.notifier);
void enqueueAll({String? qualityOverride, String? service}) {
final svc = service ?? settings.defaultService;
final svc =
service ??
resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (svc.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
}
return;
}
for (final playlist in selectedPlaylists) {
final tracks = playlist.tracks.map((e) => e.track).toList();
queueNotifier.addMultipleToQueue(
@@ -5218,10 +5232,18 @@ class _QueueTabState extends ConsumerState<QueueTab> {
);
final targetService = LocalTrackRedownloadService.preferredFlacService(
settings,
extensionState,
);
if (targetService.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
final targetQuality =
LocalTrackRedownloadService.preferredFlacQualityForService(
targetService,
extensionState,
);
final matchedTracks = <Track>[];
+13 -1
View File
@@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/services/cover_cache_manager.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/providers/track_provider.dart';
import 'package:spotiflac_android/providers/download_queue_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
@@ -49,9 +50,20 @@ class _SearchScreenState extends ConsumerState<SearchScreen> {
void _downloadTrack(Track track) {
final settings = ref.read(settingsProvider);
final extensionState = ref.read(extensionProvider);
final service = resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
if (service.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.extensionsNoDownloadProvider)),
);
return;
}
ref
.read(downloadQueueProvider.notifier)
.addToQueue(track, settings.defaultService);
.addToQueue(track, service);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
);
@@ -1,5 +1,6 @@
import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/services/library_database.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';
@@ -101,19 +102,26 @@ class LocalTrackRedownloadService {
);
}
static String preferredFlacService(AppSettings settings) {
switch (settings.defaultService.toLowerCase()) {
case 'tidal':
case 'qobuz':
case 'deezer':
return settings.defaultService.toLowerCase();
default:
return '';
}
static String preferredFlacService(
AppSettings settings,
ExtensionState extensionState,
) {
return resolveEffectiveDownloadService(
settings.defaultService,
extensionState,
);
}
static String preferredFlacQualityForService(String service) {
return service.toLowerCase() == 'deezer' ? 'FLAC' : 'LOSSLESS';
static String preferredFlacQualityForService(
String service,
ExtensionState extensionState,
) {
if (service.trim().isEmpty) {
return 'LOSSLESS';
}
return isDeezerCompatibleDownloadService(service, extensionState)
? 'FLAC'
: 'LOSSLESS';
}
static String _buildSearchQuery(LocalLibraryItem item) {