From 77d6f7257e84de100a1f5cc24e300ed20781f7ab Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Thu, 13 Feb 2025 16:47:03 +0100 Subject: [PATCH] feat: use getAllUsersForChat on ChatDetailScreen to avoid call to getUser for each user --- .../lib/src/config/chat_builders.dart | 5 +- .../chat_detail/chat_detail_screen.dart | 98 ++++++++++--------- .../widgets/default_message_builder.dart | 32 +++--- 3 files changed, 70 insertions(+), 65 deletions(-) diff --git a/packages/flutter_chat/lib/src/config/chat_builders.dart b/packages/flutter_chat/lib/src/config/chat_builders.dart index 5981e62..bb66518 100644 --- a/packages/flutter_chat/lib/src/config/chat_builders.dart +++ b/packages/flutter_chat/lib/src/config/chat_builders.dart @@ -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 diff --git a/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart b/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart index 0c41d02..4f8496e 100644 --- a/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart @@ -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 _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 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 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, ); } } diff --git a/packages/flutter_chat/lib/src/screens/chat_detail/widgets/default_message_builder.dart b/packages/flutter_chat/lib/src/screens/chat_detail/widgets/default_message_builder.dart index fea0044..501b3b7 100644 --- a/packages/flutter_chat/lib/src/screens/chat_detail/widgets/default_message_builder.dart +++ b/packages/flutter_chat/lib/src/screens/chat_detail/widgets/default_message_builder.dart @@ -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, );