Files
stream-chat-flutter/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart
T

192 lines
5.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/user_mention_tile.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Builder function for building a mention tile.
///
/// Use [UserMentionTile] for the default implementation.
typedef MentionTileBuilder = Widget Function(
BuildContext context,
User user,
);
/// Overlay for displaying users that can be mentioned.
class UserMentionsOverlay extends StatefulWidget {
/// Constructor for creating a [UserMentionsOverlay].
UserMentionsOverlay({
Key? key,
required this.query,
required this.channel,
required this.size,
this.client,
this.limit = 10,
this.mentionAllAppUsers = false,
this.mentionsTileBuilder,
this.onMentionUserTap,
}) : assert(
channel.state != null,
'Channel ${channel.cid} is not yet initialized',
),
assert(
!mentionAllAppUsers || (mentionAllAppUsers && client != null),
'StreamChatClient is required in order to use mentionAllAppUsers',
),
super(key: key);
/// Query for searching users.
final String query;
/// Limit applied on user search results.
final int limit;
/// The size of the overlay.
final Size size;
/// The channel to search for users.
final Channel channel;
/// The client to search for users in case [mentionAllAppUsers] is True.
final StreamChatClient? client;
/// When enabled mentions search users across the entire app.
///
/// Defaults to false.
final bool mentionAllAppUsers;
/// Customize the tile for the mentions overlay.
final MentionTileBuilder? mentionsTileBuilder;
/// Callback called when a user is selected.
final void Function(User user)? onMentionUserTap;
@override
_UserMentionsOverlayState createState() => _UserMentionsOverlayState();
}
class _UserMentionsOverlayState extends State<UserMentionsOverlay> {
late Future<List<User>> userMentionsFuture;
late List<User> initialMentions = membersAndWatchers;
@override
void initState() {
super.initState();
userMentionsFuture = queryMentions(widget.query);
}
@override
void didUpdateWidget(covariant UserMentionsOverlay oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.channel != oldWidget.channel ||
widget.query != oldWidget.query ||
widget.mentionAllAppUsers != oldWidget.mentionAllAppUsers ||
widget.limit != oldWidget.limit) {
userMentionsFuture = queryMentions(widget.query);
}
}
@override
Widget build(BuildContext context) {
final theme = StreamChatTheme.of(context);
return Card(
margin: const EdgeInsets.all(8),
elevation: 2,
color: theme.colorTheme.barsBg,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
clipBehavior: Clip.hardEdge,
child: Container(
constraints: BoxConstraints.loose(widget.size),
decoration: BoxDecoration(color: theme.colorTheme.barsBg),
child: FutureBuilder<List<User>>(
future: userMentionsFuture,
initialData: initialMentions,
builder: (context, snapshot) {
final users = snapshot.data!;
return ListView.builder(
padding: const EdgeInsets.all(0),
shrinkWrap: true,
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return Material(
color: theme.colorTheme.barsBg,
child: InkWell(
onTap: () => widget.onMentionUserTap?.call(user),
child: widget.mentionsTileBuilder?.call(context, user) ??
UserMentionTile(user),
),
);
},
);
},
),
),
);
}
List<User> get membersAndWatchers {
final state = widget.channel.state!;
return {
...state.watchers,
...state.members.map((it) => it.user),
}.whereType<User>().toList(growable: false);
}
Future<List<User>> queryMentions(String query) async {
if (widget.mentionAllAppUsers) {
return _queryUsers(query);
}
var channelState = widget.channel.state;
channelState = channelState!;
final members = channelState.members;
// By default, we return maximum 100 members via queryChannels api call.
// Thus it is safe to assume, that if number of members in channel.state
// is < 100, then all the members are already available on client side
// and we don't need to make any api call to queryMembers endpoint.
if (members.length < 100) {
final matchingUsers = membersAndWatchers.search(query);
return matchingUsers.toList(growable: false);
}
final result = await _queryMembers(query);
return result
.map((it) => it.user)
.whereType<User>()
.toList(growable: false);
}
Future<List<Member>> _queryMembers(String query) async {
final response = await widget.channel.queryMembers(
pagination: PaginationParams(limit: widget.limit),
filter: query.isEmpty
? const Filter.empty()
: Filter.autoComplete('name', query),
);
return response.members;
}
Future<List<User>> _queryUsers(String query) async {
assert(
widget.client != null,
'StreamChatClient is required in order to query all app users',
);
final response = await widget.client!.queryUsers(
pagination: PaginationParams(limit: widget.limit),
filter: query.isEmpty
? const Filter.empty()
: Filter.or([
Filter.autoComplete('id', query),
Filter.autoComplete('name', query),
]),
sort: [const SortOption('id', direction: SortOption.ASC)],
);
return response.users;
}
}