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 fd85241..837f27f 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 @@ -249,6 +249,7 @@ class _ChatBody extends HookWidget { var isLoadingOlder = useState(false); var isLoadingNewer = useState(false); + var autoScrollEnabled = useState(true); var messagesStream = useMemoized( () => service.getMessages(chatId: chatId), @@ -320,14 +321,21 @@ class _ChatBody extends HookWidget { var offset = scrollController.offset; var maxScroll = scrollController.position.maxScrollExtent; + var threshold = options.paginationControls.scrollOffset; - if ((maxScroll - offset) <= options.paginationControls.scrollOffset && - !isLoadingOlder.value) { + var distanceFromBottom = maxScroll - offset; + + if (distanceFromBottom > 50) { + autoScrollEnabled.value = false; + } else { + autoScrollEnabled.value = true; + } + + if (offset <= threshold && !isLoadingOlder.value) { unawaited(loadOlderMessages()); } - if (offset <= options.paginationControls.scrollOffset && - !isLoadingNewer.value) { + if (distanceFromBottom <= threshold && !isLoadingNewer.value) { unawaited(loadNewerMessages()); } } @@ -341,6 +349,34 @@ class _ChatBody extends HookWidget { chat, ]); + useEffect( + () { + var disposed = false; + + /// Continuously scroll to the bottom of the chat + Future scrollLoop() async { + while (!disposed && autoScrollEnabled.value) { + await Future.delayed(const Duration(milliseconds: 500)); + if (disposed || !autoScrollEnabled.value) break; + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (disposed || !autoScrollEnabled.value) return; + if (scrollController.hasClients) { + scrollController.jumpTo( + scrollController.position.maxScrollExtent, + ); + } + }); + } + } + + unawaited(scrollLoop()); + + return () => disposed = true; + }, + [autoScrollEnabled.value], + ); + if (chat == null) { if (!options.enableLoadingIndicator) return const SizedBox.shrink(); return options.builders.loadingWidgetBuilder.call(context); @@ -361,17 +397,13 @@ class _ChatBody extends HookWidget { ? options.builders.loadingChatMessageBuilder.call(context) : const SizedBox.shrink(); - var reversedMessages = messages.reversed.toList(); var bubbleChildren = []; - if (reversedMessages.isEmpty) { + if (messages.isEmpty) { bubbleChildren .add(ChatNoMessages(isGroupChat: chat?.isGroupChat ?? false)); } else { - for (var (index, msg) in reversedMessages.indexed) { - var nextIndex = index + 1; - var prevMsg = nextIndex < reversedMessages.length - ? reversedMessages[nextIndex] - : null; + for (var (index, msg) in messages.indexed) { + var prevMsg = index > 0 ? messages[index - 1] : null; bubbleChildren.add( ChatBubble( @@ -393,15 +425,13 @@ class _ChatBody extends HookWidget { return Column( children: [ Expanded( - child: Align( - alignment: options.chatAlignment ?? Alignment.bottomCenter, - child: ListView( - reverse: true, - controller: scrollController, - physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 24), - children: listViewChildren, - ), + child: ListView.builder( + reverse: false, + controller: scrollController, + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.only(top: 24), + itemCount: listViewChildren.length, + itemBuilder: (context, index) => listViewChildren[index], ), ), ChatBottomInputSection(