forceScrollToFirstMessageInChain method
Find first message in a response chain and force scroll to it with extra reliability
Implementation
void forceScrollToFirstMessageInChain(String responseId) {
if (_scrollController?.hasClients != true) return;
// Implement debounce for scrolling to prevent jitter
final now = DateTime.now();
if (now.difference(_lastScrollTime).inMilliseconds < _scrollDebounceMs) {
debugPrint('SCROLL DEBOUNCED: Too soon after last scroll');
return;
}
_lastScrollTime = now;
// Log information about the animation being used for debugging
final animationInfo =
'Animation: duration=${scrollBehaviorConfig.scrollAnimationDuration.inMilliseconds}ms, '
'curve=${scrollBehaviorConfig.scrollAnimationCurve.runtimeType}';
debugPrint('SCROLL ANIMATION INFO: $animationInfo');
try {
// Find the first message with this responseId
final firstMessageInChain = _messages.firstWhere(
(msg) =>
msg.customProperties?['responseId'] == responseId &&
(msg.customProperties?['isStartOfResponse'] == true ||
msg.customProperties?['isFirstResponseMessage'] == true),
orElse: () => _messages.firstWhere(
(msg) => msg.customProperties?['responseId'] == responseId,
orElse: () => throw Exception('No message found with responseId: $responseId'),
),
);
// Find the index of this message
final index = _messages.indexOf(firstMessageInChain);
if (index < 0) {
debugPrint('FORCE SCROLL: Message not found in list');
return;
}
debugPrint(
'FORCE SCROLL: Found first message in chain at index $index with responseId: $responseId, reverseOrder: ${paginationConfig.reverseOrder}');
// Always use animation when testing different animation curves
final scrollDuration = scrollBehaviorConfig.scrollAnimationDuration;
final scrollCurve = scrollBehaviorConfig.scrollAnimationCurve;
debugPrint(
'APPLYING ANIMATION: duration=${scrollDuration.inMilliseconds}ms, curve=$scrollCurve');
// Use a simple approach: scroll to a calculated position based on message index
// Get list properties
final maxExtent = _scrollController!.position.maxScrollExtent;
final itemCount = _messages.length;
debugPrint('SCROLL INFO: maxExtent=$maxExtent, itemCount=$itemCount, messageIndex=$index');
double targetPosition;
if (paginationConfig.reverseOrder) {
// In reverse order mode (newest messages at bottom)
// Index 0 = newest message (bottom), higher index = older messages (top)
// We want to show the first message of the response, so scroll towards the top
if (index == 0) {
targetPosition = 0.0; // Show the newest message (at bottom)
} else {
// Calculate position to show this message near the top of the viewport
// Since it's reverse order, we need to scroll down more to see older messages
targetPosition = maxExtent * (index / itemCount) * 0.8; // Show near top of viewport
}
} else {
// In chronological mode (oldest messages at top)
// Index 0 = oldest message (top), higher index = newer messages (bottom)
// We want to show the first message of the response near the top
if (index < itemCount * 0.2) {
// If message is in first 20% of list, scroll to top
targetPosition = 0.0;
} else {
// Calculate position to show this message near the top of viewport
targetPosition = (maxExtent * (index / itemCount)) - (maxExtent * 0.2);
}
}
// Clamp to valid range
targetPosition = targetPosition.clamp(0.0, maxExtent);
debugPrint(
'FORCE SCROLL: Scrolling to position $targetPosition (reverse: ${paginationConfig.reverseOrder})');
_scrollController!.animateTo(
targetPosition,
duration: scrollDuration,
curve: scrollCurve,
);
debugPrint(
'FORCE SCROLL: Animation started to first message in chain using ${scrollCurve.runtimeType}');
} catch (e) {
debugPrint('ERROR FORCE SCROLLING TO CHAIN: $e');
// Fallback: just scroll to top to show the beginning of messages
try {
_scrollController!.animateTo(
0.0,
duration: scrollBehaviorConfig.scrollAnimationDuration,
curve: scrollBehaviorConfig.scrollAnimationCurve,
);
debugPrint('FALLBACK SCROLL: Scrolled to top as fallback');
} catch (fallbackError) {
debugPrint('FALLBACK SCROLL ERROR: $fallbackError');
}
}
}