diff --git a/lib/api/api_provider.dart b/lib/api/api_provider.dart index 8139060..cfb9bb9 100644 --- a/lib/api/api_provider.dart +++ b/lib/api/api_provider.dart @@ -83,19 +83,20 @@ class PersonalizedView extends _$PersonalizedView { final apiSettings = ref.watch(apiSettingsProvider); final user = apiSettings.activeUser; if (apiSettings.activeLibraryId == null) { - // set it to default user library by logging in and getting the library id - final login = - await api.login(username: user!.username!, password: user.password!); + // set it to default user library by logging in and getting the library id + final login = + await api.login(username: user!.username!, password: user.password!); ref.read(apiSettingsProvider.notifier).updateState( apiSettings.copyWith(activeLibraryId: login!.userDefaultLibraryId), ); - } + } // try to find in cache // final cacheKey = 'personalizedView:${apiSettings.activeLibraryId}'; var key = 'personalizedView:${apiSettings.activeLibraryId! + user!.id!}'; - final cachedRes = await apiResponseCacheManager.getFileFromCache( - key, - ); + final cachedRes = await apiResponseCacheManager.getFileFromMemory( + key, + ) ?? + await apiResponseCacheManager.getFileFromCache(key); if (cachedRes != null) { final resJson = jsonDecode(await cachedRes.file.readAsString()) as List; final res = [ @@ -107,8 +108,8 @@ class PersonalizedView extends _$PersonalizedView { } // ! exagerated delay - await Future.delayed(const Duration(seconds: 2)); - final res = await api.libraries + // await Future.delayed(const Duration(seconds: 2)); + final res = await api.libraries .getPersonalized(libraryId: apiSettings.activeLibraryId!); // debugPrint('personalizedView: ${res!.map((e) => e).toSet()}'); // save to cache diff --git a/lib/api/api_provider.g.dart b/lib/api/api_provider.g.dart index cec1ace..64b6122 100644 --- a/lib/api/api_provider.g.dart +++ b/lib/api/api_provider.g.dart @@ -348,7 +348,7 @@ final fetchContinueListeningProvider = typedef FetchContinueListeningRef = AutoDisposeFutureProviderRef; -String _$personalizedViewHash() => r'52a89c46ce668238ca11b5394fd1d14c910947f5'; +String _$personalizedViewHash() => r'2e70fe2bfc766a963f7a8e94211ad50d959fbaa2'; /// fetch the personalized view /// diff --git a/lib/api/image_provider.dart b/lib/api/image_provider.dart index 07b02ba..e225d48 100644 --- a/lib/api/image_provider.dart +++ b/lib/api/image_provider.dart @@ -24,7 +24,8 @@ class CoverImage extends _$CoverImage { // await Future.delayed(const Duration(seconds: 2)); // try to get the image from the cache - final file = await imageCacheManager.getFileFromCache(libraryItem.id); + final file = await imageCacheManager.getFileFromMemory(libraryItem.id) ?? + await imageCacheManager.getFileFromCache(libraryItem.id); if (file != null) { // if the image is in the cache, yield it @@ -34,6 +35,9 @@ class CoverImage extends _$CoverImage { yield await file.file.readAsBytes(); // return if no need to fetch from the server if (libraryItem.updatedAt.isBefore(await file.file.lastModified())) { + debugPrint( + 'cover image is up to date for ${libraryItem.id}, no need to fetch from the server', + ); return; } else { debugPrint( @@ -45,17 +49,20 @@ class CoverImage extends _$CoverImage { // check if the image is in the cache final coverImage = await api.items.getCover( libraryItemId: libraryItem.id, - parameters: const GetImageReqParams(width: 1000), + parameters: const GetImageReqParams(width: 1200), ); // save the image to the cache - final newFile = await imageCacheManager.putFile( - libraryItem.id, - coverImage ?? Uint8List(0), - key: libraryItem.id, - ); - debugPrint( - 'cover image fetched for for ${libraryItem.id}, file time: ${await newFile.lastModified()}', - ); + if (coverImage != null) { + final newFile = await imageCacheManager.putFile( + libraryItem.id, + coverImage, + key: libraryItem.id, + ); + debugPrint( + 'cover image fetched for for ${libraryItem.id}, file time: ${await newFile.lastModified()}', + ); + } + yield coverImage ?? Uint8List(0); } } diff --git a/lib/api/image_provider.g.dart b/lib/api/image_provider.g.dart index ac06dbf..e1f1b49 100644 --- a/lib/api/image_provider.g.dart +++ b/lib/api/image_provider.g.dart @@ -6,7 +6,7 @@ part of 'image_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$coverImageHash() => r'57a164772b0350cd451535ed9d6347ff74671d2e'; +String _$coverImageHash() => r'010200735fbe7567ffdaad68bc5a98a475dfda42'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/api/library_item_provider.dart b/lib/api/library_item_provider.dart new file mode 100644 index 0000000..73c9505 --- /dev/null +++ b/lib/api/library_item_provider.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk; +import 'package:whispering_pages/api/api_provider.dart'; +import 'package:whispering_pages/db/cache/cache_key.dart'; +import 'package:whispering_pages/db/cache_manager.dart'; + +part 'library_item_provider.g.dart'; + +/// provides the library item for the given id +@riverpod +class LibraryItem extends _$LibraryItem { + @override + Stream build(String id) async* { + final api = ref.watch(authenticatedApiProvider); + + debugPrint('fetching library item: $id'); + + // ! this is a mock delay + // await Future.delayed(const Duration(seconds: 10)); + + // look for the item in the cache + final key = CacheKey.libraryItem(id); + final cachedFile = await apiResponseCacheManager.getFileFromMemory(key) ?? + await apiResponseCacheManager.getFileFromCache(key); + if (cachedFile != null) { + debugPrint('reading from cache for $id from ${cachedFile.file}'); + // read file as json + final cachedItem = shelfsdk.LibraryItem.fromJson( + jsonDecode(await cachedFile.file.readAsString()), + ); + yield cachedItem; + } + final item = await api.items.get(libraryItemId: id); + if (item != null) { + // save to cache + final newFile = await apiResponseCacheManager.putFile( + key, + utf8.encode(jsonEncode(item)), + fileExtension: 'json', + key: key, + ); + debugPrint('writing to cache: $newFile'); + yield item; + } + } +} diff --git a/lib/api/library_item_provider.g.dart b/lib/api/library_item_provider.g.dart new file mode 100644 index 0000000..5057c0e --- /dev/null +++ b/lib/api/library_item_provider.g.dart @@ -0,0 +1,187 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'library_item_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$libraryItemHash() => r'c7919065062e066a0d086508ca6c44187b0bc257'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$LibraryItem + extends BuildlessAutoDisposeStreamNotifier { + late final String id; + + Stream build( + String id, + ); +} + +/// provides the library item for the given id +/// +/// Copied from [LibraryItem]. +@ProviderFor(LibraryItem) +const libraryItemProvider = LibraryItemFamily(); + +/// provides the library item for the given id +/// +/// Copied from [LibraryItem]. +class LibraryItemFamily extends Family> { + /// provides the library item for the given id + /// + /// Copied from [LibraryItem]. + const LibraryItemFamily(); + + /// provides the library item for the given id + /// + /// Copied from [LibraryItem]. + LibraryItemProvider call( + String id, + ) { + return LibraryItemProvider( + id, + ); + } + + @override + LibraryItemProvider getProviderOverride( + covariant LibraryItemProvider provider, + ) { + return call( + provider.id, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'libraryItemProvider'; +} + +/// provides the library item for the given id +/// +/// Copied from [LibraryItem]. +class LibraryItemProvider extends AutoDisposeStreamNotifierProviderImpl< + LibraryItem, shelfsdk.LibraryItem> { + /// provides the library item for the given id + /// + /// Copied from [LibraryItem]. + LibraryItemProvider( + String id, + ) : this._internal( + () => LibraryItem()..id = id, + from: libraryItemProvider, + name: r'libraryItemProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$libraryItemHash, + dependencies: LibraryItemFamily._dependencies, + allTransitiveDependencies: + LibraryItemFamily._allTransitiveDependencies, + id: id, + ); + + LibraryItemProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.id, + }) : super.internal(); + + final String id; + + @override + Stream runNotifierBuild( + covariant LibraryItem notifier, + ) { + return notifier.build( + id, + ); + } + + @override + Override overrideWith(LibraryItem Function() create) { + return ProviderOverride( + origin: this, + override: LibraryItemProvider._internal( + () => create()..id = id, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + id: id, + ), + ); + } + + @override + AutoDisposeStreamNotifierProviderElement + createElement() { + return _LibraryItemProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is LibraryItemProvider && other.id == id; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, id.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin LibraryItemRef + on AutoDisposeStreamNotifierProviderRef { + /// The parameter `id` of this provider. + String get id; +} + +class _LibraryItemProviderElement + extends AutoDisposeStreamNotifierProviderElement with LibraryItemRef { + _LibraryItemProviderElement(super.provider); + + @override + String get id => (origin as LibraryItemProvider).id; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/db/cache/cache_key.dart b/lib/db/cache/cache_key.dart new file mode 100644 index 0000000..a152de5 --- /dev/null +++ b/lib/db/cache/cache_key.dart @@ -0,0 +1,5 @@ +class CacheKey { + static libraryItem(String id) { + return 'library_item_$id'; + } +} diff --git a/lib/main.dart b/lib/main.dart index 4f54531..bf2c432 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,25 +12,34 @@ void main() async { // initialize the storage await initStorage(); - runApp(const ProviderScope( - child: MyApp(), + + runApp( + const ProviderScope( + child: MyApp(), ), ); } +// final _router = MyAppRouter(needOnboarding: _needAuth()); + +// bool _needAuth() { +// final apiSettings = ApiSettings().readFromBoxOrCreate(); +// final servers = AudiobookShelfServer().readFromBoxOrCreate(); +// return apiSettings.activeUser == null || servers.isEmpty; +// } + class MyApp extends ConsumerWidget { const MyApp({super.key}); - @override Widget build(BuildContext context, WidgetRef ref) { final servers = ref.watch(audiobookShelfServerProvider); - final appSettings = ref.watch(appSettingsProvider); final apiSettings = ref.watch(apiSettingsProvider); bool needOnboarding() { return apiSettings.activeUser == null || servers.isEmpty; } + return MaterialApp.router( theme: lightTheme, darkTheme: darkTheme, @@ -38,6 +47,8 @@ class MyApp extends ConsumerWidget { ? ThemeMode.dark : ThemeMode.light, routerConfig: MyAppRouter(needOnboarding: needOnboarding()).config, + // routerConfig: _router.config, + ); } } diff --git a/lib/pages/library_item_page.dart b/lib/pages/library_item_page.dart index f8de013..d90f025 100644 --- a/lib/pages/library_item_page.dart +++ b/lib/pages/library_item_page.dart @@ -1,8 +1,12 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:whispering_pages/api/api_provider.dart'; +import 'package:whispering_pages/api/image_provider.dart'; +import 'package:whispering_pages/api/library_item_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/widgets/shelves/book_shelf.dart'; class LibraryItemPage extends HookConsumerWidget { const LibraryItemPage({ @@ -15,24 +19,119 @@ class LibraryItemPage extends HookConsumerWidget { final Object? extra; @override Widget build(BuildContext context, WidgetRef ref) { - final views = ref.watch(personalizedViewProvider); final extraMap = extra is LibraryItemExtras ? extra as LibraryItemExtras : null; + final item = ref.watch(libraryItemProvider(itemId)); + final providedCacheImage = extraMap?.coverImage != null + ? Image.memory(extraMap!.coverImage!) + : null; return Scaffold( appBar: AppBar(), body: Center( - child: Hero( - tag: HeroTagPrefixes.bookCover + - itemId + - (extraMap?.heroTagSuffix ?? ''), - child: Container( - color: Colors.amber, - height: 200, - width: 200, - ), + child: Column( + children: [ + // cover image + Hero( + tag: HeroTagPrefixes.bookCover + + itemId + + (extraMap?.heroTagSuffix ?? ''), + child: LayoutBuilder( + builder: (context, constraints) { + final width = + calculateWidth(context, constraints, heightRatio: 0.35); + return SizedBox( + height: width, + width: width, + child: providedCacheImage ?? + item.when( + data: (libraryItem) { + final coverImage = + ref.watch(coverImageProvider(libraryItem)); + return Stack( + children: [ + 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 Image.memory( + image, + fit: BoxFit.cover, + // cacheWidth: (height * + // MediaQuery.of(context).devicePixelRatio) + // .round(), + ); + }, + loading: () { + return const Center( + child: BookCoverSkeleton(), + ); + }, + error: (error, stack) { + return const Icon(Icons.error); + }, + ), + ), + ], + ); + }, + error: (error, stack) => + const CircularProgressIndicator(), + loading: () => + const Center(child: BookCoverSkeleton()), + ), + ); + }, + ), + ), + + // author + + // title + + + // description + const Text('Description'), + ], ), ), ); } } + +// child: Hero( +// tag: HeroTagPrefixes.bookCover + +// itemId + +// (extraMap?.heroTagSuffix ?? ''), +// child: Container( +// color: Colors.amber, +// height: 200, +// width: 200, +// ), +// ), + +double calculateWidth( + BuildContext context, + BoxConstraints constraints, { + double heightRatio = 0.25, + double widthRatio = 0.9, +}) { + final availHeight = + min(constraints.maxHeight, MediaQuery.of(context).size.height); + final availWidth = + min(constraints.maxWidth, MediaQuery.of(context).size.width); + + // make the width 90% of the available width + var width = availWidth * widthRatio; + // but never exceed more than 25% of height + if (width > availHeight * heightRatio) { + width = availHeight * heightRatio; + } + + return width; +} diff --git a/lib/router/models/library_item_extras.dart b/lib/router/models/library_item_extras.dart index d77fa60..089d649 100644 --- a/lib/router/models/library_item_extras.dart +++ b/lib/router/models/library_item_extras.dart @@ -1,10 +1,11 @@ // a freezed class to store the settings of the app +import 'dart:typed_data'; + 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 /// @@ -15,9 +16,8 @@ part 'library_item_extras.g.dart'; class LibraryItemExtras with _$LibraryItemExtras { const factory LibraryItemExtras({ BookMinified? book, - String? heroTagSuffix, + @Default('') String heroTagSuffix, + Uint8List? coverImage, }) = _LibraryItemExtras; - factory LibraryItemExtras.fromJson(Map json) => - _$LibraryItemExtrasFromJson(json); } diff --git a/lib/router/models/library_item_extras.freezed.dart b/lib/router/models/library_item_extras.freezed.dart index e1ee3b8..b27f20b 100644 --- a/lib/router/models/library_item_extras.freezed.dart +++ b/lib/router/models/library_item_extras.freezed.dart @@ -14,16 +14,12 @@ T _$identity(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 json) { - return _LibraryItemExtras.fromJson(json); -} - /// @nodoc mixin _$LibraryItemExtras { BookMinified? get book => throw _privateConstructorUsedError; - String? get heroTagSuffix => throw _privateConstructorUsedError; + String get heroTagSuffix => throw _privateConstructorUsedError; + Uint8List? get coverImage => throw _privateConstructorUsedError; - Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) $LibraryItemExtrasCopyWith get copyWith => throw _privateConstructorUsedError; @@ -35,7 +31,7 @@ abstract class $LibraryItemExtrasCopyWith<$Res> { LibraryItemExtras value, $Res Function(LibraryItemExtras) then) = _$LibraryItemExtrasCopyWithImpl<$Res, LibraryItemExtras>; @useResult - $Res call({BookMinified? book, String? heroTagSuffix}); + $Res call({BookMinified? book, String heroTagSuffix, Uint8List? coverImage}); } /// @nodoc @@ -52,17 +48,22 @@ class _$LibraryItemExtrasCopyWithImpl<$Res, $Val extends LibraryItemExtras> @override $Res call({ Object? book = freezed, - Object? heroTagSuffix = freezed, + Object? heroTagSuffix = null, + Object? coverImage = freezed, }) { return _then(_value.copyWith( book: freezed == book ? _value.book : book // ignore: cast_nullable_to_non_nullable as BookMinified?, - heroTagSuffix: freezed == heroTagSuffix + heroTagSuffix: null == heroTagSuffix ? _value.heroTagSuffix : heroTagSuffix // ignore: cast_nullable_to_non_nullable - as String?, + as String, + coverImage: freezed == coverImage + ? _value.coverImage + : coverImage // ignore: cast_nullable_to_non_nullable + as Uint8List?, ) as $Val); } } @@ -75,7 +76,7 @@ abstract class _$$LibraryItemExtrasImplCopyWith<$Res> __$$LibraryItemExtrasImplCopyWithImpl<$Res>; @override @useResult - $Res call({BookMinified? book, String? heroTagSuffix}); + $Res call({BookMinified? book, String heroTagSuffix, Uint8List? coverImage}); } /// @nodoc @@ -90,37 +91,43 @@ class __$$LibraryItemExtrasImplCopyWithImpl<$Res> @override $Res call({ Object? book = freezed, - Object? heroTagSuffix = freezed, + Object? heroTagSuffix = null, + Object? coverImage = freezed, }) { return _then(_$LibraryItemExtrasImpl( book: freezed == book ? _value.book : book // ignore: cast_nullable_to_non_nullable as BookMinified?, - heroTagSuffix: freezed == heroTagSuffix + heroTagSuffix: null == heroTagSuffix ? _value.heroTagSuffix : heroTagSuffix // ignore: cast_nullable_to_non_nullable - as String?, + as String, + coverImage: freezed == coverImage + ? _value.coverImage + : coverImage // ignore: cast_nullable_to_non_nullable + as Uint8List?, )); } } /// @nodoc -@JsonSerializable() -class _$LibraryItemExtrasImpl implements _LibraryItemExtras { - const _$LibraryItemExtrasImpl({this.book, this.heroTagSuffix}); - factory _$LibraryItemExtrasImpl.fromJson(Map json) => - _$$LibraryItemExtrasImplFromJson(json); +class _$LibraryItemExtrasImpl implements _LibraryItemExtras { + const _$LibraryItemExtrasImpl( + {this.book, this.heroTagSuffix = '', this.coverImage}); @override final BookMinified? book; @override - final String? heroTagSuffix; + @JsonKey() + final String heroTagSuffix; + @override + final Uint8List? coverImage; @override String toString() { - return 'LibraryItemExtras(book: $book, heroTagSuffix: $heroTagSuffix)'; + return 'LibraryItemExtras(book: $book, heroTagSuffix: $heroTagSuffix, coverImage: $coverImage)'; } @override @@ -130,12 +137,14 @@ class _$LibraryItemExtrasImpl implements _LibraryItemExtras { other is _$LibraryItemExtrasImpl && (identical(other.book, book) || other.book == book) && (identical(other.heroTagSuffix, heroTagSuffix) || - other.heroTagSuffix == heroTagSuffix)); + other.heroTagSuffix == heroTagSuffix) && + const DeepCollectionEquality() + .equals(other.coverImage, coverImage)); } - @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, book, heroTagSuffix); + int get hashCode => Object.hash(runtimeType, book, heroTagSuffix, + const DeepCollectionEquality().hash(coverImage)); @JsonKey(ignore: true) @override @@ -143,27 +152,20 @@ class _$LibraryItemExtrasImpl implements _LibraryItemExtras { _$$LibraryItemExtrasImplCopyWith<_$LibraryItemExtrasImpl> get copyWith => __$$LibraryItemExtrasImplCopyWithImpl<_$LibraryItemExtrasImpl>( this, _$identity); - - @override - Map toJson() { - return _$$LibraryItemExtrasImplToJson( - this, - ); - } } abstract class _LibraryItemExtras implements LibraryItemExtras { const factory _LibraryItemExtras( {final BookMinified? book, - final String? heroTagSuffix}) = _$LibraryItemExtrasImpl; - - factory _LibraryItemExtras.fromJson(Map json) = - _$LibraryItemExtrasImpl.fromJson; + final String heroTagSuffix, + final Uint8List? coverImage}) = _$LibraryItemExtrasImpl; @override BookMinified? get book; @override - String? get heroTagSuffix; + String get heroTagSuffix; + @override + Uint8List? get coverImage; @override @JsonKey(ignore: true) _$$LibraryItemExtrasImplCopyWith<_$LibraryItemExtrasImpl> get copyWith => diff --git a/lib/router/models/library_item_extras.g.dart b/lib/router/models/library_item_extras.g.dart deleted file mode 100644 index aa9b0d3..0000000 --- a/lib/router/models/library_item_extras.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'library_item_extras.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$LibraryItemExtrasImpl _$$LibraryItemExtrasImplFromJson( - Map json) => - _$LibraryItemExtrasImpl( - book: json['book'] == null - ? null - : BookMinified.fromJson(json['book'] as Map), - heroTagSuffix: json['heroTagSuffix'] as String?, - ); - -Map _$$LibraryItemExtrasImplToJson( - _$LibraryItemExtrasImpl instance) => - { - 'book': instance.book, - 'heroTagSuffix': instance.heroTagSuffix, - }; diff --git a/lib/widgets/shelves/book_shelf.dart b/lib/widgets/shelves/book_shelf.dart index 5e3e385..ed26814 100644 --- a/lib/widgets/shelves/book_shelf.dart +++ b/lib/widgets/shelves/book_shelf.dart @@ -83,6 +83,7 @@ class BookOnShelf extends HookConsumerWidget { extra: LibraryItemExtras( book: book, heroTagSuffix: heroTagSuffix, + coverImage: coverImage.valueOrNull, ), ); }, @@ -103,6 +104,7 @@ class BookOnShelf extends HookConsumerWidget { image, fit: BoxFit.cover, cacheWidth: (height * + 1.2 * MediaQuery.of(context).devicePixelRatio) .round(), ),