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
+37 -11
View File
@@ -481,6 +481,7 @@ class ExtensionState {
}
class ExtensionNotifier extends Notifier<ExtensionState> {
static const _builtInMetadataProviders = ['qobuz', 'tidal'];
AppLifecycleListener? _appLifecycleListener;
bool _cleanupInFlight = false;
Completer<void>? _initializationCompleter;
@@ -883,10 +884,15 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
);
await PlatformBridge.setMetadataProviderPriority(priority);
} else {
priority = _sanitizeMetadataProviderPriority(
await PlatformBridge.getMetadataProviderPriority(),
);
final backendPriority =
await PlatformBridge.getMetadataProviderPriority();
priority = _sanitizeMetadataProviderPriority(backendPriority);
_log.d('Using default metadata provider priority: $priority');
await prefs.setString(
_metadataProviderPriorityKey,
jsonEncode(priority),
);
await PlatformBridge.setMetadataProviderPriority(priority);
}
state = state.copyWith(metadataProviderPriority: priority);
@@ -942,17 +948,26 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
List<String> getAllMetadataProviders() {
final providers = ['deezer', 'qobuz', 'tidal'];
for (final ext in state.extensions) {
if (ext.enabled && ext.hasMetadataProvider) {
providers.add(ext.id);
}
}
return providers;
final metadataExtensions = state.extensions
.where((ext) => ext.enabled && ext.hasMetadataProvider)
.toList();
final primarySearchMetadataExtensions = metadataExtensions
.where((ext) => ext.searchBehavior?.primary == true)
.map((ext) => ext.id);
final otherMetadataExtensions = metadataExtensions
.where((ext) => ext.searchBehavior?.primary != true)
.map((ext) => ext.id);
return [
...primarySearchMetadataExtensions,
..._builtInMetadataProviders,
...otherMetadataExtensions,
];
}
List<String> _sanitizeMetadataProviderPriority(List<String> input) {
final allowed = getAllMetadataProviders().toSet();
final preferredOrder = getAllMetadataProviders();
final result = <String>[];
for (final provider in input) {
@@ -961,7 +976,18 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
for (final provider in const ['deezer', 'qobuz', 'tidal']) {
final hasPreferredExtension = preferredOrder.any(
(provider) => !_builtInMetadataProviders.contains(provider),
);
final hasSavedExtension = result.any(
(provider) => !_builtInMetadataProviders.contains(provider),
);
if (!hasSavedExtension && hasPreferredExtension) {
return List<String>.from(preferredOrder);
}
for (final provider in preferredOrder) {
if (!result.contains(provider)) {
result.add(provider);
}
+104 -30
View File
@@ -583,8 +583,97 @@ class TrackNotifier extends Notifier<TrackState> {
String? builtInSearchProvider,
}) async {
final requestId = ++_currentRequestId;
final currentFilter = filterOverride ?? state.selectedSearchFilter;
final settings = ref.read(settingsProvider);
final extensionState = ref.read(extensionProvider);
String? resolvedProvider = builtInSearchProvider;
if (resolvedProvider == null || resolvedProvider.isEmpty) {
final explicitProvider = settings.searchProvider?.trim();
if (explicitProvider != null && explicitProvider.isNotEmpty) {
resolvedProvider = explicitProvider;
} else {
resolvedProvider =
extensionState.extensions
.where(
(ext) =>
ext.enabled &&
ext.hasCustomSearch &&
ext.searchBehavior?.primary == true,
)
.map((ext) => ext.id)
.firstOrNull ??
extensionState.extensions
.where((ext) => ext.enabled && ext.hasCustomSearch)
.map((ext) => ext.id)
.firstOrNull;
}
}
final isEnabledExtensionProvider =
resolvedProvider != null &&
resolvedProvider.isNotEmpty &&
extensionState.extensions.any(
(ext) => ext.enabled && ext.id == resolvedProvider,
);
if (resolvedProvider != null &&
resolvedProvider.isNotEmpty &&
resolvedProvider != 'tidal' &&
resolvedProvider != 'qobuz' &&
!isEnabledExtensionProvider &&
settings.searchProvider?.trim() == resolvedProvider) {
ref.read(settingsProvider.notifier).setSearchProvider(null);
resolvedProvider =
extensionState.extensions
.where(
(ext) =>
ext.enabled &&
ext.hasCustomSearch &&
ext.searchBehavior?.primary == true,
)
.map((ext) => ext.id)
.firstOrNull ??
extensionState.extensions
.where((ext) => ext.enabled && ext.hasCustomSearch)
.map((ext) => ext.id)
.firstOrNull;
}
if (resolvedProvider != null &&
resolvedProvider.isNotEmpty &&
resolvedProvider != 'tidal' &&
resolvedProvider != 'qobuz' &&
extensionState.extensions.any(
(ext) => ext.enabled && ext.id == resolvedProvider,
)) {
final resolvedFilter = currentFilter ?? 'track';
Map<String, dynamic>? options;
options = {'filter': resolvedFilter};
await customSearch(
resolvedProvider,
query,
options: options,
selectedFilter: resolvedFilter,
);
return;
}
final effectiveBuiltInProvider =
resolvedProvider == 'tidal' || resolvedProvider == 'qobuz'
? resolvedProvider
: builtInSearchProvider;
if (effectiveBuiltInProvider == null || effectiveBuiltInProvider.isEmpty) {
state = TrackState(
isLoading: false,
error: 'No active search provider available',
hasSearchText: state.hasSearchText,
isShowingRecentAccess: state.isShowingRecentAccess,
selectedSearchFilter: currentFilter,
);
return;
}
state = TrackState(
isLoading: true,
@@ -594,15 +683,13 @@ class TrackNotifier extends Notifier<TrackState> {
);
try {
final settings = ref.read(settingsProvider);
final extensionState = ref.read(extensionProvider);
final hasActiveMetadataExtensions = extensionState.extensions.any(
(e) => e.enabled && e.hasMetadataProvider,
);
final includeExtensions =
settings.useExtensionProviders && hasActiveMetadataExtensions;
final effectiveProvider = builtInSearchProvider ?? 'deezer';
final effectiveProvider = effectiveBuiltInProvider;
_log.i(
'Search started: provider=$effectiveProvider, query="$query", includeExtensions=$includeExtensions, filter=$currentFilter',
@@ -611,25 +698,6 @@ class TrackNotifier extends Notifier<TrackState> {
Map<String, dynamic> results;
List<Map<String, dynamic>> metadataTrackResults = [];
if (effectiveProvider == 'deezer') {
try {
_log.d('Calling metadata provider search API...');
metadataTrackResults =
await PlatformBridge.searchTracksWithMetadataProviders(
query,
limit: 20,
includeExtensions: includeExtensions,
);
_log.i(
'Metadata providers returned ${metadataTrackResults.length} tracks',
);
} catch (e) {
_log.w(
'Metadata provider search failed, falling back to Deezer tracks: $e',
);
}
}
switch (effectiveProvider) {
case 'tidal':
_log.d('Calling Tidal search API...');
@@ -650,13 +718,19 @@ class TrackNotifier extends Notifier<TrackState> {
);
break;
default:
_log.d('Calling Deezer search API...');
results = await PlatformBridge.searchDeezerAll(
query,
trackLimit: 20,
artistLimit: 2,
filter: currentFilter,
);
_log.d('Calling metadata provider track search API...');
metadataTrackResults =
await PlatformBridge.searchTracksWithMetadataProviders(
query,
limit: 20,
includeExtensions: includeExtensions,
);
results = const <String, List<dynamic>>{
'tracks': <dynamic>[],
'artists': <dynamic>[],
'albums': <dynamic>[],
'playlists': <dynamic>[],
};
break;
}
_log.i(