mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-11 05:29:29 +00:00
routes
This commit is contained in:
parent
ebc14a0448
commit
f8597f7430
13 changed files with 509 additions and 33 deletions
12
lib/extensions/hero_tag_conventions.dart
Normal file
12
lib/extensions/hero_tag_conventions.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
class HeroTagPrefixes {
|
||||||
|
static const String heroTagPrefix = 'hero_tag_';
|
||||||
|
|
||||||
|
/// The hero tag for the book cover
|
||||||
|
static const String bookCover = 'book_cover_';
|
||||||
|
static const String bookCoverSkeleton = 'book_cover_skeleton_';
|
||||||
|
static const String authorAvatar = 'author_avatar_';
|
||||||
|
static const String authorAvatarSkeleton = 'author_avatar_skeleton_';
|
||||||
|
static const String authorName = 'author_name_';
|
||||||
|
static const String bookTitle = 'book_title_';
|
||||||
|
static const String narratorName = 'narrator_name_';
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:whispering_pages/api/server_provider.dart';
|
import 'package:whispering_pages/api/server_provider.dart';
|
||||||
import 'package:whispering_pages/db/storage.dart';
|
import 'package:whispering_pages/db/storage.dart';
|
||||||
import 'package:whispering_pages/pages/onboarding/onboarding.dart';
|
import 'package:whispering_pages/router/router.dart';
|
||||||
import 'package:whispering_pages/pages/pages.dart';
|
|
||||||
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||||
import 'package:whispering_pages/theme/theme.dart';
|
import 'package:whispering_pages/theme/theme.dart';
|
||||||
|
|
@ -32,13 +31,13 @@ class MyApp extends ConsumerWidget {
|
||||||
bool needOnboarding() {
|
bool needOnboarding() {
|
||||||
return apiSettings.activeUser == null || servers.isEmpty;
|
return apiSettings.activeUser == null || servers.isEmpty;
|
||||||
}
|
}
|
||||||
return MaterialApp(
|
return MaterialApp.router(
|
||||||
theme: lightTheme,
|
theme: lightTheme,
|
||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: ref.watch(appSettingsProvider).isDarkMode
|
themeMode: ref.watch(appSettingsProvider).isDarkMode
|
||||||
? ThemeMode.dark
|
? ThemeMode.dark
|
||||||
: ThemeMode.light,
|
: ThemeMode.light,
|
||||||
home: needOnboarding() ? const OnboardingPage() : const HomePage(),
|
routerConfig: MyAppRouter(needOnboarding: needOnboarding()).config,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
lib/pages/library_item_page.dart
Normal file
38
lib/pages/library_item_page.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:whispering_pages/api/api_provider.dart';
|
||||||
|
import 'package:whispering_pages/extensions/hero_tag_conventions.dart';
|
||||||
|
import 'package:whispering_pages/router/models/library_item_extras.dart';
|
||||||
|
|
||||||
|
class LibraryItemPage extends HookConsumerWidget {
|
||||||
|
const LibraryItemPage({
|
||||||
|
super.key,
|
||||||
|
required this.itemId,
|
||||||
|
this.extra,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String itemId;
|
||||||
|
final Object? extra;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final views = ref.watch(personalizedViewProvider);
|
||||||
|
final extraMap =
|
||||||
|
extra is LibraryItemExtras ? extra as LibraryItemExtras : null;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: Center(
|
||||||
|
child: Hero(
|
||||||
|
tag: HeroTagPrefixes.bookCover +
|
||||||
|
itemId +
|
||||||
|
(extraMap?.heroTagSuffix ?? ''),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.amber,
|
||||||
|
height: 200,
|
||||||
|
width: 200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
93
lib/pages/library_page.dart
Normal file
93
lib/pages/library_page.dart
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:whispering_pages/api/api_provider.dart';
|
||||||
|
import 'package:whispering_pages/settings/api_settings_provider.dart';
|
||||||
|
|
||||||
|
import '../widgets/drawer.dart';
|
||||||
|
import '../widgets/shelves/home_shelf.dart';
|
||||||
|
|
||||||
|
// TODO: implement the library page
|
||||||
|
class LibraryPage extends HookConsumerWidget {
|
||||||
|
const LibraryPage({this.libraryId, super.key});
|
||||||
|
|
||||||
|
final String? libraryId;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
// set the library id as the active library
|
||||||
|
if (libraryId != null) {
|
||||||
|
ref.read(apiSettingsProvider.notifier).updateState(
|
||||||
|
ref.watch(apiSettingsProvider).copyWith(activeLibraryId: libraryId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final views = ref.watch(personalizedViewProvider);
|
||||||
|
final scrollController = useScrollController();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: GestureDetector(
|
||||||
|
child: const Text('Whispering Pages'),
|
||||||
|
onTap: () {
|
||||||
|
// scroll to the top of the page
|
||||||
|
scrollController.animateTo(
|
||||||
|
0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
// refresh the view
|
||||||
|
ref.invalidate(personalizedViewProvider);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
drawer: const MyDrawer(),
|
||||||
|
body: Container(
|
||||||
|
child: views.when(
|
||||||
|
data: (data) {
|
||||||
|
final shelvesToDisplay = data
|
||||||
|
// .where((element) => !element.id.contains('discover'))
|
||||||
|
.map((shelf) {
|
||||||
|
debugPrint('building shelf ${shelf.label}');
|
||||||
|
return HomeShelf(
|
||||||
|
title: shelf.label,
|
||||||
|
shelf: shelf,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
return ref.refresh(personalizedViewProvider);
|
||||||
|
},
|
||||||
|
child: ListView.separated(
|
||||||
|
itemBuilder: (context, index) => shelvesToDisplay[index],
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
|
indent: 16,
|
||||||
|
endIndent: 16,
|
||||||
|
),
|
||||||
|
itemCount: shelvesToDisplay.length,
|
||||||
|
controller: scrollController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const LibraryPageSkeleton(),
|
||||||
|
error: (error, stack) {
|
||||||
|
return Text('Error: $error');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LibraryPageSkeleton extends StatelessWidget {
|
||||||
|
const LibraryPageSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export 'home_page.dart';
|
|
||||||
export 'server_manager.dart';
|
|
||||||
34
lib/router/constants.dart
Normal file
34
lib/router/constants.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// to store names of routes
|
||||||
|
|
||||||
|
part of 'router.dart';
|
||||||
|
|
||||||
|
class Routes {
|
||||||
|
static const home = 'home';
|
||||||
|
static const onboarding = 'onboarding';
|
||||||
|
static const library = _SimpleRoute(
|
||||||
|
pathName: 'library',
|
||||||
|
pathParamName: 'libraryId',
|
||||||
|
name: 'library',
|
||||||
|
);
|
||||||
|
static const libraryItem = _SimpleRoute(
|
||||||
|
pathName: 'item',
|
||||||
|
pathParamName: 'itemId',
|
||||||
|
name: 'libraryItem',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a class to store path
|
||||||
|
|
||||||
|
class _SimpleRoute {
|
||||||
|
const _SimpleRoute({
|
||||||
|
required this.pathName,
|
||||||
|
required this.pathParamName,
|
||||||
|
required this.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String pathName;
|
||||||
|
final String pathParamName;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
String get path => '/$pathName/:$pathParamName';
|
||||||
|
}
|
||||||
23
lib/router/models/library_item_extras.dart
Normal file
23
lib/router/models/library_item_extras.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// a freezed class to store the settings of the app
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||||
|
|
||||||
|
part 'library_item_extras.freezed.dart';
|
||||||
|
part 'library_item_extras.g.dart';
|
||||||
|
|
||||||
|
/// any extras when navigating to a library item
|
||||||
|
///
|
||||||
|
/// [shelfId] is the id of the shelf that the item was on before navigating to the item
|
||||||
|
/// [book] is the book that the item represents
|
||||||
|
/// [heroTagSuffix] is the suffix to use for the hero tag to avoid conflicts
|
||||||
|
@freezed
|
||||||
|
class LibraryItemExtras with _$LibraryItemExtras {
|
||||||
|
const factory LibraryItemExtras({
|
||||||
|
BookMinified? book,
|
||||||
|
String? heroTagSuffix,
|
||||||
|
}) = _LibraryItemExtras;
|
||||||
|
|
||||||
|
factory LibraryItemExtras.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$LibraryItemExtrasFromJson(json);
|
||||||
|
}
|
||||||
171
lib/router/models/library_item_extras.freezed.dart
Normal file
171
lib/router/models/library_item_extras.freezed.dart
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'library_item_extras.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
LibraryItemExtras _$LibraryItemExtrasFromJson(Map<String, dynamic> json) {
|
||||||
|
return _LibraryItemExtras.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$LibraryItemExtras {
|
||||||
|
BookMinified? get book => throw _privateConstructorUsedError;
|
||||||
|
String? get heroTagSuffix => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$LibraryItemExtrasCopyWith<LibraryItemExtras> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $LibraryItemExtrasCopyWith<$Res> {
|
||||||
|
factory $LibraryItemExtrasCopyWith(
|
||||||
|
LibraryItemExtras value, $Res Function(LibraryItemExtras) then) =
|
||||||
|
_$LibraryItemExtrasCopyWithImpl<$Res, LibraryItemExtras>;
|
||||||
|
@useResult
|
||||||
|
$Res call({BookMinified? book, String? heroTagSuffix});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$LibraryItemExtrasCopyWithImpl<$Res, $Val extends LibraryItemExtras>
|
||||||
|
implements $LibraryItemExtrasCopyWith<$Res> {
|
||||||
|
_$LibraryItemExtrasCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? book = freezed,
|
||||||
|
Object? heroTagSuffix = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
book: freezed == book
|
||||||
|
? _value.book
|
||||||
|
: book // ignore: cast_nullable_to_non_nullable
|
||||||
|
as BookMinified?,
|
||||||
|
heroTagSuffix: freezed == heroTagSuffix
|
||||||
|
? _value.heroTagSuffix
|
||||||
|
: heroTagSuffix // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$LibraryItemExtrasImplCopyWith<$Res>
|
||||||
|
implements $LibraryItemExtrasCopyWith<$Res> {
|
||||||
|
factory _$$LibraryItemExtrasImplCopyWith(_$LibraryItemExtrasImpl value,
|
||||||
|
$Res Function(_$LibraryItemExtrasImpl) then) =
|
||||||
|
__$$LibraryItemExtrasImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({BookMinified? book, String? heroTagSuffix});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$LibraryItemExtrasImplCopyWithImpl<$Res>
|
||||||
|
extends _$LibraryItemExtrasCopyWithImpl<$Res, _$LibraryItemExtrasImpl>
|
||||||
|
implements _$$LibraryItemExtrasImplCopyWith<$Res> {
|
||||||
|
__$$LibraryItemExtrasImplCopyWithImpl(_$LibraryItemExtrasImpl _value,
|
||||||
|
$Res Function(_$LibraryItemExtrasImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? book = freezed,
|
||||||
|
Object? heroTagSuffix = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$LibraryItemExtrasImpl(
|
||||||
|
book: freezed == book
|
||||||
|
? _value.book
|
||||||
|
: book // ignore: cast_nullable_to_non_nullable
|
||||||
|
as BookMinified?,
|
||||||
|
heroTagSuffix: freezed == heroTagSuffix
|
||||||
|
? _value.heroTagSuffix
|
||||||
|
: heroTagSuffix // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$LibraryItemExtrasImpl implements _LibraryItemExtras {
|
||||||
|
const _$LibraryItemExtrasImpl({this.book, this.heroTagSuffix});
|
||||||
|
|
||||||
|
factory _$LibraryItemExtrasImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$LibraryItemExtrasImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final BookMinified? book;
|
||||||
|
@override
|
||||||
|
final String? heroTagSuffix;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LibraryItemExtras(book: $book, heroTagSuffix: $heroTagSuffix)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$LibraryItemExtrasImpl &&
|
||||||
|
(identical(other.book, book) || other.book == book) &&
|
||||||
|
(identical(other.heroTagSuffix, heroTagSuffix) ||
|
||||||
|
other.heroTagSuffix == heroTagSuffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, book, heroTagSuffix);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$LibraryItemExtrasImplCopyWith<_$LibraryItemExtrasImpl> get copyWith =>
|
||||||
|
__$$LibraryItemExtrasImplCopyWithImpl<_$LibraryItemExtrasImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$LibraryItemExtrasImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _LibraryItemExtras implements LibraryItemExtras {
|
||||||
|
const factory _LibraryItemExtras(
|
||||||
|
{final BookMinified? book,
|
||||||
|
final String? heroTagSuffix}) = _$LibraryItemExtrasImpl;
|
||||||
|
|
||||||
|
factory _LibraryItemExtras.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$LibraryItemExtrasImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BookMinified? get book;
|
||||||
|
@override
|
||||||
|
String? get heroTagSuffix;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$LibraryItemExtrasImplCopyWith<_$LibraryItemExtrasImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
23
lib/router/models/library_item_extras.g.dart
Normal file
23
lib/router/models/library_item_extras.g.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'library_item_extras.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$LibraryItemExtrasImpl _$$LibraryItemExtrasImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$LibraryItemExtrasImpl(
|
||||||
|
book: json['book'] == null
|
||||||
|
? null
|
||||||
|
: BookMinified.fromJson(json['book'] as Map<String, dynamic>),
|
||||||
|
heroTagSuffix: json['heroTagSuffix'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$LibraryItemExtrasImplToJson(
|
||||||
|
_$LibraryItemExtrasImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'book': instance.book,
|
||||||
|
'heroTagSuffix': instance.heroTagSuffix,
|
||||||
|
};
|
||||||
52
lib/router/router.dart
Normal file
52
lib/router/router.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:whispering_pages/pages/home_page.dart';
|
||||||
|
import 'package:whispering_pages/pages/library_item_page.dart';
|
||||||
|
import 'package:whispering_pages/pages/library_page.dart';
|
||||||
|
import 'package:whispering_pages/pages/onboarding/onboarding.dart';
|
||||||
|
|
||||||
|
part 'constants.dart';
|
||||||
|
|
||||||
|
// GoRouter configuration
|
||||||
|
class MyAppRouter {
|
||||||
|
const MyAppRouter({required this.needOnboarding});
|
||||||
|
final bool needOnboarding;
|
||||||
|
// some static strings for named routes
|
||||||
|
|
||||||
|
GoRouter get config => GoRouter(
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/login',
|
||||||
|
name: Routes.onboarding,
|
||||||
|
builder: (context, state) => const OnboardingPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
name: Routes.home,
|
||||||
|
builder: (context, state) => const HomePage(),
|
||||||
|
),
|
||||||
|
// /library/:libraryId
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.library.path,
|
||||||
|
name: Routes.library.name,
|
||||||
|
builder: (context, state) => LibraryPage(
|
||||||
|
libraryId: state.pathParameters[Routes.library.pathParamName]!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.libraryItem.path,
|
||||||
|
name: Routes.libraryItem.name,
|
||||||
|
builder: (context, state) {
|
||||||
|
final itemId =
|
||||||
|
state.pathParameters[Routes.libraryItem.pathParamName]!;
|
||||||
|
return LibraryItemPage(itemId: itemId, extra: state.extra);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
redirect: (context, state) {
|
||||||
|
if (needOnboarding) {
|
||||||
|
return context.namedLocation(Routes.onboarding);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:whispering_pages/settings/models/api_settings.dart' as model;
|
|
||||||
import 'package:whispering_pages/db/available_boxes.dart';
|
import 'package:whispering_pages/db/available_boxes.dart';
|
||||||
|
import 'package:whispering_pages/settings/models/api_settings.dart' as model;
|
||||||
|
|
||||||
part 'api_settings_provider.g.dart';
|
part 'api_settings_provider.g.dart';
|
||||||
|
|
||||||
|
|
@ -41,7 +41,12 @@ class ApiSettings extends _$ApiSettings {
|
||||||
debugPrint('wrote api settings to box: $state');
|
debugPrint('wrote api settings to box: $state');
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateState(model.ApiSettings newSettings) {
|
void updateState(model.ApiSettings newSettings, {bool force = false}) {
|
||||||
|
// check if the settings are different
|
||||||
|
|
||||||
|
if (state == newSettings && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state = newSettings;
|
state = newSettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'api_settings_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$apiSettingsHash() => r'a6927751bd91ec7c9e1a2810dc939407d9112210';
|
String _$apiSettingsHash() => r'f08d87b716b31bfb4040fc6440840ac97b7ee686';
|
||||||
|
|
||||||
/// See also [ApiSettings].
|
/// See also [ApiSettings].
|
||||||
@ProviderFor(ApiSettings)
|
@ProviderFor(ApiSettings)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||||
import 'package:shimmer/shimmer.dart' show Shimmer, ShimmerDirection;
|
import 'package:shimmer/shimmer.dart' show Shimmer;
|
||||||
import 'package:whispering_pages/api/image_provider.dart';
|
import 'package:whispering_pages/api/image_provider.dart';
|
||||||
|
import 'package:whispering_pages/extensions/hero_tag_conventions.dart';
|
||||||
|
import 'package:whispering_pages/router/models/library_item_extras.dart';
|
||||||
|
import 'package:whispering_pages/router/router.dart';
|
||||||
import 'package:whispering_pages/widgets/shelves/home_shelf.dart';
|
import 'package:whispering_pages/widgets/shelves/home_shelf.dart';
|
||||||
|
|
||||||
/// A shelf that displays books on the home page
|
/// A shelf that displays books on the home page
|
||||||
|
|
@ -28,6 +32,7 @@ class BookHomeShelf extends HookConsumerWidget {
|
||||||
MediaType.book => BookOnShelf(
|
MediaType.book => BookOnShelf(
|
||||||
item: item,
|
item: item,
|
||||||
key: ValueKey(shelf.id + item.id),
|
key: ValueKey(shelf.id + item.id),
|
||||||
|
heroTagSuffix: shelf.id,
|
||||||
),
|
),
|
||||||
_ => Container(),
|
_ => Container(),
|
||||||
},
|
},
|
||||||
|
|
@ -42,10 +47,14 @@ class BookOnShelf extends HookConsumerWidget {
|
||||||
const BookOnShelf({
|
const BookOnShelf({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
|
this.heroTagSuffix = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final LibraryItem item;
|
final LibraryItem item;
|
||||||
|
|
||||||
|
/// makes the hero tag unique
|
||||||
|
final String heroTagSuffix;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final book = BookMinified.fromJson(item.media.toJson());
|
final book = BookMinified.fromJson(item.media.toJson());
|
||||||
|
|
@ -63,30 +72,49 @@ class BookOnShelf extends HookConsumerWidget {
|
||||||
// the cover image of the book
|
// the cover image of the book
|
||||||
// take up remaining space
|
// take up remaining space
|
||||||
Expanded(
|
Expanded(
|
||||||
// border radius
|
child: InkWell(
|
||||||
child: ClipRRect(
|
onTap: () {
|
||||||
borderRadius: BorderRadius.circular(10),
|
// open the book
|
||||||
child: coverImage.when(
|
context.pushNamed(
|
||||||
data: (image) {
|
Routes.libraryItem.name,
|
||||||
// return const BookCoverSkeleton();
|
pathParameters: {
|
||||||
if (image.isEmpty) {
|
Routes.libraryItem.pathParamName: item.id,
|
||||||
return const Icon(Icons.error);
|
},
|
||||||
}
|
extra: LibraryItemExtras(
|
||||||
// cover 80% of parent height
|
book: book,
|
||||||
return Image.memory(
|
heroTagSuffix: heroTagSuffix,
|
||||||
image,
|
),
|
||||||
fit: BoxFit.cover,
|
);
|
||||||
cacheWidth:
|
},
|
||||||
(height * MediaQuery.of(context).devicePixelRatio)
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: coverImage.when(
|
||||||
|
data: (image) {
|
||||||
|
// return const BookCoverSkeleton();
|
||||||
|
if (image.isEmpty) {
|
||||||
|
return const Icon(Icons.error);
|
||||||
|
}
|
||||||
|
// cover 80% of parent height
|
||||||
|
return Hero(
|
||||||
|
tag: HeroTagPrefixes.bookCover +
|
||||||
|
item.id +
|
||||||
|
heroTagSuffix,
|
||||||
|
child: Image.memory(
|
||||||
|
image,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
cacheWidth: (height *
|
||||||
|
MediaQuery.of(context).devicePixelRatio)
|
||||||
.round(),
|
.round(),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
loading: () {
|
},
|
||||||
return const Center(child: BookCoverSkeleton());
|
loading: () {
|
||||||
},
|
return const Center(child: BookCoverSkeleton());
|
||||||
error: (error, stack) {
|
},
|
||||||
return const Icon(Icons.error);
|
error: (error, stack) {
|
||||||
},
|
return const Icon(Icons.error);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue