fix(mobile): replace KeyboardAvoidingView with Keyboard API listener

KeyboardAvoidingView was causing two issues: scrolling the page to the
top on focus, and not restoring the layout when dismissing the keyboard.

New approach uses Keyboard.addListener to track keyboard height and
applies it as paddingBottom on the message bar directly. This works
reliably on both iOS and Android regardless of softInputMode.

- Removed softwareKeyboardLayoutMode: "pan" from app.json
- Replaced KAV with plain View + dynamic keyboard padding
- Reverted SpawnSession/Settings screens to simple iOS-only KAV

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
suraj-markup 2026-03-03 03:48:44 +05:30
parent a685565bbb
commit ba2053dd4c
4 changed files with 26 additions and 18 deletions

View File

@ -30,8 +30,7 @@
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#0d1117"
},
"package": "com.composio.aomobile",
"softwareKeyboardLayoutMode": "pan"
"package": "com.composio.aomobile"
},
"web": {
"favicon": "./assets/favicon.png"

View File

@ -1,4 +1,4 @@
import React, { useState, useCallback, useRef } from "react";
import React, { useState, useCallback, useRef, useEffect } from "react";
import {
View,
Text,
@ -8,7 +8,7 @@ import {
TouchableOpacity,
Alert,
ActivityIndicator,
KeyboardAvoidingView,
Keyboard,
Platform,
Linking,
} from "react-native";
@ -43,6 +43,24 @@ export default function SessionDetailScreen({ route, navigation }: Props) {
const [ciFixState, setCiFixState] = useState<ActionButtonState>("idle");
const [commentFixStates, setCommentFixStates] = useState<Record<string, ActionButtonState>>({});
const scrollRef = useRef<ScrollView>(null);
const [keyboardHeight, setKeyboardHeight] = useState(0);
useEffect(() => {
const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
const showSub = Keyboard.addListener(showEvent, (e) => {
setKeyboardHeight(e.endCoordinates.height);
});
const hideSub = Keyboard.addListener(hideEvent, () => {
setKeyboardHeight(0);
});
return () => {
showSub.remove();
hideSub.remove();
};
}, []);
const handleSend = useCallback(async () => {
if (!message.trim()) return;
@ -162,11 +180,7 @@ export default function SessionDetailScreen({ route, navigation }: Props) {
const unresolvedComments = pr?.unresolvedComments ?? [];
return (
<KeyboardAvoidingView
style={styles.root}
behavior="padding"
keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 140}
>
<View style={styles.root}>
<ScrollView ref={scrollRef} style={styles.container} contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled">
{/* Header row */}
<View style={[styles.headerCard, { borderLeftColor: color }]}>
@ -336,7 +350,7 @@ export default function SessionDetailScreen({ route, navigation }: Props) {
{/* Message input — only show for active sessions */}
{!isDone && (
<View style={styles.messageBar}>
<View style={[styles.messageBar, { paddingBottom: keyboardHeight > 0 ? keyboardHeight - (Platform.OS === "ios" ? 34 : 0) : 10 }]}>
<TextInput
style={styles.messageInput}
placeholder="Send message to agent..."
@ -347,9 +361,6 @@ export default function SessionDetailScreen({ route, navigation }: Props) {
returnKeyType="send"
onSubmitEditing={handleSend}
blurOnSubmit={false}
onFocus={() => {
setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 300);
}}
/>
<TouchableOpacity
style={[styles.sendButton, (!message.trim() || sending) && styles.sendButtonDisabled]}
@ -364,7 +375,7 @@ export default function SessionDetailScreen({ route, navigation }: Props) {
</TouchableOpacity>
</View>
)}
</KeyboardAvoidingView>
</View>
);
}

View File

@ -132,8 +132,7 @@ export default function SettingsScreen({ navigation }: Props) {
return (
<KeyboardAvoidingView
style={styles.root}
behavior="padding"
keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 140}
behavior={Platform.OS === "ios" ? "padding" : undefined}
>
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<View style={styles.section}>

View File

@ -52,8 +52,7 @@ export default function SpawnSessionScreen({ navigation }: Props) {
return (
<KeyboardAvoidingView
style={styles.root}
behavior="padding"
keyboardVerticalOffset={Platform.OS === "ios" ? 88 : 140}
behavior={Platform.OS === "ios" ? "padding" : undefined}
>
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
<View style={styles.section}>