Files
paseo/packages/app/src/components/voice-note-recording-overlay.tsx
T
Mohamed BoudraandClaude 98c24f22a0 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>
2025-12-03 22:25:30 +07:00

130 lines
3.8 KiB
TypeScript

import { View, Text, Pressable, ActivityIndicator } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { X, ArrowUp, RefreshCcw } from "lucide-react-native";
import { VolumeMeter } from "./volume-meter";
import { FOOTER_HEIGHT } from "@/constants/layout";
import type { DictationStatus } from "@/hooks/use-dictation";
interface VoiceNoteRecordingOverlayProps {
volume: number;
duration: number;
onCancel: () => void;
onSend: () => void;
isTranscribing?: boolean;
status?: DictationStatus;
onRetry?: () => void;
onDiscardFailed?: () => void;
}
function formatDuration(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
}
export function VoiceNoteRecordingOverlay({
volume,
duration,
onCancel,
onSend,
isTranscribing = false,
status = "idle",
onRetry,
onDiscardFailed,
}: VoiceNoteRecordingOverlayProps) {
const { theme } = useUnistyles();
const isRetrying = status === "retrying";
const isFailed = status === "failed";
const primaryDisabled = isTranscribing || isRetrying;
const handlePrimary = isFailed ? onRetry ?? onSend : onSend;
const handleCancel = isFailed && onDiscardFailed ? onDiscardFailed : onCancel;
return (
<View style={[styles.container, { backgroundColor: theme.colors.palette.blue[600] }]}>
{/* Cancel button */}
<Pressable
onPress={handleCancel}
disabled={isTranscribing}
style={[styles.cancelButton, isTranscribing && styles.buttonDisabled]}
>
<X size={24} color={theme.colors.palette.white} strokeWidth={2.5} />
</Pressable>
{/* Center: Volume meter and timer */}
<View style={styles.centerContainer}>
<VolumeMeter
volume={volume}
isMuted={false}
isDetecting={true}
isSpeaking={false}
orientation="horizontal"
/>
<Text style={[styles.timerText, { color: theme.colors.palette.white }]}>
{formatDuration(duration)}
</Text>
</View>
{/* Send button */}
<Pressable
onPress={handlePrimary}
disabled={primaryDisabled}
style={[styles.sendButton, { backgroundColor: theme.colors.palette.white }, primaryDisabled && styles.buttonDisabled]}
>
{isRetrying ? (
<ActivityIndicator size="small" color={theme.colors.palette.blue[600]} />
) : isTranscribing ? (
<ActivityIndicator size="small" color={theme.colors.palette.blue[600]} />
) : isFailed ? (
<RefreshCcw size={24} color={theme.colors.palette.blue[600]} strokeWidth={2.5} />
) : (
<ArrowUp size={24} color={theme.colors.palette.blue[600]} strokeWidth={2.5} />
)}
</Pressable>
</View>
);
}
const BUTTON_SIZE = 56;
const VERTICAL_PADDING = (FOOTER_HEIGHT - BUTTON_SIZE) / 2;
const styles = StyleSheet.create((theme) => ({
container: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: theme.spacing[4],
paddingVertical: VERTICAL_PADDING,
height: FOOTER_HEIGHT,
},
cancelButton: {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: theme.borderRadius.full,
backgroundColor: "rgba(0, 0, 0, 0.15)",
alignItems: "center",
justifyContent: "center",
},
centerContainer: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: theme.spacing[4],
},
timerText: {
fontSize: theme.fontSize.xl,
fontWeight: theme.fontWeight.semibold,
fontVariant: ["tabular-nums"],
},
sendButton: {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
borderRadius: theme.borderRadius.full,
alignItems: "center",
justifyContent: "center",
},
buttonDisabled: {
opacity: 0.5,
},
}));