From b115cd254c377981aff56737374b5b8991838493 Mon Sep 17 00:00:00 2001 From: xsahil03x Date: Wed, 18 Nov 2020 12:56:46 +0530 Subject: [PATCH] feat : Add alphabetically grouping flag to User list --- lib/src/user_list_view.dart | 166 +++++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 41 deletions(-) diff --git a/lib/src/user_list_view.dart b/lib/src/user_list_view.dart index f4487dc0..56cb762d 100644 --- a/lib/src/user_list_view.dart +++ b/lib/src/user_list_view.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/users_bloc.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'stream_chat.dart'; import 'user_item.dart'; @@ -10,8 +11,8 @@ import 'user_item.dart'; /// Callback called when tapping on a user typedef UserTapCallback = void Function(User, Widget); -/// Builder used to create a custom [UserItem] from a [User] -typedef UserItemBuilder = Widget Function(BuildContext, User); +/// Builder used to create a custom [ListUserItem] from a [User] +typedef UserItemBuilder = Widget Function(BuildContext, User, bool); /// /// It shows the list of current users. @@ -63,6 +64,7 @@ class UserListView extends StatefulWidget { this.selectedUsers, this.swipeToAction = false, this.pullToRefresh = true, + this.groupAlphabetically = false, }) : super(key: key); /// The builder that will be used in case of error @@ -120,9 +122,14 @@ class UserListView extends StatefulWidget { /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; - /// Sets a blue trailing checkMark in [UserItem] for all the [selectedUsers] + /// Sets a blue trailing checkMark in [ListUserItem] for all the [selectedUsers] final List selectedUsers; + /// Set it to true to group users by their first character + /// + /// defaults to false + final bool groupAlphabetically; + @override _UserListViewState createState() => _UserListViewState(); } @@ -172,11 +179,30 @@ class _UserListViewState extends State ); } - StreamBuilder> _buildListView( + StreamBuilder> _buildListView( UsersBlocState usersBlocState, ) { return StreamBuilder( - stream: usersBlocState.usersStream, + stream: usersBlocState.usersStream.map( + (users) { + if (widget.groupAlphabetically) { + final temp = users + ..sort((curr, next) => curr.name.compareTo(next.name)); + final groupedUsers = >{}; + for (var e in temp) { + final alphabet = e.name[0]; + groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; + } + final items = []; + for (var key in groupedUsers.keys) { + items.add(ListHeaderItem(key)); + items.addAll(groupedUsers[key].map((e) => ListUserItem(e))); + } + return items; + } + return users.map((e) => ListUserItem(e)).toList(); + }, + ), builder: (context, snapshot) { if (snapshot.hasError) { if (snapshot.error is Error) { @@ -256,13 +282,13 @@ class _UserListViewState extends State ); } - final users = snapshot.data; + final items = snapshot.data; - if (users.isEmpty && widget.emptyBuilder != null) { + if (items.isEmpty && widget.emptyBuilder != null) { return widget.emptyBuilder(context); } - if (users.isEmpty && widget.emptyBuilder == null) { + if (items.isEmpty && widget.emptyBuilder == null) { return LayoutBuilder( builder: (context, viewportConstraints) { return SingleChildScrollView( @@ -285,13 +311,13 @@ class _UserListViewState extends State controller: _scrollController, childrenDelegate: SliverChildBuilderDelegate( (context, i) { - return _itemBuilder(context, i, users); + return _itemBuilder(context, i, items); }, - childCount: (users.length * 2) + 1, + childCount: (items.length * 2) + 1, findChildIndexCallback: (key) { final ValueKey valueKey = key; - final index = users - .indexWhere((user) => 'USER-${user.id}' == valueKey.value); + final index = + items.indexWhere((item) => item.key == valueKey.value); return index != -1 ? (index * 2) : null; }, ), @@ -300,7 +326,7 @@ class _UserListViewState extends State ); } - Widget _itemBuilder(context, int i, List users) { + Widget _itemBuilder(BuildContext context, int i, List items) { if (i % 2 != 0) { if (widget.separatorBuilder != null) { return widget.separatorBuilder(context, i); @@ -311,36 +337,57 @@ class _UserListViewState extends State i = i ~/ 2; final usersProvider = UsersBloc.of(context); - if (i < users.length) { - final user = users[i]; - final selected = widget.selectedUsers?.contains(user) ?? false; + if (i < items.length) { + final item = items[i]; + return item.when( + headerItem: (header) { + return Container( + key: ValueKey('HEADER-$header'), + color: Colors.grey.shade100, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + header, + style: TextStyle(fontWeight: FontWeight.w500), + ), + ), + ); + }, + userItem: (user) { + final selected = widget.selectedUsers?.contains(user) ?? false; - UserTapCallback onTap; - if (widget.onUserTap != null) { - onTap = widget.onUserTap; - } else { - onTap = (client, _) { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) { - // return StreamChannel( - // child: widget.userWidget, - // channel: client, - // ); - // }, - // ), - // ); - }; - } + UserTapCallback onTap; + if (widget.onUserTap != null) { + onTap = widget.onUserTap; + } else { + onTap = (client, _) { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) { + // return StreamChannel( + // child: widget.userWidget, + // channel: client, + // ); + // }, + // ), + // ); + }; + } - return UserItem( - key: ValueKey('USER-${user.id}'), - user: user, - onTap: (user) => onTap(user, widget.userWidget), - onLongPress: widget.onUserLongPress, - onImageTap: widget.onImageTap, - selected: selected, + return Container( + key: ValueKey('USER-${user.id}'), + child: widget.userItemBuilder != null + ? widget.userItemBuilder(context, user, selected) + : UserItem( + user: user, + onTap: (user) => onTap(user, widget.userWidget), + onLongPress: widget.onUserLongPress, + onImageTap: widget.onImageTap, + selected: selected, + ), + ); + }, ); } else { return _buildQueryProgressIndicator(context, usersProvider); @@ -415,3 +462,40 @@ class _UserListViewState extends State } } } + +abstract class ListItem { + String get key { + if (this is ListHeaderItem) { + final header = (this as ListHeaderItem).heading; + return 'HEADER-$header'; + } + if (this is ListUserItem) { + final user = (this as ListUserItem).user; + return 'USER-${user.id}'; + } + } + + Widget when({ + @required Widget Function(String heading) headerItem, + @required Widget Function(User user) userItem, + }) { + if (this is ListHeaderItem) { + return headerItem((this as ListHeaderItem).heading); + } + if (this is ListUserItem) { + return userItem((this as ListUserItem).user); + } + } +} + +class ListHeaderItem extends ListItem { + final String heading; + + ListHeaderItem(this.heading); +} + +class ListUserItem extends ListItem { + final User user; + + ListUserItem(this.user); +}