yeet
This commit is contained in:
22
node_modules/expo-location/ios/EXLocation.podspec
generated
vendored
Normal file
22
node_modules/expo-location/ios/EXLocation.podspec
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'EXLocation'
|
||||
s.version = package['version']
|
||||
s.summary = package['description']
|
||||
s.description = package['description']
|
||||
s.license = package['license']
|
||||
s.author = package['author']
|
||||
s.homepage = package['homepage']
|
||||
s.platform = :ios, '10.0'
|
||||
s.source = { git: 'https://github.com/expo/expo.git' }
|
||||
s.source_files = 'EXLocation/**/*.{h,m}'
|
||||
s.preserve_paths = 'EXLocation/**/*.{h,m}'
|
||||
s.requires_arc = true
|
||||
|
||||
s.dependency 'UMCore'
|
||||
s.dependency 'UMPermissionsInterface'
|
||||
s.dependency 'UMTaskManagerInterface'
|
||||
end
|
39
node_modules/expo-location/ios/EXLocation/EXLocation.h
generated
vendored
Normal file
39
node_modules/expo-location/ios/EXLocation/EXLocation.h
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2015-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CoreLocation/CLLocation.h>
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
|
||||
#import <UMCore/UMEventEmitter.h>
|
||||
#import <UMCore/UMExportedModule.h>
|
||||
#import <UMCore/UMModuleRegistryConsumer.h>
|
||||
|
||||
// Location accuracies
|
||||
typedef NS_ENUM(NSUInteger, EXLocationAccuracy) {
|
||||
EXLocationAccuracyLowest = 1,
|
||||
EXLocationAccuracyLow = 2,
|
||||
EXLocationAccuracyBalanced = 3,
|
||||
EXLocationAccuracyHigh = 4,
|
||||
EXLocationAccuracyHighest = 5,
|
||||
EXLocationAccuracyBestForNavigation = 6,
|
||||
};
|
||||
|
||||
// Geofencing event types
|
||||
typedef NS_ENUM(NSUInteger, EXGeofencingEventType) {
|
||||
EXGeofencingEventTypeEnter = 1,
|
||||
EXGeofencingEventTypeExit = 2,
|
||||
};
|
||||
|
||||
// Geofencing region states
|
||||
typedef NS_ENUM(NSUInteger, EXGeofencingRegionState) {
|
||||
EXGeofencingRegionStateUnknown = 0,
|
||||
EXGeofencingRegionStateInside = 1,
|
||||
EXGeofencingRegionStateOutside = 2,
|
||||
};
|
||||
|
||||
@interface EXLocation : UMExportedModule <UMEventEmitter, UMModuleRegistryConsumer>
|
||||
|
||||
+ (NSDictionary *)exportLocation:(CLLocation *)location;
|
||||
+ (CLLocationAccuracy)CLLocationAccuracyFromOption:(EXLocationAccuracy)accuracy;
|
||||
+ (CLActivityType)CLActivityTypeFromOption:(NSInteger)activityType;
|
||||
|
||||
@end
|
563
node_modules/expo-location/ios/EXLocation/EXLocation.m
generated
vendored
Normal file
563
node_modules/expo-location/ios/EXLocation/EXLocation.m
generated
vendored
Normal file
@ -0,0 +1,563 @@
|
||||
// Copyright 2016-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXLocation/EXLocation.h>
|
||||
#import <EXLocation/EXLocationDelegate.h>
|
||||
#import <EXLocation/EXLocationTaskConsumer.h>
|
||||
#import <EXLocation/EXGeofencingTaskConsumer.h>
|
||||
#import <EXLocation/EXLocationPermissionRequester.h>
|
||||
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
#import <CoreLocation/CLHeading.h>
|
||||
#import <CoreLocation/CLGeocoder.h>
|
||||
#import <CoreLocation/CLPlacemark.h>
|
||||
#import <CoreLocation/CLError.h>
|
||||
#import <CoreLocation/CLCircularRegion.h>
|
||||
|
||||
#import <UMCore/UMEventEmitterService.h>
|
||||
|
||||
#import <UMPermissionsInterface/UMPermissionsInterface.h>
|
||||
#import <UMPermissionsInterface/UMPermissionsMethodsDelegate.h>
|
||||
|
||||
#import <UMTaskManagerInterface/UMTaskManagerInterface.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString * const EXLocationChangedEventName = @"Expo.locationChanged";
|
||||
NSString * const EXHeadingChangedEventName = @"Expo.headingChanged";
|
||||
|
||||
@interface EXLocation ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, EXLocationDelegate*> *delegates;
|
||||
@property (nonatomic, strong) NSMutableSet<EXLocationDelegate *> *retainedDelegates;
|
||||
@property (nonatomic, weak) id<UMEventEmitterService> eventEmitter;
|
||||
@property (nonatomic, weak) id<UMPermissionsInterface> permissionsManager;
|
||||
@property (nonatomic, weak) id<UMTaskManagerInterface> tasksManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXLocation
|
||||
|
||||
UM_EXPORT_MODULE(ExpoLocation);
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_delegates = [NSMutableDictionary dictionary];
|
||||
_retainedDelegates = [NSMutableSet set];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry
|
||||
{
|
||||
_eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(UMEventEmitterService)];
|
||||
_permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMPermissionsInterface)];
|
||||
[UMPermissionsMethodsDelegate registerRequesters:@[[EXLocationPermissionRequester new]] withPermissionsManager:_permissionsManager];
|
||||
_tasksManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMTaskManagerInterface)];
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
// Location managers must be created on the main thread
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
# pragma mark - UMEventEmitter
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
{
|
||||
return @[EXLocationChangedEventName, EXHeadingChangedEventName];
|
||||
}
|
||||
|
||||
- (void)startObserving {}
|
||||
- (void)stopObserving {}
|
||||
|
||||
# pragma mark - Exported methods
|
||||
|
||||
UM_EXPORT_METHOD_AS(getProviderStatusAsync,
|
||||
resolver:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
resolve(@{
|
||||
@"locationServicesEnabled": @([CLLocationManager locationServicesEnabled]),
|
||||
@"backgroundModeEnabled": @([_tasksManager hasBackgroundModeEnabled:@"location"]),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
UM_EXPORT_METHOD_AS(getCurrentPositionAsync,
|
||||
options:(NSDictionary *)options
|
||||
resolver:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkPermissions:reject]) {
|
||||
return;
|
||||
}
|
||||
|
||||
CLLocationManager *locMgr = [self locationManagerWithOptions:options];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
__block EXLocationDelegate *delegate;
|
||||
|
||||
delegate = [[EXLocationDelegate alloc] initWithId:nil withLocMgr:locMgr onUpdateLocations:^(NSArray<CLLocation *> * _Nonnull locations) {
|
||||
if (delegate != nil) {
|
||||
if (locations.lastObject != nil) {
|
||||
resolve([EXLocation exportLocation:locations.lastObject]);
|
||||
} else {
|
||||
reject(@"E_LOCATION_NOT_FOUND", @"Current location not found.", nil);
|
||||
}
|
||||
[weakSelf.retainedDelegates removeObject:delegate];
|
||||
delegate = nil;
|
||||
}
|
||||
} onUpdateHeadings:nil onError:^(NSError *error) {
|
||||
reject(@"E_LOCATION_UNAVAILABLE", [@"Cannot obtain current location: " stringByAppendingString:error.description], nil);
|
||||
}];
|
||||
|
||||
// retain location manager delegate so it will not dealloc until onUpdateLocations gets called
|
||||
[_retainedDelegates addObject:delegate];
|
||||
|
||||
locMgr.delegate = delegate;
|
||||
[locMgr requestLocation];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(watchPositionImplAsync,
|
||||
watchId:(nonnull NSNumber *)watchId
|
||||
options:(NSDictionary *)options
|
||||
resolver:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkPermissions:reject]) {
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
CLLocationManager *locMgr = [self locationManagerWithOptions:options];
|
||||
|
||||
EXLocationDelegate *delegate = [[EXLocationDelegate alloc] initWithId:watchId withLocMgr:locMgr onUpdateLocations:^(NSArray<CLLocation *> *locations) {
|
||||
if (locations.lastObject != nil && weakSelf != nil) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
|
||||
CLLocation *loc = locations.lastObject;
|
||||
NSDictionary *body = @{
|
||||
@"watchId": watchId,
|
||||
@"location": [EXLocation exportLocation:loc],
|
||||
};
|
||||
|
||||
[strongSelf->_eventEmitter sendEventWithName:EXLocationChangedEventName body:body];
|
||||
}
|
||||
} onUpdateHeadings:nil onError:^(NSError *error) {
|
||||
// TODO: report errors
|
||||
// (ben) error could be (among other things):
|
||||
// - kCLErrorDenied - we should use the same UNAUTHORIZED behavior as elsewhere
|
||||
// - kCLErrorLocationUnknown - we can actually ignore this error and keep tracking
|
||||
// location (I think -- my knowledge might be a few months out of date)
|
||||
}];
|
||||
|
||||
_delegates[delegate.watchId] = delegate;
|
||||
locMgr.delegate = delegate;
|
||||
[locMgr startUpdatingLocation];
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(getLastKnownPositionAsync,
|
||||
getLastKnownPositionWithOptions:(NSDictionary *)options
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkPermissions:reject]) {
|
||||
return;
|
||||
}
|
||||
CLLocation *location = [[self locationManagerWithOptions:nil] location];
|
||||
|
||||
if ([self.class isLocation:location validWithOptions:options]) {
|
||||
resolve([EXLocation exportLocation:location]);
|
||||
} else {
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
}
|
||||
|
||||
// Watch method for getting compass updates
|
||||
UM_EXPORT_METHOD_AS(watchDeviceHeading,
|
||||
watchHeadingWithWatchId:(nonnull NSNumber *)watchId
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject) {
|
||||
if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXLocationPermissionRequester class]]) {
|
||||
reject(@"E_LOCATION_UNAUTHORIZED", @"Not authorized to use location services", nil);
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
CLLocationManager *locMgr = [[CLLocationManager alloc] init];
|
||||
|
||||
locMgr.distanceFilter = kCLDistanceFilterNone;
|
||||
locMgr.desiredAccuracy = kCLLocationAccuracyBest;
|
||||
locMgr.allowsBackgroundLocationUpdates = NO;
|
||||
|
||||
EXLocationDelegate *delegate = [[EXLocationDelegate alloc] initWithId:watchId withLocMgr:locMgr onUpdateLocations: nil onUpdateHeadings:^(CLHeading *newHeading) {
|
||||
if (newHeading != nil && weakSelf != nil) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
NSNumber *accuracy;
|
||||
|
||||
// Convert iOS heading accuracy to Android system
|
||||
// 3: high accuracy, 2: medium, 1: low, 0: none
|
||||
if (newHeading.headingAccuracy > 50 || newHeading.headingAccuracy < 0) {
|
||||
accuracy = @(0);
|
||||
} else if (newHeading.headingAccuracy > 35) {
|
||||
accuracy = @(1);
|
||||
} else if (newHeading.headingAccuracy > 20) {
|
||||
accuracy = @(2);
|
||||
} else {
|
||||
accuracy = @(3);
|
||||
}
|
||||
NSDictionary *body = @{@"watchId": watchId,
|
||||
@"heading": @{
|
||||
@"trueHeading": @(newHeading.trueHeading),
|
||||
@"magHeading": @(newHeading.magneticHeading),
|
||||
@"accuracy": accuracy,
|
||||
},
|
||||
};
|
||||
[strongSelf->_eventEmitter sendEventWithName:EXHeadingChangedEventName body:body];
|
||||
}
|
||||
} onError:^(NSError *error) {
|
||||
// Error getting updates
|
||||
}];
|
||||
|
||||
_delegates[delegate.watchId] = delegate;
|
||||
locMgr.delegate = delegate;
|
||||
[locMgr startUpdatingHeading];
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(removeWatchAsync,
|
||||
watchId:(nonnull NSNumber *)watchId
|
||||
resolver:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
EXLocationDelegate *delegate = _delegates[watchId];
|
||||
|
||||
if (delegate) {
|
||||
// Unsuscribe from both location and heading updates
|
||||
[delegate.locMgr stopUpdatingLocation];
|
||||
[delegate.locMgr stopUpdatingHeading];
|
||||
delegate.locMgr.delegate = nil;
|
||||
[_delegates removeObjectForKey:watchId];
|
||||
}
|
||||
resolve([NSNull null]);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(geocodeAsync,
|
||||
address:(nonnull NSString *)address
|
||||
resolver:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
|
||||
|
||||
[geocoder geocodeAddressString:address completionHandler:^(NSArray* placemarks, NSError* error){
|
||||
if (!error) {
|
||||
NSMutableArray *results = [NSMutableArray arrayWithCapacity:placemarks.count];
|
||||
for (CLPlacemark* placemark in placemarks) {
|
||||
CLLocation *location = placemark.location;
|
||||
[results addObject:@{
|
||||
@"latitude": @(location.coordinate.latitude),
|
||||
@"longitude": @(location.coordinate.longitude),
|
||||
@"altitude": @(location.altitude),
|
||||
@"accuracy": @(location.horizontalAccuracy),
|
||||
}];
|
||||
}
|
||||
resolve(results);
|
||||
} else if (error.code == kCLErrorGeocodeFoundNoResult || error.code == kCLErrorGeocodeFoundPartialResult) {
|
||||
resolve(@[]);
|
||||
} else if (error.code == kCLErrorNetwork) {
|
||||
reject(@"E_RATE_EXCEEDED", @"Rate limit exceeded - too many requests", error);
|
||||
} else {
|
||||
reject(@"E_GEOCODING_FAILED", @"Error while geocoding an address", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(reverseGeocodeAsync,
|
||||
locationMap:(nonnull NSDictionary *)locationMap
|
||||
resolver:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
|
||||
CLLocation *location = [[CLLocation alloc] initWithLatitude:[locationMap[@"latitude"] floatValue] longitude:[locationMap[@"longitude"] floatValue]];
|
||||
|
||||
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray* placemarks, NSError* error){
|
||||
if (!error) {
|
||||
NSMutableArray *results = [NSMutableArray arrayWithCapacity:placemarks.count];
|
||||
for (CLPlacemark* placemark in placemarks) {
|
||||
NSDictionary *address = @{
|
||||
@"city": UMNullIfNil(placemark.locality),
|
||||
@"district": UMNullIfNil(placemark.subLocality),
|
||||
@"street": UMNullIfNil(placemark.thoroughfare),
|
||||
@"region": UMNullIfNil(placemark.administrativeArea),
|
||||
@"subregion": UMNullIfNil(placemark.subAdministrativeArea),
|
||||
@"country": UMNullIfNil(placemark.country),
|
||||
@"postalCode": UMNullIfNil(placemark.postalCode),
|
||||
@"name": UMNullIfNil(placemark.name),
|
||||
@"isoCountryCode": UMNullIfNil(placemark.ISOcountryCode),
|
||||
@"timezone": UMNullIfNil(placemark.timeZone.name),
|
||||
};
|
||||
[results addObject:address];
|
||||
}
|
||||
resolve(results);
|
||||
} else if (error.code == kCLErrorGeocodeFoundNoResult || error.code == kCLErrorGeocodeFoundPartialResult) {
|
||||
resolve(@[]);
|
||||
} else if (error.code == kCLErrorNetwork) {
|
||||
reject(@"E_RATE_EXCEEDED", @"Rate limit exceeded - too many requests", error);
|
||||
} else {
|
||||
reject(@"E_REVGEOCODING_FAILED", @"Error while reverse-geocoding a location", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(getPermissionsAsync,
|
||||
getPermissionsAsync:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
[UMPermissionsMethodsDelegate getPermissionWithPermissionsManager:_permissionsManager
|
||||
withRequester:[EXLocationPermissionRequester class]
|
||||
resolve:resolve
|
||||
reject:reject];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(requestPermissionsAsync,
|
||||
requestPermissionsAsync:(UMPromiseResolveBlock)resolve
|
||||
rejecter:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
[UMPermissionsMethodsDelegate askForPermissionWithPermissionsManager:_permissionsManager
|
||||
withRequester:[EXLocationPermissionRequester class]
|
||||
resolve:resolve
|
||||
reject:reject];
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(hasServicesEnabledAsync,
|
||||
hasServicesEnabled:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
BOOL servicesEnabled = [CLLocationManager locationServicesEnabled];
|
||||
resolve(@(servicesEnabled));
|
||||
}
|
||||
|
||||
# pragma mark - Background location
|
||||
|
||||
UM_EXPORT_METHOD_AS(startLocationUpdatesAsync,
|
||||
startLocationUpdatesForTaskWithName:(nonnull NSString *)taskName
|
||||
withOptions:(nonnull NSDictionary *)options
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkPermissions:reject] || ![self checkTaskManagerExists:reject] || ![self checkBackgroundServices:reject]) {
|
||||
return;
|
||||
}
|
||||
if (![CLLocationManager significantLocationChangeMonitoringAvailable]) {
|
||||
return reject(@"E_SIGNIFICANT_CHANGES_UNAVAILABLE", @"Significant location changes monitoring is not available.", nil);
|
||||
}
|
||||
|
||||
@try {
|
||||
[_tasksManager registerTaskWithName:taskName consumer:[EXLocationTaskConsumer class] options:options];
|
||||
}
|
||||
@catch (NSException *e) {
|
||||
return reject(e.name, e.reason, nil);
|
||||
}
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(stopLocationUpdatesAsync,
|
||||
stopLocationUpdatesForTaskWithName:(NSString *)taskName
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkTaskManagerExists:reject]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@try {
|
||||
[_tasksManager unregisterTaskWithName:taskName consumerClass:[EXLocationTaskConsumer class]];
|
||||
} @catch (NSException *e) {
|
||||
return reject(e.name, e.reason, nil);
|
||||
}
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(hasStartedLocationUpdatesAsync,
|
||||
hasStartedLocationUpdatesForTaskWithName:(nonnull NSString *)taskName
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkTaskManagerExists:reject]) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(@([_tasksManager taskWithName:taskName hasConsumerOfClass:[EXLocationTaskConsumer class]]));
|
||||
}
|
||||
|
||||
# pragma mark - Geofencing
|
||||
|
||||
UM_EXPORT_METHOD_AS(startGeofencingAsync,
|
||||
startGeofencingWithTaskName:(nonnull NSString *)taskName
|
||||
withOptions:(nonnull NSDictionary *)options
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkPermissions:reject] || ![self checkTaskManagerExists:reject]) {
|
||||
return;
|
||||
}
|
||||
if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
|
||||
return reject(@"E_GEOFENCING_UNAVAILABLE", @"Geofencing is not available", nil);
|
||||
}
|
||||
|
||||
@try {
|
||||
[_tasksManager registerTaskWithName:taskName consumer:[EXGeofencingTaskConsumer class] options:options];
|
||||
} @catch (NSException *e) {
|
||||
return reject(e.name, e.reason, nil);
|
||||
}
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(stopGeofencingAsync,
|
||||
stopGeofencingWithTaskName:(nonnull NSString *)taskName
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkTaskManagerExists:reject]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@try {
|
||||
[_tasksManager unregisterTaskWithName:taskName consumerClass:[EXGeofencingTaskConsumer class]];
|
||||
} @catch (NSException *e) {
|
||||
return reject(e.name, e.reason, nil);
|
||||
}
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
UM_EXPORT_METHOD_AS(hasStartedGeofencingAsync,
|
||||
hasStartedGeofencingForTaskWithName:(NSString *)taskName
|
||||
resolve:(UMPromiseResolveBlock)resolve
|
||||
reject:(UMPromiseRejectBlock)reject)
|
||||
{
|
||||
if (![self checkTaskManagerExists:reject]) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(@([_tasksManager taskWithName:taskName hasConsumerOfClass:[EXGeofencingTaskConsumer class]]));
|
||||
}
|
||||
|
||||
# pragma mark - helpers
|
||||
|
||||
- (CLLocationManager *)locationManagerWithOptions:(nullable NSDictionary *)options
|
||||
{
|
||||
CLLocationManager *locMgr = [[CLLocationManager alloc] init];
|
||||
locMgr.allowsBackgroundLocationUpdates = NO;
|
||||
|
||||
if (options) {
|
||||
locMgr.distanceFilter = options[@"distanceInterval"] ? [options[@"distanceInterval"] doubleValue] ?: kCLDistanceFilterNone : kCLLocationAccuracyHundredMeters;
|
||||
|
||||
if (options[@"accuracy"]) {
|
||||
EXLocationAccuracy accuracy = [options[@"accuracy"] unsignedIntegerValue] ?: EXLocationAccuracyBalanced;
|
||||
locMgr.desiredAccuracy = [self.class CLLocationAccuracyFromOption:accuracy];
|
||||
}
|
||||
}
|
||||
return locMgr;
|
||||
}
|
||||
|
||||
- (BOOL)checkPermissions:(UMPromiseRejectBlock)reject
|
||||
{
|
||||
if (![CLLocationManager locationServicesEnabled]) {
|
||||
reject(@"E_LOCATION_SERVICES_DISABLED", @"Location services are disabled", nil);
|
||||
return NO;
|
||||
}
|
||||
if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXLocationPermissionRequester class]]) {
|
||||
reject(@"E_NO_PERMISSIONS", @"LOCATION permission is required to do this operation.", nil);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)checkTaskManagerExists:(UMPromiseRejectBlock)reject
|
||||
{
|
||||
if (_tasksManager == nil) {
|
||||
reject(@"E_TASKMANAGER_NOT_FOUND", @"`expo-task-manager` module is required to use background services.", nil);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)checkBackgroundServices:(UMPromiseRejectBlock)reject
|
||||
{
|
||||
if (![_tasksManager hasBackgroundModeEnabled:@"location"]) {
|
||||
reject(@"E_BACKGROUND_SERVICES_DISABLED", @"Background Location has not been configured. To enable it, add `location` to `UIBackgroundModes` in Info.plist file.", nil);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
# pragma mark - static helpers
|
||||
|
||||
+ (NSDictionary *)exportLocation:(CLLocation *)location
|
||||
{
|
||||
return @{
|
||||
@"coords": @{
|
||||
@"latitude": @(location.coordinate.latitude),
|
||||
@"longitude": @(location.coordinate.longitude),
|
||||
@"altitude": @(location.altitude),
|
||||
@"accuracy": @(location.horizontalAccuracy),
|
||||
@"altitudeAccuracy": @(location.verticalAccuracy),
|
||||
@"heading": @(location.course),
|
||||
@"speed": @(location.speed),
|
||||
},
|
||||
@"timestamp": @([location.timestamp timeIntervalSince1970] * 1000),
|
||||
};
|
||||
}
|
||||
|
||||
+ (CLLocationAccuracy)CLLocationAccuracyFromOption:(EXLocationAccuracy)accuracy
|
||||
{
|
||||
switch (accuracy) {
|
||||
case EXLocationAccuracyLowest:
|
||||
return kCLLocationAccuracyThreeKilometers;
|
||||
case EXLocationAccuracyLow:
|
||||
return kCLLocationAccuracyKilometer;
|
||||
case EXLocationAccuracyBalanced:
|
||||
return kCLLocationAccuracyHundredMeters;
|
||||
case EXLocationAccuracyHigh:
|
||||
return kCLLocationAccuracyNearestTenMeters;
|
||||
case EXLocationAccuracyHighest:
|
||||
return kCLLocationAccuracyBest;
|
||||
case EXLocationAccuracyBestForNavigation:
|
||||
return kCLLocationAccuracyBestForNavigation;
|
||||
default:
|
||||
return kCLLocationAccuracyHundredMeters;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CLActivityType)CLActivityTypeFromOption:(NSInteger)activityType
|
||||
{
|
||||
if (activityType >= CLActivityTypeOther && activityType <= CLActivityTypeOtherNavigation) {
|
||||
return activityType;
|
||||
}
|
||||
if (@available(iOS 12.0, *)) {
|
||||
if (activityType == CLActivityTypeAirborne) {
|
||||
return activityType;
|
||||
}
|
||||
}
|
||||
return CLActivityTypeOther;
|
||||
}
|
||||
|
||||
+ (BOOL)isLocation:(nullable CLLocation *)location validWithOptions:(nullable NSDictionary *)options
|
||||
{
|
||||
if (location == nil) {
|
||||
return NO;
|
||||
}
|
||||
NSTimeInterval maxAge = options[@"maxAge"] ? [options[@"maxAge"] doubleValue] : DBL_MAX;
|
||||
CLLocationAccuracy requiredAccuracy = options[@"requiredAccuracy"] ? [options[@"requiredAccuracy"] doubleValue] : DBL_MAX;
|
||||
NSTimeInterval timeDiff = -location.timestamp.timeIntervalSinceNow;
|
||||
|
||||
return location != nil && timeDiff * 1000 <= maxAge && location.horizontalAccuracy <= requiredAccuracy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
32
node_modules/expo-location/ios/EXLocation/EXLocationDelegate.h
generated
vendored
Normal file
32
node_modules/expo-location/ios/EXLocation/EXLocationDelegate.h
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2015-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CoreLocation/CLHeading.h>
|
||||
#import <CoreLocation/CLLocation.h>
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXLocationDelegate : NSObject <CLLocationManagerDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSNumber *watchId;
|
||||
@property (nonatomic, strong) CLLocationManager *locMgr;
|
||||
@property (nonatomic, strong) void (^onUpdateLocations)(NSArray<CLLocation *> *locations);
|
||||
@property (nonatomic, strong) void (^onUpdateHeadings)(CLHeading *newHeading);
|
||||
@property (nonatomic, strong) void (^onError)(NSError *error);
|
||||
|
||||
- (instancetype)initWithId:(nullable NSNumber *)watchId
|
||||
withLocMgr:(CLLocationManager *)locMgr
|
||||
onUpdateLocations:(nullable void (^)(NSArray<CLLocation *> *locations))onUpdateLocations
|
||||
onUpdateHeadings:(nullable void (^)(CLHeading *newHeading))onUpdateHeadings
|
||||
onError:(nullable void (^)(NSError *error))onError;
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations;
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading;
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(nonnull NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
46
node_modules/expo-location/ios/EXLocation/EXLocationDelegate.m
generated
vendored
Normal file
46
node_modules/expo-location/ios/EXLocation/EXLocationDelegate.m
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2015-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXLocation/EXLocationDelegate.h>
|
||||
|
||||
@implementation EXLocationDelegate
|
||||
|
||||
- (instancetype)initWithId:(nullable NSNumber *)watchId
|
||||
withLocMgr:(CLLocationManager *)locMgr
|
||||
onUpdateLocations:(nullable void (^)(NSArray<CLLocation *> *locations))onUpdateLocations
|
||||
onUpdateHeadings:(nullable void (^)(CLHeading *newHeading))onUpdateHeadings
|
||||
onError:(nullable void (^)(NSError *error))onError
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_watchId = watchId;
|
||||
_locMgr = locMgr;
|
||||
_onUpdateLocations = onUpdateLocations;
|
||||
_onUpdateHeadings = onUpdateHeadings;
|
||||
_onError = onError;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Delegate method called by CLLocationManager
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
|
||||
{
|
||||
if (_onUpdateLocations) {
|
||||
_onUpdateLocations(locations);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
|
||||
{
|
||||
if (_onUpdateHeadings) {
|
||||
_onUpdateHeadings(newHeading);
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate method called by CLLocationManager
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(nonnull NSError *)error
|
||||
{
|
||||
if (_onError) {
|
||||
_onError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
7
node_modules/expo-location/ios/EXLocation/EXLocationPermissionRequester.h
generated
vendored
Normal file
7
node_modules/expo-location/ios/EXLocation/EXLocationPermissionRequester.h
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2016-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <UMPermissionsInterface/UMPermissionsInterface.h>
|
||||
|
||||
@interface EXLocationPermissionRequester : NSObject<UMPermissionsRequester>
|
||||
|
||||
@end
|
177
node_modules/expo-location/ios/EXLocation/EXLocationPermissionRequester.m
generated
vendored
Normal file
177
node_modules/expo-location/ios/EXLocation/EXLocationPermissionRequester.m
generated
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright 2016-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <EXLocation/EXLocationPermissionRequester.h>
|
||||
#import <UMCore/UMUtilities.h>
|
||||
|
||||
#import <objc/message.h>
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
|
||||
static SEL alwaysAuthorizationSelector;
|
||||
static SEL whenInUseAuthorizationSelector;
|
||||
|
||||
@interface EXLocationPermissionRequester () <CLLocationManagerDelegate>
|
||||
|
||||
@property (nonatomic, strong) CLLocationManager *locMgr;
|
||||
@property (nonatomic, strong) UMPromiseResolveBlock resolve;
|
||||
@property (nonatomic, strong) UMPromiseRejectBlock reject;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXLocationPermissionRequester
|
||||
|
||||
+ (NSString *)permissionType
|
||||
{
|
||||
return @"location";
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
alwaysAuthorizationSelector = NSSelectorFromString([@"request" stringByAppendingString:@"AlwaysAuthorization"]);
|
||||
whenInUseAuthorizationSelector = NSSelectorFromString([@"request" stringByAppendingString:@"WhenInUseAuthorization"]);
|
||||
}
|
||||
|
||||
- (NSDictionary *)getPermissions
|
||||
{
|
||||
UMPermissionStatus status;
|
||||
NSString *scope = @"none";
|
||||
|
||||
CLAuthorizationStatus systemStatus;
|
||||
if (![[self class] isConfiguredForAlwaysAuthorization] && ![[self class] isConfiguredForWhenInUseAuthorization]) {
|
||||
UMFatal(UMErrorWithMessage(@"This app is missing usage descriptions, so location services will fail. Add one of the `NSLocation*UsageDescription` keys to your bundle's Info.plist. See https://bit.ly/2P5fEbG (https://docs.expo.io/versions/latest/guides/app-stores.html#system-permissions-dialogs-on-ios) for more information."));
|
||||
systemStatus = kCLAuthorizationStatusDenied;
|
||||
} else {
|
||||
systemStatus = [CLLocationManager authorizationStatus];
|
||||
}
|
||||
|
||||
switch (systemStatus) {
|
||||
case kCLAuthorizationStatusAuthorizedWhenInUse: {
|
||||
status = UMPermissionStatusGranted;
|
||||
scope = @"whenInUse";
|
||||
break;
|
||||
}
|
||||
case kCLAuthorizationStatusAuthorizedAlways: {
|
||||
status = UMPermissionStatusGranted;
|
||||
scope = @"always";
|
||||
break;
|
||||
}
|
||||
case kCLAuthorizationStatusDenied: case kCLAuthorizationStatusRestricted: {
|
||||
status = UMPermissionStatusDenied;
|
||||
break;
|
||||
}
|
||||
case kCLAuthorizationStatusNotDetermined: default: {
|
||||
status = UMPermissionStatusUndetermined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
@"status": @(status),
|
||||
@"scope": scope
|
||||
};
|
||||
}
|
||||
|
||||
- (void)requestPermissionsWithResolver:(UMPromiseResolveBlock)resolve rejecter:(UMPromiseRejectBlock)reject
|
||||
{
|
||||
NSDictionary *existingPermissions = [self getPermissions];
|
||||
if (existingPermissions && [existingPermissions[@"status"] intValue] != UMPermissionStatusUndetermined) {
|
||||
// since permissions are already determined, the iOS request methods will be no-ops.
|
||||
// just resolve with whatever existing permissions.
|
||||
resolve(existingPermissions);
|
||||
} else {
|
||||
_resolve = resolve;
|
||||
_reject = reject;
|
||||
|
||||
UM_WEAKIFY(self)
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
UM_ENSURE_STRONGIFY(self)
|
||||
self.locMgr = [[CLLocationManager alloc] init];
|
||||
self.locMgr.delegate = self;
|
||||
}];
|
||||
|
||||
// 1. Why do we call CLLocationManager methods by those dynamically created selectors?
|
||||
//
|
||||
// Most probably application code submitted to Apple Store is statically analyzed
|
||||
// paying special attention to camelcase(request_always_location) being called on CLLocationManager.
|
||||
// This lets Apple warn developers when it notices that location authorization may be requested
|
||||
// while there is no NSLocationUsageDescription in Info.plist. Since we want to neither
|
||||
// make Expo developers receive this kind of messages nor add our own default usage description,
|
||||
// we try to fool the static analyzer and construct the selector in runtime.
|
||||
// This way behavior of this requester is governed by provided NSLocationUsageDescriptions.
|
||||
//
|
||||
// 2. Why there's no way to call specifically whenInUse or always authorization?
|
||||
//
|
||||
// The requester sets itself as the delegate of the CLLocationManager, so when the user responds
|
||||
// to a permission requesting dialog, manager calls `locationManager:didChangeAuthorizationStatus:` method.
|
||||
// To be precise, manager calls this method in two circumstances:
|
||||
// - right when `request*Authorization` method is called,
|
||||
// - when `authorizationStatus` changes.
|
||||
// With this behavior we aren't able to support the following use case:
|
||||
// - app requests `whenInUse` authorization
|
||||
// - user allows `whenInUse` authorization
|
||||
// - `authorizationStatus` changes from `undetermined` to `whenInUse`, callback is called, promise is resolved
|
||||
// - app wants to escalate authorization to `always`
|
||||
// - user selects `whenInUse` authorization (iOS 11+)
|
||||
// - `authorizationStatus` doesn't change, so callback is not called and requester can't know whether
|
||||
// user responded to the dialog selecting `whenInUse` or is still deciding
|
||||
// To support this use case we will have to change the way location authorization is requested
|
||||
// from promise-based to listener-based.
|
||||
|
||||
if ([[self class] isConfiguredForAlwaysAuthorization] && [_locMgr respondsToSelector:alwaysAuthorizationSelector]) {
|
||||
((void (*)(id, SEL))objc_msgSend)(_locMgr, alwaysAuthorizationSelector);
|
||||
} else if ([[self class] isConfiguredForWhenInUseAuthorization] && [_locMgr respondsToSelector:whenInUseAuthorizationSelector]) {
|
||||
((void (*)(id, SEL))objc_msgSend)(_locMgr, whenInUseAuthorizationSelector);
|
||||
} else {
|
||||
_reject(@"E_LOCATION_INFO_PLIST", @"One of the `NSLocation*UsageDescription` keys must be present in Info.plist to be able to use geolocation.", nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - internal
|
||||
|
||||
+ (BOOL)isConfiguredForWhenInUseAuthorization
|
||||
{
|
||||
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] != nil;
|
||||
}
|
||||
|
||||
+ (BOOL)isConfiguredForAlwaysAuthorization
|
||||
{
|
||||
if (@available(iOS 11.0, *)) {
|
||||
return [self isConfiguredForWhenInUseAuthorization] && [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"];
|
||||
}
|
||||
|
||||
// iOS 10 fallback
|
||||
return [self isConfiguredForWhenInUseAuthorization] && [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"];
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
{
|
||||
if (_reject) {
|
||||
_reject(@"E_LOCATION_ERROR_UNKNOWN", error.localizedDescription, error);
|
||||
_resolve = nil;
|
||||
_reject = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
|
||||
{
|
||||
// TODO: Permissions.LOCATION issue (search by this phrase)
|
||||
// if Permissions.LOCATION is being called for the first time on iOS devide and prompts for user action it might not call this callback at all
|
||||
// it happens if user requests more that one permission at the same time via Permissions.askAsync(...) and LOCATION dialog is not being called first
|
||||
// to reproduce this find NCL code testing that
|
||||
if (status == kCLAuthorizationStatusNotDetermined) {
|
||||
// CLLocationManager calls this delegate method once on start with kCLAuthorizationNotDetermined even before the user responds
|
||||
// to the "Don't Allow" / "Allow" dialog box. This isn't the event we care about so we skip it. See:
|
||||
// http://stackoverflow.com/questions/30106341/swift-locationmanager-didchangeauthorizationstatus-always-called/30107511#30107511
|
||||
return;
|
||||
}
|
||||
if (_resolve) {
|
||||
_resolve([self getPermissions]);
|
||||
_resolve = nil;
|
||||
_reject = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
14
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXGeofencingTaskConsumer.h
generated
vendored
Normal file
14
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXGeofencingTaskConsumer.h
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2018-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
#import <UMTaskManagerInterface/UMTaskConsumerInterface.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXGeofencingTaskConsumer : NSObject <UMTaskConsumerInterface, CLLocationManagerDelegate>
|
||||
|
||||
@property (nonatomic, strong) id<UMTaskInterface> task;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
215
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXGeofencingTaskConsumer.m
generated
vendored
Normal file
215
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXGeofencingTaskConsumer.m
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
// Copyright 2018-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CoreLocation/CLCircularRegion.h>
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLErrorDomain.h>
|
||||
|
||||
#import <UMCore/UMUtilities.h>
|
||||
#import <EXLocation/EXLocation.h>
|
||||
#import <EXLocation/EXGeofencingTaskConsumer.h>
|
||||
#import <UMTaskManagerInterface/UMTaskInterface.h>
|
||||
|
||||
@interface EXGeofencingTaskConsumer ()
|
||||
|
||||
@property (nonatomic, strong) CLLocationManager *locationManager;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *regionStates;
|
||||
@property (nonatomic, assign) BOOL backgroundOnly;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXGeofencingTaskConsumer
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self reset];
|
||||
}
|
||||
|
||||
# pragma mark - UMTaskConsumerInterface
|
||||
|
||||
- (NSString *)taskType
|
||||
{
|
||||
return @"geofencing";
|
||||
}
|
||||
|
||||
- (void)setOptions:(nonnull NSDictionary *)options
|
||||
{
|
||||
[self stopMonitoringAllRegions];
|
||||
[self startMonitoringRegionsForTask:self->_task];
|
||||
}
|
||||
|
||||
- (void)didRegisterTask:(id<UMTaskInterface>)task
|
||||
{
|
||||
[self startMonitoringRegionsForTask:task];
|
||||
}
|
||||
|
||||
- (void)didUnregister
|
||||
{
|
||||
[self reset];
|
||||
}
|
||||
|
||||
# pragma mark - helpers
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
[self stopMonitoringAllRegions];
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
self->_locationManager = nil;
|
||||
self->_task = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startMonitoringRegionsForTask:(id<UMTaskInterface>)task
|
||||
{
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
CLLocationManager *locationManager = [CLLocationManager new];
|
||||
NSMutableDictionary *regionStates = [NSMutableDictionary new];
|
||||
NSDictionary *options = [task options];
|
||||
NSArray *regions = options[@"regions"];
|
||||
|
||||
self->_task = task;
|
||||
self->_locationManager = locationManager;
|
||||
self->_regionStates = regionStates;
|
||||
|
||||
locationManager.delegate = self;
|
||||
locationManager.allowsBackgroundLocationUpdates = YES;
|
||||
locationManager.pausesLocationUpdatesAutomatically = NO;
|
||||
|
||||
for (NSDictionary *regionDict in regions) {
|
||||
NSString *identifier = regionDict[@"identifier"] ?: [[NSUUID UUID] UUIDString];
|
||||
CLLocationDistance radius = [regionDict[@"radius"] doubleValue];
|
||||
CLLocationCoordinate2D center = [self.class coordinateFromDictionary:regionDict];
|
||||
BOOL notifyOnEntry = [self.class boolValueFrom:regionDict[@"notifyOnEntry"] defaultValue:YES];
|
||||
BOOL notifyOnExit = [self.class boolValueFrom:regionDict[@"notifyOnExit"] defaultValue:YES];
|
||||
|
||||
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:radius identifier:identifier];
|
||||
|
||||
region.notifyOnEntry = notifyOnEntry;
|
||||
region.notifyOnExit = notifyOnExit;
|
||||
|
||||
[regionStates setObject:@(CLRegionStateUnknown) forKey:identifier];
|
||||
[locationManager startMonitoringForRegion:region];
|
||||
[locationManager requestStateForRegion:region];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopMonitoringAllRegions
|
||||
{
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
for (CLRegion *region in self->_locationManager.monitoredRegions) {
|
||||
[self->_locationManager stopMonitoringForRegion:region];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)executeTaskWithRegion:(nonnull CLRegion *)region eventType:(EXGeofencingEventType)eventType
|
||||
{
|
||||
if ([region isKindOfClass:[CLCircularRegion class]]) {
|
||||
CLCircularRegion *circularRegion = (CLCircularRegion *)region;
|
||||
CLRegionState regionState = [self regionStateForIdentifier:circularRegion.identifier];
|
||||
NSDictionary *data = @{
|
||||
@"eventType": @(eventType),
|
||||
@"region": [[self class] exportRegion:circularRegion withState:regionState],
|
||||
};
|
||||
|
||||
[_task executeWithData:data withError:nil];
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - CLLocationManagerDelegate
|
||||
|
||||
// There is a bug in iOS that causes didEnterRegion and didExitRegion to be called multiple times.
|
||||
// https://stackoverflow.com/questions/36807060/region-monitoring-method-getting-called-multiple-times-in-geo-fencing
|
||||
// To prevent this behavior, we execute tasks only when the state has changed.
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
|
||||
{
|
||||
if ([self regionStateForIdentifier:region.identifier] != CLRegionStateInside) {
|
||||
[self setRegionState:CLRegionStateInside forIdentifier:region.identifier];
|
||||
[self executeTaskWithRegion:region eventType:EXGeofencingEventTypeEnter];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
|
||||
{
|
||||
if ([self regionStateForIdentifier:region.identifier] != CLRegionStateOutside) {
|
||||
[self setRegionState:CLRegionStateOutside forIdentifier:region.identifier];
|
||||
[self executeTaskWithRegion:region eventType:EXGeofencingEventTypeExit];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
{
|
||||
[_task executeWithData:nil withError:error];
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
|
||||
{
|
||||
if (error && error.domain == kCLErrorDomain) {
|
||||
// This error might happen when the device is not able to find out the location. Try to restart monitoring this region.
|
||||
[_locationManager stopMonitoringForRegion:region];
|
||||
[_locationManager startMonitoringForRegion:region];
|
||||
[_locationManager requestStateForRegion:region];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
|
||||
{
|
||||
if ([self regionStateForIdentifier:region.identifier] != state) {
|
||||
EXGeofencingEventType eventType = state == CLRegionStateInside ? EXGeofencingEventTypeEnter : EXGeofencingEventTypeExit;
|
||||
|
||||
[self setRegionState:state forIdentifier:region.identifier];
|
||||
[self executeTaskWithRegion:region eventType:eventType];
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - helpers
|
||||
|
||||
- (CLRegionState)regionStateForIdentifier:(NSString *)identifier
|
||||
{
|
||||
return [_regionStates[identifier] integerValue];
|
||||
}
|
||||
|
||||
- (void)setRegionState:(CLRegionState)regionState forIdentifier:(NSString *)identifier
|
||||
{
|
||||
[_regionStates setObject:@(regionState) forKey:identifier];
|
||||
}
|
||||
|
||||
# pragma mark - static helpers
|
||||
|
||||
+ (nonnull NSDictionary *)exportRegion:(nonnull CLCircularRegion *)region withState:(CLRegionState)regionState
|
||||
{
|
||||
return @{
|
||||
@"identifier": region.identifier,
|
||||
@"state": @([self exportRegionState:regionState]),
|
||||
@"radius": @(region.radius),
|
||||
@"latitude": @(region.center.latitude),
|
||||
@"longitude": @(region.center.longitude),
|
||||
};
|
||||
}
|
||||
|
||||
+ (EXGeofencingRegionState)exportRegionState:(CLRegionState)regionState
|
||||
{
|
||||
switch (regionState) {
|
||||
case CLRegionStateUnknown:
|
||||
return EXGeofencingRegionStateUnknown;
|
||||
case CLRegionStateInside:
|
||||
return EXGeofencingRegionStateInside;
|
||||
case CLRegionStateOutside:
|
||||
return EXGeofencingRegionStateOutside;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CLLocationCoordinate2D)coordinateFromDictionary:(nonnull NSDictionary *)dict
|
||||
{
|
||||
CLLocationDegrees latitude = [dict[@"latitude"] doubleValue];
|
||||
CLLocationDegrees longitude = [dict[@"longitude"] doubleValue];
|
||||
return CLLocationCoordinate2DMake(latitude, longitude);
|
||||
}
|
||||
|
||||
+ (BOOL)boolValueFrom:(id)pointer defaultValue:(BOOL)defaultValue
|
||||
{
|
||||
return pointer == nil ? defaultValue : [pointer boolValue];
|
||||
}
|
||||
|
||||
@end
|
14
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXLocationTaskConsumer.h
generated
vendored
Normal file
14
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXLocationTaskConsumer.h
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2018-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CoreLocation/CLLocationManagerDelegate.h>
|
||||
#import <UMTaskManagerInterface/UMTaskConsumerInterface.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface EXLocationTaskConsumer : NSObject <UMTaskConsumerInterface, CLLocationManagerDelegate>
|
||||
|
||||
@property (nonatomic, strong) id<UMTaskInterface> task;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
190
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXLocationTaskConsumer.m
generated
vendored
Normal file
190
node_modules/expo-location/ios/EXLocation/TaskConsumers/EXLocationTaskConsumer.m
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2018-present 650 Industries. All rights reserved.
|
||||
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <CoreLocation/CLErrorDomain.h>
|
||||
|
||||
#import <UMCore/UMUtilities.h>
|
||||
#import <EXLocation/EXLocation.h>
|
||||
#import <EXLocation/EXLocationTaskConsumer.h>
|
||||
#import <UMTaskManagerInterface/UMTaskInterface.h>
|
||||
|
||||
@interface EXLocationTaskConsumer ()
|
||||
|
||||
@property (nonatomic, strong) CLLocationManager *locationManager;
|
||||
@property (nonatomic, strong) NSMutableArray<CLLocation *> *deferredLocations;
|
||||
@property (nonatomic, strong) CLLocation *lastReportedLocation;
|
||||
@property (nonatomic, assign) CLLocationDistance deferredDistance;
|
||||
|
||||
@end
|
||||
|
||||
@implementation EXLocationTaskConsumer
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_deferredLocations = [NSMutableArray new];
|
||||
_deferredDistance = 0.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self reset];
|
||||
}
|
||||
|
||||
# pragma mark - UMTaskConsumerInterface
|
||||
|
||||
- (NSString *)taskType
|
||||
{
|
||||
return @"location";
|
||||
}
|
||||
|
||||
- (void)didRegisterTask:(id<UMTaskInterface>)task
|
||||
{
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
CLLocationManager *locationManager = [CLLocationManager new];
|
||||
|
||||
self->_task = task;
|
||||
self->_locationManager = locationManager;
|
||||
|
||||
locationManager.delegate = self;
|
||||
locationManager.allowsBackgroundLocationUpdates = YES;
|
||||
|
||||
// Set options-specific things in location manager.
|
||||
[self setOptions:task.options];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didUnregister
|
||||
{
|
||||
[self reset];
|
||||
}
|
||||
|
||||
- (void)setOptions:(NSDictionary *)options
|
||||
{
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
CLLocationManager *locationManager = self->_locationManager;
|
||||
EXLocationAccuracy accuracy = [options[@"accuracy"] unsignedIntegerValue] ?: EXLocationAccuracyBalanced;
|
||||
|
||||
locationManager.desiredAccuracy = [EXLocation CLLocationAccuracyFromOption:accuracy];
|
||||
locationManager.distanceFilter = [options[@"distanceInterval"] doubleValue] ?: kCLDistanceFilterNone;
|
||||
locationManager.activityType = [EXLocation CLActivityTypeFromOption:[options[@"activityType"] integerValue]];
|
||||
locationManager.pausesLocationUpdatesAutomatically = [options[@"pausesUpdatesAutomatically"] boolValue];
|
||||
|
||||
if (@available(iOS 11.0, *)) {
|
||||
locationManager.showsBackgroundLocationIndicator = [options[@"showsBackgroundLocationIndicator"] boolValue];
|
||||
}
|
||||
|
||||
[locationManager startUpdatingLocation];
|
||||
[locationManager startMonitoringSignificantLocationChanges];
|
||||
}];
|
||||
}
|
||||
|
||||
# pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
|
||||
{
|
||||
if (_task != nil && locations.count > 0) {
|
||||
[self deferLocations:locations];
|
||||
[self maybeReportDeferredLocations];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
|
||||
{
|
||||
if (error.domain == kCLErrorDomain) {
|
||||
// This error might happen when the device is not able to find out the location. Try to restart monitoring location.
|
||||
[manager stopUpdatingLocation];
|
||||
[manager stopMonitoringSignificantLocationChanges];
|
||||
[manager startUpdatingLocation];
|
||||
[manager startMonitoringSignificantLocationChanges];
|
||||
} else {
|
||||
[_task executeWithData:nil withError:error];
|
||||
}
|
||||
}
|
||||
|
||||
# pragma mark - internal
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
[UMUtilities performSynchronouslyOnMainThread:^{
|
||||
[self->_locationManager stopUpdatingLocation];
|
||||
[self->_locationManager stopMonitoringSignificantLocationChanges];
|
||||
[self->_deferredLocations removeAllObjects];
|
||||
self->_lastReportedLocation = nil;
|
||||
self->_deferredDistance = 0.0;
|
||||
self->_locationManager = nil;
|
||||
self->_task = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)executeTaskWithDeferredLocations
|
||||
{
|
||||
// Execute task with deferred locations.
|
||||
NSDictionary *data = @{ @"locations": [EXLocationTaskConsumer _exportLocations:_deferredLocations] };
|
||||
[_task executeWithData:data withError:nil];
|
||||
|
||||
// Reset deferring state.
|
||||
_lastReportedLocation = _deferredLocations.lastObject;
|
||||
_deferredDistance = 0.0;
|
||||
[_deferredLocations removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)maybeReportDeferredLocations
|
||||
{
|
||||
if ([self shouldReportDeferredLocations]) {
|
||||
[self executeTaskWithDeferredLocations];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deferLocations:(NSArray<CLLocation *> *)locations
|
||||
{
|
||||
CLLocation *lastLocation = _deferredLocations.lastObject ?: _lastReportedLocation;
|
||||
|
||||
for (CLLocation *location in locations) {
|
||||
if (lastLocation) {
|
||||
_deferredDistance += [location distanceFromLocation:lastLocation];
|
||||
}
|
||||
lastLocation = location;
|
||||
}
|
||||
[_deferredLocations addObjectsFromArray:locations];
|
||||
}
|
||||
|
||||
- (BOOL)shouldReportDeferredLocations
|
||||
{
|
||||
if (_deferredLocations.count <= 0) {
|
||||
return NO;
|
||||
}
|
||||
UIApplicationState appState = [[UIApplication sharedApplication] applicationState];
|
||||
|
||||
if (appState == UIApplicationStateActive) {
|
||||
// Don't defer location updates when app is in foreground state.
|
||||
return YES;
|
||||
}
|
||||
|
||||
CLLocation *oldestLocation = _lastReportedLocation ?: _deferredLocations.firstObject;
|
||||
CLLocation *newestLocation = _deferredLocations.lastObject;
|
||||
NSDictionary *options = _task.options;
|
||||
CLLocationDistance distance = [self numberToDouble:options[@"deferredUpdatesDistance"] defaultValue:0];
|
||||
NSTimeInterval interval = [self numberToDouble:options[@"deferredUpdatesInterval"] defaultValue:0];
|
||||
|
||||
return [newestLocation.timestamp timeIntervalSinceDate:oldestLocation.timestamp] >= interval / 1000.0 && _deferredDistance >= distance;
|
||||
}
|
||||
|
||||
- (double)numberToDouble:(NSNumber *)number defaultValue:(double)defaultValue
|
||||
{
|
||||
return number == nil ? defaultValue : [number doubleValue];
|
||||
}
|
||||
|
||||
+ (NSArray<NSDictionary *> *)_exportLocations:(NSArray<CLLocation *> *)locations
|
||||
{
|
||||
NSMutableArray<NSDictionary *> *result = [NSMutableArray new];
|
||||
|
||||
for (CLLocation *location in locations) {
|
||||
[result addObject:[EXLocation exportLocation:location]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user