Vaani/lib/shared/widgets/shelves/book_shelf.dart

191 lines
6.4 KiB
Dart
Raw Normal View History

2024-05-08 21:25:06 -04:00
import 'dart:math';
2024-05-08 05:03:49 -04:00
import 'package:flutter/material.dart';
2024-05-09 00:41:19 -04:00
import 'package:go_router/go_router.dart';
2024-05-08 05:03:49 -04:00
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelfsdk/audiobookshelf_api.dart';
2024-05-09 00:41:19 -04:00
import 'package:shimmer/shimmer.dart' show Shimmer;
2024-05-08 05:03:49 -04:00
import 'package:whispering_pages/api/image_provider.dart';
2024-05-14 10:11:25 -04:00
import 'package:whispering_pages/constants/hero_tag_conventions.dart';
2024-05-09 00:41:19 -04:00
import 'package:whispering_pages/router/models/library_item_extras.dart';
import 'package:whispering_pages/router/router.dart';
2024-05-14 10:11:25 -04:00
import 'package:whispering_pages/shared/widgets/shelves/home_shelf.dart';
2024-05-08 05:03:49 -04:00
/// A shelf that displays books on the home page
class BookHomeShelf extends HookConsumerWidget {
const BookHomeShelf({
super.key,
required this.shelf,
required this.title,
});
2024-05-08 21:25:06 -04:00
final String title;
2024-05-08 05:03:49 -04:00
final LibraryItemShelf shelf;
@override
Widget build(BuildContext context, WidgetRef ref) {
return SimpleHomeShelf(
title: title,
children: shelf.entities
.map(
(item) => switch (item.mediaType) {
MediaType.book => BookOnShelf(
item: item,
key: ValueKey(shelf.id + item.id),
2024-05-09 00:41:19 -04:00
heroTagSuffix: shelf.id,
2024-05-08 05:03:49 -04:00
),
_ => Container(),
},
)
.toList(),
);
}
}
// a widget to display a item on the shelf
class BookOnShelf extends HookConsumerWidget {
const BookOnShelf({
super.key,
required this.item,
2024-05-09 00:41:19 -04:00
this.heroTagSuffix = '',
2024-05-08 05:03:49 -04:00
});
final LibraryItem item;
2024-05-09 00:41:19 -04:00
/// makes the hero tag unique
final String heroTagSuffix;
2024-05-08 05:03:49 -04:00
@override
Widget build(BuildContext context, WidgetRef ref) {
final book = BookMinified.fromJson(item.media.toJson());
final metadata = BookMetadataMinified.fromJson(book.metadata.toJson());
final coverImage = ref.watch(coverImageProvider(item));
2024-05-08 21:25:06 -04:00
return LayoutBuilder(
builder: (context, constraints) {
final height = min(constraints.maxHeight, 500);
final width = height * 0.75;
return SizedBox(
width: width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// the cover image of the book
2024-05-12 05:38:30 -04:00
// take up remaining space hence the expanded
2024-05-08 21:25:06 -04:00
Expanded(
child: Center(
2024-05-12 05:38:30 -04:00
child: Hero(
tag: HeroTagPrefixes.bookCover + item.id + heroTagSuffix,
2024-05-14 06:13:16 -04:00
child: InkWell(
onTap: () {
// open the book
context.pushNamed(
Routes.libraryItem.name,
pathParameters: {
Routes.libraryItem.pathParamName!: item.id,
},
extra: LibraryItemExtras(
book: book,
heroTagSuffix: heroTagSuffix,
coverImage: coverImage.valueOrNull,
),
);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: coverImage.when(
data: (image) {
// return const BookCoverSkeleton();
if (image.isEmpty) {
return const Icon(Icons.error);
}
var imageWidget = Image.memory(
2024-05-12 05:38:30 -04:00
image,
fit: BoxFit.fill,
cacheWidth: (height *
1.2 *
MediaQuery.of(context).devicePixelRatio)
.round(),
2024-05-14 06:13:16 -04:00
);
return Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
child: imageWidget,
);
},
loading: () {
return const Center(child: BookCoverSkeleton());
},
error: (error, stack) {
return const Icon(Icons.error);
},
),
2024-05-12 05:38:30 -04:00
),
2024-05-09 00:41:19 -04:00
),
2024-05-08 21:25:06 -04:00
),
2024-05-08 05:03:49 -04:00
),
),
2024-05-08 21:25:06 -04:00
// the title and author of the book
// AutoScrollText(
2024-05-11 04:06:25 -04:00
Hero(
tag: HeroTagPrefixes.bookTitle + item.id + heroTagSuffix,
child: Text(
metadata.title ?? '',
// mode: AutoScrollTextMode.bouncing,
// curve: Curves.easeInOut,
// velocity: const Velocity(pixelsPerSecond: Offset(15, 0)),
// delayBefore: const Duration(seconds: 2),
// pauseBetween: const Duration(seconds: 2),
// numberOfReps: 15,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge,
),
2024-05-08 21:25:06 -04:00
),
const SizedBox(height: 3),
2024-05-11 04:06:25 -04:00
Hero(
tag: HeroTagPrefixes.authorName + item.id + heroTagSuffix,
child: Text(
metadata.authorName ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
2024-05-08 21:25:06 -04:00
),
],
2024-05-08 05:03:49 -04:00
),
2024-05-08 21:25:06 -04:00
);
},
);
}
}
// a skeleton for the book cover
class BookCoverSkeleton extends StatelessWidget {
const BookCoverSkeleton({
super.key,
});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: SizedBox(
width: 150,
child: Shimmer.fromColors(
baseColor: Theme.of(context).colorScheme.surface.withOpacity(0.3),
highlightColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.1),
child: Container(
color: Theme.of(context).colorScheme.surface,
2024-05-08 05:03:49 -04:00
),
2024-05-08 21:25:06 -04:00
),
2024-05-08 05:03:49 -04:00
),
);
}
}