diff --git a/lib/api/library_browser_provider.dart b/lib/api/library_browser_provider.dart index 38aec82..607cb56 100644 --- a/lib/api/library_browser_provider.dart +++ b/lib/api/library_browser_provider.dart @@ -1,13 +1,33 @@ 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:shelfsdk/audiobookshelf_api.dart' show Author; import 'package:vaani/api/api_provider.dart' show authenticatedApiProvider; import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; part 'library_browser_provider.g.dart'; +/// Simple series data for display purposes +class SimpleSeries { + final String id; + final String name; + final int? numBooks; + + SimpleSeries({ + required this.id, + required this.name, + this.numBooks, + }); + + factory SimpleSeries.fromJson(Map json) { + return SimpleSeries( + id: json['id'] as String, + name: json['name'] as String, + numBooks: json['numBooks'] as int?, + ); + } +} + final _logger = Logger('LibraryBrowserProvider'); /// Provider for fetching all authors in the current library @@ -43,37 +63,59 @@ Future> libraryGenres(Ref ref) async { return []; } - final filterData = await api.libraries.getFilterData(libraryId: currentLibrary.id); + // Use raw API call to avoid Series deserialization issues in LibraryFilterData + final genres = await api.getJson>( + path: '/api/libraries/${currentLibrary.id}/filterdata', + requiresAuth: true, + fromJson: (json) { + if (json is Map && json.containsKey('genres')) { + final genresList = json['genres'] as List; + return genresList.map((e) => e.toString()).toList(); + } + return []; + }, + ); - if (filterData == null) { - _logger.warning('Failed to fetch filter data for library ${currentLibrary.id}'); + if (genres == null) { + _logger.warning('Failed to fetch genres for library ${currentLibrary.id}'); return []; } - _logger.fine('Fetched ${filterData.genres.length} genres'); - return filterData.genres; + _logger.fine('Fetched ${genres.length} genres'); + return genres; } /// Provider for fetching all series in the current library @riverpod -Future librarySeries(Ref ref) async { +Future> librarySeries(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 null; + return []; } - final series = await api.libraries.getSeries( - libraryId: currentLibrary.id, + // Use raw API call to avoid Series deserialization issues + final seriesList = await api.getJson>( + path: '/api/libraries/${currentLibrary.id}/series', + requiresAuth: true, + fromJson: (json) { + if (json is Map && json.containsKey('results')) { + final results = json['results'] as List; + return results + .map((e) => SimpleSeries.fromJson(e as Map)) + .toList(); + } + return []; + }, ); - if (series == null) { + if (seriesList == null) { _logger.warning('Failed to fetch series for library ${currentLibrary.id}'); - return null; + return []; } - _logger.fine('Fetched ${series.results.length} series (${series.total} total)'); - return series; + _logger.fine('Fetched ${seriesList.length} series'); + return seriesList; } diff --git a/lib/features/library_browser/view/library_authors_page.dart b/lib/features/library_browser/view/library_authors_page.dart index c01cc8d..7c42ac8 100644 --- a/lib/features/library_browser/view/library_authors_page.dart +++ b/lib/features/library_browser/view/library_authors_page.dart @@ -25,6 +25,10 @@ class LibraryAuthorsPage extends HookConsumerWidget { ); } + // Sort authors alphabetically by name + final sortedAuthors = List.from(authors) + ..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); + return GridView.builder( padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( @@ -33,9 +37,9 @@ class LibraryAuthorsPage extends HookConsumerWidget { crossAxisSpacing: 16, mainAxisSpacing: 16, ), - itemCount: authors.length, + itemCount: sortedAuthors.length, itemBuilder: (context, index) { - final author = authors[index]; + final author = sortedAuthors[index]; return AuthorCard(author: author); }, ); diff --git a/lib/features/library_browser/view/library_series_page.dart b/lib/features/library_browser/view/library_series_page.dart index 50fbab1..33248c5 100644 --- a/lib/features/library_browser/view/library_series_page.dart +++ b/lib/features/library_browser/view/library_series_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:vaani/api/library_browser_provider.dart'; class LibrarySeriesPage extends HookConsumerWidget { @@ -15,8 +14,8 @@ class LibrarySeriesPage extends HookConsumerWidget { title: const Text('Series'), ), body: seriesAsync.when( - data: (seriesResponse) { - if (seriesResponse == null || seriesResponse.results.isEmpty) { + data: (seriesList) { + if (seriesList.isEmpty) { return const Center( child: Text('No series found'), ); @@ -24,9 +23,9 @@ class LibrarySeriesPage extends HookConsumerWidget { return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: seriesResponse.results.length, + itemCount: seriesList.length, itemBuilder: (context, index) { - final series = seriesResponse.results[index]; + final series = seriesList[index]; return SeriesListTile(series: series); }, ); @@ -55,22 +54,10 @@ class SeriesListTile extends StatelessWidget { required this.series, }); - final Series series; + final SimpleSeries series; @override Widget build(BuildContext context) { - // Extract series data based on variant - final String seriesName = series.name; - final int? numBooks = series.maybeMap( - (s) => null, // base variant - numBooks: (s) => s.numBooks, - books: (s) => s.books.length, - sequence: (s) => null, - shelf: (s) => s.books.length, - author: (s) => s.items?.length, - orElse: () => null, - ); - return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: ListTile( @@ -87,18 +74,18 @@ class SeriesListTile extends StatelessWidget { ), ), title: Text( - seriesName, + series.name, style: Theme.of(context).textTheme.titleMedium, ), - subtitle: numBooks != null - ? Text('$numBooks ${numBooks == 1 ? 'book' : 'books'}') + subtitle: series.numBooks != null + ? Text('${series.numBooks} ${series.numBooks == 1 ? 'book' : 'books'}') : null, trailing: const Icon(Icons.chevron_right), onTap: () { // TODO: Navigate to series detail page ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Tapped on $seriesName'), + content: Text('Tapped on ${series.name}'), duration: const Duration(seconds: 1), ), );