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 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "whispering_pages", "name": "whispering_pages",
"request": "launch", "request": "launch",

View file

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

View file

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

View file

@ -38,19 +38,16 @@ class HomePage extends HookConsumerWidget {
child: views.when( child: views.when(
data: (data) { data: (data) {
final shelvesToDisplay = data final shelvesToDisplay = data
.where((element) => !element.id.contains('discover')) // .where((element) => !element.id.contains('discover'))
.map( .map((shelf) {
(shelf) => HomeShelf( debugPrint('building shelf ${shelf.label}');
title: Text(shelf.label), return HomeShelf(
shelf: shelf, title: shelf.label,
), shelf: shelf,
) );
.toList(); }).toList();
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
// await ref
// .read(personalizedViewProvider.notifier)
// .forceRefresh();
return ref.refresh(personalizedViewProvider); return ref.refresh(personalizedViewProvider);
}, },
child: ListView.separated( child: ListView.separated(
@ -65,7 +62,7 @@ class HomePage extends HookConsumerWidget {
), ),
); );
}, },
loading: () => const CircularProgressIndicator(), loading: () => const HomePageSkeleton(),
error: (error, stack) { error: (error, stack) {
return Text('Error: $error'); 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, required this.title,
}); });
final Widget title; final String title;
final AuthorShelf shelf; final AuthorShelf shelf;
@override @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:flutter/material.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:whispering_pages/api/image_provider.dart'; import 'package:whispering_pages/api/image_provider.dart';
import 'package:whispering_pages/widgets/shelves/home_shelf.dart'; import 'package:whispering_pages/widgets/shelves/home_shelf.dart';
@ -13,7 +15,7 @@ class BookHomeShelf extends HookConsumerWidget {
required this.title, required this.title,
}); });
final Widget title; final String title;
final LibraryItemShelf shelf; final LibraryItemShelf shelf;
@override @override
@ -49,69 +51,94 @@ class BookOnShelf extends HookConsumerWidget {
final book = BookMinified.fromJson(item.media.toJson()); final book = BookMinified.fromJson(item.media.toJson());
final metadata = BookMetadataMinified.fromJson(book.metadata.toJson()); final metadata = BookMetadataMinified.fromJson(book.metadata.toJson());
final coverImage = ref.watch(coverImageProvider(item)); final coverImage = ref.watch(coverImageProvider(item));
const coverSize = 150.0; return LayoutBuilder(
return Container( builder: (context, constraints) {
margin: const EdgeInsets.only(right: 10, bottom: 10), final height = min(constraints.maxHeight, 500);
constraints: const BoxConstraints(maxWidth: coverSize), final width = height * 0.75;
child: Column( return SizedBox(
crossAxisAlignment: CrossAxisAlignment.start, width: width,
children: [ child: Column(
ClipRRect( crossAxisAlignment: CrossAxisAlignment.start,
borderRadius: BorderRadius.circular(10), children: [
child: AspectRatio( // the cover image of the book
aspectRatio: 1, // take up remaining space
child: Container( Expanded(
constraints: const BoxConstraints(maxWidth: coverSize), // border radius
color: Colors.grey[800], child: ClipRRect(
child: coverImage.when( borderRadius: BorderRadius.circular(10),
data: (image) { child: coverImage.when(
if (image.isEmpty) { 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 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 ?? '', // a skeleton for the book cover
mode: AutoScrollTextMode.bouncing, class BookCoverSkeleton extends StatelessWidget {
curve: Curves.easeInOut, const BookCoverSkeleton({
velocity: const Velocity(pixelsPerSecond: Offset(15, 0)), super.key,
delayBefore: const Duration(seconds: 2), });
pauseBetween: const Duration(seconds: 2),
numberOfReps: 15, @override
// maxLines: 1, Widget build(BuildContext context) {
// overflow: TextOverflow.ellipsis, return AspectRatio(
), aspectRatio: 1,
const SizedBox(height: 3), child: SizedBox(
Text( width: 150,
metadata.authorName ?? '', child: Shimmer.fromColors(
maxLines: 1, baseColor: Theme.of(context).colorScheme.surface.withOpacity(0.3),
overflow: TextOverflow.ellipsis, 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:flutter/material.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';
@ -14,7 +16,7 @@ class HomeShelf extends HookConsumerWidget {
required this.title, required this.title,
}); });
final Widget title; final String title;
final Shelf shelf; final Shelf shelf;
@override @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 { class SimpleHomeShelf extends HookConsumerWidget {
const SimpleHomeShelf({ const SimpleHomeShelf({
super.key, super.key,
required this.children, required this.children,
required this.title, 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 List<Widget> children;
final double? height;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// if height is null take up 30% of the smallest screen dimension
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
title, Text(title, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 16), const SizedBox(height: 16),
SingleChildScrollView( SizedBox(
scrollDirection: Axis.horizontal, height: max(
child: Row( min(
children: children, 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 description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
freezed: freezed:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -411,6 +416,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" 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: graphs:
dependency: transitive dependency: transitive
description: description:
@ -786,6 +799,14 @@ packages:
relative: true relative: true
source: path source: path
version: "1.0.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter

View file

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