Skip to content

Commit 057e06d

Browse files
authored
feat(Expo): Added Expo config plugin (#317)
* Created config plugin * Updated example * Updated docs * Improved prepare script * Remove empty testing * fixed up package.json * Update README.md * Fixed prop names * Bump config-plugins * Updated docs * Update README.md * Revert example * clean up lock * Pull ems out
1 parent ced30f8 commit 057e06d

File tree

7 files changed

+366
-48
lines changed

7 files changed

+366
-48
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ VoiceModule.iml
1616
yarn-error.log
1717

1818
dist
19+
20+
plugin/build

README.md

+41-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
```sh
1212
yarn add @react-native-voice/voice
1313

14-
# or
14+
# or
1515

1616
npm i @react-native-voice/voice --save
1717
```
@@ -27,6 +27,7 @@ npx pod-install
2727
- [Linking](#linking)
2828
- [Manually Link Android](#manually-link-android)
2929
- [Manually Link iOS](#manually-link-ios)
30+
- [Prebuild Plugin](#prebuild-plugin)
3031
- [Usage](#usage)
3132
- [Example](#example)
3233
- [API](#api)
@@ -93,6 +94,45 @@ public class MainActivity extends Activity implements ReactApplication {
9394

9495
- Click on your main project file (the one that represents the .xcodeproj) select Build Phases and drag the static library, lib.Voice.a, from the Libraries/Voice.xcodeproj/Products folder to Link Binary With Libraries
9596

97+
<h2 align="center">Prebuild Plugin</h2>
98+
99+
> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/).
100+
101+
After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`:
102+
103+
```json
104+
{
105+
"expo": {
106+
"plugins": ["@react-native-voice/voice"]
107+
}
108+
}
109+
```
110+
111+
Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide.
112+
113+
### Props
114+
115+
The plugin provides props for extra customization. Every time you change the props or plugins, you'll need to rebuild (and `prebuild`) the native app. If no extra properties are added, defaults will be used.
116+
117+
- `speechRecognition` (_string | false_): Sets the message for the `NSSpeechRecognitionUsageDescription` key in the `Info.plist` message. When undefined, a default permission message will be used. When `false`, the permission will be skipped.
118+
- `microphone` (_string | false_): Sets the message for the `NSMicrophoneUsageDescription` key in the `Info.plist`. When undefined, a default permission message will be used. When `false`, the `android.permission.RECORD_AUDIO` will not be added to the `AndroidManifest.xml` and the iOS permission will be skipped.
119+
120+
### Example
121+
122+
```json
123+
{
124+
"plugins": [
125+
[
126+
"@react-native-voice/voice",
127+
{
128+
"microphonePermission": "CUSTOM: Allow $(PRODUCT_NAME) to access the microphone",
129+
"speechRecognitionPermission": "CUSTOM: Allow $(PRODUCT_NAME) to securely recognize user speech"
130+
}
131+
]
132+
]
133+
}
134+
```
135+
96136
<h2 align="center">Usage</h2>
97137

98138
<p align="center"><a href="https://github.com/react-native-voice/voice/blob/master/example/src/VoiceTest.tsx">Full example for Android and iOS.</a></p>

app.plugin.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./plugin/build/withVoice');

package.json

+8-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"devDependencies": {
99
"@react-native-community/eslint-config": "^0.0.7",
1010
"@semantic-release/git": "^9.0.0",
11+
"@tsconfig/node10": "^1.0.7",
1112
"@types/invariant": "^2.2.31",
1213
"@types/react-native": "0.61.17",
1314
"eslint": "^6.8.0",
@@ -30,6 +31,7 @@
3031
"peerDependencies": {
3132
"react-native": ">= 0.60.2"
3233
},
34+
3335
"repository": {
3436
"type": "git",
3537
"url": "git://github.com/react-native-voice/voice.git"
@@ -40,12 +42,15 @@
4042
"start": "yarn --cwd example start",
4143
"ios": "yarn --cwd example ios",
4244
"android": "yarn --cwd example android",
43-
"prepare": "yarn build",
45+
"prepare": "yarn build && yarn build:plugin",
4446
"build": "tsc",
47+
"dev-sync": "cp -r ./dist example/node_modules/@react-native-voice/voice",
4548
"type-check": "tsc -noEmit",
46-
"dev-sync": "cp -r ./dist example/node_modules/@react-native-voice/voice"
49+
"build:plugin": "tsc --build plugin",
50+
"lint:plugin": "eslint plugin/src/*"
4751
},
4852
"dependencies": {
49-
"invariant": "^2.2.4"
53+
"invariant": "^2.2.4",
54+
"@expo/config-plugins": "^2.0.0"
5055
}
5156
}

plugin/src/withVoice.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
AndroidConfig,
3+
ConfigPlugin,
4+
createRunOncePlugin,
5+
withInfoPlist,
6+
} from '@expo/config-plugins';
7+
8+
const pkg = require('@react-native-voice/voice/package.json');
9+
10+
const MICROPHONE = 'Allow $(PRODUCT_NAME) to access the microphone';
11+
12+
const SPEECH_RECOGNITION =
13+
'Allow $(PRODUCT_NAME) to securely recognize user speech';
14+
15+
export type Props = {
16+
/**
17+
* `NSSpeechRecognitionUsageDescription` message.
18+
*/
19+
speechRecognitionPermission?: string | false;
20+
21+
/**
22+
* `NSMicrophoneUsageDescription` message.
23+
*/
24+
microphonePermission?: string | false;
25+
};
26+
27+
/**
28+
* Adds `NSMicrophoneUsageDescription` and `NSSpeechRecognitionUsageDescription` to the `Info.plist`.
29+
*
30+
* @param props.speechRecognitionPermission speech recognition message
31+
* @param props.microphonePermission microphone permission message
32+
* @returns
33+
*/
34+
const withIosPermissions: ConfigPlugin<Props> = (
35+
c,
36+
{ microphonePermission, speechRecognitionPermission } = {},
37+
) => {
38+
return withInfoPlist(c, config => {
39+
if (microphonePermission !== false) {
40+
config.modResults.NSMicrophoneUsageDescription =
41+
microphonePermission ||
42+
config.modResults.NSMicrophoneUsageDescription ||
43+
MICROPHONE;
44+
}
45+
if (speechRecognitionPermission !== false) {
46+
config.modResults.NSSpeechRecognitionUsageDescription =
47+
speechRecognitionPermission ||
48+
config.modResults.NSSpeechRecognitionUsageDescription ||
49+
SPEECH_RECOGNITION;
50+
}
51+
52+
return config;
53+
});
54+
};
55+
56+
/**
57+
* Adds the following to the `AndroidManifest.xml`: `<uses-permission android:name="android.permission.RECORD_AUDIO" />`
58+
*/
59+
const withAndroidPermissions: ConfigPlugin = config => {
60+
return AndroidConfig.Permissions.withPermissions(config, [
61+
'android.permission.RECORD_AUDIO',
62+
]);
63+
};
64+
65+
const withVoice: ConfigPlugin<Props | void> = (config, props = {}) => {
66+
const _props = props ? props : {};
67+
config = withIosPermissions(config, _props);
68+
if (_props.microphonePermission !== false) {
69+
config = withAndroidPermissions(config);
70+
}
71+
return config;
72+
};
73+
74+
export default createRunOncePlugin(withVoice, pkg.name, pkg.version);

plugin/tsconfig.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "@tsconfig/node10/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "build",
5+
"rootDir": "src",
6+
"declaration": true
7+
},
8+
"include": ["./src"],
9+
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
10+
}
11+

0 commit comments

Comments
 (0)