Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Location full accuracy (issue #489) #503

Merged
merged 8 commits into from
Oct 10, 2020
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ Then update your `Info.plist` with wanted permissions usage descriptions:
<string>YOUR TEXT</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>YOUR TEXT</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>YOUR-PURPOSE-KEY</key>
<string>YOUR TEXT</string>
</dict>
<key>NSMicrophoneUsageDescription</key>
<string>YOUR TEXT</string>
<key>NSMotionUsageDescription</key>
Expand Down Expand Up @@ -386,6 +391,7 @@ PERMISSIONS.IOS.CONTACTS;
PERMISSIONS.IOS.FACE_ID;
PERMISSIONS.IOS.LOCATION_ALWAYS;
PERMISSIONS.IOS.LOCATION_WHEN_IN_USE;
PERMISSIONS.IOS.LOCATION_FULL_ACCURACY;
PERMISSIONS.IOS.MEDIA_LIBRARY;
PERMISSIONS.IOS.MICROPHONE;
PERMISSIONS.IOS.MOTION;
Expand Down Expand Up @@ -466,9 +472,13 @@ type Rationale = {
buttonNeutral?: string;
};

type FullAccuracyOptionsIOS {
temporaryPurposeKey: string
}

function request(
permission: string,
rationale?: Rationale,
options?: Rationale | FullAccuracyOptionsIOS,
): Promise<PermissionStatus>;
```

Expand All @@ -488,7 +498,7 @@ Check notifications permission status and get notifications settings values.

```ts
interface NotificationSettings {
// properties only availables on iOS
// properties only available on iOS
// unavailable settings will not be included in the response object
alert?: boolean;
badge?: boolean;
Expand Down Expand Up @@ -530,7 +540,7 @@ type NotificationOption =
| 'provisional';

interface NotificationSettings {
// properties only availables on iOS
// properties only available on iOS
// unavailable settings will not be included in the response object
alert?: boolean;
badge?: boolean;
Expand Down
59 changes: 37 additions & 22 deletions example/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import React from 'react';
import {FlatList, Platform, StatusBar, Text, View} from 'react-native';
import {
AppState,
FlatList,
ListRenderItemInfo,
Platform,
StatusBar,
Text,
View,
} from 'react-native';
import {Appbar, List, TouchableRipple} from 'react-native-paper';
import RNPermissions, {
NotificationsResponse,
Expand Down Expand Up @@ -90,6 +98,11 @@ export default class App extends React.Component<{}, State> {

componentDidMount() {
this.check();
AppState.addEventListener('change', this.check);
}

componentWillUnmount() {
AppState.removeEventListener('change', this.check);
}

render() {
Expand Down Expand Up @@ -121,27 +134,8 @@ export default class App extends React.Component<{}, State> {

<FlatList
keyExtractor={(item) => item}
data={Object.keys(PLATFORM_PERMISSIONS)}
renderItem={({item, index}) => {
const value = PERMISSIONS_VALUES[index];
const status = this.state.statuses[value];

if (!status) {
return null;
}

return (
<PermissionRow
status={status}
name={item}
onPress={() => {
RNPermissions.request(value)
.then(() => this.check())
.catch((error) => console.error(error));
}}
/>
);
}}
data={PERMISSIONS_VALUES}
renderItem={this.renderPermissionItem}
/>

<View
Expand Down Expand Up @@ -181,4 +175,25 @@ export default class App extends React.Component<{}, State> {
</View>
);
}

renderPermissionItem = ({item, index}: ListRenderItemInfo<Permission>) => {
const value = PERMISSIONS_VALUES[index];
const status = this.state.statuses[value];

if (!status) {
return null;
}

return (
<PermissionRow
status={status}
name={item}
onPress={() => {
RNPermissions.request(value)
.then(() => this.check())
.catch((error) => console.error(error));
}}
/>
);
};
}
1 change: 1 addition & 0 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ target 'RNPermissionsExample' do
pod 'Permission-FaceID', :path => "#{permissions_path}/FaceID.podspec"
pod 'Permission-LocationAlways', :path => "#{permissions_path}/LocationAlways.podspec"
pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse.podspec"
pod 'Permission-LocationFullAccuracy', :path => "#{permissions_path}/LocationFullAccuracy.podspec"
pod 'Permission-MediaLibrary', :path => "#{permissions_path}/MediaLibrary.podspec"
pod 'Permission-Microphone', :path => "#{permissions_path}/Microphone.podspec"
pod 'Permission-Motion', :path => "#{permissions_path}/Motion.podspec"
Expand Down
8 changes: 7 additions & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ PODS:
- RNPermissions
- Permission-LocationAlways (2.2.1):
- RNPermissions
- Permission-LocationFullAccuracy (2.2.1):
- RNPermissions
- Permission-LocationWhenInUse (2.2.1):
- RNPermissions
- Permission-MediaLibrary (2.2.1):
Expand Down Expand Up @@ -364,6 +366,7 @@ DEPENDENCIES:
- Permission-Contacts (from `../node_modules/react-native-permissions/ios/Contacts.podspec`)
- Permission-FaceID (from `../node_modules/react-native-permissions/ios/FaceID.podspec`)
- Permission-LocationAlways (from `../node_modules/react-native-permissions/ios/LocationAlways.podspec`)
- Permission-LocationFullAccuracy (from `../node_modules/react-native-permissions/ios/LocationFullAccuracy.podspec`)
- Permission-LocationWhenInUse (from `../node_modules/react-native-permissions/ios/LocationWhenInUse.podspec`)
- Permission-MediaLibrary (from `../node_modules/react-native-permissions/ios/MediaLibrary.podspec`)
- Permission-Microphone (from `../node_modules/react-native-permissions/ios/Microphone.podspec`)
Expand Down Expand Up @@ -439,6 +442,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-permissions/ios/FaceID.podspec"
Permission-LocationAlways:
:path: "../node_modules/react-native-permissions/ios/LocationAlways.podspec"
Permission-LocationFullAccuracy:
:path: "../node_modules/react-native-permissions/ios/LocationFullAccuracy.podspec"
Permission-LocationWhenInUse:
:path: "../node_modules/react-native-permissions/ios/LocationWhenInUse.podspec"
Permission-MediaLibrary:
Expand Down Expand Up @@ -526,6 +531,7 @@ SPEC CHECKSUMS:
Permission-Contacts: 842e5c90da4f14f6ddce430f059ae93337e34a9f
Permission-FaceID: 65e9643924136d34007c339f64dd9389ba174312
Permission-LocationAlways: f67f696820b2ee8a420a049242c96eaaa2cfc0da
Permission-LocationFullAccuracy: 245a9be334c3cd4b309a676c0d94b5f4c3615212
Permission-LocationWhenInUse: 3d1bd1eeedcf397090e31ffcb4e6b6ff128087ca
Permission-MediaLibrary: 67006580556e4fd8d886486348fcf309922cff13
Permission-Microphone: 0f6d419edccc94135333daf5cb873f9d161557e3
Expand Down Expand Up @@ -559,6 +565,6 @@ SPEC CHECKSUMS:
Yoga: 3ebccbdd559724312790e7742142d062476b698e
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

PODFILE CHECKSUM: 33d4013cd5154539040dffe614d959a536bc61f4
PODFILE CHECKSUM: b923998a83e9d4b4cd58b688d2952dc56cc42676

COCOAPODS: 1.9.3
5 changes: 5 additions & 0 deletions example/ios/RNPermissionsExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<string>Let me use your location, even in background</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Let me use your location when the app is opened</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
<key>full-accuracy</key>
<string>Let me use your precise location temporarily</string>
</dict>
<key>NSMicrophoneUsageDescription</key>
<string>Let me use the microphone</string>
<key>NSMotionUsageDescription</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
if (@available(iOS 14.0, *)) {
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(__unused ATTrackingManagerAuthorizationStatus status) {
[self checkWithResolver:resolve rejecter:reject];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
#if TARGET_OS_SIMULATOR
return resolve(RNPermissionStatusNotAvailable);
#else
Expand Down
3 changes: 2 additions & 1 deletion ios/Calendars/RNPermissionHandlerCalendars.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
[[EKEventStore new] requestAccessToEntityType:EKEntityTypeEvent
completion:^(__unused BOOL granted, NSError * _Nullable error) {
if (error != nil) {
Expand Down
3 changes: 2 additions & 1 deletion ios/Camera/RNPermissionHandlerCamera.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(__unused BOOL granted) {
[self checkWithResolver:resolve rejecter:reject];
Expand Down
3 changes: 2 additions & 1 deletion ios/Contacts/RNPermissionHandlerContacts.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
[[CNContactStore new] requestAccessForEntityType:CNEntityTypeContacts
completionHandler:^(__unused BOOL granted, NSError * _Nullable error) {
if (error != nil && error.code != 100) { // error code 100 is permission denied
Expand Down
3 changes: 2 additions & 1 deletion ios/FaceID/RNPermissionHandlerFaceID.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
if (@available(iOS 11.0.1, *)) {
LAContext *context = [LAContext new];
NSError *error;
Expand Down
3 changes: 2 additions & 1 deletion ios/LocationAlways/RNPermissionHandlerLocationAlways.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
if (![CLLocationManager locationServicesEnabled]) {
return resolve(RNPermissionStatusNotAvailable);
}
Expand Down
21 changes: 21 additions & 0 deletions ios/LocationFullAccuracy.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'json'
package = JSON.parse(File.read('../package.json'))

Pod::Spec.new do |s|
s.name = "Permission-LocationFullAccuracy"
s.dependency "RNPermissions"

s.version = package["version"]
s.license = package["license"]
s.summary = package["description"]
s.authors = package["author"]
s.homepage = package["homepage"]

s.platform = :ios, "9.0"
s.ios.deployment_target = "9.0"
s.tvos.deployment_target = "11.0"
s.requires_arc = true

s.source = { :git => package["repository"]["url"], :tag => s.version }
s.source_files = "LocationFullAccuracy/*.{h,m}"
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import "RNPermissions.h"

@interface RNPermissionHandlerLocationFullAccuracy : NSObject<RNPermissionHandler>

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#import "RNPermissionHandlerLocationFullAccuracy.h"

@import CoreLocation;
@import UIKit;

NSErrorDomain const RNPermissionHandlerLocationFullAccuracyDomain = @"RNPermissionHandlerLocationFullAccuracy";
NS_ENUM(NSInteger) {
RNPermissionHandlerLocationFullAccuracyNoPurposeKey = 1,
};

@interface RNPermissionHandlerLocationFullAccuracy()

@end

@implementation RNPermissionHandlerLocationFullAccuracy

+ (NSArray<NSString *> * _Nonnull)usageDescriptionKeys {
return @[
@"NSLocationTemporaryUsageDescriptionDictionary"
];
}

+ (NSString * _Nonnull)handlerUniqueId {
return @"ios.permission.LOCATION_FULL_ACCURACY";
}

+ (RNPermissionStatus)getAccuracyStatus:(CLLocationManager *)locationManager API_AVAILABLE(ios(14.0)) {
switch (locationManager.accuracyAuthorization) {
case CLAccuracyAuthorizationFullAccuracy:
return RNPermissionStatusAuthorized;
case CLAccuracyAuthorizationReducedAccuracy:
default:
return RNPermissionStatusNotDetermined;
}
}

- (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (__unused ^ _Nonnull)(NSError * _Nonnull))reject {
if (!CLLocationManager.locationServicesEnabled) {
return resolve(RNPermissionStatusNotAvailable);
}

if (@available(iOS 14.0, *)) {
CLLocationManager *locationManager = [CLLocationManager new];
return resolve([RNPermissionHandlerLocationFullAccuracy getAccuracyStatus:locationManager]);
} else {
return resolve(RNPermissionStatusAuthorized);
}
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
if (!CLLocationManager.locationServicesEnabled) {
return resolve(RNPermissionStatusNotAvailable);
}

if (@available(iOS 14.0, *)) {
CLLocationManager *locationManager = [CLLocationManager new];
NSString *purposeKey = [options objectForKey:@"temporaryPurposeKey"];

if (!purposeKey) {
purposeKey = @"full-accuracy";
}

[locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:purposeKey
completion:^(NSError * _Nullable error) {
RNPermissionStatus status = [RNPermissionHandlerLocationFullAccuracy getAccuracyStatus:locationManager];

// Ignore errors due to full accuracy already being authorized
if (error && (error.code != kCLErrorPromptDeclined || status != RNPermissionStatusAuthorized)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it (error && error.code != kCLErrorPromptDeclined) || status != RNPermissionStatusAuthorized ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a failure to get authorisation should still resolve the promise successfully.

What's happening here is that requestTemporaryFullAccuracyAuthorizationWithPurposeKey will error with kCLErrorPromptDeclined when permission is already granted, causing it not to show a prompt to the user. When we see this error, and can confirm that authorisation is granted, the call is treated as a success instead.

return reject(error);
} else {
return resolve(status);
}
}];
} else {
return resolve(RNPermissionStatusAuthorized);
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
if (![CLLocationManager locationServicesEnabled]) {
return resolve(RNPermissionStatusNotAvailable);
}
Expand Down
3 changes: 2 additions & 1 deletion ios/MediaLibrary/RNPermissionHandlerMediaLibrary.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
}

- (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject {
rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject
options:(NSDictionary *_Nullable)options {
#if TARGET_OS_SIMULATOR
resolve(RNPermissionStatusNotAvailable);
#else
Expand Down