fix(stt): graceful degradation for Expo Go
Handle missing native module @jamsch/expo-speech-recognition gracefully. In Expo Go the native module is not available, which was causing the entire _layout.tsx to fail to export, breaking tab navigation. - Use dynamic require() with try/catch instead of static import - Initialize ExpoSpeechRecognitionModule and useSpeechRecognitionEvent as no-ops - Check module availability before calling any native methods - isAvailable state properly reflects module presence Tab navigation now works in Expo Go (with STT disabled). Full STT functionality requires a development build.
This commit is contained in:
parent
76d93abf1e
commit
f2803ca5db
@ -4,6 +4,10 @@
|
|||||||
* Wraps @jamsch/expo-speech-recognition for easy use in components.
|
* Wraps @jamsch/expo-speech-recognition for easy use in components.
|
||||||
* Provides start/stop controls, recognized text, and status states.
|
* Provides start/stop controls, recognized text, and status states.
|
||||||
*
|
*
|
||||||
|
* NOTE: Gracefully handles missing native module (Expo Go)
|
||||||
|
* - In Expo Go: isAvailable = false, all methods are no-ops
|
||||||
|
* - In Dev Build: Full functionality
|
||||||
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const { startListening, stopListening, isListening, recognizedText, error } = useSpeechRecognition();
|
* const { startListening, stopListening, isListening, recognizedText, error } = useSpeechRecognition();
|
||||||
@ -19,12 +23,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import {
|
|
||||||
ExpoSpeechRecognitionModule,
|
|
||||||
useSpeechRecognitionEvent,
|
|
||||||
} from '@jamsch/expo-speech-recognition';
|
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
|
// Try to import the native module - may fail in Expo Go
|
||||||
|
let ExpoSpeechRecognitionModule: any = null;
|
||||||
|
let useSpeechRecognitionEvent: any = () => {}; // no-op by default
|
||||||
|
|
||||||
|
try {
|
||||||
|
const speechRecognition = require('@jamsch/expo-speech-recognition');
|
||||||
|
ExpoSpeechRecognitionModule = speechRecognition.ExpoSpeechRecognitionModule;
|
||||||
|
useSpeechRecognitionEvent = speechRecognition.useSpeechRecognitionEvent;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[SpeechRecognition] Native module not available (Expo Go?). Speech recognition disabled.');
|
||||||
|
}
|
||||||
|
|
||||||
export interface UseSpeechRecognitionOptions {
|
export interface UseSpeechRecognitionOptions {
|
||||||
/** Language for recognition (default: 'en-US') */
|
/** Language for recognition (default: 'en-US') */
|
||||||
lang?: string;
|
lang?: string;
|
||||||
@ -83,7 +95,7 @@ export function useSpeechRecognition(
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const [isListening, setIsListening] = useState(false);
|
const [isListening, setIsListening] = useState(false);
|
||||||
const [isAvailable, setIsAvailable] = useState(true);
|
const [isAvailable, setIsAvailable] = useState(!!ExpoSpeechRecognitionModule);
|
||||||
const [recognizedText, setRecognizedText] = useState('');
|
const [recognizedText, setRecognizedText] = useState('');
|
||||||
const [partialTranscript, setPartialTranscript] = useState('');
|
const [partialTranscript, setPartialTranscript] = useState('');
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -95,6 +107,11 @@ export function useSpeechRecognition(
|
|||||||
|
|
||||||
// Check availability on mount
|
// Check availability on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!ExpoSpeechRecognitionModule) {
|
||||||
|
setIsAvailable(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const checkAvailability = async () => {
|
const checkAvailability = async () => {
|
||||||
try {
|
try {
|
||||||
// Check if we can get permissions (indirect availability check)
|
// Check if we can get permissions (indirect availability check)
|
||||||
@ -131,7 +148,7 @@ export function useSpeechRecognition(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Event: Result available
|
// Event: Result available
|
||||||
useSpeechRecognitionEvent('result', (event) => {
|
useSpeechRecognitionEvent('result', (event: any) => {
|
||||||
const results = event.results;
|
const results = event.results;
|
||||||
if (results && results.length > 0) {
|
if (results && results.length > 0) {
|
||||||
const result = results[results.length - 1];
|
const result = results[results.length - 1];
|
||||||
@ -159,7 +176,7 @@ export function useSpeechRecognition(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Event: Error occurred
|
// Event: Error occurred
|
||||||
useSpeechRecognitionEvent('error', (event) => {
|
useSpeechRecognitionEvent('error', (event: any) => {
|
||||||
const errorMessage = event.message || event.error || 'Speech recognition error';
|
const errorMessage = event.message || event.error || 'Speech recognition error';
|
||||||
console.error('[SpeechRecognition] Error:', errorMessage);
|
console.error('[SpeechRecognition] Error:', errorMessage);
|
||||||
|
|
||||||
@ -178,6 +195,11 @@ export function useSpeechRecognition(
|
|||||||
* @returns true if started successfully, false otherwise
|
* @returns true if started successfully, false otherwise
|
||||||
*/
|
*/
|
||||||
const startListening = useCallback(async (): Promise<boolean> => {
|
const startListening = useCallback(async (): Promise<boolean> => {
|
||||||
|
if (!ExpoSpeechRecognitionModule) {
|
||||||
|
console.warn('[SpeechRecognition] Cannot start - native module not available');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isListening || isStartingRef.current) {
|
if (isListening || isStartingRef.current) {
|
||||||
console.log('[SpeechRecognition] Already listening or starting');
|
console.log('[SpeechRecognition] Already listening or starting');
|
||||||
return false;
|
return false;
|
||||||
@ -239,6 +261,8 @@ export function useSpeechRecognition(
|
|||||||
* Stop listening and process final result
|
* Stop listening and process final result
|
||||||
*/
|
*/
|
||||||
const stopListening = useCallback(() => {
|
const stopListening = useCallback(() => {
|
||||||
|
if (!ExpoSpeechRecognitionModule) return;
|
||||||
|
|
||||||
if (!isListening && !isStartingRef.current) {
|
if (!isListening && !isStartingRef.current) {
|
||||||
console.log('[SpeechRecognition] Not listening, nothing to stop');
|
console.log('[SpeechRecognition] Not listening, nothing to stop');
|
||||||
return;
|
return;
|
||||||
@ -256,6 +280,8 @@ export function useSpeechRecognition(
|
|||||||
* Abort listening without processing
|
* Abort listening without processing
|
||||||
*/
|
*/
|
||||||
const abortListening = useCallback(() => {
|
const abortListening = useCallback(() => {
|
||||||
|
if (!ExpoSpeechRecognitionModule) return;
|
||||||
|
|
||||||
if (!isListening && !isStartingRef.current) {
|
if (!isListening && !isStartingRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user