mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-10 21:19:29 +00:00
responsive home_page
This commit is contained in:
parent
a720c977c2
commit
ebc14a0448
9 changed files with 171 additions and 85 deletions
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
21
pubspec.lock
21
pubspec.lock
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue