refactor: simplify footer architecture by removing global footer pattern

Replace portal/registry pattern with direct rendering where each screen
owns its footer. This eliminates FooterControlsContext indirection and
makes the code follow standard React composition patterns.

- Remove GlobalFooter and FooterControlsProvider from _layout.tsx
- Add HomeFooter component rendered directly in home screen
- Render AgentInputArea directly in agent screen (not via registration)
- Add keyboard animation and safe area handling to AgentInputArea
- Move FOOTER_HEIGHT constant to shared constants/layout.ts
- Delete footer-controls-context.tsx and global-footer.tsx

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-03 22:25:30 +07:00
co-authored by Claude
parent 5cbeb97391
commit 98c24f22a0
9 changed files with 54 additions and 133 deletions
@@ -20,9 +20,11 @@ import Animated, {
FadeIn,
FadeOut,
} from "react-native-reanimated";
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useRealtime } from "@/contexts/realtime-context";
import { useDictation } from "@/hooks/use-dictation";
import { FOOTER_HEIGHT } from "@/contexts/footer-controls-context";
import { FOOTER_HEIGHT } from "@/constants/layout";
import { VoiceNoteRecordingOverlay } from "./voice-note-recording-overlay";
import { DictationStatusNotice, type DictationToastVariant } from "./dictation-status-notice";
import { generateMessageId } from "@/types/stream";
@@ -85,6 +87,8 @@ type DictationToastConfig = {
export function AgentInputArea({ agentId, serverId }: AgentInputAreaProps) {
const { theme } = useUnistyles();
const insets = useSafeAreaInsets();
const { height: keyboardHeight } = useReanimatedKeyboardAnimation();
const ws = useSessionStore((state) => state.sessions[serverId]?.ws);
@@ -825,6 +829,15 @@ export function AgentInputArea({ agentId, serverId }: AgentInputAreaProps) {
};
});
const keyboardAnimatedStyle = useAnimatedStyle(() => {
"worklet";
const absoluteHeight = Math.abs(keyboardHeight.value);
const shift = Math.max(0, absoluteHeight - insets.bottom);
return {
transform: [{ translateY: -shift }],
};
});
async function handleRealtimePress() {
try {
if (isRealtimeMode) {
@@ -923,7 +936,7 @@ export function AgentInputArea({ agentId, serverId }: AgentInputAreaProps) {
);
return (
<View style={styles.container}>
<Animated.View style={[styles.container, { paddingBottom: insets.bottom }, keyboardAnimatedStyle]}>
{/* Border separator */}
<View style={styles.borderSeparator} />
@@ -1104,7 +1117,7 @@ export function AgentInputArea({ agentId, serverId }: AgentInputAreaProps) {
</View>
</View>
) : null}
</View>
</Animated.View>
);
}