From 2b66b21c953c9beb21f973c3ef538d3aa073feed Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Mon, 16 Mar 2015 19:01:28 -0700 Subject: [PATCH] Updates from Mon 16 Mar - [ReactNative] Share same server port for debugger proxy | Alex Kotliarskyi - [react-packager] small fixes to image loader | Amjad Masad - [ReactNative] NetworkInformation.reachability API w/ example | Eric Vicenti - [ReactNative] Put launchOptions in RCTPushNotificationManager | Andrew Rasmussen - [ReactNative] Improve PixelRatio documentation | Christopher Chedeau --- Examples/UIExplorer/NetInfoExample.js | 135 +++++++++++++++++ Examples/UIExplorer/UIExplorerList.js | 1 + Libraries/AppState/AppState.js | 18 --- Libraries/Network/NetInfo.js | 143 ++++++++++++++++++ Libraries/ReactIOS/renderApplication.js | 6 +- Libraries/Utilities/PixelRatio.js | 48 +++--- Libraries/Utilities/PushNotificationIOS.js | 14 ++ Libraries/react-native/react-native.js | 1 + ReactKit/Base/RCTBridge.m | 2 +- ReactKit/Modules/RCTPushNotificationManager.h | 12 ++ ReactKit/Modules/RCTPushNotificationManager.m | 64 ++++++++ .../haste/DependencyGraph/index.js | 19 ++- 12 files changed, 411 insertions(+), 52 deletions(-) create mode 100644 Examples/UIExplorer/NetInfoExample.js create mode 100644 Libraries/Network/NetInfo.js create mode 100644 ReactKit/Modules/RCTPushNotificationManager.h create mode 100644 ReactKit/Modules/RCTPushNotificationManager.m diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js new file mode 100644 index 000000000..017da9921 --- /dev/null +++ b/Examples/UIExplorer/NetInfoExample.js @@ -0,0 +1,135 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + */ +'use strict'; + +var React = require('react-native'); +var { + NetInfo, + Text, + View +} = React; + +var ReachabilitySubscription = React.createClass({ + getInitialState() { + return { + reachabilityHistory: [], + }; + }, + componentDidMount: function() { + NetInfo.reachabilityIOS.addEventListener( + 'change', + this._handleReachabilityChange + ); + }, + componentWillUnmount: function() { + NetInfo.reachabilityIOS.removeEventListener( + 'change', + this._handleReachabilityChange + ); + }, + _handleReachabilityChange: function(reachability) { + var reachabilityHistory = this.state.reachabilityHistory.slice(); + reachabilityHistory.push(reachability); + this.setState({ + reachabilityHistory, + }); + }, + render() { + return ( + + {JSON.stringify(this.state.reachabilityHistory)} + + ); + } +}); + +var ReachabilityCurrent = React.createClass({ + getInitialState() { + return { + reachability: null, + }; + }, + componentDidMount: function() { + NetInfo.reachabilityIOS.addEventListener( + 'change', + this._handleReachabilityChange + ); + NetInfo.reachabilityIOS.fetch().done( + (reachability) => { this.setState({reachability}); } + ); + }, + componentWillUnmount: function() { + NetInfo.reachabilityIOS.removeEventListener( + 'change', + this._handleReachabilityChange + ); + }, + _handleReachabilityChange: function(reachability) { + this.setState({ + reachability, + }); + }, + render() { + return ( + + {this.state.reachability} + + ); + } +}); + +var IsConnected = React.createClass({ + getInitialState() { + return { + isConnected: null, + }; + }, + componentDidMount: function() { + NetInfo.isConnected.addEventListener( + 'change', + this._handleConnectivityChange + ); + NetInfo.isConnected.fetch().done( + (isConnected) => { this.setState({isConnected}); } + ); + }, + componentWillUnmount: function() { + NetInfo.isConnected.removeEventListener( + 'change', + this._handleConnectivityChange + ); + }, + _handleConnectivityChange: function(isConnected) { + this.setState({ + isConnected, + }); + }, + render() { + return ( + + {this.state.isConnected ? 'Online' : 'Offline'} + + ); + } +}); + +exports.title = 'NetInfo'; +exports.description = 'Monitor network status'; +exports.examples = [ + { + title: 'NetInfo.isConnected', + description: 'Asyncronously load and observe connectivity', + render() { return ; } + }, + { + title: 'NetInfo.reachabilityIOS', + description: 'Asyncronously load and observe iOS reachability', + render() { return ; } + }, + { + title: 'NetInfo.reachabilityIOS', + description: 'Observed updates to iOS reachability', + render() { return ; } + }, +]; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index b7108681e..a4f70fbcd 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -42,6 +42,7 @@ var EXAMPLES = [ require('./MapViewExample'), require('./WebViewExample'), require('./AppStateIOSExample'), + require('./NetInfoExample'), require('./AlertIOSExample'), require('./AdSupportIOSExample'), require('./AppStateExample'), diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index 43b9db1a5..ca5a7e607 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -25,22 +25,4 @@ var AppState = { }; -// This check avoids redboxing if native RKReachability library isn't included in app -// TODO: Move reachability API into separate JS module to prevent need for this -if (RKReachability) { - AppState.networkReachability = new Subscribable( - RCTDeviceEventEmitter, - 'reachabilityDidChange', - (resp) => resp.network_reachability, - RKReachability.getCurrentReachability - ); -} - -AppState.NetworkReachability = keyMirror({ - wifi: true, - cell: true, - none: true, - unknown: true, -}); - module.exports = AppState; diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js new file mode 100644 index 000000000..59c29cb07 --- /dev/null +++ b/Libraries/Network/NetInfo.js @@ -0,0 +1,143 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule NetInfo + * @flow + */ +'use strict'; + +var NativeModules = require('NativeModules'); +var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var RKReachability = NativeModules.RKReachability; + +var DEVICE_REACHABILITY_EVENT = 'reachabilityDidChange'; + +type ChangeEventName = $Enum<{ + change: string; +}>; + + +/** + * NetInfo exposes info about online/offline status + * + * == iOS Reachability + * + * Asyncronously determine if the device is online and on a cellular network. + * + * - "none" - device is offline + * - "wifi" - device is online and connected via wifi, or is the iOS simulator + * - "cell" - device is connected via Edge, 3G, WiMax, or LTE + * - "unknown" - error case and the network status is unknown + * + * ``` + * NetInfo.reachabilityIOS.fetch().done((reach) => { + * console.log('Initial: ' + reach); + * }); + * function handleFirstReachabilityChange(reach) { + * console.log('First change: ' + reach); + * NetInfo.reachabilityIOS.removeEventListener( + * 'change', + * handleFirstReachabilityChange + * ); + * } + * NetInfo.reachabilityIOS.addEventListener( + * 'change', + * handleFirstReachabilityChange + * ); + * ``` + */ + +var NetInfo = {}; + +if (RKReachability) { + var _reachabilitySubscriptions = {}; + + NetInfo.reachabilityIOS = { + addEventListener: function ( + eventName: ChangeEventName, + handler: Function + ): void { + _reachabilitySubscriptions[handler] = RCTDeviceEventEmitter.addListener( + DEVICE_REACHABILITY_EVENT, + (appStateData) => { + handler(appStateData.network_reachability); + } + ); + }, + + removeEventListener: function( + eventName: ChangeEventName, + handler: Function + ): void { + if (!_reachabilitySubscriptions[handler]) { + return; + } + _reachabilitySubscriptions[handler].remove(); + _reachabilitySubscriptions[handler] = null; + }, + + fetch: function(): Promise { + return new Promise((resolve, reject) => { + RKReachability.getCurrentReachability( + (resp) => { + resolve(resp.network_reachability); + }, + reject + ); + }); + }, + }; + + /** + * + * == NetInfo.isConnected + * + * Available on all platforms. Asyncronously fetch a boolean to determine + * internet connectivity. + * + * ``` + * NetInfo.isConnected.fetch().done((isConnected) => { + * console.log('First, is ' + (isConnected ? 'online' : 'offline')); + * }); + * function handleFirstConnectivityChange(isConnected) { + * console.log('Then, is ' + (isConnected ? 'online' : 'offline')); + * NetInfo.isConnected.removeEventListener( + * 'change', + * handleFirstConnectivityChange + * ); + * } + * NetInfo.isConnected.addEventListener( + * 'change', + * handleFirstConnectivityChange + * ); + * ``` + * + */ + var _isConnectedSubscriptions = {}; + NetInfo.isConnected = { + addEventListener: function ( + eventName: ChangeEventName, + handler: Function + ): void { + _isConnectedSubscriptions[handler] = (reachability) => { + handler(reachability !== 'none'); + }; + NetInfo.reachabilityIOS.addEventListener(eventName, _isConnectedSubscriptions[handler]); + }, + + removeEventListener: function( + eventName: ChangeEventName, + handler: Function + ): void { + NetInfo.reachabilityIOS.removeEventListener(eventName, _isConnectedSubscriptions[handler]); + }, + + fetch: function(): Promise { + return NetInfo.reachabilityIOS.fetch().then( + (reachability) => reachability !== 'none' + ); + }, + }; +} + +module.exports = NetInfo; diff --git a/Libraries/ReactIOS/renderApplication.js b/Libraries/ReactIOS/renderApplication.js index 64e26126c..176dfa72a 100644 --- a/Libraries/ReactIOS/renderApplication.js +++ b/Libraries/ReactIOS/renderApplication.js @@ -15,12 +15,10 @@ function renderApplication(RootComponent, initialProps, rootTag) { rootTag, 'Expect to have a valid rootTag, instead got ', rootTag ); - var pushNotification = initialProps.launchOptions && - initialProps.launchOptions.remoteNotification && - new PushNotificationIOS(initialProps.launchOptions.remoteNotification); + var initialNotification = PushNotificationIOS.popInitialNotification(); React.render( , rootTag diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js index 0eeb074c6..f9b1ac13a 100644 --- a/Libraries/Utilities/PixelRatio.js +++ b/Libraries/Utilities/PixelRatio.js @@ -10,45 +10,49 @@ var Dimensions = require('Dimensions'); /** * PixelRatio class gives access to the device pixel density. * - * Some examples: - * - PixelRatio.get() === 2 - * - iPhone 4, 4S - * - iPhone 5, 5c, 5s - * - iPhone 6 - * - * - PixelRatio.get() === 3 - * - iPhone 6 plus - * * There are a few use cases for using PixelRatio: * - * == Displaying a line that's as thin as the device permits + * ### Displaying a line that's as thin as the device permits * * A width of 1 is actually pretty thick on an iPhone 4+, we can do one that's - * thinner using a width of 1 / PixelRatio.get(). It's a technique that works + * thinner using a width of `1 / PixelRatio.get()`. It's a technique that works * on all the devices independent of their pixel density. * - * style={{ borderWidth: 1 / PixelRatio.get() }} + * ``` + * style={{ borderWidth: 1 / PixelRatio.get() }} + * ``` * - * == Fetching a correctly sized image + * ### Fetching a correctly sized image * * You should get a higher resolution image if you are on a high pixel density * device. A good rule of thumb is to multiply the size of the image you display * by the pixel ratio. * - * var image = getImage({ - * width: 200 * PixelRatio.get(), - * height: 100 * PixelRatio.get() - * }); - * + * ``` + * var image = getImage({ + * width: 200 * PixelRatio.get(), + * height: 100 * PixelRatio.get() + * }); + * + * ``` */ class PixelRatio { + /** + * Returns the device pixel density. Some examples: + * + * - PixelRatio.get() === 2 + * - iPhone 4, 4S + * - iPhone 5, 5c, 5s + * - iPhone 6 + * - PixelRatio.get() === 3 + * - iPhone 6 plus + */ static get() { return Dimensions.get('window').scale; } +} - static startDetecting() { - // no-op for iOS, but this is useful for other platforms - } -}; +// No-op for iOS, but used on the web. Should not be documented. +PixelRatio.startDetecting = function() {}; module.exports = PixelRatio; diff --git a/Libraries/Utilities/PushNotificationIOS.js b/Libraries/Utilities/PushNotificationIOS.js index 0cd8a6db6..86733bde3 100644 --- a/Libraries/Utilities/PushNotificationIOS.js +++ b/Libraries/Utilities/PushNotificationIOS.js @@ -5,8 +5,14 @@ */ 'use strict'; +var NativeModules = require('NativeModules'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var RCTPushNotificationManager = NativeModules.RCTPushNotificationManager; +if (RCTPushNotificationManager) { + var _initialNotification = RCTPushNotificationManager.initialNotification; +} + var _notifHandlers = {}; var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; @@ -30,6 +36,14 @@ class PushNotificationIOS { _notifHandlers[handler] = null; } + + static popInitialNotification() { + var initialNotification = _initialNotification && + new PushNotificationIOS(_initialNotification); + _initialNotification = null; + return initialNotification; + } + constructor(nativeNotif) { this._data = {}; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 00be3f65a..5e164b5fc 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -24,6 +24,7 @@ var ReactNative = { ListViewDataSource: require('ListViewDataSource'), MapView: require('MapView'), NavigatorIOS: require('NavigatorIOS'), + NetInfo: require('NetInfo'), PickerIOS: require('PickerIOS'), PixelRatio: require('PixelRatio'), ScrollView: require('ScrollView'), diff --git a/ReactKit/Base/RCTBridge.m b/ReactKit/Base/RCTBridge.m index 5173bc5d2..1fd446b3f 100644 --- a/ReactKit/Base/RCTBridge.m +++ b/ReactKit/Base/RCTBridge.m @@ -554,7 +554,7 @@ static id _latestJSExecutor; }]; if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) { - RCTLogMustFix(@"JavaScriptExecutor took too long to inject JSON object"); + RCTLogError(@"JavaScriptExecutor took too long to inject JSON object"); } } diff --git a/ReactKit/Modules/RCTPushNotificationManager.h b/ReactKit/Modules/RCTPushNotificationManager.h new file mode 100644 index 000000000..e0ba53a62 --- /dev/null +++ b/ReactKit/Modules/RCTPushNotificationManager.h @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import + +extern NSString *const RKRemoteNotificationReceived; +extern NSString *const RKOpenURLNotification; + +@interface RCTPushNotificationManager : NSObject + +- (instancetype)initWithInitialNotification:(NSDictionary *)initialNotification NS_DESIGNATED_INITIALIZER; + +@end diff --git a/ReactKit/Modules/RCTPushNotificationManager.m b/ReactKit/Modules/RCTPushNotificationManager.m new file mode 100644 index 000000000..b895f4d28 --- /dev/null +++ b/ReactKit/Modules/RCTPushNotificationManager.m @@ -0,0 +1,64 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTPushNotificationManager.h" + +#import "RCTAssert.h" +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" + +NSString *const RKRemoteNotificationReceived = @"RemoteNotificationReceived"; +NSString *const RKOpenURLNotification = @"RKOpenURLNotification"; + +@implementation RCTPushNotificationManager +{ + NSDictionary *_initialNotification; +} + +@synthesize bridge = _bridge; + +- (instancetype)init +{ + return [self initWithInitialNotification:nil]; +} + +- (instancetype)initWithInitialNotification:(NSDictionary *)initialNotification +{ + if ((self = [super init])) { + _initialNotification = [initialNotification copy]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationReceived:) + name:RKRemoteNotificationReceived + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleOpenURLNotification:) + name:RKOpenURLNotification + object:nil]; + } + return self; +} + +- (void)handleRemoteNotificationReceived:(NSNotification *)notification +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationReceived" + body:[notification userInfo]]; +} + +- (void)handleOpenURLNotification:(NSNotification *)notification +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"openURL" + body:[notification userInfo]]; +} + +- (NSDictionary *)constantsToExport +{ + return @{ + @"initialNotification": _initialNotification ?: [NSNull null] + }; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 122701d56..a7bf1f533 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -134,13 +134,17 @@ DependecyGraph.prototype.resolveDependency = function( fromModule, depModuleId ) { - // Process asset requires. - var assetMatch = depModuleId.match(/^image!(.+)/); - if (assetMatch && assetMatch[1]) { - if (!this._assetMap[assetMatch[1]]) { - throw new Error('Cannot find asset: ' + assetMatch[1]); + + if (this._assetMap != null) { + // Process asset requires. + var assetMatch = depModuleId.match(/^image!(.+)/); + if (assetMatch && assetMatch[1]) { + if (!this._assetMap[assetMatch[1]]) { + console.warn('Cannot find asset: ' + assetMatch[1]); + return null; + } + return this._assetMap[assetMatch[1]]; } - return this._assetMap[assetMatch[1]]; } var packageJson, modulePath, dep; @@ -577,7 +581,8 @@ function buildAssetMap(roots, exts) { } else { var ext = path.extname(file).replace(/^\./, ''); if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext); + var assetName = path.basename(file, '.' + ext) + .replace(/@[\d\.]+x/, ''); if (map[assetName] != null) { debug('Conflcting assets', assetName); }