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
/// override for specific cases
/// [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(
BuildContext context,
MessageModel message,
MessageModel? previousMessage,
UserModel user,
Function(UserModel user) onPressUserProfile,
UserModel? sender,
Function(UserModel sender) onPressSender,
);
/// The group avatar builder

View file

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

View file

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