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(
|
2024-05-10 17:49:47 -04:00
|
|
|
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
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|