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:
zarzet
2026-05-01 04:43:06 +07:00
parent bdd3f4aef5
commit 3f56b88fa5
25 changed files with 205 additions and 98 deletions
+2
View File
@@ -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,
+8
View File
@@ -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 {
+5 -5
View File
@@ -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(),
+17
View File
@@ -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",
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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 =>
+2 -2
View File
@@ -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';
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+2 -2
View File
@@ -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';
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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 =>
+3 -3
View File
@@ -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": {
+3 -3
View File
@@ -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": {
+5
View File
@@ -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,
+5 -1
View File
@@ -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();
}
+1 -9
View File
@@ -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,
),
],
),
+76 -25
View File
@@ -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,