diff --git a/Examples/UIExplorer/GeoLocationExample.js b/Examples/UIExplorer/GeoLocationExample.js
index 1ab5f290c..fac3dd205 100644
--- a/Examples/UIExplorer/GeoLocationExample.js
+++ b/Examples/UIExplorer/GeoLocationExample.js
@@ -1,7 +1,7 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
- * @providesModule GeoLocationExample
+ * @providesModule GeolocationExample
*/
/* eslint no-console: 0 */
'use strict';
@@ -15,19 +15,19 @@ var {
} = React;
exports.framework = 'React';
-exports.title = 'GeoLocation';
-exports.description = 'Examples of using the GeoLocation API.';
+exports.title = 'Geolocation';
+exports.description = 'Examples of using the Geolocation API.';
exports.examples = [
{
title: 'navigator.geolocation',
render: function() {
- return ;
+ return ;
},
}
];
-var GeoLocationExample = React.createClass({
+var GeolocationExample = React.createClass({
getInitialState: function() {
return {
initialPosition: 'unknown',
diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index 942ec24b0..ea13d28e3 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -11,6 +11,7 @@
134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; };
134180021AA9153C003F314A /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FFF1AA91531003F314A /* libReactKit.a */; };
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
+ 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
@@ -46,6 +47,13 @@
remoteGlobalIDString = 58B511DB1A9E6C8500147676;
remoteInfo = RCTNetwork;
};
+ 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 134814201AA4EA6300B7C361;
+ remoteInfo = RCTGeolocation;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@@ -53,6 +61,7 @@
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; };
13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = ""; };
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; };
+ 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
@@ -67,6 +76,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */,
134180011AA9153C003F314A /* libRCTText.a in Frameworks */,
134180021AA9153C003F314A /* libReactKit.a in Frameworks */,
@@ -80,6 +90,7 @@
1316A21D1AA397F400C0188E /* Libraries */ = {
isa = PBXGroup;
children = (
+ 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
13417FFA1AA91531003F314A /* ReactKit.xcodeproj */,
134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
@@ -120,6 +131,14 @@
name = Products;
sourceTree = "";
};
+ 134A8A211AACED6A00945AAE /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
13B07FAE1A68108700A75B9A /* UIExplorer */ = {
isa = PBXGroup;
children = (
@@ -191,6 +210,10 @@
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectReferences = (
+ {
+ ProductGroup = 134A8A211AACED6A00945AAE /* Products */;
+ ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
+ },
{
ProductGroup = 13417FE41AA91428003F314A /* Products */;
ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */;
@@ -244,6 +267,13 @@
remoteRef = 1341802A1AA91779003F314A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
+ 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTGeolocation.a;
+ remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js
index ee4130e4a..dd658b6ad 100644
--- a/Examples/UIExplorer/UIExplorerList.js
+++ b/Examples/UIExplorer/UIExplorerList.js
@@ -32,7 +32,7 @@ var EXAMPLES = [
require('./ActivityIndicatorExample'),
require('./ScrollViewExample'),
require('./DatePickerExample'),
- require('./GeoLocationExample'),
+ require('./GeolocationExample'),
require('./TabBarExample'),
];
diff --git a/Libraries/GeoLocation/GeoLocation.js b/Libraries/GeoLocation/Geolocation.ios.js
similarity index 65%
rename from Libraries/GeoLocation/GeoLocation.js
rename to Libraries/GeoLocation/Geolocation.ios.js
index 9a7f792c4..1b5719212 100644
--- a/Libraries/GeoLocation/GeoLocation.js
+++ b/Libraries/GeoLocation/Geolocation.ios.js
@@ -1,12 +1,12 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
- * @providesModule GeoLocation
+ * @providesModule Geolocation
*/
'use strict';
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
-var RCTLocationObserver = require('NativeModules').RKLocationObserver;
+var RCTLocationObserver = require('NativeModulesDeprecated').RKLocationObserver;
var invariant = require('invariant');
var logError = require('logError');
@@ -16,13 +16,6 @@ var subscriptions = [];
var updatesEnabled = false;
-var ensureObserving = function() {
- if (!updatesEnabled) {
- RCTLocationObserver.startObserving();
- updatesEnabled = true;
- }
-};
-
/**
* /!\ ATTENTION /!\
* You need to add NSLocationWhenInUseUsageDescription key
@@ -30,43 +23,51 @@ var ensureObserving = function() {
* to *fail silently*!
* \!/ \!/
*
- * GeoLocation follows the MDN specification:
+ * Geolocation follows the MDN specification:
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
*/
-class GeoLocation {
- static getCurrentPosition(geo_success, geo_error, geo_options) {
+var Geolocation = {
+
+ getCurrentPosition: function(geo_success, geo_error, geo_options) {
invariant(
typeof geo_success === 'function',
'Must provide a valid geo_success callback.'
);
- if (geo_options) {
- warning('geo_options are not yet supported.');
- }
- ensureObserving();
RCTLocationObserver.getCurrentPosition(
geo_success,
- geo_error || logError
+ geo_error || logError,
+ geo_options || {}
);
- }
- static watchPosition(callback) {
- ensureObserving();
+ },
+
+ watchPosition: function(success, error, options) {
+ if (!updatesEnabled) {
+ RCTLocationObserver.startObserving(options || {});
+ updatesEnabled = true;
+ }
var watchID = subscriptions.length;
- subscriptions.push(
+ subscriptions.push([
RCTDeviceEventEmitter.addListener(
- 'geoLocationDidChange',
- callback
- )
- );
+ 'geolocationDidChange',
+ success
+ ),
+ error ? RCTDeviceEventEmitter.addListener(
+ 'geolocationError',
+ error
+ ) : null,
+ ]);
return watchID;
- }
- static clearWatch(watchID) {
+ },
+
+ clearWatch: function(watchID) {
var sub = subscriptions[watchID];
if (!sub) {
// Silently exit when the watchID is invalid or already cleared
// This is consistent with timers
return;
}
- sub.remove();
+ sub[0].remove();
+ sub[1] && sub[1].remove();
subscriptions[watchID] = undefined;
var noWatchers = true;
for (var ii = 0; ii < subscriptions.length; ii++) {
@@ -75,10 +76,11 @@ class GeoLocation {
}
}
if (noWatchers) {
- GeoLocation.stopObserving();
+ Geolocation.stopObserving();
}
- }
- static stopObserving() {
+ },
+
+ stopObserving: function() {
if (updatesEnabled) {
RCTLocationObserver.stopObserving();
updatesEnabled = false;
@@ -89,10 +91,8 @@ class GeoLocation {
}
}
subscriptions = [];
- } else {
- warning('Tried to stop observing when not observing.');
}
}
}
-module.exports = GeoLocation;
+module.exports = Geolocation;
diff --git a/ReactKit/Modules/RCTLocationObserver.h b/Libraries/GeoLocation/RCTLocationObserver.h
similarity index 100%
rename from ReactKit/Modules/RCTLocationObserver.h
rename to Libraries/GeoLocation/RCTLocationObserver.h
diff --git a/Libraries/GeoLocation/RCTLocationObserver.m b/Libraries/GeoLocation/RCTLocationObserver.m
new file mode 100644
index 000000000..019653640
--- /dev/null
+++ b/Libraries/GeoLocation/RCTLocationObserver.m
@@ -0,0 +1,319 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTLocationObserver.h"
+
+#import
+#import
+#import
+
+#import "RCTAssert.h"
+#import "RCTBridge.h"
+#import "RCTConvert.h"
+#import "RCTEventDispatcher.h"
+#import "RCTLog.h"
+
+typedef NS_ENUM(NSInteger, RCTPositionErrorCode) {
+ RCTPositionErrorDenied = 1,
+ RCTPositionErrorUnavailable,
+ RCTPositionErrorTimeout,
+};
+
+#define RCT_DEFAULT_LOCATION_ACCURACY kCLLocationAccuracyHundredMeters
+
+typedef struct {
+ NSTimeInterval timeout;
+ NSTimeInterval maximumAge;
+ CLLocationAccuracy accuracy;
+} RCTLocationOptions;
+
+static RCTLocationOptions RCTLocationOptionsWithJSON(id json)
+{
+ NSDictionary *options = [RCTConvert NSDictionary:json];
+ return (RCTLocationOptions){
+ .timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY,
+ .maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY,
+ .accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY
+ };
+}
+
+static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */)
+{
+ if (!msg) {
+ switch (code) {
+ case RCTPositionErrorDenied:
+ msg = @"User denied access to location services.";
+ break;
+ case RCTPositionErrorUnavailable:
+ msg = @"Unable to retrieve location.";
+ break;
+ case RCTPositionErrorTimeout:
+ msg = @"The location request timed out.";
+ break;
+ }
+ }
+
+ return @{
+ @"code": @(code),
+ @"message": msg,
+ @"PERMISSION_DENIED": @(RCTPositionErrorDenied),
+ @"POSITION_UNAVAILABLE": @(RCTPositionErrorUnavailable),
+ @"TIMEOUT": @(RCTPositionErrorTimeout)
+ };
+}
+
+@interface RCTLocationRequest : NSObject
+
+@property (nonatomic, copy) RCTResponseSenderBlock successBlock;
+@property (nonatomic, copy) RCTResponseSenderBlock errorBlock;
+@property (nonatomic, assign) RCTLocationOptions options;
+@property (nonatomic, strong) NSTimer *timeoutTimer;
+
+@end
+
+@implementation RCTLocationRequest
+
+- (void)dealloc
+{
+ [_timeoutTimer invalidate];
+}
+
+@end
+
+@interface RCTLocationObserver ()
+
+@end
+
+@implementation RCTLocationObserver
+{
+ CLLocationManager *_locationManager;
+ NSDictionary *_lastLocationEvent;
+ NSMutableArray *_pendingRequests;
+ BOOL _observingLocation;
+ RCTLocationOptions _observerOptions;
+}
+
+@synthesize bridge = _bridge;
+
+#pragma mark - Lifecycle
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+
+ _locationManager = [[CLLocationManager alloc] init];
+ _locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
+ _locationManager.delegate = self;
+
+ _pendingRequests = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [_locationManager stopUpdatingLocation];
+}
+
+#pragma mark - Private API
+
+- (void)beginLocationUpdates
+{
+ // Request location access permission
+ if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
+ [_locationManager requestWhenInUseAuthorization];
+ }
+
+ // Start observing location
+ [_locationManager startUpdatingLocation];
+}
+
+#pragma mark - Timeout handler
+
+- (void)timeout:(NSTimer *)timer
+{
+ RCTLocationRequest *request = timer.userInfo;
+ NSString *message = [NSString stringWithFormat: @"Unable to fetch location within %zds.", (NSInteger)(timer.timeInterval * 1000.0)];
+ request.errorBlock(@[RCTPositionError(RCTPositionErrorTimeout, message)]);
+ [_pendingRequests removeObject:request];
+
+ // Stop updating if no pending requests
+ if (_pendingRequests.count == 0 && !_observingLocation) {
+ [_locationManager stopUpdatingLocation];
+ }
+}
+
+#pragma mark - Public API
+
+- (void)startObserving:(NSDictionary *)optionsJSON
+{
+ RCT_EXPORT();
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ // Select best options
+ _observerOptions = RCTLocationOptionsWithJSON(optionsJSON);
+ for (RCTLocationRequest *request in _pendingRequests) {
+ _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
+ }
+
+ _locationManager.desiredAccuracy = _observerOptions.accuracy;
+ [self beginLocationUpdates];
+ _observingLocation = YES;
+
+ });
+}
+
+- (void)stopObserving
+{
+ RCT_EXPORT();
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ // Stop observing
+ _observingLocation = NO;
+
+ // Stop updating if no pending requests
+ if (_pendingRequests.count == 0) {
+ [_locationManager stopUpdatingLocation];
+ }
+
+ });
+}
+
+- (void)getCurrentPosition:(RCTResponseSenderBlock)successBlock
+ withErrorCallback:(RCTResponseSenderBlock)errorBlock
+ options:(NSDictionary *)optionsJSON
+{
+ RCT_EXPORT();
+
+ if (!successBlock) {
+ RCTLogError(@"%@.getCurrentPosition called with nil success parameter.", [self class]);
+ return;
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ if (![CLLocationManager locationServicesEnabled]) {
+ if (errorBlock) {
+ errorBlock(@[
+ RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.")
+ ]);
+ return;
+ }
+ }
+
+ if (![CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
+ if (errorBlock) {
+ errorBlock(@[
+ RCTPositionError(RCTPositionErrorDenied, nil)
+ ]);
+ return;
+ }
+ }
+
+ // Get options
+ RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON);
+
+ // Check if previous recorded location exists and is good enough
+ if (_lastLocationEvent &&
+ CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
+ [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
+
+ // Call success block with most recent known location
+ successBlock(@[_lastLocationEvent]);
+ return;
+ }
+
+ // Create request
+ RCTLocationRequest *request = [[RCTLocationRequest alloc] init];
+ request.successBlock = successBlock;
+ request.errorBlock = errorBlock ?: ^(NSArray *args){};
+ request.options = options;
+ request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout
+ target:self
+ selector:@selector(timeout:)
+ userInfo:request
+ repeats:NO];
+ [_pendingRequests addObject:request];
+
+ // Configure location manager and begin updating location
+ _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
+ [self beginLocationUpdates];
+
+ });
+}
+
+#pragma mark - CLLocationManagerDelegate
+
+- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
+{
+ // Create event
+ CLLocation *location = [locations lastObject];
+ _lastLocationEvent = @{
+ @"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": @(CFAbsoluteTimeGetCurrent() * 1000.0) // in ms
+ };
+
+ // Send event
+ if (_observingLocation) {
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationDidChange"
+ body:_lastLocationEvent];
+ }
+
+ // Fire all queued callbacks
+ for (RCTLocationRequest *request in _pendingRequests) {
+ request.successBlock(@[_lastLocationEvent]);
+ }
+ [_pendingRequests removeAllObjects];
+
+ // Stop updating if not not observing
+ if (!_observingLocation) {
+ [_locationManager stopUpdatingLocation];
+ }
+
+ // Reset location accuracy
+ _locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
+}
+
+- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
+{
+ // Check error type
+ NSDictionary *jsError = nil;
+ switch (error.code) {
+ case kCLErrorDenied:
+ jsError = RCTPositionError(RCTPositionErrorDenied, nil);
+ break;
+ case kCLErrorNetwork:
+ jsError = RCTPositionError(RCTPositionErrorUnavailable, @"Unable to retrieve location due to a network failure");
+ break;
+ case kCLErrorLocationUnknown:
+ default:
+ jsError = RCTPositionError(RCTPositionErrorUnavailable, nil);
+ break;
+ }
+
+ // Send event
+ if (_observingLocation) {
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationError"
+ body:jsError];
+ }
+
+ // Fire all queued error callbacks
+ for (RCTLocationRequest *request in _pendingRequests) {
+ request.errorBlock(@[jsError]);
+ }
+ [_pendingRequests removeAllObjects];
+
+ // Reset location accuracy
+ _locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY;
+}
+
+@end
diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
index 3c507129b..422447d46 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
@@ -140,7 +140,7 @@ function setupXHR() {
function setupGeolocation() {
GLOBAL.navigator = GLOBAL.navigator || {};
- GLOBAL.navigator.geolocation = require('GeoLocation');
+ GLOBAL.navigator.geolocation = require('Geolocation');
}
setupDocumentShim();
diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h
index a5aa59fe6..16cd6967b 100644
--- a/ReactKit/Base/RCTConvert.h
+++ b/ReactKit/Base/RCTConvert.h
@@ -19,6 +19,8 @@
+ (float)float:(id)json;
+ (int)int:(id)json;
++ (NSArray *)NSArray:(id)json;
++ (NSDictionary *)NSDictionary:(id)json;
+ (NSString *)NSString:(id)json;
+ (NSNumber *)NSNumber:(id)json;
+ (NSInteger)NSInteger:(id)json;
diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m
index bec60ff6c..760f8284d 100644
--- a/ReactKit/Base/RCTConvert.m
+++ b/ReactKit/Base/RCTConvert.m
@@ -99,6 +99,8 @@ RCT_CONVERTER(double, double, doubleValue)
RCT_CONVERTER(float, float, floatValue)
RCT_CONVERTER(int, int, intValue)
+RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
+RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CONVERTER(NSString *, NSString, description)
RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
RCT_CONVERTER(NSInteger, NSInteger, integerValue)
diff --git a/ReactKit/Modules/RCTLocationObserver.m b/ReactKit/Modules/RCTLocationObserver.m
deleted file mode 100644
index fd9c7ac07..000000000
--- a/ReactKit/Modules/RCTLocationObserver.m
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
-
-#import "RCTLocationObserver.h"
-
-#import
-#import
-
-#import "RCTAssert.h"
-#import "RCTBridge.h"
-#import "RCTEventDispatcher.h"
-#import "RCTLog.h"
-
-// TODO (#5906496): Shouldn't these be configurable?
-const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters
-
-@interface RCTPendingLocationRequest : NSObject
-
-@property (nonatomic, copy) RCTResponseSenderBlock successBlock;
-@property (nonatomic, copy) RCTResponseSenderBlock errorBlock;
-
-@end
-
-@implementation RCTPendingLocationRequest @end
-
-@interface RCTLocationObserver ()
-
-@end
-
-@implementation RCTLocationObserver
-{
- CLLocationManager *_locationManager;
- NSDictionary *_lastLocationEvent;
- NSMutableDictionary *_pendingRequests;
-}
-
-@synthesize bridge = _bridge;
-
-#pragma mark - Lifecycle
-
-- (instancetype)init
-{
- if ((self = [super init])) {
- _pendingRequests = [[NSMutableDictionary alloc] init];
- }
- return self;
-}
-
-- (void)dealloc
-{
- [_locationManager stopUpdatingLocation];
-}
-
-#pragma mark - Public API
-
-- (void)startObserving
-{
- RCT_EXPORT();
-
- dispatch_async(dispatch_get_main_queue(), ^{
-
- // Create the location manager if this object does not
- // already have one, and it must be created and accessed
- // on the main thread
- if (nil == _locationManager) {
- _locationManager = [[CLLocationManager alloc] init];
- }
-
- _locationManager.delegate = self;
- _locationManager.desiredAccuracy = RCTLocationAccuracy;
-
- // Set a movement threshold for new events.
- _locationManager.distanceFilter = RCTLocationAccuracy; // meters
-
- if([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
- [_locationManager requestWhenInUseAuthorization];
- }
-
- [_locationManager startUpdatingLocation];
-
- });
-}
-
-- (void)stopObserving
-{
- RCT_EXPORT();
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [_locationManager stopUpdatingLocation];
- _lastLocationEvent = nil;
- });
-}
-
-#pragma mark - CLLocationManagerDelegate
-
-- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
-{
- CLLocation *loc = [locations lastObject];
- NSDictionary *event = @{
- @"coords": @{
- @"latitude": @(loc.coordinate.latitude),
- @"longitude": @(loc.coordinate.longitude),
- @"altitude": @(loc.altitude),
- @"accuracy": @(RCTLocationAccuracy),
- @"altitudeAccuracy": @(RCTLocationAccuracy),
- @"heading": @(loc.course),
- @"speed": @(loc.speed),
- },
- @"timestamp": @(CACurrentMediaTime())
- };
- [_bridge.eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event];
- NSArray *pendingRequestsCopy;
-
- // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
- @synchronized(self) {
-
- pendingRequestsCopy = [_pendingRequests allValues];
- [_pendingRequests removeAllObjects];
-
- _lastLocationEvent = event;
- }
-
- for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
- if (request.successBlock) {
- request.successBlock(@[event]);
- }
- }
-}
-
-- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
-{
- NSArray *pendingRequestsCopy;
-
- // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
- @synchronized(self) {
- pendingRequestsCopy = [_pendingRequests allValues];
- [_pendingRequests removeAllObjects];
- }
-
- NSString *errorMsg = @"User denied location service or location service not available.";
- for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
- if (request.errorBlock) {
- request.errorBlock(@[errorMsg]);
- }
- }
-}
-
-- (void)getCurrentPosition:(RCTResponseSenderBlock)geoSuccess withErrorCallback:(RCTResponseSenderBlock)geoError
-{
- RCT_EXPORT();
-
- NSDictionary *lastLocationCopy;
- // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
- @synchronized(self) {
- if (![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
- if (geoError) {
- NSString *errorMsg = @"User denied location service or location service not available.";
- geoError(@[errorMsg]);
- return;
- }
- }
-
- // If a request for the current position comes in before the OS has informed us, we wait for the first
- // OS event and then call our callbacks. This obviates the need for handling of the otherwise
- // common failure case of requesting the geolocation until it succeeds, assuming we would have
- // instead returned an error if it wasn't yet available.
- if (!_lastLocationEvent) {
- NSInteger requestID = [_pendingRequests count];
- RCTPendingLocationRequest *request = [[RCTPendingLocationRequest alloc] init];
- request.successBlock = geoSuccess;
- request.errorBlock = geoError;
- _pendingRequests[@(requestID)] = request;
- return;
- } else {
- lastLocationCopy = [_lastLocationEvent copy];
- }
- }
- if (geoSuccess) {
- geoSuccess(@[lastLocationCopy]);
- }
-}
-
-@end
diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m
index 47b2e6c84..a22f24540 100644
--- a/ReactKit/Modules/RCTUIManager.m
+++ b/ReactKit/Modules/RCTUIManager.m
@@ -74,8 +74,9 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
_property = [RCTConvert NSString:config[@"property"]];
// TODO: this should be provided in ms, not seconds
- _duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration;
- _delay = [RCTConvert NSTimeInterval:config[@"delay"]];
+ // (this will require changing all call sites to ms as well)
+ _duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0 ?: duration;
+ _delay = [RCTConvert NSTimeInterval:config[@"delay"]] * 1000.0;
_animationType = [RCTConvert RCTAnimationType:config[@"type"]];
if (_animationType == RCTAnimationTypeSpring) {
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
@@ -135,7 +136,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
if ((self = [super init])) {
// TODO: this should be provided in ms, not seconds
- NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]];
+ // (this will require changing all call sites to ms as well)
+ NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0;
_createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]];
_updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]];
diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj
index cb3cb840c..63dff04fb 100644
--- a/ReactKit/ReactKit.xcodeproj/project.pbxproj
+++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj
@@ -35,7 +35,6 @@
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */; };
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; };
- 5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */; };
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
@@ -126,8 +125,6 @@
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = ""; };
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; };
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; };
- 5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocationObserver.h; sourceTree = ""; };
- 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = ""; };
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; };
@@ -190,8 +187,6 @@
13B07FE01A69315300A75B9A /* Modules */ = {
isa = PBXGroup;
children = (
- 5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */,
- 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */,
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
@@ -398,7 +393,6 @@
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
- 5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */,
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,