LCOV - code coverage report
Current view: top level - src - user_list_core.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 75 76 98.7 %
Date: 2021-05-27 10:59:48 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:convert';
       2             : 
       3             : import 'package:flutter/foundation.dart';
       4             : import 'package:flutter/material.dart';
       5             : import 'package:stream_chat/stream_chat.dart';
       6             : import 'package:stream_chat_flutter_core/src/users_bloc.dart';
       7             : 
       8             : ///
       9             : /// [UserListCore] is a simplified class that allows fetching users while
      10             : /// exposing UI builders.
      11             : /// A [UserListController] is used to load and paginate data.
      12             : ///
      13             : /// ```dart
      14             : /// class UsersListPage extends StatelessWidget {
      15             : ///   @override
      16             : ///   Widget build(BuildContext context) {
      17             : ///     return Scaffold(
      18             : ///       body: UsersListCore(
      19             : ///         filter: {
      20             : ///           'members': {
      21             : ///             '\$in': [StreamChat.of(context).user.id],
      22             : ///           }
      23             : ///         },
      24             : ///         sort: [SortOption('last_message_at')],
      25             : ///         pagination: PaginationParams(
      26             : ///           limit: 20,
      27             : ///         ),
      28             : ///         errorBuilder: (err) {
      29             : ///           return Center(
      30             : ///             child: Text('An error has occured'),
      31             : ///           );
      32             : ///         },
      33             : ///         emptyBuilder: (context) {
      34             : ///           return Center(
      35             : ///             child: Text('Nothing here...'),
      36             : ///           );
      37             : ///         },
      38             : ///         emptyBuilder: (context) {
      39             : ///           return Center(
      40             : ///             child: CircularProgressIndicator(),
      41             : ///           );
      42             : ///         },
      43             : ///         listBuilder: (context, list) {
      44             : ///           return UsersPage(list);
      45             : ///         }
      46             : ///       ),
      47             : ///     );
      48             : ///   }
      49             : /// }
      50             : /// ```
      51             : ///
      52             : /// [UsersBloc] must be the ancestor of this widget. This is necessary since
      53             : /// [UserListCore] depends on functionality contained within [UsersBloc].
      54             : ///
      55             : /// The parameters [listBuilder], [loadingBuilder], [emptyBuilder] and
      56             : /// [errorBuilder] must all be supplied and not null.
      57             : class UserListCore extends StatefulWidget {
      58             :   /// Instantiate a new [UserListCore]
      59           1 :   const UserListCore({
      60             :     required this.errorBuilder,
      61             :     required this.emptyBuilder,
      62             :     required this.loadingBuilder,
      63             :     required this.listBuilder,
      64             :     Key? key,
      65             :     this.filter,
      66             :     this.options,
      67             :     this.sort,
      68             :     this.pagination,
      69             :     this.groupAlphabetically = false,
      70             :     this.userListController,
      71           1 :   }) : super(key: key);
      72             : 
      73             :   /// A [UserListController] allows reloading and pagination.
      74             :   /// Use [UserListController.loadData] and [UserListController.paginateData]
      75             :   /// respectively for reloading and pagination.
      76             :   final UserListController? userListController;
      77             : 
      78             :   /// The builder that will be used in case of error
      79             :   final Widget Function(Object error) errorBuilder;
      80             : 
      81             :   /// The builder that will be used to build the list
      82             :   final Widget Function(BuildContext context, List<ListItem> users) listBuilder;
      83             : 
      84             :   /// The builder that will be used for loading
      85             :   final WidgetBuilder loadingBuilder;
      86             : 
      87             :   /// The builder used when the channel list is empty.
      88             :   final WidgetBuilder emptyBuilder;
      89             : 
      90             :   /// The query filters to use.
      91             :   /// You can query on any of the custom fields you've defined on the [Channel].
      92             :   /// You can also filter other built-in channel fields.
      93             :   final Filter? filter;
      94             : 
      95             :   /// Query channels options.
      96             :   ///
      97             :   /// state: if true returns the Channel state
      98             :   /// watch: if true listen to changes to this Channel in real time.
      99             :   final Map<String, dynamic>? options;
     100             : 
     101             :   /// The sorting used for the channels matching the filters.
     102             :   /// Sorting is based on field and direction, multiple sorting options can be
     103             :   /// provided. You can sort based on last_updated, last_message_at, updated_at,
     104             :   /// created_at or member_count. Direction can be ascending or descending.
     105             :   final List<SortOption>? sort;
     106             : 
     107             :   /// Pagination parameters
     108             :   /// limit: the number of users to return (max is 30)
     109             :   /// offset: the offset (max is 1000)
     110             :   /// message_limit: how many messages should be included to each channel
     111             :   final PaginationParams? pagination;
     112             : 
     113             :   /// Set it to true to group users by their first character
     114             :   ///
     115             :   /// defaults to false
     116             :   final bool groupAlphabetically;
     117             : 
     118           1 :   @override
     119           1 :   UserListCoreState createState() => UserListCoreState();
     120             : }
     121             : 
     122             : /// The current state of the [UserListCore].
     123             : class UserListCoreState extends State<UserListCore>
     124             :     with WidgetsBindingObserver {
     125             :   UsersBlocState? _usersBloc;
     126             : 
     127           1 :   @override
     128             :   void didChangeDependencies() {
     129           2 :     final newUsersBloc = UsersBloc.of(context);
     130           2 :     if (newUsersBloc != _usersBloc) {
     131           1 :       _usersBloc = newUsersBloc;
     132           1 :       loadData();
     133           2 :       if (widget.userListController != null) {
     134           4 :         widget.userListController!.loadData = loadData;
     135           4 :         widget.userListController!.paginateData = paginateData;
     136             :       }
     137             :     }
     138           1 :     super.didChangeDependencies();
     139             :   }
     140             : 
     141           1 :   @override
     142           1 :   Widget build(BuildContext context) => _buildListView();
     143             : 
     144           1 :   bool get _isListAlreadySorted =>
     145           2 :       widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false;
     146             : 
     147           4 :   Stream<List<ListItem>> _buildUserStream() => _usersBloc!.usersStream.map(
     148           1 :         (users) {
     149           2 :           if (widget.groupAlphabetically) {
     150             :             var temp = users;
     151           1 :             if (!_isListAlreadySorted) {
     152             :               temp = users
     153           5 :                 ..sort((curr, next) => curr.name.compareTo(next.name));
     154             :             }
     155           1 :             final groupedUsers = <String, List<User>>{};
     156           2 :             for (final e in temp) {
     157           3 :               final alphabet = e.name[0].toUpperCase();
     158           5 :               groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
     159             :             }
     160           1 :             final items = <ListItem>[];
     161           2 :             for (final key in groupedUsers.keys) {
     162             :               items
     163           2 :                 ..add(ListHeaderItem(key))
     164           5 :                 ..addAll(groupedUsers[key]!.map((e) => ListUserItem(e)));
     165             :             }
     166             :             return items;
     167             :           }
     168           4 :           return users.map((e) => ListUserItem(e)).toList();
     169             :         },
     170             :       );
     171             : 
     172           2 :   StreamBuilder<List<ListItem>> _buildListView() => StreamBuilder(
     173           1 :         stream: _buildUserStream(),
     174           1 :         builder: (context, snapshot) {
     175           1 :           if (snapshot.hasError) {
     176           4 :             return widget.errorBuilder(snapshot.error!);
     177             :           }
     178           1 :           if (!snapshot.hasData) {
     179           3 :             return widget.loadingBuilder(context);
     180             :           }
     181           1 :           final items = snapshot.data!;
     182           1 :           if (items.isEmpty) {
     183           3 :             return widget.emptyBuilder(context);
     184             :           }
     185           3 :           return widget.listBuilder(context, items);
     186             :         },
     187             :       );
     188             : 
     189             :   // ignore: public_member_api_docs
     190           3 :   Future<void> loadData() => _usersBloc!.queryUsers(
     191           2 :         filter: widget.filter,
     192           2 :         sort: widget.sort,
     193           2 :         pagination: widget.pagination,
     194           2 :         options: widget.options,
     195             :       );
     196             : 
     197             :   // ignore: public_member_api_docs
     198           3 :   Future<void> paginateData() => _usersBloc!.queryUsers(
     199           2 :         filter: widget.filter,
     200           2 :         sort: widget.sort,
     201           3 :         pagination: widget.pagination!.copyWith(
     202           3 :           offset: _usersBloc!.users?.length ?? 0,
     203             :         ),
     204           2 :         options: widget.options,
     205             :       );
     206             : 
     207           1 :   @override
     208             :   void didUpdateWidget(UserListCore oldWidget) {
     209           1 :     super.didUpdateWidget(oldWidget);
     210           4 :     if (widget.filter?.toString() != oldWidget.filter?.toString() ||
     211           6 :         jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
     212           4 :         widget.options?.toString() != oldWidget.options?.toString() ||
     213           5 :         widget.pagination?.toJson().toString() !=
     214           3 :             oldWidget.pagination?.toJson().toString()) {
     215           1 :       loadData();
     216             :     }
     217             :   }
     218             : }
     219             : 
     220             : /// Represents an item in a the user stream list.
     221             : /// Header items are prefixed with the key `HEADER` While users are prefixed
     222             : /// with `USER`.
     223             : abstract class ListItem {
     224             :   /// Unique key per list item
     225           1 :   String? get key {
     226           1 :     if (this is ListHeaderItem) {
     227           1 :       final header = (this as ListHeaderItem).heading;
     228           2 :       return 'HEADER-${header.toLowerCase()}';
     229             :     }
     230           1 :     if (this is ListUserItem) {
     231           1 :       final user = (this as ListUserItem).user;
     232           2 :       return 'USER-${user.id}';
     233             :     }
     234             :     return null;
     235             :   }
     236             : 
     237             :   /// Helper function to build widget based on ListItem type
     238             :   // ignore: missing_return
     239           1 :   Widget when({
     240             :     required Widget Function(String heading) headerItem,
     241             :     required Widget Function(User user) userItem,
     242             :   }) {
     243           1 :     if (this is ListHeaderItem) {
     244           2 :       return headerItem((this as ListHeaderItem).heading);
     245             :     }
     246           1 :     if (this is ListUserItem) {
     247           2 :       return userItem((this as ListUserItem).user);
     248             :     }
     249           0 :     return Container();
     250             :   }
     251             : }
     252             : 
     253             : /// Header Item
     254             : class ListHeaderItem extends ListItem {
     255             :   /// Constructs a new [ListHeaderItem]
     256           1 :   ListHeaderItem(this.heading);
     257             : 
     258             :   /// Heading used to build the item.
     259             :   final String heading;
     260             : }
     261             : 
     262             : /// User Item
     263             : class ListUserItem extends ListItem {
     264             :   /// Constructs a new [ListUserItem]
     265           1 :   ListUserItem(this.user);
     266             : 
     267             :   /// [User] used to build the item.
     268             :   final User user;
     269             : }
     270             : 
     271             : /// Controller used for paginating data in [ChannelListView]
     272             : class UserListController {
     273             :   /// Call this function to reload data
     274             :   AsyncCallback? loadData;
     275             : 
     276             :   /// Call this function to load further data
     277             :   AsyncCallback? paginateData;
     278             : }

Generated by: LCOV version 1.14