소스 검색

added android support 🎉🎉

Yonah Forst 8 년 전
부모
커밋
eb87369148

+ 90
- 10
README.md 파일 보기

@@ -2,16 +2,16 @@
2 2
 Request user permissions from React Native (iOS only - android coming soon)
3 3
 
4 4
 The current supported permissions are:
5
-- Push Notifications
6 5
 - Location
7 6
 - Camera
8 7
 - Microhone
9 8
 - Photos
10 9
 - Contacts
11 10
 - Events
12
-- Reminders
13
-- Bluetooth (Peripheral role. Don't use for Central only)
14
-- Background Refresh
11
+- Reminders *(iOS only)*
12
+- Bluetooth *(iOS only)*
13
+- Push Notifications *(iOS only)*
14
+- Background Refresh *(iOS only)*
15 15
 
16 16
 ##General Usage
17 17
 ```js
@@ -72,10 +72,32 @@ const Permissions = require('react-native-permissions');
72 72
 
73 73
 ##API
74 74
 
75
-_Permission statuses_ - `authorized`, `denied`, `restricted`, or `undetermined`
75
+###Permission statuses
76
+Promises resolve into one of these statuses
76 77
 
77
-_Permission types_ - `location`, `camera`, `microphone`, `photo`, `contacts`, `event`, `reminder`, `bluetooth`, `notification`, or `backgroundRefresh`
78
+| Return value | Notes|
79
+|---|---|
80
+|`authorized`| user has authorized this permission |
81
+|`denied`| user has denied permissions at least once. On iOS this means that the user will not be prompted again. Android users can be promted multiple times until they select 'Never ask me again'|
82
+|`restricted`| iOS only|
83
+|`undetermined`| user has not yet been prompted with a permission dialog |
78 84
 
85
+###Supported permission types
86
+
87
+| Name | iOS | Android |
88
+|---|---|---|
89
+|`location`| ✔️ | ✔ |
90
+|`camera`| ✔️ | ✔ |
91
+|`microphone`| ✔️ | ✔ |
92
+|`photo`| ✔️ | ✔ |
93
+|`contacts`| ✔️ | ✔ |
94
+|`event`| ✔️ | ✔ |
95
+|`bluetooth`| ✔️ | ❌ |
96
+|`reminder`| ✔️ | ❌ |
97
+|`notification`| ✔️ | ❌ |
98
+|`backgroundRefresh`| ✔️ | ❌ |
99
+
100
+###Methods
79 101
 | Method Name | Arguments | Notes
80 102
 |---|---|---|
81 103
 | `getPermissionStatus` | `type` | - Returns a promise with the permission status. Note: for type `location`, iOS `AuthorizedAlways` and `AuthorizedWhenInUse` both return `authorized` |
@@ -85,9 +107,8 @@ _Permission types_ - `location`, `camera`, `microphone`, `photo`, `contacts`, `e
85 107
 | `openSettings` | *none* | - Switches the user to the settings page of your app (iOS 8.0 and later)  |
86 108
 | `canOpenSettings` | *none* | - Returns a boolean indicating if the device supports switching to the settings page |
87 109
 
88
-Note: Permission type `bluetooth` represents the status of the `CBPeripheralManager`. Don't use this if you're only using `CBCentralManager`
89
-
90
-###Special cases
110
+###iOS Notes
111
+Permission type `bluetooth` represents the status of the `CBPeripheralManager`. Don't use this if only need `CBCentralManager`
91 112
 
92 113
 `requestPermission` also accepts a second parameter for types `location` and `notification`.
93 114
 - `location`: the second parameter is a string, either `always` or `whenInUse`(default).
@@ -105,14 +126,73 @@ Note: Permission type `bluetooth` represents the status of the `CBPeripheralMana
105 126
       })
106 127
 ```
107 128
 
129
+###Android Notes
130
+All required permissions also need to be included in the Manifest before they can be requested. Otherwise `requestPermission` will immediately return `denied`.
131
+
132
+Permissions are automatically accepted for targetSdkVersion < 23 but you can still use `getPermissionStatus` to check if the user has disabled them from Settings.
133
+
134
+Here's a map of types to Android system permissions names:  
135
+`location` -> `android.permission.ACCESS_FINE_LOCATION`  
136
+`camera` -> `android.permission.CAMERA`  
137
+`microphone` -> `android.permission.RECORD_AUDIO`  
138
+`photo` -> `android.permission.READ_EXTERNAL_STORAGE`  
139
+`contacts` -> `android.permission.READ_CONTACTS`  
140
+`event` -> `android.permission.READ_CALENDAR`  
141
+
142
+You can request write access to any of these types by also including the appropriate write permission in the Manifest. Read more here: https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
108 143
 
109 144
 ##Setup
110 145
 
111 146
 ````
112 147
 npm install --save react-native-permissions
148
+rnpm link
113 149
 ````
114 150
 
115
-##iOS
151
+###Or manualy linking   
152
+
153
+####iOS
116 154
 * Run open node_modules/react-native-permissions
117 155
 * Drag ReactNativePermissions.xcodeproj into the Libraries group of your app's Xcode project
118 156
 * Add libReactNativePermissions.a to `Build Phases -> Link Binary With Libraries.
157
+
158
+####Android
159
+#####Step 1 - Update Gradle Settings
160
+
161
+```
162
+// file: android/settings.gradle
163
+...
164
+
165
+include ':react-native-permissions'
166
+project(':react-native-permissions').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-permissions/android')
167
+```
168
+#####Step 2 - Update Gradle Build
169
+
170
+```
171
+// file: android/app/build.gradle
172
+...
173
+
174
+dependencies {
175
+    ...
176
+    compile project(':react-native-permissions')
177
+}
178
+```
179
+#####Step 3 - Register React Package
180
+```
181
+...
182
+import com.joshblour.reactnativepermissions.ReactNativePermissionsPackage; // <--- import
183
+
184
+public class MainActivity extends ReactActivity {
185
+
186
+    ...
187
+
188
+    @Override
189
+    protected List<ReactPackage> getPackages() {
190
+        return Arrays.<ReactPackage>asList(
191
+            new MainReactPackage(),
192
+            new ReactNativePermissionsPackage() // <------ add the package
193
+        );
194
+    }
195
+
196
+    ...
197
+}
198
+```

+ 0
- 3
ReactNativePermissions.android.js 파일 보기

@@ -1,3 +0,0 @@
1
-'use strict';
2
-
3
-module.exports = {};

ReactNativePermissions.ios.js → ReactNativePermissions.js 파일 보기

@@ -1,20 +1,31 @@
1 1
 'use strict';
2 2
 
3
-var React = require('react-native');
4
-var RNPermissions = React.NativeModules.ReactNativePermissions;
3
+var ReactNative = require('react-native')
4
+var Platform = ReactNative.Platform
5
+var RNPermissions = ReactNative.NativeModules.ReactNativePermissions;
5 6
 
6
-const RNPTypes = [
7
-	'location',
8
-	'camera',
9
-	'microphone',
10
-	'photo',
11
-	'contacts',
12
-	'event',
13
-	'reminder',
14
-	'bluetooth',
15
-	'notification',
16
-	'backgroundRefresh', 
17
-]
7
+const RNPTypes = {
8
+	ios: [
9
+		'location',
10
+		'camera',
11
+		'microphone',
12
+		'photo',
13
+		'contacts',
14
+		'event',
15
+		'reminder',
16
+		'bluetooth',
17
+		'notification',
18
+		'backgroundRefresh', 
19
+	],
20
+	android: [
21
+		'location',
22
+		'camera',
23
+		'microphone',
24
+		'contacts',
25
+		'event',
26
+		'photos',
27
+	]
28
+}
18 29
 
19 30
 class ReactNativePermissions {
20 31
 	constructor() {
@@ -24,7 +35,7 @@ class ReactNativePermissions {
24 35
 		this.StatusAuthorized = 'authorized'
25 36
 		this.StatusRestricted = 'restricted'
26 37
 
27
-		RNPTypes.forEach(type => {
38
+		this.getPermissionTypes().forEach(type => {
28 39
 			let methodName = `${type}PermissionStatus`
29 40
 			this[methodName] = p => {
30 41
 				console.warn(`ReactNativePermissions: ${methodName} is depricated. Use getPermissionStatus('${type}') instead.`)
@@ -42,42 +53,31 @@ class ReactNativePermissions {
42 53
 	}
43 54
 
44 55
 	getPermissionTypes() {
45
-		return RNPTypes;
56
+		return RNPTypes[Platform.OS];
46 57
 	}
47 58
 
48 59
 	getPermissionStatus(permission) {
49
-		if (RNPTypes.includes(permission)) {
60
+		if (this.getPermissionTypes().includes(permission)) {
50 61
 			return RNPermissions.getPermissionStatus(permission)
51 62
 		} else {
52
-			return Promise.reject(`ReactNativePermissions: ${permission} is not a valid permission type`)
63
+			return Promise.reject(`ReactNativePermissions: ${permission} is not a valid permission type on ${Platform.OS}`)
53 64
 		}
54 65
 	}
55 66
 
56 67
 	requestPermission(permission, type) {
57
-		switch (permission) {
58
-			case "location":
59
-				return RNPermissions.requestLocation(type || 'always')
60
-			case "camera":
61
-				return RNPermissions.requestCamera();
62
-			case "microphone":
63
-				return RNPermissions.requestMicrophone();
64
-			case "photo":
65
-				return RNPermissions.requestPhoto();
66
-			case "contacts":
67
-				return RNPermissions.requestContacts();
68
-			case "event":
69
-				return RNPermissions.requestEvent();
70
-			case "reminder":
71
-				return RNPermissions.requestReminder();
72
-			case "bluetooth":
73
-				return RNPermissions.requestBluetooth();
74
-			case "notification":
75
-				return RNPermissions.requestNotification(type || ['alert', 'badge', 'sound'])
76
-			case "backgroundRefresh":
77
-				return Promise.reject('You cannot request backgroundRefresh')
78
-			default:
79
-				return Promise.reject('invalid type: ' + type)
68
+		let options; 
69
+
70
+		if (!this.getPermissionTypes().includes(permission)) {
71
+			return Promise.reject(`ReactNativePermissions: ${permission} is not a valid permission type on ${Platform.OS}`)
72
+		} else if (permission == 'backgroundRefresh'){
73
+			return Promise.reject('You cannot request backgroundRefresh')
74
+		} else if (permission == 'location') {
75
+			options = type || 'always'
76
+		} else if (permission == 'notification') {
77
+			options = type || ['alert', 'badge', 'sound']
80 78
 		}
79
+
80
+		return RNPermissions.requestPermission(permission, options)
81 81
 	}
82 82
 
83 83
 	//recursive funciton to chain a promises for a list of permissions

+ 38
- 35
ReactNativePermissions.m 파일 보기

@@ -109,17 +109,52 @@ RCT_REMAP_METHOD(getPermissionStatus, getPermissionStatus:(RNPType)type resolve:
109 109
     resolve(status);
110 110
 }
111 111
 
112
-RCT_REMAP_METHOD(requestLocation, requestLocation:(NSString *)type resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
112
+RCT_REMAP_METHOD(requestPermission, permissionType:(RNPType)type json:(id)json resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
113
+{
114
+    NSString *status;
115
+    
116
+    switch (type) {
117
+        case RNPTypeLocation:
118
+            return [self requestLocation:json resolve:resolve];
119
+        case RNPTypeCamera:
120
+            return [RNPAudioVideo request:@"video" completionHandler:resolve];
121
+        case RNPTypeMicrophone:
122
+            return [RNPAudioVideo request:@"audio" completionHandler:resolve];
123
+        case RNPTypePhoto:
124
+            return [RNPPhoto request:resolve];
125
+        case RNPTypeContacts:
126
+            return [RNPContacts request:resolve];
127
+        case RNPTypeEvent:
128
+            return [RNPEvent request:@"event" completionHandler:resolve];
129
+        case RNPTypeReminder:
130
+            return [RNPEvent request:@"reminder" completionHandler:resolve];
131
+        case RNPTypeBluetooth:
132
+            return [self requestBluetooth:resolve];
133
+        case RNPTypeNotification:
134
+            return [self requestNotification:json resolve:resolve];
135
+        default:
136
+            break;
137
+    }
138
+    
139
+
140
+}
141
+
142
+
143
+- (void) requestLocation:(id)json resolve:(RCTPromiseResolveBlock)resolve
113 144
 {
114 145
     if (self.locationMgr == nil) {
115 146
         self.locationMgr = [[RNPLocation alloc] init];
116 147
     }
117 148
     
149
+    NSString *type = [RCTConvert NSString:json];
150
+    
118 151
     [self.locationMgr request:type completionHandler:resolve];
119 152
 }
120 153
 
121
-RCT_REMAP_METHOD(requestNotification, requestNotification:(NSArray *)typeStrings resolve:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
154
+- (void) requestNotification:(id)json resolve:(RCTPromiseResolveBlock)resolve
122 155
 {
156
+    NSArray *typeStrings = [RCTConvert NSArray:json];
157
+    
123 158
     UIUserNotificationType types;
124 159
     if ([typeStrings containsObject:@"alert"])
125 160
         types = types | UIUserNotificationTypeAlert;
@@ -140,7 +175,7 @@ RCT_REMAP_METHOD(requestNotification, requestNotification:(NSArray *)typeStrings
140 175
 }
141 176
 
142 177
 
143
-RCT_REMAP_METHOD(requestBluetooth, requestBluetooth:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
178
+- (void) requestBluetooth:(RCTPromiseResolveBlock)resolve
144 179
 {
145 180
     if (self.bluetoothMgr == nil) {
146 181
         self.bluetoothMgr = [[RNPBluetooth alloc] init];
@@ -149,38 +184,6 @@ RCT_REMAP_METHOD(requestBluetooth, requestBluetooth:(RCTPromiseResolveBlock)reso
149 184
     [self.bluetoothMgr request:resolve];
150 185
 }
151 186
 
152
-RCT_REMAP_METHOD(requestCamera, requestCamera:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
153
-{
154
-    [RNPAudioVideo request:@"video" completionHandler:resolve];
155
-}
156
-
157
-RCT_REMAP_METHOD(requestMicrophone, requestMicrophone:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
158
-{
159
-    [RNPAudioVideo request:@"audio" completionHandler:resolve];
160
-}
161
-
162
-RCT_REMAP_METHOD(requestEvent, requestEvents:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
163
-{
164
-    [RNPEvent request:@"event" completionHandler:resolve];
165
-}
166
-
167
-RCT_REMAP_METHOD(requestReminder, requestReminders:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
168
-{
169
-    [RNPEvent request:@"reminder" completionHandler:resolve];
170
-}
171
-
172
-RCT_REMAP_METHOD(requestPhoto, requestPhoto:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
173
-{
174
-    [RNPPhoto request:resolve];
175
-}
176
-
177
-RCT_REMAP_METHOD(requestContacts, requestContacts:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
178
-{
179
-    [RNPContacts request:resolve];
180
-}
181
-
182
-
183
-
184 187
 
185 188
 
186 189
 

+ 34
- 0
android/build.gradle 파일 보기

@@ -0,0 +1,34 @@
1
+buildscript {
2
+    repositories {
3
+        jcenter()
4
+    }
5
+
6
+    dependencies {
7
+        classpath 'com.android.tools.build:gradle:2.1.+'
8
+    }
9
+}
10
+
11
+apply plugin: 'com.android.library'
12
+
13
+android {
14
+    compileSdkVersion 23
15
+    buildToolsVersion "23.0.1"
16
+
17
+    defaultConfig {
18
+        minSdkVersion 18
19
+        targetSdkVersion 23
20
+        versionCode 1
21
+        versionName "1.0"
22
+    }
23
+    lintOptions {
24
+        abortOnError false
25
+    }
26
+}
27
+
28
+repositories {
29
+    jcenter()
30
+}
31
+
32
+dependencies {
33
+    compile 'com.facebook.react:react-native:+'
34
+}

+ 3
- 0
android/src/main/AndroidManifest.xml 파일 보기

@@ -0,0 +1,3 @@
1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+    package="com.joshblour.reactnativepermissions">
3
+</manifest>

+ 128
- 0
android/src/main/java/com/joshblour/reactnativepermissions/ReactNativePermissionsModule.java 파일 보기

@@ -0,0 +1,128 @@
1
+package com.joshblour.reactnativepermissions;
2
+
3
+import android.Manifest;
4
+import android.content.Intent;
5
+import android.net.Uri;
6
+import android.provider.Settings;
7
+import android.support.v4.app.ActivityCompat;
8
+import android.support.v4.content.ContextCompat;
9
+import android.support.v4.content.PermissionChecker;
10
+
11
+import com.facebook.react.bridge.Callback;
12
+import com.facebook.react.bridge.Promise;
13
+import com.facebook.react.bridge.ReactApplicationContext;
14
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
15
+import com.facebook.react.bridge.ReactMethod;
16
+import com.facebook.react.bridge.ReadableMap;
17
+import com.facebook.react.bridge.ReadableArray;
18
+import com.facebook.react.modules.permissions.PermissionsModule;
19
+
20
+
21
+public class ReactNativePermissionsModule extends ReactContextBaseJavaModule {
22
+  private final ReactApplicationContext reactContext;
23
+  private final PermissionsModule mPermissionsModule;
24
+
25
+  public enum RNType {
26
+    LOCATION,
27
+    CAMERA,
28
+    MICROPHONE,
29
+    CONTACTS,
30
+    EVENT,
31
+    PHOTOS;
32
+  }
33
+
34
+  public ReactNativePermissionsModule(ReactApplicationContext reactContext) {
35
+    super(reactContext);
36
+    this.reactContext = reactContext;
37
+    mPermissionsModule = new PermissionsModule(this.reactContext);
38
+  }
39
+
40
+  @Override
41
+  public String getName() {
42
+    return "ReactNativePermissions";
43
+  }
44
+
45
+  @ReactMethod
46
+  public void getPermissionStatus(String permissionString, Promise promise) {
47
+    String permission = permissionForString(permissionString);
48
+
49
+    // check if permission is valid
50
+    if (permission == null) {
51
+      promise.reject("unknown-permission", "ReactNativePermissions: unknown permission type - " + permissionString);
52
+      return;
53
+    }
54
+
55
+    int result = PermissionChecker.checkSelfPermission(this.reactContext, permission);
56
+    switch (result) {
57
+      case PermissionChecker.PERMISSION_DENIED:
58
+        // PermissionDenied could also mean that we've never asked for permission yet.
59
+        // Use shouldShowRequestPermissionRationale to determined which on it is.
60
+        if (getCurrentActivity() != null) {
61
+          boolean deniedOnce = ActivityCompat.shouldShowRequestPermissionRationale(getCurrentActivity(), permission);
62
+          promise.resolve(deniedOnce ? "denied" : "undetermined");
63
+        } else {
64
+          promise.resolve("denied");
65
+        }
66
+        break;
67
+      case PermissionChecker.PERMISSION_DENIED_APP_OP:
68
+        promise.resolve("denied");
69
+        break;
70
+      case PermissionChecker.PERMISSION_GRANTED:
71
+        promise.resolve("authorized");
72
+        break;
73
+      default:
74
+        promise.resolve("undetermined");
75
+        break;
76
+    }
77
+  }
78
+
79
+  @ReactMethod
80
+  public void requestPermission(final String permissionString, String nullForiOSCompat, final Promise promise) {
81
+    String permission = permissionForString(permissionString);
82
+    mPermissionsModule.requestPermission(permission, new Callback() {
83
+      @Override
84
+      public void invoke(Object... args) {
85
+        getPermissionStatus(permissionString, promise);
86
+//        promise.resolve((boolean)args[1] ? "authorized" : "denied");
87
+      }
88
+    }, null);
89
+  }
90
+
91
+
92
+  @ReactMethod
93
+  public void canOpenSettings(Promise promise) {
94
+    promise.resolve(true);
95
+  }
96
+
97
+  @ReactMethod
98
+  public void openSettings() {
99
+    final Intent i = new Intent();
100
+    i.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
101
+    i.addCategory(Intent.CATEGORY_DEFAULT);
102
+    i.setData(Uri.parse("package:" + this.reactContext.getPackageName()));
103
+    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
104
+    i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
105
+    i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
106
+    this.reactContext.startActivity(i);
107
+  }
108
+
109
+  private String permissionForString(String permission) {
110
+    switch (RNType.valueOf(permission.toUpperCase())) {
111
+      case LOCATION:
112
+        return Manifest.permission.ACCESS_FINE_LOCATION;
113
+      case CAMERA:
114
+        return Manifest.permission.CAMERA;
115
+      case MICROPHONE:
116
+        return Manifest.permission.RECORD_AUDIO;
117
+      case CONTACTS:
118
+        return Manifest.permission.READ_CONTACTS;
119
+      case EVENT:
120
+        return Manifest.permission.READ_CALENDAR;
121
+      case PHOTOS:
122
+        return Manifest.permission.READ_EXTERNAL_STORAGE;
123
+      default:
124
+        return null;
125
+    }
126
+  }
127
+
128
+}

+ 28
- 0
android/src/main/java/com/joshblour/reactnativepermissions/ReactNativePermissionsPackage.java 파일 보기

@@ -0,0 +1,28 @@
1
+package com.joshblour.reactnativepermissions;
2
+
3
+import java.util.Arrays;
4
+import java.util.Collections;
5
+import java.util.List;
6
+
7
+import com.facebook.react.ReactPackage;
8
+import com.facebook.react.bridge.NativeModule;
9
+import com.facebook.react.bridge.ReactApplicationContext;
10
+import com.facebook.react.uimanager.ViewManager;
11
+import com.facebook.react.bridge.JavaScriptModule;
12
+
13
+public class ReactNativePermissionsPackage implements ReactPackage {
14
+    @Override
15
+    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
16
+      return Arrays.<NativeModule>asList(new ReactNativePermissionsModule(reactContext));
17
+    }
18
+
19
+    @Override
20
+    public List<Class<? extends JavaScriptModule>> createJSModules() {
21
+      return Collections.emptyList();
22
+    }
23
+
24
+    @Override
25
+    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
26
+      return Collections.emptyList();
27
+    }
28
+}