loadConversationMessages method

Future<void> loadConversationMessages({
  1. bool reset = false,
})

Implementation

Future<void> loadConversationMessages({bool reset = false}) async {
  if (isLoadingConversationPage || isConversationLastPage) {
    return;
  }

  double? previousMaxExtent;
  double? previousPixels;
  if (!reset && chatScrollController.hasClients) {
    previousMaxExtent =
        chatScrollController.positions.lastOrNull?.maxScrollExtent;
    previousPixels = chatScrollController.positions.lastOrNull?.pixels;
  }

  isLoadingConversationPage = true;

  String url = ApiUrls.queriesUrl(
    assistantId,
    conversation.value?.id ?? "",
    page: conversationPage,
    isMarketplace: isMarketplace,
  );

  try {
    await ApiService.call(
      url,
      RequestType.get,
      onSuccess: (response) {
        List<dynamic> items = response.data['items'] ?? [];
        if (items.isNotEmpty) {
          List<PupauMessage> queryList = messagesFromLoadedChat(
            jsonEncode(items),
          );
          for (PupauMessage message in queryList) {
            PupauMessage userMessage = MessageService.getUserLoadedMessage(
              message,
            );
            if (isFirstMessageInGroup(message.groupId)) {
              messages.insert(0, userMessage);
            }
            PupauMessage assistantMessage =
                MessageService.getAssistantLoadedMessage(message);
            messages.insert(0, assistantMessage);
          }
        }
        int total = response.data['total'] ?? 0;
        conversationItemsLoaded += items.length;
        if (total > 0) {
          isConversationLastPage = conversationItemsLoaded >= total;
        } else {
          isConversationLastPage = items.length < 20;
        }
        conversationPage++;
        messages.refresh();
        update();
      },
      onError: (error) {
        update();
      },
    );
  } finally {
    isLoadingConversationPage = false;
    update();
  }

  if (!reset &&
      previousPixels != null &&
      previousMaxExtent != null &&
      chatScrollController.hasClients) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!chatScrollController.hasClients) return;

      final position = chatScrollController.positions.lastOrNull;
      if (position == null) return;

      double newMax = position.maxScrollExtent;
      double minExtent = position.minScrollExtent;
      double offsetDiff = newMax - previousMaxExtent!;
      double targetOffset = previousPixels! + offsetDiff;

      // Clamp targetOffset to valid bounds first, then apply safety margin to prevent bounce
      // Use a small safety margin (0.5px) to prevent floating point precision issues
      final safetyMargin = 0.5;
      targetOffset = targetOffset.clamp(minExtent, newMax);
      // Apply safety margin to prevent overshoot that causes bounce
      if (targetOffset >= newMax - safetyMargin) {
        targetOffset = newMax - safetyMargin;
      }

      // Use position.jumpTo() which respects physics constraints better
      position.jumpTo(targetOffset);
    });
  }
}