fix(chat): Listen-Spinner-Hänger + Auto-Scroll bei Bild-Nachrichten
Bug 1 — Chat-Liste: RefreshControl nutzte React-Querys `isRefetching`, das bei JEDEM Background-Refetch (focus-/stale-getriggert) true wird → nach Zurück-Navigation hing der Pull-to-Refresh-Spinner endlos. Fix: eigener `userRefreshing`-State, nur bei explizitem Pull-to-Refresh true, im finally zurückgesetzt. Bug 2 — Conversation scrollte nicht bis zur letzten Nachricht, wenn die ein Bild war: onContentSizeChange-scrollToEnd feuerte vor dem Bild-Load. Fix: ChatBubble bekommt onImageLoad-Callback, die letzte Bild-Nachricht triggert nach dem Laden erneut scrollToBottom. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
bf28d81d13
commit
708eac51c0
@ -94,11 +94,11 @@ export default function ChatScreen() {
|
||||
const colors = useColors();
|
||||
const styles = makeStyles(colors);
|
||||
const [search, setSearch] = useState('');
|
||||
const [userRefreshing, setUserRefreshing] = useState(false);
|
||||
|
||||
const {
|
||||
data: convs = [],
|
||||
isLoading: loadingDms,
|
||||
isRefetching: refetchingDms,
|
||||
refetch: refetchDms,
|
||||
} = useQuery<DmConversation[]>({
|
||||
queryKey: ['dm-conversations'],
|
||||
@ -106,6 +106,15 @@ export default function ChatScreen() {
|
||||
staleTime: 30_000,
|
||||
});
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setUserRefreshing(true);
|
||||
try {
|
||||
await refetchDms();
|
||||
} finally {
|
||||
setUserRefreshing(false);
|
||||
}
|
||||
}, [refetchDms]);
|
||||
|
||||
const filtered = search.trim()
|
||||
? convs.filter((c) =>
|
||||
c.partnerName.toLowerCase().includes(search.toLowerCase()) ||
|
||||
@ -152,8 +161,8 @@ export default function ChatScreen() {
|
||||
keyExtractor={(item) => item.partnerId}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refetchingDms}
|
||||
onRefresh={refetchDms}
|
||||
refreshing={userRefreshing}
|
||||
onRefresh={handleRefresh}
|
||||
tintColor={colors.brandOrange}
|
||||
/>
|
||||
}
|
||||
|
||||
@ -93,6 +93,7 @@ export default function DmScreen() {
|
||||
setPartner(null);
|
||||
partnerRef.current = null;
|
||||
setReplyTo(null);
|
||||
isInitialLoad.current = true;
|
||||
}, [userId]);
|
||||
|
||||
// Lade DM-History — staleTime:0 erzwingt immer frischen Fetch (kein Cache-Hit-Bug)
|
||||
@ -174,12 +175,20 @@ export default function DmScreen() {
|
||||
);
|
||||
useDmRealtime(userId, onDmInsert, !!myUserId);
|
||||
|
||||
const isInitialLoad = useRef(true);
|
||||
|
||||
const scrollToBottom = useCallback((animated: boolean) => {
|
||||
setTimeout(() => flatRef.current?.scrollToEnd({ animated }), 80);
|
||||
}, []);
|
||||
|
||||
// Auto-Scroll bei neuen Messages
|
||||
useEffect(() => {
|
||||
if (messages.length > 0) {
|
||||
requestAnimationFrame(() => flatRef.current?.scrollToEnd({ animated: true }));
|
||||
const animated = !isInitialLoad.current;
|
||||
isInitialLoad.current = false;
|
||||
scrollToBottom(animated);
|
||||
}
|
||||
}, [messages.length]);
|
||||
}, [messages.length, scrollToBottom]);
|
||||
|
||||
async function handleSend(payload: SendPayload) {
|
||||
if (sending) return;
|
||||
@ -310,12 +319,12 @@ export default function DmScreen() {
|
||||
onReply={startReply}
|
||||
onLike={toggleLike}
|
||||
onOpenImage={() => {}}
|
||||
onImageLoad={index === messages.length - 1 ? () => scrollToBottom(false) : undefined}
|
||||
/>
|
||||
)}
|
||||
keyExtractor={(m) => m.id}
|
||||
contentContainerStyle={{ paddingTop: 12, paddingBottom: 8 }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onContentSizeChange={() => flatRef.current?.scrollToEnd({ animated: false })}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@ -47,6 +47,7 @@ type Props = {
|
||||
onReply: (msg: ChatMsg) => void;
|
||||
onLike: (msg: ChatMsg) => void;
|
||||
onOpenImage: (url: string) => void;
|
||||
onImageLoad?: () => void;
|
||||
};
|
||||
|
||||
function formatTime(ts: string) {
|
||||
@ -75,6 +76,7 @@ export function ChatBubble({
|
||||
onReply,
|
||||
onLike,
|
||||
onOpenImage,
|
||||
onImageLoad,
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
const colors = useColors();
|
||||
@ -201,6 +203,7 @@ export function ChatBubble({
|
||||
contentFit="cover"
|
||||
cachePolicy="memory-disk"
|
||||
transition={200}
|
||||
onLoad={onImageLoad ? () => onImageLoad() : undefined}
|
||||
/>
|
||||
{isImageOnly && (
|
||||
<View style={styles.imageTimeOverlay}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user