Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { StatusBar } from "expo-status-bar"
import { useColorScheme, View, ActivityIndicator, AppState } from "react-native"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { GestureHandlerRootView } from "react-native-gesture-handler"
import { KeyboardProvider } from "react-native-keyboard-controller"
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"
import { I18nextProvider, useTranslation } from "react-i18next"
import i18n from "../src/lib/i18n/config"
Expand Down Expand Up @@ -171,6 +172,7 @@ function RootLayout() {
<ErrorBoundary>
<I18nextProvider i18n={i18n}>
<GestureHandlerRootView style={{ flex: 1 }}>
<KeyboardProvider statusBarTranslucent navigationBarTranslucent>
<BottomSheetModalProvider>
<QueryClientProvider client={queryClient}>
<AuthGate>
Expand Down Expand Up @@ -212,6 +214,7 @@ function RootLayout() {
</AuthGate>
</QueryClientProvider>
</BottomSheetModalProvider>
</KeyboardProvider>
</GestureHandlerRootView>
{/* Telemetry consent modal — shown once on first launch */}
<TelemetryConsentModal
Expand Down
25 changes: 8 additions & 17 deletions app/session/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
TouchableOpacity,
StyleSheet,
useColorScheme,
KeyboardAvoidingView,
Platform,
ActivityIndicator,
Alert,
} from "react-native"
import { KeyboardStickyView } from "react-native-keyboard-controller"
import { useLocalSearchParams, Stack, useRouter, useFocusEffect } from "expo-router"
import { Ionicons } from "@expo/vector-icons"
import { useSafeAreaInsets } from "react-native-safe-area-context"
Expand Down Expand Up @@ -591,22 +591,8 @@ export default function SessionScreen() {
}}
/>

<KeyboardAvoidingView
<View
style={[s.container, isDark && s.containerDark]}
// Both platforms use "padding" so the composer/toolbar is pushed up
// above the keyboard via JS-measured keyboard height.
//
// Android previously relied on the native android:windowSoftInputMode
// (adjustResize, see AndroidManifest.xml) with behavior={undefined}
// to let the OS resize the window (see #70/#53). Since adopting
// Expo's mandatory edge-to-edge display, Android no longer resizes
// the window when the keyboard opens — the system assumes insets are
// handled dynamically — so adjustResize became a no-op and the
// bottom toolbar + input were left completely hidden behind the
// keyboard (#147). "padding" restores avoidance without depending
// on native resize.
behavior="padding"
keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 0}
>
{/* Session info pulldown */}
<SessionInfo
Expand Down Expand Up @@ -740,6 +726,10 @@ export default function SessionScreen() {
<SlashPopover query={slashQuery} commands={allCommands} isDark={isDark} onSelect={handleSlashSelect} />
)}

{/* Composer sticks above the keyboard (edge-to-edge safe).
offset = closed-keyboard inset so the bar rests on the
gesture nav pill instead of overlapping it. */}
<KeyboardStickyView offset={{ closed: -insets.bottom, opened: 0 }}>
{/* Agent/model toolbar */}
<View style={[s.toolbar, isDark && s.toolbarDark]}>
<TouchableOpacity
Expand Down Expand Up @@ -838,7 +828,8 @@ export default function SessionScreen() {
)}
</View>
</View>
</KeyboardAvoidingView>
</KeyboardStickyView>
</View>

{/* Model picker bottom sheet */}
<ModelPicker
Expand Down
19 changes: 17 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"react-i18next": "^17.0.10",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "^1.22.4",
"react-native-marked": "8.0.0",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
Expand Down
14 changes: 13 additions & 1 deletion src/components/chat/MessageBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { memo } from "react"
import { View, Text, Image, StyleSheet, ScrollView, TouchableOpacity, Dimensions } from "react-native"
import { View, Text, Image, StyleSheet, ScrollView, TouchableOpacity, Dimensions, ActivityIndicator } from "react-native"
import { Ionicons } from "@expo/vector-icons"
import { Markdown } from "../markdown"
import { ToolCallCard } from "./ToolCallCard"
Expand Down Expand Up @@ -96,6 +96,14 @@ export const MessageBubble = memo(
</View>
))}

{/* Typing placeholder — assistant message arrived before any part */}
{!isUser && text.length === 0 && reasoning.length === 0 && toolParts.length === 0 && fileParts.length === 0 && (
<View style={s.typingRow}>
<ActivityIndicator size="small" color="#8b5cf6" />
<Text style={[s.typingText, isDark && s.typingTextDark]}>Typing…</Text>
</View>
)}

{/* Tool calls */}
{toolParts.map((tool) => (
<ToolCallCard key={tool.id} tool={tool} isDark={isDark} />
Expand Down Expand Up @@ -170,4 +178,8 @@ const s = StyleSheet.create({
},
imageLabel: { fontSize: 10, color: "#666666", marginTop: 2, maxWidth: 200 },
imageLabelDark: { color: "#888888" },

typingRow: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 4 },
typingText: { fontSize: 14, color: "#8b5cf6", fontStyle: "italic" },
typingTextDark: { color: "#a78bfa" },
})