Merge pull request #45 from krawieck/infinite-scroll

This commit is contained in:
Filip Krawczyk 2020-09-19 01:16:14 +02:00 committed by GitHub
commit 33f57b7e54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 107 additions and 10 deletions

View File

@ -29,6 +29,9 @@ jobs:
- name: Run lints
run: flutter analyze
- name: Run dartfmt
run: dartfmt --dry-run --set-exit-if-changed .
- name: Run tests
run: flutter test

View File

@ -0,0 +1,6 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import '../widgets/infinite_scroll.dart';
InfiniteScrollController useInfiniteScrollController() =>
useMemoized(() => InfiniteScrollController());

View File

@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../widgets/markdown_text.dart';

View File

@ -11,7 +11,7 @@ import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../hooks/memo_future.dart';
import '../hooks/stores.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../util/intl.dart';
import '../util/text_color.dart';

View File

@ -5,7 +5,7 @@ import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../hooks/memo_future.dart';
import '../hooks/stores.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../widgets/comment_section.dart';
import '../widgets/post.dart';
import '../widgets/save_post_button.dart';

View File

@ -7,7 +7,7 @@ import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../util/text_color.dart';
import '../widgets/badge.dart';

View File

@ -4,7 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../hooks/stores.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../widgets/bottom_modal.dart';
import '../widgets/user_profile.dart';

View File

@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../widgets/markdown_text.dart';
class UsersListPage extends StatelessWidget {

View File

@ -9,7 +9,7 @@ import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.dart' as ul;
import '../comment_tree.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../util/text_color.dart';
import 'bottom_modal.dart';

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import '../hooks/ref.dart';
class InfiniteScrollController {
Function() clear;
InfiniteScrollController() {
usedBeforeCreation() => throw Exception(
'Tried to use $runtimeType before it being initialized');
clear = usedBeforeCreation;
}
void dispose() {
clear = null;
}
}
class InfiniteScroll<T> extends HookWidget {
final int batchSize;
final Widget loadingWidget;
final Widget Function(T data) builder;
final Future<List<T>> Function(int page, int batchSize) fetchMore;
final InfiniteScrollController controller;
InfiniteScroll({
this.batchSize = 10,
this.loadingWidget =
const ListTile(title: Center(child: CircularProgressIndicator())),
@required this.builder,
@required this.fetchMore,
this.controller,
}) : assert(builder != null),
assert(fetchMore != null),
assert(batchSize > 0);
@override
Widget build(BuildContext context) {
final data = useState<List<T>>([]);
final hasMore = useRef(true);
final isFetching = useRef(false);
useEffect(() {
if (controller != null) {
controller.clear = () => data.value = [];
return controller.dispose;
}
return null;
}, []);
final page = data.value.length ~/ batchSize + 1;
return ListView.builder(
// +1 for the loading widget
itemCount: data.value.length + 1,
itemBuilder: (_, i) {
// reached the bottom, fetch more
if (i == data.value.length) {
// if there are no more, skip
if (!hasMore.current) {
return Container();
}
// if it's already fetching more, skip
if (!isFetching.current) {
isFetching.current = true;
fetchMore(page, batchSize).then((newData) {
// if got less than the batchSize, mark the list as done
if (newData.length < batchSize) {
hasMore.current = false;
}
// append new data
data.value = [...data.value, ...newData];
}).whenComplete(() => isFetching.current = false);
}
return loadingWidget;
}
// not last element, render list item
return builder(data.value[i]);
},
);
}
}

View File

@ -13,7 +13,7 @@ import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../pages/full_post.dart';
import '../url_launcher.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import 'bottom_modal.dart';
import 'fullscreenable_image.dart';

View File

@ -4,7 +4,7 @@ import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../hooks/delayed_loading.dart';
import '../hooks/logged_in_action.dart';
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
// TODO: sync this button between post and fullpost. the same with voting

View File

@ -5,7 +5,7 @@ import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:timeago/timeago.dart' as timeago;
import '../util/api_extensions.dart';
import '../util/extensions/api.dart';
import '../util/goto.dart';
import '../util/intl.dart';
import '../util/text_color.dart';