refactor: centralize global footer and simplify agent screen input handling
```
This commit is contained in:
Mohamed Boudra
2025-10-23 12:35:28 +02:00
parent 027ae59ae7
commit 10189514dd
6 changed files with 43 additions and 43 deletions
+21 -17
View File
@@ -7,6 +7,7 @@ import { SessionProvider } from "@/contexts/session-context";
import { RealtimeProvider } from "@/contexts/realtime-context"; import { RealtimeProvider } from "@/contexts/realtime-context";
import { useSettings } from "@/hooks/use-settings"; import { useSettings } from "@/hooks/use-settings";
import { View, ActivityIndicator } from "react-native"; import { View, ActivityIndicator } from "react-native";
import { GlobalFooter } from "@/components/global-footer";
function ProvidersWrapper({ children }: { children: React.ReactNode }) { function ProvidersWrapper({ children }: { children: React.ReactNode }) {
const { settings, isLoading } = useSettings(); const { settings, isLoading } = useSettings();
@@ -40,23 +41,26 @@ export default function RootLayout() {
<KeyboardProvider> <KeyboardProvider>
<BottomSheetModalProvider> <BottomSheetModalProvider>
<ProvidersWrapper> <ProvidersWrapper>
<Stack <View style={{ flex: 1 }}>
screenOptions={{ <Stack
headerShown: false, screenOptions={{
animation: "slide_from_right", headerShown: false,
animationDuration: 250, animation: "slide_from_right",
gestureEnabled: true, animationDuration: 250,
gestureDirection: "horizontal", gestureEnabled: true,
fullScreenGestureEnabled: true, gestureDirection: "horizontal",
animationMatchesGesture: true, fullScreenGestureEnabled: true,
}} animationMatchesGesture: true,
> }}
<Stack.Screen name="index" /> >
<Stack.Screen name="orchestrator" /> <Stack.Screen name="index" />
<Stack.Screen name="agent/[id]" /> <Stack.Screen name="orchestrator" />
<Stack.Screen name="settings" /> <Stack.Screen name="agent/[id]" />
<Stack.Screen name="audio-test" /> <Stack.Screen name="settings" />
</Stack> <Stack.Screen name="audio-test" />
</Stack>
<GlobalFooter />
</View>
</ProvidersWrapper> </ProvidersWrapper>
</BottomSheetModalProvider> </BottomSheetModalProvider>
</KeyboardProvider> </KeyboardProvider>
+10 -3
View File
@@ -6,7 +6,7 @@ import ReanimatedAnimated, { useAnimatedStyle } 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";
import { GlobalFooter } from "@/components/global-footer"; import { AgentInputArea } from "@/components/agent-input-area";
import { useSession } from "@/contexts/session-context"; import { useSession } from "@/contexts/session-context";
export default function AgentScreen() { export default function AgentScreen() {
@@ -60,8 +60,10 @@ export default function AgentScreen() {
} }
/> />
{/* Footer */} {/* Agent Input - will hide itself when realtime is active */}
<GlobalFooter agentId={id} /> <View style={[styles.inputContainer, { paddingBottom: insets.bottom }]}>
<AgentInputArea agentId={id!} />
</View>
</ReanimatedAnimated.View> </ReanimatedAnimated.View>
</View> </View>
); );
@@ -75,6 +77,11 @@ const styles = StyleSheet.create((theme) => ({
content: { content: {
flex: 1, flex: 1,
}, },
inputContainer: {
backgroundColor: theme.colors.background,
borderTopWidth: theme.borderWidth[1],
borderTopColor: theme.colors.border,
},
errorContainer: { errorContainer: {
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
-4
View File
@@ -7,7 +7,6 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { HomeHeader } from "@/components/headers/home-header"; import { HomeHeader } from "@/components/headers/home-header";
import { EmptyState } from "@/components/empty-state"; import { EmptyState } from "@/components/empty-state";
import { AgentList } from "@/components/agent-list"; import { AgentList } from "@/components/agent-list";
import { GlobalFooter } from "@/components/global-footer";
import { CreateAgentModal } from "@/components/create-agent-modal"; import { CreateAgentModal } from "@/components/create-agent-modal";
import { useSession } from "@/contexts/session-context"; import { useSession } from "@/contexts/session-context";
@@ -46,9 +45,6 @@ export default function HomeScreen() {
) : ( ) : (
<EmptyState onCreateAgent={handleCreateAgent} /> <EmptyState onCreateAgent={handleCreateAgent} />
)} )}
{/* Footer */}
<GlobalFooter />
</ReanimatedAnimated.View> </ReanimatedAnimated.View>
{/* Create Agent Modal */} {/* Create Agent Modal */}
-4
View File
@@ -6,7 +6,6 @@ import ReanimatedAnimated, { useAnimatedStyle } 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 { OrchestratorMessagesView } from "@/components/orchestrator-messages-view"; import { OrchestratorMessagesView } from "@/components/orchestrator-messages-view";
import { GlobalFooter } from "@/components/global-footer";
import { useSession } from "@/contexts/session-context"; import { useSession } from "@/contexts/session-context";
import type { ScrollView } from "react-native"; import type { ScrollView } from "react-native";
import type { Artifact } from "@/components/artifact-drawer"; import type { Artifact } from "@/components/artifact-drawer";
@@ -47,9 +46,6 @@ export default function OrchestratorScreen() {
currentAssistantMessage={currentAssistantMessage} currentAssistantMessage={currentAssistantMessage}
onArtifactClick={handleArtifactClick} onArtifactClick={handleArtifactClick}
/> />
{/* Footer */}
<GlobalFooter />
</ReanimatedAnimated.View> </ReanimatedAnimated.View>
</View> </View>
); );
@@ -14,7 +14,7 @@ interface AgentInputAreaProps {
export function AgentInputArea({ agentId }: AgentInputAreaProps) { export function AgentInputArea({ agentId }: AgentInputAreaProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const { agents, ws, sendAgentMessage, setAgentMode } = useSession(); const { agents, ws, sendAgentMessage, setAgentMode } = useSession();
const { startRealtime } = useRealtime(); const { isRealtimeMode, startRealtime } = useRealtime();
const audioRecorder = useAudioRecorder(); const audioRecorder = useAudioRecorder();
const [userInput, setUserInput] = useState(""); const [userInput, setUserInput] = useState("");
@@ -24,6 +24,11 @@ export function AgentInputArea({ agentId }: AgentInputAreaProps) {
const agent = agents.get(agentId); const agent = agents.get(agentId);
// Hide when realtime is active - global footer takes over
if (isRealtimeMode) {
return null;
}
async function handleSendMessage() { async function handleSendMessage() {
if (!userInput.trim() || !ws.isConnected) return; if (!userInput.trim() || !ws.isConnected) return;
+6 -14
View File
@@ -1,21 +1,17 @@
import { View, Pressable } from "react-native"; import { View, Pressable } from "react-native";
import { usePathname } from "expo-router"; import { usePathname, useLocalSearchParams } from "expo-router";
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 { AudioLines } from "lucide-react-native"; import { AudioLines } from "lucide-react-native";
import { useRealtime } from "@/contexts/realtime-context"; import { useRealtime } from "@/contexts/realtime-context";
import { useSession } from "@/contexts/session-context"; import { useSession } from "@/contexts/session-context";
import { RealtimeControls } from "./realtime-controls"; import { RealtimeControls } from "./realtime-controls";
import { AgentInputArea } from "./agent-input-area";
interface GlobalFooterProps { export function GlobalFooter() {
agentId?: string;
}
export function GlobalFooter({ agentId }: GlobalFooterProps) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const pathname = usePathname(); const pathname = usePathname();
const params = useLocalSearchParams();
const { isRealtimeMode, startRealtime } = useRealtime(); const { isRealtimeMode, startRealtime } = useRealtime();
const { ws } = useSession(); const { ws } = useSession();
@@ -33,13 +29,9 @@ export function GlobalFooter({ agentId }: GlobalFooterProps) {
); );
} }
// If on agent screen, show full input area // If on agent screen and realtime is off, hide footer (agent has its own controls)
if (isAgentScreen && agentId) { if (isAgentScreen) {
return ( return null;
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
<AgentInputArea agentId={agentId} />
</View>
);
} }
// For home and orchestrator screens, show centered realtime button // For home and orchestrator screens, show centered realtime button