mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 15:02:42 +01:00
albums grid, pagination
This commit is contained in:
61
lib/hooks/use_paging_controller.dart
Normal file
61
lib/hooks/use_paging_controller.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
PagingController<PageKeyType, ItemType>
|
||||
usePagingController<PageKeyType, ItemType>({
|
||||
required PageKeyType? Function(PagingState<PageKeyType, ItemType>)
|
||||
getNextPageKey,
|
||||
required FutureOr<List<ItemType>> Function(PageKeyType) fetchPage,
|
||||
}) {
|
||||
return use(
|
||||
_PagingControllerHook<PageKeyType, ItemType>(
|
||||
getNextPageKey: getNextPageKey,
|
||||
fetchPage: fetchPage,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _PagingControllerHook<PageKeyType, ItemType>
|
||||
extends Hook<PagingController<PageKeyType, ItemType>> {
|
||||
const _PagingControllerHook({
|
||||
super.keys,
|
||||
required this.getNextPageKey,
|
||||
required this.fetchPage,
|
||||
});
|
||||
|
||||
final PageKeyType? Function(PagingState<PageKeyType, ItemType>)
|
||||
getNextPageKey;
|
||||
final FutureOr<List<ItemType>> Function(PageKeyType) fetchPage;
|
||||
|
||||
@override
|
||||
HookState<
|
||||
PagingController<PageKeyType, ItemType>,
|
||||
Hook<PagingController<PageKeyType, ItemType>>
|
||||
>
|
||||
createState() => _PagingControllerHookState<PageKeyType, ItemType>();
|
||||
}
|
||||
|
||||
class _PagingControllerHookState<PageKeyType, ItemType>
|
||||
extends
|
||||
HookState<
|
||||
PagingController<PageKeyType, ItemType>,
|
||||
_PagingControllerHook<PageKeyType, ItemType>
|
||||
> {
|
||||
late final controller = PagingController<PageKeyType, ItemType>(
|
||||
getNextPageKey: hook.getNextPageKey,
|
||||
fetchPage: hook.fetchPage,
|
||||
);
|
||||
|
||||
@override
|
||||
PagingController<PageKeyType, ItemType> build(BuildContext context) =>
|
||||
controller;
|
||||
|
||||
@override
|
||||
void dispose() => controller.dispose();
|
||||
|
||||
@override
|
||||
String get debugLabel => 'usePagingController';
|
||||
}
|
||||
40
lib/lists/albums_grid.dart
Normal file
40
lib/lists/albums_grid.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
import '../hooks/use_paging_controller.dart';
|
||||
import 'list_items.dart';
|
||||
|
||||
class AlbumsGrid extends HookWidget {
|
||||
const AlbumsGrid({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = usePagingController<int, String>(
|
||||
getNextPageKey: (state) =>
|
||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
||||
fetchPage: (pageKey) => List.generate(30, (_) => pageKey.toString()),
|
||||
);
|
||||
|
||||
return PagingListener(
|
||||
controller: controller,
|
||||
builder: (context, state, fetchNextPage) {
|
||||
return PagedSliverGrid(
|
||||
state: state,
|
||||
fetchNextPage: fetchNextPage,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
),
|
||||
builderDelegate: PagedChildBuilderDelegate<String>(
|
||||
itemBuilder: (context, item, index) => AlbumGridTile(
|
||||
onTap: () {
|
||||
context.push('/album');
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
85
lib/lists/list_items.dart
Normal file
85
lib/lists/list_items.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../util/clip.dart';
|
||||
|
||||
class AlbumGridTile extends StatelessWidget {
|
||||
const AlbumGridTile({
|
||||
super.key,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final void Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CardTheme(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadiusGeometry.circular(3),
|
||||
),
|
||||
margin: EdgeInsets.all(2),
|
||||
child: ImageCard(
|
||||
onTap: onTap,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: 'https://placehold.net/400x400.png',
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ArtistListTile extends StatelessWidget {
|
||||
const ArtistListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: CircleClip(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: 'https://placehold.net/400x400.png',
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||
),
|
||||
),
|
||||
title: Text('Some Artist'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ImageCard extends StatelessWidget {
|
||||
const ImageCard({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final void Function()? onTap;
|
||||
final void Function()? onLongPress;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
child,
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -18,6 +19,11 @@ class AlbumScreen extends StatelessWidget {
|
||||
},
|
||||
child: Text('Artist...'),
|
||||
),
|
||||
CachedNetworkImage(
|
||||
imageUrl: 'https://placehold.net/400x400.png',
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../lists/albums_grid.dart';
|
||||
import '../util/custom_scroll_fix.dart';
|
||||
|
||||
class LibraryScreen extends StatefulWidget {
|
||||
@@ -16,7 +17,7 @@ class _LibraryScreenState extends State<LibraryScreen>
|
||||
late final TabController tabController;
|
||||
|
||||
final iconSize = 26.0;
|
||||
final tabHeight = 32.0;
|
||||
final tabHeight = 36.0;
|
||||
|
||||
late final List<(String, Widget)> tabs = [
|
||||
('Home', Icon(Symbols.home_rounded, size: iconSize)),
|
||||
@@ -202,20 +203,7 @@ class _NewWidgetState extends State<NewWidget>
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
sliver: SliverFixedExtentList(
|
||||
itemExtent: 48.0,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text('Item $index'),
|
||||
onTap: () {
|
||||
context.push('/album');
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: 30,
|
||||
),
|
||||
),
|
||||
sliver: AlbumsGrid(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
21
lib/util/clip.dart
Normal file
21
lib/util/clip.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CircleClip extends StatelessWidget {
|
||||
const CircleClip({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipOval(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user