diff --git a/lib/src/lazy_load_scroll_view.dart b/lib/src/lazy_load_scroll_view.dart new file mode 100644 index 00000000..e83fc777 --- /dev/null +++ b/lib/src/lazy_load_scroll_view.dart @@ -0,0 +1,81 @@ +import 'package:flutter/widgets.dart'; + +enum LoadingStatus { LOADING, STABLE } + +/// Signature for EndOfPageListeners +typedef EndOfPageListenerCallback = void Function(); + +/// A widget that wraps a [Widget] and will trigger [onEndOfPage] when it +/// reaches the bottom of the list +class LazyLoadScrollView extends StatefulWidget { + /// The [Widget] that this widget watches for changes on + final Widget child; + + /// Called when the [child] reaches the end of the list + final EndOfPageListenerCallback onEndOfPage; + + /// The offset to take into account when triggering [onEndOfPage] in pixels + final int scrollOffset; + + /// Used to determine if loading of new data has finished. You should use set this if you aren't using a FutureBuilder or StreamBuilder + final bool isLoading; + + LazyLoadScrollView({ + Key key, + @required this.child, + @required this.onEndOfPage, + this.isLoading = false, + this.scrollOffset = 100, + }) : assert(onEndOfPage != null), + assert(child != null), + super(key: key); + + @override + State createState() => _LazyLoadScrollViewState(); +} + +class _LazyLoadScrollViewState extends State { + LoadingStatus _loadMoreStatus = LoadingStatus.STABLE; + + @override + void didUpdateWidget(LazyLoadScrollView oldWidget) { + super.didUpdateWidget(oldWidget); + if (!widget.isLoading) { + _loadMoreStatus = LoadingStatus.STABLE; + } + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + child: widget.child, + onNotification: _onNotification, + ); + } + + bool _onNotification(Notification notification) { + if (notification is ScrollUpdateNotification) { + if (notification.metrics.maxScrollExtent > notification.metrics.pixels && + notification.metrics.maxScrollExtent - notification.metrics.pixels <= + widget.scrollOffset) { + if (_loadMoreStatus != null && + _loadMoreStatus == LoadingStatus.STABLE) { + _loadMoreStatus = LoadingStatus.LOADING; + widget.onEndOfPage(); + } + } + return true; + } + if (notification is OverscrollNotification) { + if (notification.overscroll > 0) { + if (_loadMoreStatus != null && + _loadMoreStatus == LoadingStatus.STABLE) { + _loadMoreStatus = LoadingStatus.LOADING; + widget.onEndOfPage(); + } + } + return true; + } + return false; + } +} diff --git a/lib/src/user_list_view.dart b/lib/src/user_list_view.dart index 046ad9c7..569eb38a 100644 --- a/lib/src/user_list_view.dart +++ b/lib/src/user_list_view.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/src/users_bloc.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -144,8 +145,6 @@ class UserListView extends StatefulWidget { class _UserListViewState extends State with WidgetsBindingObserver { - final ScrollController _scrollController = ScrollController(); - bool get _isListView => widget.crossAxisCount == 1; @override @@ -158,14 +157,6 @@ class _UserListViewState extends State pagination: widget.pagination, options: widget.options, ); - - _scrollController.addListener(() { - usersBloc.queryUsersLoading.first.then((loading) { - if (!loading) { - _listenUserPagination(usersBloc); - } - }); - }); } @override @@ -327,32 +318,35 @@ class _UserListViewState extends State ); } - if (_isListView) { - return ListView.separated( - physics: AlwaysScrollableScrollPhysics(), - controller: _scrollController, - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - separatorBuilder: (_, index) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); - } - return _separatorBuilder(context, index); - }, - itemBuilder: (context, index) { - return _listItemBuilder(context, index, items); - }, - ); - } - return GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount, - ), - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - physics: AlwaysScrollableScrollPhysics(), - controller: _scrollController, - itemBuilder: (context, index) { - return _gridItemBuilder(context, index, items); - }, + final child = _isListView + ? ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + // controller: _scrollController, + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, index); + } + return _separatorBuilder(context, index); + }, + itemBuilder: (context, index) { + return _listItemBuilder(context, index, items); + }, + ) + : GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.crossAxisCount, + ), + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + physics: AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) { + return _gridItemBuilder(context, index, items); + }, + ); + + return LazyLoadScrollView( + onEndOfPage: () => _listenUserPagination(usersBlocState), + child: child, ); }, ); @@ -488,18 +482,14 @@ class _UserListViewState extends State } void _listenUserPagination(UsersBlocState usersProvider) { - if (_scrollController.position.maxScrollExtent == - _scrollController.offset && - _scrollController.offset != 0) { - usersProvider.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination.copyWith( - offset: usersProvider.users?.length ?? 0, - ), - options: widget.options, - ); - } + usersProvider.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination.copyWith( + offset: usersProvider.users?.length ?? 0, + ), + options: widget.options, + ); } @override