feat : Add alphabetically grouping flag to User list

This commit is contained in:
xsahil03x
2020-11-18 12:56:46 +05:30
parent c8001de7ad
commit b115cd254c
+125 -41
View File
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/users_bloc.dart'; import 'package:stream_chat_flutter/src/users_bloc.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'stream_chat.dart'; import 'stream_chat.dart';
import 'user_item.dart'; import 'user_item.dart';
@@ -10,8 +11,8 @@ import 'user_item.dart';
/// Callback called when tapping on a user /// Callback called when tapping on a user
typedef UserTapCallback = void Function(User, Widget); typedef UserTapCallback = void Function(User, Widget);
/// Builder used to create a custom [UserItem] from a [User] /// Builder used to create a custom [ListUserItem] from a [User]
typedef UserItemBuilder = Widget Function(BuildContext, User); typedef UserItemBuilder = Widget Function(BuildContext, User, bool);
/// ///
/// It shows the list of current users. /// It shows the list of current users.
@@ -63,6 +64,7 @@ class UserListView extends StatefulWidget {
this.selectedUsers, this.selectedUsers,
this.swipeToAction = false, this.swipeToAction = false,
this.pullToRefresh = true, this.pullToRefresh = true,
this.groupAlphabetically = false,
}) : super(key: key); }) : super(key: key);
/// The builder that will be used in case of error /// 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 /// Set it to false to disable the pull-to-refresh widget
final bool pullToRefresh; 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<User> selectedUsers; final List<User> selectedUsers;
/// Set it to true to group users by their first character
///
/// defaults to false
final bool groupAlphabetically;
@override @override
_UserListViewState createState() => _UserListViewState(); _UserListViewState createState() => _UserListViewState();
} }
@@ -172,11 +179,30 @@ class _UserListViewState extends State<UserListView>
); );
} }
StreamBuilder<List<User>> _buildListView( StreamBuilder<List<ListItem>> _buildListView(
UsersBlocState usersBlocState, UsersBlocState usersBlocState,
) { ) {
return StreamBuilder( 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 = <String, List<User>>{};
for (var e in temp) {
final alphabet = e.name[0];
groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
}
final items = <ListItem>[];
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) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
if (snapshot.error is Error) { if (snapshot.error is Error) {
@@ -256,13 +282,13 @@ class _UserListViewState extends State<UserListView>
); );
} }
final users = snapshot.data; final items = snapshot.data;
if (users.isEmpty && widget.emptyBuilder != null) { if (items.isEmpty && widget.emptyBuilder != null) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }
if (users.isEmpty && widget.emptyBuilder == null) { if (items.isEmpty && widget.emptyBuilder == null) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, viewportConstraints) { builder: (context, viewportConstraints) {
return SingleChildScrollView( return SingleChildScrollView(
@@ -285,13 +311,13 @@ class _UserListViewState extends State<UserListView>
controller: _scrollController, controller: _scrollController,
childrenDelegate: SliverChildBuilderDelegate( childrenDelegate: SliverChildBuilderDelegate(
(context, i) { (context, i) {
return _itemBuilder(context, i, users); return _itemBuilder(context, i, items);
}, },
childCount: (users.length * 2) + 1, childCount: (items.length * 2) + 1,
findChildIndexCallback: (key) { findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key; final ValueKey<String> valueKey = key;
final index = users final index =
.indexWhere((user) => 'USER-${user.id}' == valueKey.value); items.indexWhere((item) => item.key == valueKey.value);
return index != -1 ? (index * 2) : null; return index != -1 ? (index * 2) : null;
}, },
), ),
@@ -300,7 +326,7 @@ class _UserListViewState extends State<UserListView>
); );
} }
Widget _itemBuilder(context, int i, List<User> users) { Widget _itemBuilder(BuildContext context, int i, List<ListItem> items) {
if (i % 2 != 0) { if (i % 2 != 0) {
if (widget.separatorBuilder != null) { if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, i); return widget.separatorBuilder(context, i);
@@ -311,36 +337,57 @@ class _UserListViewState extends State<UserListView>
i = i ~/ 2; i = i ~/ 2;
final usersProvider = UsersBloc.of(context); final usersProvider = UsersBloc.of(context);
if (i < users.length) { if (i < items.length) {
final user = users[i]; final item = items[i];
final selected = widget.selectedUsers?.contains(user) ?? false; return item.when(
headerItem: (header) {
return Container(
key: ValueKey<String>('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; UserTapCallback onTap;
if (widget.onUserTap != null) { if (widget.onUserTap != null) {
onTap = widget.onUserTap; onTap = widget.onUserTap;
} else { } else {
onTap = (client, _) { onTap = (client, _) {
// Navigator.push( // Navigator.push(
// context, // context,
// MaterialPageRoute( // MaterialPageRoute(
// builder: (context) { // builder: (context) {
// return StreamChannel( // return StreamChannel(
// child: widget.userWidget, // child: widget.userWidget,
// channel: client, // channel: client,
// ); // );
// }, // },
// ), // ),
// ); // );
}; };
} }
return UserItem( return Container(
key: ValueKey<String>('USER-${user.id}'), key: ValueKey<String>('USER-${user.id}'),
user: user, child: widget.userItemBuilder != null
onTap: (user) => onTap(user, widget.userWidget), ? widget.userItemBuilder(context, user, selected)
onLongPress: widget.onUserLongPress, : UserItem(
onImageTap: widget.onImageTap, user: user,
selected: selected, onTap: (user) => onTap(user, widget.userWidget),
onLongPress: widget.onUserLongPress,
onImageTap: widget.onImageTap,
selected: selected,
),
);
},
); );
} else { } else {
return _buildQueryProgressIndicator(context, usersProvider); return _buildQueryProgressIndicator(context, usersProvider);
@@ -415,3 +462,40 @@ class _UserListViewState extends State<UserListView>
} }
} }
} }
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);
}