I have an issue to my current chat ui , each time a user sends a booking request or a message the chat ui automatically scrolls up or middle of chat, how I can block the view of the user at the bottom of the chat after sending a message? The app is made in flutter, I created this function scrollToBottom, but there is no package? or what should I add
void sendMessage() {
final messageText = messageController.text.trim();
if (messageText.isEmpty) return;
messageController.clear();
// Send message in background
Future(() async {
try {
final chatRoomId = getChatRoomId();
final messageDoc = messageColl
.doc(chatRoomId)
.collection("chats")
.doc();
await messageDoc.set({
'sender': currentUserId,
'time': DateTime.now(),
'type': 'text',
'message': messageText,
'unread': true,
});
// Update chat room in background
messageColl.doc(chatRoomId).set({
"users": [currentUserId, widget.serviceProviderId],
"chatRoomId": chatRoomId,
"lastMessage": messageText,
"lastMessageTime": DateTime.now(),
});
// Scroll to bottom after sending message
_scrollToBottom();
} catch (e) {
print('Error sending message: $e');
}
});
}
void _scrollToBottom() {
if (_scrollController.hasClients) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
}
// Add this method to properly handle scroll position
void _maintainScrollPosition(VoidCallback callback) {
if (_scrollController.hasClients) {
final currentPosition = _scrollController.position.pixels;
callback();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.jumpTo(currentPosition);
}
});
} else {
callback();
}
}
// Add this method to handle keyboard visibility
void _handleKeyboardVisibility() {
if (mounted) {
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
if (bottomInset > 0 && _scrollController.hasClients) {
// Delay the scroll to ensure the keyboard is fully shown
Future.delayed(const Duration(milliseconds: 100), () {
_scrollToBottom();
});
}
}
}
Widget buildMessageItem(Map<String, dynamic> messageData) {
bool isMe = messageData['sender'] == currentUserId;
if (messageData['type'] == 'booking_request') {
bool isTimeUpdated = messageData['isTimeUpdated'] == true;
bool needsResponse = messageData['needsResponse'] == true;
bool isOriginalSender = messageData['sender'] == currentUserId;
String status = messageData['status'] ?? 'pending';
return Column(
children: [
BookingCard(bookingData: messageData),
// Show accept/decline buttons only when status is pending AND time is updated AND needs response
if (isTimeUpdated &&
needsResponse &&
isOriginalSender &&
status == 'pending')
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 32,
child: ElevatedButton.icon(
onPressed: () => handleRescheduleResponse(messageData['docId'], true),
icon: const Icon(Icons.check_circle, color: Colors.white, size: 16),
label: const Text(
'Accept Time',
style: TextStyle(fontSize: 12),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
),
),
),
SizedBox(
height: 32,
child: ElevatedButton.icon(
onPressed: () => handleRescheduleResponse(messageData['docId'], false),
icon: const Icon(Icons.cancel, color: Colors.white, size: 16),
label: const Text(
'Decline Time',
style: TextStyle(fontSize: 12),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
),
),
),
],
),
),
// Show provider buttons only if not updated and is service provider
if (!isTimeUpdated &&
messageData['serviceProviderId'] == currentUserId &&
status == 'pending')
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 32,
child: ElevatedButton.icon(
onPressed: () => handleRescheduleResponse(messageData['docId'], true),
icon: const Icon(Icons.check_circle, color: Colors.white, size: 16),
label: const Text(
'Accept',
style: TextStyle(fontSize: 12),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
),
),
),
SizedBox(
height: 32,
child: ElevatedButton.icon(
onPressed: () => showRescheduleDialog(messageData['docId']),
icon: const Icon(Icons.schedule, color: Colors.white, size: 16),
label: const Text(
'Reschedule',
style: TextStyle(fontSize: 12),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
),
),
),
SizedBox(
height: 32,
child: ElevatedButton.icon(
onPressed: () => handleRescheduleResponse(messageData['docId'], false),
icon: const Icon(Icons.cancel, color: Colors.white, size: 16),
label: const Text(
'Decline',
style: TextStyle(fontSize: 12),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
),
),
),
],
),
),
],
);
} else {
return buildTextMessage(messageData, isMe);
}
}