Clean up inifinite lists

This commit is contained in:
shilangyu 2021-02-09 20:39:31 +01:00
parent 2b95ea5f0f
commit 737ded307e
5 changed files with 83 additions and 69 deletions

View File

@ -30,7 +30,7 @@ class CommunitiesListPage extends StatelessWidget {
),
body: SortableInfiniteList<CommunityView>(
fetcher: fetcher,
builder: (community) => Column(
itemBuilder: (community) => Column(
children: [
const Divider(),
CommunitiesListItem(

View File

@ -12,8 +12,7 @@ import '../hooks/stores.dart';
import '../util/goto.dart';
import '../widgets/bottom_modal.dart';
import '../widgets/infinite_scroll.dart';
import '../widgets/post.dart';
import '../widgets/post_list_options.dart';
import '../widgets/sortable_infinite_list.dart';
import 'add_account.dart';
import 'inbox.dart';
@ -262,13 +261,6 @@ class InfiniteHomeList extends HookWidget {
Widget build(BuildContext context) {
final accStore = useAccountsStore();
final sort = useState(SortType.active);
void changeSorting(SortType newSort) {
sort.value = newSort;
controller.clear();
}
/// fetches post from many instances at once and combines them into a single
/// list
///
@ -313,9 +305,9 @@ class InfiniteHomeList extends HookWidget {
return newPosts;
}
Future<List<PostView>> Function(int, int) fetcherFromInstance(
String instanceHost, PostListingType listingType, SortType sort) =>
(page, batchSize) => LemmyApiV2(instanceHost).run(GetPosts(
FetcherWithSorting<PostView> fetcherFromInstance(
String instanceHost, PostListingType listingType) =>
(page, batchSize, sort) => LemmyApiV2(instanceHost).run(GetPosts(
type: listingType,
sort: sort,
page: page,
@ -323,33 +315,13 @@ class InfiniteHomeList extends HookWidget {
auth: accStore.defaultTokenFor(instanceHost)?.raw,
));
return InfiniteScroll<PostView>(
prepend: Column(
children: [
PostListOptions(
sortValue: sort.value,
onSortChanged: changeSorting,
styleButton: onStyleChange != null,
),
],
),
builder: (post) => Column(
children: [
PostWidget(post),
const SizedBox(height: 20),
],
),
padding: EdgeInsets.zero,
fetchMore: selectedList.instanceHost == null
? (page, limit) =>
generalFetcher(page, limit, sort.value, selectedList.listingType)
return InfinitePostList(
fetcher: selectedList.instanceHost == null
? (page, limit, sort) =>
generalFetcher(page, limit, sort, selectedList.listingType)
: fetcherFromInstance(
selectedList.instanceHost,
selectedList.listingType,
sort.value,
),
selectedList.instanceHost, selectedList.listingType),
controller: controller,
batchSize: 20,
);
}
}

View File

@ -103,7 +103,7 @@ class _SearchResultsList extends HookWidget {
throw UnimplementedError();
}
},
builder: (data) {
itemBuilder: (data) {
switch (type) {
case SearchType.comments:
return CommentWidget(

View File

@ -20,27 +20,45 @@ class InfiniteScrollController {
}
}
/// `ListView.builder` with asynchronous data fetching
/// `ListView.builder` with asynchronous data fetching and no `itemCount`
class InfiniteScroll<T> extends HookWidget {
/// How many items should be fetched per call
final int batchSize;
/// Widget displayed at the bottom when InfiniteScroll is fetching
final Widget loadingWidget;
final Widget Function(T data) builder;
final Future<List<T>> Function(int page, int batchSize) fetchMore;
/// Builds your widget from the fetched data
final Widget Function(T data) itemBuilder;
/// Fetches data to be displayed. It is important to respect `batchSize`,
/// if the returned list has less than `batchSize` then the InfiniteScroll
/// is considered finished
final Future<List<T>> Function(int page, int batchSize) fetcher;
final InfiniteScrollController controller;
final Widget prepend;
/// Widget to be added at the beginning of the list
final Widget leading;
/// Padding for the [ListView.builder]
final EdgeInsetsGeometry padding;
/// Widget that will be displayed if there are no items
final Widget noItems;
const InfiniteScroll({
this.batchSize = 10,
this.prepend = const SizedBox.shrink(),
this.leading = const SizedBox.shrink(),
this.padding,
this.loadingWidget =
const ListTile(title: Center(child: CircularProgressIndicator())),
@required this.builder,
@required this.fetchMore,
@required this.itemBuilder,
@required this.fetcher,
this.controller,
}) : assert(builder != null),
assert(fetchMore != null),
this.noItems = const SizedBox.shrink(),
}) : assert(itemBuilder != null),
assert(fetcher != null),
assert(batchSize > 0);
@override
@ -71,14 +89,19 @@ class InfiniteScroll<T> extends HookWidget {
},
child: ListView.builder(
padding: padding,
// +2 for the loading widget and prepend widget
// +2 for the loading widget and leading widget
itemCount: data.value.length + 2,
itemBuilder: (_, i) {
if (i == 0) {
return prepend;
return leading;
}
i -= 1;
// if we are done but we have no data it means the list is empty
if (!hasMore.current && data.value.isEmpty) {
return Center(child: noItems);
}
// reached the bottom, fetch more
if (i == data.value.length) {
// if there are no more, skip
@ -89,7 +112,7 @@ class InfiniteScroll<T> extends HookWidget {
// if it's already fetching more, skip
if (!isFetching.current) {
isFetching.current = true;
fetchMore(page, batchSize).then((newData) {
fetcher(page, batchSize).then((newData) {
// if got less than the batchSize, mark the list as done
if (newData.length < batchSize) {
hasMore.current = false;
@ -106,7 +129,7 @@ class InfiniteScroll<T> extends HookWidget {
}
// not last element, render list item
return builder(data.value[i]);
return itemBuilder(data.value[i]);
},
),
);

View File

@ -9,23 +9,31 @@ import 'infinite_scroll.dart';
import 'post.dart';
import 'post_list_options.dart';
typedef FetcherWithSorting<T> = Future<List<T>> Function(
int page, int batchSize, SortType sortType);
/// Infinite list of posts
class SortableInfiniteList<T> extends HookWidget {
final Future<List<T>> Function(int page, int batchSize, SortType sortType)
fetcher;
final Widget Function(T) builder;
final FetcherWithSorting<T> fetcher;
final Widget Function(T) itemBuilder;
final InfiniteScrollController controller;
final Function onStyleChange;
final Widget noItems;
const SortableInfiniteList({
@required this.fetcher,
@required this.builder,
@required this.itemBuilder,
this.controller,
this.onStyleChange,
this.noItems,
}) : assert(fetcher != null),
assert(builder != null);
assert(itemBuilder != null);
@override
Widget build(BuildContext context) {
final isc = useInfiniteScrollController();
final defaultController = useInfiniteScrollController();
final isc = controller ?? defaultController;
final sort = useState(SortType.active);
void changeSorting(SortType newSort) {
@ -34,50 +42,61 @@ class SortableInfiniteList<T> extends HookWidget {
}
return InfiniteScroll<T>(
prepend: PostListOptions(
leading: PostListOptions(
sortValue: sort.value,
onSortChanged: changeSorting,
styleButton: onStyleChange != null,
),
builder: builder,
itemBuilder: itemBuilder,
padding: EdgeInsets.zero,
fetchMore: (page, batchSize) => fetcher(page, batchSize, sort.value),
fetcher: (page, batchSize) => fetcher(page, batchSize, sort.value),
controller: isc,
batchSize: 20,
noItems: noItems,
);
}
}
class InfinitePostList extends StatelessWidget {
final Future<List<PostView>> Function(
int page, int batchSize, SortType sortType) fetcher;
final FetcherWithSorting<PostView> fetcher;
final InfiniteScrollController controller;
const InfinitePostList({@required this.fetcher}) : assert(fetcher != null);
const InfinitePostList({
@required this.fetcher,
this.controller,
}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<PostView>(
onStyleChange: () {},
builder: (post) => Column(
itemBuilder: (post) => Column(
children: [
PostWidget(post),
const SizedBox(height: 20),
],
),
fetcher: fetcher,
controller: controller,
noItems: const Text('there are no posts'),
);
}
class InfiniteCommentList extends StatelessWidget {
final Future<List<CommentView>> Function(
int page, int batchSize, SortType sortType) fetcher;
final FetcherWithSorting<CommentView> fetcher;
final InfiniteScrollController controller;
const InfiniteCommentList({@required this.fetcher}) : assert(fetcher != null);
const InfiniteCommentList({
@required this.fetcher,
this.controller,
}) : assert(fetcher != null);
Widget build(BuildContext context) => SortableInfiniteList<CommentView>(
builder: (comment) => CommentWidget(
itemBuilder: (comment) => CommentWidget(
CommentTree(comment),
postCreatorId: null,
detached: true,
),
fetcher: fetcher,
controller: controller,
noItems: const Text('there are no comments'),
);
}