fix(chat): maintain scroll position when keyboard appears

When the keyboard appeared, the chat content would get pushed up and the user would lose sight of what they were viewing. This happened because the outer container had keyboard-aware padding, but the ScrollView's content padding was static.

Now the ScrollView's contentContainerStyle receives animated padding that matches the keyboard height, ensuring the content maintains its relative position and the user keeps seeing the same messages when the keyboard appears.

Changes:
- Pass keyboardHeight SharedValue to AgentStreamView
- Add animated contentPaddingStyle that adjusts for keyboard
- Apply dynamic padding to ScrollView contentContainerStyle
- Content now smoothly adjusts as keyboard animates in/out

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-10-26 15:08:52 +01:00
parent e82a7410e7
commit 985e9db6db
2 changed files with 21 additions and 5 deletions

View File

@@ -96,6 +96,7 @@ export default function AgentScreen() {
onPermissionResponse={(requestId, optionId) =>
respondToPermission(requestId, id!, agent.sessionId || "", [optionId])
}
keyboardHeight={keyboardHeight}
/>
)}
</ReanimatedAnimated.View>

View File

@@ -3,7 +3,7 @@ import { View, Text, ScrollView, Pressable } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { BottomSheetModal } from "@gorhom/bottom-sheet";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import Animated, { FadeIn, FadeOut, useAnimatedStyle, SharedValue } from "react-native-reanimated";
import { ChevronDown } from "lucide-react-native";
import {
AssistantMessage,
@@ -25,6 +25,7 @@ export interface AgentStreamViewProps {
streamItems: StreamItem[];
pendingPermissions: Map<string, PendingPermission>;
onPermissionResponse: (requestId: string, optionId: string) => void;
keyboardHeight: SharedValue<number>;
}
export function AgentStreamView({
@@ -33,6 +34,7 @@ export function AgentStreamView({
streamItems,
pendingPermissions,
onPermissionResponse,
keyboardHeight,
}: AgentStreamViewProps) {
const scrollViewRef = useRef<ScrollView>(null);
const bottomSheetRef = useRef<BottomSheetModal | null>(null);
@@ -42,6 +44,17 @@ export function AgentStreamView({
const [isNearBottom, setIsNearBottom] = useState(true);
const hasScrolledInitially = useRef(false);
// Animated content padding that responds to keyboard
const contentPaddingStyle = useAnimatedStyle(() => {
"worklet";
const absoluteHeight = Math.abs(keyboardHeight.value);
const keyboardPadding = Math.max(0, absoluteHeight - insets.bottom);
const basePadding = Math.max(insets.bottom, 32);
return {
paddingBottom: basePadding + keyboardPadding,
};
}, [insets.bottom]);
// Scroll to bottom immediately on initial load, then animate for new messages
useEffect(() => {
if (isNearBottom) {
@@ -82,10 +95,12 @@ export function AgentStreamView({
<ScrollView
ref={scrollViewRef}
style={stylesheet.scrollView}
contentContainerStyle={{
paddingTop: 24,
paddingBottom: Math.max(insets.bottom, 32),
}}
contentContainerStyle={[
{
paddingTop: 24,
},
contentPaddingStyle,
]}
onScroll={handleScroll}
scrollEventThrottle={16}
>