From bd8d5a307213effbe89b2155d68fa9fb9a12cc90 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Mon, 1 Jun 2026 10:42:44 +0200 Subject: [PATCH] fix(dm): Android scroll-to-bottom via scrollToOffset(999999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scrollToEnd() unterschätzt Content-Höhe auf Android und stoppt konsistent eine Message zu früh (verifiziert per adb-Screenshot). scrollToOffset({offset:999999}) wird auf den echten Max-Wert geclampt und landet immer am absoluten Ende der Liste. Co-Authored-By: Claude Sonnet 4.6 --- apps/rebreak-native/app/dm.tsx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/rebreak-native/app/dm.tsx b/apps/rebreak-native/app/dm.tsx index d24e03d..b49f826 100644 --- a/apps/rebreak-native/app/dm.tsx +++ b/apps/rebreak-native/app/dm.tsx @@ -79,6 +79,16 @@ export default function DmScreen() { const { userId } = useLocalSearchParams<{ userId: string }>(); const flatListRef = useRef>(null); + + // scrollToEnd() auf Android unterschätzt Content-Höhe und stoppt 1 Item + // zu früh. scrollToOffset(999999) wird auf den echten Max-Wert geclampt. + const scrollToBottom = useCallback((animated = false) => { + if (Platform.OS === 'android') { + flatListRef.current?.scrollToOffset({ offset: 999999, animated }); + } else { + flatListRef.current?.scrollToEnd({ animated }); + } + }, []); const [messages, setMessages] = useState([]); const [partner, setPartner] = useState(null); const partnerRef = useRef(null); @@ -110,12 +120,12 @@ export default function DmScreen() { const show = Keyboard.addListener(showEvent, (e) => { setKeyboardHeight(e.endCoordinates.height); setKeyboardVisible(true); - requestAnimationFrame(() => flatListRef.current?.scrollToEnd({ animated: false })); + requestAnimationFrame(() => scrollToBottom(false)); }); const hide = Keyboard.addListener(hideEvent, () => { setKeyboardHeight(0); setKeyboardVisible(false); - setTimeout(() => flatListRef.current?.scrollToEnd({ animated: false }), 50); + setTimeout(() => scrollToBottom(false), 50); }); return () => { show.remove(); hide.remove(); }; }, []); @@ -170,9 +180,9 @@ export default function DmScreen() { setMessages(msgs); // Dreistufiges Scroll-to-bottom: rAF + 100ms + 300ms deckt // Fälle ab wo Bilder nachgeladen werden und Content-Höhe wächst. - requestAnimationFrame(() => flatListRef.current?.scrollToEnd({ animated: false })); - setTimeout(() => flatListRef.current?.scrollToEnd({ animated: false }), 100); - setTimeout(() => flatListRef.current?.scrollToEnd({ animated: false }), 300); + requestAnimationFrame(() => scrollToBottom(false)); + setTimeout(() => scrollToBottom(false), 100); + setTimeout(() => scrollToBottom(false), 300); return data; } catch (err: any) { console.error('[dm] history fetch failed:', err?.message ?? err); @@ -187,7 +197,7 @@ export default function DmScreen() { // Neue Nachricht (incoming Realtime oder outgoing send) → immer scrollen useEffect(() => { if (messages.length === 0) return; - requestAnimationFrame(() => flatListRef.current?.scrollToEnd({ animated: true })); + requestAnimationFrame(() => scrollToBottom(true)); }, [messages.length]); // Realtime: neue DMs vom Partner @@ -545,8 +555,8 @@ export default function DmScreen() { showsVerticalScrollIndicator={false} keyboardDismissMode="interactive" keyboardShouldPersistTaps="handled" - onContentSizeChange={() => flatListRef.current?.scrollToEnd({ animated: false })} - onLayout={() => flatListRef.current?.scrollToEnd({ animated: false })} + onContentSizeChange={() => scrollToBottom(false)} + onLayout={() => scrollToBottom(false)} /> )}