feat: use getAllUsersForChat on ChatDetailScreen to avoid call to getUser for each user

This commit is contained in:
Freek van de Ven 2025-02-13 16:47:03 +01:00 committed by Freek van de Ven
parent cb1ceb456a
commit 756b99cfaa
3 changed files with 70 additions and 65 deletions

View file

@ -121,12 +121,13 @@ typedef ContainerBuilder = Widget Function(
/// If null is returned, the default chat message widget will be used so you can /// If null is returned, the default chat message widget will be used so you can
/// override for specific cases /// override for specific cases
/// [previousMessage] is the previous message in the chat /// [previousMessage] is the previous message in the chat
/// [sender] is the sender of the message and null if no user sent the message
typedef ChatMessageBuilder = Widget? Function( typedef ChatMessageBuilder = Widget? Function(
BuildContext context, BuildContext context,
MessageModel message, MessageModel message,
MessageModel? previousMessage, MessageModel? previousMessage,
UserModel user, UserModel? sender,
Function(UserModel user) onPressUserProfile, Function(UserModel sender) onPressSender,
); );
/// The group avatar builder /// The group avatar builder

View file

@ -60,23 +60,24 @@ class ChatDetailScreen extends HookWidget {
var chatSnapshot = useStream(chatStream); var chatSnapshot = useStream(chatStream);
var chat = chatSnapshot.data; var chat = chatSnapshot.data;
var allUsersStream = useMemoized(
() => options.userRepository.getAllUsersForChat(chatId: chatId),
[chatId],
);
var usersSnapshot = useStream(allUsersStream);
var allUsers = usersSnapshot.data ?? [];
useEffect( useEffect(
() { () {
if (chat == null) return; if (chat == null) return;
if (chat.isGroupChat) { chatTitle.value = _getChatTitle(
chatTitle.value = options.translations.groupNameEmpty;
} else {
unawaited(
_computeChatTitle(
chatScope: chatScope, chatScope: chatScope,
chat: chat, chat: chat,
onTitleComputed: (title) => chatTitle.value = title, allUsers: allUsers,
),
); );
}
return; return;
}, },
[chat], [chat, allUsers],
); );
useEffect( useEffect(
@ -98,6 +99,7 @@ class ChatDetailScreen extends HookWidget {
var body = _Body( var body = _Body(
chatId: chatId, chatId: chatId,
chat: chat, chat: chat,
chatUsers: allUsers,
onPressUserProfile: onPressUserProfile, onPressUserProfile: onPressUserProfile,
onUploadImage: onUploadImage, onUploadImage: onUploadImage,
onMessageSubmit: onMessageSubmit, onMessageSubmit: onMessageSubmit,
@ -120,21 +122,26 @@ class ChatDetailScreen extends HookWidget {
); );
} }
Future<void> _computeChatTitle({ String? _getChatTitle({
required ChatScope chatScope, required ChatScope chatScope,
required ChatModel chat, required ChatModel chat,
required void Function(String?) onTitleComputed, required List<UserModel> allUsers,
}) async { }) {
if (chatScope.options.chatTitleResolver != null) { if (chat.isGroupChat) {
onTitleComputed(chatScope.options.chatTitleResolver!.call(chat)); return chatScope.options.translations.groupNameEmpty;
return;
} }
var userId = chat.users.firstWhere((user) => user != chatScope.userId); // For one-to-one, pick the 'other' user from the list
var user = await chatScope.service.getUser(userId: userId).first; var otherUser = allUsers.firstWhere(
onTitleComputed( (u) => u.id != chatScope.userId,
user.fullname ?? chatScope.options.translations.anonymousUser, orElse: () => const UserModel(
id: "",
),
); );
return otherUser.fullname?.isNotEmpty ?? false
? otherUser.fullname
: chatScope.options.translations.anonymousUser;
} }
} }
@ -198,6 +205,7 @@ class _Body extends HookWidget {
const _Body({ const _Body({
required this.chatId, required this.chatId,
required this.chat, required this.chat,
required this.chatUsers,
required this.onPressUserProfile, required this.onPressUserProfile,
required this.onUploadImage, required this.onUploadImage,
required this.onMessageSubmit, required this.onMessageSubmit,
@ -206,6 +214,7 @@ class _Body extends HookWidget {
final String chatId; final String chatId;
final ChatModel? chat; final ChatModel? chat;
final List<UserModel> chatUsers;
final Function(UserModel) onPressUserProfile; final Function(UserModel) onPressUserProfile;
final Function(Uint8List image) onUploadImage; final Function(Uint8List image) onUploadImage;
final Function(String message) onMessageSubmit; final Function(String message) onMessageSubmit;
@ -261,15 +270,21 @@ class _Body extends HookWidget {
_ChatNoMessages(isGroupChat: chat!.isGroupChat), _ChatNoMessages(isGroupChat: chat!.isGroupChat),
] ]
: [ : [
for (var (index, message) in messages.indexed) for (var (index, message) in messages.indexed) ...[
if (chat!.id == message.chatId) if (chat!.id == message.chatId)
_ChatBubble( _ChatBubble(
key: ValueKey(message.id), key: ValueKey(message.id),
sender: chatUsers
.where(
(u) => u.id == message.senderId,
)
.firstOrNull,
message: message, message: message,
previousMessage: previousMessage:
index < messages.length - 1 ? messages[index + 1] : null, index < messages.length - 1 ? messages[index + 1] : null,
onPressUserProfile: onPressUserProfile, onPressSender: onPressUserProfile,
), ),
],
]; ];
return Stack( return Stack(
@ -457,7 +472,8 @@ class _ChatBottom extends HookWidget {
class _ChatBubble extends HookWidget { class _ChatBubble extends HookWidget {
const _ChatBubble({ const _ChatBubble({
required this.message, required this.message,
required this.onPressUserProfile, required this.sender,
required this.onPressSender,
this.previousMessage, this.previousMessage,
super.key, super.key,
}); });
@ -465,45 +481,33 @@ class _ChatBubble extends HookWidget {
/// The message to display. /// The message to display.
final MessageModel message; final MessageModel message;
/// The user who sent the message. This can be null because some messages are
/// not from users
final UserModel? sender;
/// The previous message in the list, if any. /// The previous message in the list, if any.
final MessageModel? previousMessage; final MessageModel? previousMessage;
/// Callback function when the user's profile is pressed. /// Callback function when a message sender is pressed.
final Function(UserModel user) onPressUserProfile; final Function(UserModel user) onPressSender;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var chatScope = ChatScope.of(context); var chatScope = ChatScope.of(context);
var service = chatScope.service;
var options = chatScope.options; var options = chatScope.options;
var userStream = useMemoized(
() => service.getUser(userId: message.senderId),
[message.senderId],
);
var userSnapshot = useStream(userStream);
if (userSnapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
var user = userSnapshot.data;
if (user == null) {
return const SizedBox.shrink();
}
return options.builders.chatMessageBuilder.call( return options.builders.chatMessageBuilder.call(
context, context,
message, message,
previousMessage, previousMessage,
user, sender,
onPressUserProfile, onPressSender,
) ?? ) ??
DefaultChatMessageBuilder( DefaultChatMessageBuilder(
message: message, message: message,
previousMessage: previousMessage, previousMessage: previousMessage,
user: user, sender: sender,
onPressUserProfile: onPressUserProfile, onPressSender: onPressSender,
); );
} }
} }

View file

@ -13,8 +13,8 @@ class DefaultChatMessageBuilder extends StatelessWidget {
const DefaultChatMessageBuilder({ const DefaultChatMessageBuilder({
required this.message, required this.message,
required this.previousMessage, required this.previousMessage,
required this.user, required this.sender,
required this.onPressUserProfile, required this.onPressSender,
super.key, super.key,
}); });
@ -25,25 +25,25 @@ class DefaultChatMessageBuilder extends StatelessWidget {
/// is from the same sender as the previous message. /// is from the same sender as the previous message.
final MessageModel? previousMessage; final MessageModel? previousMessage;
/// The user that sent the message /// The user that sent the message, can be null if the message is an event
final UserModel user; final UserModel? sender;
/// The function that is called when the user profile is pressed /// The function that is called when the sender is clicked
final Function(UserModel user) onPressUserProfile; final Function(UserModel user) onPressSender;
/// implements [ChatMessageBuilder] /// implements [ChatMessageBuilder]
static Widget builder( static Widget builder(
BuildContext context, BuildContext context,
MessageModel message, MessageModel message,
MessageModel? previousMessage, MessageModel? previousMessage,
UserModel user, UserModel? sender,
Function(UserModel user) onPressUserProfile, Function(UserModel sender) onPressSender,
) => ) =>
DefaultChatMessageBuilder( DefaultChatMessageBuilder(
message: message, message: message,
previousMessage: previousMessage, previousMessage: previousMessage,
user: user, sender: sender,
onPressUserProfile: onPressUserProfile, onPressSender: onPressSender,
); );
/// Merges the [MessageTheme] from the themeresolver with the [MessageTheme] /// Merges the [MessageTheme] from the themeresolver with the [MessageTheme]
@ -53,7 +53,7 @@ class DefaultChatMessageBuilder extends StatelessWidget {
required BuildContext context, required BuildContext context,
required ChatOptions options, required ChatOptions options,
required MessageModel message, required MessageModel message,
required UserModel user, required UserModel? user,
}) => }) =>
[ [
options.messageThemeResolver(context, message, user), options.messageThemeResolver(context, message, user),
@ -71,7 +71,7 @@ class DefaultChatMessageBuilder extends StatelessWidget {
context: context, context: context,
options: options, options: options,
message: message, message: message,
user: user, user: sender,
); );
var isSameSender = previousMessage != null && var isSameSender = previousMessage != null &&
@ -84,7 +84,7 @@ class DefaultChatMessageBuilder extends StatelessWidget {
isMessageFromSelf: isMessageFromSelf, isMessageFromSelf: isMessageFromSelf,
message: message, message: message,
messageTheme: messageTheme, messageTheme: messageTheme,
user: user, sender: sender,
); );
var messagePadding = messageTheme.messageSidePadding!; var messagePadding = messageTheme.messageSidePadding!;
@ -122,14 +122,14 @@ class _ChatMessageBubble extends StatelessWidget {
required this.isMessageFromSelf, required this.isMessageFromSelf,
required this.message, required this.message,
required this.messageTheme, required this.messageTheme,
required this.user, required this.sender,
}); });
final bool isSameSender; final bool isSameSender;
final bool isMessageFromSelf; final bool isMessageFromSelf;
final MessageModel message; final MessageModel message;
final MessageTheme messageTheme; final MessageTheme messageTheme;
final UserModel user; final UserModel? sender;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -141,7 +141,7 @@ class _ChatMessageBubble extends StatelessWidget {
var messageTime = dateFormatter.format(date: message.timestamp); var messageTime = dateFormatter.format(date: message.timestamp);
var senderTitle = Text( var senderTitle = Text(
user.firstName ?? "", sender?.firstName ?? "",
style: theme.textTheme.titleMedium, style: theme.textTheme.titleMedium,
); );