feat: add search filters for Deezer default search

- Add filter parameter to Deezer SearchAll (track/artist/album/playlist)
- When filter is specified, increase limit for that type only
- Add default Deezer filters when not using extension search
- Reduce artist limit from 5 to 2 in home search results
- Filter bar now shows for both extension and default Deezer search
- Fix filter not being passed correctly during search (preserve filter state)
This commit is contained in:
zarzet
2026-01-31 12:28:12 +07:00
parent ff7135bf2c
commit fd3158fd15
6 changed files with 226 additions and 169 deletions
+10 -6
View File
@@ -317,10 +317,13 @@ class TrackNotifier extends Notifier<TrackState> {
}
}
Future<void> search(String query, {String? metadataSource}) async {
Future<void> search(String query, {String? metadataSource, String? filterOverride}) async {
final requestId = ++_currentRequestId;
// Preserve selected filter during loading
final currentFilter = filterOverride ?? state.selectedSearchFilter;
state = TrackState(isLoading: true, hasSearchText: state.hasSearchText);
state = TrackState(isLoading: true, hasSearchText: state.hasSearchText, selectedSearchFilter: currentFilter);
try {
final settings = ref.read(settingsProvider);
@@ -338,7 +341,7 @@ class TrackNotifier extends Notifier<TrackState> {
final source = metadataSource ?? 'deezer';
_log.i(
'Search started: source=$source, query="$query", useExtensions=$useExtensions',
'Search started: source=$source, query="$query", useExtensions=$useExtensions, filter=$currentFilter',
);
Map<String, dynamic> results;
@@ -364,11 +367,11 @@ class TrackNotifier extends Notifier<TrackState> {
if (source == 'deezer') {
_log.d('Calling Deezer search API...');
results = await PlatformBridge.searchDeezerAll(query, trackLimit: 20, artistLimit: 5);
results = await PlatformBridge.searchDeezerAll(query, trackLimit: 20, artistLimit: 2, filter: currentFilter);
_log.i('Deezer returned ${(results['tracks'] as List?)?.length ?? 0} tracks, ${(results['artists'] as List?)?.length ?? 0} artists, ${(results['albums'] as List?)?.length ?? 0} albums');
} else {
_log.d('Calling Spotify search API...');
results = await PlatformBridge.searchSpotifyAll(query, trackLimit: 20, artistLimit: 5);
results = await PlatformBridge.searchSpotifyAll(query, trackLimit: 20, artistLimit: 2);
_log.i('Spotify returned ${(results['tracks'] as List?)?.length ?? 0} tracks, ${(results['artists'] as List?)?.length ?? 0} artists');
}
@@ -461,11 +464,12 @@ class TrackNotifier extends Notifier<TrackState> {
searchPlaylists: playlists,
isLoading: false,
hasSearchText: state.hasSearchText,
selectedSearchFilter: currentFilter, // Preserve filter in results
);
} catch (e, stackTrace) {
if (!_isRequestValid(requestId)) return;
_log.e('Search failed: $e', e, stackTrace);
state = TrackState(isLoading: false, error: e.toString(), hasSearchText: state.hasSearchText);
state = TrackState(isLoading: false, error: e.toString(), hasSearchText: state.hasSearchText, selectedSearchFilter: currentFilter);
}
}
+20 -5
View File
@@ -204,11 +204,12 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
}
}
Future<void> _performSearch(String query) async {
Future<void> _performSearch(String query, {String? filterOverride}) async {
final settings = ref.read(settingsProvider);
final extState = ref.read(extensionProvider);
final searchProvider = settings.searchProvider;
final selectedFilter = ref.read(trackProvider).selectedSearchFilter;
// Use filterOverride if provided, otherwise read from state
final selectedFilter = filterOverride ?? ref.read(trackProvider).selectedSearchFilter;
final searchKey = '${searchProvider ?? 'default'}:$query:${selectedFilter ?? 'all'}';
if (_lastSearchQuery == searchKey) return;
@@ -229,7 +230,7 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
if (searchProvider != null && searchProvider.isNotEmpty && !isExtensionEnabled) {
ref.read(settingsProvider.notifier).setSearchProvider(null);
}
await ref.read(trackProvider.notifier).search(query, metadataSource: settings.metadataSource);
await ref.read(trackProvider.notifier).search(query, metadataSource: settings.metadataSource, filterOverride: selectedFilter);
}
ref.read(settingsProvider.notifier).setHasSearchedBefore();
}
@@ -510,11 +511,25 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
final selectedSearchFilter = ref.watch(trackProvider.select((s) => s.selectedSearchFilter));
Extension? currentSearchExtension;
List<SearchFilter> searchFilters = [];
if (currentSearchProvider != null && currentSearchProvider.isNotEmpty) {
// Check if using extension search provider
final isUsingExtensionSearch = currentSearchProvider != null &&
currentSearchProvider.isNotEmpty &&
extState.extensions.any((e) => e.id == currentSearchProvider && e.enabled);
if (isUsingExtensionSearch) {
currentSearchExtension = extState.extensions.where((e) => e.id == currentSearchProvider && e.enabled).firstOrNull;
if (currentSearchExtension?.searchBehavior?.filters.isNotEmpty == true) {
searchFilters = currentSearchExtension!.searchBehavior!.filters;
}
} else {
// Default Deezer filters
searchFilters = const [
SearchFilter(id: 'track', label: 'Tracks', icon: 'music'),
SearchFilter(id: 'artist', label: 'Artists', icon: 'artist'),
SearchFilter(id: 'album', label: 'Albums', icon: 'album'),
SearchFilter(id: 'playlist', label: 'Playlists', icon: 'playlist'),
];
}
if (hasActualResults && isShowingRecentAccess) {
@@ -2127,7 +2142,7 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
// Reset last search query to force new search
_lastSearchQuery = null;
_performSearch(text);
_performSearch(text, filterOverride: filter);
}
Widget _buildSearchBar(ColorScheme colorScheme) {
+2 -1
View File
@@ -343,11 +343,12 @@ class PlatformBridge {
await _channel.invokeMethod('clearTrackCache');
}
static Future<Map<String, dynamic>> searchDeezerAll(String query, {int trackLimit = 15, int artistLimit = 3}) async {
static Future<Map<String, dynamic>> searchDeezerAll(String query, {int trackLimit = 15, int artistLimit = 2, String? filter}) async {
final result = await _channel.invokeMethod('searchDeezerAll', {
'query': query,
'track_limit': trackLimit,
'artist_limit': artistLimit,
'filter': filter ?? '',
});
return jsonDecode(result as String) as Map<String, dynamic>;
}