@@ -39,38 +39,45 @@ class ChatScreen extends ConsumerStatefulWidget {
3939 ConsumerState <ChatScreen > createState () => _ChatScreenState ();
4040}
4141
42- class _ChatScreenState extends ConsumerState <ChatScreen > {
42+ class _ChatScreenState extends ConsumerState <ChatScreen > with WidgetsBindingObserver {
4343 final ScrollController _scrollController = ScrollController ();
4444 double _lastScrollOffset = 0.0 ;
4545 Future <DMChatData ?>? _dmChatDataFuture;
4646 ProviderSubscription <ChatState >? _chatSubscription;
47+ bool _hasInitialScrollCompleted = false ;
48+ bool _isKeyboardOpen = false ;
4749
4850 @override
4951 void initState () {
5052 super .initState ();
53+ WidgetsBinding .instance.addObserver (this );
5154 _initializeDMChatData ();
55+
5256 WidgetsBinding .instance.addPostFrameCallback ((_) {
5357 if (widget.inviteId == null ) {
5458 ref.read (groupsProvider.notifier).loadGroupDetails (widget.groupId);
5559 ref.read (chatProvider.notifier).loadMessagesForGroup (widget.groupId);
5660 }
5761 });
5862
63+ // Listen for chat state changes to handle auto-scroll
5964 _chatSubscription = ref.listenManual (chatProvider, (previous, next) {
60- _handleScrollOnChatStateChange (previous, next);
65+ _handleChatStateChange (previous, next);
6166 });
6267 }
6368
6469 @override
6570 void didUpdateWidget (ChatScreen oldWidget) {
6671 super .didUpdateWidget (oldWidget);
6772 if (oldWidget.groupId != widget.groupId) {
73+ _hasInitialScrollCompleted = false ; // Reset for new chat
6874 _initializeDMChatData ();
6975 }
7076 }
7177
7278 @override
7379 void dispose () {
80+ WidgetsBinding .instance.removeObserver (this );
7481 _chatSubscription? .close ();
7582 _scrollController.dispose ();
7683 super .dispose ();
@@ -84,37 +91,73 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
8491 }
8592 }
8693
87- void _handleScrollToBottom ({bool hasAnimation = true }) {
94+ /// Scroll to the bottom of the chat
95+ void _scrollToBottom ({bool animated = true }) {
8896 WidgetsBinding .instance.addPostFrameCallback ((_) {
8997 if (! _scrollController.hasClients || ! mounted) return ;
90- final double max = _scrollController.position.maxScrollExtent;
91- if (hasAnimation) {
98+
99+ final maxScrollExtent = _scrollController.position.maxScrollExtent;
100+
101+ if (animated) {
92102 _scrollController.animateTo (
93- max ,
94- duration: const Duration (milliseconds: 200 ),
95- curve: Curves .easeOut ,
103+ maxScrollExtent ,
104+ duration: const Duration (milliseconds: 300 ),
105+ curve: Curves .easeOutCubic ,
96106 );
97107 } else {
98- _scrollController.jumpTo (max );
108+ _scrollController.jumpTo (maxScrollExtent );
99109 }
100110 });
101111 }
102112
103- void _handleScrollOnChatStateChange (
104- ChatState ? previous,
105- ChatState next,
106- ) {
113+ /// Handle keyboard visibility changes
114+ @override
115+ void didChangeMetrics () {
116+ super .didChangeMetrics ();
117+
118+ final bottomInset = WidgetsBinding .instance.platformDispatcher.views.first.viewInsets.bottom;
119+ final keyboardHeight =
120+ bottomInset / WidgetsBinding .instance.platformDispatcher.views.first.devicePixelRatio;
121+
122+ // Simple keyboard state tracking
123+ if (keyboardHeight > 100 ) {
124+ // Keyboard is open
125+ if (! _isKeyboardOpen) {
126+ _isKeyboardOpen = true ;
127+ // Scroll to bottom when keyboard opens (with delay)
128+ Future .delayed (const Duration (milliseconds: 300 ), () {
129+ if (mounted && _isKeyboardOpen) {
130+ _scrollToBottom ();
131+ }
132+ });
133+ }
134+ } else {
135+ // Keyboard is closed
136+ _isKeyboardOpen = false ;
137+ }
138+ }
139+
140+ /// Handle chat state changes for auto-scroll
141+ void _handleChatStateChange (ChatState ? previous, ChatState next) {
107142 final currentMessages = next.groupMessages[widget.groupId] ?? [];
108143 final previousMessages = previous? .groupMessages[widget.groupId] ?? [];
109144 final wasLoading = previous? .isGroupLoading (widget.groupId) ?? false ;
110145 final isLoading = next.isGroupLoading (widget.groupId);
111146 final isLoadingCompleted = wasLoading && ! isLoading;
112- if (isLoadingCompleted && currentMessages.isNotEmpty) {
113- _handleScrollToBottom (hasAnimation: false );
114- } else if (previousMessages.isNotEmpty &&
147+
148+ // Auto-scroll when chat first loads
149+ if (isLoadingCompleted && currentMessages.isNotEmpty && ! _hasInitialScrollCompleted) {
150+ _hasInitialScrollCompleted = true ;
151+ _scrollToBottom (animated: false );
152+ return ;
153+ }
154+
155+ // Auto-scroll when new messages arrive (after initial load)
156+ if (_hasInitialScrollCompleted &&
157+ previousMessages.isNotEmpty &&
115158 currentMessages.length > previousMessages.length &&
116159 currentMessages.last.id != previousMessages.last.id) {
117- _handleScrollToBottom ();
160+ _scrollToBottom ();
118161 }
119162 }
120163
@@ -240,7 +283,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
240283 onNotification: (scrollInfo) {
241284 if (scrollInfo is ScrollUpdateNotification ) {
242285 final currentFocus = FocusManager .instance.primaryFocus;
243- if (currentFocus != null && currentFocus.hasFocus) {
286+ if (currentFocus != null && currentFocus.hasFocus && ! _isKeyboardOpen ) {
244287 final currentOffset = scrollInfo.metrics.pixels;
245288 final scrollDelta = currentOffset - _lastScrollOffset;
246289 if (scrollDelta < - 20 ) currentFocus.unfocus ();
@@ -402,16 +445,16 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
402445 groupId: widget.groupId,
403446 replyToMessageId: replyingTo.id,
404447 message: message,
405- onMessageSent: _handleScrollToBottom,
406448 );
407449 } else {
408450 await chatNotifier.sendMessage (
409451 groupId: widget.groupId,
410452 message: message,
411453 isEditing: isEditing,
412- onMessageSent: _handleScrollToBottom,
413454 );
414455 }
456+ // Auto-scroll after sending message
457+ _scrollToBottom ();
415458 },
416459 ),
417460 ],
0 commit comments