import { useState, useEffect } from 'react' import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native' import { COLORS } from '../constants/Config' // expo-speech-recognition 선택적 임포트 let ExpoSpeechRecognitionModule: any = null let useSpeechRecognitionEvent: any = null try { const mod = require('@jamsch/expo-speech-recognition') ExpoSpeechRecognitionModule = mod.ExpoSpeechRecognitionModule useSpeechRecognitionEvent = mod.useSpeechRecognitionEvent } catch { // 패키지 미설치 시 폴백 } interface Props { onTranscript: (text: string) => void size?: 'small' | 'normal' } export function VoiceInput({ onTranscript, size = 'normal' }: Props) { const [isListening, setListening] = useState(false) const [interim, setInterim] = useState('') const [hasPermission, setPermission] = useState(null) // 음성 인식 이벤트 (패키지 있을 때만) if (useSpeechRecognitionEvent) { useSpeechRecognitionEvent('result', (e: any) => { const t = e.results?.[0]?.transcript || '' if (e.isFinal) { onTranscript(t) setInterim('') setListening(false) } else { setInterim(t) } }) useSpeechRecognitionEvent('error', () => { setListening(false); setInterim('') }) useSpeechRecognitionEvent('end', () => { setListening(false) }) } async function toggleListen() { if (!ExpoSpeechRecognitionModule) { // 폴백: 텍스트 입력 모드 안내 onTranscript('') return } if (isListening) { ExpoSpeechRecognitionModule.stop() setListening(false) } else { try { const granted = await ExpoSpeechRecognitionModule.requestPermissionsAsync() if (!granted.granted) { setPermission(false); return } setPermission(true) await ExpoSpeechRecognitionModule.start({ lang: 'ko-KR', interimResults: true, continuous: false, requiresOnDeviceRecognition: false, }) setListening(true) } catch (e) { setListening(false) } } } const btnSize = size === 'small' ? 36 : 44 const iconSize = size === 'small' ? 18 : 22 return ( {isListening ? : 🎤 } {interim ? ( {interim} ) : null} {hasPermission === false ? ( 마이크 권한 필요 ) : null} {!ExpoSpeechRecognitionModule ? ( 음성인식 미지원 ) : null} ) } const S = StyleSheet.create({ wrap: { flexDirection: 'row', alignItems: 'center', gap: 6 }, btn: { backgroundColor: COLORS.gnbBg, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 3, elevation: 2 }, btnActive: { backgroundColor: '#dc2626' }, interimBox: { backgroundColor: '#f0f4ff', borderRadius: 8, paddingHorizontal: 8, paddingVertical: 3, maxWidth: 160 }, interimText: { fontSize: 12, color: COLORS.gnbBg }, errText: { fontSize: 10, color: '#94a3b8' }, })