fix: service selection priority and Amazon fallback-only

- Fix service selection ignored: user's preferred service now takes priority
- Add preferredService parameter to downloadWithExtensions
- Gray out Amazon in service picker (fallback only)
- Clean up unused code in Go backend
This commit is contained in:
zarzet
2026-02-01 21:04:35 +07:00
parent b9c3f2f0dd
commit 8ace180fa8
12 changed files with 98 additions and 184 deletions
-1
View File
@@ -43,7 +43,6 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> {
void initState() {
super.initState();
_initializeExtensions();
// Trigger history provider initialization without subscribing to updates.
ref.read(downloadHistoryProvider);
}
+2 -1
View File
@@ -1878,7 +1878,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}',
);
_log.d('Output dir: $outputDir');
result = await PlatformBridge.downloadWithExtensions(
result = await PlatformBridge.downloadWithExtensions(
isrc: trackToDownload.isrc ?? '',
spotifyId: trackToDownload.id,
trackName: trackToDownload.name,
@@ -1898,6 +1898,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
genre: genre,
label: label,
lyricsMode: settings.lyricsMode,
preferredService: item.service,
);
} else if (state.autoFallback) {
_log.d('Using auto-fallback mode');
+4 -6
View File
@@ -323,7 +323,6 @@ class PlatformBridge {
});
}
/// Returns true if credentials are available (custom or env vars)
static Future<bool> hasSpotifyCredentials() async {
final result = await _channel.invokeMethod('hasSpotifyCredentials');
return result as bool;
@@ -410,7 +409,6 @@ class PlatformBridge {
return logs.map((e) => e as Map<String, dynamic>).toList();
}
/// Get logs since a specific index (for incremental updates)
static Future<Map<String, dynamic>> getGoLogsSince(int index) async {
final result = await _channel.invokeMethod('getLogsSince', {'index': index});
return jsonDecode(result as String) as Map<String, dynamic>;
@@ -561,7 +559,7 @@ class PlatformBridge {
return list.map((e) => e as Map<String, dynamic>).toList();
}
static Future<Map<String, dynamic>> downloadWithExtensions({
static Future<Map<String, dynamic>> downloadWithExtensions({
required String isrc,
required String spotifyId,
required String trackName,
@@ -584,8 +582,9 @@ class PlatformBridge {
String? genre,
String? label,
String lyricsMode = 'embed',
String? preferredService,
}) async {
_log.i('downloadWithExtensions: "$trackName" by $artistName${source != null ? ' (source: $source)' : ''}');
_log.i('downloadWithExtensions: "$trackName" by $artistName${source != null ? ' (source: $source)' : ''}${preferredService != null ? ' (service: $preferredService)' : ''}');
final request = jsonEncode({
'isrc': isrc,
'spotify_id': spotifyId,
@@ -609,6 +608,7 @@ class PlatformBridge {
'genre': genre ?? '',
'label': label ?? '',
'lyrics_mode': lyricsMode,
'service': preferredService ?? '',
});
final result = await _channel.invokeMethod('downloadWithExtensions', request);
@@ -795,7 +795,6 @@ class PlatformBridge {
}
}
/// Get extension home feed
static Future<Map<String, dynamic>?> getExtensionHomeFeed(String extensionId) async {
try {
final result = await _channel.invokeMethod('getExtensionHomeFeed', {
@@ -809,7 +808,6 @@ class PlatformBridge {
}
}
/// Get extension browse categories
static Future<Map<String, dynamic>?> getExtensionBrowseCategories(String extensionId) async {
try {
final result = await _channel.invokeMethod('getExtensionBrowseCategories', {
+33 -8
View File
@@ -10,11 +10,15 @@ class BuiltInService {
final String id;
final String label;
final List<QualityOption> qualityOptions;
final bool isDisabled; // If true, service is grayed out (fallback only)
final String? disabledReason;
const BuiltInService({
required this.id,
required this.label,
required this.qualityOptions,
this.isDisabled = false,
this.disabledReason,
});
}
@@ -47,6 +51,8 @@ const _builtInServices = [
QualityOption(id: 'HI_RES', label: 'Hi-Res FLAC', description: '24-bit / up to 96kHz'),
QualityOption(id: 'HI_RES_LOSSLESS', label: 'Hi-Res FLAC Max', description: '24-bit / up to 192kHz'),
],
isDisabled: true,
disabledReason: 'Fallback only',
),
];
@@ -169,7 +175,7 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
),
),
Padding(
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Wrap(
spacing: 8,
@@ -177,9 +183,14 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
children: [
for (final service in _builtInServices)
_ServiceChip(
label: service.label,
label: service.isDisabled
? '${service.label} (${service.disabledReason})'
: service.label,
isSelected: _selectedService == service.id,
onTap: () => setState(() => _selectedService = service.id),
isDisabled: service.isDisabled,
onTap: service.isDisabled
? null
: () => setState(() => _selectedService = service.id),
),
for (final ext in downloadExtensions)
_ServiceChip(
@@ -392,26 +403,32 @@ class _QualityOption extends StatelessWidget {
class _ServiceChip extends StatelessWidget {
final String label;
final bool isSelected;
final VoidCallback onTap;
final VoidCallback? onTap;
final String? iconPath;
final bool isDisabled;
const _ServiceChip({
required this.label,
required this.isSelected,
required this.onTap,
this.iconPath,
this.isDisabled = false,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
onTap: isDisabled ? null : onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? colorScheme.primaryContainer : colorScheme.surfaceContainerHighest,
color: isDisabled
? colorScheme.surfaceContainerHighest.withValues(alpha: 0.5)
: isSelected
? colorScheme.primaryContainer
: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: isSelected ? null : Border.all(color: colorScheme.outlineVariant.withValues(alpha: 0.5)),
),
@@ -429,7 +446,11 @@ class _ServiceChip extends StatelessWidget {
errorBuilder: (context, error, stackTrace) => Icon(
Icons.extension,
size: 18,
color: isSelected ? colorScheme.onPrimaryContainer : colorScheme.onSurfaceVariant,
color: isDisabled
? colorScheme.onSurfaceVariant.withValues(alpha: 0.4)
: isSelected
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant,
),
),
),
@@ -439,7 +460,11 @@ class _ServiceChip extends StatelessWidget {
label,
style: TextStyle(
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? colorScheme.onPrimaryContainer : colorScheme.onSurfaceVariant,
color: isDisabled
? colorScheme.onSurfaceVariant.withValues(alpha: 0.4)
: isSelected
? colorScheme.onPrimaryContainer
: colorScheme.onSurfaceVariant,
),
),
],