diff --git a/.circleci/config.yml b/.circleci/config.yml index 2cfbaf4f6a39..1b75edce4848 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -211,27 +211,27 @@ aliases: mkdir -p ~/react-native/reports/junit/ - &build-objc-ios-test-app - name: Build Objective-C iOS Test App + name: Build iOS Test App command: ./scripts/objc-test-ios.sh - &run-objc-ios-tests - name: Objective-C iOS Test Suite + name: iOS Test Suite command: ./scripts/objc-test-ios.sh test - &build-objc-tvos-test-app - name: Build Objective-C tvOS Test App + name: Build tvOS Test App command: ./scripts/objc-test-tvos.sh - &run-objc-tvos-tests - name: Objective-C tvOS Test Suite + name: tvOS Test Suite command: ./scripts/objc-test-tvos.sh test - &run-objc-ios-e2e-tests - name: Objective-C iOS End-to-End Test Suite + name: iOS End-to-End Test Suite command: node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3; - &run-objc-tvos-e2e-tests - name: Objective-C tvOS End-to-End Test Suite + name: tvOS End-to-End Test Suite command: node ./scripts/run-ci-e2e-tests.js --tvos --js --retries 3; defaults: &defaults @@ -292,8 +292,6 @@ jobs: - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit - store_artifacts: path: ~/react-native/yarn.lock @@ -308,8 +306,6 @@ jobs: - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit # Runs JavaScript tests on Node 6 test_javascript_node6_compatibility: @@ -328,83 +324,47 @@ jobs: - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit - - # Builds iOS test app - build_objc_ios_test_app: - <<: *macos_defaults - dependencies: - pre: - - xcrun instruments -w "iPhone 5s (11.1)" || true - steps: - - attach_workspace: - at: ~/react-native - - - run: *build-objc-ios-test-app # Runs unit tests on iOS devices - test_objc_ios: + test_ios: <<: *macos_defaults - dependencies: - pre: - - xcrun instruments -w "iPhone 5s (11.1)" || true steps: - attach_workspace: at: ~/react-native + - run: xcrun instruments -w "iPhone 5s (11.1)" || true + - run: brew install watchman - run: *run-objc-ios-tests - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit - - # Builds tvOS test app - build_objc_tvos_test_app: - <<: *macos_defaults - dependencies: - pre: - - xcrun instruments -w "Apple TV 1080p (11.1)" || true - steps: - - attach_workspace: - at: ~/react-native - - - run: *build-objc-tvos-test-app # Runs unit tests on tvOS devices - test_objc_tvos: + test_tvos: <<: *macos_defaults - dependencies: - pre: - - xcrun instruments -w "Apple TV 1080p (11.1)" || true steps: - attach_workspace: at: ~/react-native + - run: xcrun instruments -w "Apple TV 1080p (11.1)" || true + - run: brew install watchman - run: *run-objc-tvos-tests - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit # Runs end to end tests - test_objc_ios_e2e: + test_ios_e2e: <<: *macos_defaults - dependencies: - pre: - - xcrun instruments -w "iPhone 5s (11.1)" || true steps: - attach_workspace: at: ~/react-native + - run: xcrun instruments -w "iPhone 5s (11.1)" || true - run: *run-objc-ios-e2e-tests - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit # Checks podspec test_podspec: @@ -523,8 +483,6 @@ jobs: - run: *collect-android-test-results - store_test_results: path: ~/react-native/reports/junit - - store_artifacts: - path: ~/react-native/reports/junit # Analyze pull request and raise any lint/flow issues. @@ -604,18 +562,18 @@ workflows: requires: - checkout_code - # Build iOS & tvOS test apps - - build_objc_ios_test_app: + # Test iOS & tvOS + - test_ios: filters: *filter-ignore-gh-pages requires: - checkout_code - - build_objc_tvos_test_app: + - test_tvos: filters: *filter-ignore-gh-pages requires: - checkout_code # End-to-end tests - - test_objc_ios_e2e: + - test_ios_e2e: filters: *filter-ignore-gh-pages requires: - checkout_code @@ -657,15 +615,6 @@ workflows: # # The following were DISABLED because they have not run since # # the migration from Travis, and they have broken since then, - # # Test iOS & tvOS - # - test_objc_ios: - # filters: *filter-ignore-gh-pages - # requires: - # - checkout_code - # - test_objc_tvos: - # filters: *filter-ignore-gh-pages - # requires: - # - checkout_code # # CocoaPods # - test_podspec: # filters: *filter-ignore-gh-pages diff --git a/.eslintrc b/.eslintrc index 2c1f512a16ce..9887d1c6d8db 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,7 @@ "flowtype", "prettier", "react", + "react-native", "jest" ], @@ -252,6 +253,11 @@ "react/self-closing-comp": 1, "react/wrap-multilines": 0, + // React-Native Plugin + // The following rules are made available via `eslint-plugin-react-native` + + "react-native/no-inline-styles": 1, + // Jest Plugin // The following rules are made available via `eslint-plugin-jest`. "jest/no-disabled-tests": 1, diff --git a/Libraries/Animated/src/nodes/AnimatedTransform.js b/Libraries/Animated/src/nodes/AnimatedTransform.js index fd5049e9b8d8..48f82f9fe344 100644 --- a/Libraries/Animated/src/nodes/AnimatedTransform.js +++ b/Libraries/Animated/src/nodes/AnimatedTransform.js @@ -15,9 +15,9 @@ const AnimatedWithChildren = require('./AnimatedWithChildren'); const NativeAnimatedHelper = require('../NativeAnimatedHelper'); class AnimatedTransform extends AnimatedWithChildren { - _transforms: Array; + _transforms: $ReadOnlyArray; - constructor(transforms: Array) { + constructor(transforms: $ReadOnlyArray) { super(); this._transforms = transforms; } @@ -34,7 +34,7 @@ class AnimatedTransform extends AnimatedWithChildren { }); } - __getValue(): Array { + __getValue(): $ReadOnlyArray { return this._transforms.map(transform => { const result = {}; for (const key in transform) { @@ -49,7 +49,7 @@ class AnimatedTransform extends AnimatedWithChildren { }); } - __getAnimatedValue(): Array { + __getAnimatedValue(): $ReadOnlyArray { return this._transforms.map(transform => { const result = {}; for (const key in transform) { diff --git a/Libraries/Blob/__tests__/File-test.js b/Libraries/Blob/__tests__/File-test.js index d466d61b8b35..c10f90c18766 100644 --- a/Libraries/Blob/__tests__/File-test.js +++ b/Libraries/Blob/__tests__/File-test.js @@ -13,8 +13,39 @@ jest.setMock('NativeModules', { BlobModule: require('../__mocks__/BlobModule'), }); +const Blob = require('Blob'); const File = require('File'); +describe('babel 7 smoke test', function() { + it('should be able to extend a class with native name', function() { + let called = false; + class Array { + constructor() { + called = true; + return {foo: 'PASS'}; + } + } + class A extends Array { + constructor() { + super(); + } + } + + // there is/was a regression in Babel where this would break and super() + // would not actually invoke the constructor of the parent class if the + // parent class had a name matching a built-in class (like Blob) + expect(new A().foo).toBe('PASS'); + expect(called).toBe(true); + }); +}); + +describe('Blob', function() { + it('regression caused by circular dep && babel 7', function() { + const blob = new Blob([], {type: 'image/jpeg'}); + expect(blob).toBeInstanceOf(Blob); + }); +}); + describe('File', function() { it('should create empty file', () => { const file = new File([], 'test.jpg'); diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js index b08593a06672..afc4abe1533a 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js @@ -9,18 +9,18 @@ */ 'use strict'; -var NativeModules = require('NativeModules'); -var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +const NativeModules = require('NativeModules'); +const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var RCTAccessibilityInfo = NativeModules.AccessibilityInfo; +const RCTAccessibilityInfo = NativeModules.AccessibilityInfo; -var TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange'; +const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange'; type ChangeEventName = $Enum<{ change: string, }>; -var _subscriptions = new Map(); +const _subscriptions = new Map(); /** * Sometimes it's useful to know whether or not the device has a screen reader @@ -32,7 +32,7 @@ var _subscriptions = new Map(); * See http://facebook.github.io/react-native/docs/accessibilityinfo.html */ -var AccessibilityInfo = { +const AccessibilityInfo = { fetch: function(): Promise { return new Promise((resolve, reject) => { @@ -48,7 +48,7 @@ var AccessibilityInfo = { eventName: ChangeEventName, handler: Function ): void { - var listener = RCTDeviceEventEmitter.addListener( + const listener = RCTDeviceEventEmitter.addListener( TOUCH_EXPLORATION_EVENT, (enabled) => { handler(enabled); @@ -61,7 +61,7 @@ var AccessibilityInfo = { eventName: ChangeEventName, handler: Function ): void { - var listener = _subscriptions.get(handler); + const listener = _subscriptions.get(handler); if (!listener) { return; } diff --git a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js index 822f0dcbaab8..6e39b5e477d5 100644 --- a/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js +++ b/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js @@ -9,21 +9,21 @@ */ 'use strict'; -var NativeModules = require('NativeModules'); -var Promise = require('Promise'); -var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +const NativeModules = require('NativeModules'); +const Promise = require('Promise'); +const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var AccessibilityManager = NativeModules.AccessibilityManager; +const AccessibilityManager = NativeModules.AccessibilityManager; -var VOICE_OVER_EVENT = 'voiceOverDidChange'; -var ANNOUNCEMENT_DID_FINISH_EVENT = 'announcementDidFinish'; +const VOICE_OVER_EVENT = 'voiceOverDidChange'; +const ANNOUNCEMENT_DID_FINISH_EVENT = 'announcementDidFinish'; type ChangeEventName = $Enum<{ change: string, announcementFinished: string }>; -var _subscriptions = new Map(); +const _subscriptions = new Map(); /** * Sometimes it's useful to know whether or not the device has a screen reader @@ -34,7 +34,7 @@ var _subscriptions = new Map(); * * See http://facebook.github.io/react-native/docs/accessibilityinfo.html */ -var AccessibilityInfo = { +const AccessibilityInfo = { /** * Query whether a screen reader is currently enabled. @@ -72,7 +72,7 @@ var AccessibilityInfo = { eventName: ChangeEventName, handler: Function ): Object { - var listener; + let listener; if (eventName === 'change') { listener = RCTDeviceEventEmitter.addListener( @@ -127,7 +127,7 @@ var AccessibilityInfo = { eventName: ChangeEventName, handler: Function ): void { - var listener = _subscriptions.get(handler); + const listener = _subscriptions.get(handler); if (!listener) { return; } diff --git a/Libraries/Components/AppleTV/TVEventHandler.android.js b/Libraries/Components/AppleTV/TVEventHandler.android.js deleted file mode 100644 index 718fa84a8de5..000000000000 --- a/Libraries/Components/AppleTV/TVEventHandler.android.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @providesModule TVEventHandler - * @flow - */ -'use strict'; - -function TVEventHandler() {} - -TVEventHandler.prototype.enable = function(component: ?any, callback: Function) {}; - -TVEventHandler.prototype.disable = function() {}; - -module.exports = TVEventHandler; diff --git a/Libraries/Components/AppleTV/TVEventHandler.ios.js b/Libraries/Components/AppleTV/TVEventHandler.js similarity index 91% rename from Libraries/Components/AppleTV/TVEventHandler.ios.js rename to Libraries/Components/AppleTV/TVEventHandler.js index 94e67c23cf35..363d50d903d8 100644 --- a/Libraries/Components/AppleTV/TVEventHandler.ios.js +++ b/Libraries/Components/AppleTV/TVEventHandler.js @@ -9,7 +9,7 @@ */ 'use strict'; -const React = require('React'); +const Platform = require('Platform'); const TVNavigationEventEmitter = require('NativeModules').TVNavigationEventEmitter; const NativeEventEmitter = require('NativeEventEmitter'); @@ -19,13 +19,13 @@ function TVEventHandler() { } TVEventHandler.prototype.enable = function(component: ?any, callback: Function) { - if (!TVNavigationEventEmitter) { + if (Platform.OS === 'ios' && !TVNavigationEventEmitter) { return; } this.__nativeTVNavigationEventEmitter = new NativeEventEmitter(TVNavigationEventEmitter); this.__nativeTVNavigationEventListener = this.__nativeTVNavigationEventEmitter.addListener( - 'onTVNavEvent', + 'onHWKeyEvent', (data) => { if (callback) { callback(component, data); diff --git a/Libraries/Components/AppleTV/TVViewPropTypes.js b/Libraries/Components/AppleTV/TVViewPropTypes.js index fab7a30b84d4..2b6a6d4e53dc 100644 --- a/Libraries/Components/AppleTV/TVViewPropTypes.js +++ b/Libraries/Components/AppleTV/TVViewPropTypes.js @@ -8,24 +8,20 @@ * @flow */ 'use strict'; -var PropTypes = require('prop-types'); +const PropTypes = require('prop-types'); /** * Additional View properties for Apple TV */ -var TVViewPropTypes = { +const TVViewPropTypes = { /** - * *(Apple TV only)* When set to true, this view will be focusable - * and navigable using the Apple TV remote. - * - * @platform ios + * When set to true, this view will be focusable + * and navigable using the TV remote. */ isTVSelectable: PropTypes.bool, /** - * *(Apple TV only)* May be set to true to force the Apple TV focus engine to move focus to this view. - * - * @platform ios + * May be set to true to force the TV focus engine to move focus to this view. */ hasTVPreferredFocus: PropTypes.bool, diff --git a/Libraries/Components/Button.js b/Libraries/Components/Button.js index dc5071f83193..4370f3571c10 100644 --- a/Libraries/Components/Button.js +++ b/Libraries/Components/Button.js @@ -53,6 +53,7 @@ class Button extends React.Component<{ title: string, onPress: () => any, color?: ?string, + hasTVPreferredFocus?: ?boolean, accessibilityLabel?: ?string, disabled?: ?boolean, testID?: ?string, @@ -75,6 +76,10 @@ class Button extends React.Component<{ * If true, disable all interactions for this component. */ disabled: PropTypes.bool, + /** + * TV preferred focus (see documentation for the View component). + */ + hasTVPreferredFocus: PropTypes.bool, /** * Handler to be called when the user taps the button */ @@ -83,12 +88,6 @@ class Button extends React.Component<{ * Used to locate this view in end-to-end tests. */ testID: PropTypes.string, - /** - * *(Apple TV only)* TV preferred focus (see documentation for the View component). - * - * @platform ios - */ - hasTVPreferredFocus: PropTypes.bool, }; render() { diff --git a/Libraries/Components/DatePicker/DatePickerIOS.android.js b/Libraries/Components/DatePicker/DatePickerIOS.android.js index 8a1db045e017..2a0d8998bfb5 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.android.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.android.js @@ -9,10 +9,10 @@ 'use strict'; -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var View = require('View'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const View = require('View'); class DummyDatePickerIOS extends React.Component { render() { @@ -24,7 +24,7 @@ class DummyDatePickerIOS extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ dummyDatePickerIOS: { height: 100, width: 300, diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js index 3a39a7d02f6b..6a20d894f4f4 100644 --- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js +++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js @@ -8,28 +8,28 @@ */ 'use strict'; -var ColorPropType = require('ColorPropType'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var Platform = require('Platform'); -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNative = require('ReactNative'); -var StatusBar = require('StatusBar'); -var StyleSheet = require('StyleSheet'); -var UIManager = require('UIManager'); -var View = require('View'); -var ViewPropTypes = require('ViewPropTypes'); +const ColorPropType = require('ColorPropType'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const Platform = require('Platform'); +const React = require('React'); +const PropTypes = require('prop-types'); +const ReactNative = require('ReactNative'); +const StatusBar = require('StatusBar'); +const StyleSheet = require('StyleSheet'); +const UIManager = require('UIManager'); +const View = require('View'); +const ViewPropTypes = require('ViewPropTypes'); -var DrawerConsts = UIManager.AndroidDrawerLayout.Constants; +const DrawerConsts = UIManager.AndroidDrawerLayout.Constants; -var createReactClass = require('create-react-class'); -var dismissKeyboard = require('dismissKeyboard'); -var requireNativeComponent = require('requireNativeComponent'); +const createReactClass = require('create-react-class'); +const dismissKeyboard = require('dismissKeyboard'); +const requireNativeComponent = require('requireNativeComponent'); -var RK_DRAWER_REF = 'drawerlayout'; -var INNERVIEW_REF = 'innerView'; +const RK_DRAWER_REF = 'drawerlayout'; +const INNERVIEW_REF = 'innerView'; -var DRAWER_STATES = [ +const DRAWER_STATES = [ 'Idle', 'Dragging', 'Settling', @@ -66,7 +66,7 @@ var DRAWER_STATES = [ * }, * ``` */ -var DrawerLayoutAndroid = createReactClass({ +const DrawerLayoutAndroid = createReactClass({ displayName: 'DrawerLayoutAndroid', statics: { positions: DrawerConsts.DrawerPosition, @@ -169,8 +169,8 @@ var DrawerLayoutAndroid = createReactClass({ }, render: function() { - var drawStatusBar = Platform.Version >= 21 && this.props.statusBarBackgroundColor; - var drawerViewWrapper = + const drawStatusBar = Platform.Version >= 21 && this.props.statusBarBackgroundColor; + const drawerViewWrapper = } ; - var childrenWrapper = + const childrenWrapper = {drawStatusBar && =0.54.0 site=react_native_oss) This comment suppresses an error * found when Flow v0.54 was deployed. To see the error delete this comment and * run Flow. */ const keyMirror = require('fbjs/lib/keyMirror'); -var TRANSITIONER_REF = 'transitionerRef'; +const TRANSITIONER_REF = 'transitionerRef'; -var __uid = 0; +let __uid = 0; function getuid() { return __uid++; } @@ -305,7 +305,7 @@ type Event = Object; * is pushed. * */ -var NavigatorIOS = createReactClass({ +const NavigatorIOS = createReactClass({ displayName: 'NavigatorIOS', propTypes: { @@ -584,7 +584,7 @@ var NavigatorIOS = createReactClass({ _getFocusEmitter: function(): EventEmitter { // Flow not yet tracking assignments to instance fields. - var focusEmitter = this._focusEmitter; + let focusEmitter = this._focusEmitter; if (!focusEmitter) { focusEmitter = new EventEmitter(); this._focusEmitter = focusEmitter; @@ -614,13 +614,13 @@ var NavigatorIOS = createReactClass({ }, _handleNavigatorStackChanged: function(e: Event) { - var newObservedTopOfStack = e.nativeEvent.stackLength - 1; + const newObservedTopOfStack = e.nativeEvent.stackLength - 1; invariant( newObservedTopOfStack <= this.state.requestedTopOfStack, 'No navigator item should be pushed without JS knowing about it %s %s', newObservedTopOfStack, this.state.requestedTopOfStack ); - var wasWaitingForConfirmation = + const wasWaitingForConfirmation = this.state.requestedTopOfStack !== this.state.observedTopOfStack; if (wasWaitingForConfirmation) { invariant( @@ -638,7 +638,7 @@ var NavigatorIOS = createReactClass({ // Progress isn't always 0 or 1 at the end, the system rounds // If the Navigator is offscreen these values won't be updated // TOOD: Revisit this decision when no longer relying on native navigator. - var nextState = { + const nextState = { observedTopOfStack: newObservedTopOfStack, makingNavigatorRequest: false, updatingAllIndicesAtOrBeyond: null, @@ -653,7 +653,7 @@ var NavigatorIOS = createReactClass({ // Updating the indices that we're deleting and that's all. (Truth: Nothing // even uses the indices in this case, but let's make this describe the // truth anyways). - var updatingAllIndicesAtOrBeyond = + const updatingAllIndicesAtOrBeyond = this.state.routeStack.length > this.state.observedTopOfStack + 1 ? this.state.observedTopOfStack + 1 : null; @@ -677,8 +677,8 @@ var NavigatorIOS = createReactClass({ if (this.state.requestedTopOfStack === this.state.observedTopOfStack) { this._tryLockNavigator(() => { - var nextStack = this.state.routeStack.concat([route]); - var nextIDStack = this.state.idStack.concat([getuid()]); + const nextStack = this.state.routeStack.concat([route]); + const nextIDStack = this.state.idStack.concat([getuid()]); this.setState({ // We have to make sure that we've also supplied enough views to // satisfy our request to adjust the `requestedTopOfStack`. @@ -704,7 +704,7 @@ var NavigatorIOS = createReactClass({ if (this.state.requestedTopOfStack === this.state.observedTopOfStack) { if (this.state.requestedTopOfStack > 0) { this._tryLockNavigator(() => { - var newRequestedTopOfStack = this.state.requestedTopOfStack - n; + const newRequestedTopOfStack = this.state.requestedTopOfStack - n; invariant(newRequestedTopOfStack >= 0, 'Cannot pop below 0'); this.setState({ requestedTopOfStack: newRequestedTopOfStack, @@ -742,8 +742,8 @@ var NavigatorIOS = createReactClass({ // I don't believe we need to lock for a replace since there's no // navigation actually happening - var nextIDStack = this.state.idStack.slice(); - var nextRouteStack = this.state.routeStack.slice(); + const nextIDStack = this.state.idStack.slice(); + const nextRouteStack = this.state.routeStack.slice(); nextIDStack[index] = getuid(); nextRouteStack[index] = route; @@ -785,12 +785,12 @@ var NavigatorIOS = createReactClass({ * @param route The new route to navigate to. */ popToRoute: function(route: Route) { - var indexOfRoute = this.state.routeStack.indexOf(route); + const indexOfRoute = this.state.routeStack.indexOf(route); invariant( indexOfRoute !== -1, 'Calling pop to route for a route that doesn\'t exist!' ); - var numToPop = this.state.routeStack.length - indexOfRoute - 1; + const numToPop = this.state.routeStack.length - indexOfRoute - 1; this.popN(numToPop); }, @@ -841,12 +841,12 @@ var NavigatorIOS = createReactClass({ }, _routeToStackItem: function(routeArg: Route, i: number) { - var {component, wrapperStyle, passProps, ...route} = routeArg; - var {itemWrapperStyle, ...props} = this.props; - var shouldUpdateChild = + const {component, wrapperStyle, passProps, ...route} = routeArg; + const {itemWrapperStyle, ...props} = this.props; + const shouldUpdateChild = this.state.updatingAllIndicesAtOrBeyond != null && this.state.updatingAllIndicesAtOrBeyond >= i; - var Component = component; + const Component = component; return ( @@ -919,7 +919,7 @@ var NavigatorIOS = createReactClass({ }, }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ stackItem: { backgroundColor: 'white', overflow: 'hidden', @@ -934,7 +934,7 @@ var styles = StyleSheet.create({ }, }); -var RCTNavigator = requireNativeComponent('RCTNavigator'); -var RCTNavigatorItem = requireNativeComponent('RCTNavItem'); +const RCTNavigator = requireNativeComponent('RCTNavigator'); +const RCTNavigatorItem = requireNativeComponent('RCTNavItem'); module.exports = NavigatorIOS; diff --git a/Libraries/Components/Picker/Picker.js b/Libraries/Components/Picker/Picker.js index d4b225c8abe7..1df316249d7c 100644 --- a/Libraries/Components/Picker/Picker.js +++ b/Libraries/Components/Picker/Picker.js @@ -10,27 +10,27 @@ 'use strict'; -var ColorPropType = require('ColorPropType'); -var PickerIOS = require('PickerIOS'); -var PickerAndroid = require('PickerAndroid'); -var Platform = require('Platform'); -var React = require('React'); +const ColorPropType = require('ColorPropType'); +const PickerIOS = require('PickerIOS'); +const PickerAndroid = require('PickerAndroid'); +const Platform = require('Platform'); +const React = require('React'); const PropTypes = require('prop-types'); -var StyleSheetPropType = require('StyleSheetPropType'); -var TextStylePropTypes = require('TextStylePropTypes'); -var UnimplementedView = require('UnimplementedView'); +const StyleSheetPropType = require('StyleSheetPropType'); +const TextStylePropTypes = require('TextStylePropTypes'); +const UnimplementedView = require('UnimplementedView'); const ViewPropTypes = require('ViewPropTypes'); -var ViewStylePropTypes = require('ViewStylePropTypes'); +const ViewStylePropTypes = require('ViewStylePropTypes'); -var itemStylePropType = StyleSheetPropType(TextStylePropTypes); +const itemStylePropType = StyleSheetPropType(TextStylePropTypes); -var pickerStyleType = StyleSheetPropType({ +const pickerStyleType = StyleSheetPropType({ ...ViewStylePropTypes, color: ColorPropType, }); -var MODE_DIALOG = 'dialog'; -var MODE_DROPDOWN = 'dropdown'; +const MODE_DIALOG = 'dialog'; +const MODE_DROPDOWN = 'dropdown'; /** * Individual selectable item in a Picker. diff --git a/Libraries/Components/Picker/PickerAndroid.android.js b/Libraries/Components/Picker/PickerAndroid.android.js index c164db953731..bd9c664a8fa5 100644 --- a/Libraries/Components/Picker/PickerAndroid.android.js +++ b/Libraries/Components/Picker/PickerAndroid.android.js @@ -10,21 +10,21 @@ 'use strict'; -var ColorPropType = require('ColorPropType'); -var React = require('React'); -var ReactPropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); -var StyleSheetPropType = require('StyleSheetPropType'); +const ColorPropType = require('ColorPropType'); +const React = require('React'); +const ReactPropTypes = require('prop-types'); +const StyleSheet = require('StyleSheet'); +const StyleSheetPropType = require('StyleSheetPropType'); const ViewPropTypes = require('ViewPropTypes'); -var ViewStylePropTypes = require('ViewStylePropTypes'); +const ViewStylePropTypes = require('ViewStylePropTypes'); -var processColor = require('processColor'); -var requireNativeComponent = require('requireNativeComponent'); +const processColor = require('processColor'); +const requireNativeComponent = require('requireNativeComponent'); -var REF_PICKER = 'picker'; -var MODE_DROPDOWN = 'dropdown'; +const REF_PICKER = 'picker'; +const MODE_DROPDOWN = 'dropdown'; -var pickerStyleType = StyleSheetPropType({ +const pickerStyleType = StyleSheetPropType({ ...ViewStylePropTypes, color: ColorPropType, }); @@ -56,7 +56,7 @@ class PickerAndroid extends React.Component<{ constructor(props, context) { super(props, context); - var state = this._stateFromProps(props); + const state = this._stateFromProps(props); this.state = { ...state, @@ -70,7 +70,7 @@ class PickerAndroid extends React.Component<{ // Translate prop and children into stuff that the native picker understands. _stateFromProps = (props) => { - var selectedIndex = 0; + let selectedIndex = 0; const items = React.Children.map(props.children, (child, index) => { if (child.props.value === props.selectedValue) { selectedIndex = index; @@ -88,9 +88,9 @@ class PickerAndroid extends React.Component<{ }; render() { - var Picker = this.props.mode === MODE_DROPDOWN ? DropdownPicker : DialogPicker; + const Picker = this.props.mode === MODE_DROPDOWN ? DropdownPicker : DialogPicker; - var nativeProps = { + const nativeProps = { enabled: this.props.enabled, items: this.state.items, mode: this.props.mode, @@ -107,10 +107,10 @@ class PickerAndroid extends React.Component<{ _onChange = (event: Event) => { if (this.props.onValueChange) { - var position = event.nativeEvent.position; + const position = event.nativeEvent.position; if (position >= 0) { - var children = React.Children.toArray(this.props.children); - var value = children[position].props.value; + const children = React.Children.toArray(this.props.children); + const value = children[position].props.value; this.props.onValueChange(value, position); } else { this.props.onValueChange(null, position); @@ -138,7 +138,7 @@ class PickerAndroid extends React.Component<{ } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ pickerAndroid: { // The picker will conform to whatever width is given, but we do // have to set the component's height explicitly on the @@ -149,14 +149,14 @@ var styles = StyleSheet.create({ }, }); -var cfg = { +const cfg = { nativeOnly: { items: true, selected: true, } }; -var DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg); -var DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg); +const DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg); +const DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg); module.exports = PickerAndroid; diff --git a/Libraries/Components/Picker/PickerIOS.ios.js b/Libraries/Components/Picker/PickerIOS.ios.js index 3963a9485f25..d1fe0761d5cc 100644 --- a/Libraries/Components/Picker/PickerIOS.ios.js +++ b/Libraries/Components/Picker/PickerIOS.ios.js @@ -10,21 +10,21 @@ */ 'use strict'; -var NativeMethodsMixin = require('NativeMethodsMixin'); -var React = require('React'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); const PropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); -var StyleSheetPropType = require('StyleSheetPropType'); -var TextStylePropTypes = require('TextStylePropTypes'); -var View = require('View'); +const StyleSheet = require('StyleSheet'); +const StyleSheetPropType = require('StyleSheetPropType'); +const TextStylePropTypes = require('TextStylePropTypes'); +const View = require('View'); const ViewPropTypes = require('ViewPropTypes'); -var processColor = require('processColor'); +const processColor = require('processColor'); -var createReactClass = require('create-react-class'); -var itemStylePropType = StyleSheetPropType(TextStylePropTypes); -var requireNativeComponent = require('requireNativeComponent'); +const createReactClass = require('create-react-class'); +const itemStylePropType = StyleSheetPropType(TextStylePropTypes); +const requireNativeComponent = require('requireNativeComponent'); -var PickerIOS = createReactClass({ +const PickerIOS = createReactClass({ displayName: 'PickerIOS', mixins: [NativeMethodsMixin], @@ -45,8 +45,8 @@ var PickerIOS = createReactClass({ // Translate PickerIOS prop and children into stuff that RCTPickerIOS understands. _stateFromProps: function(props) { - var selectedIndex = 0; - var items = []; + let selectedIndex = 0; + const items = []; React.Children.toArray(props.children).forEach(function (child, index) { if (child.props.value === props.selectedValue) { selectedIndex = index; @@ -111,7 +111,7 @@ PickerIOS.Item = class extends React.Component { } }; -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ pickerIOS: { // The picker will conform to whatever width is given, but we do // have to set the component's height explicitly on the @@ -120,7 +120,7 @@ var styles = StyleSheet.create({ }, }); -var RCTPickerIOS = requireNativeComponent('RCTPicker', { +const RCTPickerIOS = requireNativeComponent('RCTPicker', { propTypes: { style: itemStylePropType, }, diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js index 46591f628029..f467db1ff51f 100644 --- a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js @@ -10,10 +10,10 @@ 'use strict'; -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var View = require('View'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const View = require('View'); class DummyProgressViewIOS extends React.Component { render() { @@ -27,7 +27,7 @@ class DummyProgressViewIOS extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ dummy: { width: 120, height: 20, diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js index 310f02507c94..55ae1b83abe1 100644 --- a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js @@ -9,20 +9,20 @@ */ 'use strict'; -var Image = require('Image'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var React = require('React'); -var PropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); -var ViewPropTypes = require('ViewPropTypes'); +const Image = require('Image'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const PropTypes = require('prop-types'); +const StyleSheet = require('StyleSheet'); +const ViewPropTypes = require('ViewPropTypes'); -var createReactClass = require('create-react-class'); -var requireNativeComponent = require('requireNativeComponent'); +const createReactClass = require('create-react-class'); +const requireNativeComponent = require('requireNativeComponent'); /** * Use `ProgressViewIOS` to render a UIProgressView on iOS. */ -var ProgressViewIOS = createReactClass({ +const ProgressViewIOS = createReactClass({ displayName: 'ProgressViewIOS', mixins: [NativeMethodsMixin], @@ -69,13 +69,13 @@ var ProgressViewIOS = createReactClass({ } }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ progressView: { height: 2, }, }); -var RCTProgressView = requireNativeComponent( +const RCTProgressView = requireNativeComponent( 'RCTProgressView', ProgressViewIOS ); diff --git a/Libraries/Components/RefreshControl/RefreshControl.js b/Libraries/Components/RefreshControl/RefreshControl.js index 14f383dc08c2..8aac1ebf3e57 100644 --- a/Libraries/Components/RefreshControl/RefreshControl.js +++ b/Libraries/Components/RefreshControl/RefreshControl.js @@ -20,7 +20,11 @@ const createReactClass = require('create-react-class'); const requireNativeComponent = require('requireNativeComponent'); if (Platform.OS === 'android') { - var RefreshLayoutConsts = require('UIManager').AndroidSwipeRefreshLayout.Constants; + const AndroidSwipeRefreshLayout = + require('UIManager').AndroidSwipeRefreshLayout; + var RefreshLayoutConsts = AndroidSwipeRefreshLayout + ? AndroidSwipeRefreshLayout.Constants + : {SIZE: {}}; } else { var RefreshLayoutConsts = {SIZE: {}}; } diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js index 16be1c8f2b14..a5b4c0b339bf 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js @@ -10,10 +10,10 @@ 'use strict'; -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var View = require('View'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const View = require('View'); class DummySegmentedControlIOS extends React.Component { render() { @@ -27,7 +27,7 @@ class DummySegmentedControlIOS extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ dummy: { width: 120, height: 50, diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js index f7748fcc8dbf..3eb4249fa2db 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js @@ -9,21 +9,21 @@ */ 'use strict'; -var NativeMethodsMixin = require('NativeMethodsMixin'); -var React = require('React'); -var PropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); -var ViewPropTypes = require('ViewPropTypes'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const PropTypes = require('prop-types'); +const StyleSheet = require('StyleSheet'); +const ViewPropTypes = require('ViewPropTypes'); -var createReactClass = require('create-react-class'); -var requireNativeComponent = require('requireNativeComponent'); +const createReactClass = require('create-react-class'); +const requireNativeComponent = require('requireNativeComponent'); type DefaultProps = { values: Array, enabled: boolean, }; -var SEGMENTED_CONTROL_REFERENCE = 'segmentedcontrol'; +const SEGMENTED_CONTROL_REFERENCE = 'segmentedcontrol'; type Event = Object; @@ -47,7 +47,7 @@ type Event = Object; * /> * ```` */ -var SegmentedControlIOS = createReactClass({ +const SegmentedControlIOS = createReactClass({ displayName: 'SegmentedControlIOS', mixins: [NativeMethodsMixin], @@ -117,13 +117,13 @@ var SegmentedControlIOS = createReactClass({ } }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ segmentedControl: { height: 28, }, }); -var RCTSegmentedControl = requireNativeComponent( +const RCTSegmentedControl = requireNativeComponent( 'RCTSegmentedControl', SegmentedControlIOS ); diff --git a/Libraries/Components/Slider/Slider.js b/Libraries/Components/Slider/Slider.js index 683789520e56..5a163fe176d1 100644 --- a/Libraries/Components/Slider/Slider.js +++ b/Libraries/Components/Slider/Slider.js @@ -9,18 +9,18 @@ */ 'use strict'; -var Image = require('Image'); -var ColorPropType = require('ColorPropType'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); -var Platform = require('Platform'); -var React = require('React'); -var PropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); -var ViewPropTypes = require('ViewPropTypes'); +const Image = require('Image'); +const ColorPropType = require('ColorPropType'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const Platform = require('Platform'); +const React = require('React'); +const PropTypes = require('prop-types'); +const StyleSheet = require('StyleSheet'); +const ViewPropTypes = require('ViewPropTypes'); -var createReactClass = require('create-react-class'); -var requireNativeComponent = require('requireNativeComponent'); +const createReactClass = require('create-react-class'); +const requireNativeComponent = require('requireNativeComponent'); type Event = Object; @@ -84,7 +84,7 @@ type Event = Object; *``` * */ -var Slider = createReactClass({ +const Slider = createReactClass({ displayName: 'Slider', mixins: [NativeMethodsMixin], diff --git a/Libraries/Components/StaticRenderer.js b/Libraries/Components/StaticRenderer.js index d5474e943bcc..777fe900e120 100644 --- a/Libraries/Components/StaticRenderer.js +++ b/Libraries/Components/StaticRenderer.js @@ -9,9 +9,9 @@ */ 'use strict'; -var React = require('React'); +const React = require('React'); -var PropTypes = require('prop-types'); +const PropTypes = require('prop-types'); class StaticRenderer extends React.Component<{ shouldUpdate: boolean, diff --git a/Libraries/Components/Subscribable.js b/Libraries/Components/Subscribable.js index cc665ef98f78..6825acd02f70 100644 --- a/Libraries/Components/Subscribable.js +++ b/Libraries/Components/Subscribable.js @@ -19,7 +19,7 @@ import type EventEmitter from 'EventEmitter'; * React Core */ -var Subscribable = {}; +const Subscribable = {}; Subscribable.Mixin = { diff --git a/Libraries/Components/Switch/Switch.js b/Libraries/Components/Switch/Switch.js index 545b29b271f0..e88272dacfa1 100644 --- a/Libraries/Components/Switch/Switch.js +++ b/Libraries/Components/Switch/Switch.js @@ -9,16 +9,16 @@ */ 'use strict'; -var ColorPropType = require('ColorPropType'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var Platform = require('Platform'); -var React = require('React'); +const ColorPropType = require('ColorPropType'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const Platform = require('Platform'); +const React = require('React'); const PropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); +const StyleSheet = require('StyleSheet'); const ViewPropTypes = require('ViewPropTypes'); -var createReactClass = require('create-react-class'); -var requireNativeComponent = require('requireNativeComponent'); +const createReactClass = require('create-react-class'); +const requireNativeComponent = require('requireNativeComponent'); type DefaultProps = { value: boolean, @@ -36,7 +36,7 @@ type DefaultProps = { * @keyword checkbox * @keyword toggle */ -var Switch = createReactClass({ +const Switch = createReactClass({ displayName: 'Switch', propTypes: { ...ViewPropTypes, @@ -98,7 +98,7 @@ var Switch = createReactClass({ }, render: function() { - var props = {...this.props}; + const props = {...this.props}; props.onStartShouldSetResponder = () => true; props.onResponderTerminationRequest = () => false; if (Platform.OS === 'android') { @@ -122,7 +122,7 @@ var Switch = createReactClass({ }, }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ rctSwitchIOS: { height: 31, width: 51, diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js index 2e838b097df3..9d1b63567ba8 100644 --- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js @@ -9,20 +9,20 @@ */ 'use strict'; -var ColorPropType = require('ColorPropType'); -var React = require('React'); +const ColorPropType = require('ColorPropType'); +const React = require('React'); const PropTypes = require('prop-types'); -var StyleSheet = require('StyleSheet'); -var TabBarItemIOS = require('TabBarItemIOS'); +const StyleSheet = require('StyleSheet'); +const TabBarItemIOS = require('TabBarItemIOS'); const ViewPropTypes = require('ViewPropTypes'); -var requireNativeComponent = require('requireNativeComponent'); +const requireNativeComponent = require('requireNativeComponent'); -import type {StyleObj} from 'StyleSheetTypes'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; import type {ViewProps} from 'ViewPropTypes'; class TabBarIOS extends React.Component ); } else { - var children = props.children; - var childCount = 0; + let children = props.children; + let childCount = 0; React.Children.forEach(children, () => ++childCount); invariant( !(props.value && childCount), @@ -797,7 +797,7 @@ const TextInput = createReactClass({ }, _renderIOS: function() { - var props = Object.assign({}, this.props); + const props = Object.assign({}, this.props); props.style = [this.props.style]; if (props.selection && props.selection.end == null) { @@ -857,8 +857,8 @@ const TextInput = createReactClass({ /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This comment * suppresses an error when upgrading Flow's support for React. To see the * error delete this comment and run Flow. */ - var children = this.props.children; - var childCount = 0; + let children = this.props.children; + let childCount = 0; React.Children.forEach(children, () => ++childCount); invariant( !(this.props.value && childCount), @@ -932,7 +932,7 @@ const TextInput = createReactClass({ }); } - var text = event.nativeEvent.text; + const text = event.nativeEvent.text; this.props.onChange && this.props.onChange(event); this.props.onChangeText && this.props.onChangeText(text); @@ -1016,7 +1016,7 @@ const TextInput = createReactClass({ }, }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ multilineInput: { // This default top inset makes RCTMultilineTextInputView seem as close as possible // to single-line RCTSinglelineTextInputView defaults, using the system defaults diff --git a/Libraries/Components/TextInput/TextInputState.js b/Libraries/Components/TextInput/TextInputState.js index 46524070fe21..afcca82bf320 100644 --- a/Libraries/Components/TextInput/TextInputState.js +++ b/Libraries/Components/TextInput/TextInputState.js @@ -13,10 +13,10 @@ */ 'use strict'; -var Platform = require('Platform'); -var UIManager = require('UIManager'); +const Platform = require('Platform'); +const UIManager = require('UIManager'); -var TextInputState = { +const TextInputState = { /** * Internal state */ diff --git a/Libraries/Components/ToastAndroid/ToastAndroid.android.js b/Libraries/Components/ToastAndroid/ToastAndroid.android.js index 5434126660f3..15a8a597a5b6 100644 --- a/Libraries/Components/ToastAndroid/ToastAndroid.android.js +++ b/Libraries/Components/ToastAndroid/ToastAndroid.android.js @@ -10,7 +10,7 @@ 'use strict'; -var RCTToastAndroid = require('NativeModules').ToastAndroid; +const RCTToastAndroid = require('NativeModules').ToastAndroid; /** * This exposes the native ToastAndroid module as a JS module. This has a function 'show' @@ -33,7 +33,7 @@ var RCTToastAndroid = require('NativeModules').ToastAndroid; * ``` */ -var ToastAndroid = { +const ToastAndroid = { // Toast duration constants SHORT: RCTToastAndroid.SHORT, diff --git a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js index b44f812ecff0..83db0cfa82ca 100644 --- a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js +++ b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js @@ -9,9 +9,9 @@ */ 'use strict'; -var warning = require('fbjs/lib/warning'); +const warning = require('fbjs/lib/warning'); -var ToastAndroid = { +const ToastAndroid = { show: function ( message: string, diff --git a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js index 941d1432855a..1d845e5b65ba 100644 --- a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js +++ b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js @@ -9,20 +9,20 @@ 'use strict'; -var Image = require('Image'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); -var UIManager = require('UIManager'); -var ViewPropTypes = require('ViewPropTypes'); -var ColorPropType = require('ColorPropType'); +const Image = require('Image'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const PropTypes = require('prop-types'); +const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const UIManager = require('UIManager'); +const ViewPropTypes = require('ViewPropTypes'); +const ColorPropType = require('ColorPropType'); -var createReactClass = require('create-react-class'); -var requireNativeComponent = require('requireNativeComponent'); -var resolveAssetSource = require('resolveAssetSource'); +const createReactClass = require('create-react-class'); +const requireNativeComponent = require('requireNativeComponent'); +const resolveAssetSource = require('resolveAssetSource'); -var optionalImageSource = PropTypes.oneOfType([ +const optionalImageSource = PropTypes.oneOfType([ Image.propTypes.source, // Image.propTypes.source is required but we want it to be optional, so we OR // it with a nullable propType. @@ -64,7 +64,7 @@ var optionalImageSource = PropTypes.oneOfType([ * * [0]: https://developer.android.com/reference/android/support/v7/widget/Toolbar.html */ -var ToolbarAndroid = createReactClass({ +const ToolbarAndroid = createReactClass({ displayName: 'ToolbarAndroid', mixins: [NativeMethodsMixin], @@ -162,7 +162,7 @@ var ToolbarAndroid = createReactClass({ }, render: function() { - var nativeProps = { + const nativeProps = { ...this.props, }; if (this.props.logo) { @@ -175,9 +175,9 @@ var ToolbarAndroid = createReactClass({ nativeProps.overflowIcon = resolveAssetSource(this.props.overflowIcon); } if (this.props.actions) { - var nativeActions = []; - for (var i = 0; i < this.props.actions.length; i++) { - var action = { + const nativeActions = []; + for (let i = 0; i < this.props.actions.length; i++) { + const action = { ...this.props.actions[i], }; if (action.icon) { @@ -195,7 +195,7 @@ var ToolbarAndroid = createReactClass({ }, _onSelect: function(event) { - var position = event.nativeEvent.position; + const position = event.nativeEvent.position; if (position === -1) { this.props.onIconClicked && this.props.onIconClicked(); } else { @@ -204,7 +204,7 @@ var ToolbarAndroid = createReactClass({ }, }); -var NativeToolbar = requireNativeComponent('ToolbarAndroid', ToolbarAndroid, { +const NativeToolbar = requireNativeComponent('ToolbarAndroid', ToolbarAndroid, { nativeOnly: { nativeActions: true, } diff --git a/Libraries/Components/Touchable/BoundingDimensions.js b/Libraries/Components/Touchable/BoundingDimensions.js index 73e700ca313b..9bc78e515d8e 100644 --- a/Libraries/Components/Touchable/BoundingDimensions.js +++ b/Libraries/Components/Touchable/BoundingDimensions.js @@ -9,9 +9,9 @@ 'use strict'; -var PooledClass = require('PooledClass'); +const PooledClass = require('PooledClass'); -var twoArgumentPooler = PooledClass.twoArgumentPooler; +const twoArgumentPooler = PooledClass.twoArgumentPooler; /** * PooledClass representing the bounding rectangle of a region. diff --git a/Libraries/Components/Touchable/PooledClass.js b/Libraries/Components/Touchable/PooledClass.js index ad5d874baa9c..a3ae190aa3d2 100644 --- a/Libraries/Components/Touchable/PooledClass.js +++ b/Libraries/Components/Touchable/PooledClass.js @@ -10,7 +10,7 @@ 'use strict'; -var invariant = require('fbjs/lib/invariant'); +const invariant = require('fbjs/lib/invariant'); /** * Static poolers. Several custom versions for each potential number of @@ -19,10 +19,10 @@ var invariant = require('fbjs/lib/invariant'); * the Class itself, not an instance. If any others are needed, simply add them * here, or in their own files. */ -var oneArgumentPooler = function(copyFieldsFrom) { - var Klass = this; +const oneArgumentPooler = function(copyFieldsFrom) { + const Klass = this; if (Klass.instancePool.length) { - var instance = Klass.instancePool.pop(); + const instance = Klass.instancePool.pop(); Klass.call(instance, copyFieldsFrom); return instance; } else { @@ -30,10 +30,10 @@ var oneArgumentPooler = function(copyFieldsFrom) { } }; -var twoArgumentPooler = function(a1, a2) { - var Klass = this; +const twoArgumentPooler = function(a1, a2) { + const Klass = this; if (Klass.instancePool.length) { - var instance = Klass.instancePool.pop(); + const instance = Klass.instancePool.pop(); Klass.call(instance, a1, a2); return instance; } else { @@ -41,10 +41,10 @@ var twoArgumentPooler = function(a1, a2) { } }; -var threeArgumentPooler = function(a1, a2, a3) { - var Klass = this; +const threeArgumentPooler = function(a1, a2, a3) { + const Klass = this; if (Klass.instancePool.length) { - var instance = Klass.instancePool.pop(); + const instance = Klass.instancePool.pop(); Klass.call(instance, a1, a2, a3); return instance; } else { @@ -52,10 +52,10 @@ var threeArgumentPooler = function(a1, a2, a3) { } }; -var fourArgumentPooler = function(a1, a2, a3, a4) { - var Klass = this; +const fourArgumentPooler = function(a1, a2, a3, a4) { + const Klass = this; if (Klass.instancePool.length) { - var instance = Klass.instancePool.pop(); + const instance = Klass.instancePool.pop(); Klass.call(instance, a1, a2, a3, a4); return instance; } else { @@ -63,8 +63,8 @@ var fourArgumentPooler = function(a1, a2, a3, a4) { } }; -var standardReleaser = function(instance) { - var Klass = this; +const standardReleaser = function(instance) { + const Klass = this; invariant( instance instanceof Klass, 'Trying to release an instance into a pool of a different type.', @@ -75,8 +75,8 @@ var standardReleaser = function(instance) { } }; -var DEFAULT_POOL_SIZE = 10; -var DEFAULT_POOLER = oneArgumentPooler; +const DEFAULT_POOL_SIZE = 10; +const DEFAULT_POOLER = oneArgumentPooler; type Pooler = any; @@ -89,7 +89,7 @@ type Pooler = any; * @param {Function} CopyConstructor Constructor that can be used to reset. * @param {Function} pooler Customizable pooler. */ -var addPoolingTo = function( +const addPoolingTo = function( CopyConstructor: Class, pooler: Pooler, ): Class & { @@ -100,7 +100,7 @@ var addPoolingTo = function( } { // Casting as any so that flow ignores the actual implementation and trusts // it to match the type we declared - var NewKlass = (CopyConstructor: any); + const NewKlass = (CopyConstructor: any); NewKlass.instancePool = []; NewKlass.getPooled = pooler || DEFAULT_POOLER; if (!NewKlass.poolSize) { @@ -110,7 +110,7 @@ var addPoolingTo = function( return NewKlass; }; -var PooledClass = { +const PooledClass = { addPoolingTo: addPoolingTo, oneArgumentPooler: (oneArgumentPooler: Pooler), twoArgumentPooler: (twoArgumentPooler: Pooler), diff --git a/Libraries/Components/Touchable/Position.js b/Libraries/Components/Touchable/Position.js index 229e791fb927..572d1132f038 100644 --- a/Libraries/Components/Touchable/Position.js +++ b/Libraries/Components/Touchable/Position.js @@ -9,9 +9,9 @@ 'use strict'; -var PooledClass = require('PooledClass'); +const PooledClass = require('PooledClass'); -var twoArgumentPooler = PooledClass.twoArgumentPooler; +const twoArgumentPooler = PooledClass.twoArgumentPooler; /** * Position does not expose methods for construction via an `HTMLDOMElement`, diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js index ff63195b089a..00a82cd64ac6 100644 --- a/Libraries/Components/Touchable/Touchable.js +++ b/Libraries/Components/Touchable/Touchable.js @@ -110,7 +110,7 @@ const normalizeColor = require('normalizeColor'); /** * Touchable states. */ -var States = keyMirror({ +const States = keyMirror({ NOT_RESPONDER: null, // Not the responder RESPONDER_INACTIVE_PRESS_IN: null, // Responder, inactive, in the `PressRect` RESPONDER_INACTIVE_PRESS_OUT: null, // Responder, inactive, out of `PressRect` @@ -124,7 +124,7 @@ var States = keyMirror({ /** * Quick lookup map for states that are considered to be "active" */ -var IsActive = { +const IsActive = { RESPONDER_ACTIVE_PRESS_OUT: true, RESPONDER_ACTIVE_PRESS_IN: true }; @@ -133,20 +133,20 @@ var IsActive = { * Quick lookup for states that are considered to be "pressing" and are * therefore eligible to result in a "selection" if the press stops. */ -var IsPressingIn = { +const IsPressingIn = { RESPONDER_INACTIVE_PRESS_IN: true, RESPONDER_ACTIVE_PRESS_IN: true, RESPONDER_ACTIVE_LONG_PRESS_IN: true, }; -var IsLongPressingIn = { +const IsLongPressingIn = { RESPONDER_ACTIVE_LONG_PRESS_IN: true, }; /** * Inputs to the state machine. */ -var Signals = keyMirror({ +const Signals = keyMirror({ DELAY: null, RESPONDER_GRANT: null, RESPONDER_RELEASE: null, @@ -159,7 +159,7 @@ var Signals = keyMirror({ /** * Mapping from States x Signals => States */ -var Transitions = { +const Transitions = { NOT_RESPONDER: { DELAY: States.ERROR, RESPONDER_GRANT: States.RESPONDER_INACTIVE_PRESS_IN, @@ -237,15 +237,15 @@ var Transitions = { // ==== Typical Constants for integrating into UI components ==== // var HIT_EXPAND_PX = 20; // var HIT_VERT_OFFSET_PX = 10; -var HIGHLIGHT_DELAY_MS = 130; +const HIGHLIGHT_DELAY_MS = 130; -var PRESS_EXPAND_PX = 20; +const PRESS_EXPAND_PX = 20; -var LONG_PRESS_THRESHOLD = 500; +const LONG_PRESS_THRESHOLD = 500; -var LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS; +const LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS; -var LONG_PRESS_ALLOWED_MOVEMENT = 10; +const LONG_PRESS_ALLOWED_MOVEMENT = 10; // Default amount "active" region protrudes beyond box @@ -313,15 +313,15 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10; * * @lends Touchable.prototype */ -var TouchableMixin = { +const TouchableMixin = { componentDidMount: function() { - if (!Platform.isTVOS) { + if (!Platform.isTV) { return; } this._tvEventHandler = new TVEventHandler(); this._tvEventHandler.enable(this, function(cmp, evt) { - var myTag = ReactNative.findNodeHandle(cmp); + const myTag = ReactNative.findNodeHandle(cmp); evt.dispatchConfig = {}; if (myTag === evt.tag) { if (evt.eventType === 'focus') { @@ -329,7 +329,7 @@ var TouchableMixin = { } else if (evt.eventType === 'blur') { cmp.touchableHandleActivePressOut && cmp.touchableHandleActivePressOut(evt); } else if (evt.eventType === 'select') { - cmp.touchableHandlePress && cmp.touchableHandlePress(evt); + cmp.touchableHandlePress && !cmp.props.disabled && cmp.touchableHandlePress(evt); } } }); @@ -389,7 +389,7 @@ var TouchableMixin = { * */ touchableHandleResponderGrant: function(e) { - var dispatchID = e.currentTarget; + const dispatchID = e.currentTarget; // Since e is used in a callback invoked on another event loop // (as in setTimeout etc), we need to call e.persist() on the // event to make sure it doesn't get reused in the event object pool. @@ -401,7 +401,7 @@ var TouchableMixin = { this.state.touchable.touchState = States.NOT_RESPONDER; this.state.touchable.responderID = dispatchID; this._receiveSignal(Signals.RESPONDER_GRANT, e); - var delayMS = + let delayMS = this.touchableGetHighlightDelayMS !== undefined ? Math.max(this.touchableGetHighlightDelayMS(), 0) : HIGHLIGHT_DELAY_MS; delayMS = isNaN(delayMS) ? HIGHLIGHT_DELAY_MS : delayMS; @@ -414,7 +414,7 @@ var TouchableMixin = { this._handleDelay(e); } - var longDelayMS = + let longDelayMS = this.touchableGetLongPressDelayMS !== undefined ? Math.max(this.touchableGetLongPressDelayMS(), 10) : LONG_PRESS_DELAY_MS; longDelayMS = isNaN(longDelayMS) ? LONG_PRESS_DELAY_MS : longDelayMS; @@ -453,9 +453,9 @@ var TouchableMixin = { return; } - var positionOnActivate = this.state.touchable.positionOnActivate; - var dimensionsOnActivate = this.state.touchable.dimensionsOnActivate; - var pressRectOffset = this.touchableGetPressRectOffset ? + const positionOnActivate = this.state.touchable.positionOnActivate; + const dimensionsOnActivate = this.state.touchable.dimensionsOnActivate; + const pressRectOffset = this.touchableGetPressRectOffset ? this.touchableGetPressRectOffset() : { left: PRESS_EXPAND_PX, right: PRESS_EXPAND_PX, @@ -463,12 +463,12 @@ var TouchableMixin = { bottom: PRESS_EXPAND_PX }; - var pressExpandLeft = pressRectOffset.left; - var pressExpandTop = pressRectOffset.top; - var pressExpandRight = pressRectOffset.right; - var pressExpandBottom = pressRectOffset.bottom; + let pressExpandLeft = pressRectOffset.left; + let pressExpandTop = pressRectOffset.top; + let pressExpandRight = pressRectOffset.right; + let pressExpandBottom = pressRectOffset.bottom; - var hitSlop = this.touchableGetHitSlop ? + const hitSlop = this.touchableGetHitSlop ? this.touchableGetHitSlop() : null; if (hitSlop) { @@ -478,18 +478,18 @@ var TouchableMixin = { pressExpandBottom += hitSlop.bottom; } - var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent); - var pageX = touch && touch.pageX; - var pageY = touch && touch.pageY; + const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent); + const pageX = touch && touch.pageX; + const pageY = touch && touch.pageY; if (this.pressInLocation) { - var movedDistance = this._getDistanceBetweenPoints(pageX, pageY, this.pressInLocation.pageX, this.pressInLocation.pageY); + const movedDistance = this._getDistanceBetweenPoints(pageX, pageY, this.pressInLocation.pageX, this.pressInLocation.pageY); if (movedDistance > LONG_PRESS_ALLOWED_MOVEMENT) { this._cancelLongPressDelayTimeout(); } } - var isTouchWithinActive = + const isTouchWithinActive = pageX > positionOnActivate.left - pressExpandLeft && pageY > positionOnActivate.top - pressExpandTop && pageX < @@ -502,7 +502,7 @@ var TouchableMixin = { pressExpandBottom; if (isTouchWithinActive) { this._receiveSignal(Signals.ENTER_PRESS_RECT, e); - var curState = this.state.touchable.touchState; + const curState = this.state.touchable.touchState; if (curState === States.RESPONDER_INACTIVE_PRESS_IN) { // fix for t7967420 this._cancelLongPressDelayTimeout(); @@ -620,7 +620,7 @@ var TouchableMixin = { _handleLongDelay: function(e) { this.longPressDelayTimeout = null; - var curState = this.state.touchable.touchState; + const curState = this.state.touchable.touchState; if (curState !== States.RESPONDER_ACTIVE_PRESS_IN && curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) { console.error('Attempted to transition from state `' + curState + '` to `' + @@ -640,9 +640,9 @@ var TouchableMixin = { * @sideeffects */ _receiveSignal: function(signal, e) { - var responderID = this.state.touchable.responderID; - var curState = this.state.touchable.touchState; - var nextState = Transitions[curState] && Transitions[curState][signal]; + const responderID = this.state.touchable.responderID; + const curState = this.state.touchable.touchState; + const nextState = Transitions[curState] && Transitions[curState][signal]; if (!responderID && signal === Signals.RESPONDER_RELEASE) { return; } @@ -675,17 +675,17 @@ var TouchableMixin = { }, _savePressInLocation: function(e) { - var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent); - var pageX = touch && touch.pageX; - var pageY = touch && touch.pageY; - var locationX = touch && touch.locationX; - var locationY = touch && touch.locationY; + const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent); + const pageX = touch && touch.pageX; + const pageY = touch && touch.pageY; + const locationX = touch && touch.locationX; + const locationY = touch && touch.locationY; this.pressInLocation = {pageX, pageY, locationX, locationY}; }, _getDistanceBetweenPoints: function (aX, aY, bX, bY) { - var deltaX = aX - bX; - var deltaY = aY - bY; + const deltaX = aX - bX; + const deltaY = aY - bY; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); }, @@ -701,10 +701,10 @@ var TouchableMixin = { * @sideeffects */ _performSideEffectsForTransition: function(curState, nextState, signal, e) { - var curIsHighlight = this._isHighlight(curState); - var newIsHighlight = this._isHighlight(nextState); + const curIsHighlight = this._isHighlight(curState); + const newIsHighlight = this._isHighlight(nextState); - var isFinalSignal = + const isFinalSignal = signal === Signals.RESPONDER_TERMINATED || signal === Signals.RESPONDER_RELEASE; @@ -727,14 +727,14 @@ var TouchableMixin = { } if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) { - var hasLongPressHandler = !!this.props.onLongPress; - var pressIsLongButStillCallOnPress = + const hasLongPressHandler = !!this.props.onLongPress; + const pressIsLongButStillCallOnPress = IsLongPressingIn[curState] && ( // We *are* long pressing.. - !hasLongPressHandler || // But either has no long handler - !this.touchableLongPressCancelsPress() // or we're told to ignore it. + (// But either has no long handler + !hasLongPressHandler || !this.touchableLongPressCancelsPress()) // or we're told to ignore it. ); - var shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress; + const shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress; if (shouldInvokePress && this.touchableHandlePress) { if (!newIsHighlight && !curIsHighlight) { // we never highlighted because of delay, but we should highlight now @@ -768,7 +768,7 @@ var TouchableMixin = { }; -var Touchable = { +const Touchable = { Mixin: TouchableMixin, TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector. /** diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 2f0949b6ff22..2afef30637b5 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -10,13 +10,13 @@ */ 'use strict'; -var Animated = require('Animated'); -var EdgeInsetsPropType = require('EdgeInsetsPropType'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var React = require('React'); -var createReactClass = require('create-react-class'); -var PropTypes = require('prop-types'); -var Touchable = require('Touchable'); +const Animated = require('Animated'); +const EdgeInsetsPropType = require('EdgeInsetsPropType'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const createReactClass = require('create-react-class'); +const PropTypes = require('prop-types'); +const Touchable = require('Touchable'); type Event = Object; @@ -25,7 +25,7 @@ type State = { scale: Animated.Value, }; -var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; +const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; /** * Example of using the `TouchableMixin` to play well with other responder @@ -34,7 +34,7 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; * `TouchableMixin` expects us to implement some abstract methods to handle * interesting interactions such as `handleTouchablePress`. */ -var TouchableBounce = createReactClass({ +const TouchableBounce = createReactClass({ displayName: 'TouchableBounce', mixins: [Touchable.Mixin, NativeMethodsMixin], @@ -115,7 +115,7 @@ var TouchableBounce = createReactClass({ }, touchableHandlePress: function(e: Event) { - var onPressWithCompletion = this.props.onPressWithCompletion; + const onPressWithCompletion = this.props.onPressWithCompletion; if (onPressWithCompletion) { onPressWithCompletion(() => { this.state.scale.setValue(0.93); diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js index effad3764275..4f3887642dce 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js @@ -8,37 +8,37 @@ */ 'use strict'; -var Platform = require('Platform'); -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNative = require('ReactNative'); -var Touchable = require('Touchable'); -var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); -var UIManager = require('UIManager'); - -var createReactClass = require('create-react-class'); -var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); -var processColor = require('processColor'); - -var rippleBackgroundPropType = PropTypes.shape({ +const Platform = require('Platform'); +const React = require('React'); +const PropTypes = require('prop-types'); +const ReactNative = require('ReactNative'); +const Touchable = require('Touchable'); +const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); +const UIManager = require('UIManager'); + +const createReactClass = require('create-react-class'); +const ensurePositiveDelayProps = require('ensurePositiveDelayProps'); +const processColor = require('processColor'); + +const rippleBackgroundPropType = PropTypes.shape({ type: PropTypes.oneOf(['RippleAndroid']), color: PropTypes.number, borderless: PropTypes.bool, }); -var themeAttributeBackgroundPropType = PropTypes.shape({ +const themeAttributeBackgroundPropType = PropTypes.shape({ type: PropTypes.oneOf(['ThemeAttrAndroid']), attribute: PropTypes.string.isRequired, }); -var backgroundPropType = PropTypes.oneOfType([ +const backgroundPropType = PropTypes.oneOfType([ rippleBackgroundPropType, themeAttributeBackgroundPropType, ]); type Event = Object; -var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; +const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; /** * A wrapper for making views respond properly to touches (Android only). @@ -69,7 +69,7 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; * ``` */ -var TouchableNativeFeedback = createReactClass({ +const TouchableNativeFeedback = createReactClass({ displayName: 'TouchableNativeFeedback', propTypes: { ...TouchableWithoutFeedback.propTypes, @@ -82,6 +82,11 @@ var TouchableNativeFeedback = createReactClass({ */ background: backgroundPropType, + /** + * TV preferred focus (see documentation for the View component). + */ + hasTVPreferredFocus: PropTypes.bool, + /** * Set to true to add the ripple effect to the foreground of the view, instead of the * background. This is useful if one of your child views has a background of its own, or you're @@ -156,7 +161,9 @@ var TouchableNativeFeedback = createReactClass({ touchableHandleActivePressIn: function(e: Event) { this.props.onPressIn && this.props.onPressIn(e); this._dispatchPressedStateChange(true); - this._dispatchHotspotUpdate(this.pressInLocation.locationX, this.pressInLocation.locationY); + if (this.pressInLocation) { + this._dispatchHotspotUpdate(this.pressInLocation.locationX, this.pressInLocation.locationY); + } }, touchableHandleActivePressOut: function(e: Event) { @@ -233,7 +240,7 @@ var TouchableNativeFeedback = createReactClass({ this.props.useForeground && TouchableNativeFeedback.canUseNativeForeground() ? 'nativeForegroundAndroid' : 'nativeBackgroundAndroid'; - var childProps = { + const childProps = { ...child.props, [drawableProp]: this.props.background, accessible: this.props.accessible !== false, @@ -244,6 +251,8 @@ var TouchableNativeFeedback = createReactClass({ testID: this.props.testID, onLayout: this.props.onLayout, hitSlop: this.props.hitSlop, + isTVSelectable: true, + hasTVPreferredFocus: this.props.hasTVPreferredFocus, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, onResponderGrant: this.touchableHandleResponderGrant, diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.ios.js b/Libraries/Components/Touchable/TouchableNativeFeedback.ios.js index c624cb7fdbf0..e898d20ab64a 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.ios.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.ios.js @@ -9,10 +9,10 @@ 'use strict'; -var React = require('React'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var View = require('View'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const View = require('View'); class DummyTouchableNativeFeedback extends React.Component { static SelectableBackground = () => ({}); @@ -29,7 +29,7 @@ class DummyTouchableNativeFeedback extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { height: 100, width: 300, diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index e8d6ca171abc..8ff2ab8d0f2a 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -11,22 +11,22 @@ // Note (avik): add @flow when Flow supports spread properties in propTypes -var Animated = require('Animated'); -var Easing = require('Easing'); -var NativeMethodsMixin = require('NativeMethodsMixin'); -var React = require('React'); -var PropTypes = require('prop-types'); -var TimerMixin = require('react-timer-mixin'); -var Touchable = require('Touchable'); -var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); +const Animated = require('Animated'); +const Easing = require('Easing'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const PropTypes = require('prop-types'); +const TimerMixin = require('react-timer-mixin'); +const Touchable = require('Touchable'); +const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); -var createReactClass = require('create-react-class'); -var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); -var flattenStyle = require('flattenStyle'); +const createReactClass = require('create-react-class'); +const ensurePositiveDelayProps = require('ensurePositiveDelayProps'); +const flattenStyle = require('flattenStyle'); type Event = Object; -var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; +const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; /** * A wrapper for making views respond properly to touches. @@ -116,7 +116,7 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; * ``` * */ -var TouchableOpacity = createReactClass({ +const TouchableOpacity = createReactClass({ displayName: 'TouchableOpacity', mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin], @@ -128,9 +128,7 @@ var TouchableOpacity = createReactClass({ */ activeOpacity: PropTypes.number, /** - * *(Apple TV only)* TV preferred focus (see documentation for the View component). - * - * @platform ios + * TV preferred focus (see documentation for the View component). */ hasTVPreferredFocus: PropTypes.bool, /** @@ -160,6 +158,12 @@ var TouchableOpacity = createReactClass({ ensurePositiveDelayProps(nextProps); }, + componentDidUpdate: function(prevProps, prevState) { + if (this.props.disabled !== prevProps.disabled) { + this._opacityInactive(250); + } + }, + /** * Animate the touchable to a new opacity. */ @@ -234,7 +238,7 @@ var TouchableOpacity = createReactClass({ }, _getChildStyleOpacityWithDefault: function() { - var childStyle = flattenStyle(this.props.style) || {}; + const childStyle = flattenStyle(this.props.style) || {}; return childStyle.opacity == undefined ? 1 : childStyle.opacity; }, diff --git a/Libraries/Components/Touchable/ensureComponentIsNative.js b/Libraries/Components/Touchable/ensureComponentIsNative.js index de01d017716b..63c1e8bf8d02 100644 --- a/Libraries/Components/Touchable/ensureComponentIsNative.js +++ b/Libraries/Components/Touchable/ensureComponentIsNative.js @@ -9,9 +9,9 @@ */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); +const invariant = require('fbjs/lib/invariant'); -var ensureComponentIsNative = function(component: any) { +const ensureComponentIsNative = function(component: any) { invariant( component && typeof component.setNativeProps === 'function', 'Touchable child must either be native or forward setNativeProps to a ' + diff --git a/Libraries/Components/Touchable/ensurePositiveDelayProps.js b/Libraries/Components/Touchable/ensurePositiveDelayProps.js index 85f60091f604..51b97f5ca42e 100644 --- a/Libraries/Components/Touchable/ensurePositiveDelayProps.js +++ b/Libraries/Components/Touchable/ensurePositiveDelayProps.js @@ -9,9 +9,9 @@ */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); +const invariant = require('fbjs/lib/invariant'); -var ensurePositiveDelayProps = function(props: any) { +const ensurePositiveDelayProps = function(props: any) { invariant( !(props.delayPressIn < 0 || props.delayPressOut < 0 || props.delayLongPress < 0), diff --git a/Libraries/Components/View/PlatformViewPropTypes.ios.js b/Libraries/Components/View/PlatformViewPropTypes.js similarity index 57% rename from Libraries/Components/View/PlatformViewPropTypes.ios.js rename to Libraries/Components/View/PlatformViewPropTypes.js index 2394a51746d1..891fa34572a4 100644 --- a/Libraries/Components/View/PlatformViewPropTypes.ios.js +++ b/Libraries/Components/View/PlatformViewPropTypes.js @@ -10,8 +10,11 @@ const Platform = require('Platform'); -var TVViewPropTypes = {}; -if (Platform.isTVOS) { +let TVViewPropTypes = {}; +// We need to always include TVViewPropTypes on Android +// as unlike on iOS we can't detect TV devices at build time +// and hence make view manager export a different list of native properties. +if (Platform.isTV || Platform.OS === 'android') { TVViewPropTypes = require('TVViewPropTypes'); } diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js index 462e9b7caeb6..a9441cad97f1 100644 --- a/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -10,19 +10,19 @@ 'use strict'; -var ImageStylePropTypes = require('ImageStylePropTypes'); -var TextStylePropTypes = require('TextStylePropTypes'); -var ViewStylePropTypes = require('ViewStylePropTypes'); +const ImageStylePropTypes = require('ImageStylePropTypes'); +const TextStylePropTypes = require('TextStylePropTypes'); +const ViewStylePropTypes = require('ViewStylePropTypes'); /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error * found when Flow v0.54 was deployed. To see the error delete this comment and * run Flow. */ -var keyMirror = require('fbjs/lib/keyMirror'); -var processColor = require('processColor'); -var processTransform = require('processTransform'); -var sizesDiffer = require('sizesDiffer'); +const keyMirror = require('fbjs/lib/keyMirror'); +const processColor = require('processColor'); +const processTransform = require('processTransform'); +const sizesDiffer = require('sizesDiffer'); -var ReactNativeStyleAttributes = { +const ReactNativeStyleAttributes = { ...keyMirror(ViewStylePropTypes), ...keyMirror(TextStylePropTypes), ...keyMirror(ImageStylePropTypes), @@ -31,7 +31,7 @@ var ReactNativeStyleAttributes = { ReactNativeStyleAttributes.transform = { process: processTransform }; ReactNativeStyleAttributes.shadowOffset = { diff: sizesDiffer }; -var colorAttributes = { process: processColor }; +const colorAttributes = { process: processColor }; ReactNativeStyleAttributes.backgroundColor = colorAttributes; ReactNativeStyleAttributes.borderBottomColor = colorAttributes; ReactNativeStyleAttributes.borderColor = colorAttributes; diff --git a/Libraries/Components/View/ReactNativeViewAttributes.js b/Libraries/Components/View/ReactNativeViewAttributes.js index 64f81c719669..26fb368aceb9 100644 --- a/Libraries/Components/View/ReactNativeViewAttributes.js +++ b/Libraries/Components/View/ReactNativeViewAttributes.js @@ -9,9 +9,9 @@ */ 'use strict'; -var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); +const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); -var ReactNativeViewAttributes = {}; +const ReactNativeViewAttributes = {}; ReactNativeViewAttributes.UIView = { pointerEvents: true, diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js index 6e9c92163b31..c21ce5934ac3 100644 --- a/Libraries/Components/View/ViewStylePropTypes.js +++ b/Libraries/Components/View/ViewStylePropTypes.js @@ -9,16 +9,16 @@ */ 'use strict'; -var ColorPropType = require('ColorPropType'); -var LayoutPropTypes = require('LayoutPropTypes'); -var ReactPropTypes = require('prop-types'); -var ShadowPropTypesIOS = require('ShadowPropTypesIOS'); -var TransformPropTypes = require('TransformPropTypes'); +const ColorPropType = require('ColorPropType'); +const LayoutPropTypes = require('LayoutPropTypes'); +const ReactPropTypes = require('prop-types'); +const ShadowPropTypesIOS = require('ShadowPropTypesIOS'); +const TransformPropTypes = require('TransformPropTypes'); /** * Warning: Some of these properties may not be supported in all releases. */ -var ViewStylePropTypes = { +const ViewStylePropTypes = { ...LayoutPropTypes, ...ShadowPropTypesIOS, ...TransformPropTypes, diff --git a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js index bcc1d8609a5f..8463503bd793 100644 --- a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js +++ b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js @@ -9,16 +9,16 @@ */ 'use strict'; -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNative = require('ReactNative'); -var UIManager = require('UIManager'); -var ViewPropTypes = require('ViewPropTypes'); +const React = require('React'); +const PropTypes = require('prop-types'); +const ReactNative = require('ReactNative'); +const UIManager = require('UIManager'); +const ViewPropTypes = require('ViewPropTypes'); -var dismissKeyboard = require('dismissKeyboard'); -var requireNativeComponent = require('requireNativeComponent'); +const dismissKeyboard = require('dismissKeyboard'); +const requireNativeComponent = require('requireNativeComponent'); -var VIEWPAGER_REF = 'viewPager'; +const VIEWPAGER_REF = 'viewPager'; type Event = Object; @@ -163,7 +163,7 @@ class ViewPagerAndroid extends React.Component<{ if (!child) { return null; } - var newProps = { + const newProps = { ...child.props, style: [child.props.style, { position: 'absolute', @@ -246,6 +246,6 @@ class ViewPagerAndroid extends React.Component<{ } } -var NativeAndroidViewPager = requireNativeComponent('AndroidViewPager', ViewPagerAndroid); +const NativeAndroidViewPager = requireNativeComponent('AndroidViewPager', ViewPagerAndroid); module.exports = ViewPagerAndroid; diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index 851378755da4..16f2991df1e7 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -8,30 +8,30 @@ */ 'use strict'; -var EdgeInsetsPropType = require('EdgeInsetsPropType'); -var ActivityIndicator = require('ActivityIndicator'); -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNative = require('ReactNative'); -var StyleSheet = require('StyleSheet'); -var UIManager = require('UIManager'); -var View = require('View'); -var ViewPropTypes = require('ViewPropTypes'); - -var deprecatedPropType = require('deprecatedPropType'); -var keyMirror = require('fbjs/lib/keyMirror'); -var requireNativeComponent = require('requireNativeComponent'); -var resolveAssetSource = require('resolveAssetSource'); - -var RCT_WEBVIEW_REF = 'webview'; - -var WebViewState = keyMirror({ +const EdgeInsetsPropType = require('EdgeInsetsPropType'); +const ActivityIndicator = require('ActivityIndicator'); +const React = require('React'); +const PropTypes = require('prop-types'); +const ReactNative = require('ReactNative'); +const StyleSheet = require('StyleSheet'); +const UIManager = require('UIManager'); +const View = require('View'); +const ViewPropTypes = require('ViewPropTypes'); + +const deprecatedPropType = require('deprecatedPropType'); +const keyMirror = require('fbjs/lib/keyMirror'); +const requireNativeComponent = require('requireNativeComponent'); +const resolveAssetSource = require('resolveAssetSource'); + +const RCT_WEBVIEW_REF = 'webview'; + +const WebViewState = keyMirror({ IDLE: null, LOADING: null, ERROR: null, }); -var defaultRenderLoading = () => ( +const defaultRenderLoading = () => ( { - var onLoadStart = this.props.onLoadStart; + const onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); this.updateNavigationState(event); }; onLoadingError = (event) => { event.persist(); // persist this event because we need to store it - var {onError, onLoadEnd} = this.props; + const {onError, onLoadEnd} = this.props; onError && onError(event); onLoadEnd && onLoadEnd(event); console.warn('Encountered an error loading page', event.nativeEvent); @@ -418,7 +418,7 @@ class WebView extends React.Component { }; onLoadingFinish = (event) => { - var {onLoad, onLoadEnd} = this.props; + const {onLoad, onLoadEnd} = this.props; onLoad && onLoad(event); onLoadEnd && onLoadEnd(event); this.setState({ @@ -428,14 +428,14 @@ class WebView extends React.Component { }; onMessage = (event: Event) => { - var {onMessage} = this.props; + const {onMessage} = this.props; onMessage && onMessage(event); } } -var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig); +const RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, }, diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index e11f8e3851b6..f01cfae537dd 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -9,31 +9,31 @@ */ 'use strict'; -var ActivityIndicator = require('ActivityIndicator'); -var EdgeInsetsPropType = require('EdgeInsetsPropType'); -var React = require('React'); -var PropTypes = require('prop-types'); -var ReactNative = require('ReactNative'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var UIManager = require('UIManager'); -var View = require('View'); -var ViewPropTypes = require('ViewPropTypes'); -var ScrollView = require('ScrollView'); - -var deprecatedPropType = require('deprecatedPropType'); -var invariant = require('fbjs/lib/invariant'); -var keyMirror = require('fbjs/lib/keyMirror'); -var processDecelerationRate = require('processDecelerationRate'); -var requireNativeComponent = require('requireNativeComponent'); -var resolveAssetSource = require('resolveAssetSource'); - -var RCTWebViewManager = require('NativeModules').WebViewManager; - -var BGWASH = 'rgba(255,255,255,0.8)'; -var RCT_WEBVIEW_REF = 'webview'; - -var WebViewState = keyMirror({ +const ActivityIndicator = require('ActivityIndicator'); +const EdgeInsetsPropType = require('EdgeInsetsPropType'); +const React = require('React'); +const PropTypes = require('prop-types'); +const ReactNative = require('ReactNative'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const UIManager = require('UIManager'); +const View = require('View'); +const ViewPropTypes = require('ViewPropTypes'); +const ScrollView = require('ScrollView'); + +const deprecatedPropType = require('deprecatedPropType'); +const invariant = require('fbjs/lib/invariant'); +const keyMirror = require('fbjs/lib/keyMirror'); +const processDecelerationRate = require('processDecelerationRate'); +const requireNativeComponent = require('requireNativeComponent'); +const resolveAssetSource = require('resolveAssetSource'); + +const RCTWebViewManager = require('NativeModules').WebViewManager; + +const BGWASH = 'rgba(255,255,255,0.8)'; +const RCT_WEBVIEW_REF = 'webview'; + +const WebViewState = keyMirror({ IDLE: null, LOADING: null, ERROR: null, @@ -67,12 +67,12 @@ const DataDetectorTypes = [ 'all', ]; -var defaultRenderLoading = () => ( +const defaultRenderLoading = () => ( ); -var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( +const defaultRenderError = (errorDomain, errorCode, errorDesc) => ( Error loading page @@ -415,12 +415,12 @@ class WebView extends React.Component { } render() { - var otherView = null; + let otherView = null; if (this.state.viewState === WebViewState.LOADING) { otherView = (this.props.renderLoading || defaultRenderLoading)(); } else if (this.state.viewState === WebViewState.ERROR) { - var errorEvent = this.state.lastErrorEvent; + const errorEvent = this.state.lastErrorEvent; invariant( errorEvent != null, 'lastErrorEvent expected to be non-null' @@ -436,7 +436,7 @@ class WebView extends React.Component { ); } - var webViewStyles = [styles.container, styles.webView, this.props.style]; + const webViewStyles = [styles.container, styles.webView, this.props.style]; if (this.state.viewState === WebViewState.LOADING || this.state.viewState === WebViewState.ERROR) { // if we're in either LOADING or ERROR states, don't show the webView @@ -447,15 +447,15 @@ class WebView extends React.Component { const viewManager = nativeConfig.viewManager || RCTWebViewManager; - var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => { - var shouldStart = this.props.onShouldStartLoadWithRequest && + const onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => { + const shouldStart = this.props.onShouldStartLoadWithRequest && this.props.onShouldStartLoadWithRequest(event.nativeEvent); viewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier); }); - var decelerationRate = processDecelerationRate(this.props.decelerationRate); + const decelerationRate = processDecelerationRate(this.props.decelerationRate); - var source = this.props.source || {}; + const source = this.props.source || {}; if (this.props.html) { source.html = this.props.html; } else if (this.props.url) { @@ -466,7 +466,7 @@ class WebView extends React.Component { const NativeWebView = nativeConfig.component || RCTWebView; - var webView = + const webView = { - var onLoadStart = this.props.onLoadStart; + const onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); this._updateNavigationState(event); }; _onLoadingError = (event: Event) => { event.persist(); // persist this event because we need to store it - var {onError, onLoadEnd} = this.props; + const {onError, onLoadEnd} = this.props; onError && onError(event); onLoadEnd && onLoadEnd(event); console.warn('Encountered an error loading page', event.nativeEvent); @@ -613,7 +613,7 @@ class WebView extends React.Component { }; _onLoadingFinish = (event: Event) => { - var {onLoad, onLoadEnd} = this.props; + const {onLoad, onLoadEnd} = this.props; onLoad && onLoad(event); onLoadEnd && onLoadEnd(event); this.setState({ @@ -623,14 +623,14 @@ class WebView extends React.Component { }; _onMessage = (event: Event) => { - var {onMessage} = this.props; + const {onMessage} = this.props; onMessage && onMessage(event); } } -var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig); +const RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, }, diff --git a/Libraries/Core/Devtools/getDevServer.js b/Libraries/Core/Devtools/getDevServer.js index e413709da1ce..975a75f827b2 100644 --- a/Libraries/Core/Devtools/getDevServer.js +++ b/Libraries/Core/Devtools/getDevServer.js @@ -25,7 +25,7 @@ type DevServerInfo = { */ function getDevServer(): DevServerInfo { if (_cachedDevServerURL === undefined) { - const match = SourceCode.scriptURL && SourceCode.scriptURL.match(/^https?:\/\/.*?\//); + const match = SourceCode && SourceCode.scriptURL && SourceCode.scriptURL.match(/^https?:\/\/.*?\//); _cachedDevServerURL = match ? match[0] : null; } diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index a5b6229d35d7..24fec6ef33c7 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -92,7 +92,7 @@ if (!sourceCode) { const NativeModules = require('NativeModules'); sourceCode = NativeModules && NativeModules.SourceCode; } -_sourceCodeScriptURL = sourceCode.scriptURL; +_sourceCodeScriptURL = sourceCode && sourceCode.scriptURL; module.exports = resolveAssetSource; module.exports.pickScale = AssetSourceResolver.pickScale; diff --git a/Libraries/Inspector/ElementProperties.js b/Libraries/Inspector/ElementProperties.js index f7937c5f7edb..460e1c2df810 100644 --- a/Libraries/Inspector/ElementProperties.js +++ b/Libraries/Inspector/ElementProperties.js @@ -23,11 +23,11 @@ const flattenStyle = require('flattenStyle'); const mapWithSeparator = require('mapWithSeparator'); const openFileInEditor = require('openFileInEditor'); -import type {StyleObj} from 'StyleSheetTypes'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; class ElementProperties extends React.Component<{ hierarchy: Array<$FlowFixMe>, - style?: StyleObj, + style?: DangerouslyImpreciseStyleProp, source?: { fileName?: string, lineNumber?: number, diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index 90e30b346a94..963b616fca3b 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -32,15 +32,26 @@ export type ReactRenderer = { }; const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; -const renderer: ReactRenderer = findRenderer(); +const renderers = findRenderers(); + // required for devtools to be able to edit react native styles hook.resolveRNStyle = require('flattenStyle'); -function findRenderer(): ReactRenderer { - const renderers = hook._renderers; - const keys = Object.keys(renderers); - invariant(keys.length === 1, 'Expected to find exactly one React Native renderer on DevTools hook.'); - return renderers[keys[0]]; +function findRenderers(): $ReadOnlyArray { + const allRenderers = Object.keys(hook._renderers).map(key => hook._renderers[key]); + invariant(allRenderers.length >= 1, 'Expected to find at least one React Native renderer on DevTools hook.'); + return allRenderers; +} + +function getInspectorDataForViewTag(touchedViewTag: number) { + for (let i = 0; i < renderers.length; i++) { + const renderer = renderers[i]; + const inspectorData = renderer.getInspectorDataForViewTag(touchedViewTag); + if (inspectorData.hierarchy.length > 0) { + return inspectorData; + } + } + throw new Error('Expected to find at least one React renderer.'); } class Inspector extends React.Component<{ @@ -170,7 +181,7 @@ class Inspector extends React.Component<{ props, selection, source, - } = renderer.getInspectorDataForViewTag(touchedViewTag); + } = getInspectorDataForViewTag(touchedViewTag); if (this.state.devtoolsAgent) { // Skip host leafs diff --git a/Libraries/Lists/FlatList.js b/Libraries/Lists/FlatList.js index e5b4a8310330..13b87ed995b7 100644 --- a/Libraries/Lists/FlatList.js +++ b/Libraries/Lists/FlatList.js @@ -18,7 +18,7 @@ const ListView = require('ListView'); const invariant = require('fbjs/lib/invariant'); -import type {StyleObj} from 'StyleSheetTypes'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; import type { ViewabilityConfig, ViewToken, @@ -96,7 +96,7 @@ type OptionalProps = { /** * Optional custom style for multi-item rows generated when numColumns > 1. */ - columnWrapperStyle?: StyleObj, + columnWrapperStyle?: DangerouslyImpreciseStyleProp, /** * A marker property for telling the list to re-render (since it implements `PureComponent`). If * any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index f40b44e404c4..90e978cf8e3c 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -32,7 +32,7 @@ const warning = require('fbjs/lib/warning'); const {computeWindowedRenderLimits} = require('VirtualizeUtils'); -import type {StyleObj} from 'StyleSheetTypes'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; import type { ViewabilityConfig, ViewToken, @@ -442,6 +442,7 @@ class VirtualizedList extends React.PureComponent { getScrollMetrics: PropTypes.func, horizontal: PropTypes.bool, getOutermostParentListRef: PropTypes.func, + getNestedChildState: PropTypes.func, registerAsNestedChild: PropTypes.func, unregisterAsNestedChild: PropTypes.func, }), @@ -452,6 +453,7 @@ class VirtualizedList extends React.PureComponent { getScrollMetrics: PropTypes.func, horizontal: PropTypes.bool, getOutermostParentListRef: PropTypes.func, + getNestedChildState: PropTypes.func, registerAsNestedChild: PropTypes.func, unregisterAsNestedChild: PropTypes.func, }), @@ -463,6 +465,7 @@ class VirtualizedList extends React.PureComponent { getScrollMetrics: this._getScrollMetrics, horizontal: this.props.horizontal, getOutermostParentListRef: this._getOutermostParentListRef, + getNestedChildState: this._getNestedChildState, registerAsNestedChild: this._registerAsNestedChild, unregisterAsNestedChild: this._unregisterAsNestedChild, }, @@ -492,6 +495,11 @@ class VirtualizedList extends React.PureComponent { } }; + _getNestedChildState = (key: string): ?ChildListState => { + const existingChildData = this._nestedChildLists.get(key); + return existingChildData && existingChildData.state; + }; + _registerAsNestedChild = (childList: { cellKey: string, key: string, @@ -518,8 +526,6 @@ class VirtualizedList extends React.PureComponent { if (this._hasInteracted) { childList.ref.recordInteraction(); } - - return existingChildData && existingChildData.state; }; _unregisterAsNestedChild = (childList: { @@ -577,11 +583,9 @@ class VirtualizedList extends React.PureComponent { }; if (this._isNestedWithSameOrientation()) { - const storedState = this.context.virtualizedList.registerAsNestedChild({ - cellKey: this._getCellKey(), - key: this.props.listKey || this._getCellKey(), - ref: this, - }); + const storedState = this.context.virtualizedList.getNestedChildState( + this.props.listKey || this._getCellKey(), + ); if (storedState) { initialState = storedState; this.state = storedState; @@ -592,6 +596,16 @@ class VirtualizedList extends React.PureComponent { this.state = initialState; } + componentDidMount() { + if (this._isNestedWithSameOrientation()) { + this.context.virtualizedList.registerAsNestedChild({ + cellKey: this._getCellKey(), + key: this.props.listKey || this._getCellKey(), + ref: this, + }); + } + } + componentWillUnmount() { if (this._isNestedWithSameOrientation()) { this.context.virtualizedList.unregisterAsNestedChild({ @@ -611,29 +625,17 @@ class VirtualizedList extends React.PureComponent { this._fillRateHelper.deactivateAndFlush(); } - UNSAFE_componentWillReceiveProps(newProps: Props) { + static getDerivedStateFromProps(newProps: Props, prevState: State) { const {data, extraData, getItemCount, maxToRenderPerBatch} = newProps; // first and last could be stale (e.g. if a new, shorter items props is passed in), so we make // sure we're rendering a reasonable range here. - this.setState({ + return { first: Math.max( 0, - Math.min( - this.state.first, - getItemCount(data) - 1 - maxToRenderPerBatch, - ), + Math.min(prevState.first, getItemCount(data) - 1 - maxToRenderPerBatch), ), - last: Math.max(0, Math.min(this.state.last, getItemCount(data) - 1)), - }); - if (data !== this.props.data || extraData !== this.props.extraData) { - this._hasDataChangedSinceEndReached = true; - - // clear the viewableIndices cache to also trigger - // the onViewableItemsChanged callback with the new data - this._viewabilityTuples.forEach(tuple => { - tuple.viewabilityHelper.resetViewableIndices(); - }); - } + last: Math.max(0, Math.min(prevState.last, getItemCount(data) - 1)), + }; } _pushCells( @@ -642,7 +644,7 @@ class VirtualizedList extends React.PureComponent { stickyIndicesFromProps: Set, first: number, last: number, - inversionStyle: ?StyleObj, + inversionStyle: ?DangerouslyImpreciseStyleProp, ) { const { CellRendererComponent, @@ -916,7 +918,17 @@ class VirtualizedList extends React.PureComponent { } } - componentDidUpdate() { + componentDidUpdate(prevProps: Props) { + const {data, extraData} = this.props; + if (data !== prevProps.data || extraData !== prevProps.extraData) { + this._hasDataChangedSinceEndReached = true; + + // clear the viewableIndices cache to also trigger + // the onViewableItemsChanged callback with the new data + this._viewabilityTuples.forEach(tuple => { + tuple.viewabilityHelper.resetViewableIndices(); + }); + } this._scheduleCellsToRenderUpdate(); } @@ -1533,7 +1545,7 @@ class CellRenderer extends React.Component< fillRateHelper: FillRateHelper, horizontal: ?boolean, index: number, - inversionStyle: ?StyleObj, + inversionStyle: ?DangerouslyImpreciseStyleProp, item: Item, onLayout: (event: Object) => void, // This is extracted by ScrollViewStickyHeader onUnmount: (cellKey: string) => void, diff --git a/Libraries/StyleSheet/StyleSheet.js b/Libraries/StyleSheet/StyleSheet.js index 3971365c98e9..3320c459b68c 100644 --- a/Libraries/StyleSheet/StyleSheet.js +++ b/Libraries/StyleSheet/StyleSheet.js @@ -6,6 +6,7 @@ * * @providesModule StyleSheet * @flow + * @format */ 'use strict'; @@ -17,33 +18,35 @@ const StyleSheetValidation = require('StyleSheetValidation'); const flatten = require('flattenStyle'); import type { - StyleSheetStyle as _StyleSheetStyle, - Styles as _Styles, - StyleSheet as _StyleSheet, - StyleValue as _StyleValue, - StyleObj, + ____StyleSheetInternalStyleIdentifier_Internal as StyleSheetInternalStyleIdentifier, + ____Styles_Internal, + ____DangerouslyImpreciseStyleProp_Internal, + ____ViewStyleProp_Internal, + ____TextStyleProp_Internal, + ____ImageStyleProp_Internal, + LayoutStyle, } from 'StyleSheetTypes'; -export type StyleProp = StyleObj; -export type Styles = _Styles; -export type StyleSheet = _StyleSheet; -export type StyleValue = _StyleValue; -export type StyleSheetStyle = _StyleSheetStyle; +export type DangerouslyImpreciseStyleProp = ____DangerouslyImpreciseStyleProp_Internal; +export type ViewStyleProp = ____ViewStyleProp_Internal; +export type TextStyleProp = ____TextStyleProp_Internal; +export type ImageStyleProp = ____ImageStyleProp_Internal; let hairlineWidth = PixelRatio.roundToNearestPixel(0.4); if (hairlineWidth === 0) { hairlineWidth = 1 / PixelRatio.get(); } -const absoluteFillObject = { - position: ('absolute': 'absolute'), +const absoluteFillObject: LayoutStyle = { + position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }; -const absoluteFill: typeof absoluteFillObject = - ReactNativePropRegistry.register(absoluteFillObject); // This also freezes it +const absoluteFill: StyleSheetInternalStyleIdentifier = ReactNativePropRegistry.register( + absoluteFillObject, +); // This also freezes it /** * A StyleSheet is an abstraction similar to CSS StyleSheets @@ -138,11 +141,14 @@ module.exports = { * array, saving allocations and maintaining reference equality for * PureComponent checks. */ - compose(style1: ?StyleProp, style2: ?StyleProp): ?StyleProp { - if (style1 && style2) { + compose( + style1: ?DangerouslyImpreciseStyleProp, + style2: ?DangerouslyImpreciseStyleProp, + ): ?DangerouslyImpreciseStyleProp { + if (style1 != null && style2 != null) { return [style1, style2]; } else { - return style1 || style2; + return style1 != null ? style1 : style2; } }, @@ -196,7 +202,10 @@ module.exports = { * internally to process color and transform values. You should not use this * unless you really know what you are doing and have exhausted other options. */ - setStyleAttributePreprocessor(property: string, process: (nextProp: mixed) => mixed) { + setStyleAttributePreprocessor( + property: string, + process: (nextProp: mixed) => mixed, + ) { let value; if (typeof ReactNativeStyleAttributes[property] === 'string') { @@ -212,13 +221,15 @@ module.exports = { console.warn(`Overwriting ${property} style attribute preprocessor`); } - ReactNativeStyleAttributes[property] = { ...value, process }; + ReactNativeStyleAttributes[property] = {...value, process}; }, /** * Creates a StyleSheet style reference from the given object. */ - create(obj: S): StyleSheet { + create<+S: ____Styles_Internal>( + obj: S, + ): $ObjMap StyleSheetInternalStyleIdentifier> { const result = {}; for (const key in obj) { StyleSheetValidation.validateStyle(key, obj); diff --git a/Libraries/StyleSheet/StyleSheetTypes.js b/Libraries/StyleSheet/StyleSheetTypes.js index cfd0f50bba2c..b9c3c59fd2d3 100644 --- a/Libraries/StyleSheet/StyleSheetTypes.js +++ b/Libraries/StyleSheet/StyleSheetTypes.js @@ -13,158 +13,160 @@ import AnimatedNode from 'AnimatedNode'; -export opaque type StyleSheetStyle: number = number; +export opaque type ____StyleSheetInternalStyleIdentifier_Internal: number = number; export type ColorValue = null | string; export type DimensionValue = null | number | string | AnimatedNode; -export type LayoutStyle<+Dimension = DimensionValue> = { - +display?: 'none' | 'flex', - +width?: Dimension, - +height?: Dimension, - +bottom?: Dimension, - +end?: Dimension, - +left?: Dimension, - +right?: Dimension, - +start?: Dimension, - +top?: Dimension, - +minWidth?: Dimension, - +maxWidth?: Dimension, - +minHeight?: Dimension, - +maxHeight?: Dimension, - +margin?: Dimension, - +marginBottom?: Dimension, - +marginEnd?: Dimension, - +marginHorizontal?: Dimension, - +marginLeft?: Dimension, - +marginRight?: Dimension, - +marginStart?: Dimension, - +marginTop?: Dimension, - +marginVertical?: Dimension, - +padding?: Dimension, - +paddingBottom?: Dimension, - +paddingEnd?: Dimension, - +paddingHorizontal?: Dimension, - +paddingLeft?: Dimension, - +paddingRight?: Dimension, - +paddingStart?: Dimension, - +paddingTop?: Dimension, - +paddingVertical?: Dimension, - +borderWidth?: number, - +borderBottomWidth?: number, - +borderEndWidth?: number, - +borderLeftWidth?: number, - +borderRightWidth?: number, - +borderStartWidth?: number, - +borderTopWidth?: number, - +position?: 'absolute' | 'relative', - +flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse', - +flexWrap?: 'wrap' | 'nowrap', - +justifyContent?: +export type LayoutStyle = $ReadOnly<{| + display?: 'none' | 'flex', + width?: DimensionValue, + height?: DimensionValue, + bottom?: DimensionValue, + end?: DimensionValue, + left?: DimensionValue, + right?: DimensionValue, + start?: DimensionValue, + top?: DimensionValue, + minWidth?: DimensionValue, + maxWidth?: DimensionValue, + minHeight?: DimensionValue, + maxHeight?: DimensionValue, + margin?: DimensionValue, + marginBottom?: DimensionValue, + marginEnd?: DimensionValue, + marginHorizontal?: DimensionValue, + marginLeft?: DimensionValue, + marginRight?: DimensionValue, + marginStart?: DimensionValue, + marginTop?: DimensionValue, + marginVertical?: DimensionValue, + padding?: DimensionValue, + paddingBottom?: DimensionValue, + paddingEnd?: DimensionValue, + paddingHorizontal?: DimensionValue, + paddingLeft?: DimensionValue, + paddingRight?: DimensionValue, + paddingStart?: DimensionValue, + paddingTop?: DimensionValue, + paddingVertical?: DimensionValue, + borderWidth?: number, + borderBottomWidth?: number, + borderEndWidth?: number, + borderLeftWidth?: number, + borderRightWidth?: number, + borderStartWidth?: number, + borderTopWidth?: number, + position?: 'absolute' | 'relative', + flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse', + flexWrap?: 'wrap' | 'nowrap', + justifyContent?: | 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly', - +alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline', - +alignSelf?: + alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline', + alignSelf?: | 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline', - +alignContent?: + alignContent?: | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'space-between' | 'space-around', - +overflow?: 'visible' | 'hidden' | 'scroll', - +flex?: number, - +flexGrow?: number, - +flexShrink?: number, - +flexBasis?: number | string, - +aspectRatio?: number, - +zIndex?: number, - +direction?: 'inherit' | 'ltr' | 'rtl', -}; + overflow?: 'visible' | 'hidden' | 'scroll', + flex?: number, + flexGrow?: number, + flexShrink?: number, + flexBasis?: number | string, + aspectRatio?: number, + zIndex?: number, + direction?: 'inherit' | 'ltr' | 'rtl', +|}>; -export type TransformStyle = { - +transform?: $ReadOnlyArray< - | {+perspective: number | AnimatedNode} - | {+rotate: string} - | {+rotateX: string} - | {+rotateY: string} - | {+rotateZ: string} - | {+scale: number | AnimatedNode} - | {+scaleX: number | AnimatedNode} - | {+scaleY: number | AnimatedNode} - | {+translateX: number | AnimatedNode} - | {+translateY: number | AnimatedNode} - | { +export type TransformStyle = $ReadOnly<{| + transform?: $ReadOnlyArray< + | {|+perspective: number | AnimatedNode|} + | {|+rotate: string|} + | {|+rotateX: string|} + | {|+rotateY: string|} + | {|+rotateZ: string|} + | {|+scale: number | AnimatedNode|} + | {|+scaleX: number | AnimatedNode|} + | {|+scaleY: number | AnimatedNode|} + | {|+translateX: number | AnimatedNode|} + | {|+translateY: number | AnimatedNode|} + | {| +translate: [number | AnimatedNode, number | AnimatedNode] | AnimatedNode, - } - | {+skewX: string} - | {+skewY: string} + |} + | {|+skewX: string|} + | {|+skewY: string|} // TODO: what is the actual type it expects? - | {+matrix: $ReadOnlyArray | AnimatedNode}, + | {| + +matrix: $ReadOnlyArray | AnimatedNode, + |}, >, -}; +|}>; -export type ShadowStyle<+Color = ColorValue> = { - +shadowColor?: Color, - +shadowOffset?: { - +width?: number, - +height?: number, - }, - +shadowOpacity?: number | AnimatedNode, - +shadowRadius?: number, -}; +export type ShadowStyle = $ReadOnly<{| + shadowColor?: ColorValue, + shadowOffset?: $ReadOnly<{| + width?: number, + height?: number, + |}>, + shadowOpacity?: number | AnimatedNode, + shadowRadius?: number, +|}>; -export type ViewStyle<+Dimension = DimensionValue, +Color = ColorValue> = { - ...$Exact>, - ...$Exact>, +export type ViewStyle = $ReadOnly<{| + ...$Exact, + ...$Exact, ...$Exact, - +backfaceVisibility?: 'visible' | 'hidden', - +backgroundColor?: Color, - +borderColor?: Color, - +borderBottomColor?: Color, - +borderEndColor?: Color, - +borderLeftColor?: Color, - +borderRightColor?: Color, - +borderStartColor?: Color, - +borderTopColor?: Color, - +borderRadius?: number, - +borderBottomEndRadius?: number, - +borderBottomLeftRadius?: number, - +borderBottomRightRadius?: number, - +borderBottomStartRadius?: number, - +borderTopEndRadius?: number, - +borderTopLeftRadius?: number, - +borderTopRightRadius?: number, - +borderTopStartRadius?: number, - +borderStyle?: 'solid' | 'dotted' | 'dashed', - +borderWidth?: number, - +borderBottomWidth?: number, - +borderEndWidth?: number, - +borderLeftWidth?: number, - +borderRightWidth?: number, - +borderStartWidth?: number, - +borderTopWidth?: number, - +opacity?: number | AnimatedNode, - +elevation?: number, -}; + backfaceVisibility?: 'visible' | 'hidden', + backgroundColor?: ColorValue, + borderColor?: ColorValue, + borderBottomColor?: ColorValue, + borderEndColor?: ColorValue, + borderLeftColor?: ColorValue, + borderRightColor?: ColorValue, + borderStartColor?: ColorValue, + borderTopColor?: ColorValue, + borderRadius?: number, + borderBottomEndRadius?: number, + borderBottomLeftRadius?: number, + borderBottomRightRadius?: number, + borderBottomStartRadius?: number, + borderTopEndRadius?: number, + borderTopLeftRadius?: number, + borderTopRightRadius?: number, + borderTopStartRadius?: number, + borderStyle?: 'solid' | 'dotted' | 'dashed', + borderWidth?: number, + borderBottomWidth?: number, + borderEndWidth?: number, + borderLeftWidth?: number, + borderRightWidth?: number, + borderStartWidth?: number, + borderTopWidth?: number, + opacity?: number | AnimatedNode, + elevation?: number, +|}>; -export type TextStyle<+Dimension = DimensionValue, +Color = ColorValue> = { - ...$Exact>, - +color?: Color, - +fontFamily?: string, - +fontSize?: number, - +fontStyle?: 'normal' | 'italic', - +fontWeight?: +export type TextStyle = $ReadOnly<{| + ...$Exact, + color?: ColorValue, + fontFamily?: string, + fontSize?: number, + fontStyle?: 'normal' | 'italic', + fontWeight?: | 'normal' | 'bold' | '100' @@ -176,74 +178,74 @@ export type TextStyle<+Dimension = DimensionValue, +Color = ColorValue> = { | '700' | '800' | '900', - +fontVariant?: $ReadOnlyArray< + fontVariant?: $ReadOnlyArray< | 'small-caps' | 'oldstyle-nums' | 'lining-nums' | 'tabular-nums' | 'proportional-nums', >, - +textShadowOffset?: {+width?: number, +height?: number}, - +textShadowRadius?: number, - +textShadowColor?: Color, - +letterSpacing?: number, - +lineHeight?: number, - +textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify', - +textAlignVertical?: 'auto' | 'top' | 'bottom' | 'center', - +includeFontPadding?: boolean, - +textDecorationLine?: + textShadowOffset?: $ReadOnly<{| + width?: number, + height?: number, + |}>, + textShadowRadius?: number, + textShadowColor?: ColorValue, + letterSpacing?: number, + lineHeight?: number, + textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify', + textAlignVertical?: 'auto' | 'top' | 'bottom' | 'center', + includeFontPadding?: boolean, + textDecorationLine?: | 'none' | 'underline' | 'line-through' | 'underline line-through', - +textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed', - +textDecorationColor?: Color, - +writingDirection?: 'auto' | 'ltr' | 'rtl', -}; + textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed', + textDecorationColor?: ColorValue, + writingDirection?: 'auto' | 'ltr' | 'rtl', +|}>; -export type ImageStyle<+Dimension = DimensionValue, +Color = ColorValue> = { - ...$Exact>, - +resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat', - +tintColor?: Color, - +overlayColor?: string, -}; +export type ImageStyle = $ReadOnly<{| + ...$Exact, + resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat', + tintColor?: ColorValue, + overlayColor?: string, +|}>; -export type Style<+Dimension = DimensionValue, +Color = ColorValue> = { - ...$Exact>, +export type DangerouslyImpreciseStyle = { + ...$Exact, +resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat', - +tintColor?: Color, + +tintColor?: ColorValue, +overlayColor?: string, }; -export type StyleProp<+T> = +type GenericStyleProp<+T> = | null | void | T - | StyleSheetStyle + | ____StyleSheetInternalStyleIdentifier_Internal | number | false | '' - | $ReadOnlyArray>; - -// export type ViewStyleProp = StyleProp<$Shape>>; -// export type TextStyleProp = StyleProp< -// $Shape>, -// >; -// export type ImageStyleProp = StyleProp< -// $Shape>, -// >; - -export type StyleObj = StyleProp<$Shape>>; -export type StyleValue = StyleObj; + | $ReadOnlyArray>; -export type ViewStyleProp = StyleObj; -export type TextStyleProp = StyleObj; -export type ImageStyleProp = StyleObj; +export type ____DangerouslyImpreciseStyleProp_Internal = GenericStyleProp< + $Shape, +>; +export type ____ViewStyleProp_Internal = GenericStyleProp< + $ReadOnly<$Shape>, +>; +export type ____TextStyleProp_Internal = GenericStyleProp< + $ReadOnly<$Shape>, +>; +export type ____ImageStyleProp_Internal = GenericStyleProp< + $ReadOnly<$Shape>, +>; -export type Styles = { - +[key: string]: $Shape>, +export type ____Styles_Internal = { + +[key: string]: $Shape, }; -export type StyleSheet<+S: Styles> = $ObjMap StyleSheetStyle>; /* Utility type get non-nullable types for specific style keys. @@ -260,4 +262,6 @@ type Props = {position: TypeForStyleKey<'position'>}; This will correctly give you the type 'absolute' | 'relative' instead of the weak type of just string; */ -export type TypeForStyleKey<+key: $Keys>> = $ElementType, key>; +export type TypeForStyleKey< + +key: $Keys, +> = $ElementType; diff --git a/Libraries/StyleSheet/__tests__/flattenStyle-test.js b/Libraries/StyleSheet/__tests__/flattenStyle-test.js index 6613944ed966..2f0fdcc0fb0a 100644 --- a/Libraries/StyleSheet/__tests__/flattenStyle-test.js +++ b/Libraries/StyleSheet/__tests__/flattenStyle-test.js @@ -8,30 +8,48 @@ */ 'use strict'; -var flattenStyle = require('flattenStyle'); +const StyleSheet = require('StyleSheet'); +const StyleSheetValidation = require('StyleSheetValidation'); +const flattenStyle = require('flattenStyle'); -describe('flattenStyle', () => { +function getFixture() { + StyleSheetValidation.addValidStylePropTypes({ + styleA: () => {}, + styleB: () => {}, + }); + + return StyleSheet.create({ + elementA: { + styleA: 'moduleA/elementA/styleA', + styleB: 'moduleA/elementA/styleB', + }, + elementB: { + styleB: 'moduleA/elementB/styleB', + }, + }); +} +describe('flattenStyle', () => { it('should merge style objects', () => { - var style1 = {width: 10}; - var style2 = {height: 20}; - var flatStyle = flattenStyle([style1, style2]); + const style1 = {width: 10}; + const style2 = {height: 20}; + const flatStyle = flattenStyle([style1, style2]); expect(flatStyle.width).toBe(10); expect(flatStyle.height).toBe(20); }); it('should override style properties', () => { - var style1 = {backgroundColor: '#000', width: 10}; - var style2 = {backgroundColor: '#023c69', width: null}; - var flatStyle = flattenStyle([style1, style2]); + const style1 = {backgroundColor: '#000', width: 10}; + const style2 = {backgroundColor: '#023c69', width: null}; + const flatStyle = flattenStyle([style1, style2]); expect(flatStyle.backgroundColor).toBe('#023c69'); expect(flatStyle.width).toBe(null); }); it('should overwrite properties with `undefined`', () => { - var style1 = {backgroundColor: '#000'}; - var style2 = {backgroundColor: undefined}; - var flatStyle = flattenStyle([style1, style2]); + const style1 = {backgroundColor: '#000'}; + const style2 = {backgroundColor: undefined}; + const flatStyle = flattenStyle([style1, style2]); expect(flatStyle.backgroundColor).toBe(undefined); }); @@ -40,11 +58,99 @@ describe('flattenStyle', () => { }); it('should recursively flatten arrays', () => { - var style1 = {width: 10}; - var style2 = {height: 20}; - var style3 = {width: 30}; - var flatStyle = flattenStyle([null, [], [style1, style2], style3]); + const style1 = {width: 10}; + const style2 = {height: 20}; + const style3 = {width: 30}; + const flatStyle = flattenStyle([null, [], [style1, style2], style3]); expect(flatStyle.width).toBe(30); expect(flatStyle.height).toBe(20); }); + + it('should not allocate an object when there is no style', () => { + const nullStyle = flattenStyle(null); + const nullStyleAgain = flattenStyle(null); + + expect(nullStyle).toBe(nullStyleAgain); + expect(nullStyle).toBe(undefined); + }); + + it('should not allocate an object when there is a style', () => { + const style = {a: 'b'}; + const nullStyle = flattenStyle(style); + + expect(nullStyle).toBe(style); + }); + + it('should not allocate an object when there is a single class', () => { + const fixture = getFixture(); + var singleStyle = flattenStyle(fixture.elementA); + var singleStyleAgain = flattenStyle(fixture.elementA); + + expect(singleStyle).toBe(singleStyleAgain); + expect(singleStyle).toEqual({ + styleA: 'moduleA/elementA/styleA', + styleB: 'moduleA/elementA/styleB', + }); + }); + + it('should merge single class and style properly', () => { + const fixture = getFixture(); + var style = {styleA: 'overrideA', styleC: 'overrideC'}; + var arrayStyle = flattenStyle([fixture.elementA, style]); + + expect(arrayStyle).toEqual({ + styleA: 'overrideA', + styleB: 'moduleA/elementA/styleB', + styleC: 'overrideC', + }); + }); + + it('should merge multiple classes', () => { + const fixture = getFixture(); + var AthenB = flattenStyle([fixture.elementA, fixture.elementB]); + var BthenA = flattenStyle([fixture.elementB, fixture.elementA]); + + expect(AthenB).toEqual({ + styleA: 'moduleA/elementA/styleA', + styleB: 'moduleA/elementB/styleB', + }); + expect(BthenA).toEqual({ + styleA: 'moduleA/elementA/styleA', + styleB: 'moduleA/elementA/styleB', + }); + }); + + it('should merge multiple classes with style', () => { + const fixture = getFixture(); + var style = {styleA: 'overrideA'}; + var AthenB = flattenStyle([fixture.elementA, fixture.elementB, style]); + var BthenA = flattenStyle([fixture.elementB, fixture.elementA, style]); + + expect(AthenB).toEqual({ + styleA: 'overrideA', + styleB: 'moduleA/elementB/styleB', + }); + expect(BthenA).toEqual({ + styleA: 'overrideA', + styleB: 'moduleA/elementA/styleB', + }); + }); + + it('should flatten recursively', () => { + const fixture = getFixture(); + var style = [{styleA: 'newA', styleB: 'newB'}, {styleA: 'newA2'}]; + var AthenB = flattenStyle([fixture.elementA, fixture.elementB, style]); + + expect(AthenB).toEqual({ + styleA: 'newA2', + styleB: 'newB', + }); + }); + + it('should ignore invalid class names', () => { + var invalid = flattenStyle(1234, null); + + expect(invalid).toEqual({}); + // Invalid class name 1234 skipping ... + }); }); diff --git a/Libraries/StyleSheet/flattenStyle.js b/Libraries/StyleSheet/flattenStyle.js index 88f839e2885e..37ac2d434a0f 100644 --- a/Libraries/StyleSheet/flattenStyle.js +++ b/Libraries/StyleSheet/flattenStyle.js @@ -6,13 +6,14 @@ * * @providesModule flattenStyle * @flow + * @format */ 'use strict'; -var invariant = require('fbjs/lib/invariant'); var ReactNativePropRegistry; -import type { StyleObj } from 'StyleSheetTypes'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; +import type {DangerouslyImpreciseStyle} from 'StyleSheetTypes'; function getStyle(style) { if (ReactNativePropRegistry === undefined) { @@ -24,11 +25,12 @@ function getStyle(style) { return style; } -function flattenStyle(style: ?StyleObj): ?Object { - if (!style) { +function flattenStyle( + style: ?DangerouslyImpreciseStyleProp, +): ?DangerouslyImpreciseStyle { + if (style == null) { return undefined; } - invariant(style !== true, 'style may be false but not true'); if (!Array.isArray(style)) { /* $FlowFixMe(>=0.63.0 site=react_native_fb) This comment suppresses an diff --git a/Libraries/Text/TextProps.js b/Libraries/Text/TextProps.js index 7b077f9cd785..770e7ec0e501 100644 --- a/Libraries/Text/TextProps.js +++ b/Libraries/Text/TextProps.js @@ -14,7 +14,7 @@ import type {Node} from 'react'; import type {LayoutEvent} from 'CoreEventTypes'; -import type {TextStyleProp} from 'StyleSheetTypes'; +import type {TextStyleProp} from 'StyleSheet'; type PressRetentionOffset = { top: number, diff --git a/Libraries/Utilities/JSDevSupportModule.js b/Libraries/Utilities/JSDevSupportModule.js index 0b8cdca1f683..a33e91e88618 100644 --- a/Libraries/Utilities/JSDevSupportModule.js +++ b/Libraries/Utilities/JSDevSupportModule.js @@ -18,7 +18,6 @@ var JSDevSupportModule = { var result = renderer.getInspectorDataForViewTag(tag); var path = result.hierarchy.map( (item) => item.name).join(' -> '); - console.error('StackOverflowException rendering JSComponent: ' + path); require('NativeModules').JSDevSupport.setResult(path, null); }, }; diff --git a/Libraries/Utilities/Platform.android.js b/Libraries/Utilities/Platform.android.js index 006f371777ec..f0e80633f2f2 100644 --- a/Libraries/Utilities/Platform.android.js +++ b/Libraries/Utilities/Platform.android.js @@ -22,6 +22,10 @@ const Platform = { const constants = NativeModules.PlatformConstants; return constants && constants.isTesting; }, + get isTV(): boolean { + const constants = NativeModules.PlatformConstants; + return constants && constants.uiMode === 'tv'; + }, select: (obj: Object) => 'android' in obj ? obj.android : obj.default, }; diff --git a/Libraries/Utilities/Platform.ios.js b/Libraries/Utilities/Platform.ios.js index 00bd3882651f..776880b7fa40 100644 --- a/Libraries/Utilities/Platform.ios.js +++ b/Libraries/Utilities/Platform.ios.js @@ -22,7 +22,13 @@ const Platform = { const constants = NativeModules.PlatformConstants; return constants ? constants.interfaceIdiom === 'pad' : false; }, + /** + * Deprecated, use `isTV` instead. + */ get isTVOS() { + return Platform.isTV; + }, + get isTV() { const constants = NativeModules.PlatformConstants; return constants ? constants.interfaceIdiom === 'tv' : false; }, diff --git a/RNTester/android/app/build.gradle b/RNTester/android/app/build.gradle index 9e55d12326f0..95d02acb788a 100644 --- a/RNTester/android/app/build.gradle +++ b/RNTester/android/app/build.gradle @@ -64,7 +64,7 @@ project.ext.react = [ inputExcludes: ["android/**", "./**"] ] -apply from: "react.gradle" +apply from: "../../../react.gradle" /** * Set this to true to create three separate APKs instead of one: diff --git a/RNTester/android/app/react.gradle b/RNTester/android/app/react.gradle deleted file mode 100644 index c0dca024c8df..000000000000 --- a/RNTester/android/app/react.gradle +++ /dev/null @@ -1,96 +0,0 @@ -import org.apache.tools.ant.taskdefs.condition.Os - -def config = project.hasProperty("react") ? project.react : []; - -def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" -def entryFile = config.entryFile ?: "index.android.js" - -// because elvis operator -def elvisFile(thing) { - return thing ? file(thing) : null; -} - -def reactRoot = elvisFile(config.root) ?: file("../../") -def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] - -void runBefore(String dependentTaskName, Task task) { - Task dependentTask = tasks.findByPath(dependentTaskName); - if (dependentTask != null) { - dependentTask.dependsOn task - } -} - -gradle.projectsEvaluated { - // Grab all build types and product flavors - def buildTypes = android.buildTypes.collect { type -> type.name } - def productFlavors = android.productFlavors.collect { flavor -> flavor.name } - - // When no product flavors defined, use empty - if (!productFlavors) productFlavors.add('') - - productFlavors.each { productFlavorName -> - buildTypes.each { buildTypeName -> - // Create variant and source names - def sourceName = "${buildTypeName}" - def targetName = "${sourceName.capitalize()}" - if (productFlavorName) { - sourceName = "${productFlavorName}${targetName}" - } - - // React js bundle directories - def jsBundleDirConfigName = "jsBundleDir${targetName}" - def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: - file("$buildDir/intermediates/assets/${sourceName}") - - def resourcesDirConfigName = "jsBundleDir${targetName}" - def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: - file("$buildDir/intermediates/res/merged/${sourceName}") - def jsBundleFile = file("$jsBundleDir/$bundleAssetName") - - // Bundle task name for variant - def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" - - def currentBundleTask = tasks.create( - name: bundleJsAndAssetsTaskName, - type: Exec) { - group = "react" - description = "bundle JS and assets for ${targetName}." - - // Create dirs if they are not there (e.g. the "clean" task just ran) - doFirst { - jsBundleDir.mkdirs() - resourcesDir.mkdirs() - } - - // Set up inputs and outputs so gradle can cache the result - inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) - outputs.dir jsBundleDir - outputs.dir resourcesDir - - // Set up the call to the react-native cli - workingDir reactRoot - - // Set up dev mode - def devEnabled = !targetName.toLowerCase().contains("release") - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine "cmd", "/c", "node", "./local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}", - "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir - } else { - commandLine "node", "./local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}", - "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir - } - - enabled config."bundleIn${targetName}" ?: targetName.toLowerCase().contains("release") - } - - // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process - currentBundleTask.dependsOn("merge${targetName}Resources") - currentBundleTask.dependsOn("merge${targetName}Assets") - - runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask) - runBefore("processX86${targetName}Resources", currentBundleTask) - runBefore("processUniversal${targetName}Resources", currentBundleTask) - runBefore("process${targetName}Resources", currentBundleTask) - } - } -} diff --git a/RNTester/android/app/src/main/AndroidManifest.xml b/RNTester/android/app/src/main/AndroidManifest.xml index 818cdd422d54..0bde8735a3a8 100644 --- a/RNTester/android/app/src/main/AndroidManifest.xml +++ b/RNTester/android/app/src/main/AndroidManifest.xml @@ -20,9 +20,14 @@ android:minSdkVersion="16" android:targetSdkVersion="23" /> + @@ -34,6 +39,8 @@ + + diff --git a/RNTester/android/app/src/main/res/drawable/tv_banner.png b/RNTester/android/app/src/main/res/drawable/tv_banner.png new file mode 100644 index 000000000000..d67884677f12 Binary files /dev/null and b/RNTester/android/app/src/main/res/drawable/tv_banner.png differ diff --git a/RNTester/js/RNTesterExampleList.js b/RNTester/js/RNTesterExampleList.js index 072a31f3d1b6..96e1ed9b474a 100644 --- a/RNTester/js/RNTesterExampleList.js +++ b/RNTester/js/RNTesterExampleList.js @@ -20,15 +20,9 @@ const RNTesterActions = require('./RNTesterActions'); const RNTesterStatePersister = require('./RNTesterStatePersister'); const View = require('View'); -import type { - RNTesterExample, -} from './RNTesterList.ios'; -import type { - PassProps, -} from './RNTesterStatePersister'; -import type { - StyleObj, -} from 'StyleSheetTypes'; +import type {RNTesterExample} from './RNTesterList.ios'; +import type {PassProps} from './RNTesterStatePersister'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; type Props = { onNavigate: Function, @@ -37,8 +31,8 @@ type Props = { APIExamples: Array, }, persister: PassProps<*>, - searchTextInputStyle: StyleObj, - style?: ?StyleObj, + searchTextInputStyle: DangerouslyImpreciseStyleProp, + style?: ?DangerouslyImpreciseStyleProp, }; class RowComponent extends React.PureComponent<{ diff --git a/RNTester/js/ScrollViewExample.js b/RNTester/js/ScrollViewExample.js index f0d1e9bb19c6..f5b5c8aab8fd 100644 --- a/RNTester/js/ScrollViewExample.js +++ b/RNTester/js/ScrollViewExample.js @@ -10,7 +10,7 @@ */ 'use strict'; -import type {StyleObj} from 'StyleSheetTypes'; +import type {DangerouslyImpreciseStyleProp} from 'StyleSheet'; const ActivityIndicator = require('ActivityIndicator'); const Platform = require('Platform'); @@ -252,7 +252,7 @@ if (Platform.OS === 'ios') { class Thumb extends React.PureComponent<{| source?: string | number, msg?: string, - style?: StyleObj, + style?: DangerouslyImpreciseStyleProp, |}> { render() { const {source} = this.props; diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 7db16baea377..2a7eabe5ce84 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -19,10 +19,16 @@ @class RCTPerformanceLogger; /** - * This notification fires when the bridge starts loading the JS bundle. + * This notification fires when the bridge initializes. */ RCT_EXTERN NSString *const RCTJavaScriptWillStartLoadingNotification; + +/** + * This notification fires when the bridge starts executing the JS bundle. + */ +RCT_EXTERN NSString *const RCTJavaScriptWillStartExecutingNotification; + /** * This notification fires when the bridge has finished loading the JS bundle. */ @@ -120,22 +126,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args; - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion; -/** - * This method is used to call functions in the JavaScript application context - * synchronously. This is intended for use by applications which do their own - * thread management and are careful to manage multi-threaded access to the JSVM. - * See also -[RCTBridgeDelgate shouldBridgeLoadJavaScriptSynchronously], which - * may be needed to ensure that any requires JS code is loaded before this method - * is called. If the underlying executor is not JSC, this will return nil. Safe - * to call from any thread. - * - * @experimental - */ -- (JSValue *)callFunctionOnModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)arguments - error:(NSError **)error; - /** * This method registers the file path of an additional JS segment by its ID. * diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index a84a966e3618..76f2d17a203f 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -23,6 +23,7 @@ #import "RCTUtils.h" NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification"; +NSString *const RCTJavaScriptWillStartExecutingNotification = @"RCTJavaScriptWillStartExecutingNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification"; @@ -364,14 +365,6 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args [self.batchedBridge enqueueCallback:cbID args:args]; } -- (JSValue *)callFunctionOnModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)arguments - error:(NSError **)error -{ - return [self.batchedBridge callFunctionOnModule:module method:method arguments:arguments error:error]; -} - - (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path { [self.batchedBridge registerSegmentWithId:segmentId path:path]; diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index f138868eb306..07116bb80ef9 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -197,7 +197,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \ */ #define RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(js_name, returnType, method) \ _RCT_EXTERN_REMAP_METHOD(js_name, method, YES) \ - - (returnType)method; + - (returnType)method RCT_DYNAMIC; /** * Use this macro in a private Objective-C implementation file to automatically diff --git a/React/Base/RCTRootContentView.h b/React/Base/RCTRootContentView.h index bd14cfb2005e..28a0646cfed7 100644 --- a/React/Base/RCTRootContentView.h +++ b/React/Base/RCTRootContentView.h @@ -24,17 +24,9 @@ @property (nonatomic, assign) BOOL passThroughTouches; @property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility; -@property (nonatomic, assign) BOOL fabric; - -- (instancetype)initWithFrame:(CGRect)frame - bridge:(RCTBridge *)bridge - reactTag:(NSNumber *)reactTag - sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility - fabric:(BOOL)fabric NS_DESIGNATED_INITIALIZER; - - (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge reactTag:(NSNumber *)reactTag - sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility; + sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility NS_DESIGNATED_INITIALIZER; @end diff --git a/React/Base/RCTRootContentView.m b/React/Base/RCTRootContentView.m index 7782d06f02c3..67b1f8b70021 100644 --- a/React/Base/RCTRootContentView.m +++ b/React/Base/RCTRootContentView.m @@ -21,10 +21,8 @@ - (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge reactTag:(NSNumber *)reactTag sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility - fabric:(BOOL)fabric { if ((self = [super initWithFrame:frame])) { - _fabric = fabric; _bridge = bridge; self.reactTag = reactTag; _sizeFlexibility = sizeFlexibility; @@ -35,14 +33,6 @@ - (instancetype)initWithFrame:(CGRect)frame return self; } -- (instancetype)initWithFrame:(CGRect)frame - bridge:(RCTBridge *)bridge - reactTag:(NSNumber *)reactTag - sizeFlexiblity:(RCTRootViewSizeFlexibility)sizeFlexibility -{ - return [self initWithFrame:frame bridge:bridge reactTag:reactTag sizeFlexiblity:sizeFlexibility fabric:NO]; -} - RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame) RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder) @@ -109,17 +99,10 @@ - (void)invalidate self.userInteractionEnabled = NO; [(RCTRootView *)self.superview contentViewInvalidated]; - if (_fabric) { - [_bridge enqueueJSCall:@"ReactFabric" - method:@"unmountComponentAtNodeAndRemoveContainer" - args:@[self.reactTag] - completion:NULL]; - } else { - [_bridge enqueueJSCall:@"AppRegistry" - method:@"unmountApplicationComponentAtRootTag" - args:@[self.reactTag] - completion:NULL]; - } + [_bridge enqueueJSCall:@"AppRegistry" + method:@"unmountApplicationComponentAtRootTag" + args:@[self.reactTag] + completion:NULL]; } } diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index 41c21f672fdb..daab13efbe8c 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -44,16 +44,7 @@ extern NSString *const RCTContentDidAppearNotification; */ - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric NS_DESIGNATED_INITIALIZER; - -/** - * - Convenience initializer - - * Initialize without using FabricUIManager. - */ -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties; + initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER; /** * - Convenience initializer - @@ -67,15 +58,6 @@ extern NSString *const RCTContentDidAppearNotification; initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions; -/** - * - Convenience initializer - - */ -- (instancetype)initWithBundleURL:(NSURL *)bundleURL - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - launchOptions:(NSDictionary *)launchOptions - fabric:(BOOL)fabric; - /** * The name of the JavaScript module to execute within the @@ -165,11 +147,6 @@ extern NSString *const RCTContentDidAppearNotification; @property (nonatomic, assign) NSTimeInterval loadingViewFadeDelay; @property (nonatomic, assign) NSTimeInterval loadingViewFadeDuration; -/** - * Indicates whether this view is managed by FabricUIManager or the traditional UIManager. - */ -@property (nonatomic, assign) BOOL fabric; - @end @interface RCTRootView (Deprecated) diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 8fabbd6e8473..204dd5871be1 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -52,7 +52,6 @@ @implementation RCTRootView - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric { RCTAssertMainQueue(); RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView"); @@ -66,7 +65,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge if (self = [super initWithFrame:CGRectZero]) { self.backgroundColor = [UIColor whiteColor]; - _fabric = fabric; _bridge = bridge; _moduleName = moduleName; _appProperties = [initialProperties copy]; @@ -108,32 +106,16 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge return self; } -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:NO]; -} - - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions - fabric:(BOOL)fabric { RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions]; - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:fabric]; -} - -- (instancetype)initWithBundleURL:(NSURL *)bundleURL - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - launchOptions:(NSDictionary *)launchOptions -{ - return [self initWithBundleURL:bundleURL moduleName:moduleName initialProperties:initialProperties launchOptions:launchOptions fabric:NO]; + return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) @@ -289,8 +271,7 @@ - (void)bundleFinishedLoading:(RCTBridge *)bridge _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge reactTag:self.reactTag - sizeFlexiblity:_sizeFlexibility - fabric:self.fabric]; + sizeFlexiblity:_sizeFlexibility]; [self runApplication:bridge]; _contentView.passThroughTouches = _passThroughTouches; diff --git a/React/Base/Surface/RCTSurface.h b/React/Base/Surface/RCTSurface.h index 01d4896f9e30..5c3566725e49 100644 --- a/React/Base/Surface/RCTSurface.h +++ b/React/Base/Surface/RCTSurface.h @@ -42,16 +42,9 @@ NS_ASSUME_NONNULL_BEGIN @property (atomic, copy, readwrite) NSDictionary *properties; -@property (nonatomic, assign, readonly) BOOL fabric; - -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric NS_DESIGNATED_INITIALIZER; - - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties; + initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER; #pragma mark - Dealing with UIView representation, the Main thread only access @@ -122,6 +115,19 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)synchronouslyWaitForStage:(RCTSurfaceStage)stage timeout:(NSTimeInterval)timeout; +#pragma mark - Mounting/Unmounting of React components + +/** + * Mount the React component specified by the given moduleName. This is typically + * calling runApplication.js from the native side. + */ +- (void)mountReactComponentWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName params:(NSDictionary *)params; + +/** + * Unmount the React component specified by the given rootViewTag, called from native. + */ +- (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag; + @end NS_ASSUME_NONNULL_END diff --git a/React/Base/Surface/RCTSurface.mm b/React/Base/Surface/RCTSurface.mm index eac4985e04c3..30110ceae42e 100644 --- a/React/Base/Surface/RCTSurface.mm +++ b/React/Base/Surface/RCTSurface.mm @@ -61,12 +61,10 @@ @implementation RCTSurface { - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric { RCTAssert(bridge.valid, @"Valid bridge is required to instanciate `RCTSurface`."); if (self = [super init]) { - _fabric = fabric; _bridge = bridge; _batchedBridge = [_bridge batchedBridge] ?: _bridge; _moduleName = moduleName; @@ -105,13 +103,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge return self; } -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:NO]; -} - - (void)dealloc { [self _stop]; @@ -195,6 +186,10 @@ - (void)handleBridgeWillLoadJavaScriptNotification:(NSNotification *)notificatio { RCTAssertMainQueue(); + // Reset states because the bridge is reloading. This is similar to initialization phase. + _stage = RCTSurfaceStageSurfaceDidInitialize; + _view = nil; + _touchHandler = nil; [self _setStage:RCTSurfaceStageBridgeDidLoad]; } @@ -218,6 +213,7 @@ - (void)handleBridgeDidLoadJavaScriptNotification:(NSNotification *)notification } if (isRerunNeeded) { + [self _registerRootView]; [self _run]; } } @@ -310,21 +306,14 @@ - (void)_run RCTLogInfo(@"Running surface %@ (%@)", _moduleName, applicationParameters); - [batchedBridge enqueueJSCall:@"AppRegistry" - method:@"runApplication" - args:@[_moduleName, applicationParameters] - completion:NULL]; + [self mountReactComponentWithBridge:batchedBridge moduleName:_moduleName params:applicationParameters]; [self _setStage:RCTSurfaceStageSurfaceDidRun]; } - (void)_stop { - RCTBridge *batchedBridge = self._batchedBridge; - [batchedBridge enqueueJSCall:@"AppRegistry" - method:@"unmountApplicationComponentAtRootTag" - args:@[self->_rootViewTag] - completion:NULL]; + [self unmountReactComponentWithBridge:self._batchedBridge rootViewTag:self->_rootViewTag]; } - (void)_registerRootView @@ -571,4 +560,16 @@ - (void)uiManagerDidPerformMounting:(RCTUIManager *)manager } } +#pragma mark - Mounting/Unmounting of React components + +- (void)mountReactComponentWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName params:(NSDictionary *)params +{ + [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[moduleName, params] completion:NULL]; +} + +- (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag +{ + [bridge enqueueJSCall:@"AppRegistry" method:@"unmountApplicationComponentAtRootTag" args:@[rootViewTag] completion:NULL]; +} + @end diff --git a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.h b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.h index b761cc33bde8..6e07b1ac0a8f 100644 --- a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.h +++ b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.h @@ -35,28 +35,16 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL passThroughTouches; @property (nonatomic, assign) NSTimeInterval loadingViewFadeDelay; @property (nonatomic, assign) NSTimeInterval loadingViewFadeDuration; -@property (nonatomic, assign) BOOL fabric; - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties; + initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER; - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions; -- (instancetype)initWithBundleURL:(NSURL *)bundleURL - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - launchOptions:(NSDictionary *)launchOptions - fabric:(BOOL)fabric; - - (void)cancelTouches; @end diff --git a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm index 114b1f3981bf..4fc100f6b6c1 100644 --- a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm +++ b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingProxyRootView.mm @@ -51,7 +51,6 @@ @implementation RCTSurfaceHostingProxyRootView - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric { RCTAssertMainQueue(); RCTAssert(bridge, @"A bridge instance is required to create an RCTSurfaceHostingProxyRootView"); @@ -62,7 +61,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge [bridge.performanceLogger markStartForTag:RCTPLTTI]; } - if (self = [super initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:fabric]) { + if (self = [super initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]) { self.backgroundColor = [UIColor whiteColor]; } @@ -71,32 +70,16 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge return self; } -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:NO]; -} - - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions - fabric:(BOOL)fabric { RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions]; - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:fabric]; -} - -- (instancetype)initWithBundleURL:(NSURL *)bundleURL - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - launchOptions:(NSDictionary *)launchOptions -{ - return [self initWithBundleURL:bundleURL moduleName:moduleName initialProperties:initialProperties launchOptions:launchOptions fabric:NO]; + return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) @@ -114,11 +97,6 @@ - (RCTBridge *)bridge return super.surface.bridge; } -- (BOOL)fabric -{ - return super.surface.fabric; -} - - (UIView *)contentView { return self; diff --git a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h index 6f6ffe50f599..4434df84e005 100644 --- a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h +++ b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h @@ -43,13 +43,11 @@ NS_ASSUME_NONNULL_BEGIN initialProperties:(NSDictionary *)initialProperties; /** - * Convenience initializer. - * To control toggle Fabric for the Surface. + * Create an instance of RCTSurface to be hosted. */ -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric; +- (RCTSurface *)createSurfaceWithBridge:(RCTBridge *)bridge + moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties; /** * Surface object which is currently using to power the view. diff --git a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm index de4f5ec97332..3c3ab2bf7fa8 100644 --- a/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm +++ b/React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm @@ -33,23 +33,12 @@ @implementation RCTSurfaceHostingView { - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties - fabric:(BOOL)fabric { - RCTSurface *surface = [[RCTSurface alloc] initWithBridge:bridge - moduleName:moduleName - initialProperties:initialProperties - fabric:fabric]; + RCTSurface *surface = [self createSurfaceWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; return [self initWithSurface:surface]; } -- (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName - initialProperties:(NSDictionary *)initialProperties -{ - return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties fabric:NO]; -} - - (instancetype)initWithSurface:(RCTSurface *)surface { if (self = [super initWithFrame:CGRectZero]) { @@ -67,6 +56,13 @@ - (instancetype)initWithSurface:(RCTSurface *)surface return self; } +- (RCTSurface *)createSurfaceWithBridge:(RCTBridge *)bridge + moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties +{ + return [[RCTSurface alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; +} + - (void)setFrame:(CGRect)frame { [super setFrame:frame]; diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 1da12b8a217e..7411dcdcc613 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -1162,6 +1162,9 @@ - (void)executeApplicationScript:(NSData *)script { [self _tryAndHandleError:^{ NSString *sourceUrlStr = deriveSourceURL(url); + [[NSNotificationCenter defaultCenter] + postNotificationName:RCTJavaScriptWillStartExecutingNotification + object:self->_parentBridge userInfo:@{@"bridge": self}]; if (isRAMBundle(script)) { [self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad]; auto ramBundle = std::make_unique(sourceUrlStr.UTF8String); @@ -1183,53 +1186,6 @@ - (void)executeApplicationScript:(NSData *)script }]; } -- (JSValue *)callFunctionOnModule:(NSString *)module - method:(NSString *)method - arguments:(NSArray *)arguments - error:(NSError **)error -{ - if (!_reactInstance) { - if (error) { - *error = RCTErrorWithMessage( - @"callFunctionOnModule was called on uninitialized bridge"); - } - return nil; - } else if (self.executorClass) { - if (error) { - *error = RCTErrorWithMessage( - @"callFunctionOnModule can only be used with JSC executor"); - } - return nil; - } else if (!self.valid) { - if (error) { - *error = RCTErrorWithMessage( - @"Bridge is no longer valid"); - } - return nil; - } else if (self.loading) { - if (error) { - *error = RCTErrorWithMessage( - @"Bridge is still loading"); - } - return nil; - } - - RCT_PROFILE_BEGIN_EVENT(0, @"callFunctionOnModule", (@{ @"module": module, @"method": method })); - __block JSValue *ret = nil; - NSError *errorObj = tryAndReturnError(^{ - Value result = self->_reactInstance->callFunctionSync([module UTF8String], [method UTF8String], (id)arguments); - JSContext *context = contextForGlobalContextRef(JSC_JSContextGetGlobalContext(result.context())); - ret = [JSC_JSValue(result.context()) valueWithJSValueRef:result inContext:context]; - }); - RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call"); - - if (error) { - *error = errorObj; - } - - return ret; -} - - (void)registerSegmentWithId:(NSUInteger)segmentId path:(NSString *)path { if (_reactInstance) { diff --git a/React/Fabric/RCTFabricPlatformUIOperationManager.h b/React/Fabric/RCTFabricPlatformUIOperationManager.h new file mode 100644 index 000000000000..2e9df3c77044 --- /dev/null +++ b/React/Fabric/RCTFabricPlatformUIOperationManager.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import + +#import + +#include + +@class RCTFabricPlatformUIOperationManager; + +namespace facebook { +namespace react { + +/** + * Connector class (from C++ to ObjC++) to allow FabricUIManager to invoke native UI operations/updates. + * UIKit-related impl doesn't live here, but this class gets passed to the FabricUIManager C++ impl directly. + */ +class RCTFabricPlatformUIOperationManagerConnector : public IFabricPlatformUIOperationManager { +public: + RCTFabricPlatformUIOperationManagerConnector(); + ~RCTFabricPlatformUIOperationManagerConnector(); + + void performUIOperation(); + +private: + void *self_; + RCTFabricPlatformUIOperationManager *manager_; +}; + +} // namespace react +} // namespace facebook + +/** + * Actual ObjC++ implementation of the UI operations. + */ +@interface RCTFabricPlatformUIOperationManager : NSObject + +- (void)performUIOperation; + +@end diff --git a/React/Fabric/RCTFabricPlatformUIOperationManager.mm b/React/Fabric/RCTFabricPlatformUIOperationManager.mm new file mode 100644 index 000000000000..3bccc58e0516 --- /dev/null +++ b/React/Fabric/RCTFabricPlatformUIOperationManager.mm @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTFabricPlatformUIOperationManager.h" + +namespace facebook { +namespace react { + +RCTFabricPlatformUIOperationManagerConnector::RCTFabricPlatformUIOperationManagerConnector() { + self_ = (__bridge_retained void *)[RCTFabricPlatformUIOperationManager new]; + manager_ = (__bridge RCTFabricPlatformUIOperationManager *)self_; +} + +RCTFabricPlatformUIOperationManagerConnector::~RCTFabricPlatformUIOperationManagerConnector() { + CFRelease(self_); + self_ = NULL; + manager_ = NULL; +} + +void RCTFabricPlatformUIOperationManagerConnector::performUIOperation() { + [manager_ performUIOperation]; +} + +} // namespace react +} // namespace facebook + +// ----------------------------------------------------------------------------- +// Start of ObjC++ impl +// Access UIKit here. +// ----------------------------------------------------------------------------- +@implementation RCTFabricPlatformUIOperationManager + +- (void)dealloc +{ + NSLog(@"RCTFabricPlatformUIOperationManager: dealloc()"); +} + +- (void)performUIOperation +{ + // TODO + NSLog(@"RCTFabricPlatformUIOperationManager: performUIOperation()"); +} + +@end diff --git a/React/Fabric/RCTFabricUIManagerWrapper.h b/React/Fabric/RCTFabricUIManagerWrapper.h index 1db660fcf380..d40ef78c359c 100644 --- a/React/Fabric/RCTFabricUIManagerWrapper.h +++ b/React/Fabric/RCTFabricUIManagerWrapper.h @@ -18,8 +18,8 @@ namespace react { class FabricUIManager; -} -} +} // namespace react +} // namespace facebook using namespace facebook::react; @@ -28,7 +28,6 @@ using namespace facebook::react; */ @interface RCTFabricUIManagerWrapper : NSObject -- (instancetype)initWithManager:(std::shared_ptr)manager; - (std::shared_ptr)manager; @end diff --git a/React/Fabric/RCTFabricUIManagerWrapper.mm b/React/Fabric/RCTFabricUIManagerWrapper.mm index e1fc129d8a7b..892669145d0a 100644 --- a/React/Fabric/RCTFabricUIManagerWrapper.mm +++ b/React/Fabric/RCTFabricUIManagerWrapper.mm @@ -9,17 +9,21 @@ #include +#import "RCTFabricPlatformUIOperationManager.h" + // This file contains experimental placeholders, nothing is finalized. @implementation RCTFabricUIManagerWrapper { std::shared_ptr _manager; + std::shared_ptr _platformUIOperationManager; } -- (instancetype)initWithManager:(std::shared_ptr)manager +- (instancetype)init { self = [super init]; if (self) { - _manager = manager; + _platformUIOperationManager = std::make_shared(); + _manager = std::make_shared(_platformUIOperationManager); } return self; } diff --git a/React/Fabric/Surface/RCTFabricSurface.h b/React/Fabric/Surface/RCTFabricSurface.h new file mode 100644 index 000000000000..1672a9a86ac6 --- /dev/null +++ b/React/Fabric/Surface/RCTFabricSurface.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +/** + * Fabric-compatible RCTSurface implementation. + * It may not continue extending from RCTSurface in the future. + */ +@interface RCTFabricSurface : RCTSurface + +@end diff --git a/React/Fabric/Surface/RCTFabricSurface.mm b/React/Fabric/Surface/RCTFabricSurface.mm new file mode 100644 index 000000000000..bcdaac13129e --- /dev/null +++ b/React/Fabric/Surface/RCTFabricSurface.mm @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTFabricSurface.h" + +#import + +@implementation RCTFabricSurface + +- (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag +{ + [bridge enqueueJSCall:@"ReactFabric" + method:@"unmountComponentAtNodeAndRemoveContainer" + args:@[rootViewTag] + completion:NULL]; +} + +@end diff --git a/React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.h b/React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.h new file mode 100644 index 000000000000..3214426e77d6 --- /dev/null +++ b/React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +/** + * Fabric-compatible RCTSurfaceHostingProxyRootView implementation. + */ +@interface RCTFabricSurfaceHostingProxyRootView : RCTSurfaceHostingProxyRootView + +@end + diff --git a/React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.mm b/React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.mm new file mode 100644 index 000000000000..159a63c865e4 --- /dev/null +++ b/React/Fabric/Surface/RCTFabricSurfaceHostingProxyRootView.mm @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTFabricSurfaceHostingProxyRootView.h" + +#import "RCTFabricSurface.h" + +@implementation RCTFabricSurfaceHostingProxyRootView + +- (RCTSurface *)createSurfaceWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties +{ + return [[RCTFabricSurface alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; +} + +@end diff --git a/Libraries/Components/View/PlatformViewPropTypes.android.js b/React/Fabric/Surface/RCTFabricSurfaceHostingView.h similarity index 50% rename from Libraries/Components/View/PlatformViewPropTypes.android.js rename to React/Fabric/Surface/RCTFabricSurfaceHostingView.h index 64425f941109..c50fa51acee9 100644 --- a/Libraries/Components/View/PlatformViewPropTypes.android.js +++ b/React/Fabric/Surface/RCTFabricSurfaceHostingView.h @@ -3,9 +3,14 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @providesModule PlatformViewPropTypes - * @flow */ -module.export = {}; +#import + +/** + * Fabric-compatible RCTSurfaceHostingView implementation. + */ +@interface RCTFabricSurfaceHostingView : RCTSurfaceHostingView + +@end + diff --git a/React/Fabric/Surface/RCTFabricSurfaceHostingView.mm b/React/Fabric/Surface/RCTFabricSurfaceHostingView.mm new file mode 100644 index 000000000000..395d40afb68e --- /dev/null +++ b/React/Fabric/Surface/RCTFabricSurfaceHostingView.mm @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTFabricSurfaceHostingView.h" + +#import "RCTFabricSurface.h" + +@implementation RCTFabricSurfaceHostingView + +- (instancetype)initWithBridge:(RCTBridge *)bridge + moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties +{ + RCTFabricSurface *surface = [[RCTFabricSurface alloc] initWithBridge:bridge + moduleName:moduleName + initialProperties:initialProperties]; + return [self initWithSurface:surface]; +} + +@end + diff --git a/React/Modules/RCTTVNavigationEventEmitter.m b/React/Modules/RCTTVNavigationEventEmitter.m index 7d6774b012dd..3ebe46381d18 100644 --- a/React/Modules/RCTTVNavigationEventEmitter.m +++ b/React/Modules/RCTTVNavigationEventEmitter.m @@ -9,7 +9,7 @@ NSString *const RCTTVNavigationEventNotification = @"RCTTVNavigationEventNotification"; -static NSString *const TVNavigationEventName = @"onTVNavEvent"; +static NSString *const TVNavigationEventName = @"onHWKeyEvent"; @implementation RCTTVNavigationEventEmitter diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java index 31947b068277..e08878d3c780 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java @@ -7,6 +7,7 @@ package com.facebook.react.testing; +import com.facebook.react.modules.core.ReactChoreographer; import javax.annotation.Nullable; import java.util.concurrent.CountDownLatch; @@ -139,6 +140,7 @@ protected Timing createTimingModule() { new Runnable() { @Override public void run() { + ReactChoreographer.initialize(); Timing timing = new Timing(getContext(), mock(DevSupportManager.class)); simpleSettableFuture.set(timing); } diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java index ffaeb08148cb..00dee97df81c 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java @@ -98,7 +98,7 @@ public void run() { mRecordingTestModule = new RecordingTestModule(); mCatalystInstance = ReactTestHelper.catalystInstanceBuilder(this) .addNativeModule(mRecordingTestModule) - .addNativeModule(new AndroidInfoModule()) + .addNativeModule(new AndroidInfoModule(getContext())) .addNativeModule(new DeviceInfoModule(getContext())) .addNativeModule(new AppStateModule(getContext())) .addNativeModule(new FakeWebSocketModule()) diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java index d753d03f96de..4d58d579139d 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystUIManagerTestCase.java @@ -88,7 +88,7 @@ public void run() { jsModule = ReactTestHelper.catalystInstanceBuilder(this) .addNativeModule(uiManager) - .addNativeModule(new AndroidInfoModule()) + .addNativeModule(new AndroidInfoModule(getContext())) .addNativeModule(new DeviceInfoModule(getContext())) .addNativeModule(new AppStateModule(getContext())) .addNativeModule(new FakeWebSocketModule()) diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ProgressBarTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ProgressBarTestCase.java index 38ed87b5d8c3..70dd287164bd 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ProgressBarTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ProgressBarTestCase.java @@ -80,7 +80,7 @@ public void run() { mInstance = ReactTestHelper.catalystInstanceBuilder(this) .addNativeModule(mUIManager) - .addNativeModule(new AndroidInfoModule()) + .addNativeModule(new AndroidInfoModule(getContext())) .addNativeModule(new DeviceInfoModule(getContext())) .addNativeModule(new AppStateModule(getContext())) .addNativeModule(new FakeWebSocketModule()) diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ViewRenderingTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ViewRenderingTestCase.java index 11a2328021db..73412bc21239 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ViewRenderingTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ViewRenderingTestCase.java @@ -61,7 +61,7 @@ public void run() { mCatalystInstance = ReactTestHelper.catalystInstanceBuilder(this) .addNativeModule(uiManager) - .addNativeModule(new AndroidInfoModule()) + .addNativeModule(new AndroidInfoModule(getContext())) .addNativeModule(new DeviceInfoModule(getContext())) .addNativeModule(new AppStateModule(getContext())) .addNativeModule(new FakeWebSocketModule()) diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index ecda016133b2..c6a778a6745e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -79,7 +79,7 @@ public List getNativeModules(final ReactApplicationContext reactCont new Provider() { @Override public NativeModule get() { - return new AndroidInfoModule(); + return new AndroidInfoModule(reactContext); } }), ModuleSpec.nativeModuleSpec( diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index 3a8ca14b3ce8..bbe9cad44738 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -75,11 +75,21 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { mDelegate.onActivityResult(requestCode, resultCode, data); } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); } + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return mDelegate.onKeyLongPress(keyCode, event) || super.onKeyLongPress(keyCode, event); + } + @Override public void onBackPressed() { if (!mDelegate.onBackPressed()) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index bee643c9bbc1..03141fb00b07 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -127,6 +127,16 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (getReactNativeHost().hasInstance() + && getReactNativeHost().getUseDeveloperSupport() + && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { + event.startTracking(); + return true; + } + return false; + } + public boolean onKeyUp(int keyCode, KeyEvent event) { if (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) { if (keyCode == KeyEvent.KEYCODE_MENU) { @@ -143,6 +153,16 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { return false; } + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (getReactNativeHost().hasInstance() + && getReactNativeHost().getUseDeveloperSupport() + && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { + getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); + return true; + } + return false; + } + public boolean onBackPressed() { if (getReactNativeHost().hasInstance()) { getReactNativeHost().getReactInstanceManager().onBackPressed(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.java b/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.java new file mode 100644 index 000000000000..8c3df938340e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react; + +import android.view.KeyEvent; +import android.view.View; + +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.common.MapBuilder; + +import java.util.Map; + +/** + * Responsible for dispatching events specific for hardware inputs. + */ +public class ReactAndroidHWInputDeviceHelper { + + /** + * Contains a mapping between handled KeyEvents and the corresponding navigation event + * that should be fired when the KeyEvent is received. + */ + private static final Map KEY_EVENTS_ACTIONS = MapBuilder.of( + KeyEvent.KEYCODE_DPAD_CENTER, + "select", + KeyEvent.KEYCODE_ENTER, + "select", + KeyEvent.KEYCODE_SPACE, + "select", + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, + "playPause", + KeyEvent.KEYCODE_MEDIA_REWIND, + "rewind", + KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, + "fastForward" + ); + + /** + * We keep a reference to the last focused view id + * so that we can send it as a target for key events + * and be able to send a blur event when focus changes. + */ + private int mLastFocusedViewId = View.NO_ID; + + private final ReactRootView mReactRootView; + + ReactAndroidHWInputDeviceHelper(ReactRootView mReactRootView) { + this.mReactRootView = mReactRootView; + } + + /** + * Called from {@link ReactRootView}. + * This is the main place the key events are handled. + */ + public void handleKeyEvent(KeyEvent ev) { + int eventKeyCode = ev.getKeyCode(); + int eventKeyAction = ev.getAction(); + if (eventKeyAction == KeyEvent.ACTION_UP && KEY_EVENTS_ACTIONS.containsKey(eventKeyCode)) { + dispatchEvent(KEY_EVENTS_ACTIONS.get(eventKeyCode), mLastFocusedViewId); + } + } + + /** + * Called from {@link ReactRootView} when focused view changes. + */ + public void onFocusChanged(View newFocusedView) { + if (mLastFocusedViewId == newFocusedView.getId()) { + return; + } + if (mLastFocusedViewId != View.NO_ID) { + dispatchEvent("blur", mLastFocusedViewId); + } + mLastFocusedViewId = newFocusedView.getId(); + dispatchEvent("focus", newFocusedView.getId()); + } + + /** + * Called from {@link ReactRootView} when the whole view hierarchy looses focus. + */ + public void clearFocus() { + if (mLastFocusedViewId != View.NO_ID) { + dispatchEvent("blur", mLastFocusedViewId); + } + mLastFocusedViewId = View.NO_ID; + } + + private void dispatchEvent(String eventType, int targetViewId) { + WritableMap event = new WritableNativeMap(); + event.putString("eventType", eventType); + if (targetViewId != View.NO_ID) { + event.putInt("tag", targetViewId); + } + mReactRootView.sendEvent("onHWKeyEvent", event); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 1ecbd0efd45c..1d2731a71c85 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -43,10 +43,10 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.infer.annotation.ThreadSafe; -import com.facebook.react.bridge.BridgeListener; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.CatalystInstanceImpl; import com.facebook.react.bridge.JSBundleLoader; +import com.facebook.react.bridge.JSIModulesProvider; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptExecutor; import com.facebook.react.bridge.JavaScriptExecutorFactory; @@ -71,11 +71,11 @@ import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; import com.facebook.react.modules.appregistry.AppRegistry; -import com.facebook.react.modules.fabric.ReactFabric; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.ReactChoreographer; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; +import com.facebook.react.modules.fabric.ReactFabric; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; @@ -157,7 +157,7 @@ public interface ReactInstanceEventListener { private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final boolean mLazyNativeModulesEnabled; private final boolean mDelayViewManagerClassLoadsEnabled; - private final @Nullable BridgeListener mBridgeListener; + private final @Nullable JSIModulesProvider mJSIModulesProvider; private List mViewManagers; private class ReactContextInitParams { @@ -207,7 +207,7 @@ public static ReactInstanceManagerBuilder builder() { @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes, int minTimeLeftInFrameForNonBatchedOperationMs, - @Nullable BridgeListener bridgeListener) { + @Nullable JSIModulesProvider jsiModulesProvider) { Log.d(ReactConstants.TAG, "ReactInstanceManager.ctor()"); initializeSoLoaderIfNecessary(applicationContext); @@ -256,7 +256,7 @@ public void invokeDefaultOnBackPressed() { } mPackages.addAll(packages); } - mBridgeListener = bridgeListener; + mJSIModulesProvider = jsiModulesProvider; // Instantiate ReactChoreographer in UI thread. ReactChoreographer.initialize(); @@ -1008,7 +1008,7 @@ private void attachRootViewToInstance( CatalystInstance catalystInstance) { Log.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()"); Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance"); - UIManager uiManagerModule = rootView.isFabric() ? catalystInstance.getFabricUIManager() : catalystInstance.getNativeModule(UIManagerModule.class); + UIManager uiManagerModule = rootView.isFabric() ? catalystInstance.getJSIModule(UIManager.class) : catalystInstance.getNativeModule(UIManagerModule.class); final int rootTag = uiManagerModule.addRootView(rootView); rootView.setRootViewTag(rootTag); rootView.invokeJSEntryPoint(); @@ -1099,8 +1099,9 @@ private ReactApplicationContext createReactContext( Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END); } - if (mBridgeListener != null) { - mBridgeListener.onBridgeStarted(reactContext, catalystInstance); + if (mJSIModulesProvider != null) { + catalystInstance.addJSIModules(mJSIModulesProvider + .getJSIModules(reactContext, catalystInstance.getJavaScriptContextHolder())); } if (mBridgeIdleDebugListener != null) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index 5f9042093ab9..75792d83db3b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -7,8 +7,7 @@ import android.app.Activity; import android.app.Application; import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.BridgeListener; -import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.JSIModulesProvider; import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JSCJavaScriptExecutorFactory; import com.facebook.react.bridge.JavaScriptExecutorFactory; @@ -50,7 +49,7 @@ public class ReactInstanceManagerBuilder { private @Nullable JavaScriptExecutorFactory mJavaScriptExecutorFactory; private int mMinNumShakes = 1; private int mMinTimeLeftInFrameForNonBatchedOperationMs = -1; - private @Nullable BridgeListener mBridgeListener; + private @Nullable JSIModulesProvider mJSIModulesProvider; /* package protected */ ReactInstanceManagerBuilder() { } @@ -65,9 +64,9 @@ public ReactInstanceManagerBuilder setUIImplementationProvider( return this; } - public ReactInstanceManagerBuilder setBridgeListener( - @Nullable BridgeListener listener) { - mBridgeListener = listener; + public ReactInstanceManagerBuilder setJSIModulesProvider( + @Nullable JSIModulesProvider jsiModulesProvider) { + mJSIModulesProvider = jsiModulesProvider; return this; } @@ -290,6 +289,6 @@ public ReactInstanceManager build() { mDevBundleDownloadListener, mMinNumShakes, mMinTimeLeftInFrameForNonBatchedOperationMs, - mBridgeListener); + mJSIModulesProvider); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index 243053fcfe68..e1537d550f36 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -7,6 +7,7 @@ package com.facebook.react; +import com.facebook.react.bridge.JSIModulesProvider; import javax.annotation.Nullable; import java.util.List; @@ -14,7 +15,7 @@ import android.app.Application; import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.BridgeListener; +import com.facebook.react.bridge.JSIModulesProvider; import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; @@ -75,7 +76,7 @@ protected ReactInstanceManager createReactInstanceManager() { .setRedBoxHandler(getRedBoxHandler()) .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory()) .setUIImplementationProvider(getUIImplementationProvider()) - .setBridgeListener(getBridgeListener()) + .setJSIModulesProvider(getJSIModulesProvider()) .setInitialLifecycleState(LifecycleState.BEFORE_CREATE); for (ReactPackage reactPackage : getPackages()) { @@ -122,7 +123,8 @@ protected UIImplementationProvider getUIImplementationProvider() { return new UIImplementationProvider(); } - protected @Nullable BridgeListener getBridgeListener() { + protected @Nullable + JSIModulesProvider getJSIModulesProvider() { return null; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 1bed2f5bb3b9..5d4a88bc5025 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -16,6 +16,7 @@ import android.os.Bundle; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -84,6 +85,7 @@ public interface ReactRootViewEventListener { private boolean mIsAttachedToInstance; private boolean mShouldLogContentAppeared; private final JSTouchDispatcher mJSTouchDispatcher = new JSTouchDispatcher(this); + private final ReactAndroidHWInputDeviceHelper mAndroidHWInputDeviceHelper = new ReactAndroidHWInputDeviceHelper(this); private boolean mWasMeasured = false; private int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); private int mHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); @@ -213,6 +215,47 @@ protected void dispatchDraw(Canvas canvas) { } } + @Override + public boolean dispatchKeyEvent(KeyEvent ev) { + if (mReactInstanceManager == null || !mIsAttachedToInstance || + mReactInstanceManager.getCurrentReactContext() == null) { + FLog.w( + ReactConstants.TAG, + "Unable to handle key event as the catalyst instance has not been attached"); + return super.dispatchKeyEvent(ev); + } + mAndroidHWInputDeviceHelper.handleKeyEvent(ev); + return super.dispatchKeyEvent(ev); + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + if (mReactInstanceManager == null || !mIsAttachedToInstance || + mReactInstanceManager.getCurrentReactContext() == null) { + FLog.w( + ReactConstants.TAG, + "Unable to handle focus changed event as the catalyst instance has not been attached"); + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + return; + } + mAndroidHWInputDeviceHelper.clearFocus(); + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + } + + @Override + public void requestChildFocus(View child, View focused) { + if (mReactInstanceManager == null || !mIsAttachedToInstance || + mReactInstanceManager.getCurrentReactContext() == null) { + FLog.w( + ReactConstants.TAG, + "Unable to handle child focus changed event as the catalyst instance has not been attached"); + super.requestChildFocus(child, focused); + return; + } + mAndroidHWInputDeviceHelper.onFocusChanged(focused); + super.requestChildFocus(child, focused); + } + private void dispatchJSTouchEvent(MotionEvent event) { if (mReactInstanceManager == null || !mIsAttachedToInstance || mReactInstanceManager.getCurrentReactContext() == null) { @@ -536,6 +579,14 @@ public boolean isFabric() { public ReactInstanceManager getReactInstanceManager() { return mReactInstanceManager; } + + /* package */ void sendEvent(String eventName, @Nullable WritableMap params) { + if (mReactInstanceManager != null) { + mReactInstanceManager.getCurrentReactContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } + } private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { private final Rect mVisibleViewArea; @@ -665,13 +716,5 @@ private void emitUpdateDimensionsEvent() { .getNativeModule(DeviceInfoModule.class) .emitUpdateDimensionsEvent(); } - - private void sendEvent(String eventName, @Nullable WritableMap params) { - if (mReactInstanceManager != null) { - mReactInstanceManager.getCurrentReactContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); - } - } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeListener.java deleted file mode 100644 index 70b771ff558e..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeListener.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.facebook.react.bridge; - -/** - * Interface to listen to bridge events. - */ -public interface BridgeListener { - - /** - * Called right after the RN Bridge is initialized - * @param catalystInstance {@link CatalystInstance} bridge - */ - void onBridgeStarted(ReactApplicationContext reactApplicationContext, CatalystInstance catalystInstance); - -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index 1f277e6ec384..fe6d46112266 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -11,6 +11,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.annotations.VisibleForTesting; import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; /** @@ -62,6 +63,7 @@ void callFunction( T getJSModule(Class jsInterface); boolean hasNativeModule(Class nativeModuleInterface); T getNativeModule(Class nativeModuleInterface); + T getJSIModule(Class jsiModuleInterface); Collection getNativeModules(); /** @@ -99,7 +101,5 @@ void callFunction( */ JavaScriptContextHolder getJavaScriptContextHolder(); - void setFabricUIManager(T fabricUIManager); - - T getFabricUIManager(); + void addJSIModules(List jsiModules); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 79233aae2e32..7ab1216267db 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -26,6 +26,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; @@ -79,13 +80,13 @@ public String toString() { private final Object mJSCallsPendingInitLock = new Object(); private final NativeModuleRegistry mNativeModuleRegistry; + private final JSIModuleRegistry mJSIModuleRegistry = new JSIModuleRegistry(); private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; private final MessageQueueThread mNativeModulesQueueThread; private boolean mInitialized = false; private volatile boolean mAcceptCalls = false; private boolean mJSBundleHasLoaded; - private UIManager mFabricUIManager; private @Nullable String mSourceURL; private JavaScriptContextHolder mJavaScriptContextHolder; @@ -453,12 +454,14 @@ public JavaScriptContextHolder getJavaScriptContextHolder() { return mJavaScriptContextHolder; } - public UIManager getFabricUIManager() { - return mFabricUIManager; + @Override + public void addJSIModules(List jsiModules) { + mJSIModuleRegistry.registerModules(jsiModules); } - public void setFabricUIManager(UIManager fabricUIManager) { - mFabricUIManager = fabricUIManager; + @Override + public T getJSIModule(Class jsiModuleInterface) { + return mJSIModuleRegistry.getModule(jsiModuleInterface); } private native long getJavaScriptContext(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModule.java new file mode 100644 index 000000000000..5b9e2495449d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModule.java @@ -0,0 +1,7 @@ +package com.facebook.react.bridge; + +/** + * Marker interface used to represent a JSI Module. + */ +public interface JSIModule { +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleHolder.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleHolder.java new file mode 100644 index 000000000000..de98ae54b51c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleHolder.java @@ -0,0 +1,12 @@ +package com.facebook.react.bridge; + +/** + * Holder class used to register {@link JSIModule} into JSI Bridge. + */ +public interface JSIModuleHolder { + + Class getJSIModuleClass(); + + T getJSIModule(); + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java new file mode 100644 index 000000000000..913aae68bcbf --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java @@ -0,0 +1,23 @@ +package com.facebook.react.bridge; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.facebook.infer.annotation.Assertions; + +public class JSIModuleRegistry { + + private final Map mModules = new HashMap<>(); + + public JSIModuleRegistry() { } + + public T getModule(Class moduleClass) { + return (T) Assertions.assertNotNull(mModules.get(moduleClass)); + } + + public void registerModules(List jsiModules) { + for (JSIModuleHolder holder : jsiModules) { + mModules.put(holder.getJSIModuleClass(), holder.getJSIModule()); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModulesProvider.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModulesProvider.java new file mode 100644 index 000000000000..8abaa1ff79e2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModulesProvider.java @@ -0,0 +1,15 @@ +package com.facebook.react.bridge; + +import java.util.List; + +/** + * Interface used to initialize JSI Modules into the JSI Bridge. + */ +public interface JSIModulesProvider { + + /** + * @return a {@link List} that contain the list of JSI Modules. + */ + List getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext); + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java index 62be17622de3..bce96a33921d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java @@ -107,8 +107,8 @@ public String getString(String name) { } @Override - public JavaOnlyMap getMap(String name) { - return (JavaOnlyMap) mBackingMap.get(name); + public ReadableMap getMap(String name) { + return (ReadableMap) mBackingMap.get(name); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java index ff3e93ca1d5b..6cbca5de698f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.java @@ -3,7 +3,7 @@ import com.facebook.react.uimanager.common.MeasureSpecProvider; import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout; -public interface UIManager { +public interface UIManager extends JSIModule { /** * Registers a new root view. diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index ed8369cba76b..2fa9557f5293 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.UIManager; import com.facebook.react.modules.i18nmanager.I18nUtil; +import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.NativeViewHierarchyManager; import com.facebook.react.uimanager.ReactRootViewTagGenerator; import com.facebook.react.uimanager.ReactShadowNode; @@ -46,6 +47,7 @@ public class FabricUIManager implements UIManager { public FabricUIManager(ReactApplicationContext reactContext, ViewManagerRegistry viewManagerRegistry) { + DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); mReactApplicationContext = reactContext; mViewManagerRegistry = viewManagerRegistry; mUIViewOperationQueue = new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(viewManagerRegistry), 0); @@ -64,16 +66,19 @@ public ReactShadowNode createNode(int reactTag, ReactShadowNode node = viewManager.createShadowNodeInstance(mReactApplicationContext); ReactShadowNode rootNode = getRootNode(rootTag); node.setRootNode(rootNode); + node.setViewClassName(viewName); node.setReactTag(reactTag); node.setThemedContext(rootNode.getThemedContext()); ReactStylesDiffMap styles = updateProps(node, props); - mUIViewOperationQueue - .enqueueCreateView(rootNode.getThemedContext(), reactTag, viewName, styles); + if (!node.isVirtual()) { + mUIViewOperationQueue + .enqueueCreateView(rootNode.getThemedContext(), reactTag, viewName, styles); + } return node; - } catch (Exception e) { - handleException(rootTag, e); + } catch (Throwable t) { + handleException(getRootNode(rootTag), t); return null; } } @@ -102,8 +107,8 @@ public ReactShadowNode cloneNode(ReactShadowNode node) { ReactShadowNode clone = node.mutableCopy(); assertReactShadowNodeCopy(node, clone); return clone; - } catch (Exception e) { - handleException(node.getThemedContext(), e); + } catch (Throwable t) { + handleException(node, t); return null; } } @@ -119,8 +124,8 @@ public ReactShadowNode cloneNodeWithNewChildren(ReactShadowNode node) { ReactShadowNode clone = node.mutableCopyWithNewChildren(); assertReactShadowNodeCopy(node, clone); return clone; - } catch (Exception e) { - handleException(node.getThemedContext(), e); + } catch (Throwable t) { + handleException(node, t); return null; } } @@ -139,8 +144,8 @@ public ReactShadowNode cloneNodeWithNewProps( updateProps(clone, newProps); assertReactShadowNodeCopy(node, clone); return clone; - } catch (Exception e) { - handleException(node.getThemedContext(), e); + } catch (Throwable t) { + handleException(node, t); return null; } } @@ -160,9 +165,8 @@ public ReactShadowNode cloneNodeWithNewChildrenAndProps( updateProps(clone, newProps); assertReactShadowNodeCopy(node, clone); return clone; - } catch (Exception e) { - handleException(node.getThemedContext(), e); - getRootNode(1).getThemedContext().handleException(e); + } catch (Throwable t) { + handleException(node, t); return null; } } @@ -184,14 +188,16 @@ public void appendChild(ReactShadowNode parent, ReactShadowNode child) { parent.addChildAt(child, childIndex); ViewAtIndex[] viewsToAdd = new ViewAtIndex[]{new ViewAtIndex(child.getReactTag(), childIndex)}; - mUIViewOperationQueue.enqueueManageChildren( - parent.getReactTag(), - null, - viewsToAdd, - null - ); - } catch (Exception e) { - handleException(parent.getThemedContext(), e); + if (!child.isVirtual()) { + mUIViewOperationQueue.enqueueManageChildren( + parent.getReactTag(), + null, + viewsToAdd, + null + ); + } + } catch (Throwable t) { + handleException(parent, t); } } @@ -221,15 +227,26 @@ public void completeRoot(int rootTag, List childList) { appendChild(rootNode, child); } + notifyOnBeforeLayoutRecursive(rootNode); calculateRootLayout(rootNode); applyUpdatesRecursive(rootNode, 0, 0); mUIViewOperationQueue .dispatchViewUpdates(1, System.currentTimeMillis(), System.currentTimeMillis()); } catch (Exception e) { - handleException(rootTag, e); + handleException(getRootNode(rootTag), e); } } + private void notifyOnBeforeLayoutRecursive(ReactShadowNode node) { + if (!node.hasUpdates()) { + return; + } + for (int i = 0; i < node.getChildCount(); i++) { + notifyOnBeforeLayoutRecursive(node.getChildAt(i)); + } + node.onBeforeLayout(); + } + private void calculateRootLayout(ReactShadowNode cssRoot) { cssRoot.calculateLayout(); } @@ -331,16 +348,16 @@ public void updateRootView( } } - private void handleException(ThemedReactContext context, Exception e) { + private void handleException(ReactShadowNode node, Throwable t) { try { - context.handleException(e); + ThemedReactContext context = node.getThemedContext(); + // TODO move exception management to JNI side, and refactor to avoid wrapping Throwable into + // a RuntimeException + context.handleException(new RuntimeException(t)); } catch (Exception ex) { - Log.e(TAG, "Exception while executing a Fabric method", e); - throw new RuntimeException(ex.getMessage(), e); + Log.e(TAG, "Exception while executing a Fabric method", t); + throw new RuntimeException(ex.getMessage(), t); } } - private void handleException(int rootTag, Exception e) { - handleException(getRootNode(rootTag).getThemedContext(), e); - } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java index cc9c63490530..5534eb6449cf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java @@ -348,7 +348,7 @@ public void onProgress(long bytesWritten, long contentLength, boolean done) { } RequestBody requestBody; - if (data == null) { + if (data == null || method.toLowerCase().equals("get") || method.toLowerCase().equals("head")) { requestBody = RequestBodyUtil.getEmptyBody(method); } else if (handler != null) { requestBody = handler.toRequestBody(data, contentType); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java index 98ca742f7168..1037d4c5ecbd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java @@ -7,9 +7,12 @@ package com.facebook.react.modules.systeminfo; +import android.app.UiModeManager; +import android.content.res.Configuration; import android.os.Build; -import com.facebook.react.bridge.BaseJavaModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.module.annotations.ReactModule; import java.util.HashMap; @@ -17,14 +20,41 @@ import javax.annotation.Nullable; +import static android.content.Context.UI_MODE_SERVICE; + /** * Module that exposes Android Constants to JS. */ @ReactModule(name = "PlatformConstants") -public class AndroidInfoModule extends BaseJavaModule { +public class AndroidInfoModule extends ReactContextBaseJavaModule { private static final String IS_TESTING = "IS_TESTING"; + public AndroidInfoModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + /** + * See: https://developer.android.com/reference/android/app/UiModeManager.html#getCurrentModeType() + */ + private String uiMode() { + UiModeManager uiModeManager = (UiModeManager) getReactApplicationContext().getSystemService(UI_MODE_SERVICE); + switch (uiModeManager.getCurrentModeType()) { + case Configuration.UI_MODE_TYPE_TELEVISION: + return "tv"; + case Configuration.UI_MODE_TYPE_CAR: + return "car"; + case Configuration.UI_MODE_TYPE_DESK: + return "desk"; + case Configuration.UI_MODE_TYPE_WATCH: + return "watch"; + case Configuration.UI_MODE_TYPE_NORMAL: + return "normal"; + default: + return "unknown"; + } + } + @Override public String getName() { return "PlatformConstants"; @@ -41,6 +71,7 @@ public String getName() { constants.put("ServerHost", AndroidInfoHelpers.getServerHost()); constants.put("isTesting", "true".equals(System.getProperty(IS_TESTING))); constants.put("reactNativeVersion", ReactNativeVersion.VERSION); + constants.put("uiMode", uiMode()); return constants; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java index fe807cfc2c7e..5624324c04d2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -104,7 +104,7 @@ public ReactShadowNodeImpl(ReactShadowNodeImpl original) { mShouldNotifyOnLayout = original.mShouldNotifyOnLayout; mNodeUpdated = original.mNodeUpdated; mChildren = original.mChildren == null ? null : new ArrayList<>(original.mChildren); - mParent = original.mParent; + mParent = null; mIsLayoutOnly = original.mIsLayoutOnly; mTotalNativeChildren = original.mTotalNativeChildren; mNativeParent = original.mNativeParent; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 0cd8f4ccf58b..b4eb587049bd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -56,8 +56,16 @@ public void setAccessible(ReactViewGroup view, boolean accessible) { view.setFocusable(accessible); } - @ReactPropGroup( - names = { + @ReactProp(name = "hasTVPreferredFocus") + public void setTVPreferredFocus(ReactViewGroup view, boolean hasTVPreferredFocus) { + if (hasTVPreferredFocus) { + view.setFocusable(true); + view.setFocusableInTouchMode(true); + view.requestFocus(); + } + } + + @ReactPropGroup(names = { ViewProps.BORDER_RADIUS, ViewProps.BORDER_TOP_LEFT_RADIUS, ViewProps.BORDER_TOP_RIGHT_RADIUS, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java index 77e93269bdcf..48b5f1a61949 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java @@ -210,6 +210,26 @@ public void setScrollEnabled(boolean scrollEnabled) { mScrollEnabled = scrollEnabled; } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // The viewpager reset an internal flag on this method so we need to run another layout pass + // after attaching to window. + this.requestLayout(); + post(measureAndLayout); + } + + private final Runnable measureAndLayout = new Runnable() { + @Override + public void run() { + measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)); + layout(getLeft(), getTop(), getRight(), getBottom()); + } + }; + /*package*/ void addViewToAdapter(View child, int index) { getAdapter().addView(child, index); } diff --git a/ReactCommon/cxxreact/BUCK b/ReactCommon/cxxreact/BUCK index 24d1d421e6f6..5f84b3fd5a24 100644 --- a/ReactCommon/cxxreact/BUCK +++ b/ReactCommon/cxxreact/BUCK @@ -1,4 +1,4 @@ -load("@xplat//configurations/buck/apple:flag_defs.bzl", "DEBUG_PREPROCESSOR_FLAGS") +load("@xplat//configurations/buck/apple:flag_defs.bzl", "get_debug_preprocessor_flags") load("//ReactNative:DEFS.bzl", "IS_OSS_BUILD", "rn_xplat_cxx_library", "ANDROID_INSPECTOR_FLAGS", "APPLE_INSPECTOR_FLAGS", "ANDROID_JSC_DEPS", "APPLE_JSC_DEPS", "react_native_xplat_target") CXX_LIBRARY_COMPILER_FLAGS = [ @@ -9,8 +9,8 @@ CXX_LIBRARY_COMPILER_FLAGS = [ APPLE_COMPILER_FLAGS = [] if not IS_OSS_BUILD: - load("@xplat//configurations/buck/apple:flag_defs.bzl", "STATIC_LIBRARY_IOS_FLAGS", "flags") - APPLE_COMPILER_FLAGS = flags.get_flag_value(STATIC_LIBRARY_IOS_FLAGS, 'compiler_flags') + load("@xplat//configurations/buck/apple:flag_defs.bzl", "get_static_library_ios_flags", "flags") + APPLE_COMPILER_FLAGS = flags.get_flag_value(get_static_library_ios_flags(), 'compiler_flags') rn_xplat_cxx_library( name = "module", @@ -139,7 +139,7 @@ rn_xplat_cxx_library( fbobjc_frameworks = [ "$SDKROOT/System/Library/Frameworks/JavaScriptCore.framework", ], - fbobjc_preprocessor_flags = DEBUG_PREPROCESSOR_FLAGS + APPLE_INSPECTOR_FLAGS, + fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + APPLE_INSPECTOR_FLAGS, force_static = True, preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/cxxreact/Instance.h b/ReactCommon/cxxreact/Instance.h index bcd0bea395ac..e35ce4bbe2ac 100644 --- a/ReactCommon/cxxreact/Instance.h +++ b/ReactCommon/cxxreact/Instance.h @@ -63,15 +63,6 @@ class RN_EXPORT Instance { // This method is experimental, and may be modified or removed. void registerBundle(uint32_t bundleId, const std::string& bundlePath); - // This method is experimental, and may be modified or removed. - template - Value callFunctionSync(const std::string &module, const std::string &method, - T &&args) { - CHECK(nativeToJsBridge_); - return nativeToJsBridge_->callFunctionSync(module, method, - std::forward(args)); - } - const ModuleRegistry &getModuleRegistry() const; ModuleRegistry &getModuleRegistry(); diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 01c8c96de195..81e71eddbaa5 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -581,30 +581,6 @@ namespace facebook { callNativeModules(std::move(result)); } - Value JSCExecutor::callFunctionSyncWithValue( - const std::string& module, const std::string& method, Value args) { - SystraceSection s("JSCExecutor::callFunction"); - Object result = [&] { - JSContextLock lock(m_context); - if (!m_callFunctionReturnResultAndFlushedQueueJS) { - bindBridge(); - } - return m_callFunctionReturnResultAndFlushedQueueJS->callAsFunction({ - Value(m_context, String::createExpectingAscii(m_context, module)), - Value(m_context, String::createExpectingAscii(m_context, method)), - std::move(args), - }).asObject(); - }(); - - Value length = result.getProperty("length"); - - if (!length.isNumber() || length.asInteger() != 2) { - std::runtime_error("Return value of a callFunction must be an array of size 2"); - } - callNativeModules(result.getPropertyAtIndex(1)); - return result.getPropertyAtIndex(0); - } - void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr jsonValue) { try { SystraceSection s("JSCExecutor::setGlobalVariable", "propName", propName); diff --git a/ReactCommon/cxxreact/JSCExecutor.h b/ReactCommon/cxxreact/JSCExecutor.h index 258697af5c95..5acc967722e5 100644 --- a/ReactCommon/cxxreact/JSCExecutor.h +++ b/ReactCommon/cxxreact/JSCExecutor.h @@ -79,14 +79,6 @@ class RN_EXPORT JSCExecutor : public JSExecutor, public PrivateDataBase { const double callbackId, const folly::dynamic& arguments) override; - template - Value callFunctionSync( - const std::string& module, const std::string& method, T&& args) { - return callFunctionSyncWithValue( - module, method, JSCValueEncoder::type>::toJSCValue( - m_context, std::forward(args))); - } - virtual void setGlobalVariable( std::string propName, std::unique_ptr jsonValue) override; @@ -123,9 +115,6 @@ class RN_EXPORT JSCExecutor : public JSExecutor, public PrivateDataBase { void initOnJSVMThread() throw(JSException); static bool isNetworkInspected(const std::string &owner, const std::string &app, const std::string &device); - // This method is experimental, and may be modified or removed. - Value callFunctionSyncWithValue( - const std::string& module, const std::string& method, Value value); void terminateOnJSVMThread(); void bindBridge() throw(JSException); void callNativeModules(Value&&); diff --git a/ReactCommon/cxxreact/NativeToJsBridge.h b/ReactCommon/cxxreact/NativeToJsBridge.h index 586766e387bd..78d960a08a45 100644 --- a/ReactCommon/cxxreact/NativeToJsBridge.h +++ b/ReactCommon/cxxreact/NativeToJsBridge.h @@ -55,35 +55,6 @@ class NativeToJsBridge { */ void invokeCallback(double callbackId, folly::dynamic&& args); - /** - * Executes a JS method on the given executor synchronously, returning its - * return value. JSException will be thrown if JS throws an exception; - * another standard exception may be thrown for C++ bridge failures, or if - * the executor is not capable of synchronous calls. - * - * This method is experimental, and may be modified or removed. - * - * loadApplicationScriptSync() must be called and finished executing - * before callFunctionSync(). - */ - template - Value callFunctionSync(const std::string& module, const std::string& method, T&& args) { - if (*m_destroyed) { - throw std::logic_error( - folly::to("Synchronous call to ", module, ".", method, - " after bridge is destroyed")); - } - - JSCExecutor *jscExecutor = dynamic_cast(m_executor.get()); - if (!jscExecutor) { - throw std::invalid_argument( - folly::to("Executor type ", typeid(m_executor.get()).name(), - " does not support synchronous calls")); - } - - return jscExecutor->callFunctionSync(module, method, std::forward(args)); - } - /** * Starts the JS application. If bundleRegistry is non-null, then it is * used to fetch JavaScript modules as individual scripts. diff --git a/ReactCommon/cxxreact/tests/BUCK b/ReactCommon/cxxreact/tests/BUCK index 09c0a244fb71..010e7c8e50d0 100644 --- a/ReactCommon/cxxreact/tests/BUCK +++ b/ReactCommon/cxxreact/tests/BUCK @@ -11,8 +11,8 @@ TEST_SRCS = [ ] if not IS_OSS_BUILD: + load("@xplat//build_defs:fb_xplat_cxx.bzl", "cxx_test") load("@xplat//configurations/buck/android:jni_instrumentation_test", "jni_instrumentation_test_lib") - load("@xplat//configurations/buck:fb_xplat_cxx.bzl", "cxx_test") load("@xplat//configurations/buck:default_platform_defs.bzl", "APPLE") jni_instrumentation_test_lib( name = 'tests', diff --git a/ReactCommon/fabric/BUCK b/ReactCommon/fabric/BUCK index 4fcbe82b5b40..fcf321eaeac0 100644 --- a/ReactCommon/fabric/BUCK +++ b/ReactCommon/fabric/BUCK @@ -1,4 +1,4 @@ -load("@xplat//configurations/buck/apple:flag_defs.bzl", "DEBUG_PREPROCESSOR_FLAGS") +load("@xplat//configurations/buck/apple:flag_defs.bzl", "get_debug_preprocessor_flags") load("//ReactNative:DEFS.bzl", "IS_OSS_BUILD", "rn_xplat_cxx_library", "APPLE_INSPECTOR_FLAGS") CXX_LIBRARY_COMPILER_FLAGS = [ @@ -9,8 +9,8 @@ CXX_LIBRARY_COMPILER_FLAGS = [ APPLE_COMPILER_FLAGS = [] if not IS_OSS_BUILD: - load("@xplat//configurations/buck/apple:flag_defs.bzl", "STATIC_LIBRARY_IOS_FLAGS", "flags") - APPLE_COMPILER_FLAGS = flags.get_flag_value(STATIC_LIBRARY_IOS_FLAGS, 'compiler_flags') + load("@xplat//configurations/buck/apple:flag_defs.bzl", "get_static_library_ios_flags", "flags") + APPLE_COMPILER_FLAGS = flags.get_flag_value(get_static_library_ios_flags(), 'compiler_flags') rn_xplat_cxx_library( name = "fabric", @@ -27,7 +27,7 @@ rn_xplat_cxx_library( "-frtti", ], fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, - fbobjc_preprocessor_flags = DEBUG_PREPROCESSOR_FLAGS + APPLE_INSPECTOR_FLAGS, + fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + APPLE_INSPECTOR_FLAGS, force_static = True, preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/fabric/FabricUIManager.cpp b/ReactCommon/fabric/FabricUIManager.cpp index 1a383519a839..54262fc9edbf 100644 --- a/ReactCommon/fabric/FabricUIManager.cpp +++ b/ReactCommon/fabric/FabricUIManager.cpp @@ -1,14 +1,23 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ #include "FabricUIManager.h" + +#include "IFabricPlatformUIOperationManager.h" #include "ShadowNode.h" namespace facebook { namespace react { -FabricUIManager::FabricUIManager() {} +FabricUIManager::FabricUIManager(const std::shared_ptr &platformUIOperationManager) : + platformUIOperationManager_(platformUIOperationManager) {}; ShadowNodeRef FabricUIManager::createNode(int reactTag, std::string viewName, int rootTag, folly::dynamic props, void *instanceHandle) { + platformUIOperationManager_->performUIOperation(); return std::make_shared(reactTag, viewName, rootTag, props, instanceHandle); } @@ -41,4 +50,5 @@ void FabricUIManager::appendChildToSet(const ShadowNodeSetRef &childSet, const S void FabricUIManager::completeRoot(int rootTag, const ShadowNodeSetRef &childSet) { } -}} +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/FabricUIManager.h b/ReactCommon/fabric/FabricUIManager.h index 7362c54dfa30..06ee2a94f70e 100644 --- a/ReactCommon/fabric/FabricUIManager.h +++ b/ReactCommon/fabric/FabricUIManager.h @@ -1,4 +1,9 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ #pragma once @@ -10,6 +15,7 @@ namespace facebook { namespace react { class ShadowNode; +class IFabricPlatformUIOperationManager; typedef std::shared_ptr ShadowNodeRef; typedef folly::fbvector ShadowNodeSet; @@ -17,7 +23,7 @@ typedef std::shared_ptr ShadowNodeSetRef; class FabricUIManager { public: - FabricUIManager(); + FabricUIManager(const std::shared_ptr &platformUIOperationManager); ShadowNodeRef createNode(int reactTag, std::string viewName, int rootTag, folly::dynamic props, void *instanceHandle); ShadowNodeRef cloneNode(const ShadowNodeRef &node); @@ -28,7 +34,10 @@ class FabricUIManager { ShadowNodeSetRef createChildSet(int rootTag); void appendChildToSet(const ShadowNodeSetRef &childSet, const ShadowNodeRef &childNode); void completeRoot(int rootTag, const ShadowNodeSetRef &childSet); -}; -}} +private: + std::shared_ptr platformUIOperationManager_; +}; +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/IFabricPlatformUIOperationManager.h b/ReactCommon/fabric/IFabricPlatformUIOperationManager.h new file mode 100644 index 000000000000..ff1434c7520d --- /dev/null +++ b/ReactCommon/fabric/IFabricPlatformUIOperationManager.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook { +namespace react { + +/** + * An interface for FabricUIManager to perform platform-specific UI operations, like updating native UIView's in iOS. + */ +class IFabricPlatformUIOperationManager { +public: + // TODO: add meaningful methods + virtual void performUIOperation() = 0; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/ShadowNode.cpp b/ReactCommon/fabric/ShadowNode.cpp index 39b4d145a798..9f675572bc81 100644 --- a/ReactCommon/fabric/ShadowNode.cpp +++ b/ReactCommon/fabric/ShadowNode.cpp @@ -1,4 +1,9 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ #include "ShadowNode.h" diff --git a/ReactCommon/fabric/ShadowNode.h b/ReactCommon/fabric/ShadowNode.h index a707f7160970..257b7c19e940 100644 --- a/ReactCommon/fabric/ShadowNode.h +++ b/ReactCommon/fabric/ShadowNode.h @@ -1,4 +1,9 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ #pragma once diff --git a/local-cli/link/ios/common/isInstalled.js b/local-cli/link/ios/common/isInstalled.js index a7a5f2d07d19..69bcd8bdf32d 100644 --- a/local-cli/link/ios/common/isInstalled.js +++ b/local-cli/link/ios/common/isInstalled.js @@ -1,6 +1,6 @@ const isInstalledIOS = require('../isInstalled'); const isInstalledPods = require('../../pods/isInstalled'); -module.exports = function isInstalled(config, name) { - return isInstalledIOS(config, name) || isInstalledPods(config, name); +module.exports = function isInstalled(projectConfig, name, dependencyConfig) { + return isInstalledIOS(projectConfig, dependencyConfig) || isInstalledPods(projectConfig, dependencyConfig); }; diff --git a/local-cli/link/ios/common/unregisterNativeModule.js b/local-cli/link/ios/common/unregisterNativeModule.js index b42882623fbe..e6d34ada4cce 100644 --- a/local-cli/link/ios/common/unregisterNativeModule.js +++ b/local-cli/link/ios/common/unregisterNativeModule.js @@ -1,8 +1,8 @@ const compact = require('lodash').compact; const isInstalledIOS = require('../isInstalled'); const isInstalledPods = require('../../pods/isInstalled'); -const unregisterDependencyIOS = require('../registerNativeModule'); -const unregisterDependencyPods = require('../../pods/registerNativeModule'); +const unregisterDependencyIOS = require('../unregisterNativeModule'); +const unregisterDependencyPods = require('../../pods/unregisterNativeModule'); module.exports = function unregisterNativeModule( name, diff --git a/local-cli/link/link.js b/local-cli/link/link.js index 7799b5c171f6..b77c135f9f47 100644 --- a/local-cli/link/link.js +++ b/local-cli/link/link.js @@ -49,7 +49,7 @@ const linkDependency = (platforms, project, dependency) => { return null; } - const isInstalled = linkConfig.isInstalled(project[platform], dependency.config[platform]); + const isInstalled = linkConfig.isInstalled(project[platform], dependency.name, dependency.config[platform]); if (isInstalled) { log.info(chalk.grey(`Platform '${platform}' module ${dependency.name} is already linked`)); diff --git a/local-cli/link/unlink.js b/local-cli/link/unlink.js index 1b504e3ed4d0..91c89ab7ec08 100644 --- a/local-cli/link/unlink.js +++ b/local-cli/link/unlink.js @@ -32,7 +32,7 @@ const unlinkDependency = (platforms, project, dependency, packageName, otherDepe return; } - const isInstalled = linkConfig.isInstalled(project[platform], dependency[platform]); + const isInstalled = linkConfig.isInstalled(project[platform], packageName, dependency[platform]); if (!isInstalled) { log.info(`Platform '${platform}' module ${packageName} is not installed`); diff --git a/package.json b/package.json index 7afce5a614ec..d69018f16f56 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,7 @@ "denodeify": "^1.2.1", "envinfo": "^3.0.0", "errorhandler": "^1.5.0", + "eslint-plugin-react-native": "^3.2.1", "event-target-shim": "^1.0.5", "fbjs": "^0.8.14", "fbjs-scripts": "^0.8.1", @@ -174,14 +175,14 @@ "graceful-fs": "^4.1.3", "inquirer": "^3.0.6", "lodash": "^4.17.5", - "metro": "^0.28.0", - "metro-core": "^0.28.0", + "metro": "^0.29.0", + "metro-core": "^0.29.0", "mime": "^1.3.4", "minimist": "^1.2.0", "mkdirp": "^0.5.1", "morgan": "^1.9.0", "node-fetch": "^1.3.3", - "node-notifier": "^5.1.2", + "node-notifier": "^5.2.1", "npmlog": "^2.0.4", "opn": "^3.0.2", "optimist": "^0.6.1", diff --git a/react.gradle b/react.gradle index 685862193bb9..64b2f02f8cbf 100644 --- a/react.gradle +++ b/react.gradle @@ -6,117 +6,112 @@ def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js" def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" def entryFile = config.entryFile ?: "index.android.js" def bundleCommand = config.bundleCommand ?: "bundle" -def reactRoot = file(config.root ?: "../../") + +// because elvis operator +def elvisFile(thing) { + return thing ? file(thing) : null; +} + +def reactRoot = elvisFile(config.root) ?: file("../../") def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ; +void runBefore(String dependentTaskName, Task task) { + Task dependentTask = tasks.findByPath(dependentTaskName); + if (dependentTask != null) { + dependentTask.dependsOn task + } +} gradle.projectsEvaluated { - android.applicationVariants.all { def variant -> - // Create variant and target names - def targetName = variant.name.capitalize() - def targetPath = variant.dirName - - // React js bundle directories - def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}") - def resourcesDir = file("$buildDir/generated/res/react/${targetPath}") - - def jsBundleFile = file("$jsBundleDir/$bundleAssetName") - - // Additional node and packager commandline arguments - def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] - def extraPackagerArgs = config.extraPackagerArgs ?: [] - - def currentBundleTask = tasks.create( - name: "bundle${targetName}JsAndAssets", - type: Exec) { - group = "react" - description = "bundle JS and assets for ${targetName}." - - // Create dirs if they are not there (e.g. the "clean" task just ran) - doFirst { - jsBundleDir.deleteDir() - jsBundleDir.mkdirs() - resourcesDir.deleteDir() - resourcesDir.mkdirs() - } - - // Set up inputs and outputs so gradle can cache the result - inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) - outputs.dir jsBundleDir - outputs.dir resourcesDir - - // Set up the call to the react-native cli - workingDir reactRoot - - // Set up dev mode - def devEnabled = !(config."devDisabledIn${targetName}" - || targetName.toLowerCase().contains("release")) - - def extraArgs = extraPackagerArgs; - - if (bundleConfig) { - extraArgs = extraArgs.clone() - extraArgs.add("--config"); - extraArgs.add(bundleConfig); - } - - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", - "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs) - } else { - commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", - "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs) - } - - enabled config."bundleIn${targetName}" || - config."bundleIn${variant.buildType.name.capitalize()}" ?: - targetName.toLowerCase().contains("release") - } - - // Expose a minimal interface on the application variant and the task itself: - variant.ext.bundleJsAndAssets = currentBundleTask - currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask) - currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask) - - variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders) - variant.mergeResources.dependsOn(currentBundleTask) - - def resourcesDirConfigValue = config."resourcesDir${targetName}" - if (resourcesDirConfigValue) { - def currentCopyResTask = tasks.create( - name: "copy${targetName}BundledResources", - type: Copy) { + // Grab all build types and product flavors + def buildTypes = android.buildTypes.collect { type -> type.name } + def productFlavors = android.productFlavors.collect { flavor -> flavor.name } + + // When no product flavors defined, use empty + if (!productFlavors) productFlavors.add('') + + productFlavors.each { productFlavorName -> + buildTypes.each { buildTypeName -> + // Create variant and target names + def flavorNameCapitalized = "${productFlavorName.capitalize()}" + def buildNameCapitalized = "${buildTypeName.capitalize()}" + def targetName = "${flavorNameCapitalized}${buildNameCapitalized}" + def targetPath = productFlavorName ? + "${productFlavorName}/${buildTypeName}" : + "${buildTypeName}" + + // React js bundle directories + def jsBundleDirConfigName = "jsBundleDir${targetName}" + def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: + file("$buildDir/intermediates/assets/${targetPath}") + + def resourcesDirConfigName = "resourcesDir${targetName}" + def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: + file("$buildDir/intermediates/res/merged/${targetPath}") + def jsBundleFile = file("$jsBundleDir/$bundleAssetName") + + // Bundle task name for variant + def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" + + // Additional node and packager commandline arguments + def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"] + def extraPackagerArgs = config.extraPackagerArgs ?: [] + + def currentBundleTask = tasks.create( + name: bundleJsAndAssetsTaskName, + type: Exec) { group = "react" - description = "copy bundled resources into custom location for ${targetName}." - - from resourcesDir - into file(resourcesDirConfigValue) - - dependsOn(currentBundleTask) - - enabled currentBundleTask.enabled + description = "bundle JS and assets for ${targetName}." + + // Create dirs if they are not there (e.g. the "clean" task just ran) + doFirst { + jsBundleDir.mkdirs() + resourcesDir.mkdirs() + } + + // Set up inputs and outputs so gradle can cache the result + inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) + outputs.dir jsBundleDir + outputs.dir resourcesDir + + // Set up the call to the react-native cli + workingDir reactRoot + + // Set up dev mode + def devEnabled = !(config."devDisabledIn${targetName}" + || targetName.toLowerCase().contains("release")) + + def extraArgs = extraPackagerArgs; + + if (bundleConfig) { + extraArgs = extraArgs.clone() + extraArgs.add("--config"); + extraArgs.add(bundleConfig); + } + + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", + "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs) + } else { + commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", + "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs) + } + + enabled config."bundleIn${targetName}" || + config."bundleIn${buildTypeName.capitalize()}" ?: + targetName.toLowerCase().contains("release") } - variant.packageApplication.dependsOn(currentCopyResTask) - } - - def currentAssetsCopyTask = tasks.create( - name: "copy${targetName}BundledJs", - type: Copy) { - group = "react" - description = "copy bundled JS into ${targetName}." + // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process + currentBundleTask.dependsOn("merge${targetName}Resources") + currentBundleTask.dependsOn("merge${targetName}Assets") - from jsBundleDir - into file(config."jsBundleDir${targetName}" ?: - "$buildDir/intermediates/assets/${targetPath}") - - // mergeAssets must run first, as it clears the intermediates directory - dependsOn(variant.mergeAssets) - - enabled currentBundleTask.enabled + runBefore("process${flavorNameCapitalized}Armeabi-v7a${buildNameCapitalized}Resources", currentBundleTask) + runBefore("process${flavorNameCapitalized}X86${buildNameCapitalized}Resources", currentBundleTask) + runBefore("processUniversal${targetName}Resources", currentBundleTask) + runBefore("process${targetName}Resources", currentBundleTask) + runBefore("dataBindingProcessLayouts${targetName}", currentBundleTask) } - - variant.packageApplication.dependsOn(currentAssetsCopyTask) } } diff --git a/scripts/objc-test.sh b/scripts/objc-test.sh index 81614c5f6edf..f2c0459989da 100755 --- a/scripts/objc-test.sh +++ b/scripts/objc-test.sh @@ -73,9 +73,6 @@ curl 'http://localhost:8081/IntegrationTests/RCTRootViewIntegrationTestApp.bundl rm temp.bundle # Run tests -# TODO: We use xcodebuild because xctool would stall when collecting info about -# the tests before running them. Switch back when this issue with xctool has -# been resolved. xcodebuild \ -project "RNTester/RNTester.xcodeproj" \ -scheme $SCHEME \ @@ -87,9 +84,6 @@ xcodebuild \ else # Don't run tests. No need to pass -destination to xcodebuild. -# TODO: We use xcodebuild because xctool would stall when collecting info about -# the tests before running them. Switch back when this issue with xctool has -# been resolved. xcodebuild \ -project "RNTester/RNTester.xcodeproj" \ -scheme $SCHEME \