react-native-srschat 0.1.27 → 0.1.28
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 +144 -0
- package/lib/commonjs/components/voice.js +88 -17
- package/lib/commonjs/components/voice.js.map +1 -1
- package/lib/commonjs/utils/audioRecorder.js +58 -7
- package/lib/commonjs/utils/audioRecorder.js.map +1 -1
- package/lib/module/components/voice.js +90 -19
- package/lib/module/components/voice.js.map +1 -1
- package/lib/module/utils/audioRecorder.js +56 -7
- package/lib/module/utils/audioRecorder.js.map +1 -1
- package/lib/typescript/components/voice.d.ts.map +1 -1
- package/lib/typescript/utils/audioRecorder.d.ts +2 -0
- package/lib/typescript/utils/audioRecorder.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/voice.js +107 -23
- package/src/utils/audioRecorder.js +60 -13
package/README.md
CHANGED
|
@@ -6,3 +6,147 @@
|
|
|
6
6
|
Installing the package:
|
|
7
7
|
`npm i react-native-srschat`
|
|
8
8
|
|
|
9
|
+
###Setup
|
|
10
|
+
|
|
11
|
+
iOS
|
|
12
|
+
By default, no permissions are available. First, require the setup script in your Podfile:
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
+ def node_require(script)
|
|
17
|
+
+ # Resolve script with node to allow for hoisting
|
|
18
|
+
+ require Pod::Executable.execute_command('node', ['-p',
|
|
19
|
+
+ "require.resolve(
|
|
20
|
+
+ '#{script}',
|
|
21
|
+
+ {paths: [process.argv[1]]},
|
|
22
|
+
+ )", __dir__]).strip
|
|
23
|
+
+ end
|
|
24
|
+
|
|
25
|
+
# Use it to require both react-native's and this package's scripts:
|
|
26
|
+
+ node_require('react-native/scripts/react_native_pods.rb')
|
|
27
|
+
+ node_require('react-native-permissions/scripts/setup.rb')
|
|
28
|
+
```
|
|
29
|
+
In the same Podfile, call setup_permissions with the permissions you need. Only the permissions specified here will be added:
|
|
30
|
+
```
|
|
31
|
+
# …
|
|
32
|
+
|
|
33
|
+
platform :ios, min_ios_version_supported
|
|
34
|
+
prepare_react_native_project!
|
|
35
|
+
|
|
36
|
+
setup_permissions([
|
|
37
|
+
'Microphone',
|
|
38
|
+
'SpeechRecognition',
|
|
39
|
+
])
|
|
40
|
+
```
|
|
41
|
+
Then execute pod install in your ios directory (📌 Note that it must be re-executed each time you update this config).
|
|
42
|
+
|
|
43
|
+
Finally, add the corresponding permissions usage descriptions to your Info.plist. For example:
|
|
44
|
+
```
|
|
45
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
46
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
47
|
+
<plist version="1.0">
|
|
48
|
+
<dict>
|
|
49
|
+
|
|
50
|
+
<!-- 🚨 Keep only the permissions specified in `setup_permissions` 🚨 -->
|
|
51
|
+
|
|
52
|
+
<key>NSAppleMusicUsageDescription</key>
|
|
53
|
+
<string>[REASON]</string>
|
|
54
|
+
<key>NSBluetoothAlwaysUsageDescription</key>
|
|
55
|
+
<string>[REASON]</string>
|
|
56
|
+
<key>NSBluetoothPeripheralUsageDescription</key>
|
|
57
|
+
<string>[REASON]</string>
|
|
58
|
+
<key>NSCalendarsFullAccessUsageDescription</key>
|
|
59
|
+
<string>[REASON]</string>
|
|
60
|
+
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
|
|
61
|
+
<string>[REASON]</string>
|
|
62
|
+
<key>NSCameraUsageDescription</key>
|
|
63
|
+
<string>[REASON]</string>
|
|
64
|
+
<key>NSContactsUsageDescription</key>
|
|
65
|
+
<string>[REASON]</string>
|
|
66
|
+
<key>NSFaceIDUsageDescription</key>
|
|
67
|
+
<string>[REASON]</string>
|
|
68
|
+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
69
|
+
<string>[REASON]</string>
|
|
70
|
+
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
|
|
71
|
+
<dict>
|
|
72
|
+
<key>YOUR-PURPOSE-KEY</key>
|
|
73
|
+
<string>[REASON]</string>
|
|
74
|
+
</dict>
|
|
75
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
76
|
+
<string>[REASON]</string>
|
|
77
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
78
|
+
<string>[REASON]</string>
|
|
79
|
+
<key>NSMotionUsageDescription</key>
|
|
80
|
+
<string>[REASON]</string>
|
|
81
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
82
|
+
<string>[REASON]</string>
|
|
83
|
+
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
84
|
+
<string>[REASON]</string>
|
|
85
|
+
<key>NSRemindersFullAccessUsageDescription</key>
|
|
86
|
+
<string>[REASON]</string>
|
|
87
|
+
<key>NSSpeechRecognitionUsageDescription</key>
|
|
88
|
+
<string>[REASON]</string>
|
|
89
|
+
<key>NSSiriUsageDescription</key>
|
|
90
|
+
<string>[REASON]</string>
|
|
91
|
+
<key>NSUserTrackingUsageDescription</key>
|
|
92
|
+
<string>[REASON]</string>
|
|
93
|
+
|
|
94
|
+
<!-- … -->
|
|
95
|
+
|
|
96
|
+
</dict>
|
|
97
|
+
</plist>
|
|
98
|
+
```
|
|
99
|
+
Android
|
|
100
|
+
Add all wanted permissions to your app android/app/src/main/AndroidManifest.xml file:
|
|
101
|
+
```
|
|
102
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
103
|
+
|
|
104
|
+
<!-- 🚨 Keep only the permissions used in your app 🚨 -->
|
|
105
|
+
|
|
106
|
+
<uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
|
|
107
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
108
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
109
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
110
|
+
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
|
111
|
+
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
|
112
|
+
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
|
|
113
|
+
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
|
|
114
|
+
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
|
115
|
+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
116
|
+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
117
|
+
<uses-permission android:name="android.permission.BODY_SENSORS" />
|
|
118
|
+
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
|
|
119
|
+
<uses-permission android:name="android.permission.CALL_PHONE" />
|
|
120
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
121
|
+
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
|
122
|
+
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
|
|
123
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
124
|
+
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
|
|
125
|
+
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
|
126
|
+
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
|
127
|
+
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
|
128
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
129
|
+
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
|
130
|
+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
131
|
+
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
|
132
|
+
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
|
133
|
+
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
|
134
|
+
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
135
|
+
<uses-permission android:name="android.permission.READ_SMS" />
|
|
136
|
+
<uses-permission android:name="android.permission.RECEIVE_MMS" />
|
|
137
|
+
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
|
138
|
+
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
|
|
139
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
140
|
+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
|
141
|
+
<uses-permission android:name="android.permission.SEND_SMS" />
|
|
142
|
+
<uses-permission android:name="android.permission.USE_SIP" />
|
|
143
|
+
<uses-permission android:name="android.permission.UWB_RANGING" />
|
|
144
|
+
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
|
145
|
+
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
|
|
146
|
+
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
|
147
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
148
|
+
|
|
149
|
+
<!-- … -->
|
|
150
|
+
|
|
151
|
+
</manifest>
|
|
152
|
+
```
|
|
@@ -7,6 +7,7 @@ exports.VoiceButton = void 0;
|
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _Ionicons = _interopRequireDefault(require("react-native-vector-icons/Ionicons"));
|
|
10
|
+
var _useAsyncStorage = _interopRequireDefault(require("../hooks/useAsyncStorage"));
|
|
10
11
|
var _audioRecorder = require("../utils/audioRecorder");
|
|
11
12
|
var _AppContext = require("../contexts/AppContext");
|
|
12
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -14,49 +15,105 @@ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return
|
|
|
14
15
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
16
|
// VoiceButton.js
|
|
16
17
|
|
|
18
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
17
19
|
const VoiceButton = () => {
|
|
18
20
|
const {
|
|
19
21
|
handleVoiceSend
|
|
20
22
|
} = (0, _react.useContext)(_AppContext.AppContext);
|
|
21
23
|
const [isListening, setIsListening] = (0, _react.useState)(false);
|
|
22
24
|
const [loading, setLoading] = (0, _react.useState)(false);
|
|
25
|
+
const [permissionChecked, setPermissionChecked] = (0, _react.useState)(false);
|
|
26
|
+
const [hasPermission, setHasPermission] = (0, _react.useState)(null);
|
|
27
|
+
|
|
28
|
+
// Use your custom AsyncStorage hook
|
|
29
|
+
const [permissionStatus, setPermissionStatus] = (0, _useAsyncStorage.default)(PERMISSION_STORAGE_KEY, null);
|
|
23
30
|
(0, _react.useEffect)(() => {
|
|
31
|
+
// Register our permission handlers
|
|
32
|
+
(0, _audioRecorder.setPermissionStatusHandlers)(() => permissionStatus, setPermissionStatus);
|
|
24
33
|
const setupVoice = async () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
try {
|
|
35
|
+
// Check stored permission first
|
|
36
|
+
if (permissionStatus === 'denied') {
|
|
37
|
+
// We already know permission was denied, don't show alert again
|
|
38
|
+
setHasPermission(false);
|
|
39
|
+
setPermissionChecked(true);
|
|
30
40
|
return;
|
|
31
41
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
|
|
43
|
+
// Only request permission if not already denied
|
|
44
|
+
const permissionResult = await (0, _audioRecorder.requestAudioPermission)();
|
|
45
|
+
setHasPermission(permissionResult);
|
|
46
|
+
if (permissionResult) {
|
|
47
|
+
const initialized = await (0, _audioRecorder.initVoice)((result, error) => {
|
|
48
|
+
if (error) {
|
|
49
|
+
// Don't show alert for permission errors since we handle that elsewhere
|
|
50
|
+
if (!error.includes('permission')) {
|
|
51
|
+
_reactNative.Alert.alert('Error', error);
|
|
52
|
+
}
|
|
53
|
+
setIsListening(false);
|
|
54
|
+
setLoading(false);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (result) {
|
|
58
|
+
handleVoiceSend(null, result);
|
|
59
|
+
}
|
|
60
|
+
// Always reset states when the recognition ends
|
|
61
|
+
setIsListening(false);
|
|
62
|
+
setLoading(false);
|
|
63
|
+
});
|
|
64
|
+
if (!initialized) {
|
|
65
|
+
// Only show this alert once per session
|
|
66
|
+
if (!permissionChecked) {
|
|
67
|
+
_reactNative.Alert.alert('Error', 'Speech recognition is not available on this device');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
36
70
|
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Error in setupVoice:', error);
|
|
73
|
+
} finally {
|
|
74
|
+
setPermissionChecked(true);
|
|
40
75
|
}
|
|
41
76
|
};
|
|
42
|
-
|
|
77
|
+
if (!permissionChecked) {
|
|
78
|
+
setupVoice();
|
|
79
|
+
}
|
|
43
80
|
return () => {
|
|
44
81
|
(0, _audioRecorder.cleanup)();
|
|
45
82
|
};
|
|
46
|
-
}, []);
|
|
83
|
+
}, [permissionStatus, permissionChecked]);
|
|
47
84
|
const toggleRecording = async () => {
|
|
48
85
|
try {
|
|
49
86
|
if (!isListening) {
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
_reactNative.Alert.alert('Permission
|
|
87
|
+
// If we already know we don't have permission, show a better message
|
|
88
|
+
if (hasPermission === false) {
|
|
89
|
+
_reactNative.Alert.alert('Permission Required', 'Voice recognition requires microphone permission. Would you like to update your settings?', [{
|
|
90
|
+
text: 'Cancel',
|
|
91
|
+
style: 'cancel'
|
|
92
|
+
}, {
|
|
93
|
+
text: 'Settings',
|
|
94
|
+
onPress: () => {
|
|
95
|
+
// Reset stored permission so we can check again
|
|
96
|
+
setPermissionStatus(null);
|
|
97
|
+
// Open device settings
|
|
98
|
+
openAppSettings();
|
|
99
|
+
// Reset our permission check
|
|
100
|
+
setPermissionChecked(false);
|
|
101
|
+
}
|
|
102
|
+
}]);
|
|
53
103
|
return;
|
|
54
104
|
}
|
|
55
105
|
setLoading(true);
|
|
106
|
+
const checkPermission = await (0, _audioRecorder.requestAudioPermission)();
|
|
107
|
+
if (!checkPermission) {
|
|
108
|
+
setHasPermission(false);
|
|
109
|
+
setLoading(false);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
56
112
|
const started = await (0, _audioRecorder.startRecording)();
|
|
57
113
|
if (started) {
|
|
58
114
|
setIsListening(true);
|
|
59
115
|
} else {
|
|
116
|
+
// Only show error if not permission related
|
|
60
117
|
_reactNative.Alert.alert('Error', 'Failed to start voice recognition');
|
|
61
118
|
}
|
|
62
119
|
} else {
|
|
@@ -73,6 +130,20 @@ const VoiceButton = () => {
|
|
|
73
130
|
setLoading(false);
|
|
74
131
|
}
|
|
75
132
|
};
|
|
133
|
+
const openAppSettings = async () => {
|
|
134
|
+
try {
|
|
135
|
+
if (_reactNative.Platform.OS === 'ios') {
|
|
136
|
+
// For iOS
|
|
137
|
+
await _reactNative.Linking.openURL('app-settings://');
|
|
138
|
+
} else {
|
|
139
|
+
// For Android
|
|
140
|
+
await _reactNative.Linking.openSettings();
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Cannot open settings', error);
|
|
144
|
+
_reactNative.Alert.alert('Error', 'Unable to open settings. Please open settings manually.');
|
|
145
|
+
}
|
|
146
|
+
};
|
|
76
147
|
return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
|
|
77
148
|
style: styles.button,
|
|
78
149
|
onPress: toggleRecording,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_Ionicons","_interopRequireDefault","_audioRecorder","_AppContext","e","__esModule","default","_getRequireWildcardCache","WeakMap","r","t","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","VoiceButton","handleVoiceSend","useContext","AppContext","isListening","setIsListening","useState","loading","setLoading","useEffect","setupVoice","initialized","initVoice","result","error","Alert","alert","cleanup","toggleRecording","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_Ionicons","_interopRequireDefault","_useAsyncStorage","_audioRecorder","_AppContext","e","__esModule","default","_getRequireWildcardCache","WeakMap","r","t","has","get","n","__proto__","a","Object","defineProperty","getOwnPropertyDescriptor","u","hasOwnProperty","call","i","set","PERMISSION_STORAGE_KEY","VoiceButton","handleVoiceSend","useContext","AppContext","isListening","setIsListening","useState","loading","setLoading","permissionChecked","setPermissionChecked","hasPermission","setHasPermission","permissionStatus","setPermissionStatus","useAsyncStorage","useEffect","setPermissionStatusHandlers","setupVoice","permissionResult","requestAudioPermission","initialized","initVoice","result","error","includes","Alert","alert","console","cleanup","toggleRecording","text","style","onPress","openAppSettings","checkPermission","started","startRecording","stopRecording","Platform","OS","Linking","openURL","openSettings","createElement","TouchableOpacity","styles","button","disabled","ActivityIndicator","size","color","name","exports","StyleSheet","create","justifyContent","alignItems"],"sourceRoot":"../../../src","sources":["components/voice.js"],"mappings":"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","ignoreList":[]}
|
|
@@ -7,11 +7,14 @@ exports.cancelRecording = cancelRecording;
|
|
|
7
7
|
exports.cleanup = cleanup;
|
|
8
8
|
exports.initVoice = initVoice;
|
|
9
9
|
exports.requestAudioPermission = requestAudioPermission;
|
|
10
|
+
exports.resetStoredPermission = resetStoredPermission;
|
|
11
|
+
exports.setPermissionStatusHandlers = setPermissionStatusHandlers;
|
|
10
12
|
exports.startRecording = startRecording;
|
|
11
13
|
exports.stopRecording = stopRecording;
|
|
12
14
|
var _reactNative = require("react-native");
|
|
13
15
|
var _voice = _interopRequireDefault(require("@react-native-community/voice"));
|
|
14
16
|
var _reactNativePermissions = require("react-native-permissions");
|
|
17
|
+
var _useAsyncStorage = _interopRequireDefault(require("../hooks/useAsyncStorage"));
|
|
15
18
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
19
|
// audioRecorder.js
|
|
17
20
|
|
|
@@ -21,6 +24,18 @@ let isCurrentlyRecording = false;
|
|
|
21
24
|
let finalResult = '';
|
|
22
25
|
const SILENCE_DURATION = 1500; // 1.5 seconds of silence before stopping
|
|
23
26
|
|
|
27
|
+
// Add this constant for AsyncStorage key
|
|
28
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
29
|
+
|
|
30
|
+
// Create a function that can be called to get permission status
|
|
31
|
+
// This needs to be outside the React component lifecycle
|
|
32
|
+
let permissionStatusGetter = null;
|
|
33
|
+
let permissionStatusSetter = null;
|
|
34
|
+
function setPermissionStatusHandlers(getter, setter) {
|
|
35
|
+
permissionStatusGetter = getter;
|
|
36
|
+
permissionStatusSetter = setter;
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
// Initialize Voice handlers
|
|
25
40
|
async function initVoice(onResult) {
|
|
26
41
|
try {
|
|
@@ -64,14 +79,26 @@ async function initVoice(onResult) {
|
|
|
64
79
|
}
|
|
65
80
|
};
|
|
66
81
|
_voice.default.onSpeechError = async e => {
|
|
67
|
-
var _e$error;
|
|
82
|
+
var _e$error, _e$error2;
|
|
68
83
|
console.error('onSpeechError: ', e);
|
|
69
84
|
if (silenceTimer) {
|
|
70
85
|
clearTimeout(silenceTimer);
|
|
71
86
|
silenceTimer = null;
|
|
72
87
|
}
|
|
88
|
+
|
|
89
|
+
// Check for "No speech detected" error
|
|
90
|
+
const isNoSpeechError = ((_e$error = e.error) === null || _e$error === void 0 ? void 0 : _e$error.code) === "recognition_fail" && ((_e$error2 = e.error) === null || _e$error2 === void 0 || (_e$error2 = _e$error2.message) === null || _e$error2 === void 0 ? void 0 : _e$error2.includes("No speech detected"));
|
|
73
91
|
await cleanupVoiceSession();
|
|
74
|
-
|
|
92
|
+
|
|
93
|
+
// Only send error to callback if it's not a "No speech detected" error
|
|
94
|
+
if (!isNoSpeechError) {
|
|
95
|
+
var _e$error3;
|
|
96
|
+
resultCallback(null, ((_e$error3 = e.error) === null || _e$error3 === void 0 ? void 0 : _e$error3.message) || 'Speech recognition error');
|
|
97
|
+
} else {
|
|
98
|
+
console.log('No speech detected, ignoring error');
|
|
99
|
+
// Optionally, call the callback with null parameters or a special indicator
|
|
100
|
+
resultCallback(null, null); // This won't trigger an error alert in the component
|
|
101
|
+
}
|
|
75
102
|
};
|
|
76
103
|
_voice.default.onSpeechResults = e => {
|
|
77
104
|
console.log('onSpeechResults: ', e);
|
|
@@ -226,12 +253,29 @@ async function cancelRecording() {
|
|
|
226
253
|
}
|
|
227
254
|
}
|
|
228
255
|
async function requestAudioPermission() {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
256
|
+
try {
|
|
257
|
+
// Get stored permission if available
|
|
258
|
+
const storedPermission = permissionStatusGetter ? permissionStatusGetter() : null;
|
|
259
|
+
if (storedPermission === 'denied') {
|
|
260
|
+
console.log('Permission previously denied by user');
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
let permissionResult = false;
|
|
264
|
+
if (_reactNative.Platform.OS === 'android') {
|
|
265
|
+
permissionResult = await requestAndroidPermission();
|
|
266
|
+
} else if (_reactNative.Platform.OS === 'ios') {
|
|
267
|
+
permissionResult = await requestIOSPermission();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Store the result
|
|
271
|
+
if (permissionStatusSetter) {
|
|
272
|
+
permissionStatusSetter(permissionResult ? 'granted' : 'denied');
|
|
273
|
+
}
|
|
274
|
+
return permissionResult;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error('Error checking stored permission:', error);
|
|
277
|
+
return false;
|
|
233
278
|
}
|
|
234
|
-
return false;
|
|
235
279
|
}
|
|
236
280
|
async function requestAndroidPermission() {
|
|
237
281
|
try {
|
|
@@ -274,6 +318,13 @@ async function requestIOSPermission() {
|
|
|
274
318
|
return false;
|
|
275
319
|
}
|
|
276
320
|
}
|
|
321
|
+
function resetStoredPermission() {
|
|
322
|
+
if (permissionStatusSetter) {
|
|
323
|
+
permissionStatusSetter(null);
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
277
328
|
function cleanup() {
|
|
278
329
|
_voice.default.destroy().then(() => {
|
|
279
330
|
_voice.default.removeAllListeners();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","_voice","_interopRequireDefault","_reactNativePermissions","e","__esModule","default","resultCallback","silenceTimer","isCurrentlyRecording","finalResult","SILENCE_DURATION","initVoice","onResult","isAvailable","Voice","console","error","onSpeechStart","log","clearTimeout","onSpeechRecognized","isFinal","handleFinalResult","onSpeechEnd","onSpeechError","_e$error","
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_voice","_interopRequireDefault","_reactNativePermissions","_useAsyncStorage","e","__esModule","default","resultCallback","silenceTimer","isCurrentlyRecording","finalResult","SILENCE_DURATION","PERMISSION_STORAGE_KEY","permissionStatusGetter","permissionStatusSetter","setPermissionStatusHandlers","getter","setter","initVoice","onResult","isAvailable","Voice","console","error","onSpeechStart","log","clearTimeout","onSpeechRecognized","isFinal","handleFinalResult","onSpeechEnd","onSpeechError","_e$error","_e$error2","isNoSpeechError","code","message","includes","cleanupVoiceSession","_e$error3","onSpeechResults","value","length","handleSilenceDetection","onSpeechPartialResults","Platform","OS","onSpeechVolumeChanged","setTimeout","stopRecording","isRecognizing","stop","Promise","resolve","destroy","stillRecognizing","startRecording","hasPermission","requestAudioPermission","start","cancelRecording","cancel","storedPermission","permissionResult","requestAndroidPermission","requestIOSPermission","services","getSpeechRecognitionServices","granted","PermissionsAndroid","request","PERMISSIONS","RECORD_AUDIO","title","buttonPositive","buttonNegative","RESULTS","GRANTED","micPermission","IOS","MICROPHONE","speechPermission","SPEECH_RECOGNITION","resetStoredPermission","cleanup","then","removeAllListeners","catch"],"sourceRoot":"../../../src","sources":["utils/audioRecorder.js"],"mappings":";;;;;;;;;;;;;AAEA,IAAAA,YAAA,GAAAC,OAAA;AACA,IAAAC,MAAA,GAAAC,sBAAA,CAAAF,OAAA;AACA,IAAAG,uBAAA,GAAAH,OAAA;AACA,IAAAI,gBAAA,GAAAF,sBAAA,CAAAF,OAAA;AAAuD,SAAAE,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AALvD;;AAOA,IAAIG,cAAc,GAAG,IAAI;AACzB,IAAIC,YAAY,GAAG,IAAI;AACvB,IAAIC,oBAAoB,GAAG,KAAK;AAChC,IAAIC,WAAW,GAAG,EAAE;AACpB,MAAMC,gBAAgB,GAAG,IAAI,CAAC,CAAC;;AAE/B;AACA,MAAMC,sBAAsB,GAAG,0BAA0B;;AAEzD;AACA;AACA,IAAIC,sBAAsB,GAAG,IAAI;AACjC,IAAIC,sBAAsB,GAAG,IAAI;AAE1B,SAASC,2BAA2BA,CAACC,MAAM,EAAEC,MAAM,EAAE;EAC1DJ,sBAAsB,GAAGG,MAAM;EAC/BF,sBAAsB,GAAGG,MAAM;AACjC;;AAEA;AACO,eAAeC,SAASA,CAACC,QAAQ,EAAE;EACxC,IAAI;IACFZ,cAAc,GAAGY,QAAQ;IACzBT,WAAW,GAAG,EAAE;;IAEhB;IACA,MAAMU,WAAW,GAAG,MAAMC,cAAK,CAACD,WAAW,CAAC,CAAC;IAC7C,IAAI,CAACA,WAAW,EAAE;MAChBE,OAAO,CAACC,KAAK,CAAC,oDAAoD,CAAC;MACnE,OAAO,KAAK;IACd;;IAEA;IACAF,cAAK,CAACG,aAAa,GAAIpB,CAAC,IAAK;MAC3BkB,OAAO,CAACG,GAAG,CAAC,iBAAiB,EAAErB,CAAC,CAAC;MACjCK,oBAAoB,GAAG,IAAI;MAC3BC,WAAW,GAAG,EAAE;MAEhB,IAAIF,YAAY,EAAE;QAChBkB,YAAY,CAAClB,YAAY,CAAC;QAC1BA,YAAY,GAAG,IAAI;MACrB;IACF,CAAC;IAEDa,cAAK,CAACM,kBAAkB,GAAIvB,CAAC,IAAK;MAChCkB,OAAO,CAACG,GAAG,CAAC,sBAAsB,EAAErB,CAAC,CAAC;MACtC,IAAIA,CAAC,CAACwB,OAAO,EAAE;QACbN,OAAO,CAACG,GAAG,CAAC,0BAA0B,CAAC;QACvCI,iBAAiB,CAAC,CAAC;MACrB;IACF,CAAC;IAEDR,cAAK,CAACS,WAAW,GAAG,MAAO1B,CAAC,IAAK;MAC/BkB,OAAO,CAACG,GAAG,CAAC,eAAe,EAAErB,CAAC,CAAC;MAE/B,IAAII,YAAY,EAAE;QAChBkB,YAAY,CAAClB,YAAY,CAAC;QAC1BA,YAAY,GAAG,IAAI;MACrB;;MAEA;MACA,IAAIC,oBAAoB,EAAE;QACxB,MAAMoB,iBAAiB,CAAC,CAAC;MAC3B;IACF,CAAC;IAEDR,cAAK,CAACU,aAAa,GAAG,MAAO3B,CAAC,IAAK;MAAA,IAAA4B,QAAA,EAAAC,SAAA;MACjCX,OAAO,CAACC,KAAK,CAAC,iBAAiB,EAAEnB,CAAC,CAAC;MAEnC,IAAII,YAAY,EAAE;QAChBkB,YAAY,CAAClB,YAAY,CAAC;QAC1BA,YAAY,GAAG,IAAI;MACrB;;MAEA;MACA,MAAM0B,eAAe,GAAG,EAAAF,QAAA,GAAA5B,CAAC,CAACmB,KAAK,cAAAS,QAAA,uBAAPA,QAAA,CAASG,IAAI,MAAK,kBAAkB,MAAAF,SAAA,GACpC7B,CAAC,CAACmB,KAAK,cAAAU,SAAA,gBAAAA,SAAA,GAAPA,SAAA,CAASG,OAAO,cAAAH,SAAA,uBAAhBA,SAAA,CAAkBI,QAAQ,CAAC,oBAAoB,CAAC;MAExE,MAAMC,mBAAmB,CAAC,CAAC;;MAE3B;MACA,IAAI,CAACJ,eAAe,EAAE;QAAA,IAAAK,SAAA;QACpBhC,cAAc,CAAC,IAAI,EAAE,EAAAgC,SAAA,GAAAnC,CAAC,CAACmB,KAAK,cAAAgB,SAAA,uBAAPA,SAAA,CAASH,OAAO,KAAI,0BAA0B,CAAC;MACtE,CAAC,MAAM;QACLd,OAAO,CAACG,GAAG,CAAC,oCAAoC,CAAC;QACjD;QACAlB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;MAC9B;IACF,CAAC;IAEDc,cAAK,CAACmB,eAAe,GAAIpC,CAAC,IAAK;MAC7BkB,OAAO,CAACG,GAAG,CAAC,mBAAmB,EAAErB,CAAC,CAAC;MACnC,IAAIA,CAAC,CAACqC,KAAK,IAAIrC,CAAC,CAACqC,KAAK,CAACC,MAAM,GAAG,CAAC,EAAE;QACjChC,WAAW,GAAGN,CAAC,CAACqC,KAAK,CAAC,CAAC,CAAC;QACxBE,sBAAsB,CAAC,CAAC;MAC1B;IACF,CAAC;IAEDtB,cAAK,CAACuB,sBAAsB,GAAIxC,CAAC,IAAK;MACpCkB,OAAO,CAACG,GAAG,CAAC,0BAA0B,EAAErB,CAAC,CAAC;MAE1C,IAAII,YAAY,EAAE;QAChBkB,YAAY,CAAClB,YAAY,CAAC;MAC5B;MAEA,IAAIJ,CAAC,CAACqC,KAAK,IAAIrC,CAAC,CAACqC,KAAK,CAACC,MAAM,GAAG,CAAC,EAAE;QACjChC,WAAW,GAAGN,CAAC,CAACqC,KAAK,CAAC,CAAC,CAAC;QACxBE,sBAAsB,CAAC,CAAC;MAC1B;IACF,CAAC;IAED,IAAIE,qBAAQ,CAACC,EAAE,KAAK,SAAS,EAAE;MAC7BzB,cAAK,CAAC0B,qBAAqB,GAAI3C,CAAC,IAAK;QACnCkB,OAAO,CAACG,GAAG,CAAC,yBAAyB,EAAErB,CAAC,CAAC;MAC3C,CAAC;IACH;IAEA,OAAO,IAAI;EACb,CAAC,CAAC,OAAOmB,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,2BAA2B,EAAEA,KAAK,CAAC;IACjD,OAAO,KAAK;EACd;AACF;AAEA,MAAMoB,sBAAsB,GAAGA,CAAA,KAAM;EACnC,IAAInC,YAAY,EAAE;IAChBkB,YAAY,CAAClB,YAAY,CAAC;EAC5B;EAEAA,YAAY,GAAGwC,UAAU,CAAC,YAAY;IACpC,IAAIvC,oBAAoB,EAAE;MACxB,MAAMoB,iBAAiB,CAAC,CAAC;IAC3B;EACF,CAAC,EAAElB,gBAAgB,CAAC;AACtB,CAAC;AAED,MAAMkB,iBAAiB,GAAG,MAAAA,CAAA,KAAY;EACpC,IAAI,CAACpB,oBAAoB,EAAE;EAE3B,IAAIC,WAAW,EAAE;IACfH,cAAc,CAACG,WAAW,CAAC;EAC7B;;EAEA;EACA,MAAMuC,aAAa,CAAC,CAAC;;EAErB;EACA,MAAMX,mBAAmB,CAAC,CAAC;AAC7B,CAAC;AAED,MAAMA,mBAAmB,GAAG,MAAAA,CAAA,KAAY;EACtC7B,oBAAoB,GAAG,KAAK;EAE5B,IAAID,YAAY,EAAE;IAChBkB,YAAY,CAAClB,YAAY,CAAC;IAC1BA,YAAY,GAAG,IAAI;EACrB;EAEA,IAAI;IACF;IACA,MAAM0C,aAAa,GAAG,MAAM7B,cAAK,CAAC6B,aAAa,CAAC,CAAC;IACjD,IAAIA,aAAa,EAAE;MACjB,IAAI;QACF,MAAM7B,cAAK,CAAC8B,IAAI,CAAC,CAAC;QAClB,MAAM,IAAIC,OAAO,CAACC,OAAO,IAAIL,UAAU,CAACK,OAAO,EAAE,GAAG,CAAC,CAAC;MACxD,CAAC,CAAC,OAAOjD,CAAC,EAAE;QACVkB,OAAO,CAACC,KAAK,CAAC,4BAA4B,EAAEnB,CAAC,CAAC;MAChD;IACF;;IAEA;IACA,MAAMiB,cAAK,CAACiC,OAAO,CAAC,CAAC;IACrB,MAAM,IAAIF,OAAO,CAACC,OAAO,IAAIL,UAAU,CAACK,OAAO,EAAE,GAAG,CAAC,CAAC;;IAEtD;IACA,MAAME,gBAAgB,GAAG,MAAMlC,cAAK,CAAC6B,aAAa,CAAC,CAAC;IACpD,IAAIK,gBAAgB,EAAE;MACpB,MAAMlC,cAAK,CAACiC,OAAO,CAAC,CAAC;MACrB,MAAM,IAAIF,OAAO,CAACC,OAAO,IAAIL,UAAU,CAACK,OAAO,EAAE,GAAG,CAAC,CAAC;IACxD;EACF,CAAC,CAAC,OAAO9B,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,+BAA+B,EAAEA,KAAK,CAAC;IACrD;IACA,IAAI;MACF,MAAMF,cAAK,CAACiC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC,OAAOlD,CAAC,EAAE;MACVkB,OAAO,CAACC,KAAK,CAAC,+BAA+B,EAAEnB,CAAC,CAAC;IACnD;EACF;EAEAM,WAAW,GAAG,EAAE;AAClB,CAAC;AAEM,eAAe8C,cAAcA,CAAA,EAAG;EACrC,IAAI;IACF;IACA,MAAMlB,mBAAmB,CAAC,CAAC;IAE3B,MAAMmB,aAAa,GAAG,MAAMC,sBAAsB,CAAC,CAAC;IACpD,IAAI,CAACD,aAAa,EAAE;MAClBnC,OAAO,CAACC,KAAK,CAAC,+BAA+B,CAAC;MAC9C,OAAO,KAAK;IACd;IAEA,MAAMF,cAAK,CAACsC,KAAK,CAAC,OAAO,CAAC;IAC1BlD,oBAAoB,GAAG,IAAI;IAC3B,OAAO,IAAI;EACb,CAAC,CAAC,OAAOc,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,mCAAmC,EAAEA,KAAK,CAAC;IACzD,MAAMe,mBAAmB,CAAC,CAAC;IAC3B,OAAO,KAAK;EACd;AACF;AAEO,eAAeW,aAAaA,CAAA,EAAG;EACpC,IAAI;IACF,IAAI,CAACxC,oBAAoB,EAAE;;IAE3B;IACAA,oBAAoB,GAAG,KAAK;IAE5B,IAAID,YAAY,EAAE;MAChBkB,YAAY,CAAClB,YAAY,CAAC;MAC1BA,YAAY,GAAG,IAAI;IACrB;;IAEA;IACA,IAAI;MACF,MAAMa,cAAK,CAAC8B,IAAI,CAAC,CAAC;MAClB;MACA,MAAM,IAAIC,OAAO,CAACC,OAAO,IAAIL,UAAU,CAACK,OAAO,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC,CAAC,OAAO9B,KAAK,EAAE;MACdD,OAAO,CAACC,KAAK,CAAC,uBAAuB,EAAEA,KAAK,CAAC;IAC/C;;IAEA;IACA,IAAI;MACF,MAAMF,cAAK,CAACiC,OAAO,CAAC,CAAC;MACrB,MAAM,IAAIF,OAAO,CAACC,OAAO,IAAIL,UAAU,CAACK,OAAO,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC,CAAC,OAAO9B,KAAK,EAAE;MACdD,OAAO,CAACC,KAAK,CAAC,yBAAyB,EAAEA,KAAK,CAAC;IACjD;;IAEA;IACA,MAAMe,mBAAmB,CAAC,CAAC;EAC7B,CAAC,CAAC,OAAOf,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,yBAAyB,EAAEA,KAAK,CAAC;IAC/C;IACA,MAAMe,mBAAmB,CAAC,CAAC;EAC7B;AACF;AAEO,eAAesB,eAAeA,CAAA,EAAG;EACtC,IAAI;IACF,MAAMvC,cAAK,CAACwC,MAAM,CAAC,CAAC;IACpB,MAAMvB,mBAAmB,CAAC,CAAC;EAC7B,CAAC,CAAC,OAAOf,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,oCAAoC,EAAEA,KAAK,CAAC;IAC1D,MAAMe,mBAAmB,CAAC,CAAC;EAC7B;AAEF;AAEO,eAAeoB,sBAAsBA,CAAA,EAAG;EAC7C,IAAI;IACF;IACA,MAAMI,gBAAgB,GAAGjD,sBAAsB,GAAGA,sBAAsB,CAAC,CAAC,GAAG,IAAI;IAEjF,IAAIiD,gBAAgB,KAAK,QAAQ,EAAE;MACjCxC,OAAO,CAACG,GAAG,CAAC,sCAAsC,CAAC;MACnD,OAAO,KAAK;IACd;IAEA,IAAIsC,gBAAgB,GAAG,KAAK;IAC5B,IAAIlB,qBAAQ,CAACC,EAAE,KAAK,SAAS,EAAE;MAC7BiB,gBAAgB,GAAG,MAAMC,wBAAwB,CAAC,CAAC;IACrD,CAAC,MAAM,IAAInB,qBAAQ,CAACC,EAAE,KAAK,KAAK,EAAE;MAChCiB,gBAAgB,GAAG,MAAME,oBAAoB,CAAC,CAAC;IACjD;;IAEA;IACA,IAAInD,sBAAsB,EAAE;MAC1BA,sBAAsB,CAACiD,gBAAgB,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjE;IAEA,OAAOA,gBAAgB;EACzB,CAAC,CAAC,OAAOxC,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,mCAAmC,EAAEA,KAAK,CAAC;IACzD,OAAO,KAAK;EACd;AACF;AAEA,eAAeyC,wBAAwBA,CAAA,EAAG;EACxC,IAAI;IACF;IACA,MAAME,QAAQ,GAAG,MAAM7C,cAAK,CAAC8C,4BAA4B,CAAC,CAAC;IAC3D,IAAI,CAACD,QAAQ,IAAIA,QAAQ,CAACxB,MAAM,KAAK,CAAC,EAAE;MACtCpB,OAAO,CAACC,KAAK,CAAC,0CAA0C,CAAC;MACzD,OAAO,KAAK;IACd;IAEA,MAAM6C,OAAO,GAAG,MAAMC,+BAAkB,CAACC,OAAO,CAC9CD,+BAAkB,CAACE,WAAW,CAACC,YAAY,EAC3C;MACEC,KAAK,EAAE,uBAAuB;MAC9BrC,OAAO,EAAE,iEAAiE;MAC1EsC,cAAc,EAAE,IAAI;MACpBC,cAAc,EAAE;IAClB,CACF,CAAC;IAED,OAAOP,OAAO,KAAKC,+BAAkB,CAACO,OAAO,CAACC,OAAO;EACvD,CAAC,CAAC,OAAOtD,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,sCAAsC,EAAEA,KAAK,CAAC;IAC5D,OAAO,KAAK;EACd;AACF;AAEA,eAAe0C,oBAAoBA,CAAA,EAAG;EACpC,IAAI;IACF;IACA,MAAMa,aAAa,GAAG,MAAM,IAAAR,+BAAO,EAACC,mCAAW,CAACQ,GAAG,CAACC,UAAU,CAAC;IAC/D,IAAIF,aAAa,KAAKF,+BAAO,CAACC,OAAO,EAAE;MACrCvD,OAAO,CAACG,GAAG,CAAC,8BAA8B,CAAC;MAC3C,OAAO,KAAK;IACd;;IAEA;IACA,MAAMwD,gBAAgB,GAAG,MAAM,IAAAX,+BAAO,EAACC,mCAAW,CAACQ,GAAG,CAACG,kBAAkB,CAAC;IAC1E,IAAID,gBAAgB,KAAKL,+BAAO,CAACC,OAAO,EAAE;MACxCvD,OAAO,CAACG,GAAG,CAAC,sCAAsC,CAAC;MACnD,OAAO,KAAK;IACd;IAEA,OAAO,IAAI;EACb,CAAC,CAAC,OAAOF,KAAK,EAAE;IACdD,OAAO,CAACC,KAAK,CAAC,mCAAmC,EAAEA,KAAK,CAAC;IACzD,OAAO,KAAK;EACd;AACF;AAEO,SAAS4D,qBAAqBA,CAAA,EAAG;EACtC,IAAIrE,sBAAsB,EAAE;IAC1BA,sBAAsB,CAAC,IAAI,CAAC;IAC5B,OAAO,IAAI;EACb;EACA,OAAO,KAAK;AACd;AAEO,SAASsE,OAAOA,CAAA,EAAG;EACxB/D,cAAK,CAACiC,OAAO,CAAC,CAAC,CAAC+B,IAAI,CAAC,MAAM;IACzBhE,cAAK,CAACiE,kBAAkB,CAAC,CAAC;IAC1BhD,mBAAmB,CAAC,CAAC;EACvB,CAAC,CAAC,CAACiD,KAAK,CAAChE,KAAK,IAAI;IAChBD,OAAO,CAACC,KAAK,CAAC,mBAAmB,EAAEA,KAAK,CAAC;IACzC;IACAF,cAAK,CAACiC,OAAO,CAAC,CAAC,CAACiC,KAAK,CAACnF,CAAC,IAAIkB,OAAO,CAACC,KAAK,CAAC,+BAA+B,EAAEnB,CAAC,CAAC,CAAC;EAC/E,CAAC,CAAC;AACJ","ignoreList":[]}
|
|
@@ -1,53 +1,110 @@
|
|
|
1
1
|
// VoiceButton.js
|
|
2
2
|
|
|
3
3
|
import React, { useState, useContext, useEffect } from 'react';
|
|
4
|
-
import { TouchableOpacity, ActivityIndicator, StyleSheet, Alert } from 'react-native';
|
|
4
|
+
import { TouchableOpacity, ActivityIndicator, StyleSheet, Alert, Linking, Platform } from 'react-native';
|
|
5
5
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
6
|
-
import
|
|
6
|
+
import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
7
|
+
import { startRecording, stopRecording, cancelRecording, requestAudioPermission, cleanup, initVoice, resetStoredPermission, setPermissionStatusHandlers } from '../utils/audioRecorder';
|
|
7
8
|
import { AppContext } from '../contexts/AppContext';
|
|
9
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
8
10
|
export const VoiceButton = () => {
|
|
9
11
|
const {
|
|
10
12
|
handleVoiceSend
|
|
11
13
|
} = useContext(AppContext);
|
|
12
14
|
const [isListening, setIsListening] = useState(false);
|
|
13
15
|
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [permissionChecked, setPermissionChecked] = useState(false);
|
|
17
|
+
const [hasPermission, setHasPermission] = useState(null);
|
|
18
|
+
|
|
19
|
+
// Use your custom AsyncStorage hook
|
|
20
|
+
const [permissionStatus, setPermissionStatus] = useAsyncStorage(PERMISSION_STORAGE_KEY, null);
|
|
14
21
|
useEffect(() => {
|
|
22
|
+
// Register our permission handlers
|
|
23
|
+
setPermissionStatusHandlers(() => permissionStatus, setPermissionStatus);
|
|
15
24
|
const setupVoice = async () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
try {
|
|
26
|
+
// Check stored permission first
|
|
27
|
+
if (permissionStatus === 'denied') {
|
|
28
|
+
// We already know permission was denied, don't show alert again
|
|
29
|
+
setHasPermission(false);
|
|
30
|
+
setPermissionChecked(true);
|
|
21
31
|
return;
|
|
22
32
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
|
|
34
|
+
// Only request permission if not already denied
|
|
35
|
+
const permissionResult = await requestAudioPermission();
|
|
36
|
+
setHasPermission(permissionResult);
|
|
37
|
+
if (permissionResult) {
|
|
38
|
+
const initialized = await initVoice((result, error) => {
|
|
39
|
+
if (error) {
|
|
40
|
+
// Don't show alert for permission errors since we handle that elsewhere
|
|
41
|
+
if (!error.includes('permission')) {
|
|
42
|
+
Alert.alert('Error', error);
|
|
43
|
+
}
|
|
44
|
+
setIsListening(false);
|
|
45
|
+
setLoading(false);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (result) {
|
|
49
|
+
handleVoiceSend(null, result);
|
|
50
|
+
}
|
|
51
|
+
// Always reset states when the recognition ends
|
|
52
|
+
setIsListening(false);
|
|
53
|
+
setLoading(false);
|
|
54
|
+
});
|
|
55
|
+
if (!initialized) {
|
|
56
|
+
// Only show this alert once per session
|
|
57
|
+
if (!permissionChecked) {
|
|
58
|
+
Alert.alert('Error', 'Speech recognition is not available on this device');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
27
61
|
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error in setupVoice:', error);
|
|
64
|
+
} finally {
|
|
65
|
+
setPermissionChecked(true);
|
|
31
66
|
}
|
|
32
67
|
};
|
|
33
|
-
|
|
68
|
+
if (!permissionChecked) {
|
|
69
|
+
setupVoice();
|
|
70
|
+
}
|
|
34
71
|
return () => {
|
|
35
72
|
cleanup();
|
|
36
73
|
};
|
|
37
|
-
}, []);
|
|
74
|
+
}, [permissionStatus, permissionChecked]);
|
|
38
75
|
const toggleRecording = async () => {
|
|
39
76
|
try {
|
|
40
77
|
if (!isListening) {
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
Alert.alert('Permission
|
|
78
|
+
// If we already know we don't have permission, show a better message
|
|
79
|
+
if (hasPermission === false) {
|
|
80
|
+
Alert.alert('Permission Required', 'Voice recognition requires microphone permission. Would you like to update your settings?', [{
|
|
81
|
+
text: 'Cancel',
|
|
82
|
+
style: 'cancel'
|
|
83
|
+
}, {
|
|
84
|
+
text: 'Settings',
|
|
85
|
+
onPress: () => {
|
|
86
|
+
// Reset stored permission so we can check again
|
|
87
|
+
setPermissionStatus(null);
|
|
88
|
+
// Open device settings
|
|
89
|
+
openAppSettings();
|
|
90
|
+
// Reset our permission check
|
|
91
|
+
setPermissionChecked(false);
|
|
92
|
+
}
|
|
93
|
+
}]);
|
|
44
94
|
return;
|
|
45
95
|
}
|
|
46
96
|
setLoading(true);
|
|
97
|
+
const checkPermission = await requestAudioPermission();
|
|
98
|
+
if (!checkPermission) {
|
|
99
|
+
setHasPermission(false);
|
|
100
|
+
setLoading(false);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
47
103
|
const started = await startRecording();
|
|
48
104
|
if (started) {
|
|
49
105
|
setIsListening(true);
|
|
50
106
|
} else {
|
|
107
|
+
// Only show error if not permission related
|
|
51
108
|
Alert.alert('Error', 'Failed to start voice recognition');
|
|
52
109
|
}
|
|
53
110
|
} else {
|
|
@@ -64,6 +121,20 @@ export const VoiceButton = () => {
|
|
|
64
121
|
setLoading(false);
|
|
65
122
|
}
|
|
66
123
|
};
|
|
124
|
+
const openAppSettings = async () => {
|
|
125
|
+
try {
|
|
126
|
+
if (Platform.OS === 'ios') {
|
|
127
|
+
// For iOS
|
|
128
|
+
await Linking.openURL('app-settings://');
|
|
129
|
+
} else {
|
|
130
|
+
// For Android
|
|
131
|
+
await Linking.openSettings();
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('Cannot open settings', error);
|
|
135
|
+
Alert.alert('Error', 'Unable to open settings. Please open settings manually.');
|
|
136
|
+
}
|
|
137
|
+
};
|
|
67
138
|
return /*#__PURE__*/React.createElement(TouchableOpacity, {
|
|
68
139
|
style: styles.button,
|
|
69
140
|
onPress: toggleRecording,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useState","useContext","useEffect","TouchableOpacity","ActivityIndicator","StyleSheet","Alert","Ionicons","startRecording","stopRecording","cancelRecording","requestAudioPermission","cleanup","initVoice","AppContext","VoiceButton","handleVoiceSend","isListening","setIsListening","loading","setLoading","setupVoice","initialized","result","error","alert","toggleRecording","
|
|
1
|
+
{"version":3,"names":["React","useState","useContext","useEffect","TouchableOpacity","ActivityIndicator","StyleSheet","Alert","Linking","Platform","Ionicons","useAsyncStorage","startRecording","stopRecording","cancelRecording","requestAudioPermission","cleanup","initVoice","resetStoredPermission","setPermissionStatusHandlers","AppContext","PERMISSION_STORAGE_KEY","VoiceButton","handleVoiceSend","isListening","setIsListening","loading","setLoading","permissionChecked","setPermissionChecked","hasPermission","setHasPermission","permissionStatus","setPermissionStatus","setupVoice","permissionResult","initialized","result","error","includes","alert","console","toggleRecording","text","style","onPress","openAppSettings","checkPermission","started","OS","openURL","openSettings","createElement","styles","button","disabled","size","color","name","create","justifyContent","alignItems"],"sourceRoot":"../../../src","sources":["components/voice.js"],"mappings":"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","ignoreList":[]}
|
|
@@ -3,12 +3,25 @@
|
|
|
3
3
|
import { Platform, PermissionsAndroid } from 'react-native';
|
|
4
4
|
import Voice from '@react-native-community/voice';
|
|
5
5
|
import { check, PERMISSIONS, request, RESULTS } from 'react-native-permissions';
|
|
6
|
+
import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
6
7
|
let resultCallback = null;
|
|
7
8
|
let silenceTimer = null;
|
|
8
9
|
let isCurrentlyRecording = false;
|
|
9
10
|
let finalResult = '';
|
|
10
11
|
const SILENCE_DURATION = 1500; // 1.5 seconds of silence before stopping
|
|
11
12
|
|
|
13
|
+
// Add this constant for AsyncStorage key
|
|
14
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
15
|
+
|
|
16
|
+
// Create a function that can be called to get permission status
|
|
17
|
+
// This needs to be outside the React component lifecycle
|
|
18
|
+
let permissionStatusGetter = null;
|
|
19
|
+
let permissionStatusSetter = null;
|
|
20
|
+
export function setPermissionStatusHandlers(getter, setter) {
|
|
21
|
+
permissionStatusGetter = getter;
|
|
22
|
+
permissionStatusSetter = setter;
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
// Initialize Voice handlers
|
|
13
26
|
export async function initVoice(onResult) {
|
|
14
27
|
try {
|
|
@@ -52,14 +65,26 @@ export async function initVoice(onResult) {
|
|
|
52
65
|
}
|
|
53
66
|
};
|
|
54
67
|
Voice.onSpeechError = async e => {
|
|
55
|
-
var _e$error;
|
|
68
|
+
var _e$error, _e$error2;
|
|
56
69
|
console.error('onSpeechError: ', e);
|
|
57
70
|
if (silenceTimer) {
|
|
58
71
|
clearTimeout(silenceTimer);
|
|
59
72
|
silenceTimer = null;
|
|
60
73
|
}
|
|
74
|
+
|
|
75
|
+
// Check for "No speech detected" error
|
|
76
|
+
const isNoSpeechError = ((_e$error = e.error) === null || _e$error === void 0 ? void 0 : _e$error.code) === "recognition_fail" && ((_e$error2 = e.error) === null || _e$error2 === void 0 || (_e$error2 = _e$error2.message) === null || _e$error2 === void 0 ? void 0 : _e$error2.includes("No speech detected"));
|
|
61
77
|
await cleanupVoiceSession();
|
|
62
|
-
|
|
78
|
+
|
|
79
|
+
// Only send error to callback if it's not a "No speech detected" error
|
|
80
|
+
if (!isNoSpeechError) {
|
|
81
|
+
var _e$error3;
|
|
82
|
+
resultCallback(null, ((_e$error3 = e.error) === null || _e$error3 === void 0 ? void 0 : _e$error3.message) || 'Speech recognition error');
|
|
83
|
+
} else {
|
|
84
|
+
console.log('No speech detected, ignoring error');
|
|
85
|
+
// Optionally, call the callback with null parameters or a special indicator
|
|
86
|
+
resultCallback(null, null); // This won't trigger an error alert in the component
|
|
87
|
+
}
|
|
63
88
|
};
|
|
64
89
|
Voice.onSpeechResults = e => {
|
|
65
90
|
console.log('onSpeechResults: ', e);
|
|
@@ -214,12 +239,29 @@ export async function cancelRecording() {
|
|
|
214
239
|
}
|
|
215
240
|
}
|
|
216
241
|
export async function requestAudioPermission() {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
try {
|
|
243
|
+
// Get stored permission if available
|
|
244
|
+
const storedPermission = permissionStatusGetter ? permissionStatusGetter() : null;
|
|
245
|
+
if (storedPermission === 'denied') {
|
|
246
|
+
console.log('Permission previously denied by user');
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
let permissionResult = false;
|
|
250
|
+
if (Platform.OS === 'android') {
|
|
251
|
+
permissionResult = await requestAndroidPermission();
|
|
252
|
+
} else if (Platform.OS === 'ios') {
|
|
253
|
+
permissionResult = await requestIOSPermission();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Store the result
|
|
257
|
+
if (permissionStatusSetter) {
|
|
258
|
+
permissionStatusSetter(permissionResult ? 'granted' : 'denied');
|
|
259
|
+
}
|
|
260
|
+
return permissionResult;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('Error checking stored permission:', error);
|
|
263
|
+
return false;
|
|
221
264
|
}
|
|
222
|
-
return false;
|
|
223
265
|
}
|
|
224
266
|
async function requestAndroidPermission() {
|
|
225
267
|
try {
|
|
@@ -262,6 +304,13 @@ async function requestIOSPermission() {
|
|
|
262
304
|
return false;
|
|
263
305
|
}
|
|
264
306
|
}
|
|
307
|
+
export function resetStoredPermission() {
|
|
308
|
+
if (permissionStatusSetter) {
|
|
309
|
+
permissionStatusSetter(null);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
265
314
|
export function cleanup() {
|
|
266
315
|
Voice.destroy().then(() => {
|
|
267
316
|
Voice.removeAllListeners();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Platform","PermissionsAndroid","Voice","check","PERMISSIONS","request","RESULTS","resultCallback","silenceTimer","isCurrentlyRecording","finalResult","SILENCE_DURATION","initVoice","onResult","isAvailable","console","error","onSpeechStart","e","log","clearTimeout","onSpeechRecognized","isFinal","handleFinalResult","onSpeechEnd","onSpeechError","_e$error","
|
|
1
|
+
{"version":3,"names":["Platform","PermissionsAndroid","Voice","check","PERMISSIONS","request","RESULTS","useAsyncStorage","resultCallback","silenceTimer","isCurrentlyRecording","finalResult","SILENCE_DURATION","PERMISSION_STORAGE_KEY","permissionStatusGetter","permissionStatusSetter","setPermissionStatusHandlers","getter","setter","initVoice","onResult","isAvailable","console","error","onSpeechStart","e","log","clearTimeout","onSpeechRecognized","isFinal","handleFinalResult","onSpeechEnd","onSpeechError","_e$error","_e$error2","isNoSpeechError","code","message","includes","cleanupVoiceSession","_e$error3","onSpeechResults","value","length","handleSilenceDetection","onSpeechPartialResults","OS","onSpeechVolumeChanged","setTimeout","stopRecording","isRecognizing","stop","Promise","resolve","destroy","stillRecognizing","startRecording","hasPermission","requestAudioPermission","start","cancelRecording","cancel","storedPermission","permissionResult","requestAndroidPermission","requestIOSPermission","services","getSpeechRecognitionServices","granted","RECORD_AUDIO","title","buttonPositive","buttonNegative","GRANTED","micPermission","IOS","MICROPHONE","speechPermission","SPEECH_RECOGNITION","resetStoredPermission","cleanup","then","removeAllListeners","catch"],"sourceRoot":"../../../src","sources":["utils/audioRecorder.js"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../../../src/components/voice.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../../../src/components/voice.js"],"names":[],"mappings":"AAqBO,iDAqKN;kBAxLsD,OAAO"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export function setPermissionStatusHandlers(getter: any, setter: any): void;
|
|
1
2
|
export function initVoice(onResult: any): Promise<boolean>;
|
|
2
3
|
export function startRecording(): Promise<boolean>;
|
|
3
4
|
export function stopRecording(): Promise<void>;
|
|
4
5
|
export function cancelRecording(): Promise<void>;
|
|
5
6
|
export function requestAudioPermission(): Promise<boolean>;
|
|
7
|
+
export function resetStoredPermission(): boolean;
|
|
6
8
|
export function cleanup(): void;
|
|
7
9
|
//# sourceMappingURL=audioRecorder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioRecorder.d.ts","sourceRoot":"","sources":["../../../src/utils/audioRecorder.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audioRecorder.d.ts","sourceRoot":"","sources":["../../../src/utils/audioRecorder.js"],"names":[],"mappings":"AAqBA,4EAGC;AAGD,2DAsGC;AAuED,mDAmBC;AAED,+CAoCC;AAED,iDASC;AAED,2DA2BC;AAmDD,iDAMC;AAED,gCASC"}
|
package/package.json
CHANGED
package/src/components/voice.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// VoiceButton.js
|
|
2
2
|
|
|
3
3
|
import React, { useState, useContext, useEffect } from 'react';
|
|
4
|
-
import { TouchableOpacity, ActivityIndicator, StyleSheet, Alert } from 'react-native';
|
|
4
|
+
import { TouchableOpacity, ActivityIndicator, StyleSheet, Alert, Linking, Platform } from 'react-native';
|
|
5
5
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
6
|
+
import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
startRecording,
|
|
@@ -10,60 +11,129 @@ import {
|
|
|
10
11
|
cancelRecording,
|
|
11
12
|
requestAudioPermission,
|
|
12
13
|
cleanup,
|
|
13
|
-
initVoice
|
|
14
|
+
initVoice,
|
|
15
|
+
resetStoredPermission,
|
|
16
|
+
setPermissionStatusHandlers
|
|
14
17
|
} from '../utils/audioRecorder';
|
|
15
18
|
import { AppContext } from '../contexts/AppContext';
|
|
16
19
|
|
|
20
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
21
|
+
|
|
17
22
|
export const VoiceButton = () => {
|
|
18
23
|
const { handleVoiceSend } = useContext(AppContext);
|
|
19
24
|
const [isListening, setIsListening] = useState(false);
|
|
20
25
|
const [loading, setLoading] = useState(false);
|
|
26
|
+
const [permissionChecked, setPermissionChecked] = useState(false);
|
|
27
|
+
const [hasPermission, setHasPermission] = useState(null);
|
|
28
|
+
|
|
29
|
+
// Use your custom AsyncStorage hook
|
|
30
|
+
const [permissionStatus, setPermissionStatus] = useAsyncStorage(PERMISSION_STORAGE_KEY, null);
|
|
21
31
|
|
|
22
32
|
useEffect(() => {
|
|
33
|
+
// Register our permission handlers
|
|
34
|
+
setPermissionStatusHandlers(() => permissionStatus, setPermissionStatus);
|
|
35
|
+
|
|
23
36
|
const setupVoice = async () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
try {
|
|
38
|
+
// Check stored permission first
|
|
39
|
+
if (permissionStatus === 'denied') {
|
|
40
|
+
// We already know permission was denied, don't show alert again
|
|
41
|
+
setHasPermission(false);
|
|
42
|
+
setPermissionChecked(true);
|
|
29
43
|
return;
|
|
30
44
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
|
|
46
|
+
// Only request permission if not already denied
|
|
47
|
+
const permissionResult = await requestAudioPermission();
|
|
48
|
+
setHasPermission(permissionResult);
|
|
49
|
+
|
|
50
|
+
if (permissionResult) {
|
|
51
|
+
const initialized = await initVoice((result, error) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
// Don't show alert for permission errors since we handle that elsewhere
|
|
54
|
+
if (!error.includes('permission')) {
|
|
55
|
+
Alert.alert('Error', error);
|
|
56
|
+
}
|
|
57
|
+
setIsListening(false);
|
|
58
|
+
setLoading(false);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (result) {
|
|
62
|
+
handleVoiceSend(null, result);
|
|
63
|
+
}
|
|
64
|
+
// Always reset states when the recognition ends
|
|
65
|
+
setIsListening(false);
|
|
66
|
+
setLoading(false);
|
|
67
|
+
});
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
if (!initialized) {
|
|
70
|
+
// Only show this alert once per session
|
|
71
|
+
if (!permissionChecked) {
|
|
72
|
+
Alert.alert(
|
|
73
|
+
'Error',
|
|
74
|
+
'Speech recognition is not available on this device'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Error in setupVoice:', error);
|
|
81
|
+
} finally {
|
|
82
|
+
setPermissionChecked(true);
|
|
43
83
|
}
|
|
44
84
|
};
|
|
45
85
|
|
|
46
|
-
|
|
86
|
+
if (!permissionChecked) {
|
|
87
|
+
setupVoice();
|
|
88
|
+
}
|
|
47
89
|
|
|
48
90
|
return () => {
|
|
49
91
|
cleanup();
|
|
50
92
|
};
|
|
51
|
-
}, []);
|
|
93
|
+
}, [permissionStatus, permissionChecked]);
|
|
52
94
|
|
|
53
95
|
const toggleRecording = async () => {
|
|
54
96
|
try {
|
|
55
97
|
if (!isListening) {
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
Alert.alert(
|
|
98
|
+
// If we already know we don't have permission, show a better message
|
|
99
|
+
if (hasPermission === false) {
|
|
100
|
+
Alert.alert(
|
|
101
|
+
'Permission Required',
|
|
102
|
+
'Voice recognition requires microphone permission. Would you like to update your settings?',
|
|
103
|
+
[
|
|
104
|
+
{
|
|
105
|
+
text: 'Cancel',
|
|
106
|
+
style: 'cancel'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
text: 'Settings',
|
|
110
|
+
onPress: () => {
|
|
111
|
+
// Reset stored permission so we can check again
|
|
112
|
+
setPermissionStatus(null);
|
|
113
|
+
// Open device settings
|
|
114
|
+
openAppSettings();
|
|
115
|
+
// Reset our permission check
|
|
116
|
+
setPermissionChecked(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
);
|
|
59
121
|
return;
|
|
60
122
|
}
|
|
61
123
|
|
|
62
124
|
setLoading(true);
|
|
125
|
+
const checkPermission = await requestAudioPermission();
|
|
126
|
+
if (!checkPermission) {
|
|
127
|
+
setHasPermission(false);
|
|
128
|
+
setLoading(false);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
const started = await startRecording();
|
|
64
133
|
if (started) {
|
|
65
134
|
setIsListening(true);
|
|
66
135
|
} else {
|
|
136
|
+
// Only show error if not permission related
|
|
67
137
|
Alert.alert('Error', 'Failed to start voice recognition');
|
|
68
138
|
}
|
|
69
139
|
} else {
|
|
@@ -78,7 +148,21 @@ export const VoiceButton = () => {
|
|
|
78
148
|
await cleanup();
|
|
79
149
|
} finally {
|
|
80
150
|
setLoading(false);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
81
153
|
|
|
154
|
+
const openAppSettings = async () => {
|
|
155
|
+
try {
|
|
156
|
+
if (Platform.OS === 'ios') {
|
|
157
|
+
// For iOS
|
|
158
|
+
await Linking.openURL('app-settings://');
|
|
159
|
+
} else {
|
|
160
|
+
// For Android
|
|
161
|
+
await Linking.openSettings();
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('Cannot open settings', error);
|
|
165
|
+
Alert.alert('Error', 'Unable to open settings. Please open settings manually.');
|
|
82
166
|
}
|
|
83
167
|
};
|
|
84
168
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Platform, PermissionsAndroid } from 'react-native';
|
|
4
4
|
import Voice from '@react-native-community/voice';
|
|
5
5
|
import { check, PERMISSIONS, request, RESULTS } from 'react-native-permissions';
|
|
6
|
+
import useAsyncStorage from '../hooks/useAsyncStorage';
|
|
6
7
|
|
|
7
8
|
let resultCallback = null;
|
|
8
9
|
let silenceTimer = null;
|
|
@@ -10,6 +11,19 @@ let isCurrentlyRecording = false;
|
|
|
10
11
|
let finalResult = '';
|
|
11
12
|
const SILENCE_DURATION = 1500; // 1.5 seconds of silence before stopping
|
|
12
13
|
|
|
14
|
+
// Add this constant for AsyncStorage key
|
|
15
|
+
const PERMISSION_STORAGE_KEY = '@voice_permission_status';
|
|
16
|
+
|
|
17
|
+
// Create a function that can be called to get permission status
|
|
18
|
+
// This needs to be outside the React component lifecycle
|
|
19
|
+
let permissionStatusGetter = null;
|
|
20
|
+
let permissionStatusSetter = null;
|
|
21
|
+
|
|
22
|
+
export function setPermissionStatusHandlers(getter, setter) {
|
|
23
|
+
permissionStatusGetter = getter;
|
|
24
|
+
permissionStatusSetter = setter;
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
// Initialize Voice handlers
|
|
14
28
|
export async function initVoice(onResult) {
|
|
15
29
|
try {
|
|
@@ -60,15 +74,25 @@ export async function initVoice(onResult) {
|
|
|
60
74
|
Voice.onSpeechError = async (e) => {
|
|
61
75
|
console.error('onSpeechError: ', e);
|
|
62
76
|
|
|
63
|
-
|
|
64
77
|
if (silenceTimer) {
|
|
65
78
|
clearTimeout(silenceTimer);
|
|
66
79
|
silenceTimer = null;
|
|
67
80
|
}
|
|
68
81
|
|
|
82
|
+
// Check for "No speech detected" error
|
|
83
|
+
const isNoSpeechError = e.error?.code === "recognition_fail" &&
|
|
84
|
+
e.error?.message?.includes("No speech detected");
|
|
69
85
|
|
|
70
86
|
await cleanupVoiceSession();
|
|
71
|
-
|
|
87
|
+
|
|
88
|
+
// Only send error to callback if it's not a "No speech detected" error
|
|
89
|
+
if (!isNoSpeechError) {
|
|
90
|
+
resultCallback(null, e.error?.message || 'Speech recognition error');
|
|
91
|
+
} else {
|
|
92
|
+
console.log('No speech detected, ignoring error');
|
|
93
|
+
// Optionally, call the callback with null parameters or a special indicator
|
|
94
|
+
resultCallback(null, null); // This won't trigger an error alert in the component
|
|
95
|
+
}
|
|
72
96
|
};
|
|
73
97
|
|
|
74
98
|
Voice.onSpeechResults = (e) => {
|
|
@@ -245,13 +269,32 @@ export async function cancelRecording() {
|
|
|
245
269
|
}
|
|
246
270
|
|
|
247
271
|
export async function requestAudioPermission() {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
272
|
+
try {
|
|
273
|
+
// Get stored permission if available
|
|
274
|
+
const storedPermission = permissionStatusGetter ? permissionStatusGetter() : null;
|
|
275
|
+
|
|
276
|
+
if (storedPermission === 'denied') {
|
|
277
|
+
console.log('Permission previously denied by user');
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let permissionResult = false;
|
|
282
|
+
if (Platform.OS === 'android') {
|
|
283
|
+
permissionResult = await requestAndroidPermission();
|
|
284
|
+
} else if (Platform.OS === 'ios') {
|
|
285
|
+
permissionResult = await requestIOSPermission();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Store the result
|
|
289
|
+
if (permissionStatusSetter) {
|
|
290
|
+
permissionStatusSetter(permissionResult ? 'granted' : 'denied');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return permissionResult;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error('Error checking stored permission:', error);
|
|
296
|
+
return false;
|
|
252
297
|
}
|
|
253
|
-
|
|
254
|
-
return false;
|
|
255
298
|
}
|
|
256
299
|
|
|
257
300
|
async function requestAndroidPermission() {
|
|
@@ -267,23 +310,19 @@ async function requestAndroidPermission() {
|
|
|
267
310
|
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
|
|
268
311
|
{
|
|
269
312
|
title: 'Microphone Permission',
|
|
270
|
-
|
|
271
313
|
message: 'This app needs access to your microphone for voice recognition.',
|
|
272
|
-
|
|
273
314
|
buttonPositive: 'OK',
|
|
274
315
|
buttonNegative: 'Cancel',
|
|
275
316
|
}
|
|
276
317
|
);
|
|
277
|
-
|
|
318
|
+
|
|
278
319
|
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
|
279
320
|
} catch (error) {
|
|
280
321
|
console.error('Error requesting Android permission:', error);
|
|
281
|
-
|
|
282
322
|
return false;
|
|
283
323
|
}
|
|
284
324
|
}
|
|
285
325
|
|
|
286
|
-
|
|
287
326
|
async function requestIOSPermission() {
|
|
288
327
|
try {
|
|
289
328
|
// Request microphone permission
|
|
@@ -307,6 +346,14 @@ async function requestIOSPermission() {
|
|
|
307
346
|
}
|
|
308
347
|
}
|
|
309
348
|
|
|
349
|
+
export function resetStoredPermission() {
|
|
350
|
+
if (permissionStatusSetter) {
|
|
351
|
+
permissionStatusSetter(null);
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
310
357
|
export function cleanup() {
|
|
311
358
|
Voice.destroy().then(() => {
|
|
312
359
|
Voice.removeAllListeners();
|