diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 4959cec..faaa4be 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -104,6 +104,8 @@ const ChatScreen: React.FC = () => { const [showCameraUpload, setShowCameraUpload] = useState(false); const [gpsEnabled, setGpsEnabled] = useState(false); const [wakeWordActive, setWakeWordActive] = useState(false); + // Genauer State (off/armed/conversing) fuer UI-Feedback am Button + const [wakeWordState, setWakeWordState] = useState<'off' | 'armed' | 'conversing'>('off'); const [fullscreenImage, setFullscreenImage] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [searchVisible, setSearchVisible] = useState(false); @@ -154,6 +156,11 @@ const ChatScreen: React.FC = () => { // Wake Word: einmalig laden + Porcupine vorbereiten (wenn Access Key gesetzt) useEffect(() => { wakeWordService.loadFromStorage().catch(() => {}); + const unsub = wakeWordService.onStateChange((s) => { + setWakeWordState(s); + setWakeWordActive(s !== 'off'); + }); + return () => unsub(); }, []); // ttsCanPlayRef live aktuell halten — Closure in onMessage unten liest @@ -1009,7 +1016,10 @@ const ChatScreen: React.FC = () => { style={[styles.wakeWordBtn, wakeWordActive && styles.wakeWordBtnActive]} onPress={toggleWakeWord} > - {wakeWordActive ? '👂' : '🔇'} + + {wakeWordState === 'conversing' ? '🎙️' : + wakeWordState === 'armed' ? '👂' : '🔇'} + )} diff --git a/android/src/services/wakeword.ts b/android/src/services/wakeword.ts index c0029f8..bd5fa71 100644 --- a/android/src/services/wakeword.ts +++ b/android/src/services/wakeword.ts @@ -174,14 +174,14 @@ class WakeWordService { } } else { // Kein Porcupine init → User explicit informieren - console.warn('[WakeWord] Porcupine nicht initialisiert — Access Key fehlt? Fallback Direkt-Konversation'); + console.warn('[WakeWord] Porcupine nicht initialisiert — Access Key fehlt? Fallback Direkt-Aufnahme'); ToastAndroid.show( - 'Wake-Word nicht verfuegbar — Access Key in Settings setzen', + 'Wake-Word nicht aktiv — direkte Aufnahme startet (Mikro hoert mit)', ToastAndroid.LONG, ); } - // Fallback: direkt in die Konversation - console.log('[WakeWord] Konversation startet sofort (kein Wake-Word)'); + // Fallback: direkt in die Konversation (Mikro AKTIV, nicht passive) + console.log('[WakeWord] Direkt-Aufnahme startet (kein Wake-Word)'); this.setState('conversing'); setTimeout(() => { if (this.state === 'conversing') { @@ -203,6 +203,7 @@ class WakeWordService { /** Wake-Word getriggert: Porcupine pausieren, Konversation starten. */ private async onWakeDetected(): Promise { console.log('[WakeWord] Wake-Word "%s" erkannt!', this.keyword); + ToastAndroid.show(`Wake-Word "${this.keyword}" erkannt — sprich jetzt`, ToastAndroid.SHORT); if (this.porcupine) { try { await this.porcupine.stop(); } catch {} } @@ -225,6 +226,7 @@ class WakeWordService { try { await this.porcupine.start(); console.log('[WakeWord] Konversation zu Ende — zurueck zu armed'); + ToastAndroid.show(`Lausche wieder auf "${this.keyword}"`, ToastAndroid.SHORT); this.setState('armed'); return; } catch (err) { @@ -232,6 +234,7 @@ class WakeWordService { } } console.log('[WakeWord] Konversation zu Ende — Ohr aus'); + ToastAndroid.show('Mikro aus', ToastAndroid.SHORT); this.setState('off'); }