mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-30 03:19:28 +02:00
refactor: rename skipBuiltInFallback to stopProviderFallback, unify service/search provider grid layout, and retire useExtensionProviders toggle
- Add stopProviderFallback manifest field with backward compat for skipBuiltInFallback - Expose stop_provider_fallback in extension JSON API - Unify service and search provider chips into 4-per-row grid layout - Enable Ask Before Download for extensions with quality options - Force useExtensionProviders always-on (built-in providers retired) - Update localization: Built-in -> Legacy, remove obsolete description text - Clear hardcoded donor names list
This commit is contained in:
@@ -1000,6 +1000,7 @@ func (m *extensionManager) GetInstalledExtensionsJSON() (string, error) {
|
||||
HasLyricsProvider bool `json:"has_lyrics_provider"`
|
||||
SkipMetadataEnrichment bool `json:"skip_metadata_enrichment"`
|
||||
SkipLyrics bool `json:"skip_lyrics"`
|
||||
StopProviderFallback bool `json:"stop_provider_fallback"`
|
||||
SearchBehavior *SearchBehaviorConfig `json:"search_behavior,omitempty"`
|
||||
TrackMatching *TrackMatchingConfig `json:"track_matching,omitempty"`
|
||||
PostProcessing *PostProcessingConfig `json:"post_processing,omitempty"`
|
||||
@@ -1057,6 +1058,7 @@ func (m *extensionManager) GetInstalledExtensionsJSON() (string, error) {
|
||||
HasLyricsProvider: ext.Manifest.IsLyricsProvider(),
|
||||
SkipMetadataEnrichment: ext.Manifest.SkipMetadataEnrichment,
|
||||
SkipLyrics: ext.Manifest.SkipLyrics,
|
||||
StopProviderFallback: ext.Manifest.StopsProviderFallback(),
|
||||
SearchBehavior: ext.Manifest.SearchBehavior,
|
||||
TrackMatching: ext.Manifest.TrackMatching,
|
||||
PostProcessing: ext.Manifest.PostProcessing,
|
||||
|
||||
@@ -115,6 +115,7 @@ type ExtensionManifest struct {
|
||||
MinAppVersion string `json:"minAppVersion,omitempty"`
|
||||
SkipMetadataEnrichment bool `json:"skipMetadataEnrichment,omitempty"`
|
||||
SkipLyrics bool `json:"skipLyrics,omitempty"`
|
||||
StopProviderFallback bool `json:"stopProviderFallback,omitempty"`
|
||||
SkipBuiltInFallback bool `json:"skipBuiltInFallback,omitempty"`
|
||||
SearchBehavior *SearchBehaviorConfig `json:"searchBehavior,omitempty"`
|
||||
URLHandler *URLHandlerConfig `json:"urlHandler,omitempty"`
|
||||
@@ -226,6 +227,13 @@ func (m *ExtensionManifest) IsLyricsProvider() bool {
|
||||
return m.HasType(ExtensionTypeLyricsProvider)
|
||||
}
|
||||
|
||||
func (m *ExtensionManifest) StopsProviderFallback() bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
return m.StopProviderFallback || m.SkipBuiltInFallback
|
||||
}
|
||||
|
||||
func (m *ExtensionManifest) IsDomainAllowed(domain string) bool {
|
||||
domain = strings.ToLower(strings.TrimSpace(domain))
|
||||
for _, allowed := range m.Permissions.Network {
|
||||
|
||||
@@ -1664,7 +1664,7 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
var skipBuiltIn bool
|
||||
var stopProviderFallback bool
|
||||
var sourceExtensionLocked bool
|
||||
var sourceExtensionAvailability *ExtAvailabilityResult
|
||||
var sourceExtensionTrackID string
|
||||
@@ -1882,13 +1882,13 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
|
||||
ext, err := extManager.GetExtension(req.Source)
|
||||
if err == nil && ext.Enabled && ext.Error == "" && ext.Manifest.IsDownloadProvider() {
|
||||
skipBuiltIn = ext.Manifest.SkipBuiltInFallback
|
||||
stopProviderFallback = ext.Manifest.StopsProviderFallback()
|
||||
|
||||
provider := newExtensionProviderWrapper(ext)
|
||||
|
||||
trackID := resolvePreferredTrackIDForExtension(ext, req, sourceExtensionTrackID)
|
||||
|
||||
GoLog("[DownloadWithExtensionFallback] Downloading from source extension with trackID: %s (skipBuiltInFallback: %v)\n", trackID, skipBuiltIn)
|
||||
GoLog("[DownloadWithExtensionFallback] Downloading from source extension with trackID: %s (stopProviderFallback: %v)\n", trackID, stopProviderFallback)
|
||||
|
||||
outputPath := buildOutputPathForExtension(req, ext)
|
||||
if req.ItemID != "" {
|
||||
@@ -1987,12 +1987,12 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
}
|
||||
GoLog("[DownloadWithExtensionFallback] Source extension %s failed: %v\n", req.Source, lastErr)
|
||||
|
||||
if skipBuiltIn || sourceExtensionLocked {
|
||||
if stopProviderFallback || sourceExtensionLocked {
|
||||
if sourceExtensionLocked {
|
||||
GoLog("[DownloadWithExtensionFallback] Source extension %s requested skip_fallback, not trying other providers\n", req.Source)
|
||||
return buildExtensionFallbackStoppedResponse(req.Source, sourceExtensionAvailability, lastErr), nil
|
||||
}
|
||||
GoLog("[DownloadWithExtensionFallback] skipBuiltInFallback is true, not trying other providers\n")
|
||||
GoLog("[DownloadWithExtensionFallback] stopProviderFallback is true, not trying other providers\n")
|
||||
return &DownloadResponse{
|
||||
Success: false,
|
||||
Error: "Download failed: " + lastErr.Error(),
|
||||
|
||||
@@ -44,6 +44,23 @@ func TestParseManifest_Valid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionManifestStopsProviderFallback(t *testing.T) {
|
||||
modernManifest := &ExtensionManifest{StopProviderFallback: true}
|
||||
if !modernManifest.StopsProviderFallback() {
|
||||
t.Fatal("expected stopProviderFallback to stop provider fallback")
|
||||
}
|
||||
|
||||
legacyManifest := &ExtensionManifest{SkipBuiltInFallback: true}
|
||||
if !legacyManifest.StopsProviderFallback() {
|
||||
t.Fatal("expected legacy skipBuiltInFallback to stop provider fallback")
|
||||
}
|
||||
|
||||
defaultManifest := &ExtensionManifest{}
|
||||
if defaultManifest.StopsProviderFallback() {
|
||||
t.Fatal("expected default manifest to allow provider fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseManifest_MissingName(t *testing.T) {
|
||||
invalidManifest := `{
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1795,7 +1795,7 @@ abstract class AppLocalizations {
|
||||
/// Section description for extension fallback selection
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.'**
|
||||
/// **'Choose which installed download extensions can be used during automatic fallback.'**
|
||||
String get providerPriorityFallbackExtensionsDescription;
|
||||
|
||||
/// Hint below the extension fallback selection list
|
||||
@@ -1804,10 +1804,10 @@ abstract class AppLocalizations {
|
||||
/// **'Only enabled extensions with download-provider capability are listed here.'**
|
||||
String get providerPriorityFallbackExtensionsHint;
|
||||
|
||||
/// Label for built-in providers (Tidal/Qobuz)
|
||||
/// Label for legacy providers kept for compatibility
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Built-in'**
|
||||
/// **'Legacy'**
|
||||
String get providerBuiltIn;
|
||||
|
||||
/// Label for extension-provided providers
|
||||
|
||||
@@ -971,7 +971,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -957,14 +957,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
'Only enabled extensions with download-provider capability are listed here.';
|
||||
|
||||
@override
|
||||
String get providerBuiltIn => 'Built-in';
|
||||
String get providerBuiltIn => 'Legacy';
|
||||
|
||||
@override
|
||||
String get providerExtension => 'Extension';
|
||||
|
||||
@@ -957,7 +957,7 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -959,7 +959,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -957,7 +957,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -960,14 +960,14 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis. Provider bawaan tetap mengikuti urutan prioritas di atas.';
|
||||
'Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
'Hanya ekstensi aktif dengan kemampuan download provider yang ditampilkan di sini.';
|
||||
|
||||
@override
|
||||
String get providerBuiltIn => 'Bawaan';
|
||||
String get providerBuiltIn => 'Legacy';
|
||||
|
||||
@override
|
||||
String get providerExtension => 'Ekstensi';
|
||||
|
||||
@@ -950,7 +950,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -939,7 +939,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -957,7 +957,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -957,7 +957,7 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -971,7 +971,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -969,7 +969,7 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -957,7 +957,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Choose which installed download extensions can be used during automatic fallback.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
|
||||
@@ -1239,7 +1239,7 @@
|
||||
"@providerPriorityFallbackExtensionsTitle": {
|
||||
"description": "Section title for choosing which download extensions can be used as fallback providers"
|
||||
},
|
||||
"providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.",
|
||||
"providerPriorityFallbackExtensionsDescription": "Choose which installed download extensions can be used during automatic fallback.",
|
||||
"@providerPriorityFallbackExtensionsDescription": {
|
||||
"description": "Section description for extension fallback selection"
|
||||
},
|
||||
@@ -1247,9 +1247,9 @@
|
||||
"@providerPriorityFallbackExtensionsHint": {
|
||||
"description": "Hint below the extension fallback selection list"
|
||||
},
|
||||
"providerBuiltIn": "Built-in",
|
||||
"providerBuiltIn": "Legacy",
|
||||
"@providerBuiltIn": {
|
||||
"description": "Label for built-in providers (Tidal/Qobuz)"
|
||||
"description": "Label for legacy providers kept for compatibility"
|
||||
},
|
||||
"providerExtension": "Extension",
|
||||
"@providerExtension": {
|
||||
|
||||
@@ -1143,7 +1143,7 @@
|
||||
"@providerPriorityFallbackExtensionsTitle": {
|
||||
"description": "Section title for choosing which download extensions can be used as fallback providers"
|
||||
},
|
||||
"providerPriorityFallbackExtensionsDescription": "Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis. Provider bawaan tetap mengikuti urutan prioritas di atas.",
|
||||
"providerPriorityFallbackExtensionsDescription": "Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis.",
|
||||
"@providerPriorityFallbackExtensionsDescription": {
|
||||
"description": "Section description for extension fallback selection"
|
||||
},
|
||||
@@ -1151,9 +1151,9 @@
|
||||
"@providerPriorityFallbackExtensionsHint": {
|
||||
"description": "Hint below the extension fallback selection list"
|
||||
},
|
||||
"providerBuiltIn": "Bawaan",
|
||||
"providerBuiltIn": "Legacy",
|
||||
"@providerBuiltIn": {
|
||||
"description": "Label for built-in providers (Tidal/Qobuz)"
|
||||
"description": "Label for legacy providers kept for compatibility"
|
||||
},
|
||||
"providerExtension": "Ekstensi",
|
||||
"@providerExtension": {
|
||||
|
||||
@@ -71,6 +71,7 @@ class Extension {
|
||||
final bool hasLyricsProvider;
|
||||
final bool skipMetadataEnrichment;
|
||||
final bool skipLyrics;
|
||||
final bool stopProviderFallback;
|
||||
final SearchBehavior? searchBehavior;
|
||||
final URLHandler? urlHandler;
|
||||
final TrackMatching? trackMatching;
|
||||
@@ -95,6 +96,7 @@ class Extension {
|
||||
this.hasLyricsProvider = false,
|
||||
this.skipMetadataEnrichment = false,
|
||||
this.skipLyrics = false,
|
||||
this.stopProviderFallback = false,
|
||||
this.searchBehavior,
|
||||
this.urlHandler,
|
||||
this.trackMatching,
|
||||
@@ -132,6 +134,7 @@ class Extension {
|
||||
skipMetadataEnrichment:
|
||||
json['skip_metadata_enrichment'] as bool? ?? false,
|
||||
skipLyrics: json['skip_lyrics'] as bool? ?? false,
|
||||
stopProviderFallback: json['stop_provider_fallback'] as bool? ?? false,
|
||||
searchBehavior: json['search_behavior'] != null
|
||||
? SearchBehavior.fromJson(
|
||||
json['search_behavior'] as Map<String, dynamic>,
|
||||
@@ -172,6 +175,7 @@ class Extension {
|
||||
bool? hasLyricsProvider,
|
||||
bool? skipMetadataEnrichment,
|
||||
bool? skipLyrics,
|
||||
bool? stopProviderFallback,
|
||||
SearchBehavior? searchBehavior,
|
||||
URLHandler? urlHandler,
|
||||
TrackMatching? trackMatching,
|
||||
@@ -197,6 +201,7 @@ class Extension {
|
||||
skipMetadataEnrichment:
|
||||
skipMetadataEnrichment ?? this.skipMetadataEnrichment,
|
||||
skipLyrics: skipLyrics ?? this.skipLyrics,
|
||||
stopProviderFallback: stopProviderFallback ?? this.stopProviderFallback,
|
||||
searchBehavior: searchBehavior ?? this.searchBehavior,
|
||||
urlHandler: urlHandler ?? this.urlHandler,
|
||||
trackMatching: trackMatching ?? this.trackMatching,
|
||||
|
||||
@@ -52,6 +52,7 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
loaded.defaultSearchTab,
|
||||
);
|
||||
state = loaded.copyWith(
|
||||
useExtensionProviders: true,
|
||||
downloadFallbackExtensionIds: sanitizedDownloadFallbackExtensionIds,
|
||||
clearDownloadFallbackExtensionIds:
|
||||
loaded.downloadFallbackExtensionIds != null &&
|
||||
@@ -148,6 +149,9 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
state.defaultService == 'deezer') {
|
||||
state = state.copyWith(defaultService: '');
|
||||
}
|
||||
if (!state.useExtensionProviders) {
|
||||
state = state.copyWith(useExtensionProviders: true);
|
||||
}
|
||||
await prefs.setInt(_migrationVersionKey, _currentMigrationVersion);
|
||||
await _saveSettings();
|
||||
}
|
||||
@@ -446,7 +450,7 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
}
|
||||
|
||||
void setUseExtensionProviders(bool enabled) {
|
||||
state = state.copyWith(useExtensionProviders: enabled);
|
||||
state = state.copyWith(useExtensionProviders: true);
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
|
||||
@@ -164,15 +164,7 @@ class _RecentDonorsCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
const donorNames = <String>[
|
||||
'Ldav',
|
||||
'Nico',
|
||||
'Feuerstern',
|
||||
'R4ND0MIZ3D',
|
||||
'Isra',
|
||||
'bigJr48',
|
||||
'Mick',
|
||||
];
|
||||
const donorNames = <String>[];
|
||||
|
||||
// Match SettingsGroup color logic
|
||||
final cardColor = isDark
|
||||
|
||||
@@ -323,7 +323,11 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
final effectiveBuiltInServiceId = isBuiltInService
|
||||
? settings.defaultService
|
||||
: replacedBuiltInServiceId;
|
||||
final isBuiltInCompatibleService = effectiveBuiltInServiceId != null;
|
||||
final extensionService = extensionDownloadProviders
|
||||
.where((e) => e.id == settings.defaultService)
|
||||
.firstOrNull;
|
||||
final hasQualityOptions = extensionService?.qualityOptions.isNotEmpty ?? false;
|
||||
final canAskQuality = effectiveBuiltInServiceId != null || hasQualityOptions;
|
||||
final isTidalService = effectiveBuiltInServiceId == 'tidal';
|
||||
|
||||
return PopScope(
|
||||
@@ -400,17 +404,17 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
title: context.l10n.downloadAskBeforeDownload,
|
||||
subtitle: !hasDownloadProviders
|
||||
? context.l10n.extensionsNoDownloadProvider
|
||||
: isBuiltInCompatibleService
|
||||
: canAskQuality
|
||||
? context.l10n.downloadAskQualitySubtitle
|
||||
: context.l10n.downloadSelectServiceToEnable,
|
||||
value: settings.askQualityBeforeDownload,
|
||||
enabled: hasDownloadProviders && isBuiltInCompatibleService,
|
||||
enabled: hasDownloadProviders && canAskQuality,
|
||||
onChanged: (value) => ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setAskQualityBeforeDownload(value),
|
||||
),
|
||||
if (!settings.askQualityBeforeDownload &&
|
||||
isBuiltInCompatibleService) ...[
|
||||
canAskQuality) ...[
|
||||
_QualityOption(
|
||||
title: context.l10n.qualityFlacLossless,
|
||||
subtitle: context.l10n.qualityFlacLosslessSubtitle,
|
||||
@@ -472,7 +476,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
text: context.l10n.extensionsNoDownloadProvider,
|
||||
secondaryText: context.l10n.storeAddRepoDescription,
|
||||
),
|
||||
] else if (!isBuiltInCompatibleService) ...[
|
||||
] else if (!canAskQuality) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Row(
|
||||
@@ -2110,37 +2114,59 @@ class _ServiceSelector extends ConsumerWidget {
|
||||
text: context.l10n.extensionsNoDownloadProvider,
|
||||
secondaryText: context.l10n.storeAddRepoDescription,
|
||||
)
|
||||
else ...[
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final provider in builtInProviders)
|
||||
_ServiceChip(
|
||||
icon: resolveProviderIcon(provider.id),
|
||||
label: provider.displayName,
|
||||
isSelected: effectiveService == provider.id,
|
||||
onTap: () => onChanged(provider.id),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (extensionProviders.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final extension in extensionProviders)
|
||||
else
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final allItems = <Widget>[
|
||||
for (final provider in builtInProviders)
|
||||
_ServiceChip(
|
||||
icon: resolveProviderIcon(provider.id),
|
||||
label: provider.displayName,
|
||||
isSelected: effectiveService == provider.id,
|
||||
onTap: () => onChanged(provider.id),
|
||||
),
|
||||
for (final ext in extensionProviders)
|
||||
_ServiceChip(
|
||||
icon: Icons.extension,
|
||||
label: extension.displayName,
|
||||
isSelected: effectiveService == extension.id,
|
||||
onTap: () => onChanged(extension.id),
|
||||
label: ext.displayName,
|
||||
isSelected: effectiveService == ext.id,
|
||||
onTap: () => onChanged(ext.id),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
];
|
||||
const perRow = 4;
|
||||
final rows = <Widget>[];
|
||||
for (var i = 0; i < allItems.length; i += perRow) {
|
||||
final chunk = allItems.sublist(
|
||||
i,
|
||||
(i + perRow > allItems.length)
|
||||
? allItems.length
|
||||
: i + perRow,
|
||||
);
|
||||
rows.add(
|
||||
Row(
|
||||
children: [
|
||||
for (var j = 0; j < perRow; j++) ...[
|
||||
if (j > 0) const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: j < chunk.length
|
||||
? chunk[j]
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
for (var i = 0; i < rows.length; i++) ...[
|
||||
if (i > 0) const SizedBox(height: 8),
|
||||
rows[i],
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -2247,6 +2273,9 @@ class _ServiceChip extends StatelessWidget {
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
@@ -2254,7 +2283,6 @@ class _ServiceChip extends StatelessWidget {
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -724,6 +724,9 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final extState = ref.watch(extensionProvider);
|
||||
final builtInProviders = builtInSearchProviderSpecs;
|
||||
final extensionSearchProviders = extState.extensions
|
||||
.where((ext) => ext.enabled && ext.hasCustomSearch)
|
||||
.toList();
|
||||
|
||||
final rawSearchProvider = settings.searchProvider?.trim() ?? '';
|
||||
final isValidBuiltIn = isBuiltInSearchProvider(rawSearchProvider);
|
||||
@@ -757,6 +760,9 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
subtitle = context.l10n.optionsPrimaryProviderSubtitle;
|
||||
}
|
||||
|
||||
final hasAnyProvider =
|
||||
builtInProviders.isNotEmpty || extensionSearchProviders.isNotEmpty;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -778,28 +784,7 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
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(provider.id);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (activeExtension != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
if (!hasAnyProvider)
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
@@ -810,15 +795,78 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.l10n.optionsSwitchBack,
|
||||
context.l10n.optionsPrimaryProviderSubtitle,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final allItems = <Widget>[
|
||||
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(provider.id);
|
||||
},
|
||||
),
|
||||
for (final ext in extensionSearchProviders)
|
||||
_SourceChip(
|
||||
icon: Icons.extension,
|
||||
label: ext.displayName,
|
||||
isSelected: searchProvider == ext.id,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setSearchProvider(ext.id);
|
||||
},
|
||||
),
|
||||
];
|
||||
const perRow = 4;
|
||||
final rows = <Widget>[];
|
||||
for (var i = 0; i < allItems.length; i += perRow) {
|
||||
final chunk = allItems.sublist(
|
||||
i,
|
||||
(i + perRow > allItems.length)
|
||||
? allItems.length
|
||||
: i + perRow,
|
||||
);
|
||||
rows.add(
|
||||
Row(
|
||||
children: [
|
||||
for (var j = 0; j < perRow; j++) ...[
|
||||
if (j > 0) const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: j < chunk.length
|
||||
? chunk[j]
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
for (var i = 0; i < rows.length; i++) ...[
|
||||
if (i > 0) const SizedBox(height: 8),
|
||||
rows[i],
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -939,7 +987,7 @@ class _SourceChip extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -953,6 +1001,9 @@ class _SourceChip extends StatelessWidget {
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
|
||||
Reference in New Issue
Block a user