addMessage method
Adds a new message to the chat.
Implementation
void addMessage(ChatMessage message) {
final messageId = _getMessageId(message);
if (!_messageCache.containsKey(messageId)) {
// Determine if this is a user message using the ID and properties
final isFromUser = ((message.customProperties?['isUserMessage'] as bool?) == true) ||
(message.customProperties?['source'] == 'user') ||
(message.user.id != 'ai' && message.user.id != 'bot' && message.user.id != 'assistant');
// Get the user ID for response tracking
final userId = message.user.id;
// Get responseId if available - for linking multiple messages as one response
final responseId = message.customProperties?['responseId'] as String?;
// Track if this is the start of a response (user changed and it's not a user message)
final isStartOfResponse = (_lastMessageUserId != userId && !isFromUser) ||
message.customProperties?['isStartOfResponse'] == true;
_lastMessageUserId = userId;
// Create a property map to track messaging state
final updatedProperties = <String, dynamic>{...?message.customProperties};
// Track the first message of an AI response
if (isStartOfResponse) {
_currentResponseFirstMessageId = messageId;
// Use properties to mark this as the first message of a response
updatedProperties['isFirstResponseMessage'] = true;
updatedProperties['isStartOfResponse'] = true;
debugPrint(
'NEW RESPONSE: First message ID: $messageId from user: $userId responseId: $responseId');
}
// When a user message appears, we need to reset the first response tracking
if (isFromUser) {
// Clear the first response message ID whenever a user sends a message
// This way, the next AI message will become the first of a new response
_currentResponseFirstMessageId = null;
debugPrint('USER MESSAGE: Reset response tracking for user: $userId');
}
// For related messages with the same responseId, we want to keep the first message
// of the chain as the scroll target
if (responseId != null && !isStartOfResponse && !isFromUser) {
// Check if we already have a message with this responseId flagged as start of response
final existingFirstMessage = _messages.firstWhere(
(msg) =>
msg.customProperties?['responseId'] == responseId &&
(msg.customProperties?['isStartOfResponse'] == true ||
msg.customProperties?['isFirstResponseMessage'] == true),
orElse: () => message,
);
// If we found a message that's marked as first in this response chain,
// use its ID for scrolling to ensure consistent behavior
if (existingFirstMessage != message &&
existingFirstMessage.customProperties?['responseId'] == responseId) {
final existingFirstId = _getMessageId(existingFirstMessage);
_currentResponseFirstMessageId = existingFirstId;
debugPrint(
'CHAIN MESSAGE: Using existing first message ID: $existingFirstId for responseId: $responseId');
}
}
// Mark user messages for identification
if (!updatedProperties.containsKey('isUserMessage') &&
!updatedProperties.containsKey('source')) {
updatedProperties['isUserMessage'] = isFromUser;
debugPrint('MESSAGE TYPE: userId=${message.user.id}, isUserMessage=$isFromUser');
}
// Create a copy of the message with updated properties
final updatedMessage = message.copyWith(customProperties: updatedProperties);
if (paginationConfig.reverseOrder) {
// In reverse order (newest first), new messages go at the beginning (index 0)
// With ListView.builder(reverse: true), this puts newest messages at the bottom
_messages.insert(0, updatedMessage);
} else {
// In chronological order (oldest first), new messages go at the end
// With ListView.builder(reverse: false), this puts newest messages at the bottom
_messages.add(updatedMessage);
}
_messageCache[messageId] = updatedMessage;
notifyListeners();
// Determine if we should scroll based on the configuration
final config = scrollBehaviorConfig;
// Detect if this is a user message
final isUserMessage =
updatedProperties['isUserMessage'] as bool? ?? updatedProperties['source'] == 'user';
// Identify the first message in a response chain
final isFirstResponse = updatedProperties['isFirstResponseMessage'] as bool? ?? false;
final shouldScroll = _determineShouldScroll(config, isUserMessage, isFirstResponse);
if (shouldScroll) {
debugPrint('SCROLLING: After render for isUserMessage=$isUserMessage');
_scrollAfterRender(isUserMessage, isStartOfResponse, config);
} else {
debugPrint('NOT SCROLLING: Message doesn\'t meet scroll criteria');
}
}
}