infinite scroll

This commit is contained in:
Simon Ding
2024-07-19 14:49:50 +08:00
parent e7e90c8d4e
commit 881d40f891
2 changed files with 58 additions and 27 deletions

View File

@@ -29,9 +29,8 @@ final movieWatchlistDataProvider = FutureProvider.autoDispose((ref) async {
return favList; return favList;
}); });
var searchPageDataProvider = var searchPageDataProvider = AsyncNotifierProvider.autoDispose
AsyncNotifierProvider.autoDispose.family<SearchPageData, List<SearchResult>, String>( .family<SearchPageData, List<SearchResult>, String>(SearchPageData.new);
SearchPageData.new);
var movieTorrentsDataProvider = AsyncNotifierProvider.autoDispose var movieTorrentsDataProvider = AsyncNotifierProvider.autoDispose
.family<MovieTorrentResource, List<TorrentResource>, String>( .family<MovieTorrentResource, List<TorrentResource>, String>(
@@ -40,28 +39,41 @@ var movieTorrentsDataProvider = AsyncNotifierProvider.autoDispose
class SearchPageData class SearchPageData
extends AutoDisposeFamilyAsyncNotifier<List<SearchResult>, String> { extends AutoDisposeFamilyAsyncNotifier<List<SearchResult>, String> {
List<SearchResult> list = List.empty(growable: true); List<SearchResult> list = List.empty(growable: true);
String? q;
int page = 1;
@override @override
FutureOr<List<SearchResult>> build(String arg) async { FutureOr<List<SearchResult>> build(String arg) async {
q = arg;
if (isBlank(arg)) { if (isBlank(arg)) {
return List.empty(); return List.empty();
} }
list = List.empty(growable: true); return query(arg, 1);
}
FutureOr<List<SearchResult>> query(String q, int page) async {
final dio = await APIs.getDio(); final dio = await APIs.getDio();
var resp = await dio.get(APIs.searchUrl, queryParameters: {"query": arg}); var resp = await dio
.get(APIs.searchUrl, queryParameters: {"query": q, "page": page});
var rsp = ServerResponse.fromJson(resp.data as Map<String, dynamic>); var rsp = ServerResponse.fromJson(resp.data as Map<String, dynamic>);
if (rsp.code != 0) { if (rsp.code != 0) {
throw rsp.message; throw rsp.message;
} }
var data = rsp.data as Map<String, dynamic>; var sp = SearchResponse.fromJson(rsp.data);
var results = data["results"] as List<dynamic>; return sp.results ?? List.empty();
for (final r in results) { }
var res = SearchResult.fromJson(r);
list.add(res); FutureOr<void> queryNextPage() async {
} //state = const AsyncLoading();
return list; final newState = await AsyncValue.guard(
() async {
page++;
final awaiteddata = await query(q!, page);
return [...?state.value, ...awaiteddata];
},
);
state = newState;
} }
Future<void> submit2Watchlist( Future<void> submit2Watchlist(
@@ -92,6 +104,25 @@ class SearchPageData
} }
} }
class SearchResponse {
int? page;
int? totalResults;
int? totalPage;
List<SearchResult>? results;
SearchResponse({this.page, this.totalResults, this.totalPage, this.results});
factory SearchResponse.fromJson(Map<String, dynamic> json) {
return SearchResponse(
page: json["page"],
totalPage: json["total_page"],
totalResults: json["total_results"],
results: (json["results"] as List)
.map((v) => SearchResult.fromJson(v))
.toList());
}
}
class MediaDetail { class MediaDetail {
int? id; int? id;
int? tmdbId; int? tmdbId;

View File

@@ -21,10 +21,9 @@ class SearchPage extends ConsumerStatefulWidget {
class _SearchPageState extends ConsumerState<SearchPage> { class _SearchPageState extends ConsumerState<SearchPage> {
List<dynamic> list = List.empty(); List<dynamic> list = List.empty();
Future<void>? _pendingFuture;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final q = widget.query??""; final q = widget.query ?? "";
var searchList = ref.watch(searchPageDataProvider(q)); var searchList = ref.watch(searchPageDataProvider(q));
List<Widget> res = searchList.when( List<Widget> res = searchList.when(
@@ -96,18 +95,18 @@ class _SearchPageState extends ConsumerState<SearchPage> {
error: (err, trace) => [Text("$err")], error: (err, trace) => [Text("$err")],
loading: () => [const MyProgressIndicator()]); loading: () => [const MyProgressIndicator()]);
var f = FutureBuilder( var f = NotificationListener(
// We listen to the pending operation, to update the UI accordingly. onNotification: (ScrollNotification scrollInfo) {
future: _pendingFuture, if (scrollInfo is ScrollEndNotification &&
builder: (context, snapshot) { scrollInfo.metrics.axisDirection == AxisDirection.down &&
if (snapshot.connectionState != ConnectionState.done && scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
snapshot.connectionState != ConnectionState.none) { ref.read(searchPageDataProvider(q).notifier).queryNextPage();
return const MyProgressIndicator();
} }
return ListView( return true;
children: res, },
); child: ListView(
}); children: res,
));
return Column( return Column(
children: [ children: [
TextField( TextField(
@@ -194,7 +193,8 @@ class _SearchPageState extends ConsumerState<SearchPage> {
child: const Text('确定'), child: const Text('确定'),
onPressed: () { onPressed: () {
ref ref
.read(searchPageDataProvider(widget.query??"").notifier) .read(searchPageDataProvider(widget.query ?? "")
.notifier)
.submit2Watchlist(item.id!, storageSelected, .submit2Watchlist(item.id!, storageSelected,
resSelected, item.mediaType!); resSelected, item.mediaType!);
Navigator.of(context).pop(); Navigator.of(context).pop();