react-native-voice-ts 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +914 -83
- package/dist/components/MicIcon.d.ts +23 -0
- package/dist/components/MicIcon.d.ts.map +1 -0
- package/dist/components/MicIcon.js +28 -0
- package/dist/components/MicIcon.js.map +1 -0
- package/dist/components/VoiceMicrophone.d.ts +81 -0
- package/dist/components/VoiceMicrophone.d.ts.map +1 -0
- package/dist/components/VoiceMicrophone.js +151 -0
- package/dist/components/VoiceMicrophone.js.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useVoiceRecognition.d.ts +77 -0
- package/dist/hooks/useVoiceRecognition.d.ts.map +1 -0
- package/dist/hooks/useVoiceRecognition.js +129 -0
- package/dist/hooks/useVoiceRecognition.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/ios/Voice.xcodeproj/project.xcworkspace/xcuserdata/olumayowadaniel.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Voice.xcodeproj/project.xcworkspace/xcuserdata/rudie_shahinian.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/package.json +24 -6
- package/plugin/src/withVoice.ts +74 -0
- package/plugin/tsconfig.json +10 -0
- package/src/components/MicIcon.tsx +72 -0
- package/src/components/VoiceMicrophone.tsx +238 -0
- package/src/components/index.ts +4 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useVoiceRecognition.ts +217 -0
- package/src/images/mic.svg +16 -0
- package/src/index.ts +15 -0
- package/.nvmrc +0 -1
- package/.prettierrc +0 -5
- package/.releaserc +0 -15
- package/MIGRATION_SUMMARY.md +0 -510
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ A powerful, production-ready speech-to-text library for React Native application
|
|
|
15
15
|
[Features](#-features) •
|
|
16
16
|
[Installation](#-installation) •
|
|
17
17
|
[Quick Start](#-quick-start) •
|
|
18
|
+
[Component Usage](#-component--hook-usage) •
|
|
18
19
|
[API Reference](#-api-reference) •
|
|
19
20
|
[Examples](#-complete-examples) •
|
|
20
21
|
[Contributing](#-contributing)
|
|
@@ -23,7 +24,7 @@ A powerful, production-ready speech-to-text library for React Native application
|
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
### ✨ Features
|
|
27
28
|
|
|
28
29
|
### Core Capabilities
|
|
29
30
|
|
|
@@ -43,6 +44,9 @@ A powerful, production-ready speech-to-text library for React Native application
|
|
|
43
44
|
- 🎨 **Modern API** - Clean, intuitive API design
|
|
44
45
|
- 🛡️ **Error Handling** - Comprehensive error management
|
|
45
46
|
- 🔧 **Fully Customizable** - Extensive configuration options
|
|
47
|
+
- 🎪 **Ready-to-use Components** - VoiceMicrophone component & useVoiceRecognition hook
|
|
48
|
+
- 🔌 **Plug & Play** - Import and use instantly in any React Native app
|
|
49
|
+
- 🎯 **SVG Icons Included** - Beautiful Lucide-based mic icons (MicIcon, MicOffIcon)
|
|
46
50
|
|
|
47
51
|
### Platform Support
|
|
48
52
|
|
|
@@ -98,56 +102,168 @@ cd ios && pod install && cd ..
|
|
|
98
102
|
|
|
99
103
|
## 🚀 Quick Start
|
|
100
104
|
|
|
101
|
-
###
|
|
105
|
+
### Three Ways to Use
|
|
102
106
|
|
|
103
107
|
```typescript
|
|
104
|
-
|
|
105
|
-
import {
|
|
106
|
-
import Voice from '@react-native-voice/voice';
|
|
108
|
+
// 1. Ready-to-use Component (Easiest)
|
|
109
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const [recognizedText, setRecognizedText] = useState('');
|
|
111
|
+
// 2. Custom Hook (More control)
|
|
112
|
+
import { useVoiceRecognition } from 'react-native-voice-ts';
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Voice.onSpeechResults = (e) => {
|
|
115
|
-
setRecognizedText(e.value[0]);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Cleanup
|
|
119
|
-
return () => {
|
|
120
|
-
Voice.destroy().then(Voice.removeAllListeners);
|
|
121
|
-
};
|
|
122
|
-
}, []);
|
|
114
|
+
// 3. Core API (Advanced)
|
|
115
|
+
import Voice from 'react-native-voice-ts';
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
setIsRecording(true);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error(error);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
117
|
+
// 4. Import SVG Icons (Optional - for custom UI)
|
|
118
|
+
import { MicIcon, MicOffIcon } from 'react-native-voice-ts';
|
|
119
|
+
```
|
|
132
120
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
121
|
+
**Note:** To use SVG icons, install `react-native-svg`:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install react-native-svg
|
|
125
|
+
# or
|
|
126
|
+
yarn add react-native-svg
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### ⚡ Super Simple - Just Import and Use!
|
|
132
|
+
|
|
133
|
+
**Example 1: Voice Search (Minimal Code)**
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import React, { useState } from 'react';
|
|
137
|
+
import { View, TextInput, Button } from 'react-native';
|
|
138
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
139
|
+
|
|
140
|
+
export default function App() {
|
|
141
|
+
const [searchText, setSearchText] = useState('');
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<View style={{ padding: 20 }}>
|
|
145
|
+
<TextInput
|
|
146
|
+
value={searchText}
|
|
147
|
+
onChangeText={setSearchText}
|
|
148
|
+
placeholder="Type or speak..."
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<VoiceMicrophone onSpeechResult={setSearchText}>
|
|
152
|
+
{({ isRecording, start, stop }) => (
|
|
153
|
+
<Button
|
|
154
|
+
onPress={isRecording ? stop : start}
|
|
155
|
+
title={isRecording ? '⏹ Stop' : '🎤 Speak'}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</VoiceMicrophone>
|
|
159
|
+
</View>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Example 2: With Custom Styling & SVG Icons**
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import React, { useState } from 'react';
|
|
168
|
+
import { View, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
|
|
169
|
+
import { VoiceMicrophone, MicIcon, MicOffIcon } from 'react-native-voice-ts';
|
|
170
|
+
|
|
171
|
+
export default function App() {
|
|
172
|
+
const [text, setText] = useState('');
|
|
141
173
|
|
|
142
174
|
return (
|
|
143
175
|
<View style={styles.container}>
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
176
|
+
<TextInput
|
|
177
|
+
style={styles.input}
|
|
178
|
+
value={text}
|
|
179
|
+
onChangeText={setText}
|
|
180
|
+
placeholder="Search..."
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<VoiceMicrophone onSpeechResult={setText}>
|
|
184
|
+
{({ isRecording, start, stop }) => (
|
|
185
|
+
<TouchableOpacity
|
|
186
|
+
style={[styles.mic, isRecording && styles.recording]}
|
|
187
|
+
onPress={isRecording ? stop : start}
|
|
188
|
+
>
|
|
189
|
+
{isRecording ? (
|
|
190
|
+
<MicOffIcon size={24} color="#fff" />
|
|
191
|
+
) : (
|
|
192
|
+
<MicIcon size={24} color="#fff" />
|
|
193
|
+
)}
|
|
194
|
+
</TouchableOpacity>
|
|
195
|
+
)}
|
|
196
|
+
</VoiceMicrophone>
|
|
197
|
+
</View>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const styles = StyleSheet.create({
|
|
202
|
+
container: { flexDirection: 'row', padding: 20 },
|
|
203
|
+
input: { flex: 1, borderWidth: 1, padding: 10, marginRight: 10 },
|
|
204
|
+
mic: {
|
|
205
|
+
width: 50,
|
|
206
|
+
height: 50,
|
|
207
|
+
borderRadius: 25,
|
|
208
|
+
backgroundColor: '#007AFF',
|
|
209
|
+
justifyContent: 'center',
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
},
|
|
212
|
+
recording: { backgroundColor: '#FF3B30' },
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### 🎯 Using Hook (For More Control)
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import React from 'react';
|
|
222
|
+
import { View, Text, Button } from 'react-native';
|
|
223
|
+
import { useVoiceRecognition } from 'react-native-voice-ts';
|
|
224
|
+
|
|
225
|
+
export default function App() {
|
|
226
|
+
const { isRecording, results, start, stop } = useVoiceRecognition({
|
|
227
|
+
onResult: (text) => console.log('You said:', text),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<View style={{ padding: 20 }}>
|
|
232
|
+
<Text>{results[0] || 'Press to speak'}</Text>
|
|
233
|
+
<Button
|
|
234
|
+
onPress={isRecording ? stop : start}
|
|
235
|
+
title={isRecording ? 'Stop' : 'Start'}
|
|
236
|
+
/>
|
|
237
|
+
</View>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### 🔧 Advanced (Core API)
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import React, { useEffect, useState } from 'react';
|
|
248
|
+
import { View, Text, Button } from 'react-native';
|
|
249
|
+
import Voice from 'react-native-voice-ts';
|
|
250
|
+
|
|
251
|
+
export default function App() {
|
|
252
|
+
const [text, setText] = useState('');
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
Voice.onSpeechResults = (e) => setText(e.value[0]);
|
|
256
|
+
return () => Voice.destroy().then(Voice.removeAllListeners);
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<View style={{ padding: 20 }}>
|
|
261
|
+
<Text>{text}</Text>
|
|
262
|
+
<Button onPress={() => Voice.start('en-US')} title="Start" />
|
|
263
|
+
<Button onPress={() => Voice.stop()} title="Stop" />
|
|
264
|
+
</View>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
151
267
|
</Text>
|
|
152
268
|
</TouchableOpacity>
|
|
153
269
|
</View>
|
|
@@ -192,7 +308,95 @@ export default App;
|
|
|
192
308
|
|
|
193
309
|
## 📚 API Reference
|
|
194
310
|
|
|
195
|
-
###
|
|
311
|
+
### Components
|
|
312
|
+
|
|
313
|
+
#### VoiceMicrophone
|
|
314
|
+
|
|
315
|
+
A ready-to-use React component that handles all voice recognition logic.
|
|
316
|
+
|
|
317
|
+
**Props:**
|
|
318
|
+
|
|
319
|
+
| Prop | Type | Default | Description |
|
|
320
|
+
| ---------------------- | ------------------------- | --------- | ----------------------------------------------------- |
|
|
321
|
+
| `onSpeechResult` | `(text: string) => void` | - | Callback when final speech result is available |
|
|
322
|
+
| `onPartialResult` | `(text: string) => void` | - | Callback when partial (real-time) result is available |
|
|
323
|
+
| `onStart` | `() => void` | - | Callback when recording starts |
|
|
324
|
+
| `onStop` | `() => void` | - | Callback when recording stops |
|
|
325
|
+
| `onError` | `(error: string) => void` | - | Callback when an error occurs |
|
|
326
|
+
| `locale` | `string` | `'en-US'` | Language locale for recognition |
|
|
327
|
+
| `autoStart` | `boolean` | `false` | Auto-start recording on mount |
|
|
328
|
+
| `enablePartialResults` | `boolean` | `true` | Enable real-time partial results |
|
|
329
|
+
| `children` | `function` | - | Render prop function |
|
|
330
|
+
|
|
331
|
+
**Children Render Props:**
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
{
|
|
335
|
+
isRecording: boolean; // Whether recording is active
|
|
336
|
+
recognizedText: string; // Final recognized text
|
|
337
|
+
partialText: string; // Real-time partial text
|
|
338
|
+
start: () => Promise<void>; // Start recording
|
|
339
|
+
stop: () => Promise<void>; // Stop recording
|
|
340
|
+
cancel: () => Promise<void>; // Cancel recording
|
|
341
|
+
error: string | null; // Error message if any
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Example:**
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
<VoiceMicrophone
|
|
349
|
+
locale="en-US"
|
|
350
|
+
onSpeechResult={(text) => console.log(text)}
|
|
351
|
+
>
|
|
352
|
+
{({ isRecording, start, stop }) => (
|
|
353
|
+
<Button onPress={isRecording ? stop : start} />
|
|
354
|
+
)}
|
|
355
|
+
</VoiceMicrophone>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Hooks
|
|
359
|
+
|
|
360
|
+
#### useVoiceRecognition
|
|
361
|
+
|
|
362
|
+
A custom hook that provides voice recognition functionality.
|
|
363
|
+
|
|
364
|
+
**Options:**
|
|
365
|
+
|
|
366
|
+
| Option | Type | Default | Description |
|
|
367
|
+
| ---------------------- | ------------------------- | --------- | ---------------------- |
|
|
368
|
+
| `locale` | `string` | `'en-US'` | Language locale |
|
|
369
|
+
| `enablePartialResults` | `boolean` | `true` | Enable partial results |
|
|
370
|
+
| `onResult` | `(text: string) => void` | - | Result callback |
|
|
371
|
+
| `onError` | `(error: string) => void` | - | Error callback |
|
|
372
|
+
|
|
373
|
+
**Returns:**
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
{
|
|
377
|
+
isRecording: boolean;
|
|
378
|
+
results: string[];
|
|
379
|
+
partialResults: string[];
|
|
380
|
+
error: string | null;
|
|
381
|
+
start: () => Promise<void>;
|
|
382
|
+
stop: () => Promise<void>;
|
|
383
|
+
cancel: () => Promise<void>;
|
|
384
|
+
reset: () => void;
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Example:**
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const { isRecording, results, start, stop } = useVoiceRecognition({
|
|
392
|
+
locale: 'en-US',
|
|
393
|
+
onResult: (text) => setSearchQuery(text),
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
### Core API Methods
|
|
196
400
|
|
|
197
401
|
#### Core Methods
|
|
198
402
|
|
|
@@ -425,61 +629,688 @@ import type {
|
|
|
425
629
|
|
|
426
630
|
---
|
|
427
631
|
|
|
428
|
-
## 💡
|
|
632
|
+
## 💡 More Examples
|
|
633
|
+
|
|
634
|
+
### Example 1: Simple Voice Search
|
|
635
|
+
|
|
636
|
+
```tsx
|
|
637
|
+
import React, { useState } from 'react';
|
|
638
|
+
import { View, TextInput, Button } from 'react-native';
|
|
639
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
640
|
+
|
|
641
|
+
function VoiceSearch() {
|
|
642
|
+
const [query, setQuery] = useState('');
|
|
643
|
+
|
|
644
|
+
return (
|
|
645
|
+
<View>
|
|
646
|
+
<TextInput
|
|
647
|
+
value={query}
|
|
648
|
+
onChangeText={setQuery}
|
|
649
|
+
placeholder="Search..."
|
|
650
|
+
/>
|
|
651
|
+
<VoiceMicrophone onSpeechResult={setQuery}>
|
|
652
|
+
{({ isRecording, start, stop }) => (
|
|
653
|
+
<Button onPress={isRecording ? stop : start} title="🎤" />
|
|
654
|
+
)}
|
|
655
|
+
</VoiceMicrophone>
|
|
656
|
+
</View>
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Example 2: Real-time Transcription
|
|
662
|
+
|
|
663
|
+
```tsx
|
|
664
|
+
import React, { useState } from 'react';
|
|
665
|
+
import { View, Text, Button } from 'react-native';
|
|
666
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
667
|
+
|
|
668
|
+
function LiveTranscription() {
|
|
669
|
+
const [text, setText] = useState('');
|
|
670
|
+
|
|
671
|
+
return (
|
|
672
|
+
<View>
|
|
673
|
+
<VoiceMicrophone
|
|
674
|
+
enablePartialResults
|
|
675
|
+
onSpeechResult={setText}
|
|
676
|
+
onPartialResult={(live) => console.log('Live:', live)}
|
|
677
|
+
>
|
|
678
|
+
{({ isRecording, partialText, start, stop }) => (
|
|
679
|
+
<>
|
|
680
|
+
<Text>{isRecording ? partialText : text}</Text>
|
|
681
|
+
<Button
|
|
682
|
+
onPress={isRecording ? stop : start}
|
|
683
|
+
title={isRecording ? 'Stop' : 'Start'}
|
|
684
|
+
/>
|
|
685
|
+
</>
|
|
686
|
+
)}
|
|
687
|
+
</VoiceMicrophone>
|
|
688
|
+
</View>
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Example 3: Multi-Language
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
import React, { useState } from 'react';
|
|
697
|
+
import { View, Text, Button, Picker } from 'react-native';
|
|
698
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
699
|
+
|
|
700
|
+
function MultiLanguage() {
|
|
701
|
+
const [lang, setLang] = useState('en-US');
|
|
702
|
+
const [text, setText] = useState('');
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<View>
|
|
706
|
+
<Picker selectedValue={lang} onValueChange={setLang}>
|
|
707
|
+
<Picker.Item label="English" value="en-US" />
|
|
708
|
+
<Picker.Item label="Spanish" value="es-ES" />
|
|
709
|
+
<Picker.Item label="French" value="fr-FR" />
|
|
710
|
+
</Picker>
|
|
711
|
+
|
|
712
|
+
<Text>{text}</Text>
|
|
713
|
+
|
|
714
|
+
<VoiceMicrophone locale={lang} onSpeechResult={setText}>
|
|
715
|
+
{({ isRecording, start, stop }) => (
|
|
716
|
+
<Button onPress={isRecording ? stop : start} title="Speak" />
|
|
717
|
+
)}
|
|
718
|
+
</VoiceMicrophone>
|
|
719
|
+
</View>
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Example 4: Voice Form
|
|
725
|
+
|
|
726
|
+
```tsx
|
|
727
|
+
import React, { useState } from 'react';
|
|
728
|
+
import { View, TextInput, Button } from 'react-native';
|
|
729
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
730
|
+
|
|
731
|
+
function VoiceForm() {
|
|
732
|
+
const [name, setName] = useState('');
|
|
733
|
+
const [email, setEmail] = useState('');
|
|
734
|
+
const [active, setActive] = useState(null);
|
|
735
|
+
|
|
736
|
+
return (
|
|
737
|
+
<View>
|
|
738
|
+
<TextInput value={name} onChangeText={setName} placeholder="Name" />
|
|
739
|
+
<VoiceMicrophone onSpeechResult={setName}>
|
|
740
|
+
{({ isRecording, start, stop }) => (
|
|
741
|
+
<Button onPress={isRecording ? stop : start} title="🎤" />
|
|
742
|
+
)}
|
|
743
|
+
</VoiceMicrophone>
|
|
744
|
+
|
|
745
|
+
<TextInput value={email} onChangeText={setEmail} placeholder="Email" />
|
|
746
|
+
<VoiceMicrophone onSpeechResult={setEmail}>
|
|
747
|
+
{({ isRecording, start, stop }) => (
|
|
748
|
+
<Button onPress={isRecording ? stop : start} title="🎤" />
|
|
749
|
+
)}
|
|
750
|
+
</VoiceMicrophone>
|
|
751
|
+
</View>
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Example 5: Using the Hook
|
|
757
|
+
|
|
758
|
+
```tsx
|
|
759
|
+
import React from 'react';
|
|
760
|
+
import { View, Text, Button } from 'react-native';
|
|
761
|
+
import { useVoiceRecognition } from 'react-native-voice-ts';
|
|
762
|
+
|
|
763
|
+
function HookExample() {
|
|
764
|
+
const { isRecording, results, partialResults, start, stop, reset } =
|
|
765
|
+
useVoiceRecognition({
|
|
766
|
+
locale: 'en-US',
|
|
767
|
+
onResult: (text) => console.log(text),
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
return (
|
|
771
|
+
<View>
|
|
772
|
+
<Text>{isRecording ? partialResults[0] : results[0]}</Text>
|
|
773
|
+
<Button
|
|
774
|
+
onPress={isRecording ? stop : start}
|
|
775
|
+
title={isRecording ? 'Stop' : 'Start'}
|
|
776
|
+
/>
|
|
777
|
+
<Button onPress={reset} title="Clear" />
|
|
778
|
+
</View>
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
**More examples in the repo:**
|
|
784
|
+
|
|
785
|
+
- [VoiceSearchExample.tsx](./example/src/VoiceSearchExample.tsx) - Full search bar implementation
|
|
786
|
+
- [VoiceHookExample.tsx](./example/src/VoiceHookExample.tsx) - Hook usage with advanced features
|
|
787
|
+
- [COMPONENT_USAGE.md](./COMPONENT_USAGE.md) - Comprehensive component guide
|
|
788
|
+
style={[styles.button, isRecording && styles.recording]}
|
|
789
|
+
onPress={isRecording ? stop : start} >
|
|
790
|
+
<Text style={styles.buttonText}>
|
|
791
|
+
{isRecording ? '⏹ Stop' : '🎤 Start Recording'}
|
|
792
|
+
</Text>
|
|
793
|
+
</TouchableOpacity>
|
|
794
|
+
{error && <Text style={styles.error}>{error}</Text>}
|
|
795
|
+
</View>
|
|
796
|
+
)}
|
|
797
|
+
</VoiceMicrophone>
|
|
798
|
+
</View>
|
|
799
|
+
);
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const styles = StyleSheet.create({
|
|
803
|
+
container: {
|
|
804
|
+
flex: 1,
|
|
805
|
+
padding: 20,
|
|
806
|
+
backgroundColor: '#f5f5f5',
|
|
807
|
+
justifyContent: 'center',
|
|
808
|
+
},
|
|
809
|
+
title: {
|
|
810
|
+
fontSize: 24,
|
|
811
|
+
fontWeight: 'bold',
|
|
812
|
+
textAlign: 'center',
|
|
813
|
+
marginBottom: 30,
|
|
814
|
+
},
|
|
815
|
+
textContainer: {
|
|
816
|
+
backgroundColor: '#fff',
|
|
817
|
+
padding: 20,
|
|
818
|
+
borderRadius: 10,
|
|
819
|
+
marginBottom: 30,
|
|
820
|
+
elevation: 2,
|
|
821
|
+
},
|
|
822
|
+
label: {
|
|
823
|
+
fontSize: 12,
|
|
824
|
+
color: '#666',
|
|
825
|
+
marginTop: 10,
|
|
826
|
+
marginBottom: 5,
|
|
827
|
+
fontWeight: '600',
|
|
828
|
+
},
|
|
829
|
+
liveText: {
|
|
830
|
+
fontSize: 16,
|
|
831
|
+
color: '#007AFF',
|
|
832
|
+
fontStyle: 'italic',
|
|
833
|
+
},
|
|
834
|
+
finalText: {
|
|
835
|
+
fontSize: 18,
|
|
836
|
+
color: '#000',
|
|
837
|
+
fontWeight: '500',
|
|
838
|
+
},
|
|
839
|
+
button: {
|
|
840
|
+
backgroundColor: '#007AFF',
|
|
841
|
+
padding: 15,
|
|
842
|
+
borderRadius: 25,
|
|
843
|
+
alignItems: 'center',
|
|
844
|
+
},
|
|
845
|
+
recording: {
|
|
846
|
+
backgroundColor: '#FF3B30',
|
|
847
|
+
},
|
|
848
|
+
buttonText: {
|
|
849
|
+
color: '#fff',
|
|
850
|
+
fontSize: 18,
|
|
851
|
+
fontWeight: 'bold',
|
|
852
|
+
},
|
|
853
|
+
error: {
|
|
854
|
+
color: '#FF3B30',
|
|
855
|
+
textAlign: 'center',
|
|
856
|
+
marginTop: 10,
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
````
|
|
861
|
+
|
|
862
|
+
### Example 3: Multi-language Voice Input
|
|
429
863
|
|
|
430
|
-
|
|
864
|
+
```typescript
|
|
865
|
+
import React, { useState } from 'react';
|
|
866
|
+
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
|
|
867
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
868
|
+
|
|
869
|
+
const MultiLanguageVoice = () => {
|
|
870
|
+
const [selectedLanguage, setSelectedLanguage] = useState('en-US');
|
|
871
|
+
const [text, setText] = useState('');
|
|
872
|
+
|
|
873
|
+
const languages = [
|
|
874
|
+
{ code: 'en-US', name: 'English (US)', flag: '🇺🇸' },
|
|
875
|
+
{ code: 'es-ES', name: 'Spanish', flag: '🇪🇸' },
|
|
876
|
+
{ code: 'fr-FR', name: 'French', flag: '🇫🇷' },
|
|
877
|
+
{ code: 'de-DE', name: 'German', flag: '🇩🇪' },
|
|
878
|
+
{ code: 'zh-CN', name: 'Chinese', flag: '🇨🇳' },
|
|
879
|
+
{ code: 'ja-JP', name: 'Japanese', flag: '🇯🇵' },
|
|
880
|
+
];
|
|
431
881
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
882
|
+
return (
|
|
883
|
+
<View style={styles.container}>
|
|
884
|
+
<Text style={styles.title}>Multi-language Voice Input</Text>
|
|
885
|
+
|
|
886
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.langScroll}>
|
|
887
|
+
{languages.map((lang) => (
|
|
888
|
+
<TouchableOpacity
|
|
889
|
+
key={lang.code}
|
|
890
|
+
style={[
|
|
891
|
+
styles.langButton,
|
|
892
|
+
selectedLanguage === lang.code && styles.selectedLang,
|
|
893
|
+
]}
|
|
894
|
+
onPress={() => setSelectedLanguage(lang.code)}
|
|
895
|
+
>
|
|
896
|
+
<Text style={styles.flag}>{lang.flag}</Text>
|
|
897
|
+
<Text style={styles.langName}>{lang.name}</Text>
|
|
898
|
+
</TouchableOpacity>
|
|
899
|
+
))}
|
|
900
|
+
</ScrollView>
|
|
901
|
+
|
|
902
|
+
<View style={styles.resultContainer}>
|
|
903
|
+
<Text style={styles.resultText}>{text || 'Select language and speak...'}</Text>
|
|
904
|
+
</View>
|
|
905
|
+
|
|
906
|
+
<VoiceMicrophone
|
|
907
|
+
locale={selectedLanguage}
|
|
908
|
+
onSpeechResult={setText}
|
|
909
|
+
>
|
|
910
|
+
{({ isRecording, start, stop }) => (
|
|
911
|
+
<TouchableOpacity
|
|
912
|
+
style={[styles.micButton, isRecording && styles.recording]}
|
|
913
|
+
onPress={isRecording ? stop : start}
|
|
914
|
+
>
|
|
915
|
+
<Text style={styles.micIcon}>{isRecording ? '⏹' : '🎤'}</Text>
|
|
916
|
+
<Text style={styles.micText}>
|
|
917
|
+
{isRecording ? 'Stop' : 'Start Recording'}
|
|
918
|
+
</Text>
|
|
919
|
+
</TouchableOpacity>
|
|
920
|
+
)}
|
|
921
|
+
</VoiceMicrophone>
|
|
922
|
+
</View>
|
|
923
|
+
);
|
|
924
|
+
};
|
|
438
925
|
|
|
439
|
-
|
|
926
|
+
const styles = StyleSheet.create({
|
|
927
|
+
container: {
|
|
928
|
+
flex: 1,
|
|
929
|
+
padding: 20,
|
|
930
|
+
backgroundColor: '#fff',
|
|
931
|
+
},
|
|
932
|
+
title: {
|
|
933
|
+
fontSize: 24,
|
|
934
|
+
fontWeight: 'bold',
|
|
935
|
+
marginBottom: 20,
|
|
936
|
+
textAlign: 'center',
|
|
937
|
+
},
|
|
938
|
+
langScroll: {
|
|
939
|
+
marginBottom: 20,
|
|
940
|
+
},
|
|
941
|
+
langButton: {
|
|
942
|
+
padding: 10,
|
|
943
|
+
marginRight: 10,
|
|
944
|
+
borderRadius: 10,
|
|
945
|
+
backgroundColor: '#f0f0f0',
|
|
946
|
+
alignItems: 'center',
|
|
947
|
+
minWidth: 100,
|
|
948
|
+
},
|
|
949
|
+
selectedLang: {
|
|
950
|
+
backgroundColor: '#007AFF',
|
|
951
|
+
},
|
|
952
|
+
flag: {
|
|
953
|
+
fontSize: 30,
|
|
954
|
+
marginBottom: 5,
|
|
955
|
+
},
|
|
956
|
+
langName: {
|
|
957
|
+
fontSize: 12,
|
|
958
|
+
},
|
|
959
|
+
resultContainer: {
|
|
960
|
+
flex: 1,
|
|
961
|
+
justifyContent: 'center',
|
|
962
|
+
alignItems: 'center',
|
|
963
|
+
backgroundColor: '#f9f9f9',
|
|
964
|
+
borderRadius: 10,
|
|
965
|
+
padding: 20,
|
|
966
|
+
marginBottom: 20,
|
|
967
|
+
},
|
|
968
|
+
resultText: {
|
|
969
|
+
fontSize: 18,
|
|
970
|
+
textAlign: 'center',
|
|
971
|
+
},
|
|
972
|
+
micButton: {
|
|
973
|
+
backgroundColor: '#007AFF',
|
|
974
|
+
padding: 20,
|
|
975
|
+
borderRadius: 50,
|
|
976
|
+
alignItems: 'center',
|
|
977
|
+
},
|
|
978
|
+
recording: {
|
|
979
|
+
backgroundColor: '#FF3B30',
|
|
980
|
+
},
|
|
981
|
+
micIcon: {
|
|
982
|
+
fontSize: 40,
|
|
983
|
+
},
|
|
984
|
+
micText: {
|
|
985
|
+
color: '#fff',
|
|
986
|
+
marginTop: 10,
|
|
987
|
+
fontSize: 16,
|
|
988
|
+
fontWeight: 'bold',
|
|
989
|
+
},
|
|
990
|
+
});
|
|
991
|
+
````
|
|
440
992
|
|
|
441
|
-
###
|
|
993
|
+
### Example 4: Using Custom Hook with Form Input
|
|
442
994
|
|
|
443
995
|
```typescript
|
|
444
|
-
import {
|
|
445
|
-
import
|
|
996
|
+
import React, { useState } from 'react';
|
|
997
|
+
import { View, TextInput, TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
998
|
+
import { useVoiceRecognition } from 'react-native-voice-ts';
|
|
999
|
+
|
|
1000
|
+
const VoiceForm = () => {
|
|
1001
|
+
const [name, setName] = useState('');
|
|
1002
|
+
const [message, setMessage] = useState('');
|
|
1003
|
+
const [activeField, setActiveField] = useState<'name' | 'message' | null>(null);
|
|
1004
|
+
|
|
1005
|
+
const { isRecording, results, start, stop } = useVoiceRecognition({
|
|
1006
|
+
locale: 'en-US',
|
|
1007
|
+
onResult: (text) => {
|
|
1008
|
+
if (activeField === 'name') {
|
|
1009
|
+
setName(text);
|
|
1010
|
+
} else if (activeField === 'message') {
|
|
1011
|
+
setMessage(text);
|
|
1012
|
+
}
|
|
1013
|
+
setActiveField(null);
|
|
1014
|
+
},
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
const startRecordingForField = (field: 'name' | 'message') => {
|
|
1018
|
+
setActiveField(field);
|
|
1019
|
+
start();
|
|
1020
|
+
};
|
|
446
1021
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
1022
|
+
return (
|
|
1023
|
+
<View style={styles.container}>
|
|
1024
|
+
<Text style={styles.title}>Voice Form</Text>
|
|
1025
|
+
|
|
1026
|
+
<View style={styles.inputGroup}>
|
|
1027
|
+
<Text style={styles.label}>Name:</Text>
|
|
1028
|
+
<View style={styles.inputRow}>
|
|
1029
|
+
<TextInput
|
|
1030
|
+
style={styles.input}
|
|
1031
|
+
value={name}
|
|
1032
|
+
onChangeText={setName}
|
|
1033
|
+
placeholder="Your name"
|
|
1034
|
+
/>
|
|
1035
|
+
<TouchableOpacity
|
|
1036
|
+
style={[
|
|
1037
|
+
styles.micBtn,
|
|
1038
|
+
isRecording && activeField === 'name' && styles.recording,
|
|
1039
|
+
]}
|
|
1040
|
+
onPress={() =>
|
|
1041
|
+
isRecording ? stop() : startRecordingForField('name')
|
|
1042
|
+
}
|
|
1043
|
+
>
|
|
1044
|
+
<Text>🎤</Text>
|
|
1045
|
+
</TouchableOpacity>
|
|
1046
|
+
</View>
|
|
1047
|
+
</View>
|
|
1048
|
+
|
|
1049
|
+
<View style={styles.inputGroup}>
|
|
1050
|
+
<Text style={styles.label}>Message:</Text>
|
|
1051
|
+
<View style={styles.inputRow}>
|
|
1052
|
+
<TextInput
|
|
1053
|
+
style={[styles.input, styles.textArea]}
|
|
1054
|
+
value={message}
|
|
1055
|
+
onChangeText={setMessage}
|
|
1056
|
+
placeholder="Your message"
|
|
1057
|
+
multiline
|
|
1058
|
+
/>
|
|
1059
|
+
<TouchableOpacity
|
|
1060
|
+
style={[
|
|
1061
|
+
styles.micBtn,
|
|
1062
|
+
isRecording && activeField === 'message' && styles.recording,
|
|
1063
|
+
]}
|
|
1064
|
+
onPress={() =>
|
|
1065
|
+
isRecording ? stop() : startRecordingForField('message')
|
|
1066
|
+
}
|
|
1067
|
+
>
|
|
1068
|
+
<Text>🎤</Text>
|
|
1069
|
+
</TouchableOpacity>
|
|
1070
|
+
</View>
|
|
1071
|
+
</View>
|
|
451
1072
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}, []);
|
|
1073
|
+
<TouchableOpacity
|
|
1074
|
+
style={styles.submitButton}
|
|
1075
|
+
onPress={() => console.log({ name, message })}
|
|
1076
|
+
>
|
|
1077
|
+
<Text style={styles.submitText}>Submit</Text>
|
|
1078
|
+
</TouchableOpacity>
|
|
1079
|
+
</View>
|
|
1080
|
+
);
|
|
1081
|
+
};
|
|
462
1082
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
1083
|
+
const styles = StyleSheet.create({
|
|
1084
|
+
container: {
|
|
1085
|
+
flex: 1,
|
|
1086
|
+
padding: 20,
|
|
1087
|
+
backgroundColor: '#fff',
|
|
1088
|
+
},
|
|
1089
|
+
title: {
|
|
1090
|
+
fontSize: 24,
|
|
1091
|
+
fontWeight: 'bold',
|
|
1092
|
+
marginBottom: 30,
|
|
1093
|
+
textAlign: 'center',
|
|
1094
|
+
},
|
|
1095
|
+
inputGroup: {
|
|
1096
|
+
marginBottom: 20,
|
|
1097
|
+
},
|
|
1098
|
+
label: {
|
|
1099
|
+
fontSize: 16,
|
|
1100
|
+
marginBottom: 8,
|
|
1101
|
+
fontWeight: '600',
|
|
1102
|
+
},
|
|
1103
|
+
inputRow: {
|
|
1104
|
+
flexDirection: 'row',
|
|
1105
|
+
alignItems: 'center',
|
|
1106
|
+
},
|
|
1107
|
+
input: {
|
|
1108
|
+
flex: 1,
|
|
1109
|
+
borderWidth: 1,
|
|
1110
|
+
borderColor: '#ddd',
|
|
1111
|
+
padding: 12,
|
|
1112
|
+
borderRadius: 8,
|
|
1113
|
+
marginRight: 10,
|
|
1114
|
+
},
|
|
1115
|
+
textArea: {
|
|
1116
|
+
height: 100,
|
|
1117
|
+
textAlignVertical: 'top',
|
|
1118
|
+
},
|
|
1119
|
+
micBtn: {
|
|
1120
|
+
width: 45,
|
|
1121
|
+
height: 45,
|
|
1122
|
+
borderRadius: 22.5,
|
|
1123
|
+
backgroundColor: '#007AFF',
|
|
1124
|
+
justifyContent: 'center',
|
|
1125
|
+
alignItems: 'center',
|
|
1126
|
+
},
|
|
1127
|
+
recording: {
|
|
1128
|
+
backgroundColor: '#FF3B30',
|
|
1129
|
+
},
|
|
1130
|
+
submitButton: {
|
|
1131
|
+
backgroundColor: '#34C759',
|
|
1132
|
+
padding: 15,
|
|
1133
|
+
borderRadius: 8,
|
|
1134
|
+
alignItems: 'center',
|
|
1135
|
+
marginTop: 20,
|
|
1136
|
+
},
|
|
1137
|
+
submitText: {
|
|
1138
|
+
color: '#fff',
|
|
1139
|
+
fontSize: 18,
|
|
1140
|
+
fontWeight: 'bold',
|
|
1141
|
+
},
|
|
1142
|
+
});
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
### Example 5: Voice Commands (Advanced)
|
|
470
1146
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1147
|
+
```typescript
|
|
1148
|
+
import React, { useState, useEffect } from 'react';
|
|
1149
|
+
import { View, Text, StyleSheet, FlatList } from 'react-native';
|
|
1150
|
+
import { VoiceMicrophone } from 'react-native-voice-ts';
|
|
1151
|
+
|
|
1152
|
+
const VoiceCommands = () => {
|
|
1153
|
+
const [command, setCommand] = useState('');
|
|
1154
|
+
const [history, setHistory] = useState<string[]>([]);
|
|
1155
|
+
|
|
1156
|
+
useEffect(() => {
|
|
1157
|
+
if (command) {
|
|
1158
|
+
handleCommand(command);
|
|
476
1159
|
}
|
|
477
|
-
}, []);
|
|
1160
|
+
}, [command]);
|
|
1161
|
+
|
|
1162
|
+
const handleCommand = (text: string) => {
|
|
1163
|
+
const lowerText = text.toLowerCase();
|
|
1164
|
+
|
|
1165
|
+
if (lowerText.includes('hello') || lowerText.includes('hi')) {
|
|
1166
|
+
addToHistory('👋 Hello there!');
|
|
1167
|
+
} else if (lowerText.includes('time')) {
|
|
1168
|
+
addToHistory(`🕒 Current time: ${new Date().toLocaleTimeString()}`);
|
|
1169
|
+
} else if (lowerText.includes('date')) {
|
|
1170
|
+
addToHistory(`📅 Today's date: ${new Date().toLocaleDateString()}`);
|
|
1171
|
+
} else if (lowerText.includes('clear')) {
|
|
1172
|
+
setHistory([]);
|
|
1173
|
+
} else {
|
|
1174
|
+
addToHistory(`❓ Unknown command: "${text}"`);
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
const addToHistory = (message: string) => {
|
|
1179
|
+
setHistory((prev) => [message, ...prev]);
|
|
1180
|
+
};
|
|
478
1181
|
|
|
479
|
-
return
|
|
1182
|
+
return (
|
|
1183
|
+
<View style={styles.container}>
|
|
1184
|
+
<Text style={styles.title}>Voice Commands</Text>
|
|
1185
|
+
<Text style={styles.subtitle}>
|
|
1186
|
+
Try: "Hello", "What time is it?", "What's the date?", "Clear"
|
|
1187
|
+
</Text>
|
|
1188
|
+
|
|
1189
|
+
<VoiceMicrophone
|
|
1190
|
+
onSpeechResult={setCommand}
|
|
1191
|
+
autoStart={false}
|
|
1192
|
+
>
|
|
1193
|
+
{({ isRecording, recognizedText, start, stop }) => (
|
|
1194
|
+
<View style={styles.controlPanel}>
|
|
1195
|
+
<TouchableOpacity
|
|
1196
|
+
style={[styles.button, isRecording && styles.recording]}
|
|
1197
|
+
onPress={isRecording ? stop : start}
|
|
1198
|
+
>
|
|
1199
|
+
<Text style={styles.buttonText}>
|
|
1200
|
+
{isRecording ? '🔴 Listening...' : '🎤 Speak Command'}
|
|
1201
|
+
</Text>
|
|
1202
|
+
</TouchableOpacity>
|
|
1203
|
+
{recognizedText && (
|
|
1204
|
+
<Text style={styles.recognizedText}>"{recognizedText}"</Text>
|
|
1205
|
+
)}
|
|
1206
|
+
</View>
|
|
1207
|
+
)}
|
|
1208
|
+
</VoiceMicrophone>
|
|
1209
|
+
|
|
1210
|
+
<FlatList
|
|
1211
|
+
data={history}
|
|
1212
|
+
style={styles.list}
|
|
1213
|
+
keyExtractor={(_, index) => index.toString()}
|
|
1214
|
+
renderItem={({ item }) => (
|
|
1215
|
+
<View style={styles.historyItem}>
|
|
1216
|
+
<Text style={styles.historyText}>{item}</Text>
|
|
1217
|
+
</View>
|
|
1218
|
+
)}
|
|
1219
|
+
ListEmptyComponent={
|
|
1220
|
+
<Text style={styles.emptyText}>No commands yet...</Text>
|
|
1221
|
+
}
|
|
1222
|
+
/>
|
|
1223
|
+
</View>
|
|
1224
|
+
);
|
|
480
1225
|
};
|
|
1226
|
+
|
|
1227
|
+
const styles = StyleSheet.create({
|
|
1228
|
+
container: {
|
|
1229
|
+
flex: 1,
|
|
1230
|
+
padding: 20,
|
|
1231
|
+
backgroundColor: '#fff',
|
|
1232
|
+
},
|
|
1233
|
+
title: {
|
|
1234
|
+
fontSize: 24,
|
|
1235
|
+
fontWeight: 'bold',
|
|
1236
|
+
textAlign: 'center',
|
|
1237
|
+
marginBottom: 10,
|
|
1238
|
+
},
|
|
1239
|
+
subtitle: {
|
|
1240
|
+
fontSize: 12,
|
|
1241
|
+
color: '#666',
|
|
1242
|
+
textAlign: 'center',
|
|
1243
|
+
marginBottom: 20,
|
|
1244
|
+
},
|
|
1245
|
+
controlPanel: {
|
|
1246
|
+
marginBottom: 20,
|
|
1247
|
+
},
|
|
1248
|
+
button: {
|
|
1249
|
+
backgroundColor: '#007AFF',
|
|
1250
|
+
padding: 15,
|
|
1251
|
+
borderRadius: 10,
|
|
1252
|
+
alignItems: 'center',
|
|
1253
|
+
},
|
|
1254
|
+
recording: {
|
|
1255
|
+
backgroundColor: '#FF3B30',
|
|
1256
|
+
},
|
|
1257
|
+
buttonText: {
|
|
1258
|
+
color: '#fff',
|
|
1259
|
+
fontSize: 16,
|
|
1260
|
+
fontWeight: 'bold',
|
|
1261
|
+
},
|
|
1262
|
+
recognizedText: {
|
|
1263
|
+
textAlign: 'center',
|
|
1264
|
+
marginTop: 10,
|
|
1265
|
+
fontSize: 14,
|
|
1266
|
+
color: '#666',
|
|
1267
|
+
fontStyle: 'italic',
|
|
1268
|
+
},
|
|
1269
|
+
list: {
|
|
1270
|
+
flex: 1,
|
|
1271
|
+
},
|
|
1272
|
+
historyItem: {
|
|
1273
|
+
backgroundColor: '#f0f0f0',
|
|
1274
|
+
padding: 15,
|
|
1275
|
+
borderRadius: 8,
|
|
1276
|
+
marginBottom: 10,
|
|
1277
|
+
},
|
|
1278
|
+
historyText: {
|
|
1279
|
+
fontSize: 16,
|
|
1280
|
+
},
|
|
1281
|
+
emptyText: {
|
|
1282
|
+
textAlign: 'center',
|
|
1283
|
+
color: '#999',
|
|
1284
|
+
marginTop: 50,
|
|
1285
|
+
fontSize: 16,
|
|
1286
|
+
},
|
|
1287
|
+
});
|
|
481
1288
|
```
|
|
482
1289
|
|
|
1290
|
+
**More examples in the repo:**
|
|
1291
|
+
|
|
1292
|
+
- [VoiceSearchExample.tsx](./example/src/VoiceSearchExample.tsx) - Full search bar implementation
|
|
1293
|
+
- [VoiceHookExample.tsx](./example/src/VoiceHookExample.tsx) - Hook usage with advanced features
|
|
1294
|
+
- [COMPONENT_USAGE.md](./COMPONENT_USAGE.md) - Comprehensive component guide
|
|
1295
|
+
|
|
1296
|
+
---
|
|
1297
|
+
|
|
1298
|
+
## 📦 Component & Hook Usage
|
|
1299
|
+
|
|
1300
|
+
### Quick Links
|
|
1301
|
+
|
|
1302
|
+
- **[Complete Component Usage Guide](./COMPONENT_USAGE.md)** - Comprehensive guide with real-world examples
|
|
1303
|
+
- **[VoiceSearchExample](./example/src/VoiceSearchExample.tsx)** - Working search bar example
|
|
1304
|
+
- **[VoiceHookExample](./example/src/VoiceHookExample.tsx)** - Working hook usage example
|
|
1305
|
+
|
|
1306
|
+
### Component Benefits
|
|
1307
|
+
|
|
1308
|
+
✅ **No boilerplate** - Works out of the box
|
|
1309
|
+
✅ **Automatic cleanup** - Handles all event listeners
|
|
1310
|
+
✅ **Permission handling** - Built-in permission checks
|
|
1311
|
+
✅ **Type-safe** - Full TypeScript support
|
|
1312
|
+
✅ **Customizable** - Use your own UI with render props
|
|
1313
|
+
|
|
483
1314
|
---
|
|
484
1315
|
|
|
485
1316
|
## ⚠️ Common Issues & Solutions
|