From 8cdf33e27c633ac30e8851c4617f6063c141660d Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sat, 07 Mar 2026 17:53:05 +0100
Subject: [PATCH] fix: audio routing, WebSocket reconnection, inverted chat list
---
components/chat/MessageList.tsx | 62 ++++++++++---------------------
1 files changed, 20 insertions(+), 42 deletions(-)
diff --git a/components/chat/MessageList.tsx b/components/chat/MessageList.tsx
index c3eb13e..b65eb60 100644
--- a/components/chat/MessageList.tsx
+++ b/components/chat/MessageList.tsx
@@ -1,5 +1,5 @@
-import React, { useCallback, useEffect, useRef } from "react";
-import { FlatList, View } from "react-native";
+import React, { useCallback, useMemo } from "react";
+import { ActivityIndicator, FlatList, View } from "react-native";
import { Message } from "../../types";
import { MessageBubble } from "./MessageBubble";
import { TypingIndicator } from "./TypingIndicator";
@@ -9,62 +9,32 @@
messages: Message[];
isTyping?: boolean;
onDeleteMessage?: (id: string) => void;
+ onLoadMore?: () => void;
+ hasMore?: boolean;
}
-export function MessageList({ messages, isTyping, onDeleteMessage }: MessageListProps) {
- const listRef = useRef<FlatList<Message>>(null);
- const prevLengthRef = useRef(0);
-
- // Track the last message's content so transcript reflections trigger a scroll
- const lastContent = messages.length > 0 ? messages[messages.length - 1].content : "";
-
- // Flag: when true, every content size change triggers a scroll to bottom.
- // Used for bulk loads (restart, session switch) where FlatList renders lazily.
- const bulkScrollRef = useRef(false);
-
- useEffect(() => {
- if (messages.length > 0) {
- const delta = Math.abs(messages.length - prevLengthRef.current);
- if (delta > 1) {
- // Bulk load — let onContentSizeChange handle scrolling
- bulkScrollRef.current = true;
- setTimeout(() => { bulkScrollRef.current = false; }, 3000);
- } else {
- // Single new message — smooth scroll
- setTimeout(() => {
- listRef.current?.scrollToEnd({ animated: true });
- }, 50);
- }
- }
- prevLengthRef.current = messages.length;
- }, [messages.length, isTyping, lastContent]);
-
- const handleContentSizeChange = useCallback(() => {
- if (bulkScrollRef.current) {
- listRef.current?.scrollToEnd({ animated: false });
- }
- }, []);
+export function MessageList({ messages, isTyping, onDeleteMessage, onLoadMore, hasMore }: MessageListProps) {
+ // Inverted FlatList renders bottom-up — newest messages at the bottom (visually),
+ // which means we reverse the data so index 0 = newest = rendered at bottom.
+ const invertedData = useMemo(() => [...messages].reverse(), [messages]);
// Play from a voice message and auto-chain all consecutive assistant voice messages after it
const handlePlayVoice = useCallback(async (messageId: string) => {
const idx = messages.findIndex((m) => m.id === messageId);
if (idx === -1) return;
- // Collect this message + all consecutive assistant voice messages after it
const chain: Message[] = [];
for (let i = idx; i < messages.length; i++) {
const m = messages[i];
if (m.role === "assistant" && m.type === "voice" && m.audioUri) {
chain.push(m);
} else if (i > idx) {
- // Stop at the first non-voice or non-assistant message
break;
}
}
if (chain.length === 0) return;
- // Stop current playback, then queue all chunks
await stopPlayback();
for (const m of chain) {
playAudio(m.audioUri!);
@@ -73,8 +43,8 @@
return (
<FlatList
- ref={listRef}
- data={messages}
+ inverted
+ data={invertedData}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<MessageBubble
@@ -83,15 +53,23 @@
onPlayVoice={handlePlayVoice}
/>
)}
- onContentSizeChange={handleContentSizeChange}
+ onEndReached={hasMore ? onLoadMore : undefined}
+ onEndReachedThreshold={0.5}
contentContainerStyle={{ paddingVertical: 12 }}
showsVerticalScrollIndicator={false}
- ListFooterComponent={
+ ListHeaderComponent={
<>
{isTyping && <TypingIndicator />}
<View style={{ height: 4 }} />
</>
}
+ ListFooterComponent={
+ hasMore ? (
+ <View style={{ paddingVertical: 16, alignItems: "center" }}>
+ <ActivityIndicator size="small" />
+ </View>
+ ) : null
+ }
/>
);
}
--
Gitblit v1.3.1