refactor: move deezer search flow to extension

This commit is contained in:
zarzet
2026-04-13 23:35:02 +07:00
parent e097d3f605
commit 071db2f109
13 changed files with 337 additions and 198 deletions
+106 -27
View File
@@ -426,14 +426,18 @@ class _HomeTabState extends ConsumerState<HomeTab>
String? currentSearchProvider,
List<Extension> extensions,
) {
final resolvedSearchProvider = _resolveSearchProvider(
currentSearchProvider,
extensions,
);
final isUsingExtensionSearch =
currentSearchProvider != null &&
currentSearchProvider.isNotEmpty &&
extensions.any((e) => e.id == currentSearchProvider && e.enabled);
resolvedSearchProvider != null &&
resolvedSearchProvider.isNotEmpty &&
extensions.any((e) => e.id == resolvedSearchProvider && e.enabled);
if (isUsingExtensionSearch) {
final currentSearchExtension = extensions
.where((e) => e.id == currentSearchProvider && e.enabled)
.where((e) => e.id == resolvedSearchProvider && e.enabled)
.firstOrNull;
final filters = currentSearchExtension?.searchBehavior?.filters;
if (filters != null && filters.isNotEmpty) {
@@ -449,6 +453,36 @@ class _HomeTabState extends ConsumerState<HomeTab>
];
}
Extension? _defaultSearchExtension(List<Extension> extensions) {
return extensions
.where(
(ext) =>
ext.enabled &&
ext.hasCustomSearch &&
ext.searchBehavior?.primary == true,
)
.firstOrNull ??
extensions
.where((ext) => ext.enabled && ext.hasCustomSearch)
.firstOrNull;
}
String? _resolveSearchProvider(
String? explicitSearchProvider,
List<Extension> extensions,
) {
final explicit = explicitSearchProvider?.trim();
if (explicit != null &&
explicit.isNotEmpty &&
(_builtInSearchProviders.contains(explicit) ||
extensions.any(
(ext) => ext.enabled && ext.hasCustomSearch && ext.id == explicit,
))) {
return explicit;
}
return _defaultSearchExtension(extensions)?.id;
}
String? _sanitizeSearchFilterForProvider(
String? filter,
String? currentSearchProvider,
@@ -585,7 +619,10 @@ class _HomeTabState extends ConsumerState<HomeTab>
bool _isLiveSearchEnabled() {
final settings = ref.read(settingsProvider);
final extState = ref.read(extensionProvider);
final searchProvider = settings.searchProvider;
final searchProvider = _resolveSearchProvider(
settings.searchProvider,
extState.extensions,
);
if (searchProvider == null || searchProvider.isEmpty) return false;
@@ -654,7 +691,10 @@ class _HomeTabState extends ConsumerState<HomeTab>
Future<void> _performSearch(String query, {String? filterOverride}) async {
final settings = ref.read(settingsProvider);
final extState = ref.read(extensionProvider);
final searchProvider = settings.searchProvider;
final searchProvider = _resolveSearchProvider(
settings.searchProvider,
extState.extensions,
);
final selectedFilter =
_sanitizeSearchFilterForProvider(
filterOverride,
@@ -2166,17 +2206,25 @@ class _HomeTabState extends ConsumerState<HomeTab>
);
}
bool _isEnabledMetadataExtension(String? providerId) {
final normalized = providerId?.trim();
if (normalized == null || normalized.isEmpty) return false;
return ref
.read(extensionProvider)
.extensions
.any(
(ext) =>
ext.enabled && ext.hasMetadataProvider && ext.id == normalized,
);
}
void _navigateToRecentItem(RecentAccessItem item) {
_searchFocusNode.unfocus();
switch (item.type) {
case RecentAccessType.artist:
if (item.providerId != null &&
item.providerId!.isNotEmpty &&
item.providerId != 'deezer' &&
item.providerId != 'spotify' &&
item.providerId != 'tidal' &&
item.providerId != 'qobuz') {
if (_isEnabledMetadataExtension(item.providerId)) {
Navigator.push(
context,
MaterialPageRoute<void>(
@@ -2213,12 +2261,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
),
),
);
} else if (item.providerId != null &&
item.providerId!.isNotEmpty &&
item.providerId != 'deezer' &&
item.providerId != 'spotify' &&
item.providerId != 'tidal' &&
item.providerId != 'qobuz') {
} else if (_isEnabledMetadataExtension(item.providerId)) {
Navigator.push(
context,
MaterialPageRoute<void>(
@@ -2263,12 +2306,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
return;
}
if (item.providerId != null &&
item.providerId!.isNotEmpty &&
item.providerId != 'deezer' &&
item.providerId != 'spotify' &&
item.providerId != 'tidal' &&
item.providerId != 'qobuz') {
if (_isEnabledMetadataExtension(item.providerId)) {
Navigator.push(
context,
MaterialPageRoute<void>(
@@ -3185,8 +3223,11 @@ class _HomeTabState extends ConsumerState<HomeTab>
String _getSearchHint() {
final settings = ref.read(settingsProvider);
final searchProvider = settings.searchProvider;
final extState = ref.read(extensionProvider);
final searchProvider = _resolveSearchProvider(
settings.searchProvider,
extState.extensions,
);
if (!extState.isInitialized) {
return 'Paste supported URL or search...';
@@ -3387,9 +3428,23 @@ class _SearchProviderDropdown extends ConsumerWidget {
const _SearchProviderDropdown({this.onProviderChanged});
Extension? _defaultSearchExtension(List<Extension> extensions) {
return extensions
.where(
(ext) =>
ext.enabled &&
ext.hasCustomSearch &&
ext.searchBehavior?.primary == true,
)
.firstOrNull ??
extensions
.where((ext) => ext.enabled && ext.hasCustomSearch)
.firstOrNull;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentProvider = ref.watch(
final rawCurrentProvider = ref.watch(
settingsProvider.select((s) => s.searchProvider),
);
final extensions = ref.watch(extensionProvider.select((s) => s.extensions));
@@ -3398,6 +3453,17 @@ class _SearchProviderDropdown extends ConsumerWidget {
final searchProviders = extensions
.where((ext) => ext.enabled && ext.hasCustomSearch)
.toList();
final primarySearchExtension = _defaultSearchExtension(searchProviders);
final defaultProviderLabel =
primarySearchExtension?.displayName ?? 'Deezer';
final defaultProviderIconPath = primarySearchExtension?.iconPath;
final currentProvider =
rawCurrentProvider != null &&
rawCurrentProvider.isNotEmpty &&
({'tidal', 'qobuz'}.contains(rawCurrentProvider) ||
searchProviders.any((e) => e.id == rawCurrentProvider))
? rawCurrentProvider
: null;
Extension? currentExt;
if (currentProvider != null && currentProvider.isNotEmpty) {
@@ -3417,6 +3483,19 @@ class _SearchProviderDropdown extends ConsumerWidget {
if (currentExt.searchBehavior?.icon != null) {
displayIcon = _getIconFromName(currentExt.searchBehavior!.icon!);
}
} else if (primarySearchExtension?.searchBehavior?.icon != null) {
displayIcon = _getIconFromName(
primarySearchExtension!.searchBehavior!.icon!,
);
iconPath = defaultProviderIconPath;
} else if (defaultProviderIconPath != null &&
defaultProviderIconPath.isNotEmpty) {
iconPath = defaultProviderIconPath;
if (primarySearchExtension?.searchBehavior?.icon != null) {
displayIcon = _getIconFromName(
primarySearchExtension!.searchBehavior!.icon!,
);
}
} else if (isBuiltInProvider) {
displayIcon = Icons.music_note;
}
@@ -3471,7 +3550,7 @@ class _SearchProviderDropdown extends ConsumerWidget {
const SizedBox(width: 12),
Expanded(
child: Text(
'Deezer',
defaultProviderLabel,
style: TextStyle(
fontWeight:
currentProvider == null || currentProvider.isEmpty
@@ -225,8 +225,8 @@ class _MetadataProviderItem extends StatelessWidget {
return _MetadataProviderInfo(
name: 'Deezer',
icon: Icons.album,
description: context.l10n.metadataNoRateLimits,
isBuiltIn: true,
description: context.l10n.providerExtension,
isBuiltIn: false,
);
case 'qobuz':
return _MetadataProviderInfo(
@@ -719,13 +719,39 @@ class _MetadataSourceSelector extends ConsumerWidget {
static const _builtInProviders = {'tidal': 'Tidal', 'qobuz': 'Qobuz'};
Extension? _defaultSearchExtension(List<Extension> extensions) {
return extensions
.where(
(ext) =>
ext.enabled &&
ext.hasCustomSearch &&
ext.searchBehavior?.primary == true,
)
.firstOrNull ??
extensions
.where((ext) => ext.enabled && ext.hasCustomSearch)
.firstOrNull;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final colorScheme = Theme.of(context).colorScheme;
final settings = ref.watch(settingsProvider);
final extState = ref.watch(extensionProvider);
final searchProvider = settings.searchProvider ?? '';
final rawSearchProvider = settings.searchProvider?.trim() ?? '';
final isValidBuiltIn = _builtInProviders.containsKey(rawSearchProvider);
final primarySearchExtension = _defaultSearchExtension(extState.extensions);
final defaultProviderLabel =
primarySearchExtension?.displayName ?? 'Deezer';
final searchProvider =
isValidBuiltIn ||
extState.extensions.any(
(e) =>
e.enabled && e.hasCustomSearch && e.id == rawSearchProvider,
)
? rawSearchProvider
: '';
final isBuiltIn = _builtInProviders.containsKey(searchProvider);
Extension? activeExtension;
@@ -772,7 +798,7 @@ class _MetadataSourceSelector extends ConsumerWidget {
children: [
_SourceChip(
icon: Icons.graphic_eq,
label: 'Deezer',
label: defaultProviderLabel,
isSelected: searchProvider.isEmpty,
onTap: () {
if (hasNonDefaultProvider) {
@@ -816,7 +842,7 @@ class _MetadataSourceSelector extends ConsumerWidget {
const SizedBox(width: 8),
Expanded(
child: Text(
'Tap Deezer to switch back from extension',
'Tap $defaultProviderLabel to switch back from extension',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),