From 896aa23ad3abf7a096ef953591bcd4dcb56a8b06 Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Fri, 26 Sep 2025 11:10:08 +0530 Subject: [PATCH] feat: implement scroll controller to scroll to the bottom --- .../chat/view/pages/dashbot_chat_page.dart | 67 ++++++++++++++----- .../view/widgets/dashbot_task_buttons.dart | 13 +++- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/lib/dashbot/features/chat/view/pages/dashbot_chat_page.dart b/lib/dashbot/features/chat/view/pages/dashbot_chat_page.dart index 2eae3ec8..7d00d98e 100644 --- a/lib/dashbot/features/chat/view/pages/dashbot_chat_page.dart +++ b/lib/dashbot/features/chat/view/pages/dashbot_chat_page.dart @@ -18,6 +18,7 @@ class ChatScreen extends ConsumerStatefulWidget { class _ChatScreenState extends ConsumerState { final TextEditingController _textController = TextEditingController(); + final ScrollController _scrollController = ScrollController(); bool _showTaskSuggestions = false; @override @@ -34,12 +35,36 @@ class _ChatScreenState extends ConsumerState { }); } + @override + void dispose() { + _textController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToBottom() { + SchedulerBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + @override Widget build(BuildContext context) { ref.listen(chatViewmodelProvider, (prev, next) { if (next.isGenerating) { _showTaskSuggestions = false; } + // Scroll to bottom when new message is added or streaming updates + if (prev?.currentStreamingResponse != next.currentStreamingResponse || + (prev != null && prev.isGenerating && !next.isGenerating)) { + _scrollToBottom(); + } }); return Scaffold( @@ -54,23 +79,28 @@ class _ChatScreenState extends ConsumerState { if (msgs.isEmpty && !state.isGenerating) { return const Center(child: Text('Ask me anything!')); } - return ListView.builder( - itemCount: msgs.length + (state.isGenerating ? 1 : 0), - padding: const EdgeInsets.all(16.0), - itemBuilder: (context, index) { - if (state.isGenerating && index == msgs.length) { + return Scrollbar( + controller: _scrollController, + thumbVisibility: true, + child: ListView.builder( + controller: _scrollController, + itemCount: msgs.length + (state.isGenerating ? 1 : 0), + padding: const EdgeInsets.all(16.0), + itemBuilder: (context, index) { + if (state.isGenerating && index == msgs.length) { + return ChatBubble( + message: state.currentStreamingResponse, + role: MessageRole.system, + ); + } + final message = msgs[index]; return ChatBubble( - message: state.currentStreamingResponse, - role: MessageRole.system, + message: message.content, + role: message.role, + actions: message.actions, ); - } - final message = msgs[index]; - return ChatBubble( - message: message.content, - role: message.role, - actions: message.actions, - ); - }, + }, + ), ); }, ), @@ -80,7 +110,10 @@ class _ChatScreenState extends ConsumerState { height: 5, thickness: 6, ), - if (_showTaskSuggestions) DashbotTaskButtons(), + if (_showTaskSuggestions) + DashbotTaskButtons( + onTaskSelected: _scrollToBottom, + ), Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -128,6 +161,7 @@ class _ChatScreenState extends ConsumerState { text: text, type: ChatMessageType.general, ); + _scrollToBottom(); } }, ), @@ -145,6 +179,7 @@ class _ChatScreenState extends ConsumerState { text: text, type: ChatMessageType.general, ); + _scrollToBottom(); }, tooltip: 'Send message', ), diff --git a/lib/dashbot/features/chat/view/widgets/dashbot_task_buttons.dart b/lib/dashbot/features/chat/view/widgets/dashbot_task_buttons.dart index c94b16b9..7f189cb9 100644 --- a/lib/dashbot/features/chat/view/widgets/dashbot_task_buttons.dart +++ b/lib/dashbot/features/chat/view/widgets/dashbot_task_buttons.dart @@ -11,7 +11,9 @@ import '../../../home/view/widgets/home_screen_task_button.dart'; import '../../../../core/providers/dashbot_window_notifier.dart'; class DashbotTaskButtons extends ConsumerWidget { - const DashbotTaskButtons({super.key}); + final VoidCallback? onTaskSelected; + + const DashbotTaskButtons({super.key, this.onTaskSelected}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -37,42 +39,49 @@ class DashbotTaskButtons extends ConsumerWidget { label: '🔎 Explain me this response', onPressed: () { vm.sendTaskMessage(ChatMessageType.explainResponse); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( label: '🐞 Help me debug this error', onPressed: () { vm.sendTaskMessage(ChatMessageType.debugError); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( label: '📄 Generate documentation', onPressed: () { vm.sendTaskMessage(ChatMessageType.generateDoc); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( label: '📝 Generate Tests', onPressed: () { vm.sendTaskMessage(ChatMessageType.generateTest); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( label: '🧩 Generate Code', onPressed: () { vm.sendTaskMessage(ChatMessageType.generateCode); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( label: '📥 Import cURL', onPressed: () { vm.sendTaskMessage(ChatMessageType.importCurl); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( label: '📄 Import OpenAPI', onPressed: () { vm.sendTaskMessage(ChatMessageType.importOpenApi); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( @@ -83,6 +92,7 @@ class DashbotTaskButtons extends ConsumerWidget { notifier.hide(); await GenerateToolDialog.show(context, ref); notifier.show(); + onTaskSelected?.call(); }, ), HomeScreenTaskButton( @@ -107,6 +117,7 @@ class DashbotTaskButtons extends ConsumerWidget { ); } notifier.show(); + onTaskSelected?.call(); }, ), ],