mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-07-10 19:31:48 +00:00
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.
This commit is contained in:
parent
07aea41c6e
commit
53027bf74c
7 changed files with 485 additions and 5 deletions
79
lib/api/library_browser_provider.dart
Normal file
79
lib/api/library_browser_provider.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
163
lib/features/library_browser/view/library_authors_page.dart
Normal file
163
lib/features/library_browser/view/library_authors_page.dart
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||||
|
import 'package:vaani/api/api_provider.dart';
|
||||||
|
import 'package:vaani/api/library_browser_provider.dart';
|
||||||
|
import 'package:vaani/settings/api_settings_provider.dart';
|
||||||
|
|
||||||
|
class LibraryAuthorsPage extends HookConsumerWidget {
|
||||||
|
const LibraryAuthorsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final authorsAsync = ref.watch(libraryAuthorsProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Authors'),
|
||||||
|
),
|
||||||
|
body: authorsAsync.when(
|
||||||
|
data: (authors) {
|
||||||
|
if (authors.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text('No authors found'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GridView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
childAspectRatio: 0.75,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
),
|
||||||
|
itemCount: authors.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final author = authors[index];
|
||||||
|
return AuthorCard(author: author);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stack) => Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('Error loading authors: $error'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthorCard extends HookConsumerWidget {
|
||||||
|
const AuthorCard({
|
||||||
|
super.key,
|
||||||
|
required this.author,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Author author;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final apiSettings = ref.watch(apiSettingsProvider);
|
||||||
|
final api = ref.watch(authenticatedApiProvider);
|
||||||
|
|
||||||
|
// Determine the author variant and extract relevant data
|
||||||
|
final String? imagePath = author.map(
|
||||||
|
(base) => base.imagePath,
|
||||||
|
minified: (minified) => null,
|
||||||
|
expanded: (expanded) => expanded.imagePath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final int? numBooks = author.map(
|
||||||
|
(base) => base.libraryItems?.length,
|
||||||
|
minified: (minified) => null,
|
||||||
|
expanded: (expanded) => expanded.numBooks,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the image URL if imagePath is available
|
||||||
|
String? imageUrl;
|
||||||
|
if (imagePath != null && apiSettings.activeServer != null) {
|
||||||
|
imageUrl = '${apiSettings.activeServer!.url}/api/authors/${author.id}/image';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Navigate to author detail page
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Tapped on ${author.name}'),
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: imageUrl != null
|
||||||
|
? CachedNetworkImage(
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
httpHeaders: {
|
||||||
|
'Authorization': 'Bearer ${apiSettings.activeUser?.token}',
|
||||||
|
},
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
placeholder: (context, url) => Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
|
child: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
|
child: const Icon(Icons.person, size: 48),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
|
child: const Icon(Icons.person, size: 48),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
author.name,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
if (numBooks != null) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'$numBooks ${numBooks == 1 ? 'book' : 'books'}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,6 @@ import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'
|
||||||
show showLibrarySwitcher;
|
show showLibrarySwitcher;
|
||||||
import 'package:vaani/router/router.dart' show Routes;
|
import 'package:vaani/router/router.dart' show Routes;
|
||||||
import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons;
|
import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons;
|
||||||
import 'package:vaani/shared/widgets/not_implemented.dart'
|
|
||||||
show showNotImplementedToast;
|
|
||||||
|
|
||||||
class LibraryBrowserPage extends HookConsumerWidget {
|
class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
const LibraryBrowserPage({super.key});
|
const LibraryBrowserPage({super.key});
|
||||||
|
|
@ -48,7 +46,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
leading: const Icon(Icons.person),
|
leading: const Icon(Icons.person),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showNotImplementedToast(context);
|
GoRouter.of(context).pushNamed(Routes.libraryAuthors.name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -56,7 +54,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
leading: const Icon(Icons.category),
|
leading: const Icon(Icons.category),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showNotImplementedToast(context);
|
GoRouter.of(context).pushNamed(Routes.libraryGenres.name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
@ -64,7 +62,7 @@ class LibraryBrowserPage extends HookConsumerWidget {
|
||||||
leading: const Icon(Icons.list),
|
leading: const Icon(Icons.list),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showNotImplementedToast(context);
|
GoRouter.of(context).pushNamed(Routes.librarySeries.name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// Downloads
|
// Downloads
|
||||||
|
|
|
||||||
96
lib/features/library_browser/view/library_genres_page.dart
Normal file
96
lib/features/library_browser/view/library_genres_page.dart
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:vaani/api/library_browser_provider.dart';
|
||||||
|
|
||||||
|
class LibraryGenresPage extends HookConsumerWidget {
|
||||||
|
const LibraryGenresPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final genresAsync = ref.watch(libraryGenresProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Genres'),
|
||||||
|
),
|
||||||
|
body: genresAsync.when(
|
||||||
|
data: (genres) {
|
||||||
|
if (genres.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text('No genres found'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort genres alphabetically
|
||||||
|
final sortedGenres = List<String>.from(genres)..sort();
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
itemCount: sortedGenres.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final genre = sortedGenres[index];
|
||||||
|
return GenreListTile(genre: genre);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stack) => Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('Error loading genres: $error'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenreListTile extends StatelessWidget {
|
||||||
|
const GenreListTile({
|
||||||
|
super.key,
|
||||||
|
required this.genre,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String genre;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.category,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
genre,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Navigate to books in this genre
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Tapped on $genre'),
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
lib/features/library_browser/view/library_series_page.dart
Normal file
105
lib/features/library_browser/view/library_series_page.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
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 {
|
||||||
|
const LibrarySeriesPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final seriesAsync = ref.watch(librarySeriesProvider());
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Series'),
|
||||||
|
),
|
||||||
|
body: seriesAsync.when(
|
||||||
|
data: (seriesResponse) {
|
||||||
|
if (seriesResponse == null || seriesResponse.results.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text('No series found'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
itemCount: seriesResponse.results.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final series = seriesResponse.results[index];
|
||||||
|
return SeriesListTile(series: series);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stack) => Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('Error loading series: $error'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeriesListTile extends StatelessWidget {
|
||||||
|
const SeriesListTile({
|
||||||
|
super.key,
|
||||||
|
required this.series,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Series series;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Extract series data based on variant
|
||||||
|
final String seriesName = series.name;
|
||||||
|
final int? numBooks = series.maybeMap(
|
||||||
|
numBooks: (s) => s.numBooks,
|
||||||
|
books: (s) => s.books.length,
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.list,
|
||||||
|
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
seriesName,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
subtitle: numBooks != null
|
||||||
|
? Text('$numBooks ${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'),
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -82,6 +82,25 @@ class Routes {
|
||||||
// parentRoute: library,
|
// parentRoute: library,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// library browser sub-routes
|
||||||
|
static const libraryAuthors = _SimpleRoute(
|
||||||
|
pathName: 'authors',
|
||||||
|
name: 'libraryAuthors',
|
||||||
|
parentRoute: libraryBrowser,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const libraryGenres = _SimpleRoute(
|
||||||
|
pathName: 'genres',
|
||||||
|
name: 'libraryGenres',
|
||||||
|
parentRoute: libraryBrowser,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const librarySeries = _SimpleRoute(
|
||||||
|
pathName: 'series',
|
||||||
|
name: 'librarySeries',
|
||||||
|
parentRoute: libraryBrowser,
|
||||||
|
);
|
||||||
|
|
||||||
// you page for the user
|
// you page for the user
|
||||||
static const you = _SimpleRoute(
|
static const you = _SimpleRoute(
|
||||||
pathName: 'you',
|
pathName: 'you',
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ import 'package:vaani/features/downloads/view/downloads_page.dart';
|
||||||
import 'package:vaani/features/explore/view/explore_page.dart';
|
import 'package:vaani/features/explore/view/explore_page.dart';
|
||||||
import 'package:vaani/features/explore/view/search_result_page.dart';
|
import 'package:vaani/features/explore/view/search_result_page.dart';
|
||||||
import 'package:vaani/features/item_viewer/view/library_item_page.dart';
|
import 'package:vaani/features/item_viewer/view/library_item_page.dart';
|
||||||
|
import 'package:vaani/features/library_browser/view/library_authors_page.dart';
|
||||||
import 'package:vaani/features/library_browser/view/library_browser_page.dart';
|
import 'package:vaani/features/library_browser/view/library_browser_page.dart';
|
||||||
|
import 'package:vaani/features/library_browser/view/library_genres_page.dart';
|
||||||
|
import 'package:vaani/features/library_browser/view/library_series_page.dart';
|
||||||
import 'package:vaani/features/logging/view/logs_page.dart';
|
import 'package:vaani/features/logging/view/logs_page.dart';
|
||||||
import 'package:vaani/features/onboarding/view/callback_page.dart';
|
import 'package:vaani/features/onboarding/view/callback_page.dart';
|
||||||
import 'package:vaani/features/onboarding/view/onboarding_single_page.dart';
|
import 'package:vaani/features/onboarding/view/onboarding_single_page.dart';
|
||||||
|
|
@ -121,6 +124,23 @@ class MyAppRouter {
|
||||||
path: Routes.libraryBrowser.localPath,
|
path: Routes.libraryBrowser.localPath,
|
||||||
name: Routes.libraryBrowser.name,
|
name: Routes.libraryBrowser.name,
|
||||||
pageBuilder: defaultPageBuilder(const LibraryBrowserPage()),
|
pageBuilder: defaultPageBuilder(const LibraryBrowserPage()),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.libraryAuthors.pathName,
|
||||||
|
name: Routes.libraryAuthors.name,
|
||||||
|
pageBuilder: defaultPageBuilder(const LibraryAuthorsPage()),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.libraryGenres.pathName,
|
||||||
|
name: Routes.libraryGenres.name,
|
||||||
|
pageBuilder: defaultPageBuilder(const LibraryGenresPage()),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.librarySeries.pathName,
|
||||||
|
name: Routes.librarySeries.name,
|
||||||
|
pageBuilder: defaultPageBuilder(const LibrarySeriesPage()),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue