responsive home_page

This commit is contained in:
Dr-Blank 2024-05-08 21:25:06 -04:00
parent a720c977c2
commit ebc14a0448
No known key found for this signature in database
GPG key ID: 7452CC63F210A266
9 changed files with 171 additions and 85 deletions

1
.vscode/launch.json vendored
View file

@ -4,6 +4,7 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "whispering_pages",
"request": "launch",

View file

@ -20,11 +20,17 @@ class CoverImage extends _$CoverImage {
Stream<Uint8List> build(LibraryItem libraryItem) async* {
final api = ref.watch(authenticatedApiProvider);
// ! artifical delay for testing
// await Future.delayed(const Duration(seconds: 2));
// try to get the image from the cache
final file = await imageCacheManager.getFileFromCache(libraryItem.id);
if (file != null) {
// if the image is in the cache, yield it
debugPrint(
'cover image found in cache for ${libraryItem.id} at ${file.file.path}',
);
yield await file.file.readAsBytes();
// return if no need to fetch from the server
if (libraryItem.updatedAt.isBefore(await file.file.lastModified())) {
@ -39,7 +45,7 @@ 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: 500),
parameters: const GetImageReqParams(width: 1000),
);
// save the image to the cache
final newFile = await imageCacheManager.putFile(

View file

@ -6,7 +6,7 @@ part of 'image_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$coverImageHash() => r'34c6aaf6831fea198984d22ecdf2c5b74e110891';
String _$coverImageHash() => r'57a164772b0350cd451535ed9d6347ff74671d2e';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -38,19 +38,16 @@ class HomePage extends HookConsumerWidget {
child: views.when(
data: (data) {
final shelvesToDisplay = data
.where((element) => !element.id.contains('discover'))
.map(
(shelf) => HomeShelf(
title: Text(shelf.label),
shelf: shelf,
),
)
.toList();
// .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 {
// await ref
// .read(personalizedViewProvider.notifier)
// .forceRefresh();
return ref.refresh(personalizedViewProvider);
},
child: ListView.separated(
@ -65,7 +62,7 @@ class HomePage extends HookConsumerWidget {
),
);
},
loading: () => const CircularProgressIndicator(),
loading: () => const HomePageSkeleton(),
error: (error, stack) {
return Text('Error: $error');
},
@ -74,3 +71,17 @@ class HomePage extends HookConsumerWidget {
);
}
}
class HomePageSkeleton extends StatelessWidget {
const HomePageSkeleton({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}

View file

@ -12,7 +12,7 @@ class AuthorHomeShelf extends HookConsumerWidget {
required this.title,
});
final Widget title;
final String title;
final AuthorShelf shelf;
@override

View file

@ -1,7 +1,9 @@
import 'package:auto_scroll_text/auto_scroll_text.dart';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelfsdk/audiobookshelf_api.dart';
import 'package:shimmer/shimmer.dart' show Shimmer, ShimmerDirection;
import 'package:whispering_pages/api/image_provider.dart';
import 'package:whispering_pages/widgets/shelves/home_shelf.dart';
@ -13,7 +15,7 @@ class BookHomeShelf extends HookConsumerWidget {
required this.title,
});
final Widget title;
final String title;
final LibraryItemShelf shelf;
@override
@ -49,69 +51,94 @@ class BookOnShelf extends HookConsumerWidget {
final book = BookMinified.fromJson(item.media.toJson());
final metadata = BookMetadataMinified.fromJson(book.metadata.toJson());
final coverImage = ref.watch(coverImageProvider(item));
const coverSize = 150.0;
return Container(
margin: const EdgeInsets.only(right: 10, bottom: 10),
constraints: const BoxConstraints(maxWidth: coverSize),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: AspectRatio(
aspectRatio: 1,
child: Container(
constraints: const BoxConstraints(maxWidth: coverSize),
color: Colors.grey[800],
child: coverImage.when(
data: (image) {
if (image.isEmpty) {
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
// take up remaining space
Expanded(
// border radius
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 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);
}
return Image.memory(
image,
fit: BoxFit.cover,
cacheWidth:
(coverSize * MediaQuery.of(context).devicePixelRatio)
.round(),
);
},
loading: () {
return const Center(child: CircularProgressIndicator());
},
error: (error, stack) {
return const Icon(Icons.error);
},
},
),
),
),
),
// the title and author of the book
// AutoScrollText(
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,
),
const SizedBox(height: 3),
Text(
metadata.authorName ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
Container(
margin: const EdgeInsets.all(5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoScrollText(
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,
),
const SizedBox(height: 3),
Text(
metadata.authorName ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
);
}
}
// 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,
),
],
),
),
);
}

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelfsdk/audiobookshelf_api.dart';
@ -14,7 +16,7 @@ class HomeShelf extends HookConsumerWidget {
required this.title,
});
final Widget title;
final String title;
final Shelf shelf;
@override
@ -33,30 +35,46 @@ class HomeShelf extends HookConsumerWidget {
}
}
/// A shelf that displays books on the home page
/// A shelf that displays children on the home page
class SimpleHomeShelf extends HookConsumerWidget {
const SimpleHomeShelf({
super.key,
required this.children,
required this.title,
this.height,
});
final Widget title;
/// the title of the shelf
final String title;
/// the children to display on the shelf
final List<Widget> children;
final double? height;
@override
Widget build(BuildContext context, WidgetRef ref) {
// if height is null take up 30% of the smallest screen dimension
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title,
Text(title, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 16),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: children,
SizedBox(
height: max(
min(
height ?? 0.3 * MediaQuery.of(context).size.shortestSide,
200.0,
),
150.0,
),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) => children[index],
separatorBuilder: (context, index) => const SizedBox(width: 16),
itemCount: children.length,
),
),
],

View file

@ -379,6 +379,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
freezed:
dependency: "direct dev"
description:
@ -411,6 +416,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: "9e0f7d1a3e7dc5010903e330fbc5497872c4c3cf6626381d69083cc1d5113c1e"
url: "https://pub.dev"
source: hosted
version: "14.0.2"
graphs:
dependency: transitive
description:
@ -786,6 +799,14 @@ packages:
relative: true
source: path
version: "1.0.0"
shimmer:
dependency: "direct main"
description:
name: shimmer
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter

View file

@ -44,10 +44,11 @@ dependencies:
flutter_hooks: ^0.20.5
flutter_settings_ui: ^3.0.1
freezed_annotation: ^2.4.1
go_router: ^14.0.2
hive: ^4.0.0-dev.2
hooks_riverpod: ^2.5.1
isar: *isar_version
isar_flutter_libs: *isar_version # contains Isar Core
isar: ^4.0.0-dev.13
isar_flutter_libs: ^4.0.0-dev.13
json_annotation: ^4.9.0
lottie: ^3.1.0
path: ^1.9.0
@ -56,6 +57,7 @@ dependencies:
scroll_loop_auto_scroll: ^0.0.5
shelfsdk:
path: ../../_dart/shelfsdk
shimmer: ^3.0.0
dev_dependencies:
build_runner: ^2.4.9
custom_lint: ^0.6.4