Vaani/lib/api/library_browser_provider.dart
Claude 53027bf74c
feat: implement library view with Authors, Genres, and Series browsing
This commit implements a comprehensive library browsing feature:

- Add LibraryBrowserProvider with providers for authors, genres, and series data
- Create LibraryAuthorsPage with grid view of authors including images and book counts
- Create LibraryGenresPage with list view of all genres
- Create LibrarySeriesPage with list view of series and book counts
- Update LibraryBrowserPage navigation to route to the new views
- Add routes for /browser/authors, /browser/genres, and /browser/series
- Replace "Not Implemented" toasts with functional navigation

The implementation uses the Audiobookshelf API via shelfsdk to fetch:
- Authors list with metadata (getAuthors)
- Genres from library filter data (getFilterData)
- Series with pagination support (getSeries)

All views follow Material Design 3 patterns and include proper loading/error states.
2025-11-20 10:52:18 +00:00

79 lines
2.5 KiB
Dart

import 'package:hooks_riverpod/hooks_riverpod.dart' show Ref;
import 'package:logging/logging.dart' show Logger;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shelfsdk/audiobookshelf_api.dart'
show Author, GetLibrarysSeriesResponse, LibraryFilterData;
import 'package:vaani/api/api_provider.dart' show authenticatedApiProvider;
import 'package:vaani/api/library_provider.dart' show currentLibraryProvider;
part 'library_browser_provider.g.dart';
final _logger = Logger('LibraryBrowserProvider');
/// Provider for fetching all authors in the current library
@riverpod
Future<List<Author>> libraryAuthors(Ref ref) async {
final api = ref.watch(authenticatedApiProvider);
final currentLibrary = await ref.watch(currentLibraryProvider.future);
if (currentLibrary == null) {
_logger.warning('No current library found');
return [];
}
final authors = await api.libraries.getAuthors(libraryId: currentLibrary.id);
if (authors == null) {
_logger.warning('Failed to fetch authors for library ${currentLibrary.id}');
return [];
}
_logger.fine('Fetched ${authors.length} authors');
return authors;
}
/// Provider for fetching all genres in the current library
@riverpod
Future<List<String>> libraryGenres(Ref ref) async {
final api = ref.watch(authenticatedApiProvider);
final currentLibrary = await ref.watch(currentLibraryProvider.future);
if (currentLibrary == null) {
_logger.warning('No current library found');
return [];
}
final filterData = await api.libraries.getFilterData(libraryId: currentLibrary.id);
if (filterData == null) {
_logger.warning('Failed to fetch filter data for library ${currentLibrary.id}');
return [];
}
_logger.fine('Fetched ${filterData.genres.length} genres');
return filterData.genres;
}
/// Provider for fetching all series in the current library
@riverpod
Future<GetLibrarysSeriesResponse?> librarySeries(Ref ref, {int page = 0, int limit = 50}) async {
final api = ref.watch(authenticatedApiProvider);
final currentLibrary = await ref.watch(currentLibraryProvider.future);
if (currentLibrary == null) {
_logger.warning('No current library found');
return null;
}
final series = await api.libraries.getSeries(
libraryId: currentLibrary.id,
);
if (series == null) {
_logger.warning('Failed to fetch series for library ${currentLibrary.id}');
return null;
}
_logger.fine('Fetched ${series.results.length} series (${series.total} total)');
return series;
}