Skip to content

Commit 6384d95

Browse files
grichaclaude
andcommitted
Fix chat screen scrolling and keyboard behavior
- Auto-scroll to bottom when new streaming content arrives (only if already at bottom) - Fix keyboard avoiding view gap issue - Only scroll on keyboard show if already at bottom - Use keyboardDidShow for reliable scroll timing after layout settles - Always scroll to bottom when sending a message 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 05e7d66 commit 6384d95

File tree

1 file changed

+17
-3
lines changed

1 file changed

+17
-3
lines changed

mobile/src/screens/SessionChatScreen.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ export function SessionChatScreen({ route, navigation }: any) {
271271
const messageIdCounter = useRef(0)
272272
const hasLoadedInitial = useRef(false)
273273
const modelInitialized = useRef(false)
274+
const isAtBottomRef = useRef(true)
274275

275276
const fetchAgentType = agentType === 'opencode' ? 'opencode' : 'claude-code'
276277

@@ -301,7 +302,12 @@ export function SessionChatScreen({ route, navigation }: any) {
301302
}, [availableModels, agentsConfig, fetchAgentType])
302303

303304
useEffect(() => {
304-
const showSub = Keyboard.addListener('keyboardWillShow', () => setKeyboardVisible(true))
305+
const showSub = Keyboard.addListener('keyboardDidShow', () => {
306+
setKeyboardVisible(true)
307+
if (isAtBottomRef.current) {
308+
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 50)
309+
}
310+
})
305311
const hideSub = Keyboard.addListener('keyboardWillHide', () => setKeyboardVisible(false))
306312
return () => {
307313
showSub.remove()
@@ -501,7 +507,6 @@ export function SessionChatScreen({ route, navigation }: any) {
501507
} catch {
502508
// Non-JSON message, ignore
503509
}
504-
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100)
505510
}
506511

507512
ws.onclose = () => {
@@ -523,11 +528,19 @@ export function SessionChatScreen({ route, navigation }: any) {
523528
return cleanup
524529
}, [connect])
525530

531+
useEffect(() => {
532+
if (streamingParts.length > 0 && isAtBottomRef.current) {
533+
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 50)
534+
}
535+
}, [streamingParts])
536+
526537
const handleScroll = useCallback((event: any) => {
527-
const { contentOffset } = event.nativeEvent
538+
const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent
528539
if (contentOffset.y < 100 && hasMoreMessages && !isLoadingMore) {
529540
loadMoreMessages()
530541
}
542+
const distanceFromBottom = contentSize.height - layoutMeasurement.height - contentOffset.y
543+
isAtBottomRef.current = distanceFromBottom < 100
531544
}, [hasMoreMessages, isLoadingMore, loadMoreMessages])
532545

533546
const sendMessage = () => {
@@ -539,6 +552,7 @@ export function SessionChatScreen({ route, navigation }: any) {
539552
setIsStreaming(true)
540553
streamingPartsRef.current = []
541554
setStreamingParts([])
555+
isAtBottomRef.current = true
542556

543557
setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100)
544558

0 commit comments

Comments
 (0)