fix(keyboard): use transform instead of padding for keyboard animation
This commit is contained in:
@@ -3,7 +3,7 @@ import { View, Text, ActivityIndicator } from "react-native";
|
|||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
|
||||||
import ReanimatedAnimated, { useAnimatedStyle } from "react-native-reanimated";
|
import ReanimatedAnimated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { BackHeader } from "@/components/headers/back-header";
|
import { BackHeader } from "@/components/headers/back-header";
|
||||||
import { AgentStreamView } from "@/components/agent-stream-view";
|
import { AgentStreamView } from "@/components/agent-stream-view";
|
||||||
@@ -20,13 +20,18 @@ export default function AgentScreen() {
|
|||||||
|
|
||||||
// Keyboard animation
|
// Keyboard animation
|
||||||
const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
|
const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
|
||||||
const bottomInset = insets.bottom;
|
const bottomInset = useSharedValue(insets.bottom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
bottomInset.value = insets.bottom;
|
||||||
|
}, [insets.bottom, bottomInset]);
|
||||||
|
|
||||||
const animatedKeyboardStyle = useAnimatedStyle(() => {
|
const animatedKeyboardStyle = useAnimatedStyle(() => {
|
||||||
"worklet";
|
"worklet";
|
||||||
const absoluteHeight = Math.abs(keyboardHeight.value);
|
const absoluteHeight = Math.abs(keyboardHeight.value);
|
||||||
const padding = Math.max(0, absoluteHeight - bottomInset);
|
const shift = Math.max(0, absoluteHeight - bottomInset.value);
|
||||||
return {
|
return {
|
||||||
paddingBottom: padding,
|
transform: [{ translateY: -shift }],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,25 +86,26 @@ export default function AgentScreen() {
|
|||||||
<BackHeader title={agent.title || "Agent"} />
|
<BackHeader title={agent.title || "Agent"} />
|
||||||
|
|
||||||
{/* Content Area with Keyboard Animation */}
|
{/* Content Area with Keyboard Animation */}
|
||||||
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
<View style={styles.contentContainer}>
|
||||||
{isInitializing ? (
|
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
||||||
<View style={styles.loadingContainer}>
|
{isInitializing ? (
|
||||||
<ActivityIndicator size="large" color={theme.colors.primary} />
|
<View style={styles.loadingContainer}>
|
||||||
<Text style={styles.loadingText}>Loading agent...</Text>
|
<ActivityIndicator size="large" color={theme.colors.primary} />
|
||||||
</View>
|
<Text style={styles.loadingText}>Loading agent...</Text>
|
||||||
) : (
|
</View>
|
||||||
<AgentStreamView
|
) : (
|
||||||
agentId={id!}
|
<AgentStreamView
|
||||||
agent={agent}
|
agentId={id!}
|
||||||
streamItems={streamItems}
|
agent={agent}
|
||||||
pendingPermissions={agentPermissions}
|
streamItems={streamItems}
|
||||||
onPermissionResponse={(requestId, optionId) =>
|
pendingPermissions={agentPermissions}
|
||||||
respondToPermission(requestId, id!, agent.sessionId || "", [optionId])
|
onPermissionResponse={(requestId, optionId) =>
|
||||||
}
|
respondToPermission(requestId, id!, agent.sessionId || "", [optionId])
|
||||||
keyboardHeight={keyboardHeight}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ReanimatedAnimated.View>
|
</ReanimatedAnimated.View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -109,6 +115,10 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: theme.colors.background,
|
backgroundColor: theme.colors.background,
|
||||||
},
|
},
|
||||||
|
contentContainer: {
|
||||||
|
flex: 1,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { View, Text, ScrollView, Pressable } from "react-native";
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { BottomSheetModal } from "@gorhom/bottom-sheet";
|
import { BottomSheetModal } from "@gorhom/bottom-sheet";
|
||||||
import Animated, { FadeIn, FadeOut, useAnimatedStyle, SharedValue } from "react-native-reanimated";
|
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||||
import { ChevronDown } from "lucide-react-native";
|
import { ChevronDown } from "lucide-react-native";
|
||||||
import {
|
import {
|
||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
@@ -25,7 +25,6 @@ export interface AgentStreamViewProps {
|
|||||||
streamItems: StreamItem[];
|
streamItems: StreamItem[];
|
||||||
pendingPermissions: Map<string, PendingPermission>;
|
pendingPermissions: Map<string, PendingPermission>;
|
||||||
onPermissionResponse: (requestId: string, optionId: string) => void;
|
onPermissionResponse: (requestId: string, optionId: string) => void;
|
||||||
keyboardHeight: SharedValue<number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgentStreamView({
|
export function AgentStreamView({
|
||||||
@@ -34,7 +33,6 @@ export function AgentStreamView({
|
|||||||
streamItems,
|
streamItems,
|
||||||
pendingPermissions,
|
pendingPermissions,
|
||||||
onPermissionResponse,
|
onPermissionResponse,
|
||||||
keyboardHeight,
|
|
||||||
}: AgentStreamViewProps) {
|
}: AgentStreamViewProps) {
|
||||||
const scrollViewRef = useRef<ScrollView>(null);
|
const scrollViewRef = useRef<ScrollView>(null);
|
||||||
const bottomSheetRef = useRef<BottomSheetModal | null>(null);
|
const bottomSheetRef = useRef<BottomSheetModal | null>(null);
|
||||||
@@ -44,17 +42,6 @@ export function AgentStreamView({
|
|||||||
const [isNearBottom, setIsNearBottom] = useState(true);
|
const [isNearBottom, setIsNearBottom] = useState(true);
|
||||||
const hasScrolledInitially = useRef(false);
|
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
|
// Scroll to bottom immediately on initial load, then animate for new messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNearBottom) {
|
if (isNearBottom) {
|
||||||
@@ -95,12 +82,10 @@ export function AgentStreamView({
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
ref={scrollViewRef}
|
ref={scrollViewRef}
|
||||||
style={stylesheet.scrollView}
|
style={stylesheet.scrollView}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={{
|
||||||
{
|
paddingTop: 24,
|
||||||
paddingTop: 24,
|
paddingBottom: Math.max(insets.bottom, 32),
|
||||||
},
|
}}
|
||||||
contentPaddingStyle,
|
|
||||||
]}
|
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
scrollEventThrottle={16}
|
scrollEventThrottle={16}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -374,13 +374,13 @@
|
|||||||
{
|
{
|
||||||
"id": "97423777-33d3-414a-ae79-ea303230010a",
|
"id": "97423777-33d3-414a-ae79-ea303230010a",
|
||||||
"title": "Commit Local Changes",
|
"title": "Commit Local Changes",
|
||||||
"sessionId": "019a20d4-5fd4-76db-8021-697b8204597e",
|
"sessionId": "addbd42f-dbb1-4536-bc2b-e70a64587de6",
|
||||||
"options": {
|
"options": {
|
||||||
"type": "claude",
|
"type": "claude",
|
||||||
"sessionId": "addbd42f-dbb1-4536-bc2b-e70a64587de6"
|
"sessionId": "addbd42f-dbb1-4536-bc2b-e70a64587de6"
|
||||||
},
|
},
|
||||||
"createdAt": "2025-10-26T14:02:59.844Z",
|
"createdAt": "2025-10-26T14:02:59.844Z",
|
||||||
"lastActivityAt": "2025-10-26T14:03:22.696Z",
|
"lastActivityAt": "2025-10-26T14:08:58.462Z",
|
||||||
"cwd": "/Users/moboudra/dev/voice-dev"
|
"cwd": "/Users/moboudra/dev/voice-dev"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user