| .. | .. |
|---|
| 6 | 6 | TextInput, |
|---|
| 7 | 7 | View, |
|---|
| 8 | 8 | } from "react-native"; |
|---|
| 9 | +import * as Haptics from "expo-haptics"; |
|---|
| 9 | 10 | import { VoiceButton } from "./VoiceButton"; |
|---|
| 10 | 11 | |
|---|
| 11 | 12 | interface InputBarProps { |
|---|
| 12 | 13 | onSendText: (text: string) => void; |
|---|
| 13 | | - onSendVoice: (audioUri: string, durationMs: number) => void; |
|---|
| 14 | + onReplay: () => void; |
|---|
| 15 | + isTextMode: boolean; |
|---|
| 16 | + onToggleMode: () => void; |
|---|
| 14 | 17 | } |
|---|
| 15 | 18 | |
|---|
| 16 | | -export function InputBar({ onSendText, onSendVoice }: InputBarProps) { |
|---|
| 19 | +export function InputBar({ |
|---|
| 20 | + onSendText, |
|---|
| 21 | + onReplay, |
|---|
| 22 | + isTextMode, |
|---|
| 23 | + onToggleMode, |
|---|
| 24 | +}: InputBarProps) { |
|---|
| 17 | 25 | const [text, setText] = useState(""); |
|---|
| 18 | | - const [isVoiceMode, setIsVoiceMode] = useState(false); |
|---|
| 19 | 26 | const inputRef = useRef<TextInput>(null); |
|---|
| 20 | 27 | |
|---|
| 21 | 28 | const handleSend = useCallback(() => { |
|---|
| .. | .. |
|---|
| 25 | 32 | setText(""); |
|---|
| 26 | 33 | }, [text, onSendText]); |
|---|
| 27 | 34 | |
|---|
| 28 | | - const toggleMode = useCallback(() => { |
|---|
| 29 | | - setIsVoiceMode((prev) => { |
|---|
| 30 | | - if (prev) { |
|---|
| 31 | | - // Switching to text mode — focus input after mode switch |
|---|
| 32 | | - setTimeout(() => inputRef.current?.focus(), 100); |
|---|
| 33 | | - } else { |
|---|
| 34 | | - Keyboard.dismiss(); |
|---|
| 35 | | - } |
|---|
| 36 | | - return !prev; |
|---|
| 37 | | - }); |
|---|
| 38 | | - }, []); |
|---|
| 39 | | - |
|---|
| 40 | | - if (isVoiceMode) { |
|---|
| 35 | + if (!isTextMode) { |
|---|
| 36 | + // Voice mode: [Replay] [Talk] [Aa] |
|---|
| 41 | 37 | return ( |
|---|
| 42 | | - <View className="border-t border-pai-border bg-pai-bg"> |
|---|
| 43 | | - {/* Mode toggle */} |
|---|
| 38 | + <View |
|---|
| 39 | + style={{ |
|---|
| 40 | + flexDirection: "row", |
|---|
| 41 | + gap: 10, |
|---|
| 42 | + paddingHorizontal: 16, |
|---|
| 43 | + paddingVertical: 10, |
|---|
| 44 | + paddingBottom: 6, |
|---|
| 45 | + borderTopWidth: 1, |
|---|
| 46 | + borderTopColor: "#2E2E45", |
|---|
| 47 | + alignItems: "center", |
|---|
| 48 | + }} |
|---|
| 49 | + > |
|---|
| 50 | + {/* Replay last message */} |
|---|
| 44 | 51 | <Pressable |
|---|
| 45 | | - onPress={toggleMode} |
|---|
| 46 | | - className="absolute top-3 right-4 z-10 w-10 h-10 items-center justify-center" |
|---|
| 52 | + onPress={() => { |
|---|
| 53 | + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); |
|---|
| 54 | + onReplay(); |
|---|
| 55 | + }} |
|---|
| 47 | 56 | > |
|---|
| 48 | | - <Text className="text-2xl">⌨️</Text> |
|---|
| 57 | + <View |
|---|
| 58 | + style={{ |
|---|
| 59 | + width: 68, |
|---|
| 60 | + height: 68, |
|---|
| 61 | + borderRadius: 34, |
|---|
| 62 | + alignItems: "center", |
|---|
| 63 | + justifyContent: "center", |
|---|
| 64 | + backgroundColor: "#1A2E1A", |
|---|
| 65 | + borderWidth: 1.5, |
|---|
| 66 | + borderColor: "#3A6A3A", |
|---|
| 67 | + }} |
|---|
| 68 | + > |
|---|
| 69 | + <Text style={{ fontSize: 24 }}>▶</Text> |
|---|
| 70 | + <Text style={{ color: "#8ABF8A", fontSize: 10, marginTop: 1, fontWeight: "600" }}>Replay</Text> |
|---|
| 71 | + </View> |
|---|
| 49 | 72 | </Pressable> |
|---|
| 50 | 73 | |
|---|
| 51 | | - <VoiceButton onVoiceMessage={onSendVoice} /> |
|---|
| 74 | + {/* Talk button — center, biggest */} |
|---|
| 75 | + <View style={{ flex: 1, alignItems: "center" }}> |
|---|
| 76 | + <VoiceButton onTranscript={onSendText} /> |
|---|
| 77 | + </View> |
|---|
| 78 | + |
|---|
| 79 | + {/* Text mode toggle */} |
|---|
| 80 | + <Pressable |
|---|
| 81 | + onPress={() => { |
|---|
| 82 | + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); |
|---|
| 83 | + onToggleMode(); |
|---|
| 84 | + setTimeout(() => inputRef.current?.focus(), 150); |
|---|
| 85 | + }} |
|---|
| 86 | + > |
|---|
| 87 | + <View |
|---|
| 88 | + style={{ |
|---|
| 89 | + width: 68, |
|---|
| 90 | + height: 68, |
|---|
| 91 | + borderRadius: 34, |
|---|
| 92 | + alignItems: "center", |
|---|
| 93 | + justifyContent: "center", |
|---|
| 94 | + backgroundColor: "#1A1A3E", |
|---|
| 95 | + borderWidth: 1.5, |
|---|
| 96 | + borderColor: "#3A3A7A", |
|---|
| 97 | + }} |
|---|
| 98 | + > |
|---|
| 99 | + <Text style={{ fontSize: 22, color: "#9898D0", fontWeight: "700" }}>Aa</Text> |
|---|
| 100 | + </View> |
|---|
| 101 | + </Pressable> |
|---|
| 52 | 102 | </View> |
|---|
| 53 | 103 | ); |
|---|
| 54 | 104 | } |
|---|
| 55 | 105 | |
|---|
| 106 | + // Text mode: [Mic] [TextInput] [Send] |
|---|
| 56 | 107 | return ( |
|---|
| 57 | | - <View className="border-t border-pai-border bg-pai-bg px-3 py-2 flex-row items-end gap-2"> |
|---|
| 108 | + <View |
|---|
| 109 | + style={{ |
|---|
| 110 | + flexDirection: "row", |
|---|
| 111 | + gap: 8, |
|---|
| 112 | + paddingHorizontal: 12, |
|---|
| 113 | + paddingVertical: 8, |
|---|
| 114 | + borderTopWidth: 1, |
|---|
| 115 | + borderTopColor: "#2E2E45", |
|---|
| 116 | + alignItems: "flex-end", |
|---|
| 117 | + }} |
|---|
| 118 | + > |
|---|
| 58 | 119 | {/* Voice mode toggle */} |
|---|
| 59 | 120 | <Pressable |
|---|
| 60 | | - onPress={toggleMode} |
|---|
| 61 | | - className="w-10 h-10 items-center justify-center rounded-full bg-pai-bg-tertiary mb-0.5" |
|---|
| 121 | + onPress={() => { |
|---|
| 122 | + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); |
|---|
| 123 | + Keyboard.dismiss(); |
|---|
| 124 | + onToggleMode(); |
|---|
| 125 | + }} |
|---|
| 126 | + style={{ |
|---|
| 127 | + width: 40, |
|---|
| 128 | + height: 40, |
|---|
| 129 | + borderRadius: 20, |
|---|
| 130 | + alignItems: "center", |
|---|
| 131 | + justifyContent: "center", |
|---|
| 132 | + backgroundColor: "#1E1E2E", |
|---|
| 133 | + marginBottom: 2, |
|---|
| 134 | + }} |
|---|
| 62 | 135 | > |
|---|
| 63 | | - <Text className="text-xl">🎤</Text> |
|---|
| 136 | + <Text style={{ fontSize: 20 }}>🎤</Text> |
|---|
| 64 | 137 | </Pressable> |
|---|
| 65 | 138 | |
|---|
| 66 | 139 | {/* Text input */} |
|---|
| .. | .. |
|---|
| 75 | 148 | onSubmitEditing={handleSend} |
|---|
| 76 | 149 | returnKeyType="send" |
|---|
| 77 | 150 | blurOnSubmit |
|---|
| 78 | | - className="flex-1 bg-pai-bg-tertiary rounded-2xl px-4 py-2.5 text-pai-text text-base" |
|---|
| 79 | | - style={{ maxHeight: 120 }} |
|---|
| 151 | + style={{ |
|---|
| 152 | + flex: 1, |
|---|
| 153 | + backgroundColor: "#1E1E2E", |
|---|
| 154 | + borderRadius: 20, |
|---|
| 155 | + paddingHorizontal: 16, |
|---|
| 156 | + paddingVertical: 10, |
|---|
| 157 | + maxHeight: 120, |
|---|
| 158 | + color: "#E8E8F0", |
|---|
| 159 | + fontSize: 16, |
|---|
| 160 | + }} |
|---|
| 80 | 161 | /> |
|---|
| 81 | 162 | |
|---|
| 82 | 163 | {/* Send button */} |
|---|
| 83 | 164 | <Pressable |
|---|
| 84 | 165 | onPress={handleSend} |
|---|
| 85 | 166 | disabled={!text.trim()} |
|---|
| 86 | | - className={`w-10 h-10 rounded-full items-center justify-center mb-0.5 ${ |
|---|
| 87 | | - text.trim() ? "bg-pai-accent" : "bg-pai-bg-tertiary" |
|---|
| 88 | | - }`} |
|---|
| 167 | + style={{ |
|---|
| 168 | + width: 40, |
|---|
| 169 | + height: 40, |
|---|
| 170 | + borderRadius: 20, |
|---|
| 171 | + alignItems: "center", |
|---|
| 172 | + justifyContent: "center", |
|---|
| 173 | + marginBottom: 2, |
|---|
| 174 | + backgroundColor: text.trim() ? "#4A9EFF" : "#1E1E2E", |
|---|
| 175 | + }} |
|---|
| 89 | 176 | > |
|---|
| 90 | | - <Text className={`text-xl ${text.trim() ? "text-white" : "text-pai-text-muted"}`}> |
|---|
| 177 | + <Text |
|---|
| 178 | + style={{ |
|---|
| 179 | + fontSize: 18, |
|---|
| 180 | + fontWeight: "bold", |
|---|
| 181 | + color: text.trim() ? "#FFFFFF" : "#5A5A78", |
|---|
| 182 | + }} |
|---|
| 183 | + > |
|---|
| 91 | 184 | ↑ |
|---|
| 92 | 185 | </Text> |
|---|
| 93 | 186 | </Pressable> |
|---|