diff --git a/README.md b/README.md index b845bb3..0648a43 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,16 @@ When promise resolves, returns the status of the authorization. - `denied` - Permission denied - `restricted` - Permission restricted +#### `async getCurrentAuthorization() (iOS only)` +Get the current location permission status for the app without prompting the user for it. +Possible return values are the same as for `requestAuthorization` and `null` can also be returned if no specific permission has been granted. + +#### `watchPermission(changedCallback) (iOS Only)` + +Subscribe to changes to the location permission. These can happen if the user changes them in the settings app while the app is running. `changedCallback` will be called with the authorization result (return values of `requestAuthorization`). + +**Returns**: A function to unsubscribe (remove the listener). If called `changedCallback` will stop receiving permission updates. + #### `getCurrentPosition(successCallback, ?errorCallback, ?options)` - **successCallback**: Invoked with latest location info. - **errorCallback**: Invoked whenever an error is encountered. diff --git a/index.d.ts b/index.d.ts index 9f6c4e3..a92f377 100644 --- a/index.d.ts +++ b/index.d.ts @@ -71,10 +71,14 @@ declare module 'react-native-geolocation-service' { mocked?: boolean; } + type PermissionChangedCallback = (permission: AuthorizationResult) => void + type SuccessCallback = (position: GeoPosition) => void type ErrorCallback = (error: GeoError) => void + export function getCurrentAuthorization(): Promise + export function requestAuthorization( authorizationLevel: AuthorizationLevel ): Promise @@ -85,6 +89,10 @@ declare module 'react-native-geolocation-service' { options?: GeoOptions ): void + export function watchPermission( + changedCallback: PermissionChangedCallback + ): () => void + export function watchPosition( successCallback: SuccessCallback, errorCallback?: ErrorCallback, diff --git a/ios/RNFusedLocation.swift b/ios/RNFusedLocation.swift index 5538e1e..1bc99ab 100644 --- a/ios/RNFusedLocation.swift +++ b/ios/RNFusedLocation.swift @@ -24,6 +24,7 @@ class RNFusedLocation: RCTEventEmitter { private var resolveAuthorizationStatus: RCTPromiseResolveBlock? = nil private var successCallback: RCTResponseSenderBlock? = nil private var errorCallback: RCTResponseSenderBlock? = nil + private var permissionListenerCount: Int = 0 override init() { super.init() @@ -43,6 +44,15 @@ class RNFusedLocation: RCTEventEmitter { locationManager.delegate = nil; } + + // MARK: Bridge Method + @objc(getCurrentAuthorization:reject:) + func getCurrentAuthorization( + resolve: @escaping RCTPromiseResolveBlock, + reject: @escaping RCTPromiseRejectBlock + ) -> Void { + resolve(getCurrentAuthorizationStatus()) + } // MARK: Bridge Method @objc func requestAuthorization( @@ -56,19 +66,10 @@ class RNFusedLocation: RCTEventEmitter { resolve(AuthorizationStatus.disabled.rawValue) return } - - switch CLLocationManager.authorizationStatus() { - case .authorizedWhenInUse, .authorizedAlways: - resolve(AuthorizationStatus.granted.rawValue) - return - case .denied: - resolve(AuthorizationStatus.denied.rawValue) - return - case .restricted: - resolve(AuthorizationStatus.restricted.rawValue) - return - default: - break + + if let currentStatus = getCurrentAuthorizationStatus() { + resolve(currentStatus) + return } resolveAuthorizationStatus = resolve @@ -153,6 +154,16 @@ class RNFusedLocation: RCTEventEmitter { observing = false } + + // MARK: Bridge Method + @objc func permissionListenerAdded() -> Void { + permissionListenerCount += 1 + } + + // MARK: Bridge Method + @objc func permissionListenerRemoved() -> Void { + permissionListenerCount -= 1 + } @objc func timerFired(timer: Timer) -> Void { let data = timer.userInfo as! [String: Any] @@ -231,6 +242,23 @@ class RNFusedLocation: RCTEventEmitter { return false } + + private func getAuthorizationStatusString(_ authorizationStatus: CLAuthorizationStatus) -> String? { + switch authorizationStatus { + case .authorizedWhenInUse, .authorizedAlways: + return AuthorizationStatus.granted.rawValue + case .denied: + return AuthorizationStatus.denied.rawValue + case .restricted: + return AuthorizationStatus.restricted.rawValue + default: + return nil + } + } + + private func getCurrentAuthorizationStatus() -> String? { + return getAuthorizationStatusString(CLLocationManager.authorizationStatus()) + } private func generateErrorResponse(code: Int, message: String = "") -> [String: Any] { var msg: String = message @@ -268,7 +296,7 @@ extension RNFusedLocation { } override func supportedEvents() -> [String]! { - return ["geolocationDidChange", "geolocationError"] + return ["geolocationDidChange", "geolocationError", "geolocationPermissionDidChange"] } override func startObserving() -> Void { @@ -282,22 +310,20 @@ extension RNFusedLocation { extension RNFusedLocation: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - if status == .notDetermined || resolveAuthorizationStatus == nil { + if status == .notDetermined { return } + + let statusString = getAuthorizationStatusString(status) - switch status { - case .authorizedWhenInUse, .authorizedAlways: - resolveAuthorizationStatus?(AuthorizationStatus.granted.rawValue) - case .denied: - resolveAuthorizationStatus?(AuthorizationStatus.denied.rawValue) - case .restricted: - resolveAuthorizationStatus?(AuthorizationStatus.restricted.rawValue) - default: - break + if let resolve = resolveAuthorizationStatus { + resolve(statusString) + resolveAuthorizationStatus = nil + } + + if (permissionListenerCount > 0) { + sendEvent(withName: "geolocationPermissionDidChange", body: statusString) } - - resolveAuthorizationStatus = nil } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { diff --git a/ios/RNFusedLocationBridge.m b/ios/RNFusedLocationBridge.m index 9f619fb..6d10aba 100644 --- a/ios/RNFusedLocationBridge.m +++ b/ios/RNFusedLocationBridge.m @@ -3,6 +3,11 @@ @interface RCT_EXTERN_MODULE(RNFusedLocation, RCTEventEmitter) +RCT_EXTERN_METHOD( + getCurrentAuthorization:(RCTPromiseResolveBlock *)resolve + reject:(RCTPromiseRejectBlock)reject +) + RCT_EXTERN_METHOD( requestAuthorization:(NSString *)level resolve:(RCTPromiseResolveBlock)resolve @@ -15,6 +20,10 @@ @interface RCT_EXTERN_MODULE(RNFusedLocation, RCTEventEmitter) errorCallback:(RCTResponseSenderBlock)errorCallback ) +RCT_EXTERN_METHOD(permissionListenerAdded) + +RCT_EXTERN_METHOD(permissionListenerRemoved) + _RCT_EXTERN_REMAP_METHOD( startObserving, startLocationUpdate:(NSDictionary *)options, diff --git a/js/Geolocation.js b/js/Geolocation.js index 3fc7eba..5efd479 100644 --- a/js/Geolocation.js +++ b/js/Geolocation.js @@ -6,6 +6,10 @@ const Geolocation = { throw new Error('Method not supported by browser'); }, + getCurrentAuthorization: function () { + throw new Error('Method not supported by browser'); + }, + requestAuthorization: async function () { return Promise.reject('Method not supported by browser'); }, @@ -20,6 +24,10 @@ const Geolocation = { navigator.geolocation.getCurrentPosition(success, error, options); }, + watchPermission: function () { + throw new Error('Method not supported by browser') + }, + watchPosition: function (success, error, options) { if (!success) { throw new Error('Must provide a success callback'); diff --git a/js/Geolocation.native.js b/js/Geolocation.native.js index 89ccb4f..9e558a4 100644 --- a/js/Geolocation.native.js +++ b/js/Geolocation.native.js @@ -10,6 +10,14 @@ let updatesEnabled = false; const Geolocation = { setRNConfiguration: (config) => {}, // eslint-disable-line no-unused-vars + getCurrentAuthorization: () => { + if (Platform.OS !== 'ios') { + return Promise.reject('getCurrentAuthorization is only for iOS'); + } + + return RNFusedLocation.getCurrentAuthorization(); + }, + requestAuthorization: async (authorizationLevel) => { if (Platform.OS !== 'ios') { return Promise.reject('requestAuthorization is only for iOS'); @@ -33,6 +41,22 @@ const Geolocation = { RNFusedLocation.getCurrentPosition(options, success, error); }, + watchPermission: (changed) => { + if (Platform.OS !== 'ios') { + throw new Error('watchPermission is only for iOS'); + } + if (!changed) { + throw new Error('Must provide a changed callback'); + } + + const subscription = LocationEventEmitter.addListener('geolocationPermissionDidChange', changed); + RNFusedLocation.permissionListenerAdded(); + return () => { + RNFusedLocation.permissionListenerRemoved(); + subscription.remove(); + }; + }, + watchPosition: (success, error = null, options = {}) => { if (!success) { // eslint-disable-next-line no-console