feat : Add alphabetically grouping flag to User list
This commit is contained in:
+125
-41
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user