diff --git a/.flowconfig b/.flowconfig index ca1e816a28f1be..3e5608c4e3bfc2 100644 --- a/.flowconfig +++ b/.flowconfig @@ -41,12 +41,12 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-0]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-0]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-1]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-1]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError unsafe.enable_getters_and_setters=true [version] -^0.40.0 +^0.41.0 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index ad776d931b493d..c7b878bad1c8c9 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,8 +1,11 @@ -We use GitHub Issues for bugs. +Please read the following carefully before opening a new issue. -If you have a non-bug question, ask on Stack Overflow: http://stackoverflow.com/questions/tagged/react-native +We use GitHub Issues for tracking bugs in React Native. -If you have a feature request, post it on Product Pains: https://productpains.com/product/react-native/ +- If you have a non-bug question, ask on Stack Overflow: http://stackoverflow.com/questions/tagged/react-native +- If you have a feature request, post it on Canny: https://react-native.canny.io/feature-requests + +Your issue may be closed without explanation if it does not provide the information required by this template. --- Please use this template, and delete everything above this line before submitting your issue --- @@ -23,3 +26,4 @@ If you have a feature request, post it on Product Pains: https://productpains.co * React Native version: [FILL THIS OUT: Does the bug reproduce on the latest RN release?] * Platform: [FILL THIS OUT: iOS, Android, or both?] * Operating System: [FILL THIS OUT: MacOS, Linux, or Windows?] +* Dev tools: [FILL THIS OUT: Xcode or Android Studio version, iOS or Android SDK version, if applicable] diff --git a/.gitignore b/.gitignore index 07790dc4745276..1ecdbc1bd3fa7d 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,5 @@ node_modules # Test generated files /ReactAndroid/src/androidTest/assets/AndroidTestBundle.js *.js.meta + +/third-party diff --git a/.travis.yml b/.travis.yml index 40f52ea8c85334..5f0caaabc938ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c -osx_image: xcode8 +osx_image: xcode8.2 install: - mkdir -p /Users/travis/build/facebook/.nvm @@ -20,19 +20,18 @@ script: - if [[ "$TEST_TYPE" = objc-ios ]]; then travis_retry travis_wait ./scripts/objc-test-ios.sh; fi - if [[ "$TEST_TYPE" = objc-tvos ]]; then travis_retry travis_wait ./scripts/objc-test-tvos.sh; fi - if [[ "$TEST_TYPE" = e2e-objc ]]; then node ./scripts/run-ci-e2e-tests.js --ios --js --retries 3; fi - - if [[ "$TEST_TYPE" = e2e-objc-tvos ]]; then node ./scripts/run-ci-e2e-tests.js --tvos --retries 3; fi - - if [[ "$TEST_TYPE" = js ]]; then npm run flow check; fi - - if [[ "$TEST_TYPE" = js ]]; then npm test -- --maxWorkers=1; fi - if [[ ( "$TEST_TYPE" = podspecs ) && ( "$TRAVIS_PULL_REQUEST" = "false" ) ]]; then gem install cocoapods && ./scripts/process-podspecs.sh; fi + +matrix: + - fast_finish: true # Fail the whole build as soon as one test type fails. Should help with Travis capacity issues (very long queues). + +# The order of these tests says which are more likely to run first and fail the whole build fast. env: - matrix: - - TEST_TYPE=e2e-objc-tvos - - TEST_TYPE=e2e-objc - - TEST_TYPE=objc-ios - - TEST_TYPE=objc-tvos - - TEST_TYPE=js - - TEST_TYPE=podspecs + - TEST_TYPE=objc-ios + - TEST_TYPE=podspecs + - TEST_TYPE=e2e-objc + - TEST_TYPE=objc-tvos branches: only: @@ -43,7 +42,8 @@ notifications: email: recipients: - mkonicek@fb.com - - eloy@artsy.net + - douglowder@mac.com # Doug Lowder built and maintains Apple TV specific code and wants to be notified about tvOS failures. + - eloy@artsy.net # Eloy Durán maintains the podspecs test and wants to be notified about failures. on_failure: change on_success: change slack: diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 316b974e5ae44a..57fa82885f7950 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -123,15 +123,6 @@ var SearchScreen = React.createClass({ fetch(this._urlForQueryAndPage(query, 1)) .then((response) => response.json()) - .catch((error) => { - LOADING[query] = false; - resultsCache.dataForQuery[query] = undefined; - - this.setState({ - dataSource: this.getDataSource([]), - isLoading: false, - }); - }) .then((responseData) => { LOADING[query] = false; resultsCache.totalForQuery[query] = responseData.total; @@ -148,6 +139,15 @@ var SearchScreen = React.createClass({ dataSource: this.getDataSource(responseData.movies), }); }) + .catch((error) => { + LOADING[query] = false; + resultsCache.dataForQuery[query] = undefined; + + this.setState({ + dataSource: this.getDataSource([]), + isLoading: false, + }); + }) .done(); }, diff --git a/Examples/UIExplorer/js/CameraRollView.js b/Examples/UIExplorer/js/CameraRollView.js index 8bb9fbd2b07765..ca5f1dc5e8e26a 100644 --- a/Examples/UIExplorer/js/CameraRollView.js +++ b/Examples/UIExplorer/js/CameraRollView.js @@ -81,6 +81,7 @@ var propTypes = { }; var CameraRollView = React.createClass({ + // $FlowFixMe(>=0.41.0) propTypes: propTypes, getDefaultProps: function(): Object { @@ -123,6 +124,7 @@ var CameraRollView = React.createClass({ rendererChanged: function() { var ds = new ListView.DataSource({rowHasChanged: this._rowHasChanged}); this.state.dataSource = ds.cloneWithRows( + // $FlowFixMe(>=0.41.0) groupByEveryN(this.state.assets, this.props.imagesPerRow) ); }, @@ -209,6 +211,7 @@ var CameraRollView = React.createClass({ if (image === null) { return null; } + // $FlowFixMe(>=0.41.0) return this.props.renderImage(image); }); @@ -231,6 +234,7 @@ var CameraRollView = React.createClass({ newState.lastCursor = data.page_info.end_cursor; newState.assets = this.state.assets.concat(assets); newState.dataSource = this.state.dataSource.cloneWithRows( + // $FlowFixMe(>=0.41.0) groupByEveryN(newState.assets, this.props.imagesPerRow) ); } diff --git a/Examples/UIExplorer/js/FlatListExample.js b/Examples/UIExplorer/js/FlatListExample.js index 0871e7374ae258..6ff92e6dcdf24e 100644 --- a/Examples/UIExplorer/js/FlatListExample.js +++ b/Examples/UIExplorer/js/FlatListExample.js @@ -26,11 +26,12 @@ const React = require('react'); const ReactNative = require('react-native'); const { + Animated, + FlatList, StyleSheet, View, } = ReactNative; -const FlatList = require('FlatList'); const UIExplorerPage = require('./UIExplorerPage'); const infoLog = require('infoLog'); @@ -47,6 +48,8 @@ const { renderSmallSwitchOption, } = require('./ListExampleShared'); +const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); + const VIEWABILITY_CONFIG = { minimumViewTime: 3000, viewAreaCoveragePercentThreshold: 100, @@ -66,18 +69,34 @@ class FlatListExample extends React.PureComponent { logViewable: false, virtualized: true, }; + _onChangeFilterText = (filterText) => { this.setState({filterText}); }; + _onChangeScrollToIndex = (text) => { - this._listRef.scrollToIndex({viewPosition: 0.5, index: Number(text)}); + this._listRef.getNode().scrollToIndex({viewPosition: 0.5, index: Number(text)}); }; + + _scrollPos = new Animated.Value(0); + _scrollSinkX = Animated.event( + [{nativeEvent: { contentOffset: { x: this._scrollPos } }}], + {useNativeDriver: true}, + ); + _scrollSinkY = Animated.event( + [{nativeEvent: { contentOffset: { y: this._scrollPos } }}], + {useNativeDriver: true}, + ); + componentDidUpdate() { - this._listRef.recordInteraction(); // e.g. flipping logViewable switch + this._listRef.getNode().recordInteraction(); // e.g. flipping logViewable switch } + render() { const filterRegex = new RegExp(String(this.state.filterText), 'i'); - const filter = (item) => (filterRegex.test(item.text) || filterRegex.test(item.title)); + const filter = (item) => ( + filterRegex.test(item.text) || filterRegex.test(item.title) + ); const filteredData = this.state.data.filter(filter); return ( @@ -102,22 +120,37 @@ class FlatListExample extends React.PureComponent { {renderSmallSwitchOption(this, 'fixedHeight')} {renderSmallSwitchOption(this, 'logViewable')} {renderSmallSwitchOption(this, 'debug')} + - } ) => { // Impressions can be logged here if (this.state.logViewable) { - infoLog('onViewableItemsChanged: ', info.changed.map((v) => ({...v, item: '...'}))); + infoLog( + 'onViewableItemsChanged: ', + info.changed.map((v) => ({...v, item: '...'})), + ); } }; _pressItem = (key: number) => { - this._listRef.recordInteraction(); + this._listRef.getNode().recordInteraction(); pressItem(this, key); }; _listRef: FlatList<*>; @@ -180,6 +222,12 @@ const styles = StyleSheet.create({ searchRow: { paddingHorizontal: 10, }, + spindicator: { + marginLeft: 'auto', + width: 2, + height: 16, + backgroundColor: 'darkgray', + }, }); module.exports = FlatListExample; diff --git a/Examples/UIExplorer/js/ImageExample.js b/Examples/UIExplorer/js/ImageExample.js index 04f7437f807739..95ed82f307b990 100644 --- a/Examples/UIExplorer/js/ImageExample.js +++ b/Examples/UIExplorer/js/ImageExample.js @@ -677,6 +677,45 @@ exports.examples = [ }, platform: 'ios', }, + { + title: 'Blur Radius', + render: function() { + return ( + + + + + + + + + ); + }, + }, ]; var fullImage = {uri: 'https://facebook.github.io/react/img/logo_og.png'}; diff --git a/Examples/UIExplorer/js/MapViewExample.js b/Examples/UIExplorer/js/MapViewExample.js deleted file mode 100644 index d2143eec277bb4..00000000000000 --- a/Examples/UIExplorer/js/MapViewExample.js +++ /dev/null @@ -1,473 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @flow - * @providesModule MapViewExample - */ -'use strict'; - -var React = require('react'); -var ReactNative = require('react-native'); -var { PropTypes } = React; -var { - Image, - MapView, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - View, -} = ReactNative; - -var regionText = { - latitude: '0', - longitude: '0', - latitudeDelta: '0', - longitudeDelta: '0', -}; - -var MapRegionInput = React.createClass({ - - propTypes: { - region: PropTypes.shape({ - latitude: PropTypes.number.isRequired, - longitude: PropTypes.number.isRequired, - latitudeDelta: PropTypes.number, - longitudeDelta: PropTypes.number, - }), - onChange: PropTypes.func.isRequired, - }, - - getInitialState() { - return { - region: { - latitude: 0, - longitude: 0, - } - }; - }, - - componentWillReceiveProps: function(nextProps) { - this.setState({ - region: nextProps.region || this.getInitialState().region - }); - }, - - render: function() { - var region = this.state.region || this.getInitialState().region; - return ( - - - - {'Latitude'} - - - - - - {'Longitude'} - - - - - - {'Latitude delta'} - - - - - - {'Longitude delta'} - - - - - - {'Change'} - - - - ); - }, - - _onChangeLatitude: function(e) { - regionText.latitude = e.nativeEvent.text; - }, - - _onChangeLongitude: function(e) { - regionText.longitude = e.nativeEvent.text; - }, - - _onChangeLatitudeDelta: function(e) { - regionText.latitudeDelta = e.nativeEvent.text; - }, - - _onChangeLongitudeDelta: function(e) { - regionText.longitudeDelta = e.nativeEvent.text; - }, - - _change: function() { - this.setState({ - region: { - latitude: parseFloat(regionText.latitude), - longitude: parseFloat(regionText.longitude), - latitudeDelta: parseFloat(regionText.latitudeDelta), - longitudeDelta: parseFloat(regionText.longitudeDelta), - }, - }); - this.props.onChange(this.state.region); - }, - -}); - -class MapViewExample extends React.Component { - state = { - isFirstLoad: true, - mapRegion: undefined, - mapRegionInput: undefined, - annotations: [], - }; - - render() { - return ( - - - - - ); - } - - _getAnnotations = (region) => { - return [{ - longitude: region.longitude, - latitude: region.latitude, - title: 'You Are Here', - }]; - }; - - _onRegionChange = (region) => { - this.setState({ - mapRegionInput: region, - }); - }; - - _onRegionChangeComplete = (region) => { - if (this.state.isFirstLoad) { - this.setState({ - mapRegionInput: region, - annotations: this._getAnnotations(region), - isFirstLoad: false, - }); - } - }; - - _onRegionInputChanged = (region) => { - this.setState({ - mapRegion: region, - mapRegionInput: region, - annotations: this._getAnnotations(region), - }); - }; -} - -class AnnotationExample extends React.Component { - state = { - isFirstLoad: true, - annotations: [], - mapRegion: undefined, - }; - - render() { - if (this.state.isFirstLoad) { - var onRegionChangeComplete = (region) => { - this.setState({ - isFirstLoad: false, - annotations: [{ - longitude: region.longitude, - latitude: region.latitude, - ...this.props.annotation, - }], - }); - }; - } - - return ( - - ); - } -} - -class DraggableAnnotationExample extends React.Component { - state = { - isFirstLoad: true, - annotations: [], - mapRegion: undefined, - }; - - createAnnotation = (longitude, latitude) => { - return { - longitude, - latitude, - draggable: true, - onDragStateChange: (event) => { - if (event.state === 'idle') { - this.setState({ - annotations: [this.createAnnotation(event.longitude, event.latitude)], - }); - } - console.log('Drag state: ' + event.state); - }, - }; - }; - - render() { - if (this.state.isFirstLoad) { - var onRegionChangeComplete = (region) => { - //When the MapView loads for the first time, we can create the annotation at the - //region that was loaded. - this.setState({ - isFirstLoad: false, - annotations: [this.createAnnotation(region.longitude, region.latitude)], - }); - }; - } - - return ( - - ); - } -} - -var styles = StyleSheet.create({ - map: { - height: 150, - margin: 10, - borderWidth: 1, - borderColor: '#000000', - }, - row: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - textInput: { - width: 150, - height: 20, - borderWidth: 0.5, - borderColor: '#aaaaaa', - fontSize: 13, - padding: 4, - }, - changeButton: { - alignSelf: 'center', - marginTop: 5, - padding: 3, - borderWidth: 0.5, - borderColor: '#777777', - }, -}); - -exports.displayName = (undefined: ?string); -exports.title = ''; -exports.description = 'Base component to display maps'; -exports.examples = [ - { - title: 'Map', - render() { - return ; - } - }, - { - title: 'showsUserLocation + followUserLocation', - render() { - return ( - - ); - } - }, - { - title: 'Callout example', - render() { - return { - alert('You Are Here'); - }}> - - - ), - }}/>; - } - }, - { - title: 'Show callouts by default example', - render() { - return { - alert('You Are Here'); - }}> - - - ), - }} - showsAnnotationCallouts={true} - />; - } - }, - { - title: 'Annotation focus example', - render() { - return { - alert('Annotation gets focus'); - }, - onBlur: () => { - alert('Annotation lost focus'); - } - }}/>; - } - }, - { - title: 'Draggable pin', - render() { - return ; - } - }, - { - title: 'Custom pin color', - render() { - return ; - } - }, - { - title: 'Custom pin image', - render() { - return ; - } - }, - { - title: 'Custom pin view', - render() { - return - - Thumbs Up! - - - , - }}/>; - } - }, - { - title: 'Custom overlay', - render() { - return ; - } - }, -]; diff --git a/Examples/UIExplorer/js/MultiColumnExample.js b/Examples/UIExplorer/js/MultiColumnExample.js index d682c2d4196ff3..9b4477240f56ac 100644 --- a/Examples/UIExplorer/js/MultiColumnExample.js +++ b/Examples/UIExplorer/js/MultiColumnExample.js @@ -26,12 +26,12 @@ const React = require('react'); const ReactNative = require('react-native'); const { + FlatList, StyleSheet, Text, View, } = ReactNative; -const FlatList = require('FlatList'); const UIExplorerPage = require('./UIExplorerPage'); const infoLog = require('infoLog'); @@ -97,9 +97,9 @@ class MultiColumnExample extends React.PureComponent { to build - * an app with composite navigation system. - * @providesModule NavigationCardStack-NavigationHeader-Tabs-example - */ - -const { - Component, - PropTypes, -} = React; - -const { - NavigationExperimental, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} = ReactNative; - -const { - CardStack: NavigationCardStack, - Header: NavigationHeader, - PropTypes: NavigationPropTypes, - StateUtils: NavigationStateUtils, -} = NavigationExperimental; - -// First Step. -// Define what app navigation state will look like. -function createAppNavigationState(): Object { - return { - // Three tabs. - tabs: { - index: 0, - routes: [ - {key: 'apple'}, - {key: 'banana'}, - {key: 'orange'}, - ], - }, - // Scenes for the `apple` tab. - apple: { - index: 0, - routes: [{key: 'Apple Home'}], - }, - // Scenes for the `banana` tab. - banana: { - index: 0, - routes: [{key: 'Banana Home'}], - }, - // Scenes for the `orange` tab. - orange: { - index: 0, - routes: [{key: 'Orange Home'}], - }, - }; -} - -// Next step. -// Define what app navigation state shall be updated. -function updateAppNavigationState( - state: Object, - action: Object, -): Object { - let {type} = action; - if (type === 'BackAction') { - type = 'pop'; - } - - switch (type) { - case 'push': { - // Push a route into the scenes stack. - const route: Object = action.route; - const {tabs} = state; - const tabKey = tabs.routes[tabs.index].key; - const scenes = state[tabKey]; - const nextScenes = NavigationStateUtils.push(scenes, route); - if (scenes !== nextScenes) { - return { - ...state, - [tabKey]: nextScenes, - }; - } - break; - } - - case 'pop': { - // Pops a route from the scenes stack. - const {tabs} = state; - const tabKey = tabs.routes[tabs.index].key; - const scenes = state[tabKey]; - const nextScenes = NavigationStateUtils.pop(scenes); - if (scenes !== nextScenes) { - return { - ...state, - [tabKey]: nextScenes, - }; - } - break; - } - - case 'selectTab': { - // Switches the tab. - const tabKey: string = action.tabKey; - const tabs = NavigationStateUtils.jumpTo(state.tabs, tabKey); - if (tabs !== state.tabs) { - return { - ...state, - tabs, - }; - } - } - } - return state; -} - -// Next step. -// Defines a helper function that creates a HOC (higher-order-component) -// which provides a function `navigate` through component props. The -// `navigate` function will be used to invoke navigation changes. -// This serves a convenient way for a component to navigate. -function createAppNavigationContainer(ComponentClass) { - const key = '_yourAppNavigationContainerNavigateCall'; - - class Container extends Component { - static contextTypes = { - [key]: PropTypes.func, - }; - - static childContextTypes = { - [key]: PropTypes.func.isRequired, - }; - - static propTypes = { - navigate: PropTypes.func, - }; - - getChildContext(): Object { - return { - [key]: this.context[key] || this.props.navigate, - }; - } - - render(): React.Element { - const navigate = this.context[key] || this.props.navigate; - return ; - } - } - - return Container; -} - -// Next step. -// Define a component for your application that owns the navigation state. -class YourApplication extends Component { - - static propTypes = { - onExampleExit: PropTypes.func, - }; - - // This sets up the initial navigation state. - constructor(props, context) { - super(props, context); - // This sets up the initial navigation state. - this.state = createAppNavigationState(); - this._navigate = this._navigate.bind(this); - } - - render(): React.Element { - // User your own navigator (see next step). - return ( - - ); - } - - // This public method is optional. If exists, the UI explorer will call it - // the "back button" is pressed. Normally this is the cases for Android only. - handleBackAction(): boolean { - return this._navigate({type: 'pop'}); - } - - // This handles the navigation state changes. You're free and responsible - // to define the API that changes that navigation state. In this exmaple, - // we'd simply use a `updateAppNavigationState` to update the navigation - // state. - _navigate(action: Object): void { - if (action.type === 'exit') { - // Exits the example. `this.props.onExampleExit` is provided - // by the UI Explorer. - this.props.onExampleExit && this.props.onExampleExit(); - return; - } - - const state = updateAppNavigationState( - this.state, - action, - ); - - // `updateAppNavigationState` (which uses NavigationStateUtils) gives you - // back the same `state` if nothing has changed. You could use - // that to avoid redundant re-rendering. - if (this.state !== state) { - this.setState(state); - } - } -} - -// Next step. -// Define your own controlled navigator. -const YourNavigator = createAppNavigationContainer(class extends Component { - static propTypes = { - appNavigationState: PropTypes.shape({ - apple: NavigationPropTypes.navigationState.isRequired, - banana: NavigationPropTypes.navigationState.isRequired, - orange: NavigationPropTypes.navigationState.isRequired, - tabs: NavigationPropTypes.navigationState.isRequired, - }), - navigate: PropTypes.func.isRequired, - }; - - // This sets up the methods (e.g. Pop, Push) for navigation. - constructor(props: any, context: any) { - super(props, context); - this._back = this._back.bind(this); - this._renderHeader = this._renderHeader.bind(this); - this._renderScene = this._renderScene.bind(this); - } - - // Now use the `NavigationCardStack` to render the scenes. - render(): React.Element { - const {appNavigationState} = this.props; - const {tabs} = appNavigationState; - const tabKey = tabs.routes[tabs.index].key; - const scenes = appNavigationState[tabKey]; - - return ( - - - - - ); - } - - // Render the header. - // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` - // as type `NavigationSceneRendererProps`. - _renderHeader(sceneProps: Object): React.Element { - return ( - - ); - } - - // Render a scene for route. - // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` - // as type `NavigationSceneRendererProps`. - _renderScene(sceneProps: Object): React.Element { - return ( - - ); - } - - _back() { - this.props.navigate({type: 'pop'}); - } -}); - -// Next step. -// Define your own header. -const YourHeader = createAppNavigationContainer(class extends Component { - static propTypes = { - ...NavigationPropTypes.SceneRendererProps, - navigate: PropTypes.func.isRequired, - }; - - constructor(props: Object, context: any) { - super(props, context); - this._back = this._back.bind(this); - this._renderTitleComponent = this._renderTitleComponent.bind(this); - } - - render(): React.Element { - return ( - - ); - } - - _back(): void { - this.props.navigate({type: 'pop'}); - } - - _renderTitleComponent(props: Object): React.Element { - return ( - - {props.scene.route.key} - - ); - } -}); - -// Next step. -// Define your own scene. -const YourScene = createAppNavigationContainer(class extends Component { - static propTypes = { - ...NavigationPropTypes.SceneRendererProps, - navigate: PropTypes.func.isRequired, - }; - - constructor(props: Object, context: any) { - super(props, context); - this._exit = this._exit.bind(this); - this._popRoute = this._popRoute.bind(this); - this._pushRoute = this._pushRoute.bind(this); - } - - render(): React.Element { - return ( - - - - - - ); - } - - _pushRoute(): void { - // Just push a route with a new unique key. - const route = {key: '[' + this.props.scenes.length + ']-' + Date.now()}; - this.props.navigate({type: 'push', route}); - } - - _popRoute(): void { - this.props.navigate({type: 'pop'}); - } - - _exit(): void { - this.props.navigate({type: 'exit'}); - } -}); - -// Next step. -// Define your own tabs. -const YourTabs = createAppNavigationContainer(class extends Component { - static propTypes = { - navigationState: NavigationPropTypes.navigationState.isRequired, - navigate: PropTypes.func.isRequired, - }; - - constructor(props: Object, context: any) { - super(props, context); - } - - render(): React.Element { - return ( - - {this.props.navigationState.routes.map(this._renderTab, this)} - - ); - } - - _renderTab(route: Object, index: number): React.Element { - return ( - - ); - } -}); - -// Next step. -// Define your own Tab -const YourTab = createAppNavigationContainer(class extends Component { - - static propTypes = { - navigate: PropTypes.func.isRequired, - route: NavigationPropTypes.navigationRoute.isRequired, - selected: PropTypes.bool.isRequired, - }; - - constructor(props: Object, context: any) { - super(props, context); - this._onPress = this._onPress.bind(this); - } - - render(): React.Element { - const style = [styles.tabText]; - if (this.props.selected) { - style.push(styles.tabSelected); - } - return ( - - - {this.props.route.key} - - - ); - } - - _onPress() { - this.props.navigate({type: 'selectTab', tabKey: this.props.route.key}); - } -}); - -const styles = StyleSheet.create({ - navigator: { - flex: 1, - }, - navigatorCardStack: { - flex: 20, - }, - tabs: { - flex: 1, - flexDirection: 'row', - }, - tab: { - alignItems: 'center', - backgroundColor: '#fff', - flex: 1, - justifyContent: 'center', - }, - tabText: { - color: '#222', - fontWeight: '500', - }, - tabSelected: { - color: 'blue', - }, -}); - -module.exports = YourApplication; diff --git a/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-NoGesture-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-NoGesture-example.js deleted file mode 100644 index 4d62aaa9fbddc6..00000000000000 --- a/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-NoGesture-example.js +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @providesModule NavigationCardStack-NoGesture-example - */ -'use strict'; - -const NavigationExampleRow = require('./NavigationExampleRow'); -const React = require('react'); -const ReactNative = require('react-native'); - -/** - * Basic example that shows how to use to build - * an app with controlled navigation system but without gestures. - */ -const { - NavigationExperimental, - ScrollView, - StyleSheet, -} = ReactNative; - -const { - CardStack: NavigationCardStack, - StateUtils: NavigationStateUtils, -} = NavigationExperimental; - -// Step 1: -// Define a component for your application. -class YourApplication extends React.Component { - - // This sets up the initial navigation state. - constructor(props, context) { - super(props, context); - - this.state = { - // This defines the initial navigation state. - navigationState: { - index: 0, // starts with first route focused. - routes: [{key: 'Welcome'}], // starts with only one route. - }, - }; - - this._exit = this._exit.bind(this); - this._onNavigationChange = this._onNavigationChange.bind(this); - } - - // User your own navigator (see Step 2). - render(): React.Element { - return ( - - ); - } - - // This handles the navigation state changes. You're free and responsible - // to define the API that changes that navigation state. In this exmaple, - // we'd simply use a `function(type: string)` to update the navigation state. - _onNavigationChange(type: string): void { - let {navigationState} = this.state; - switch (type) { - case 'push': - // push a new route. - const route = {key: 'route-' + Date.now()}; - navigationState = NavigationStateUtils.push(navigationState, route); - break; - - case 'pop': - navigationState = NavigationStateUtils.pop(navigationState); - break; - } - - // NavigationStateUtils gives you back the same `navigationState` if nothing - // has changed. You could use that to avoid redundant re-rendering. - if (this.state.navigationState !== navigationState) { - this.setState({navigationState}); - } - } - - // Exits the example. `this.props.onExampleExit` is provided - // by the UI Explorer. - _exit(): void { - this.props.onExampleExit && this.props.onExampleExit(); - } - - // This public method is optional. If exists, the UI explorer will call it - // the "back button" is pressed. Normally this is the cases for Android only. - handleBackAction(): boolean { - return this._onNavigationChange('pop'); - } -} - -// Step 2: -// Define your own controlled navigator. -// -// +------------+ -// +-+ | -// +-+ | | -// | | | | -// | | | Active | -// | | | Scene | -// | | | | -// +-+ | | -// +-+ | -// +------------+ -// -class YourNavigator extends React.Component { - - // This sets up the methods (e.g. Pop, Push) for navigation. - constructor(props: any, context: any) { - super(props, context); - - this._onPushRoute = this.props.onNavigationChange.bind(null, 'push'); - this._onPopRoute = this.props.onNavigationChange.bind(null, 'pop'); - - this._renderScene = this._renderScene.bind(this); - } - - // Now use the `NavigationCardStack` to render the scenes. - render(): React.Element { - return ( - - ); - } - - // Render a scene for route. - // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` - // as type `NavigationSceneRendererProps`. - _renderScene(sceneProps: Object): React.Element { - return ( - - ); - } -} - -// Step 3: -// Define your own scene. -class YourScene extends React.Component { - render() { - return ( - - - - - - - ); - } -} - -const styles = StyleSheet.create({ - navigator: { - flex: 1, - }, -}); - -module.exports = YourApplication; diff --git a/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-example.js deleted file mode 100644 index 1029644a02668f..00000000000000 --- a/Examples/UIExplorer/js/NavigationExperimental/NavigationCardStack-example.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * @providesModule NavigationCardStack-example - */ -'use strict'; - -const NavigationExampleRow = require('./NavigationExampleRow'); -const React = require('react'); -const ReactNative = require('react-native'); - -/** - * Basic example that shows how to use to build - * an app with controlled navigation system. - */ -const { - NavigationExperimental, - ScrollView, - StyleSheet, -} = ReactNative; - -const { - CardStack: NavigationCardStack, - StateUtils: NavigationStateUtils, -} = NavigationExperimental; - -// Step 1: -// Define a component for your application. -class YourApplication extends React.Component { - - // This sets up the initial navigation state. - constructor(props, context) { - super(props, context); - - this.state = { - // This defines the initial navigation state. - navigationState: { - index: 0, // starts with first route focused. - routes: [{key: 'Welcome'}], // starts with only one route. - }, - }; - - this._exit = this._exit.bind(this); - this._onNavigationChange = this._onNavigationChange.bind(this); - } - - // User your own navigator (see Step 2). - render(): React.Element { - return ( - - ); - } - - // This handles the navigation state changes. You're free and responsible - // to define the API that changes that navigation state. In this exmaple, - // we'd simply use a `function(type: string)` to update the navigation state. - _onNavigationChange(type: string): void { - let {navigationState} = this.state; - switch (type) { - case 'push': - // push a new route. - const route = {key: 'route-' + Date.now()}; - navigationState = NavigationStateUtils.push(navigationState, route); - break; - - case 'pop': - navigationState = NavigationStateUtils.pop(navigationState); - break; - } - - // NavigationStateUtils gives you back the same `navigationState` if nothing - // has changed. You could use that to avoid redundant re-rendering. - if (this.state.navigationState !== navigationState) { - this.setState({navigationState}); - } - } - - // Exits the example. `this.props.onExampleExit` is provided - // by the UI Explorer. - _exit(): void { - this.props.onExampleExit && this.props.onExampleExit(); - } - - // This public method is optional. If exists, the UI explorer will call it - // the "back button" is pressed. Normally this is the cases for Android only. - handleBackAction(): boolean { - return this._onNavigationChange('pop'); - } -} - -// Step 2: -// Define your own controlled navigator. -// -// +------------+ -// +-+ | -// +-+ | | -// | | | | -// | | | Active | -// | | | Scene | -// | | | | -// +-+ | | -// +-+ | -// +------------+ -// -class YourNavigator extends React.Component { - - // This sets up the methods (e.g. Pop, Push) for navigation. - constructor(props: any, context: any) { - super(props, context); - - this._onPushRoute = this.props.onNavigationChange.bind(null, 'push'); - this._onPopRoute = this.props.onNavigationChange.bind(null, 'pop'); - - this._renderScene = this._renderScene.bind(this); - } - - // Now use the `NavigationCardStack` to render the scenes. - render(): React.Element { - return ( - - ); - } - - // Render a scene for route. - // The detailed spec of `sceneProps` is defined at `NavigationTypeDefinition` - // as type `NavigationSceneRendererProps`. - _renderScene(sceneProps: Object): React.Element { - return ( - - ); - } -} - -// Step 3: -// Define your own scene. -class YourScene extends React.Component { - render() { - return ( - - - - - - - ); - } -} - -const styles = StyleSheet.create({ - navigator: { - flex: 1, - }, -}); - -module.exports = YourApplication; diff --git a/Examples/UIExplorer/js/NavigationExperimental/NavigationExampleRow.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationExampleRow.js deleted file mode 100644 index 5faf80427b672c..00000000000000 --- a/Examples/UIExplorer/js/NavigationExperimental/NavigationExampleRow.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * @providesModule NavigationExampleRow - */ -'use strict'; - -var React = require('react'); -var ReactNative = require('react-native'); -var { - Text, - PixelRatio, - StyleSheet, - View, - TouchableHighlight, -} = ReactNative; - -class NavigationExampleRow extends React.Component { - render() { - if (this.props.onPress) { - return ( - - - {this.props.text} - - - ); - } - return ( - - - {this.props.text} - - - ); - } -} - -const styles = StyleSheet.create({ - row: { - padding: 15, - backgroundColor: 'white', - borderBottomWidth: 1 / PixelRatio.get(), - borderBottomColor: '#CDCDCD', - }, - rowText: { - fontSize: 17, - }, - buttonText: { - fontSize: 17, - fontWeight: '500', - }, -}); - -module.exports = NavigationExampleRow; diff --git a/Examples/UIExplorer/js/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationExperimentalExample.js deleted file mode 100644 index 8b6da3af0249f2..00000000000000 --- a/Examples/UIExplorer/js/NavigationExperimental/NavigationExperimentalExample.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * @providesModule NavigationExperimentalExample - */ -'use strict'; - -const AsyncStorage = require('AsyncStorage'); -const NavigationExampleRow = require('./NavigationExampleRow'); -const React = require('react'); -const ScrollView = require('ScrollView'); -const StyleSheet = require('StyleSheet'); -const View = require('View'); - -/* - * Heads up! This file is not the real navigation example- only a utility to switch between them. - * - * To learn how to use the Navigation API, take a look at the following example files: - */ -const EXAMPLES = { - 'CardStack + Header + Tabs Example': require('./NavigationCardStack-NavigationHeader-Tabs-example'), - 'CardStack Example': require('./NavigationCardStack-example'), - 'CardStack Without Gestures Example': require('./NavigationCardStack-NoGesture-example'), - 'Transitioner + Animated View Example': require('./NavigationTransitioner-AnimatedView-example'), - 'Transitioner + Animated View Pager Example': require('./NavigationTransitioner-AnimatedView-pager-example'), -}; - -const EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample'; - -class NavigationExperimentalExample extends React.Component { - static title = 'Navigation (Experimental)'; - static description = 'Upcoming navigation APIs and animated navigation views'; - static external = true; - - state = { - example: null, - }; - - componentDidMount() { - AsyncStorage.getItem(EXAMPLE_STORAGE_KEY, (err, example) => { - if (err || !example || !EXAMPLES[example]) { - this.setState({ - example: 'menu', - }); - return; - } - this.setState({ - example, - }); - }); - } - - setExample = (example) => { - this.setState({ - example, - }); - AsyncStorage.setItem(EXAMPLE_STORAGE_KEY, example); - }; - - _renderMenu = () => { - let exitRow = null; - if (this.props.onExampleExit) { - exitRow = ( - - ); - } - return ( - - - {this._renderExampleList()} - {exitRow} - - - ); - }; - - _renderExampleList = () => { - return Object.keys(EXAMPLES).map(exampleName => ( - { - this.setExample(exampleName); - }} - /> - )); - }; - - _exitInnerExample = () => { - this.setExample('menu'); - }; - - handleBackAction = () => { - const wasHandledByExample = ( - this.exampleRef && - this.exampleRef.handleBackAction && - this.exampleRef.handleBackAction() - ); - if (wasHandledByExample) { - return true; - } - if (this.state.example && this.state.example !== 'menu') { - this._exitInnerExample(); - return true; - } - return false; - }; - - render() { - if (this.state.example === 'menu') { - return this._renderMenu(); - } - if (EXAMPLES[this.state.example]) { - const Component = EXAMPLES[this.state.example]; - return ( - { this.exampleRef = exampleRef; }} - /> - ); - } - return null; - } -} - -const styles = StyleSheet.create({ - menu: { - backgroundColor: '#E9E9EF', - flex: 1, - marginTop: 20, - }, -}); - -module.exports = NavigationExperimentalExample; diff --git a/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js deleted file mode 100644 index b408251c0f7948..00000000000000 --- a/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-example.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @flow - * @providesModule NavigationTransitioner-AnimatedView-example - */ -'use strict'; - -const NavigationExampleRow = require('./NavigationExampleRow'); -const React = require('react'); -const ReactNative = require('react-native'); - -/** - * Basic example that shows how to use and - * to build a stack of animated scenes that render the - * navigation state. - */ - - -import type { - NavigationSceneRendererProps, - NavigationState, - NavigationTransitionProps, - NavigationTransitionSpec, -} from 'NavigationTypeDefinition'; - -const { - Component, - PropTypes, -} = React; - -const { - Animated, - Easing, - NavigationExperimental, - ScrollView, - StyleSheet, -} = ReactNative; - -const { - PropTypes: NavigationPropTypes, - StateUtils: NavigationStateUtils, - Transitioner: NavigationTransitioner, -} = NavigationExperimental; - -function reducer(state: ?NavigationState, action: any): NavigationState { - if (!state) { - return { - index: 0, - routes: [{key: 'route-1'}], - }; - } - - switch (action) { - case 'push': - const route = {key: 'route-' + (state.routes.length + 1)}; - return NavigationStateUtils.push(state, route); - case 'pop': - return NavigationStateUtils.pop(state); - } - return state; -} - -class Example extends Component { - state: NavigationState; - constructor(props: any, context: any) { - super(props, context); - this.state = reducer(); - } - - render(): React.Element { - return ( - this._navigate(action)} - /> - ); - } - - _navigate(action: any): boolean { - if (action === 'exit') { - // Exits the example. `this.props.onExampleExit` is provided - // by the UI Explorer. - this.props.onExampleExit && this.props.onExampleExit(); - return false; - } - - const state = reducer(this.state, action); - if (state === this.state) { - return false; - } - - this.setState(state); - return true; - } - - // This public method is optional. If exists, the UI explorer will call it - // the "back button" is pressed. Normally this is the cases for Android only. - handleBackAction(): boolean { - return this._navigate('pop'); - } -} - -class ExampleNavigator extends Component { - props: { - navigate: Function, - navigationState: NavigationState, - }; - - static propTypes: { - navigationState: NavigationPropTypes.navigationState.isRequired, - navigate: PropTypes.func.isRequired, - }; - - render(): React.Element { - return ( - this._render(transitionProps)} - configureTransition={this._configureTransition} - /> - ); - } - - _render( - transitionProps: NavigationTransitionProps, - ): Array> { - return transitionProps.scenes.map((scene) => { - const sceneProps = { - ...transitionProps, - scene, - }; - return this._renderScene(sceneProps); - }); - } - - _renderScene( - sceneProps: NavigationSceneRendererProps, - ): React.Element { - return ( - - ); - } - - _configureTransition(): NavigationTransitionSpec { - const easing: any = Easing.inOut(Easing.ease); - return { - duration: 500, - easing, - }; - } -} - -class ExampleScene extends Component { - props: NavigationSceneRendererProps & { - navigate: Function, - }; - - static propTypes = { - ...NavigationPropTypes.SceneRendererProps, - navigate: PropTypes.func.isRequired, - }; - - render(): React.Element { - const {scene, navigate} = this.props; - return ( - - - - navigate('push')} - /> - navigate('pop')} - /> - navigate('exit')} - /> - - - ); - } - - _getAnimatedStyle(): Object { - const { - layout, - position, - scene, - } = this.props; - - const { - index, - } = scene; - - const inputRange = [index - 1, index, index + 1]; - const width = layout.initWidth; - const translateX = position.interpolate({ - inputRange, - outputRange: ([width, 0, -10]: Array), - }); - - return { - transform: [ - { translateX }, - ], - }; - } -} - -const styles = StyleSheet.create({ - scene: { - backgroundColor: '#E9E9EF', - bottom: 0, - flex: 1, - left: 0, - position: 'absolute', - right: 0, - shadowColor: 'black', - shadowOffset: {width: 0, height: 0}, - shadowOpacity: 0.4, - shadowRadius: 10, - top: 0, - }, - scrollView: { - flex: 1, - }, -}); - -module.exports = Example; diff --git a/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js b/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js deleted file mode 100644 index 755a9b2df0cb94..00000000000000 --- a/Examples/UIExplorer/js/NavigationExperimental/NavigationTransitioner-AnimatedView-pager-example.js +++ /dev/null @@ -1,265 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * @flow - * @providesModule NavigationTransitioner-AnimatedView-pager-example - */ -'use strict'; - -const NavigationExampleRow = require('./NavigationExampleRow'); -const React = require('react'); -const ReactNative = require('react-native'); - -/** - * Basic example that shows how to use and - * to build a list of animated scenes that render the - * navigation state. - */ - -import type { - NavigationSceneRendererProps, - NavigationState, - NavigationTransitionProps, -} from 'NavigationTypeDefinition'; - -const { - Component, - PropTypes, -} = React; - -const { - Animated, - NavigationExperimental, - StyleSheet, - Text, - View, -} = ReactNative; - -const { - PropTypes: NavigationPropTypes, - StateUtils: NavigationStateUtils, - Transitioner: NavigationTransitioner, - Card: NavigationCard, -} = NavigationExperimental; - -const { - PagerPanResponder: NavigationPagerPanResponder, - PagerStyleInterpolator: NavigationPagerStyleInterpolator, -} = NavigationCard; - -function reducer(state: ?NavigationState, action: any): NavigationState { - if (!state) { - return { - index: 0, - routes: [ - {key: 'Step 1', color: '#ff0000'}, - {key: 'Step 2', color: '#ff7f00'}, - {key: 'Step 3', color: '#ffff00'}, - {key: 'Step 4', color: '#00ff00'}, - {key: 'Step 5', color: '#0000ff'}, - {key: 'Step 6', color: '#4b0082'}, - {key: 'Step 7', color: '#8f00ff'}, - ], - }; - } - - switch (action) { - case 'back': - return NavigationStateUtils.back(state); - case 'forward': - return NavigationStateUtils.forward(state); - } - return state; -} - -class Example extends Component { - state: NavigationState; - constructor(props: any, context: any) { - super(props, context); - this.state = reducer(); - } - - render(): React.Element { - return ( - - this._navigate(action)} - /> - this._navigate('exit')} - /> - - ); - } - - _navigate(action: string): boolean { - if (action === 'exit') { - // Exits the example. `this.props.onExampleExit` is provided - // by the UI Explorer. - this.props.onExampleExit && this.props.onExampleExit(); - return false; - } - - const state = reducer(this.state, action); - if (state === this.state) { - return false; - } - - this.setState(state); - return true; - } - - // This public method is optional. If exists, the UI explorer will call it - // the "back button" is pressed. Normally this is the cases for Android only. - handleBackAction(): boolean { - return this._navigate('back'); - } -} - -class ExampleNavigator extends Component { - _render: Function; - _renderScene: Function; - - props: { - navigate: Function, - navigationState: NavigationState, - }; - - static propTypes: { - navigationState: NavigationPropTypes.navigationState.isRequired, - navigate: PropTypes.func.isRequired, - }; - - constructor(props, context) { - super(props, context); - this._render = this._render.bind(this); - this._renderScene = this._renderScene.bind(this); - } - - render(): React.Element { - return ( - - ); - } - - _render( - transitionProps: NavigationTransitionProps, - ): React.Element { - const scenes = transitionProps.scenes.map((scene) => { - const sceneProps = { - ...transitionProps, - scene, - }; - return this._renderScene(sceneProps); - }); - return ( - - {scenes} - - ); - } - - _renderScene( - sceneProps: NavigationSceneRendererProps, - ): React.Element { - return ( - - ); - } -} - -class ExampleScene extends Component { - props: NavigationSceneRendererProps & { - navigate: Function, - }; - - static propTypes = { - ...NavigationPropTypes.SceneRendererProps, - navigate: PropTypes.func.isRequired, - }; - - render(): React.Element { - const {scene, navigate} = this.props; - - const panHandlers = NavigationPagerPanResponder.forHorizontal({ - ...this.props, - onNavigateBack: () => navigate('back'), - onNavigateForward: () => navigate('forward'), - }); - - const route: any = scene.route; - const style = [ - styles.scene, - {backgroundColor: route.color}, - NavigationPagerStyleInterpolator.forHorizontal(this.props), - ]; - - return ( - - - - {scene.route.key} - - - - ); - } -} - -const styles = StyleSheet.create({ - example: { - flex: 1, - }, - navigator: { - flex: 1, - }, - scene: { - backgroundColor: '#000', - bottom: 0, - flex: 1, - left: 0, - position: 'absolute', - right: 0, - top: 0, - }, - heading: { - alignItems : 'center', - flex: 1, - justifyContent: 'center', - }, - headingText: { - color: '#222', - fontSize: 24, - fontWeight: 'bold', - }, -}); - -module.exports = Example; diff --git a/Examples/UIExplorer/js/SectionListExample.js b/Examples/UIExplorer/js/SectionListExample.js index 41d3279f788ba0..08ed9572d8976e 100644 --- a/Examples/UIExplorer/js/SectionListExample.js +++ b/Examples/UIExplorer/js/SectionListExample.js @@ -26,12 +26,12 @@ const React = require('react'); const ReactNative = require('react-native'); const { + SectionList, StyleSheet, Text, View, } = ReactNative; -const SectionList = require('SectionList'); const UIExplorerPage = require('./UIExplorerPage'); const infoLog = require('infoLog'); @@ -81,7 +81,9 @@ class SectionListExample extends React.PureComponent { }; render() { const filterRegex = new RegExp(String(this.state.filterText), 'i'); - const filter = (item) => (filterRegex.test(item.text) || filterRegex.test(item.title)); + const filter = (item) => ( + filterRegex.test(item.text) || filterRegex.test(item.title) + ); const filteredData = this.state.data.filter(filter); return ( } - ItemSeparatorComponent={() => } + SectionSeparatorComponent={() => + + } + ItemSeparatorComponent={() => + + } enableVirtualization={this.state.virtualized} onRefresh={() => alert('onRefresh: nothing to refresh :P')} onViewableItemsChanged={this._onViewableItemsChanged} @@ -117,8 +123,8 @@ class SectionListExample extends React.PureComponent { {title: 'Item In Header Section', text: 'Section s1', key: '0'}, ]}, {key: 's2', data: [ - {noImage: true, title: 'First item', text: 'Section s2', key: '0'}, - {noImage: true, title: 'Second item', text: 'Section s2', key: '1'}, + {noImage: true, title: '1st item', text: 'Section s2', key: '0'}, + {noImage: true, title: '2nd item', text: 'Section s2', key: '1'}, ]}, {key: 'Filtered Items', data: filteredData}, ]} @@ -127,11 +133,18 @@ class SectionListExample extends React.PureComponent { ); } - _renderItemComponent = ({item}) => ; - // This is called when items change viewability by scrolling into our out of the viewable area. + _renderItemComponent = ({item}) => ( + + ); + // This is called when items change viewability by scrolling into our out of + // the viewable area. _onViewableItemsChanged = (info: { changed: Array<{ - key: string, isViewable: boolean, item: {columns: Array<*>}, index: ?number, section?: any + key: string, + isViewable: boolean, + item: {columns: Array<*>}, + index: ?number, + section?: any }>}, ) => { // Impressions can be logged here diff --git a/Examples/UIExplorer/js/UIExplorerApp.android.js b/Examples/UIExplorer/js/UIExplorerApp.android.js index 683f53b1342ab1..1e13a212ceb6bd 100644 --- a/Examples/UIExplorer/js/UIExplorerApp.android.js +++ b/Examples/UIExplorer/js/UIExplorerApp.android.js @@ -25,7 +25,7 @@ const AppRegistry = require('AppRegistry'); const AsyncStorage = require('AsyncStorage'); -const BackAndroid = require('BackAndroid'); +const BackHandler = require('BackHandler'); const Dimensions = require('Dimensions'); const DrawerLayoutAndroid = require('DrawerLayoutAndroid'); const Linking = require('Linking'); @@ -73,7 +73,7 @@ class UIExplorerApp extends React.Component { state: UIExplorerNavigationState; componentWillMount() { - BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress); + BackHandler.addEventListener('hardwareBackPress', this._handleBackButtonPress); } componentDidMount() { diff --git a/Examples/UIExplorer/js/UIExplorerApp.ios.js b/Examples/UIExplorer/js/UIExplorerApp.ios.js index 802413c0468894..f57e4fbf8c35bb 100644 --- a/Examples/UIExplorer/js/UIExplorerApp.ios.js +++ b/Examples/UIExplorer/js/UIExplorerApp.ios.js @@ -24,6 +24,7 @@ 'use strict'; const AsyncStorage = require('AsyncStorage'); +const BackHandler = require('BackHandler'); const Linking = require('Linking'); const React = require('react'); const ReactNative = require('react-native'); @@ -68,6 +69,10 @@ class UIExplorerApp extends React.Component { props: Props; state: UIExplorerNavigationState; + componentWillMount() { + BackHandler.addEventListener('hardwareBackPress', this._handleBack); + } + componentDidMount() { Linking.getInitialURL().then((url) => { AsyncStorage.getItem(APP_STATE_KEY, (err, storedString) => { diff --git a/Examples/UIExplorer/js/UIExplorerExampleList.js b/Examples/UIExplorer/js/UIExplorerExampleList.js index aea6911313c8c5..b805291832d9fc 100644 --- a/Examples/UIExplorer/js/UIExplorerExampleList.js +++ b/Examples/UIExplorer/js/UIExplorerExampleList.js @@ -201,6 +201,7 @@ const styles = StyleSheet.create({ backgroundColor: '#eeeeee', }, sectionHeader: { + backgroundColor: '#eeeeee', padding: 5, fontWeight: '500', fontSize: 11, diff --git a/Examples/UIExplorer/js/UIExplorerList.android.js b/Examples/UIExplorer/js/UIExplorerList.android.js index a5ac35b1a33a1f..09d81eb19c42ab 100644 --- a/Examples/UIExplorer/js/UIExplorerList.android.js +++ b/Examples/UIExplorer/js/UIExplorerList.android.js @@ -192,10 +192,6 @@ const APIExamples: Array = [ key: 'NativeAnimationsExample', module: require('./NativeAnimationsExample'), }, - { - key: 'NavigationExperimentalExample', - module: require('./NavigationExperimental/NavigationExperimentalExample'), - }, { key: 'NetInfoExample', module: require('./NetInfoExample'), diff --git a/Examples/UIExplorer/js/UIExplorerList.ios.js b/Examples/UIExplorer/js/UIExplorerList.ios.js index 0c130d74fff5f5..9ac8aeeeb3fe2b 100644 --- a/Examples/UIExplorer/js/UIExplorerList.ios.js +++ b/Examples/UIExplorer/js/UIExplorerList.ios.js @@ -80,11 +80,6 @@ const ComponentExamples: Array = [ module: require('./ListViewPagingExample'), supportsTVOS: true, }, - { - key: 'MapViewExample', - module: require('./MapViewExample'), - supportsTVOS: true, - }, { key: 'ModalExample', module: require('./ModalExample'), @@ -298,11 +293,6 @@ const APIExamples: Array = [ module: require('./NativeAnimationsExample'), supportsTVOS: true, }, - { - key: 'NavigationExperimentalExample', - module: require('./NavigationExperimental/NavigationExperimentalExample'), - supportsTVOS: true, - }, { key: 'NetInfoExample', module: require('./NetInfoExample'), diff --git a/Libraries/Alert/AlertIOS.js b/Libraries/Alert/AlertIOS.js index 500d0708d689cd..4a8143024a14ca 100644 --- a/Libraries/Alert/AlertIOS.js +++ b/Libraries/Alert/AlertIOS.js @@ -61,7 +61,7 @@ export type AlertButtonStyle = $Enum<{ * @property {Function=} onPress Callback function when button pressed * @property {AlertButtonStyle=} style Button style */ -type ButtonsArray = Array<{ +export type ButtonsArray = Array<{ /** * Button label */ diff --git a/Libraries/Animated/src/Animated.js b/Libraries/Animated/src/Animated.js index a0c0482aea1589..1eacd03c48c66e 100644 --- a/Libraries/Animated/src/Animated.js +++ b/Libraries/Animated/src/Animated.js @@ -16,12 +16,22 @@ var AnimatedImplementation = require('AnimatedImplementation'); var Image = require('Image'); var Text = require('Text'); var View = require('View'); -var ScrollView = require('ScrollView'); -module.exports = { - ...AnimatedImplementation, +let AnimatedScrollView; + +const Animated = { View: AnimatedImplementation.createAnimatedComponent(View), Text: AnimatedImplementation.createAnimatedComponent(Text), Image: AnimatedImplementation.createAnimatedComponent(Image), - ScrollView: AnimatedImplementation.createAnimatedComponent(ScrollView), + get ScrollView() { + // Make this lazy to avoid circular reference. + if (!AnimatedScrollView) { + AnimatedScrollView = AnimatedImplementation.createAnimatedComponent(require('ScrollView')); + } + return AnimatedScrollView; + }, }; + +Object.assign((Animated: Object), AnimatedImplementation); + +module.exports = ((Animated: any): (typeof AnimatedImplementation) & typeof Animated); diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 550c8062db3fc7..e0860eced71cac 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -2151,9 +2151,80 @@ type EventConfig = { useNativeDriver?: bool, }; +function attachNativeEvent(viewRef: any, eventName: string, argMapping: Array) { + // Find animated values in `argMapping` and create an array representing their + // key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x']. + const eventMappings = []; + + const traverse = (value, path) => { + if (value instanceof AnimatedValue) { + value.__makeNative(); + + eventMappings.push({ + nativeEventPath: path, + animatedValueTag: value.__getNativeTag(), + }); + } else if (typeof value === 'object') { + for (const key in value) { + traverse(value[key], path.concat(key)); + } + } + }; + + invariant( + argMapping[0] && argMapping[0].nativeEvent, + 'Native driven events only support animated values contained inside `nativeEvent`.' + ); + + // Assume that the event containing `nativeEvent` is always the first argument. + traverse(argMapping[0].nativeEvent, []); + + const viewTag = ReactNative.findNodeHandle(viewRef); + + eventMappings.forEach((mapping) => { + NativeAnimatedAPI.addAnimatedEventToView(viewTag, eventName, mapping); + }); + + return { + detach() { + eventMappings.forEach((mapping) => { + NativeAnimatedAPI.removeAnimatedEventFromView( + viewTag, + eventName, + mapping.animatedValueTag, + ); + }); + }, + }; +} + +function forkEvent(event: ?AnimatedEvent | ?Function, listener: Function): AnimatedEvent | Function { + if (!event) { + return listener; + } else if (event instanceof AnimatedEvent) { + event.__addListener(listener); + return event; + } else { + return (...args) => { + typeof event === 'function' && event(...args); + listener(...args); + }; + } +} + +function unforkEvent(event: ?AnimatedEvent | ?Function, listener: Function): void { + if (event && event instanceof AnimatedEvent) { + event.__removeListener(listener); + } +} + class AnimatedEvent { _argMapping: Array; - _listener: ?Function; + _listeners: Array = []; + _callListeners: Function; + _attachedEvent: ?{ + detach: () => void, + }; __isNative: bool; constructor( @@ -2161,7 +2232,11 @@ class AnimatedEvent { config?: EventConfig = {} ) { this._argMapping = argMapping; - this._listener = config.listener; + if (config.listener) { + this.__addListener(config.listener); + } + this._callListeners = this._callListeners.bind(this); + this._attachedEvent = null; this.__isNative = shouldUseNativeDriver(config); if (__DEV__) { @@ -2169,52 +2244,29 @@ class AnimatedEvent { } } - __attach(viewRef, eventName) { - invariant(this.__isNative, 'Only native driven events need to be attached.'); - - // Find animated values in `argMapping` and create an array representing their - // key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x']. - const eventMappings = []; - - const traverse = (value, path) => { - if (value instanceof AnimatedValue) { - value.__makeNative(); - - eventMappings.push({ - nativeEventPath: path, - animatedValueTag: value.__getNativeTag(), - }); - } else if (typeof value === 'object') { - for (const key in value) { - traverse(value[key], path.concat(key)); - } - } - }; - - invariant( - this._argMapping[0] && this._argMapping[0].nativeEvent, - 'Native driven events only support animated values contained inside `nativeEvent`.' - ); + __addListener(callback: Function): void { + this._listeners.push(callback); + } - // Assume that the event containing `nativeEvent` is always the first argument. - traverse(this._argMapping[0].nativeEvent, []); + __removeListener(callback: Function): void { + this._listeners = this._listeners.filter((listener) => listener !== callback); + } - const viewTag = ReactNative.findNodeHandle(viewRef); + __attach(viewRef, eventName) { + invariant(this.__isNative, 'Only native driven events need to be attached.'); - eventMappings.forEach((mapping) => { - NativeAnimatedAPI.addAnimatedEventToView(viewTag, eventName, mapping); - }); + this._attachedEvent = attachNativeEvent(viewRef, eventName, this._argMapping); } __detach(viewTag, eventName) { invariant(this.__isNative, 'Only native driven events need to be detached.'); - NativeAnimatedAPI.removeAnimatedEventFromView(viewTag, eventName); + this._attachedEvent && this._attachedEvent.detach(); } __getHandler() { if (this.__isNative) { - return this._listener; + return this._callListeners; } return (...args) => { @@ -2233,13 +2285,14 @@ class AnimatedEvent { traverse(mapping, args[idx], 'arg' + idx); }); } - - if (this._listener) { - this._listener.apply(null, args); - } + this._callListeners(...args); }; } + _callListeners(...args) { + this._listeners.forEach(listener => listener(...args)); + } + _validateMapping() { const traverse = (recMapping, recEvt, key) => { if (typeof recEvt === 'number') { @@ -2582,5 +2635,18 @@ module.exports = { */ createAnimatedComponent, + /** + * Imperative API to attach an animated value to an event on a view. Prefer using + * `Animated.event` with `useNativeDrive: true` if possible. + */ + attachNativeEvent, + + /** + * Advanced imperative API for snooping on animated events that are passed in through props. Use + * values directly where possible. + */ + forkEvent, + unforkEvent, + __PropsOnlyForTests: AnimatedProps, }; diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index 74c71c7dc54ba4..72c22ef63af1c8 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -93,9 +93,9 @@ const API = { assertNativeAnimatedModule(); NativeAnimatedModule.addAnimatedEventToView(viewTag, eventName, eventMapping); }, - removeAnimatedEventFromView(viewTag: ?number, eventName: string) { + removeAnimatedEventFromView(viewTag: ?number, eventName: string, animatedNodeTag: ?number) { assertNativeAnimatedModule(); - NativeAnimatedModule.removeAnimatedEventFromView(viewTag, eventName); + NativeAnimatedModule.removeAnimatedEventFromView(viewTag, eventName, animatedNodeTag); } }; diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js index be7838ecb24167..fc313a8af0582b 100644 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ b/Libraries/Animated/src/__tests__/Animated-test.js @@ -353,6 +353,22 @@ describe('Animated tests', () => { expect(listener.mock.calls.length).toBe(1); expect(listener).toBeCalledWith({foo: 42}); }); + it('should call forked event listeners', () => { + var value = new Animated.Value(0); + var listener = jest.fn(); + var handler = Animated.event( + [{foo: value}], + {listener}, + ); + var listener2 = jest.fn(); + var forkedHandler = Animated.forkEvent(handler, listener2); + forkedHandler({foo: 42}); + expect(value.__getValue()).toBe(42); + expect(listener.mock.calls.length).toBe(1); + expect(listener).toBeCalledWith({foo: 42}); + expect(listener2.mock.calls.length).toBe(1); + expect(listener2).toBeCalledWith({foo: 42}); + }); }); describe('Animated Interactions', () => { diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 581989684c078a..2a4c23f9721314 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -81,6 +81,7 @@ var getPhotosParamChecker = createStrictShapeTypeChecker({ * Shape of the return value of the `getPhotos` function. */ var getPhotosReturnChecker = createStrictShapeTypeChecker({ + // $FlowFixMe(>=0.41.0) edges: ReactPropTypes.arrayOf(createStrictShapeTypeChecker({ node: createStrictShapeTypeChecker({ type: ReactPropTypes.string.isRequired, diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js index c8024048cd6254..bb001d7e166d12 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -35,6 +35,7 @@ type Event = Object; * the user's change will be reverted immediately to reflect `props.date` as the * source of truth. */ +// $FlowFixMe(>=0.41.0) const DatePickerIOS = React.createClass({ // TOOD: Put a better type for _picker _picker: (undefined: ?$FlowFixMe), @@ -102,6 +103,7 @@ const DatePickerIOS = React.createClass({ this.props.onDateChange && this.props.onDateChange( new Date(nativeTimeStamp) ); + // $FlowFixMe(>=0.41.0) this.props.onChange && this.props.onChange(event); // We expect the onChange* handlers to be in charge of updating our `date` diff --git a/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/Libraries/Components/Keyboard/KeyboardAvoidingView.js index ec7d67187855ea..f20fb0323da9cd 100644 --- a/Libraries/Components/Keyboard/KeyboardAvoidingView.js +++ b/Libraries/Components/Keyboard/KeyboardAvoidingView.js @@ -52,6 +52,7 @@ const viewRef = 'VIEW'; * It is a component to solve the common problem of views that need to move out of the way of the virtual keyboard. * It can automatically adjust either its position or bottom padding based on the position of the keyboard. */ +// $FlowFixMe(>=0.41.0) const KeyboardAvoidingView = React.createClass({ mixins: [TimerMixin], @@ -153,6 +154,7 @@ const KeyboardAvoidingView = React.createClass({ }, render(): React.Element { + // $FlowFixMe(>=0.41.0) const {behavior, children, style, ...props} = this.props; switch (behavior) { diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js deleted file mode 100644 index 5b5a31282632c2..00000000000000 --- a/Libraries/Components/MapView/MapView.js +++ /dev/null @@ -1,563 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule MapView - * @flow - */ -'use strict'; - -const ColorPropType = require('ColorPropType'); -const EdgeInsetsPropType = require('EdgeInsetsPropType'); -const Image = require('Image'); -const NativeMethodsMixin = require('NativeMethodsMixin'); -const React = require('React'); -const StyleSheet = require('StyleSheet'); -const View = require('View'); - -const deprecatedPropType = require('deprecatedPropType'); -const processColor = require('processColor'); -const resolveAssetSource = require('resolveAssetSource'); -const requireNativeComponent = require('requireNativeComponent'); - -type Event = Object; - -/** - * State of an annotation on the map. - */ -export type AnnotationDragState = $Enum<{ - /** - * Annotation is not being touched. - */ - idle: string, - /** - * Annotation dragging has began. - */ - starting: string, - /** - * Annotation is being dragged. - */ - dragging: string, - /** - * Annotation dragging is being canceled. - */ - canceling: string, - /** - * Annotation dragging has ended. - */ - ending: string, -}>; - -/** - * **IMPORTANT: This component is now DEPRECATED and will be removed - * in January 2017 (React Native version 0.42). This component only supports - * iOS.** - * - * **Please use - * [react-native-maps](https://github.com/airbnb/react-native-maps) by Airbnb - * instead of this component.** Our friends at Airbnb have done an amazing job - * building a cross-platform `MapView` component that is more feature - * complete. It is used extensively (over 9k installs / month). - * - * `MapView` is used to display embeddable maps and annotations using - * `MKMapView`. - * - * ``` - * import React, { Component } from 'react'; - * import { MapView } from 'react-native'; - * - * class MapMyRide extends Component { - * render() { - * return ( - * - * ); - * } - * } - * ``` - * - */ - -const MapView = React.createClass({ - - mixins: [NativeMethodsMixin], - - propTypes: { - ...View.propTypes, - /** - * Used to style and layout the `MapView`. - */ - style: View.propTypes.style, - - /** - * If `true` the app will ask for the user's location and display it on - * the map. Default value is `false`. - * - * **NOTE**: You'll need to add the `NSLocationWhenInUseUsageDescription` - * key in Info.plist to enable geolocation, otherwise it will fail silently. - */ - showsUserLocation: React.PropTypes.bool, - - /** - * If `true` the map will follow the user's location whenever it changes. - * Note that this has no effect unless `showsUserLocation` is enabled. - * Default value is `true`. - */ - followUserLocation: React.PropTypes.bool, - - /** - * If `false` points of interest won't be displayed on the map. - * Default value is `true`. - */ - showsPointsOfInterest: React.PropTypes.bool, - - /** - * If `false`, compass won't be displayed on the map. - * Default value is `true`. - */ - showsCompass: React.PropTypes.bool, - - /** - * If `true` the map will show the callouts for all annotations without - * the user having to click on the annotation. - * Default value is `false`. - */ - showsAnnotationCallouts: React.PropTypes.bool, - - /** - * If `false` the user won't be able to pinch/zoom the map. - * Default value is `true`. - */ - zoomEnabled: React.PropTypes.bool, - - /** - * When this property is set to `true` and a valid camera is associated with - * the map, the camera's heading angle is used to rotate the plane of the - * map around its center point. - * - * When this property is set to `false`, the - * camera's heading angle is ignored and the map is always oriented so - * that true north is situated at the top of the map view - */ - rotateEnabled: React.PropTypes.bool, - - /** - * When this property is set to `true` and a valid camera is associated - * with the map, the camera's pitch angle is used to tilt the plane - * of the map. - * - * When this property is set to `false`, the camera's pitch - * angle is ignored and the map is always displayed as if the user - * is looking straight down onto it. - */ - pitchEnabled: React.PropTypes.bool, - - /** - * If `false` the user won't be able to change the map region being displayed. - * Default value is `true`. - */ - scrollEnabled: React.PropTypes.bool, - - /** - * The map type to be displayed. - * - * - `standard`: Standard road map (default). - * - `satellite`: Satellite view. - * - `hybrid`: Satellite view with roads and points of interest overlaid. - */ - mapType: React.PropTypes.oneOf([ - 'standard', - 'satellite', - 'hybrid', - ]), - - /** - * The region to be displayed by the map. - * - * The region is defined by the center coordinates and the span of - * coordinates to display. - */ - region: React.PropTypes.shape({ - /** - * Coordinates for the center of the map. - */ - latitude: React.PropTypes.number.isRequired, - longitude: React.PropTypes.number.isRequired, - - /** - * Distance between the minimum and the maximum latitude/longitude - * to be displayed. - */ - latitudeDelta: React.PropTypes.number, - longitudeDelta: React.PropTypes.number, - }), - - /** - * Map annotations with title and subtitle. - */ - annotations: React.PropTypes.arrayOf(React.PropTypes.shape({ - /** - * The location of the annotation. - */ - latitude: React.PropTypes.number.isRequired, - longitude: React.PropTypes.number.isRequired, - - /** - * Whether the pin drop should be animated or not - */ - animateDrop: React.PropTypes.bool, - - /** - * Whether the pin should be draggable or not - */ - draggable: React.PropTypes.bool, - - /** - * Event that fires when the annotation drag state changes. - */ - onDragStateChange: React.PropTypes.func, - - /** - * Event that fires when the annotation gets was tapped by the user - * and the callout view was displayed. - */ - onFocus: React.PropTypes.func, - - /** - * Event that fires when another annotation or the mapview itself - * was tapped and a previously shown annotation will be closed. - */ - onBlur: React.PropTypes.func, - - /** - * Annotation title and subtile. - */ - title: React.PropTypes.string, - subtitle: React.PropTypes.string, - - /** - * Callout views. - */ - leftCalloutView: React.PropTypes.element, - rightCalloutView: React.PropTypes.element, - detailCalloutView: React.PropTypes.element, - - /** - * The pin color. This can be any valid color string, or you can use one - * of the predefined PinColors constants. Applies to both standard pins - * and custom pin images. - * - * Note that on iOS 8 and earlier, only the standard PinColor constants - * are supported for regular pins. For custom pin images, any tintColor - * value is supported on all iOS versions. - */ - tintColor: ColorPropType, - - /** - * Custom pin image. This must be a static image resource inside the app. - */ - image: Image.propTypes.source, - - /** - * Custom pin view. If set, this replaces the pin or custom pin image. - */ - view: React.PropTypes.element, - - /** - * annotation id - */ - id: React.PropTypes.string, - - /** - * Deprecated. Use the left/right/detailsCalloutView props instead. - */ - hasLeftCallout: deprecatedPropType( - React.PropTypes.bool, - 'Use `leftCalloutView` instead.' - ), - hasRightCallout: deprecatedPropType( - React.PropTypes.bool, - 'Use `rightCalloutView` instead.' - ), - onLeftCalloutPress: deprecatedPropType( - React.PropTypes.func, - 'Use `leftCalloutView` instead.' - ), - onRightCalloutPress: deprecatedPropType( - React.PropTypes.func, - 'Use `rightCalloutView` instead.' - ), - })), - - /** - * Map overlays - */ - overlays: React.PropTypes.arrayOf(React.PropTypes.shape({ - /** - * Polyline coordinates - */ - coordinates: React.PropTypes.arrayOf(React.PropTypes.shape({ - latitude: React.PropTypes.number.isRequired, - longitude: React.PropTypes.number.isRequired - })), - - /** - * Line attributes - */ - lineWidth: React.PropTypes.number, - strokeColor: ColorPropType, - fillColor: ColorPropType, - - /** - * Overlay id - */ - id: React.PropTypes.string - })), - - /** - * Maximum size of the area that can be displayed. - */ - maxDelta: React.PropTypes.number, - - /** - * Minimum size of the area that can be displayed. - */ - minDelta: React.PropTypes.number, - - /** - * Insets for the map's legal label, originally at bottom left of the map. - */ - legalLabelInsets: EdgeInsetsPropType, - - /** - * Callback that is called continuously when the user is dragging the map. - */ - onRegionChange: React.PropTypes.func, - - /** - * Callback that is called once, when the user is done moving the map. - */ - onRegionChangeComplete: React.PropTypes.func, - - /** - * Deprecated. Use annotation `onFocus` and `onBlur` instead. - */ - onAnnotationPress: React.PropTypes.func, - - /** - * @platform android - */ - active: React.PropTypes.bool, - }, - - statics: { - /** - * Standard iOS MapView pin color constants, to be used with the - * `annotation.tintColor` property. On iOS 8 and earlier these are the - * only supported values when using regular pins. On iOS 9 and later - * you are not obliged to use these, but they are useful for matching - * the standard iOS look and feel. - */ - PinColors: { - RED: '#ff3b30', - GREEN: '#4cd964', - PURPLE: '#c969e0', - }, - }, - - render: function() { - let children = [], {annotations, overlays, followUserLocation} = this.props; - annotations = annotations && annotations.map((annotation: Object) => { - let { - id, - image, - tintColor, - view, - leftCalloutView, - rightCalloutView, - detailCalloutView, - } = annotation; - - if (!view && image && tintColor) { - view = ; - image = undefined; - } - if (view) { - if (image) { - console.warn('`image` and `view` both set on annotation. Image will be ignored.'); - } - var viewIndex = children.length; - children.push(React.cloneElement(view, { - // $FlowFixMe - An array of styles should be fine - style: [styles.annotationView, view.props.style || {}] - })); - } - if (leftCalloutView) { - var leftCalloutViewIndex = children.length; - children.push(React.cloneElement(leftCalloutView, { - style: [styles.calloutView, leftCalloutView.props.style || {}] - })); - } - if (rightCalloutView) { - var rightCalloutViewIndex = children.length; - children.push(React.cloneElement(rightCalloutView, { - style: [styles.calloutView, rightCalloutView.props.style || {}] - })); - } - if (detailCalloutView) { - var detailCalloutViewIndex = children.length; - children.push(React.cloneElement(detailCalloutView, { - style: [styles.calloutView, detailCalloutView.props.style || {}] - })); - } - - const result = { - ...annotation, - tintColor: tintColor && processColor(tintColor), - image, - viewIndex, - leftCalloutViewIndex, - rightCalloutViewIndex, - detailCalloutViewIndex, - view: undefined, - leftCalloutView: undefined, - rightCalloutView: undefined, - detailCalloutView: undefined, - }; - result.id = id || encodeURIComponent(JSON.stringify(result)); - result.image = image && resolveAssetSource(image); - return result; - }); - overlays = overlays && overlays.map((overlay: Object) => { - const {id, fillColor, strokeColor} = overlay; - const result = { - ...overlay, - strokeColor: strokeColor && processColor(strokeColor), - fillColor: fillColor && processColor(fillColor), - }; - result.id = id || encodeURIComponent(JSON.stringify(result)); - return result; - }); - - const findByAnnotationId = (annotationId: string) => { - if (!annotations) { - return null; - } - for (let i = 0, l = annotations.length; i < l; i++) { - if (annotations[i].id === annotationId) { - return annotations[i]; - } - } - return null; - }; - - // TODO: these should be separate events, to reduce bridge traffic - let onPress, onAnnotationDragStateChange, onAnnotationFocus, onAnnotationBlur; - if (annotations) { - onPress = (event: Event) => { - if (event.nativeEvent.action === 'annotation-click') { - // TODO: Remove deprecated onAnnotationPress API call later. - this.props.onAnnotationPress && - this.props.onAnnotationPress(event.nativeEvent.annotation); - } else if (event.nativeEvent.action === 'callout-click') { - const annotation = findByAnnotationId(event.nativeEvent.annotationId); - if (annotation) { - // Pass the right function - if (event.nativeEvent.side === 'left' && annotation.onLeftCalloutPress) { - annotation.onLeftCalloutPress(event.nativeEvent); - } else if (event.nativeEvent.side === 'right' && annotation.onRightCalloutPress) { - annotation.onRightCalloutPress(event.nativeEvent); - } - } - } - }; - onAnnotationDragStateChange = (event: Event) => { - const annotation = findByAnnotationId(event.nativeEvent.annotationId); - if (annotation) { - // Call callback - annotation.onDragStateChange && - annotation.onDragStateChange(event.nativeEvent); - } - }; - onAnnotationFocus = (event: Event) => { - const annotation = findByAnnotationId(event.nativeEvent.annotationId); - if (annotation && annotation.onFocus) { - annotation.onFocus(event.nativeEvent); - } - }; - onAnnotationBlur = (event: Event) => { - const annotation = findByAnnotationId(event.nativeEvent.annotationId); - if (annotation && annotation.onBlur) { - annotation.onBlur(event.nativeEvent); - } - }; - } - - // TODO: these should be separate events, to reduce bridge traffic - if (this.props.onRegionChange || this.props.onRegionChangeComplete) { - var onChange = (event: Event) => { - if (event.nativeEvent.continuous) { - this.props.onRegionChange && - this.props.onRegionChange(event.nativeEvent.region); - } else { - this.props.onRegionChangeComplete && - this.props.onRegionChangeComplete(event.nativeEvent.region); - } - }; - } - - // followUserLocation defaults to true if showUserLocation is set - if (followUserLocation === undefined) { - followUserLocation = this.props.showUserLocation; - } - - return ( - - ); - }, -}); - -const styles = StyleSheet.create({ - annotationView: { - position: 'absolute', - backgroundColor: 'transparent', - }, - calloutView: { - position: 'absolute', - backgroundColor: 'white', - }, -}); - -const RCTMap = requireNativeComponent('RCTMap', MapView, { - nativeOnly: { - onAnnotationDragStateChange: true, - onAnnotationFocus: true, - onAnnotationBlur: true, - onChange: true, - onPress: true - } -}); - -module.exports = MapView; diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index e9b43f3e78f9ab..90a6c37e9820c1 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -884,6 +884,7 @@ var NavigatorIOS = React.createClass({ =0.41.0) vertical={this.props.vertical} requestedTopOfStack={this.state.requestedTopOfStack} onNavigationComplete={this._handleNavigationComplete} @@ -914,6 +915,7 @@ var NavigatorIOS = React.createClass({ render: function() { return ( + // $FlowFixMe(>=0.41.0) {this._renderNavigationStackItems()} diff --git a/Libraries/Components/Picker/Picker.js b/Libraries/Components/Picker/Picker.js index 7e2ad2a9fa7f85..1f8441814f87ab 100644 --- a/Libraries/Components/Picker/Picker.js +++ b/Libraries/Components/Picker/Picker.js @@ -69,6 +69,7 @@ class Picker extends React.Component { mode: MODE_DIALOG, }; + // $FlowFixMe(>=0.41.0) static propTypes = { ...View.propTypes, style: pickerStyleType, @@ -134,7 +135,7 @@ Picker.Item = class extends React.Component { props: { label: string, value?: any, - color?: $FlowFixMe, + color?: ColorPropType, testID?: string, }; diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js index 5d5aebe361b1b6..6ee4db298d08e3 100644 --- a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js @@ -24,6 +24,7 @@ var PropTypes = React.PropTypes; /** * Use `ProgressViewIOS` to render a UIProgressView on iOS. */ +// $FlowFixMe(>=0.41.0) var ProgressViewIOS = React.createClass({ mixins: [NativeMethodsMixin], diff --git a/Libraries/Components/RefreshControl/RefreshControl.js b/Libraries/Components/RefreshControl/RefreshControl.js index 6622467709f2fd..a1eff537d8a2ef 100644 --- a/Libraries/Components/RefreshControl/RefreshControl.js +++ b/Libraries/Components/RefreshControl/RefreshControl.js @@ -70,6 +70,7 @@ if (Platform.OS === 'android') { * __Note:__ `refreshing` is a controlled prop, this is why it needs to be set to true * in the `onRefresh` function otherwise the refresh indicator will stop immediately. */ +// $FlowFixMe(>=0.41.0) const RefreshControl = React.createClass({ statics: { SIZE: RefreshLayoutConsts.SIZE, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 68ac9b987a175a..0f167228b70d73 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -11,6 +11,7 @@ */ 'use strict'; +const Animated = require('Animated'); const ColorPropType = require('ColorPropType'); const EdgeInsetsPropType = require('EdgeInsetsPropType'); const Platform = require('Platform'); @@ -18,6 +19,7 @@ const PointPropType = require('PointPropType'); const React = require('React'); const ReactNative = require('ReactNative'); const ScrollResponder = require('ScrollResponder'); +const ScrollViewStickyHeader = require('ScrollViewStickyHeader'); const StyleSheet = require('StyleSheet'); const StyleSheetPropType = require('StyleSheetPropType'); const View = require('View'); @@ -46,20 +48,26 @@ const requireNativeComponent = require('requireNativeComponent'); * view from becoming the responder. * * - * `` vs `` - which one to use? - * ScrollView simply renders all its react child components at once. That + * `` vs [``](/react-native/docs/flatlist.html) - which one to use? + * + * `ScrollView` simply renders all its react child components at once. That * makes it very easy to understand and use. + * * On the other hand, this has a performance downside. Imagine you have a very - * long list of items you want to display, worth of couple of your ScrollView’s - * heights. Creating JS components and native views upfront for all its items, - * which may not even be shown, will contribute to slow rendering of your - * screen and increased memory usage. + * long list of items you want to display, maybe several screens worth of + * content. Creating JS components and native views for everythign all at once, + * much of which may not even be shown, will contribute to slow rendering and + * increased memory usage. * - * This is where ListView comes into play. ListView renders items lazily, - * just when they are about to appear. This laziness comes at cost of a more - * complicated API, which is worth it unless you are rendering a small fixed - * set of items. + * This is where `FlatList` comes into play. `FlatList` renders items lazily, + * just when they are about to appear, and removes items that scroll way off + * screen to save memory and processing time. + * + * `FlatList` is also handy if you want to render separators between your items, + * multiple columns, infinite scroll loading, or any number of other features it + * supports out of the box. */ +// $FlowFixMe(>=0.41.0) const ScrollView = React.createClass({ propTypes: { ...View.propTypes, @@ -157,8 +165,8 @@ const ScrollView = React.createClass({ /** * The style of the scroll indicators. * - `default` (the default), same as `black`. - * - `black`, scroll indicator is black. This style is good against a white content background. - * - `white`, scroll indicator is white. This style is good against a black content background. + * - `black`, scroll indicator is black. This style is good against a light background. + * - `white`, scroll indicator is white. This style is good against a dark background. * @platform ios */ indicatorStyle: PropTypes.oneOf([ @@ -227,7 +235,8 @@ const ScrollView = React.createClass({ /** * Called when scrollable content view of the ScrollView changes. * - * Handler function is passed the content width and content height as parameters: `(contentWidth, contentHeight)` + * Handler function is passed the content width and content height as parameters: + * `(contentWidth, contentHeight)` * * It's implemented using onLayout handler attached to the content container * which this ScrollView renders. @@ -286,7 +295,6 @@ const ScrollView = React.createClass({ * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the * top of the scroll view. This property is not supported in conjunction * with `horizontal={true}`. - * @platform ios */ stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), style: StyleSheetPropType(ViewStylePropTypes), @@ -372,10 +380,33 @@ const ScrollView = React.createClass({ mixins: [ScrollResponder.Mixin], + _scrollAnimatedValue: (new Animated.Value(0): Animated.Value), + _scrollAnimatedValueAttachment: (null: ?{detach: () => void}), + _stickyHeaderRefs: (new Map(): Map), + getInitialState: function() { return this.scrollResponderMixinGetInitialState(); }, + componentWillMount: function() { + this._scrollAnimatedValue = new Animated.Value(0); + this._stickyHeaderRefs = new Map(); + }, + + componentDidMount: function() { + this._updateAnimatedNodeAttachment(); + }, + + componentDidUpdate: function() { + this._updateAnimatedNodeAttachment(); + }, + + componentWillUnmount: function() { + if (this._scrollAnimatedValueAttachment) { + this._scrollAnimatedValueAttachment.detach(); + } + }, + setNativeProps: function(props: Object) { this._scrollViewRef && this._scrollViewRef.setNativeProps(props); }, @@ -406,7 +437,7 @@ const ScrollView = React.createClass({ * `scrollTo({x: 0; y: 0; animated: true})` * * Note: The weird function signature is due to the fact that, for historical reasons, - * the function also accepts separate arguments as as alternative to the options object. + * the function also accepts separate arguments as an alternative to the options object. * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. */ scrollTo: function( @@ -415,11 +446,14 @@ const ScrollView = React.createClass({ animated?: boolean ) { if (typeof y === 'number') { - console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.'); + console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, ' + + 'animated: true})` instead.'); } else { ({x, y, animated} = y || {}); } - this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false}); + this.getScrollResponder().scrollResponderScrollTo( + {x: x || 0, y: y || 0, animated: animated !== false} + ); }, /** @@ -448,6 +482,40 @@ const ScrollView = React.createClass({ this.scrollTo({x, y, animated: false}); }, + _updateAnimatedNodeAttachment: function() { + if (this.props.stickyHeaderIndices && this.props.stickyHeaderIndices.length > 0) { + if (!this._scrollAnimatedValueAttachment) { + this._scrollAnimatedValueAttachment = Animated.attachNativeEvent( + this._scrollViewRef, + 'onScroll', + [{nativeEvent: {contentOffset: {y: this._scrollAnimatedValue}}}] + ); + } + } else { + if (this._scrollAnimatedValueAttachment) { + this._scrollAnimatedValueAttachment.detach(); + } + } + }, + + _setStickyHeaderRef: function(index, ref) { + this._stickyHeaderRefs.set(index, ref); + }, + + _onStickyHeaderLayout: function(index, event) { + if (!this.props.stickyHeaderIndices) { + return; + } + + const previousHeaderIndex = this.props.stickyHeaderIndices[ + this.props.stickyHeaderIndices.indexOf(index) - 1 + ]; + if (previousHeaderIndex != null) { + const previousHeader = this._stickyHeaderRefs.get(previousHeaderIndex); + previousHeader && previousHeader.setNextHeaderY(event.nativeEvent.layout.y); + } + }, + _handleScroll: function(e: Object) { if (__DEV__) { if (this.props.onScroll && this.props.scrollEventThrottle == null && Platform.OS === 'ios') { @@ -531,14 +599,36 @@ const ScrollView = React.createClass({ }; } - const contentContainer = + const {stickyHeaderIndices} = this.props; + const hasStickyHeaders = stickyHeaderIndices && stickyHeaderIndices.length > 0; + const children = stickyHeaderIndices && hasStickyHeaders ? + React.Children.toArray(this.props.children).map((child, index) => { + const stickyHeaderIndex = stickyHeaderIndices.indexOf(index); + if (child && stickyHeaderIndex >= 0) { + return ( + this._setStickyHeaderRef(index, ref)} + onLayout={(event) => this._onStickyHeaderLayout(index, event)} + scrollAnimatedValue={this._scrollAnimatedValue}> + {child} + + ); + } else { + return child; + } + }) : + this.props.children; + const contentContainer = - {this.props.children} + {children} ; const alwaysBounceHorizontal = @@ -560,23 +650,25 @@ const ScrollView = React.createClass({ // Override the onContentSizeChange from props, since this event can // bubble up from TextInputs onContentSizeChange: null, - onTouchStart: this.scrollResponderHandleTouchStart, - onTouchMove: this.scrollResponderHandleTouchMove, - onTouchEnd: this.scrollResponderHandleTouchEnd, - onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, - onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin, onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd, - onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, - onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, - onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, - onScroll: this._handleScroll, onResponderGrant: this.scrollResponderHandleResponderGrant, - onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, - onResponderTerminate: this.scrollResponderHandleTerminate, - onResponderRelease: this.scrollResponderHandleResponderRelease, onResponderReject: this.scrollResponderHandleResponderReject, - sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ? true : false, + onResponderRelease: this.scrollResponderHandleResponderRelease, + onResponderTerminate: this.scrollResponderHandleTerminate, + onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, + onScroll: this._handleScroll, + onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, + onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, + onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, + onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, + onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, + onTouchEnd: this.scrollResponderHandleTouchEnd, + onTouchMove: this.scrollResponderHandleTouchMove, + onTouchStart: this.scrollResponderHandleTouchStart, + scrollEventThrottle: hasStickyHeaders ? 1 : this.props.scrollEventThrottle, + sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ? + true : false, }; const { decelerationRate } = this.props; @@ -585,12 +677,14 @@ const ScrollView = React.createClass({ } const refreshControl = this.props.refreshControl; + if (refreshControl) { if (Platform.OS === 'ios') { // On iOS the RefreshControl is a child of the ScrollView. + // tvOS lacks native support for RefreshControl, so don't include it in that case return ( - {refreshControl} + {Platform.isTVOS ? null : refreshControl} {contentContainer} ); @@ -636,17 +730,25 @@ const styles = StyleSheet.create({ }, }); -let nativeOnlyProps, AndroidScrollView, AndroidHorizontalScrollView, RCTScrollView, RCTScrollContentView; +let nativeOnlyProps, + AndroidScrollView, + AndroidHorizontalScrollView, + RCTScrollView, + RCTScrollContentView; if (Platform.OS === 'android') { nativeOnlyProps = { nativeOnly: { sendMomentumEvents: true, } }; - AndroidScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps); + AndroidScrollView = requireNativeComponent( + 'RCTScrollView', + (ScrollView: ReactClass), + nativeOnlyProps + ); AndroidHorizontalScrollView = requireNativeComponent( 'AndroidHorizontalScrollView', - ScrollView, + (ScrollView: ReactClass), nativeOnlyProps ); } else if (Platform.OS === 'ios') { @@ -658,7 +760,11 @@ if (Platform.OS === 'android') { onScrollEndDrag: true, } }; - RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps); + RCTScrollView = requireNativeComponent( + 'RCTScrollView', + (ScrollView: ReactClass), + nativeOnlyProps, + ); RCTScrollContentView = requireNativeComponent('RCTScrollContentView', View); } diff --git a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js new file mode 100644 index 00000000000000..da640b56090adc --- /dev/null +++ b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ScrollViewStickyHeader + * @flow + */ +'use strict'; + +const Animated = require('Animated'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); + +type Props = { + children?: React.Element<*>, + scrollAnimatedValue: Animated.Value, + onLayout: (event: Object) => void, +}; + +class ScrollViewStickyHeader extends React.Component { + props: Props; + state = { + measured: false, + layoutY: 0, + layoutHeight: 0, + nextHeaderLayoutY: (null: ?number), + }; + + setNextHeaderY(y: number) { + this.setState({ nextHeaderLayoutY: y }); + } + + _onLayout = (event) => { + this.setState({ + measured: true, + layoutY: event.nativeEvent.layout.y, + layoutHeight: event.nativeEvent.layout.height, + }); + + this.props.onLayout(event); + const child = React.Children.only(this.props.children); + if (child.props.onLayout) { + child.props.onLayout(event); + } + }; + + render() { + const {measured, layoutHeight, layoutY, nextHeaderLayoutY} = this.state; + + let translateY; + if (measured) { + // The interpolation looks like: + // - Negative scroll: no translation + // - From 0 to the y of the header: no translation. This will cause the header + // to scroll normally until it reaches the top of the scroll view. + // - From header y to when the next header y hits the bottom edge of the header: translate + // equally to scroll. This will cause the header to stay at the top of the scroll view. + // - Past the collision with the next header y: no more translation. This will cause the + // header to continue scrolling up and make room for the next sticky header. + // In the case that there is no next header just translate equally to + // scroll indefinetly. + const inputRange = [-1, 0, layoutY]; + const outputRange: Array = [0, 0, 0]; + if (nextHeaderLayoutY != null) { + const collisionPoint = nextHeaderLayoutY - layoutHeight; + inputRange.push(collisionPoint, collisionPoint + 1); + outputRange.push(collisionPoint - layoutY, collisionPoint - layoutY); + } else { + inputRange.push(layoutY + 1); + outputRange.push(1); + } + translateY = this.props.scrollAnimatedValue.interpolate({ + inputRange, + outputRange, + }); + } else { + translateY = 0; + } + + const child = React.Children.only(this.props.children); + + return ( + + {React.cloneElement(child, { + style: styles.fill, // We transfer the child style to the wrapper. + onLayout: undefined, // we call this manually through our this._onLayout + })} + + ); + } +} + +const styles = StyleSheet.create({ + header: { + zIndex: 10, + }, + fill: { + flex: 1, + }, +}); + +module.exports = ScrollViewStickyHeader; diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js index 01f4c42ddc7142..200e198e1c025c 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js @@ -49,6 +49,7 @@ type Event = Object; * /> * ```` */ +// $FlowFixMe(>=0.41.0) var SegmentedControlIOS = React.createClass({ mixins: [NativeMethodsMixin], diff --git a/Libraries/Components/Slider/Slider.js b/Libraries/Components/Slider/Slider.js index 23ea68bd278093..3fbe92fc5f38af 100644 --- a/Libraries/Components/Slider/Slider.js +++ b/Libraries/Components/Slider/Slider.js @@ -29,6 +29,7 @@ type Event = Object; /** * A component used to select a single value from a range of values. */ +// $FlowFixMe(>=0.41.0) var Slider = React.createClass({ mixins: [NativeMethodsMixin], diff --git a/Libraries/Components/StatusBar/StatusBar.js b/Libraries/Components/StatusBar/StatusBar.js index f1712b4a52d481..1a4eb6a9722129 100644 --- a/Libraries/Components/StatusBar/StatusBar.js +++ b/Libraries/Components/StatusBar/StatusBar.js @@ -274,8 +274,6 @@ class StatusBar extends React.Component { translucent: React.PropTypes.bool, /** * Sets the color of the status bar text. - * - * @platform ios */ barStyle: React.PropTypes.oneOf([ 'default', diff --git a/Libraries/Components/Switch/Switch.js b/Libraries/Components/Switch/Switch.js index ca1e1295cadced..f614164cab8577 100644 --- a/Libraries/Components/Switch/Switch.js +++ b/Libraries/Components/Switch/Switch.js @@ -38,6 +38,7 @@ type DefaultProps = { * @keyword checkbox * @keyword toggle */ +// $FlowFixMe(>=0.41.0) var Switch = React.createClass({ propTypes: { ...View.propTypes, diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.android.js b/Libraries/Components/TabBarIOS/TabBarIOS.android.js index c582903a760478..d9efee3ff322b1 100644 --- a/Libraries/Components/TabBarIOS/TabBarIOS.android.js +++ b/Libraries/Components/TabBarIOS/TabBarIOS.android.js @@ -7,15 +7,19 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule TabBarIOS + * @flow */ 'use strict'; -var React = require('React'); -var View = require('View'); -var StyleSheet = require('StyleSheet'); +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const TabBarItemIOS = require('TabBarItemIOS'); +const View = require('View'); class DummyTabBarIOS extends React.Component { + static Item = TabBarItemIOS; + render() { return ( @@ -25,7 +29,7 @@ class DummyTabBarIOS extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ tabGroup: { flex: 1, } diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js index 283e9928f50b54..5ccab62e11fe7d 100644 --- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js @@ -32,6 +32,7 @@ class TabBarIOS extends React.Component { static Item = TabBarItemIOS; + // $FlowFixMe(>=0.41.0) static propTypes = { ...View.propTypes, style: View.propTypes.style, diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 1c70e412ae4110..e9b2de7d5474e3 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -168,6 +168,7 @@ const DataDetectorTypes = [ * or control this param programmatically with native code. * */ +// $FlowFixMe(>=0.41.0) const TextInput = React.createClass({ statics: { /* TODO(brentvatne) docs are needed for this */ diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 1abedf4e284e20..333cf6cbf79a9b 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -33,6 +33,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`. */ +// $FlowFixMe(>=0.41.0) var TouchableBounce = React.createClass({ mixins: [Touchable.Mixin, NativeMethodsMixin], @@ -148,7 +149,9 @@ var TouchableBounce = React.createClass({ onResponderMove={this.touchableHandleResponderMove} onResponderRelease={this.touchableHandleResponderRelease} onResponderTerminate={this.touchableHandleResponderTerminate}> - {this.props.children} + { + // $FlowFixMe(>=0.41.0) + this.props.children} {Touchable.renderDebugView({color: 'orange', hitSlop: this.props.hitSlop})} ); diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js index 037a3b85ca8cfd..5707b61e378aaf 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js @@ -255,7 +255,7 @@ var TouchableNativeFeedback = React.createClass({ // We need to clone the actual element so that the ripple background drawable // can be applied directly to the background of this element rather than to - // a wrapper view as done in outher Touchable* + // a wrapper view as done in other Touchable* return React.cloneElement( child, childProps diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 8d3e53312ae0f6..40079f44538f00 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -77,7 +77,7 @@ var TouchableOpacity = React.createClass({ getInitialState: function() { return { ...this.touchableGetInitialState(), - anim: new Animated.Value(1), + anim: new Animated.Value(this._getChildStyleOpacityWithDefault()), }; }, @@ -156,9 +156,8 @@ var TouchableOpacity = React.createClass({ }, _opacityInactive: function(duration: number) { - var childStyle = flattenStyle(this.props.style) || {}; this.setOpacityTo( - childStyle.opacity === undefined ? 1 : childStyle.opacity, + this._getChildStyleOpacityWithDefault(), duration ); }, @@ -166,6 +165,11 @@ var TouchableOpacity = React.createClass({ _opacityFocused: function() { this.setOpacityTo(this.props.focusedOpacity); }, + + _getChildStyleOpacityWithDefault: function() { + var childStyle = flattenStyle(this.props.style) || {}; + return childStyle.opacity == undefined ? 1 : childStyle.opacity; + }, render: function() { return ( diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index ed0d92d5c98de9..582832b2a6292b 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -31,6 +31,7 @@ const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; * TouchableWithoutFeedback supports only one child. * If you wish to have several child components, wrap them in a View. */ +// $FlowFixMe(>=0.41.0) const TouchableWithoutFeedback = React.createClass({ mixins: [TimerMixin, Touchable.Mixin], @@ -147,6 +148,7 @@ const TouchableWithoutFeedback = React.createClass({ render: function(): React.Element { // Note(avik): remove dynamic typecast once Flow has been upgraded + // $FlowFixMe(>=0.41.0) const child = React.Children.only(this.props.children); let children = child.props.children; warning( @@ -163,9 +165,11 @@ const TouchableWithoutFeedback = React.createClass({ child.props.style; return (React: any).cloneElement(child, { accessible: this.props.accessible !== false, + // $FlowFixMe(>=0.41.0) accessibilityLabel: this.props.accessibilityLabel, accessibilityComponentType: this.props.accessibilityComponentType, accessibilityTraits: this.props.accessibilityTraits, + // $FlowFixMe(>=0.41.0) testID: this.props.testID, onLayout: this.props.onLayout, hitSlop: this.props.hitSlop, diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 43ef58152729e1..a519dfbb6b1edc 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -98,6 +98,7 @@ const statics = { * - `timestamp` - A time identifier for the touch, useful for velocity calculation. * - `touches` - Array of all current touches on the screen. */ +// $FlowFixMe(>=0.41.0) const View = React.createClass({ // TODO: We should probably expose the mixins, viewConfig, and statics publicly. For example, // one of the props is of type AccessibilityComponentType. That is defined as a const[] above, diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js index 294095035d36a0..4de8be62dbb289 100644 --- a/Libraries/Components/WebView/WebView.android.js +++ b/Libraries/Components/WebView/WebView.android.js @@ -160,6 +160,28 @@ class WebView extends React.Component { * @platform android */ allowUniversalAccessFromFileURLs: PropTypes.bool, + + /** + * Function that accepts a string that will be passed to the WebView and + * executed immediately as JavaScript. + */ + injectJavaScript: PropTypes.func, + + /** + * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin. + * + * Possible values for `mixedContentMode` are: + * + * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin. + * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure. + * - `'compatibility'` - WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content. + * @platform android + */ + mixedContentMode: PropTypes.oneOf([ + 'never', + 'always', + 'compatibility' + ]), }; static defaultProps = { @@ -236,6 +258,7 @@ class WebView extends React.Component { testID={this.props.testID} mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction} allowUniversalAccessFromFileURLs={this.props.allowUniversalAccessFromFileURLs} + mixedContentMode={this.props.mixedContentMode} />; return ( diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 15409e2cc2b22d..96488b2c3ccd92 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -334,6 +334,28 @@ class WebView extends React.Component { * to tap them before they start playing. The default value is `true`. */ mediaPlaybackRequiresUserAction: PropTypes.bool, + + /** + * Function that accepts a string that will be passed to the WebView and + * executed immediately as JavaScript. + */ + injectJavaScript: PropTypes.func, + + /** + * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin. + * + * Possible values for `mixedContentMode` are: + * + * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin. + * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure. + * - `'compatibility'` - WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content. + * @platform android + */ + mixedContentMode: PropTypes.oneOf([ + 'never', + 'always', + 'compatibility' + ]), }; state = { diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 42dfa4d0916768..066691f54bbc73 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -33,6 +33,7 @@ 'use strict'; var ListViewDataSource = require('ListViewDataSource'); +var Platform = require('Platform'); var React = require('React'); var ReactNative = require('ReactNative'); var RCTScrollViewManager = require('NativeModules').ScrollViewManager; @@ -227,8 +228,9 @@ var ListView = React.createClass({ * Makes the sections headers sticky. The sticky behavior means that it * will scroll with the content at the top of the section until it reaches * the top of the screen, at which point it will stick to the top until it - * is pushed off the screen by the next section header. - * @platform ios + * is pushed off the screen by the next section header. This property is + * not supported in conjunction with `horizontal={true}`. Only enabled by + * default on iOS because of typical platform standards. */ stickySectionHeadersEnabled: React.PropTypes.bool, /** @@ -237,7 +239,6 @@ var ListView = React.createClass({ * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the * top of the scroll view. This property is not supported in conjunction * with `horizontal={true}`. - * @platform ios */ stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number).isRequired, /** @@ -330,7 +331,7 @@ var ListView = React.createClass({ renderScrollComponent: props => , scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD, onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD, - stickySectionHeadersEnabled: true, + stickySectionHeadersEnabled: Platform.OS === 'ios', stickyHeaderIndices: [], }; }, @@ -403,6 +404,8 @@ var ListView = React.createClass({ var rowCount = 0; var stickySectionHeaderIndices = []; + const {renderSectionHeader} = this.props; + var header = this.props.renderHeader && this.props.renderHeader(); var footer = this.props.renderFooter && this.props.renderFooter(); var totalIndex = header ? 1 : 0; @@ -426,22 +429,17 @@ var ListView = React.createClass({ } } - if (this.props.renderSectionHeader) { - var shouldUpdateHeader = rowCount >= this._prevRenderedRowsCount && - dataSource.sectionHeaderShouldUpdate(sectionIdx); - bodyComponents.push( - + if (renderSectionHeader) { + const element = renderSectionHeader( + dataSource.getSectionHeaderData(sectionIdx), + sectionID ); - if (this.props.stickySectionHeadersEnabled) { - stickySectionHeaderIndices.push(totalIndex++); + if (element) { + bodyComponents.push(React.cloneElement(element, {key: 's_' + sectionID})); + if (this.props.stickySectionHeadersEnabled) { + stickySectionHeaderIndices.push(totalIndex); + } + totalIndex++; } } diff --git a/Libraries/Experimental/FlatList.js b/Libraries/CustomComponents/Lists/FlatList.js similarity index 70% rename from Libraries/Experimental/FlatList.js rename to Libraries/CustomComponents/Lists/FlatList.js index 240f0c73b47e60..10a47cc46974c5 100644 --- a/Libraries/Experimental/FlatList.js +++ b/Libraries/CustomComponents/Lists/FlatList.js @@ -34,10 +34,11 @@ const MetroListView = require('MetroListView'); // Used as a fallback legacy option const React = require('React'); +const ReactNative = require('ReactNative'); const View = require('View'); const VirtualizedList = require('VirtualizedList'); -const invariant = require('invariant'); +const invariant = require('fbjs/lib/invariant'); import type {StyleObj} from 'StyleSheetTypes'; import type {ViewabilityConfig, ViewToken} from 'ViewabilityHelper'; @@ -45,19 +46,19 @@ import type {Props as VirtualizedListProps} from 'VirtualizedList'; type RequiredProps = { /** - * Takes an item from `data` and renders it into the list. Typicaly usage: + * Takes an item from `data` and renders it into the list. Typical usage: * - * _renderItem = ({item}) => ( - * this._onPress(item)}> - * {item.title}} - * - * ); - * ... - * + * _renderItem = ({item}) => ( + * this._onPress(item)}> + * {item.title}} + * + * ); + * ... + * * * Provides additional metadata like `index` if you need it. */ - renderItem: ({item: ItemT, index: number}) => ?React.Element<*>, + renderItem: (info: {item: ItemT, index: number}) => ?React.Element, /** * For simplicity, data is just a plain array. If you want to use something else, like an * immutable list, use the underlying `VirtualizedList` directly. @@ -66,26 +67,28 @@ type RequiredProps = { }; type OptionalProps = { /** - * Rendered at the bottom of all the items. + * Rendered in between each item, but not at the top or bottom. */ - FooterComponent?: ?ReactClass<*>, + ItemSeparatorComponent?: ?ReactClass, /** - * Rendered at the top of all the items. + * Rendered at the bottom of all the items. */ - HeaderComponent?: ?ReactClass<*>, + ListFooterComponent?: ?ReactClass, /** - * Rendered in between each item, but not at the top or bottom. + * Rendered at the top of all the items. */ - SeparatorComponent?: ?ReactClass<*>, + ListHeaderComponent?: ?ReactClass, /** - * getItemLayout is an optional optimizations that let us skip measurement of dynamic content if - * you know the height of items a priori. getItemLayout is the most efficient, and is easy to use - * if you have fixed height items, for example: + * `getItemLayout` is an optional optimizations that let us skip measurement of dynamic content if + * you know the height of items a priori. `getItemLayout` is the most efficient, and is easy to + * use if you have fixed height items, for example: * - * getItemLayout={(data, index) => ({length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index})} + * getItemLayout={(data, index) => ( + * {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index} + * )} * * Remember to include separator length (height or width) in your offset calculation if you - * specify `SeparatorComponent`. + * specify `ItemSeparatorComponent`. */ getItemLayout?: (data: ?Array, index: number) => {length: number, offset: number, index: number}, @@ -95,30 +98,31 @@ type OptionalProps = { horizontal?: ?boolean, /** * Used to extract a unique key for a given item at the specified index. Key is used for caching - * and as the react key to track item re-ordering. The default extractor checks item.key, then - * falls back to using the index, like react does. + * and as the react key to track item re-ordering. The default extractor checks `item.key`, then + * falls back to using the index, like React does. */ keyExtractor: (item: ItemT, index: number) => string, /** - * Multiple columns can only be rendered with horizontal={false} and will zig-zag like a flexWrap - * layout. Items should all be the same height - masonry layouts are not supported. + * Multiple columns can only be rendered with `horizontal={false}`` and will zig-zag like a + * `flexWrap` layout. Items should all be the same height - masonry layouts are not supported. */ numColumns: number, /** - * Called once when the scroll position gets within onEndReachedThreshold of the rendered content. + * Called once when the scroll position gets within `onEndReachedThreshold` of the rendered + * content. */ - onEndReached?: ?({distanceFromEnd: number}) => void, + onEndReached?: ?(info: {distanceFromEnd: number}) => void, onEndReachedThreshold?: ?number, /** * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make * sure to also set the `refreshing` prop correctly. */ - onRefresh?: ?Function, + onRefresh?: ?() => void, /** * Called when the viewability of rows changes, as defined by the * `viewablePercentThreshold` prop. */ - onViewableItemsChanged?: ?({viewableItems: Array, changed: Array}) => void, + onViewableItemsChanged?: ?(info: {viewableItems: Array, changed: Array}) => void, legacyImplementation?: ?boolean, /** * Set this true while waiting for new data from a refresh. @@ -129,14 +133,7 @@ type OptionalProps = { */ columnWrapperStyle?: StyleObj, /** - * Optional optimization to minimize re-rendering items. - */ - shouldItemUpdate: ( - prevInfo: {item: ItemT, index: number}, - nextInfo: {item: ItemT, index: number} - ) => boolean, - /** - * See ViewabilityHelper for flow type and comments. + * See `ViewabilityHelper` for flow type and further documentation. */ viewabilityConfig?: ViewabilityConfig, }; @@ -155,25 +152,43 @@ type DefaultProps = typeof defaultProps; * * - Fully cross-platform. * - Optional horizontal mode. - * - Viewability callbacks. + * - Configurable viewability callbacks. + * - Header support. * - Footer support. * - Separator support. - * - Pull to Refresh + * - Pull to Refresh. + * - Scroll loading. * - * If you need sticky section header support, use ListView. + * If you need section support, use [``](/react-native/docs/sectionlist.html). * * Minimal Example: * - * {item.key}} - * /> + * {item.key}} + * /> + * + * This is a convenience wrapper around [``](/react-native/docs/virtualizedlist.html), + * and thus inherits the following caveats: + * + * - Internal state is not preserved when content scrolls out of the render window. Make sure all + * your data is captured in the item data or external stores like Flux, Redux, or Relay. + * - This is a `PureComponent` which means that it will not re-render if `props` remain shallow- + * equal. Make sure that everything your `renderItem` function depends on is passed as a prop that + * is not `===` after updates, otherwise your UI may not update on changes. This includes the + * `data` prop and parent component state. + * - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously + * offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see + * blank content. This is a tradeoff that can be adjusted to suit the needs of each application, + * and we are working on improving it behind the scenes. + * - By default, the list looks for a `key` prop on each item and uses that for the React key. + * Alternatively, you can provide a custom `keyExtractor` prop. */ class FlatList extends React.PureComponent, void> { static defaultProps: DefaultProps = defaultProps; props: Props; /** - * Scrolls to the end of the content. May be janky without getItemLayout prop. + * Scrolls to the end of the content. May be janky without `getItemLayout` prop. */ scrollToEnd(params?: ?{animated?: ?boolean}) { this._listRef.scrollToEnd(params); @@ -181,24 +196,25 @@ class FlatList extends React.PureComponent, vo /** * Scrolls to the item at a the specified index such that it is positioned in the viewable area - * such that viewPosition 0 places it at the top, 1 at the bottom, and 0.5 centered in the middle. + * such that `viewPosition` 0 places it at the top, 1 at the bottom, and 0.5 centered in the + * middle. * - * May be janky without getItemLayout prop. + * May be janky without `getItemLayout` prop. */ scrollToIndex(params: {animated?: ?boolean, index: number, viewPosition?: number}) { this._listRef.scrollToIndex(params); } /** - * Requires linear scan through data - use scrollToIndex instead if possible. May be janky without - * `getItemLayout` prop. - */ + * Requires linear scan through data - use `scrollToIndex` instead if possible. May be janky + * without `getItemLayout` prop. + */ scrollToItem(params: {animated?: ?boolean, item: ItemT, viewPosition?: number}) { this._listRef.scrollToItem(params); } /** - * Scroll to a specific content pixel offset, like a normal ScrollView. + * Scroll to a specific content pixel offset, like a normal `ScrollView`. */ scrollToOffset(params: {animated?: ?boolean, offset: number}) { this._listRef.scrollToOffset(params); @@ -206,13 +222,21 @@ class FlatList extends React.PureComponent, vo /** * Tells the list an interaction has occured, which should trigger viewability calculations, e.g. - * if waitForInteractions is true and the user has not scrolled. This is typically called by taps - * on items or by navigation actions. + * if `waitForInteractions` is true and the user has not scrolled. This is typically called by + * taps on items or by navigation actions. */ recordInteraction() { this._listRef.recordInteraction(); } + getScrollableNode() { + if (this._listRef && this._listRef.getScrollableNode) { + return this._listRef.getScrollableNode(); + } else { + return ReactNative.findNodeHandle(this._listRef); + } + } + componentWillMount() { this._checkProps(this.props); } @@ -255,7 +279,7 @@ class FlatList extends React.PureComponent, vo } } - _getItem = (data: Array, index: number): ItemT | Array => { + _getItem = (data: Array, index: number) => { const {numColumns} = this.props; if (numColumns > 1) { const ret = []; @@ -269,11 +293,11 @@ class FlatList extends React.PureComponent, vo } }; - _getItemCount = (data?: ?Array): number => { + _getItemCount = (data: ?Array): number => { return data ? Math.ceil(data.length / this.props.numColumns) : 0; }; - _keyExtractor = (items: ItemT | Array, index: number): string => { + _keyExtractor = (items: ItemT | Array, index: number) => { const {keyExtractor, numColumns} = this.props; if (numColumns > 1) { invariant( diff --git a/Libraries/Experimental/MetroListView.js b/Libraries/CustomComponents/Lists/MetroListView.js similarity index 100% rename from Libraries/Experimental/MetroListView.js rename to Libraries/CustomComponents/Lists/MetroListView.js diff --git a/Libraries/Experimental/SectionList.js b/Libraries/CustomComponents/Lists/SectionList.js similarity index 55% rename from Libraries/Experimental/SectionList.js rename to Libraries/CustomComponents/Lists/SectionList.js index 65bb78ac9441bf..2bc66a65affe54 100644 --- a/Libraries/Experimental/SectionList.js +++ b/Libraries/CustomComponents/Lists/SectionList.js @@ -47,8 +47,8 @@ type SectionBase = { key: string, // Optional props will override list-wide props just for this section. - renderItem?: ?({item: SectionItemT, index: number}) => ?React.Element<*>, - SeparatorComponent?: ?ReactClass<*>, + renderItem?: ?(info: {item: SectionItemT, index: number}) => ?React.Element, + SeparatorComponent?: ?ReactClass, keyExtractor?: (item: SectionItemT) => string, // TODO: support more optional/override props @@ -57,53 +57,52 @@ type SectionBase = { // onViewableItemsChanged?: ({viewableItems: Array, changed: Array}) => void, }; -type RequiredProps> = { +type RequiredProps> = { sections: Array, }; -type OptionalProps> = { +type OptionalProps> = { /** * Default renderer for every item in every section. Can be over-ridden on a per-section basis. */ - renderItem: ({item: Item, index: number}) => ?React.Element<*>, + renderItem: (info: {item: Item, index: number}) => ?React.Element, /** * Rendered in between adjacent Items within each section. */ - ItemSeparatorComponent?: ?ReactClass<*>, + ItemSeparatorComponent?: ?ReactClass, /** * Rendered at the very beginning of the list. */ - ListHeaderComponent?: ?ReactClass<*>, + ListHeaderComponent?: ?ReactClass, /** * Rendered at the very end of the list. */ - ListFooterComponent?: ?ReactClass<*>, + ListFooterComponent?: ?ReactClass, /** * Rendered at the top of each section. Sticky headers are not yet supported. */ - renderSectionHeader?: ?({section: SectionT}) => ?React.Element<*>, + renderSectionHeader?: ?(info: {section: SectionT}) => ?React.Element, /** * Rendered in between each section. */ - SectionSeparatorComponent?: ?ReactClass<*>, + SectionSeparatorComponent?: ?ReactClass, /** - * Warning: Virtualization can drastically improve memory consumption for long lists, but trashes - * the state of items when they scroll out of the render window, so make sure all relavent data is - * stored outside of the recursive `renderItem` instance tree. + * Used to extract a unique key for a given item at the specified index. Key is used for caching + * and as the react key to track item re-ordering. The default extractor checks item.key, then + * falls back to using the index, like react does. */ - enableVirtualization?: ?boolean, keyExtractor: (item: Item, index: number) => string, - onEndReached?: ?({distanceFromEnd: number}) => void, + onEndReached?: ?(info: {distanceFromEnd: number}) => void, /** * If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make * sure to also set the `refreshing` prop correctly. */ - onRefresh?: ?Function, + onRefresh?: ?() => void, /** * Called when the viewability of rows changes, as defined by the * `viewabilityConfig` prop. */ - onViewableItemsChanged?: ?({viewableItems: Array, changed: Array}) => void, + onViewableItemsChanged?: ?(info: {viewableItems: Array, changed: Array}) => void, /** * Set this true while waiting for new data from a refresh. */ @@ -127,31 +126,65 @@ type DefaultProps = typeof VirtualizedSectionList.defaultProps; * A performant interface for rendering sectioned lists, supporting the most handy features: * * - Fully cross-platform. - * - Viewability callbacks. - * - Footer support. - * - Separator support. - * - Heterogeneous data and item support. + * - Configurable viewability callbacks. + * - List header support. + * - List footer support. + * - Item separator support. + * - Section header support. + * - Section separator support. + * - Heterogeneous data and item rendering support. * - Pull to Refresh. + * - Scroll loading. * - * If you don't need section support and want a simpler interface, use FlatList. + * If you don't need section support and want a simpler interface, use [``](/react-native/docs/flatlist.html). + * + * If you need _sticky_ section header support, use `ListView` for now. + * + * Simple Examples: + * + *

} + * sections={[ // homogenous rendering between sections + * {data: [...], key: ...}, + * {data: [...], key: ...}, + * {data: [...], key: ...}, + * ]} + * /> + * + * + * + * This is a convenience wrapper around [``](/react-native/docs/virtualizedlist.html), + * and thus inherits the following caveats: + * + * - Internal state is not preserved when content scrolls out of the render window. Make sure all + * your data is captured in the item data or external stores like Flux, Redux, or Relay. + * - This is a `PureComponent` which means that it will not re-render if `props` remain shallow- + * equal. Make sure that everything your `renderItem` function depends on is passed as a prop that + * is not `===` after updates, otherwise your UI may not update on changes. This includes the + * `data` prop and parent component state. + * - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously + * offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see + * blank content. This is a tradeoff that can be adjusted to suit the needs of each application, + * and we are working on improving it behind the scenes. + * - By default, the list looks for a `key` prop on each item and uses that for the React key. + * Alternatively, you can provide a custom `keyExtractor` prop. */ -class SectionList> - extends React.PureComponent, *> +class SectionList> + extends React.PureComponent, void> { props: Props; static defaultProps: DefaultProps = VirtualizedSectionList.defaultProps; render() { - const {ListFooterComponent, ListHeaderComponent, ItemSeparatorComponent} = this.props; const List = this.props.legacyImplementation ? MetroListView : VirtualizedSectionList; - return ( - - ); + return ; } } diff --git a/Libraries/Experimental/ViewabilityHelper.js b/Libraries/CustomComponents/Lists/ViewabilityHelper.js similarity index 88% rename from Libraries/Experimental/ViewabilityHelper.js rename to Libraries/CustomComponents/Lists/ViewabilityHelper.js index a8a185165c1697..f3cc0e584b34d4 100644 --- a/Libraries/Experimental/ViewabilityHelper.js +++ b/Libraries/CustomComponents/Lists/ViewabilityHelper.js @@ -11,7 +11,7 @@ */ 'use strict'; -const invariant = require('invariant'); +const invariant = require('fbjs/lib/invariant'); export type ViewToken = {item: any, key: string, index: ?number, isViewable: boolean, section?: any}; @@ -42,18 +42,12 @@ export type ViewabilityConfig = {| * render. */ waitForInteraction?: boolean, - - /** - * Criteria to filter out certain scroll events so they don't count as interactions. By default, - * any non-zero scroll offset will be considered an interaction. - */ - scrollInteractionFilter?: {| - minimumOffset?: number, // scrolls with an offset less than this are ignored. - minimumElapsed?: number, // scrolls that happen before this are ignored. - |}, |}; /** +* A Utility class for calculating viewable items based on current metrics like scroll position and +* layout. +* * An item is said to be in a "viewable" state when any of the following * is true for longer than `minViewTime` milliseconds (after an interaction if `waitForInteraction` * is true): @@ -71,17 +65,19 @@ class ViewabilityHelper { _viewableItems: Map = new Map(); constructor(config: ViewabilityConfig = {viewAreaCoveragePercentThreshold: 0}) { - invariant( - config.scrollInteractionFilter == null || config.waitForInteraction, - 'scrollInteractionFilter only works in conjunction with waitForInteraction', - ); this._config = config; } + /** + * Cleanup, e.g. on unmount. Clears any pending timers. + */ dispose() { this._timers.forEach(clearTimeout); } + /** + * Determines which items are viewable based on the current metrics and config. + */ computeViewableItems( itemCount: number, scrollOffset: number, @@ -135,6 +131,10 @@ class ViewabilityHelper { return viewableIndices; } + /** + * Figures out which items are viewable and how that has changed from before and calls + * `onViewableItemsChanged` as appropriate. + */ onUpdate( itemCount: number, scrollOffset: number, @@ -150,17 +150,6 @@ class ViewabilityHelper { this._lastUpdateTime = updateTime; } const updateElapsed = this._lastUpdateTime ? updateTime - this._lastUpdateTime : 0; - if (this._config.waitForInteraction && !this._hasInteracted && scrollOffset !== 0) { - const filter = this._config.scrollInteractionFilter; - if (filter) { - if ((filter.minimumOffset == null || scrollOffset >= filter.minimumOffset) && - (filter.minimumElapsed == null || updateElapsed >= filter.minimumElapsed)) { - this._hasInteracted = true; - } - } else { - this._hasInteracted = true; - } - } if (this._config.waitForInteraction && !this._hasInteracted) { return; } @@ -196,6 +185,9 @@ class ViewabilityHelper { } } + /** + * Records that an interaction has happened even if there has been no scroll. + */ recordInteraction() { this._hasInteracted = true; } diff --git a/Libraries/Experimental/VirtualizeUtils.js b/Libraries/CustomComponents/Lists/VirtualizeUtils.js similarity index 99% rename from Libraries/Experimental/VirtualizeUtils.js rename to Libraries/CustomComponents/Lists/VirtualizeUtils.js index c366cc80a2047a..e03fa4dd725662 100644 --- a/Libraries/Experimental/VirtualizeUtils.js +++ b/Libraries/CustomComponents/Lists/VirtualizeUtils.js @@ -11,7 +11,7 @@ */ 'use strict'; -const invariant = require('invariant'); +const invariant = require('fbjs/lib/invariant'); /** * Used to find the indices of the frames that overlap the given offsets. Useful for finding the diff --git a/Libraries/Experimental/VirtualizedList.js b/Libraries/CustomComponents/Lists/VirtualizedList.js similarity index 79% rename from Libraries/Experimental/VirtualizedList.js rename to Libraries/CustomComponents/Lists/VirtualizedList.js index bf10add143d824..9040fcf1b81763 100644 --- a/Libraries/Experimental/VirtualizedList.js +++ b/Libraries/CustomComponents/Lists/VirtualizedList.js @@ -34,6 +34,7 @@ const Batchinator = require('Batchinator'); const React = require('React'); +const ReactNative = require('ReactNative'); const RefreshControl = require('RefreshControl'); const ScrollView = require('ScrollView'); const View = require('View'); @@ -47,21 +48,8 @@ const {computeWindowedRenderLimits} = require('VirtualizeUtils'); import type {ViewabilityConfig, ViewToken} from 'ViewabilityHelper'; type Item = any; -type renderItemType = ({item: Item, index: number}) => ?React.Element<*>; +type renderItemType = (info: {item: Item, index: number}) => ?React.Element; -/** - * Renders a virtual list of items given a data blob and accessor functions. Items that are outside - * the render window (except for the initial items at the top) are 'virtualized' e.g. unmounted or - * never rendered in the first place. This improves performance and saves memory for large data - * sets, but will reset state on items that scroll too far out of the render window. - * - * TODO: Note that LayoutAnimation and sticky section headers both have bugs when used with this and - * are therefor not supported, but new Animated impl might work? - * https://github.com/facebook/react-native/pull/11315 - * - * TODO: removeClippedSubviews might not be necessary and may cause bugs? - * - */ type RequiredProps = { renderItem: renderItemType, /** @@ -71,12 +59,9 @@ type RequiredProps = { data?: any, }; type OptionalProps = { - FooterComponent?: ?ReactClass<*>, - HeaderComponent?: ?ReactClass<*>, - SeparatorComponent?: ?ReactClass<*>, /** * `debug` will turn on extra logging and visual overlays to aid with debugging both usage and - * implementation. + * implementation, but with a significant perf hit. */ debug?: ?boolean, /** @@ -85,15 +70,30 @@ type OptionalProps = { * this for debugging purposes. */ disableVirtualization: boolean, - getItem: (items: any, index: number) => ?Item, - getItemCount: (items: any) => number, - getItemLayout?: (items: any, index: number) => + /** + * A generic accessor for extracting an item from any sort of data blob. + */ + getItem: (data: any, index: number) => ?Item, + /** + * Determines how many items are in the data blob. + */ + getItemCount: (data: any) => number, + getItemLayout?: (data: any, index: number) => {length: number, offset: number, index: number}, // e.g. height, y horizontal?: ?boolean, + /** + * How many items to render in the initial batch. This should be enough to fill the screen but not + * much more. + */ initialNumToRender: number, keyExtractor: (item: Item, index: number) => string, + /** + * The maximum number of items to render in each incremental render batch. The more rendered at + * once, the better the fill rate, but responsiveness my suffer because rendering content may + * interfere with responding to button taps or other interactions. + */ maxToRenderPerBatch: number, - onEndReached?: ?({distanceFromEnd: number}) => void, + onEndReached?: ?(info: {distanceFromEnd: number}) => void, onEndReachedThreshold?: ?number, // units of visible length onLayout?: ?Function, /** @@ -105,26 +105,80 @@ type OptionalProps = { * Called when the viewability of rows changes, as defined by the * `viewabilityConfig` prop. */ - onViewableItemsChanged?: ?({viewableItems: Array, changed: Array}) => void, + onViewableItemsChanged?: ?(info: {viewableItems: Array, changed: Array}) => void, /** * Set this true while waiting for new data from a refresh. */ refreshing?: ?boolean, + /** + * A native optimization that removes clipped subviews (those outside the parent) from the view + * hierarchy to offload work from the native rendering system. They are still kept around so no + * memory is saved and state is preserved. + */ removeClippedSubviews?: boolean, - renderScrollComponent: (props: Object) => React.Element<*>, + /** + * Render a custom scroll component, e.g. with a differently styled `RefreshControl`. + */ + renderScrollComponent: (props: Object) => React.Element, shouldItemUpdate: ( props: {item: Item, index: number}, nextProps: {item: Item, index: number} ) => boolean, + /** + * Amount of time between low-pri item render batches, e.g. for rendering items quite a ways off + * screen. Similar fill rate/responsiveness tradeoff as `maxToRenderPerBatch`. + */ updateCellsBatchingPeriod: number, viewabilityConfig?: ViewabilityConfig, - windowSize: number, // units of visible length + /** + * Determines the maximum number of items rendered outside of the visible area, in units of + * visible lengths. So if your list fills the screen, then `windowSize={21}` (the default) will + * render the visible screen area plus up to 10 screens above and 10 below the viewport. Reducing + * this number will reduce memory consumption and may improve performance, but will increase the + * chance that fast scrolling may reveal momentary blank areas of unrendered content. + */ + windowSize: number, }; export type Props = RequiredProps & OptionalProps; let _usedIndexForKey = false; -class VirtualizedList extends React.PureComponent { +type State = {first: number, last: number}; + +/** + * Base implementation for the more convenient [``](/react-native/docs/flatlist.html) + * and [``](/react-native/docs/sectionlist.html) components, which are also better + * documented. In general, this should only really be used if you need more flexibility than + * `FlatList` provides, e.g. for use with immutable data instead of plain arrays. + * + * Virtualization massively improves memory consumption and performance of large lists by + * maintaining a finite render window of active items and replacing all items outside of the render + * window with appropriately sized blank space. The window adapts to scrolling behavior, and items + * are rendered incrementally with low-pri (after any running interactions) if they are far from the + * visible area, or with hi-pri otherwise to minimize the potential of seeing blank space. + * + * Some caveats: + * + * - Internal state is not preserved when content scrolls out of the render window. Make sure all + * your data is captured in the item data or external stores like Flux, Redux, or Relay. + * - This is a `PureComponent` which means that it will not re-render if `props` remain shallow- + * equal. Make sure that everything your `renderItem` function depends on is passed as a prop that + * is not `===` after updates, otherwise your UI may not update on changes. This includes the + * `data` prop and parent component state. + * - In order to constrain memory and enable smooth scrolling, content is rendered asynchronously + * offscreen. This means it's possible to scroll faster than the fill rate ands momentarily see + * blank content. This is a tradeoff that can be adjusted to suit the needs of each application, + * and we are working on improving it behind the scenes. + * - By default, the list looks for a `key` prop on each item and uses that for the React key. + * Alternatively, you can provide a custom `keyExtractor` prop. + * + * NOTE: `LayoutAnimation` and sticky section headers both have bugs when used with this and are + * therefore not officially supported yet. + * + * NOTE: `removeClippedSubviews` might not be necessary and may cause bugs. If you see issues with + * content not rendering, try disabling it, and we may change the default there. + */ +class VirtualizedList extends React.PureComponent { props: Props; // scrollToEnd may be janky without getItemLayout prop @@ -181,6 +235,14 @@ class VirtualizedList extends React.PureComponent { this._updateViewableItems(this.props.data); } + getScrollableNode() { + if (this._scrollRef && this._scrollRef.getScrollableNode) { + return this._scrollRef.getScrollableNode(); + } else { + return ReactNative.findNodeHandle(this._scrollRef); + } + } + static defaultProps = { disableVirtualization: false, getItem: (data: any, index: number) => data[index], @@ -228,7 +290,7 @@ class VirtualizedList extends React.PureComponent { windowSize: 21, // multiples of length }; - state = { + state: State = { first: 0, last: this.props.initialNumToRender, }; @@ -237,8 +299,10 @@ class VirtualizedList extends React.PureComponent { super(props); invariant( !props.onScroll || !props.onScroll.__isNative, - 'VirtualizedList does not support AnimatedEvent with onScroll and useNativeDriver', + 'Components based on VirtualizedList must be wrapped with Animated.createAnimatedComponent ' + + 'to support native onScroll events with useNativeDriver', ); + this._updateCellsToRenderBatcher = new Batchinator( this._updateCellsToRender, this.props.updateCellsBatchingPeriod, @@ -268,7 +332,7 @@ class VirtualizedList extends React.PureComponent { } _pushCells(cells, first, last) { - const {SeparatorComponent, data, getItem, getItemCount, keyExtractor} = this.props; + const {ItemSeparatorComponent, data, getItem, getItemCount, keyExtractor} = this.props; const end = getItemCount(data) - 1; last = Math.min(end, last); for (let ii = first; ii <= last; ii++) { @@ -281,24 +345,24 @@ class VirtualizedList extends React.PureComponent { index={ii} item={item} key={key} - onLayout={this._onCellLayout} + onCellLayout={this._onCellLayout} onUnmount={this._onCellUnmount} parentProps={this.props} /> ); - if (SeparatorComponent && ii < end) { - cells.push(); + if (ItemSeparatorComponent && ii < end) { + cells.push(); } } } render() { - const {FooterComponent, HeaderComponent} = this.props; + const {ListFooterComponent, ListHeaderComponent} = this.props; const {data, disableVirtualization, horizontal} = this.props; const cells = []; - if (HeaderComponent) { + if (ListHeaderComponent) { cells.push( - + ); } @@ -338,10 +402,10 @@ class VirtualizedList extends React.PureComponent { ); } } - if (FooterComponent) { + if (ListFooterComponent) { cells.push( - + ); } @@ -351,6 +415,7 @@ class VirtualizedList extends React.PureComponent { onContentSizeChange: this._onContentSizeChange, onLayout: this._onLayout, onScroll: this._onScroll, + onScrollBeginDrag: this._onScrollBeginDrag, ref: this._captureScrollRef, scrollEventThrottle: 50, // TODO: Android support }, @@ -487,6 +552,9 @@ class VirtualizedList extends React.PureComponent { } _onContentSizeChange = (width: number, height: number) => { + if (this.props.onContentSizeChange) { + this.props.onContentSizeChange(width, height); + } this._scrollMetrics.contentLength = this._selectLength({height, width}); this._updateCellsToRenderBatcher.schedule(); }; @@ -545,6 +613,10 @@ class VirtualizedList extends React.PureComponent { this._updateCellsToRenderBatcher.schedule(); }; + _onScrollBeginDrag = (e): void => { + this._viewabilityHelper.recordInteraction(); + this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e); + }; _updateCellsToRender = () => { const {data, disableVirtualization, getItemCount, onEndReachedThreshold} = this.props; this._updateViewableItems(data); @@ -571,7 +643,7 @@ class VirtualizedList extends React.PureComponent { }); }; - _createViewToken = (index: number, isViewable: boolean): ViewToken => { + _createViewToken = (index: number, isViewable: boolean) => { const {data, getItem, keyExtractor} = this.props; const item = getItem(data, index); invariant(item, 'Missing item for index ' + index); @@ -630,7 +702,7 @@ class CellRenderer extends React.Component { cellKey: string, index: number, item: Item, - onLayout: (event: Object, cellKey: string, index: number) => void, + onCellLayout: (event: Object, cellKey: string, index: number) => void, onUnmount: (cellKey: string) => void, parentProps: { renderItem: renderItemType, @@ -642,7 +714,7 @@ class CellRenderer extends React.Component { }, }; _onLayout = (e) => { - this.props.onLayout(e, this.props.cellKey, this.props.index); + this.props.onCellLayout(e, this.props.cellKey, this.props.index); } componentWillUnmount() { this.props.onUnmount(this.props.cellKey); diff --git a/Libraries/Experimental/VirtualizedSectionList.js b/Libraries/CustomComponents/Lists/VirtualizedSectionList.js similarity index 98% rename from Libraries/Experimental/VirtualizedSectionList.js rename to Libraries/CustomComponents/Lists/VirtualizedSectionList.js index c0bacd3c8ba4fc..317891c4d3ebc2 100644 --- a/Libraries/Experimental/VirtualizedSectionList.js +++ b/Libraries/CustomComponents/Lists/VirtualizedSectionList.js @@ -36,8 +36,8 @@ const React = require('React'); const View = require('View'); const VirtualizedList = require('VirtualizedList'); -const invariant = require('invariant'); -const warning = require('warning'); +const invariant = require('fbjs/lib/invariant'); +const warning = require('fbjs/lib/warning'); import type {ViewToken} from 'ViewabilityHelper'; import type {Props as VirtualizedListProps} from 'VirtualizedList'; @@ -84,7 +84,7 @@ type OptionalProps = { renderSectionHeader?: ?({section: SectionT}) => ?React.Element<*>, /** * Rendered at the bottom of every Section, except the very last one, in place of the normal - * SeparatorComponent. + * ItemSeparatorComponent. */ SectionSeparatorComponent?: ?ReactClass<*>, /** diff --git a/Libraries/Experimental/__flowtests__/FlatList-flowtest.js b/Libraries/CustomComponents/Lists/__flowtests__/FlatList-flowtest.js similarity index 100% rename from Libraries/Experimental/__flowtests__/FlatList-flowtest.js rename to Libraries/CustomComponents/Lists/__flowtests__/FlatList-flowtest.js diff --git a/Libraries/Experimental/__flowtests__/SectionList-flowtest.js b/Libraries/CustomComponents/Lists/__flowtests__/SectionList-flowtest.js similarity index 100% rename from Libraries/Experimental/__flowtests__/SectionList-flowtest.js rename to Libraries/CustomComponents/Lists/__flowtests__/SectionList-flowtest.js diff --git a/Libraries/Experimental/__tests__/ViewabilityHelper-test.js b/Libraries/CustomComponents/Lists/__tests__/ViewabilityHelper-test.js similarity index 96% rename from Libraries/Experimental/__tests__/ViewabilityHelper-test.js rename to Libraries/CustomComponents/Lists/__tests__/ViewabilityHelper-test.js index 9d7876f49db92a..199132ae8156bd 100644 --- a/Libraries/Experimental/__tests__/ViewabilityHelper-test.js +++ b/Libraries/CustomComponents/Lists/__tests__/ViewabilityHelper-test.js @@ -10,6 +10,7 @@ 'use strict'; jest.unmock('ViewabilityHelper'); + const ViewabilityHelper = require('ViewabilityHelper'); let rowFrames; @@ -315,14 +316,11 @@ describe('onUpdate', function() { ); it( - 'waitForInteraction blocks callback until scroll', + 'waitForInteraction blocks callback until interaction', function() { const helper = new ViewabilityHelper({ waitForInteraction: true, viewAreaCoveragePercentThreshold: 0, - scrollInteractionFilter: { - minimumOffset: 20, - }, }); rowFrames = { a: {y: 0, height: 200}, @@ -339,15 +337,9 @@ describe('onUpdate', function() { onViewableItemsChanged, ); expect(onViewableItemsChanged).not.toBeCalled(); - helper.onUpdate( - data.length, - 10, // not far enough to meet minimumOffset - 100, - getFrameMetrics, - createViewToken, - onViewableItemsChanged, - ); - expect(onViewableItemsChanged).not.toBeCalled(); + + helper.recordInteraction(); + helper.onUpdate( data.length, 20, diff --git a/Libraries/Experimental/__tests__/VirtualizeUtils-test.js b/Libraries/CustomComponents/Lists/__tests__/VirtualizeUtils-test.js similarity index 100% rename from Libraries/Experimental/__tests__/VirtualizeUtils-test.js rename to Libraries/CustomComponents/Lists/__tests__/VirtualizeUtils-test.js diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js index 3290ba462d2e2e..fe40a609110e46 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js @@ -1,5 +1,26 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. ("Facebook") owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the "Software"). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * ("Your Software"). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @providesModule NavigationRouteStack * @flow @@ -135,7 +156,7 @@ class RouteStack { invariant(this._routeNodes.indexOf(route) === -1, 'route must be unique'); // When pushing, removes the rest of the routes past the current index. - var routeNodes = this._routeNodes.withMutations((list: List) => { + var routeNodes = this._routeNodes.withMutations((list: List) => { list.slice(0, this._index + 1).push(new RouteNode(route)); }); @@ -232,7 +253,7 @@ class RouteStack { return new Set(items); } - _update(index: number, routeNodes: List): RouteStack { + _update(index: number, routeNodes: List): RouteStack { if (this._index === index && this._routeNodes === routeNodes) { return this; } diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js index 02b0e3dbb83f58..d188c366f74596 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js @@ -1,10 +1,30 @@ /** * Copyright (c) 2015, Facebook, Inc. All rights reserved. * + * Facebook, Inc. ("Facebook") owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the "Software"). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * ("Your Software"). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * * @providesModule NavigationTreeNode * @flow */ - 'use strict'; var invariant = require('fbjs/lib/invariant'); diff --git a/Libraries/Image/AssetRegistry.js b/Libraries/Image/AssetRegistry.js index 440f16587b6643..fd03c69b57a384 100644 --- a/Libraries/Image/AssetRegistry.js +++ b/Libraries/Image/AssetRegistry.js @@ -1,11 +1,17 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule AssetRegistry * @flow */ 'use strict'; + export type PackagerAsset = { __packager_asset: boolean, fileSystemLocation: string, diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index b0e813b3fd8b6e..7d4a7752d3b56f 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -11,23 +11,24 @@ */ 'use strict'; -var NativeMethodsMixin = require('NativeMethodsMixin'); -var NativeModules = require('NativeModules'); var ImageResizeMode = require('ImageResizeMode'); var ImageStylePropTypes = require('ImageStylePropTypes'); -var ViewStylePropTypes = require('ViewStylePropTypes'); +var NativeMethodsMixin = require('NativeMethodsMixin'); +var NativeModules = require('NativeModules'); +var PropTypes = require('react/lib/ReactPropTypes'); var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +var Set = require('Set'); var StyleSheet = require('StyleSheet'); var StyleSheetPropType = require('StyleSheetPropType'); var View = require('View'); +var ViewStylePropTypes = require('ViewStylePropTypes'); +var filterObject = require('fbjs/lib/filterObject'); var flattenStyle = require('flattenStyle'); var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); var resolveAssetSource = require('resolveAssetSource'); -var Set = require('Set'); -var filterObject = require('fbjs/lib/filterObject'); var PropTypes = React.PropTypes; var { @@ -106,6 +107,10 @@ var Image = React.createClass({ height: PropTypes.number, })) ]), + /** + * blurRadius: the blur radius of the blur filter added to the image + */ + blurRadius: PropTypes.number, /** * similarly to `source`, this property represents the resource used to render * the loading indicator for the image, displayed until image is ready to be diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 5b029d1128460b..3177b1f5e2fcb4 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -126,6 +126,7 @@ const ImageViewManager = NativeModules.ImageViewManager; * ``` * */ +// $FlowFixMe(>=0.41.0) const Image = React.createClass({ propTypes: { /** @@ -288,6 +289,8 @@ const Image = React.createClass({ * does not fully load/download the image data. A proper, supported way to * preload images will be provided as a separate API. * + * Does not work for static image resources. + * * @param uri The location of the image. * @param success The function that will be called if the image was successfully found and width * and height retrieved. diff --git a/Libraries/Interaction/InteractionMixin.js b/Libraries/Interaction/InteractionMixin.js index b92120f90fe4bf..b0e3104407ea36 100644 --- a/Libraries/Interaction/InteractionMixin.js +++ b/Libraries/Interaction/InteractionMixin.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule InteractionMixin * @flow diff --git a/Libraries/Linking/Linking.js b/Libraries/Linking/Linking.js index 8fe85bfd045224..1902bfb2a793ef 100644 --- a/Libraries/Linking/Linking.js +++ b/Libraries/Linking/Linking.js @@ -61,7 +61,7 @@ const LinkingManager = Platform.OS === 'android' ? * execution you'll need to add the following lines to you `*AppDelegate.m`: * * ``` - * #import "RCTLinkingManager.h" + * #import * * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url * sourceApplication:(NSString *)sourceApplication annotation:(id)annotation diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 55763bfe57dc28..4b30c47d7c419c 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -168,10 +168,11 @@ - (void)setBridge:(RCTBridge *)bridge } RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag - eventName:(nonnull NSString *)eventName) + eventName:(nonnull NSString *)eventName + animatedNodeTag:(nonnull NSNumber *)animatedNodeTag) { [_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) { - [nodesManager removeAnimatedEventFromView:viewTag eventName:eventName]; + [nodesManager removeAnimatedEventFromView:viewTag eventName:eventName animatedNodeTag:animatedNodeTag]; }]; } diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h index e911f1b37c2dd5..c4ffc652a693ad 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.h @@ -9,8 +9,8 @@ #import -#import #import +#import #import "RCTValueAnimatedNode.h" @@ -70,7 +70,8 @@ eventMapping:(NSDictionary *__nonnull)eventMapping; - (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag - eventName:(nonnull NSString *)eventName; + eventName:(nonnull NSString *)eventName + animatedNodeTag:(nonnull NSNumber *)animatedNodeTag; - (void)handleAnimatedEvent:(nonnull id)event; diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m index ac15ad705ddd3c..caeabba7232a62 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m @@ -11,29 +11,29 @@ #import +#import "RCTAdditionAnimatedNode.h" #import "RCTAnimatedNode.h" #import "RCTAnimationDriver.h" -#import "RCTEventAnimation.h" - -#import "RCTAdditionAnimatedNode.h" -#import "RCTInterpolationAnimatedNode.h" #import "RCTDiffClampAnimatedNode.h" #import "RCTDivisionAnimatedNode.h" +#import "RCTEventAnimation.h" +#import "RCTFrameAnimation.h" +#import "RCTInterpolationAnimatedNode.h" #import "RCTModuloAnimatedNode.h" #import "RCTMultiplicationAnimatedNode.h" -#import "RCTModuloAnimatedNode.h" #import "RCTPropsAnimatedNode.h" +#import "RCTSpringAnimation.h" #import "RCTStyleAnimatedNode.h" #import "RCTTransformAnimatedNode.h" #import "RCTValueAnimatedNode.h" -#import "RCTFrameAnimation.h" -#import "RCTSpringAnimation.h" @implementation RCTNativeAnimatedNodesManager { RCTUIManager *_uiManager; NSMutableDictionary *_animationNodes; - NSMutableDictionary *_eventDrivers; + // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time + // there will be only one driver per mapping so all code code should be optimized around that. + NSMutableDictionary *> *_eventDrivers; NSMutableSet> *_activeAnimations; CADisplayLink *_displayLink; } @@ -207,7 +207,7 @@ - (void)startAnimatingNode:(nonnull NSNumber *)animationId RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag]; NSString *type = config[@"type"]; - idanimationDriver; + id animationDriver; if ([type isEqual:@"frames"]) { animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId @@ -233,7 +233,7 @@ - (void)startAnimatingNode:(nonnull NSNumber *)animationId - (void)stopAnimation:(nonnull NSNumber *)animationId { - for (iddriver in _activeAnimations) { + for (id driver in _activeAnimations) { if ([driver.animationId isEqual:animationId]) { [driver removeAnimation]; [_activeAnimations removeObject:driver]; @@ -264,15 +264,36 @@ - (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag NSArray *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]]; RCTEventAnimation *driver = - [[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node]; + [[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node]; - _eventDrivers[[NSString stringWithFormat:@"%@%@", viewTag, eventName]] = driver; + NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName]; + if (_eventDrivers[key] != nil) { + [_eventDrivers[key] addObject:driver]; + } else { + NSMutableArray *drivers = [NSMutableArray new]; + [drivers addObject:driver]; + _eventDrivers[key] = drivers; + } } - (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag eventName:(nonnull NSString *)eventName + animatedNodeTag:(nonnull NSNumber *)animatedNodeTag { - [_eventDrivers removeObjectForKey:[NSString stringWithFormat:@"%@%@", viewTag, eventName]]; + NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName]; + if (_eventDrivers[key] != nil) { + if (_eventDrivers[key].count == 1) { + [_eventDrivers removeObjectForKey:key]; + } else { + NSMutableArray *driversForKey = _eventDrivers[key]; + for (NSUInteger i = 0; i < driversForKey.count; i++) { + if (driversForKey[i].valueNode.nodeTag == animatedNodeTag) { + [driversForKey removeObjectAtIndex:i]; + break; + } + } + } + } } - (void)handleAnimatedEvent:(id)event @@ -282,9 +303,12 @@ - (void)handleAnimatedEvent:(id)event } NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName]; - RCTEventAnimation *driver = _eventDrivers[key]; - if (driver) { - [driver updateWithEvent:event]; + NSMutableArray *driversForKey = _eventDrivers[key]; + if (driversForKey) { + for (RCTEventAnimation *driver in driversForKey) { + [driver updateWithEvent:event]; + } + [self updateAnimations]; } } @@ -337,13 +361,13 @@ - (void)stopAnimationLoop - (void)stepAnimations { - for (idanimationDriver in _activeAnimations) { + for (id animationDriver in _activeAnimations) { [animationDriver stepAnimation]; } [self updateAnimations]; - for (idanimationDriver in [_activeAnimations copy]) { + for (id animationDriver in [_activeAnimations copy]) { if (animationDriver.animationHasFinished) { [animationDriver removeAnimation]; [_activeAnimations removeObject:animationDriver]; diff --git a/Libraries/NavigationExperimental/NavigationExperimental.js b/Libraries/NavigationExperimental/NavigationExperimental.js index 4f1bb068ce14a8..c4fd955c8fd34c 100644 --- a/Libraries/NavigationExperimental/NavigationExperimental.js +++ b/Libraries/NavigationExperimental/NavigationExperimental.js @@ -18,6 +18,18 @@ const NavigationPropTypes = require('NavigationPropTypes'); const NavigationStateUtils = require('NavigationStateUtils'); const NavigationTransitioner = require('NavigationTransitioner'); +const warning = require('fbjs/lib/warning'); + +// This warning will only be reached if the user has required the module +warning( + false, + 'NavigationExperimental is deprecated and will be removed in a future ' + + 'version of React Native. The NavigationExperimental views live on in ' + + 'the React-Navigation project, which also makes it easy to declare ' + + 'navigation logic for your app. Learn more at https://reactnavigation.org/' +); + + const NavigationExperimental = { // Core StateUtils: NavigationStateUtils, diff --git a/Libraries/Network/RCTNetworking.ios.js b/Libraries/Network/RCTNetworking.ios.js index b67049036d5628..dd569a07d36aca 100644 --- a/Libraries/Network/RCTNetworking.ios.js +++ b/Libraries/Network/RCTNetworking.ios.js @@ -33,7 +33,8 @@ class RCTNetworking extends NativeEventEmitter { responseType: 'text' | 'base64', incrementalUpdates: boolean, timeout: number, - callback: (requestId: number) => any + callback: (requestId: number) => any, + withCredentials: boolean ) { const body = convertRequestBody(data); RCTNetworkingNative.sendRequest({ @@ -43,7 +44,8 @@ class RCTNetworking extends NativeEventEmitter { headers, responseType, incrementalUpdates, - timeout + timeout, + withCredentials }, callback); } diff --git a/Libraries/Network/RCTNetworking.mm b/Libraries/Network/RCTNetworking.mm index 67379f96b59865..4a7f7ccd668743 100644 --- a/Libraries/Network/RCTNetworking.mm +++ b/Libraries/Network/RCTNetworking.mm @@ -230,6 +230,7 @@ - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)q request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET"; request.allHTTPHeaderFields = [self stripNullsInRequestHeaders:[RCTConvert NSDictionary:query[@"headers"]]]; request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]]; + request.HTTPShouldHandleCookies = [RCTConvert BOOL:query[@"withCredentials"]]; NSDictionary *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])]; NSString *trackingName = data[@"trackingName"]; if (trackingName) { diff --git a/Libraries/Network/XMLHttpRequest.js b/Libraries/Network/XMLHttpRequest.js index bf6d30191a2e33..92669b8b2d81b9 100644 --- a/Libraries/Network/XMLHttpRequest.js +++ b/Libraries/Network/XMLHttpRequest.js @@ -81,6 +81,7 @@ class XMLHttpRequestEventTarget extends EventTarget(...REQUEST_EVENTS) { onprogress: ?Function; ontimeout: ?Function; onerror: ?Function; + onabort: ?Function; onloadend: ?Function; } @@ -109,6 +110,7 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { onprogress: ?Function; ontimeout: ?Function; onerror: ?Function; + onabort: ?Function; onloadend: ?Function; onreadystatechange: ?Function; @@ -117,6 +119,7 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { status: number = 0; timeout: number = 0; responseURL: ?string; + withCredentials: boolean = false upload: XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget(); @@ -499,6 +502,7 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) { incrementalEvents, this.timeout, this.__didCreateRequest.bind(this), + this.withCredentials ); } diff --git a/Libraries/Promise.js b/Libraries/Promise.js index 7b52e135cc33f9..615b990b1b17a0 100644 --- a/Libraries/Promise.js +++ b/Libraries/Promise.js @@ -13,14 +13,26 @@ const Promise = require('fbjs/lib/Promise.native'); +const prettyFormat = require('pretty-format'); + if (__DEV__) { require('promise/setimmediate/rejection-tracking').enable({ allRejections: true, onUnhandled: (id, error = {}) => { - const {message = null, stack = null} = error; + let message: string; + let stack: ?string; + + const stringValue = Object.prototype.toString.call(error); + if (stringValue === '[object Error]') { + message = Error.prototype.toString.call(error); + stack = error.stack; + } else { + message = prettyFormat(error); + } + const warning = `Possible Unhandled Promise Rejection (id: ${id}):\n` + - (message == null ? '' : `${message}\n`) + + `${message}\n` + (stack == null ? '' : stack); console.warn(warning); }, diff --git a/Libraries/RCTTest/SnapshotViewIOS.ios.js b/Libraries/RCTTest/SnapshotViewIOS.ios.js index 869a2914a2e22e..f14ef0a7d547e1 100644 --- a/Libraries/RCTTest/SnapshotViewIOS.ios.js +++ b/Libraries/RCTTest/SnapshotViewIOS.ios.js @@ -25,6 +25,7 @@ class SnapshotViewIOS extends React.Component { testIdentifier?: string, }; + // $FlowFixMe(>=0.41.0) static propTypes = { ...View.propTypes, // A callback when the Snapshot view is ready to be compared diff --git a/Libraries/ReactNative/YellowBox.js b/Libraries/ReactNative/YellowBox.js index e8be755b0faed2..2042431c3f4824 100644 --- a/Libraries/ReactNative/YellowBox.js +++ b/Libraries/ReactNative/YellowBox.js @@ -394,16 +394,22 @@ const textColor = 'white'; const rowGutter = 1; const rowHeight = 46; +// For unknown reasons, setting elevation: Number.MAX_VALUE causes remote debugging to +// hang on iOS (some sort of overflow maybe). Setting it to Number.MAX_SAFE_INTEGER fixes the iOS issue, but since +// elevation is an android-only style property we might as well remove it altogether for iOS. +// See: https://github.com/facebook/react-native/issues/12223 +const elevation = Platform.OS === 'android' ? Number.MAX_SAFE_INTEGER : undefined; + var styles = StyleSheet.create({ fullScreen: { height: '100%', - elevation: Number.MAX_VALUE + elevation: elevation }, inspector: { backgroundColor: backgroundColor(0.95), height: '100%', paddingTop: 5, - elevation: Number.MAX_VALUE + elevation:elevation }, inspectorButtons: { flexDirection: 'row', @@ -451,7 +457,7 @@ var styles = StyleSheet.create({ left: 0, right: 0, bottom: 0, - elevation: Number.MAX_VALUE + elevation: elevation }, listRow: { backgroundColor: backgroundColor(0.95), diff --git a/Libraries/ReactNative/renderApplication.js b/Libraries/ReactNative/renderApplication.js index 26cf1bed7f143a..0175b5c6f58e6e 100644 --- a/Libraries/ReactNative/renderApplication.js +++ b/Libraries/ReactNative/renderApplication.js @@ -18,8 +18,8 @@ var ReactNative = require('ReactNative'); var invariant = require('fbjs/lib/invariant'); -// require BackAndroid so it sets the default handler that exits the app if no listeners respond -require('BackAndroid'); +// require BackHandler so it sets the default handler that exits the app if no listeners respond +require('BackHandler'); function renderApplication( RootComponent: ReactClass, diff --git a/Libraries/StyleSheet/LayoutPropTypes.js b/Libraries/StyleSheet/LayoutPropTypes.js index d8790f2af8f900..a8ab32c0772d72 100644 --- a/Libraries/StyleSheet/LayoutPropTypes.js +++ b/Libraries/StyleSheet/LayoutPropTypes.js @@ -27,6 +27,13 @@ var ReactPropTypes = require('React').PropTypes; * algorithm and affect the positioning and sizing of views. */ var LayoutPropTypes = { + /** `display` sets the display type of this component. + * + * It works similarly to `display` in CSS, but only support 'flex' and 'none'. + * 'flex' is the default. + */ + display: ReactPropTypes.string, + /** `width` sets the width of this component. * * It works similarly to `width` in CSS, but in React Native you @@ -402,6 +409,20 @@ var LayoutPropTypes = { 'baseline' ]), + /** `alignContent` controls how a rows align in the cross direction, + * overriding the `alignContent` of the parent. + * See https://developer.mozilla.org/en-US/docs/Web/CSS/align-content + * for more details. + */ + alignContent: ReactPropTypes.oneOf([ + 'flex-start', + 'flex-end', + 'center', + 'stretch', + 'space-between', + 'space-around' + ]), + /** `overflow` controls how a children are measured and displayed. * `overflow: hidden` causes views to be clipped while `overflow: scroll` * causes views to be measured independently of their parents main axis.` diff --git a/React/Views/RCTMapAnnotation.m b/Libraries/Text/RCTShadowTextField.h similarity index 80% rename from React/Views/RCTMapAnnotation.m rename to Libraries/Text/RCTShadowTextField.h index 0b90fe8ab1e8b1..543f7fb349a0ef 100644 --- a/React/Views/RCTMapAnnotation.m +++ b/Libraries/Text/RCTShadowTextField.h @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTMapAnnotation.h" +#import -@implementation RCTMapAnnotation +@interface RCTShadowTextField : RCTShadowView @end diff --git a/React/Views/RCTMapOverlay.h b/Libraries/Text/RCTShadowTextField.m similarity index 58% rename from React/Views/RCTMapOverlay.h rename to Libraries/Text/RCTShadowTextField.m index a6fcdad5e4525e..3bc5929dc49c37 100644 --- a/React/Views/RCTMapOverlay.h +++ b/Libraries/Text/RCTShadowTextField.m @@ -7,12 +7,13 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import "RCTShadowTextField.h" -@interface RCTMapOverlay : MKPolyline +@implementation RCTShadowTextField -@property (nonatomic, copy) NSString *identifier; -@property (nonatomic, strong) UIColor *strokeColor; -@property (nonatomic, assign) CGFloat lineWidth; +- (BOOL)isYogaLeafNode +{ + return YES; +} @end diff --git a/React/Views/RCTMapOverlay.m b/Libraries/Text/RCTShadowTextView.h similarity index 80% rename from React/Views/RCTMapOverlay.m rename to Libraries/Text/RCTShadowTextView.h index 2a52dd5b28fba4..e2c6f1e7ed45db 100644 --- a/React/Views/RCTMapOverlay.m +++ b/Libraries/Text/RCTShadowTextView.h @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTMapOverlay.h" +#import -@implementation RCTMapOverlay +@interface RCTShadowTextView : RCTShadowView @end diff --git a/React/Views/RCTMapManager.h b/Libraries/Text/RCTShadowTextView.m similarity index 74% rename from React/Views/RCTMapManager.h rename to Libraries/Text/RCTShadowTextView.m index fdf678efd3ae9c..d3e3602edc4073 100644 --- a/React/Views/RCTMapManager.h +++ b/Libraries/Text/RCTShadowTextView.m @@ -7,8 +7,13 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import "RCTShadowTextView.h" -@interface RCTMapManager : RCTViewManager +@implementation RCTShadowTextView + +- (BOOL)isYogaLeafNode +{ + return YES; +} @end diff --git a/Libraries/Text/RCTText.xcodeproj/project.pbxproj b/Libraries/Text/RCTText.xcodeproj/project.pbxproj index 8e7ee1f1a20598..e2cfafd68291b7 100644 --- a/Libraries/Text/RCTText.xcodeproj/project.pbxproj +++ b/Libraries/Text/RCTText.xcodeproj/project.pbxproj @@ -27,6 +27,10 @@ 58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; }; 58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */; }; 58B512161A9E6EFF00147676 /* RCTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512141A9E6EFF00147676 /* RCTText.m */; }; + 59F60E911E661BDD0081153B /* RCTShadowTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E8E1E661BDD0081153B /* RCTShadowTextField.m */; }; + 59F60E921E661BDD0081153B /* RCTShadowTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E8E1E661BDD0081153B /* RCTShadowTextField.m */; }; + 59F60E931E661BDD0081153B /* RCTShadowTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E901E661BDD0081153B /* RCTShadowTextView.m */; }; + 59F60E941E661BDD0081153B /* RCTShadowTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59F60E901E661BDD0081153B /* RCTShadowTextView.m */; }; AF3225F91DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */; }; AF3225FA1DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */; }; /* End PBXBuildFile section */ @@ -54,6 +58,10 @@ 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextManager.m; sourceTree = ""; }; 58B512141A9E6EFF00147676 /* RCTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTText.m; sourceTree = ""; }; 58B512151A9E6EFF00147676 /* RCTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTText.h; sourceTree = ""; }; + 59F60E8D1E661BDD0081153B /* RCTShadowTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowTextField.h; sourceTree = ""; }; + 59F60E8E1E661BDD0081153B /* RCTShadowTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowTextField.m; sourceTree = ""; }; + 59F60E8F1E661BDD0081153B /* RCTShadowTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowTextView.h; sourceTree = ""; }; + 59F60E901E661BDD0081153B /* RCTShadowTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowTextView.m; sourceTree = ""; }; AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+Text.h"; sourceTree = ""; }; AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+Text.m"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -62,29 +70,33 @@ 58B511921A9E6C1200147676 = { isa = PBXGroup; children = ( + 58B5119C1A9E6C1200147676 /* Products */, AF3225F71DE5574F00D3E7E7 /* RCTConvert+Text.h */, AF3225F81DE5574F00D3E7E7 /* RCTConvert+Text.m */, - 19FC5C861D41A4220090108F /* RCTTextSelection.h */, - 19FC5C841D41A4120090108F /* RCTTextSelection.m */, 58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */, 58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */, 58B511C81A9E6C5C00147676 /* RCTShadowRawText.h */, 58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */, 58B511CA1A9E6C5C00147676 /* RCTShadowText.h */, 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */, + 59F60E8D1E661BDD0081153B /* RCTShadowTextField.h */, + 59F60E8E1E661BDD0081153B /* RCTShadowTextField.m */, + 59F60E8F1E661BDD0081153B /* RCTShadowTextView.h */, + 59F60E901E661BDD0081153B /* RCTShadowTextView.m */, 58B512151A9E6EFF00147676 /* RCTText.h */, 58B512141A9E6EFF00147676 /* RCTText.m */, - 58B511CC1A9E6C5C00147676 /* RCTTextManager.h */, - 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */, 1362F0FC1B4D51F400E06D8C /* RCTTextField.h */, 1362F0FD1B4D51F400E06D8C /* RCTTextField.m */, 1362F0FE1B4D51F400E06D8C /* RCTTextFieldManager.h */, 1362F0FF1B4D51F400E06D8C /* RCTTextFieldManager.m */, + 58B511CC1A9E6C5C00147676 /* RCTTextManager.h */, + 58B511CD1A9E6C5C00147676 /* RCTTextManager.m */, + 19FC5C861D41A4220090108F /* RCTTextSelection.h */, + 19FC5C841D41A4120090108F /* RCTTextSelection.m */, 131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */, 131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */, 131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */, 131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */, - 58B5119C1A9E6C1200147676 /* Products */, ); indentWidth = 2; sourceTree = ""; @@ -180,8 +192,10 @@ 2D3B5F3B1D9B106F00451313 /* RCTTextView.m in Sources */, 2D3B5F3A1D9B106F00451313 /* RCTTextFieldManager.m in Sources */, 2D3B5F341D9B103100451313 /* RCTRawTextManager.m in Sources */, + 59F60E921E661BDD0081153B /* RCTShadowTextField.m in Sources */, AF3225FA1DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */, 2D3B5F3C1D9B106F00451313 /* RCTTextViewManager.m in Sources */, + 59F60E941E661BDD0081153B /* RCTShadowTextView.m in Sources */, 2D3B5F331D9B102D00451313 /* RCTTextSelection.m in Sources */, 2D3B5F351D9B103300451313 /* RCTShadowRawText.m in Sources */, ); @@ -198,8 +212,10 @@ 1362F1001B4D51F400E06D8C /* RCTTextField.m in Sources */, 58B512161A9E6EFF00147676 /* RCTText.m in Sources */, 1362F1011B4D51F400E06D8C /* RCTTextFieldManager.m in Sources */, + 59F60E911E661BDD0081153B /* RCTShadowTextField.m in Sources */, AF3225F91DE5574F00D3E7E7 /* RCTConvert+Text.m in Sources */, 131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */, + 59F60E931E661BDD0081153B /* RCTShadowTextView.m in Sources */, 58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */, 58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */, ); diff --git a/Libraries/Text/RCTTextFieldManager.m b/Libraries/Text/RCTTextFieldManager.m index 72e205ae417322..27287c32afc262 100644 --- a/Libraries/Text/RCTTextFieldManager.m +++ b/Libraries/Text/RCTTextFieldManager.m @@ -14,6 +14,7 @@ #import #import "RCTConvert+Text.h" +#import "RCTShadowTextField.h" #import "RCTTextField.h" @@ -21,6 +22,11 @@ @implementation RCTTextFieldManager RCT_EXPORT_MODULE() +- (RCTShadowView *)shadowView +{ + return [RCTShadowTextField new]; +} + - (UIView *)view { return [[RCTTextField alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 415c0df826b0c2..83cde446a19355 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -238,6 +238,7 @@ - (void)performPendingTextUpdate [_textView layoutIfNeeded]; [self updatePlaceholderVisibility]; + [self updateContentSize]; _blockTextShouldChange = NO; } diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m index c599526b9f968c..7b7f5d0473d422 100644 --- a/Libraries/Text/RCTTextViewManager.m +++ b/Libraries/Text/RCTTextViewManager.m @@ -15,12 +15,18 @@ #import #import "RCTConvert+Text.h" +#import "RCTShadowTextView.h" #import "RCTTextView.h" @implementation RCTTextViewManager RCT_EXPORT_MODULE() +- (RCTShadowView *)shadowView +{ + return [RCTShadowTextView new]; +} + - (UIView *)view { return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 1542c9aa503236..db3a22e878e13d 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -95,6 +95,7 @@ const viewConfig = { * ``` */ +// $FlowFixMe(>=0.41.0) const Text = React.createClass({ propTypes: { /** @@ -248,6 +249,7 @@ const Text = React.createClass({ this._handlers = { onStartShouldSetResponder: (): bool => { const shouldSetFromProps = this.props.onStartShouldSetResponder && + // $FlowFixMe(>=0.41.0) this.props.onStartShouldSetResponder(); const setResponder = shouldSetFromProps || this._hasPressHandler(); if (setResponder && !this.touchableHandleActivePressIn) { @@ -288,33 +290,44 @@ const Text = React.createClass({ return this.props.pressRetentionOffset || PRESS_RECT_OFFSET; }; } + // $FlowFixMe(>=0.41.0) return setResponder; }, onResponderGrant: function(e: SyntheticEvent, dispatchID: string) { + // $FlowFixMe(>=0.41.0) this.touchableHandleResponderGrant(e, dispatchID); this.props.onResponderGrant && + // $FlowFixMe(>=0.41.0) this.props.onResponderGrant.apply(this, arguments); }.bind(this), onResponderMove: function(e: SyntheticEvent) { + // $FlowFixMe(>=0.41.0) this.touchableHandleResponderMove(e); this.props.onResponderMove && + // $FlowFixMe(>=0.41.0) this.props.onResponderMove.apply(this, arguments); }.bind(this), onResponderRelease: function(e: SyntheticEvent) { + // $FlowFixMe(>=0.41.0) this.touchableHandleResponderRelease(e); this.props.onResponderRelease && + // $FlowFixMe(>=0.41.0) this.props.onResponderRelease.apply(this, arguments); }.bind(this), onResponderTerminate: function(e: SyntheticEvent) { + // $FlowFixMe(>=0.41.0) this.touchableHandleResponderTerminate(e); this.props.onResponderTerminate && + // $FlowFixMe(>=0.41.0) this.props.onResponderTerminate.apply(this, arguments); }.bind(this), onResponderTerminationRequest: function(): bool { // Allow touchable or props.onResponderTerminationRequest to deny // the request + // $FlowFixMe(>=0.41.0) var allowTermination = this.touchableHandleResponderTerminationRequest(); if (allowTermination && this.props.onResponderTerminationRequest) { + // $FlowFixMe(>=0.41.0) allowTermination = this.props.onResponderTerminationRequest.apply(this, arguments); } return allowTermination; diff --git a/Libraries/Utilities/BackAndroid.js b/Libraries/Utilities/BackAndroid.js new file mode 100644 index 00000000000000..0289b34c65d82c --- /dev/null +++ b/Libraries/Utilities/BackAndroid.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * BackAndroid has been moved to BackHandler. This stub calls BackHandler methods + * after generating a warning to remind users to move to the new BackHandler module. + * + * @providesModule BackAndroid + */ + +'use strict'; + +var BackHandler = require('BackHandler'); + +var warning = require('fbjs/lib/warning'); + +/** + * Deprecated. Use BackHandler instead. + */ +var BackAndroid = { + + exitApp: function() { + warning(false, 'BackAndroid is deprecated. Please use BackHandler instead.'); + BackHandler.exitApp(); + }, + + addEventListener: function ( + eventName: BackPressEventName, + handler: Function + ): {remove: () => void} { + warning(false, 'BackAndroid is deprecated. Please use BackHandler instead.'); + return BackHandler.addEventListener(eventName, handler); + }, + + removeEventListener: function( + eventName: BackPressEventName, + handler: Function + ): void { + warning(false, 'BackAndroid is deprecated. Please use BackHandler instead.'); + BackHandler.removeEventListener(eventName, handler); + }, + +}; + +module.exports = BackAndroid; diff --git a/Libraries/Utilities/BackAndroid.android.js b/Libraries/Utilities/BackHandler.android.js similarity index 75% rename from Libraries/Utilities/BackAndroid.android.js rename to Libraries/Utilities/BackHandler.android.js index c3295d927b37ef..d8ca9b5eb567c9 100644 --- a/Libraries/Utilities/BackAndroid.android.js +++ b/Libraries/Utilities/BackHandler.android.js @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule BackAndroid + * @providesModule BackHandler */ 'use strict'; @@ -34,20 +34,29 @@ RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() { } if (invokeDefault) { - BackAndroid.exitApp(); + BackHandler.exitApp(); } }); /** - * Detect hardware back button presses, and programmatically invoke the default back button + * Detect hardware button presses for back navigation. + * + * Android: Detect hardware back button presses, and programmatically invoke the default back button * functionality to exit the app if there are no listeners or if none of the listeners return true. + * + * tvOS: Detect presses of the menu button on the TV remote. (Still to be implemented: + * programmatically disable menu button handling + * functionality to exit the app if there are no listeners or if none of the listeners return true.) + * + * iOS: Not applicable. + * * The event subscriptions are called in reverse order (i.e. last registered subscription first), * and if one subscription returns true then subscriptions registered earlier will not be called. * * Example: * * ```javascript - * BackAndroid.addEventListener('hardwareBackPress', function() { + * BackHandler.addEventListener('hardwareBackPress', function() { * // this.onMainScreen and this.goBack are just examples, you need to use your own implementation here * // Typically you would use the navigator here to go to the last state. * @@ -59,7 +68,7 @@ RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() { * }); * ``` */ -var BackAndroid = { +var BackHandler = { exitApp: function() { DeviceEventManager.invokeDefaultBackPressHandler(); @@ -71,7 +80,7 @@ var BackAndroid = { ): {remove: () => void} { _backPressSubscriptions.add(handler); return { - remove: () => BackAndroid.removeEventListener(eventName, handler), + remove: () => BackHandler.removeEventListener(eventName, handler), }; }, @@ -84,4 +93,4 @@ var BackAndroid = { }; -module.exports = BackAndroid; +module.exports = BackHandler; diff --git a/Libraries/Utilities/BackHandler.ios.js b/Libraries/Utilities/BackHandler.ios.js new file mode 100644 index 00000000000000..958ec69e5c0558 --- /dev/null +++ b/Libraries/Utilities/BackHandler.ios.js @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * On Apple TV, this implements back navigation using the TV remote's menu button. + * On iOS, this just implements a stub. + * + * @providesModule BackHandler + */ + +'use strict'; + +const Platform = require('Platform'); +const TVEventHandler = require('TVEventHandler'); + +type BackPressEventName = $Enum<{ + backPress: string, +}>; + +function emptyFunction() {} + +/** + * Detect hardware button presses for back navigation. + * + * Android: Detect hardware back button presses, and programmatically invoke the default back button + * functionality to exit the app if there are no listeners or if none of the listeners return true. + * + * tvOS: Detect presses of the menu button on the TV remote. (Still to be implemented: + * programmatically disable menu button handling + * functionality to exit the app if there are no listeners or if none of the listeners return true.) + * + * iOS: Not applicable. + * + * The event subscriptions are called in reverse order (i.e. last registered subscription first), + * and if one subscription returns true then subscriptions registered earlier will not be called. + * + * Example: + * + * ```javascript + * BackHandler.addEventListener('hardwareBackPress', function() { + * // this.onMainScreen and this.goBack are just examples, you need to use your own implementation here + * // Typically you would use the navigator here to go to the last state. + * + * if (!this.onMainScreen()) { + * this.goBack(); + * return true; + * } + * return false; + * }); + * ``` + */ +let BackHandler; + +if (Platform.isTVOS) { + const _tvEventHandler = new TVEventHandler(); + var _backPressSubscriptions = new Set(); + + _tvEventHandler.enable(this, function(cmp, evt) { + if (evt && evt.eventType === 'menu') { + var backPressSubscriptions = new Set(_backPressSubscriptions); + var invokeDefault = true; + var subscriptions = [...backPressSubscriptions].reverse(); + for (var i = 0; i < subscriptions.length; ++i) { + if (subscriptions[i]()) { + invokeDefault = false; + break; + } + } + + if (invokeDefault) { + BackHandler.exitApp(); + } + } + }); + + BackHandler = { + exitApp: emptyFunction, + + addEventListener: function ( + eventName: BackPressEventName, + handler: Function + ): {remove: () => void} { + _backPressSubscriptions.add(handler); + return { + remove: () => BackHandler.removeEventListener(eventName, handler), + }; + }, + + removeEventListener: function( + eventName: BackPressEventName, + handler: Function + ): void { + _backPressSubscriptions.delete(handler); + }, + + }; + +} else { + + BackHandler = { + exitApp: emptyFunction, + addEventListener() { + return { + remove: emptyFunction, + }; + }, + removeEventListener: emptyFunction, + }; + +} + +module.exports = BackHandler; diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index c3e088751288fa..e5f82ea1b3eab5 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule MatrixMath * @noflow diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js index b08884ae93ab44..9f218c91787c2c 100644 --- a/Libraries/Utilities/RCTLog.js +++ b/Libraries/Utilities/RCTLog.js @@ -11,11 +11,11 @@ */ 'use strict'; -var BatchedBridge = require('BatchedBridge'); +const BatchedBridge = require('BatchedBridge'); -var invariant = require('fbjs/lib/invariant'); +const invariant = require('fbjs/lib/invariant'); -var levelsMap = { +const levelsMap = { log: 'log', info: 'info', warn: 'warn', @@ -25,18 +25,24 @@ var levelsMap = { class RCTLog { // level one of log, info, warn, error, mustfix - static logIfNoNativeHook() { - var args = Array.prototype.slice.call(arguments); - var level = args.shift(); - var logFn = levelsMap[level]; + static logIfNoNativeHook(...args) { + if (typeof global.nativeLoggingHook === 'undefined') { + // We already printed in xcode, so only log here if using a js debugger + RCTLog.logToConsole(...args); + } + + return true; + } + + // Log to console regardless of nativeLoggingHook + static logToConsole(level, ...args) { + const logFn = levelsMap[level]; invariant( logFn, 'Level "' + level + '" not one of ' + Object.keys(levelsMap) ); - if (typeof global.nativeLoggingHook === 'undefined') { - // We already printed in xcode, so only log here if using a js debugger - console[logFn].apply(console, args); - } + + console[logFn](...args); return true; } diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js index 195c29a7cd6119..06dee18987239e 100644 --- a/Libraries/Utilities/buildStyleInterpolator.js +++ b/Libraries/Utilities/buildStyleInterpolator.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule buildStyleInterpolator */ diff --git a/Libraries/Utilities/differ/sizesDiffer.js b/Libraries/Utilities/differ/sizesDiffer.js index a431cd072273c8..7b24ea52f8dc43 100644 --- a/Libraries/Utilities/differ/sizesDiffer.js +++ b/Libraries/Utilities/differ/sizesDiffer.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule sizesDiffer */ diff --git a/Libraries/Utilities/dismissKeyboard.js b/Libraries/Utilities/dismissKeyboard.js index d8949e8b306219..183fa4a88caf3a 100644 --- a/Libraries/Utilities/dismissKeyboard.js +++ b/Libraries/Utilities/dismissKeyboard.js @@ -1,5 +1,10 @@ /** - * Copyright 2004-present Facebook. All Rights Reserved. + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule dismissKeyboard * diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index 0c5f44e4c266c1..d11a202215d013 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -34,12 +34,12 @@ const ReactNative = { get Button() { return require('Button'); }, get DatePickerIOS() { return require('DatePickerIOS'); }, get DrawerLayoutAndroid() { return require('DrawerLayoutAndroid'); }, + get FlatList() { return require('FlatList'); }, get Image() { return require('Image'); }, get ImageEditor() { return require('ImageEditor'); }, get ImageStore() { return require('ImageStore'); }, get KeyboardAvoidingView() { return require('KeyboardAvoidingView'); }, get ListView() { return require('ListView'); }, - get MapView() { return require('MapView'); }, get Modal() { return require('Modal'); }, get Navigator() { return require('Navigator'); }, get NavigatorIOS() { return require('NavigatorIOS'); }, @@ -48,6 +48,7 @@ const ReactNative = { get ProgressBarAndroid() { return require('ProgressBarAndroid'); }, get ProgressViewIOS() { return require('ProgressViewIOS'); }, get ScrollView() { return require('ScrollView'); }, + get SectionList() { return require('SectionList'); }, get SegmentedControlIOS() { return require('SegmentedControlIOS'); }, get Slider() { return require('Slider'); }, get SnapshotViewIOS() { return require('SnapshotViewIOS'); }, @@ -67,6 +68,7 @@ const ReactNative = { get TouchableWithoutFeedback() { return require('TouchableWithoutFeedback'); }, get View() { return require('View'); }, get ViewPagerAndroid() { return require('ViewPagerAndroid'); }, + get VirtualizedList() { return require('VirtualizedList'); }, get WebView() { return require('WebView'); }, // APIs @@ -78,7 +80,8 @@ const ReactNative = { get AppRegistry() { return require('AppRegistry'); }, get AppState() { return require('AppState'); }, get AsyncStorage() { return require('AsyncStorage'); }, - get BackAndroid() { return require('BackAndroid'); }, + get BackAndroid() { return require('BackAndroid'); }, // deprecated: use BackHandler instead + get BackHandler() { return require('BackHandler'); }, get CameraRoll() { return require('CameraRoll'); }, get Clipboard() { return require('Clipboard'); }, get DatePickerAndroid() { return require('DatePickerAndroid'); }, @@ -103,6 +106,7 @@ const ReactNative = { get StyleSheet() { return require('StyleSheet'); }, get Systrace() { return require('Systrace'); }, get TimePickerAndroid() { return require('TimePickerAndroid'); }, + get TVEventHandler() { return require('TVEventHandler'); }, get UIManager() { return require('UIManager'); }, get Vibration() { return require('Vibration'); }, get VibrationIOS() { return require('VibrationIOS'); }, diff --git a/React.podspec b/React.podspec index a00afd5786aa27..e6352b3fd821d2 100644 --- a/React.podspec +++ b/React.podspec @@ -60,7 +60,7 @@ Pod::Spec.new do |s| end s.subspec "jschelpers" do |ss| - ss.source_files = "ReactCommon/jschelpers/{JavaScriptCore,JSCWrapper}.{cpp,h}" + ss.source_files = "ReactCommon/jschelpers/{JavaScriptCore,JSCWrapper}.{cpp,h}", "ReactCommon/jschelpers/systemJSCWrapper.cpp" ss.private_header_files = "ReactCommon/jschelpers/{JavaScriptCore,JSCWrapper}.h" ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "$(PODS_TARGET_SRCROOT)/ReactCommon" } ss.framework = "JavaScriptCore" diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 6eb24bdb9d338b..4bfa4b3210a5d4 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -154,7 +154,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \ */ #define RCT_REMAP_METHOD(js_name, method) \ RCT_EXTERN_REMAP_METHOD(js_name, method) \ - - (void)method + - (void)method; /** * Use this macro in a private Objective-C implementation file to automatically diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 57e8333dfd27db..dbd4d1f7b8b5a0 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -112,6 +112,7 @@ typedef id NSPropertyList; typedef BOOL css_backface_visibility_t; + (YGOverflow)YGOverflow:(id)json; ++ (YGDisplay)YGDisplay:(id)json; + (css_backface_visibility_t)css_backface_visibility_t:(id)json; + (YGFlexDirection)YGFlexDirection:(id)json; + (YGJustify)YGJustify:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index d8a0e9df2ce370..256fd854bc6e1f 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -507,7 +507,9 @@ + (YGValue)YGValue:(id)json return (YGValue) { [json floatValue], YGUnitPoint }; } else if ([json isKindOfClass:[NSString class]]) { NSString *s = (NSString *) json; - if ([s hasSuffix:@"%"]) { + if ([s isEqualToString:@"auto"]) { + return (YGValue) { YGUndefined, YGUnitAuto }; + } else if ([s hasSuffix:@"%"]) { return (YGValue) { [[s substringToIndex:s.length] floatValue], YGUnitPercent }; } else { RCTLogConvertError(json, @"a YGValue. Did you forget the % or pt suffix?"); @@ -643,6 +645,11 @@ + (NSPropertyList)NSPropertyList:(id)json @"scroll": @(YGOverflowScroll), }), YGOverflowVisible, intValue) +RCT_ENUM_CONVERTER(YGDisplay, (@{ + @"flex": @(YGDisplayFlex), + @"none": @(YGDisplayNone), +}), YGDisplayFlex, intValue) + RCT_ENUM_CONVERTER(YGFlexDirection, (@{ @"row": @(YGFlexDirectionRow), @"row-reverse": @(YGFlexDirectionRowReverse), @@ -664,7 +671,9 @@ + (NSPropertyList)NSPropertyList:(id)json @"center": @(YGAlignCenter), @"auto": @(YGAlignAuto), @"stretch": @(YGAlignStretch), - @"baseline": @(YGAlignBaseline) + @"baseline": @(YGAlignBaseline), + @"space-between": @(YGAlignSpaceBetween), + @"space-around": @(YGAlignSpaceAround) }), YGAlignFlexStart, intValue) RCT_ENUM_CONVERTER(YGDirection, (@{ diff --git a/React/Base/RCTRootContentView.h b/React/Base/RCTRootContentView.h index 5092b5d3e63817..f405a85506cb33 100644 --- a/React/Base/RCTRootContentView.h +++ b/React/Base/RCTRootContentView.h @@ -22,6 +22,7 @@ @property (nonatomic, readonly, strong) RCTTouchHandler *touchHandler; @property (nonatomic, assign) BOOL passThroughTouches; @property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility; +@property (nonatomic, readonly) CGSize availableSize; - (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge diff --git a/React/Base/RCTRootContentView.m b/React/Base/RCTRootContentView.m index 995a71f708d0c4..f4fb62b5320649 100644 --- a/React/Base/RCTRootContentView.m +++ b/React/Base/RCTRootContentView.m @@ -72,20 +72,22 @@ - (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility [self setNeedsLayout]; } -- (void)updateAvailableSize +- (CGSize)availableSize { - if (!self.reactTag || !_bridge.isValid) { - return; - } - CGSize size = self.bounds.size; - CGSize availableSize = - CGSizeMake( + return CGSizeMake( _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? INFINITY : size.width, _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? INFINITY : size.height ); +} + +- (void)updateAvailableSize +{ + if (!self.reactTag || !_bridge.isValid) { + return; + } - [_bridge.uiManager setAvailableSize:availableSize forRootView:self]; + [_bridge.uiManager setAvailableSize:self.availableSize forRootView:self]; } - (void)setBackgroundColor:(UIColor *)backgroundColor diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 839640a8fbe482..db1ed02343c415 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -153,10 +153,22 @@ - (void)setPassThroughTouches:(BOOL)passThroughTouches - (CGSize)sizeThatFits:(CGSize)size { - return CGSizeMake( - _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? MIN(_intrinsicContentSize.width, size.width) : size.width, - _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? MIN(_intrinsicContentSize.height, size.height) : size.height - ); + CGSize fitSize = _intrinsicContentSize; + CGSize currentSize = self.bounds.size; + + // Following the current `size` and current `sizeFlexibility` policy. + fitSize = CGSizeMake( + _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? fitSize.width : currentSize.width, + _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? fitSize.height : currentSize.height + ); + + // Following the given size constraints. + fitSize = CGSizeMake( + MIN(size.width, fitSize.width), + MIN(size.height, fitSize.height) + ); + + return fitSize; } - (void)layoutSubviews diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index 02f65e2a559a87..431c59f4f8b040 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -1137,6 +1137,10 @@ - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray */ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args { + if (!self.valid) { + return; + } + /** * AnyThread */ diff --git a/React/Modules/RCTDevSettings.mm b/React/Modules/RCTDevSettings.mm index b41733683c3d31..171b2532094946 100644 --- a/React/Modules/RCTDevSettings.mm +++ b/React/Modules/RCTDevSettings.mm @@ -228,8 +228,9 @@ - (void)_remoteDebugSettingDidChange { // This value is passed as a command-line argument, so fall back to reading from NSUserDefaults directly NSString *executorOverride = [[NSUserDefaults standardUserDefaults] stringForKey:kRCTDevSettingExecutorOverrideClass]; - if (executorOverride) { - self.executorClass = NSClassFromString(executorOverride); + Class executorOverrideClass = executorOverride ? NSClassFromString(executorOverride) : nil; + if (executorOverrideClass) { + self.executorClass = executorOverrideClass; } else { BOOL enabled = self.isRemoteDebuggingAvailable && self.isDebuggingRemotely; self.executorClass = enabled ? objc_getClass("RCTWebSocketExecutor") : nil; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 288f3d61e1b536..b672cafbe1046b 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -27,6 +27,7 @@ #import "RCTModuleData.h" #import "RCTModuleMethod.h" #import "RCTProfile.h" +#import "RCTRootContentView.h" #import "RCTRootShadowView.h" #import "RCTRootViewInternal.h" #import "RCTScrollableProtocol.h" @@ -236,8 +237,13 @@ @implementation RCTUIManager - (void)didReceiveNewContentSizeMultiplier { // Report the event across the bridge. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_bridge.eventDispatcher sendDeviceEventWithName:@"didUpdateDimensions" + body:RCTExportedDimensions(_bridge)]; [_bridge.eventDispatcher sendDeviceEventWithName:@"didUpdateContentSizeMultiplier" body:@([_bridge.accessibilityManager multiplier])]; +#pragma clang diagnostic pop dispatch_async(RCTGetUIManagerQueue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification @@ -260,7 +266,7 @@ - (void)interfaceOrientationDidChange #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [_bridge.eventDispatcher sendDeviceEventWithName:@"didUpdateDimensions" - body:RCTExportedDimensions()]; + body:RCTExportedDimensions(_bridge)]; #pragma clang diagnostic pop } @@ -376,7 +382,7 @@ - (dispatch_queue_t)methodQueue return RCTGetUIManagerQueue(); } -- (void)registerRootView:(UIView *)rootView +- (void)registerRootView:(RCTRootContentView *)rootView { RCTAssertMainQueue(); @@ -388,6 +394,8 @@ - (void)registerRootView:(UIView *)rootView RCTAssert(existingView == nil || existingView == rootView, @"Expect all root views to have unique tag. Added %@ twice", reactTag); + CGSize availableSize = rootView.availableSize; + // Register view _viewRegistry[reactTag] = rootView; @@ -398,6 +406,7 @@ - (void)registerRootView:(UIView *)rootView } RCTRootShadowView *shadowView = [RCTRootShadowView new]; + shadowView.availableSize = availableSize; shadowView.reactTag = reactTag; shadowView.backgroundColor = rootView.backgroundColor; shadowView.viewName = NSStringFromClass([rootView class]); @@ -1541,23 +1550,26 @@ static void RCTMeasureLayout(RCTShadowView *view, constants[@"customBubblingEventTypes"] = bubblingEvents; constants[@"customDirectEventTypes"] = directEvents; - constants[@"Dimensions"] = RCTExportedDimensions(); + constants[@"Dimensions"] = RCTExportedDimensions(_bridge); return constants; } -static NSDictionary *RCTExportedDimensions() +static NSDictionary *RCTExportedDimensions(RCTBridge *bridge) { RCTAssertMainQueue(); // Don't use RCTScreenSize since it the interface orientation doesn't apply to it CGRect screenSize = [[UIScreen mainScreen] bounds]; + NSDictionary *dims = @{ + @"width": @(screenSize.size.width), + @"height": @(screenSize.size.height), + @"scale": @(RCTScreenScale()), + @"fontScale": @(bridge.accessibilityManager.multiplier) + }; return @{ - @"window": @{ - @"width": @(screenSize.size.width), - @"height": @(screenSize.size.height), - @"scale": @(RCTScreenScale()), - }, + @"window": dims, + @"screen": dims }; } @@ -1580,11 +1592,6 @@ static void RCTMeasureLayout(RCTShadowView *view, }]; } -RCT_EXPORT_METHOD(getContentSizeMultiplier:(nonnull RCTResponseSenderBlock)callback) -{ - callback(@[@(_bridge.accessibilityManager.multiplier)]); -} - - (void)rootViewForReactTag:(NSNumber *)reactTag withCompletion:(void (^)(UIView *view))completion { RCTAssertMainQueue(); @@ -1656,6 +1663,12 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view [self setSize:frame.size forView:view]; } +RCT_EXPORT_METHOD(getContentSizeMultiplier:(nonnull RCTResponseSenderBlock)callback) +{ + RCTLogWarn(@"`getContentSizeMultiplier` is deprecated. Instead, use `PixelRatio.getFontScale()` and listen to the `didUpdateDimensions` event."); + callback(@[@(_bridge.accessibilityManager.multiplier)]); +} + @end @implementation RCTBridge (RCTUIManager) diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 43c3a4497e2360..1e2d1be93f3a55 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 1339578B1DF76D3500EC27BE /* Yoga.h in Headers */ = {isa = PBXBuildFile; fileRef = 130A77081DF767AF001F9587 /* Yoga.h */; }; 133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */; }; 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; }; - 13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; }; 134FCB3D1A6E7F0800051CC8 /* RCTJSCExecutor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTJSCExecutor.mm */; }; 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */; }; 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; }; @@ -35,7 +34,6 @@ 13AB5E021DF777F2001A8C30 /* Yoga.c in Sources */ = {isa = PBXBuildFile; fileRef = 130A77071DF767AF001F9587 /* Yoga.c */; }; 13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB90C01B6FA36700713B4F /* RCTComponentData.m */; }; 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; }; - 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */; }; 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; }; 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */; }; 13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEE1A69327A00A75B9A /* RCTTiming.m */; }; @@ -47,7 +45,6 @@ 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; }; 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; }; 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; - 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; }; 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; }; 13BCE8091C99CB9D00DD7AAD /* RCTRootShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BCE8081C99CB9D00DD7AAD /* RCTRootShadowView.m */; }; 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; }; @@ -64,8 +61,6 @@ 13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; }; 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F17A841B8493E5007D4C75 /* RCTRedBox.m */; }; 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 142014171B32094000CC17BA /* RCTPerformanceLogger.m */; }; - 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; - 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; }; 1450FF861BCFF28A00208362 /* RCTProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF811BCFF28A00208362 /* RCTProfile.m */; }; 1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */; }; 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */; }; @@ -82,6 +77,8 @@ 14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; }; 191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */; }; 191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */; }; + 2638623A1E732AB60010FEBF /* systemJSCWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 65F3E41D1E73031C009375BD /* systemJSCWrapper.cpp */; }; + 2638623B1E732AB70010FEBF /* systemJSCWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 65F3E41D1E73031C009375BD /* systemJSCWrapper.cpp */; }; 2D074E381E609E65001D6DD0 /* RCTReconnectingWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = A12E9E281E5DEB860029001B /* RCTReconnectingWebSocket.h */; }; 2D074E391E609E68001D6DD0 /* RCTSRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = A12E9E291E5DEB860029001B /* RCTSRWebSocket.h */; }; 2D3B5E931D9B087300451313 /* RCTErrorInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EDCA8A41D3591E700450C31 /* RCTErrorInfo.m */; }; @@ -133,12 +130,7 @@ 2D3B5EC91D9B095C00451313 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; }; 2D3B5ECA1D9B095F00451313 /* RCTComponentData.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB90C01B6FA36700713B4F /* RCTComponentData.m */; }; 2D3B5ECB1D9B096200451313 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; }; - 2D3B5ECC1D9B096500451313 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; }; 2D3B5ECF1D9B096F00451313 /* RCTFont.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D37B5811D522B190042D5B5 /* RCTFont.mm */; }; - 2D3B5ED01D9B097200451313 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; - 2D3B5ED11D9B097500451313 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; }; - 2D3B5ED21D9B097800451313 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; }; - 2D3B5ED31D9B097B00451313 /* RCTMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */; }; 2D3B5ED41D9B097D00451313 /* RCTModalHostView.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */; }; 2D3B5ED51D9B098000451313 /* RCTModalHostViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83392EB21B6634E10013B15F /* RCTModalHostViewController.m */; }; 2D3B5ED61D9B098400451313 /* RCTModalHostViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */; }; @@ -162,6 +154,7 @@ 2D3B5EF01D9B09E300451313 /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; }; 2D3B5EF11D9B09E700451313 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; }; 2D74EAFA1DAE9590003B751B /* RCTMultipartDataTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 006FC4131D9B20820057AAAD /* RCTMultipartDataTask.m */; }; + 2D7FEB891E734B5700D3238C /* systemJSCWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 65F3E41D1E73031C009375BD /* systemJSCWrapper.cpp */; }; 2D8C2E331DA40441000EE098 /* RCTMultipartStreamReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 001BFCCF1D8381DE008E587E /* RCTMultipartStreamReader.m */; }; 2D9F8B9B1DE398DB00A16144 /* RCTPlatform.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D7749431DC1065C007EC8D8 /* RCTPlatform.m */; }; 2DD0EFE11DA84F2800B0C975 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; }; @@ -251,12 +244,7 @@ 3D302F701DF828F800D6DDAE /* RCTComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 13C325281AA63B6A0048765F /* RCTComponent.h */; }; 3D302F711DF828F800D6DDAE /* RCTComponentData.h in Headers */ = {isa = PBXBuildFile; fileRef = 13AB90BF1B6FA36700713B4F /* RCTComponentData.h */; }; 3D302F721DF828F800D6DDAE /* RCTConvert+CoreLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */; }; - 3D302F731DF828F800D6DDAE /* RCTConvert+MapKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */; }; 3D302F761DF828F800D6DDAE /* RCTFont.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D37B5801D522B190042D5B5 /* RCTFont.h */; }; - 3D302F771DF828F800D6DDAE /* RCTMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 14435CE11AAC4AE100FC20F4 /* RCTMap.h */; }; - 3D302F781DF828F800D6DDAE /* RCTMapAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */; }; - 3D302F791DF828F800D6DDAE /* RCTMapManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */; }; - 3D302F7A1DF828F800D6DDAE /* RCTMapOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */; }; 3D302F7B1DF828F800D6DDAE /* RCTModalHostView.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */; }; 3D302F7C1DF828F800D6DDAE /* RCTModalHostViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 83392EB11B6634E10013B15F /* RCTModalHostViewController.h */; }; 3D302F7D1DF828F800D6DDAE /* RCTModalHostViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */; }; @@ -367,12 +355,7 @@ 3D302FEC1DF8290600D6DDAE /* RCTComponent.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13C325281AA63B6A0048765F /* RCTComponent.h */; }; 3D302FED1DF8290600D6DDAE /* RCTComponentData.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13AB90BF1B6FA36700713B4F /* RCTComponentData.h */; }; 3D302FEE1DF8290600D6DDAE /* RCTConvert+CoreLocation.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */; }; - 3D302FEF1DF8290600D6DDAE /* RCTConvert+MapKit.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */; }; 3D302FF21DF8290600D6DDAE /* RCTFont.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3D37B5801D522B190042D5B5 /* RCTFont.h */; }; - 3D302FF31DF8290600D6DDAE /* RCTMap.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 14435CE11AAC4AE100FC20F4 /* RCTMap.h */; }; - 3D302FF41DF8290600D6DDAE /* RCTMapAnnotation.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */; }; - 3D302FF51DF8290600D6DDAE /* RCTMapManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */; }; - 3D302FF61DF8290600D6DDAE /* RCTMapOverlay.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */; }; 3D302FF71DF8290600D6DDAE /* RCTModalHostView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */; }; 3D302FF81DF8290600D6DDAE /* RCTModalHostViewController.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83392EB11B6634E10013B15F /* RCTModalHostViewController.h */; }; 3D302FF91DF8290600D6DDAE /* RCTModalHostViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */; }; @@ -515,14 +498,9 @@ 3D80D96B1DF6FA890028D040 /* RCTComponent.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13C325281AA63B6A0048765F /* RCTComponent.h */; }; 3D80D96C1DF6FA890028D040 /* RCTComponentData.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13AB90BF1B6FA36700713B4F /* RCTComponentData.h */; }; 3D80D96D1DF6FA890028D040 /* RCTConvert+CoreLocation.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */; }; - 3D80D96E1DF6FA890028D040 /* RCTConvert+MapKit.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */; }; 3D80D96F1DF6FA890028D040 /* RCTDatePicker.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */; }; 3D80D9701DF6FA890028D040 /* RCTDatePickerManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */; }; 3D80D9711DF6FA890028D040 /* RCTFont.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3D37B5801D522B190042D5B5 /* RCTFont.h */; }; - 3D80D9721DF6FA890028D040 /* RCTMap.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 14435CE11AAC4AE100FC20F4 /* RCTMap.h */; }; - 3D80D9731DF6FA890028D040 /* RCTMapAnnotation.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */; }; - 3D80D9741DF6FA890028D040 /* RCTMapManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */; }; - 3D80D9751DF6FA890028D040 /* RCTMapOverlay.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */; }; 3D80D9761DF6FA890028D040 /* RCTModalHostView.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */; }; 3D80D9771DF6FA890028D040 /* RCTModalHostViewController.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83392EB11B6634E10013B15F /* RCTModalHostViewController.h */; }; 3D80D9781DF6FA890028D040 /* RCTModalHostViewManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */; }; @@ -638,14 +616,9 @@ 3D80DA651DF820620028D040 /* RCTComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 13C325281AA63B6A0048765F /* RCTComponent.h */; }; 3D80DA661DF820620028D040 /* RCTComponentData.h in Headers */ = {isa = PBXBuildFile; fileRef = 13AB90BF1B6FA36700713B4F /* RCTComponentData.h */; }; 3D80DA671DF820620028D040 /* RCTConvert+CoreLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */; }; - 3D80DA681DF820620028D040 /* RCTConvert+MapKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */; }; 3D80DA691DF820620028D040 /* RCTDatePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */; }; 3D80DA6A1DF820620028D040 /* RCTDatePickerManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */; }; 3D80DA6B1DF820620028D040 /* RCTFont.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D37B5801D522B190042D5B5 /* RCTFont.h */; }; - 3D80DA6C1DF820620028D040 /* RCTMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 14435CE11AAC4AE100FC20F4 /* RCTMap.h */; }; - 3D80DA6D1DF820620028D040 /* RCTMapAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */; }; - 3D80DA6E1DF820620028D040 /* RCTMapManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */; }; - 3D80DA6F1DF820620028D040 /* RCTMapOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */; }; 3D80DA701DF820620028D040 /* RCTModalHostView.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */; }; 3D80DA711DF820620028D040 /* RCTModalHostViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 83392EB11B6634E10013B15F /* RCTModalHostViewController.h */; }; 3D80DA721DF820620028D040 /* RCTModalHostViewManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */; }; @@ -909,12 +882,7 @@ 3D302FEC1DF8290600D6DDAE /* RCTComponent.h in Copy Headers */, 3D302FED1DF8290600D6DDAE /* RCTComponentData.h in Copy Headers */, 3D302FEE1DF8290600D6DDAE /* RCTConvert+CoreLocation.h in Copy Headers */, - 3D302FEF1DF8290600D6DDAE /* RCTConvert+MapKit.h in Copy Headers */, 3D302FF21DF8290600D6DDAE /* RCTFont.h in Copy Headers */, - 3D302FF31DF8290600D6DDAE /* RCTMap.h in Copy Headers */, - 3D302FF41DF8290600D6DDAE /* RCTMapAnnotation.h in Copy Headers */, - 3D302FF51DF8290600D6DDAE /* RCTMapManager.h in Copy Headers */, - 3D302FF61DF8290600D6DDAE /* RCTMapOverlay.h in Copy Headers */, 3D302FF71DF8290600D6DDAE /* RCTModalHostView.h in Copy Headers */, 3D302FF81DF8290600D6DDAE /* RCTModalHostViewController.h in Copy Headers */, 3D302FF91DF8290600D6DDAE /* RCTModalHostViewManager.h in Copy Headers */, @@ -1073,14 +1041,9 @@ 3D80D96B1DF6FA890028D040 /* RCTComponent.h in Copy Headers */, 3D80D96C1DF6FA890028D040 /* RCTComponentData.h in Copy Headers */, 3D80D96D1DF6FA890028D040 /* RCTConvert+CoreLocation.h in Copy Headers */, - 3D80D96E1DF6FA890028D040 /* RCTConvert+MapKit.h in Copy Headers */, 3D80D96F1DF6FA890028D040 /* RCTDatePicker.h in Copy Headers */, 3D80D9701DF6FA890028D040 /* RCTDatePickerManager.h in Copy Headers */, 3D80D9711DF6FA890028D040 /* RCTFont.h in Copy Headers */, - 3D80D9721DF6FA890028D040 /* RCTMap.h in Copy Headers */, - 3D80D9731DF6FA890028D040 /* RCTMapAnnotation.h in Copy Headers */, - 3D80D9741DF6FA890028D040 /* RCTMapManager.h in Copy Headers */, - 3D80D9751DF6FA890028D040 /* RCTMapOverlay.h in Copy Headers */, 3D80D9761DF6FA890028D040 /* RCTModalHostView.h in Copy Headers */, 3D80D9771DF6FA890028D040 /* RCTModalHostViewController.h in Copy Headers */, 3D80D9781DF6FA890028D040 /* RCTModalHostViewManager.h in Copy Headers */, @@ -1186,8 +1149,6 @@ 13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = ""; }; 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+CoreLocation.h"; sourceTree = ""; }; 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CoreLocation.m"; sourceTree = ""; }; - 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+MapKit.h"; sourceTree = ""; }; - 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+MapKit.m"; sourceTree = ""; }; 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestDelegate.h; sourceTree = ""; }; 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestHandler.h; sourceTree = ""; }; 134FCB391A6E7F0800051CC8 /* RCTJSCExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTJSCExecutor.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; @@ -1219,8 +1180,6 @@ 13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = ""; }; 13AF20431AE707F8005F5298 /* RCTSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSlider.h; sourceTree = ""; }; 13AF20441AE707F9005F5298 /* RCTSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSlider.m; sourceTree = ""; }; - 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapOverlay.h; sourceTree = ""; }; - 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapOverlay.m; sourceTree = ""; }; 13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeMethod.h; sourceTree = ""; }; 13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewDelegate.h; sourceTree = ""; }; 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAlertManager.h; sourceTree = ""; }; @@ -1245,8 +1204,6 @@ 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.m; sourceTree = ""; }; 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = ""; }; 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; }; - 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = ""; }; - 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = ""; }; 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTImageSource.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; }; 13BCE8071C99CB9D00DD7AAD /* RCTRootShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootShadowView.h; sourceTree = ""; }; @@ -1282,10 +1239,6 @@ 142014171B32094000CC17BA /* RCTPerformanceLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerformanceLogger.m; sourceTree = ""; }; 142014181B32094000CC17BA /* RCTPerformanceLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPerformanceLogger.h; sourceTree = ""; }; 1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = ""; }; - 14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = ""; }; - 14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = ""; }; - 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = ""; }; - 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = ""; }; 1450FF801BCFF28A00208362 /* RCTProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProfile.h; sourceTree = ""; }; 1450FF811BCFF28A00208362 /* RCTProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProfile.m; sourceTree = ""; }; 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm.S"; sourceTree = ""; }; @@ -1372,6 +1325,7 @@ 594AD5CC1E46D87500B07237 /* RCTScrollContentViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollContentViewManager.m; sourceTree = ""; }; 597AD1BB1E577D7800152581 /* RCTRootContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootContentView.h; sourceTree = ""; }; 597AD1BC1E577D7800152581 /* RCTRootContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootContentView.m; sourceTree = ""; }; + 65F3E41D1E73031C009375BD /* systemJSCWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = systemJSCWrapper.cpp; sourceTree = ""; }; 68EFE4EC1CF6EB3000A1DE13 /* RCTBundleURLProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBundleURLProvider.h; sourceTree = ""; }; 68EFE4ED1CF6EB3900A1DE13 /* RCTBundleURLProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProvider.m; sourceTree = ""; }; 6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewInternal.h; sourceTree = ""; }; @@ -1545,8 +1499,6 @@ 13AB90C01B6FA36700713B4F /* RCTComponentData.m */, 13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */, 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */, - 13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */, - 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */, 945929C21DD62ADD00653A7D /* RCTConvert+Transform.h */, 945929C31DD62ADD00653A7D /* RCTConvert+Transform.m */, 133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */, @@ -1555,14 +1507,6 @@ 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */, 3D37B5801D522B190042D5B5 /* RCTFont.h */, 3D37B5811D522B190042D5B5 /* RCTFont.mm */, - 14435CE11AAC4AE100FC20F4 /* RCTMap.h */, - 14435CE21AAC4AE100FC20F4 /* RCTMap.m */, - 13B202021BFB948C00C07393 /* RCTMapAnnotation.h */, - 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */, - 14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */, - 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */, - 13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */, - 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */, 83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */, 83A1FE8B1B62640A00BE0E65 /* RCTModalHostView.m */, 83392EB11B6634E10013B15F /* RCTModalHostViewController.h */, @@ -1723,6 +1667,7 @@ 3D4A621D1DDD3985001F41B4 /* jschelpers */ = { isa = PBXGroup; children = ( + 65F3E41D1E73031C009375BD /* systemJSCWrapper.cpp */, 3D7A27DC1DE32541002E3F95 /* JavaScriptCore.h */, 3D7A27DD1DE32541002E3F95 /* JSCWrapper.cpp */, 3D7A27DE1DE32541002E3F95 /* JSCWrapper.h */, @@ -1976,12 +1921,7 @@ 3D302F701DF828F800D6DDAE /* RCTComponent.h in Headers */, 3D302F711DF828F800D6DDAE /* RCTComponentData.h in Headers */, 3D302F721DF828F800D6DDAE /* RCTConvert+CoreLocation.h in Headers */, - 3D302F731DF828F800D6DDAE /* RCTConvert+MapKit.h in Headers */, 3D302F761DF828F800D6DDAE /* RCTFont.h in Headers */, - 3D302F771DF828F800D6DDAE /* RCTMap.h in Headers */, - 3D302F781DF828F800D6DDAE /* RCTMapAnnotation.h in Headers */, - 3D302F791DF828F800D6DDAE /* RCTMapManager.h in Headers */, - 3D302F7A1DF828F800D6DDAE /* RCTMapOverlay.h in Headers */, 3D302F7B1DF828F800D6DDAE /* RCTModalHostView.h in Headers */, 3D302F7C1DF828F800D6DDAE /* RCTModalHostViewController.h in Headers */, 3D302F7D1DF828F800D6DDAE /* RCTModalHostViewManager.h in Headers */, @@ -2168,15 +2108,10 @@ 3D80DA651DF820620028D040 /* RCTComponent.h in Headers */, 3D80DA661DF820620028D040 /* RCTComponentData.h in Headers */, 3D80DA671DF820620028D040 /* RCTConvert+CoreLocation.h in Headers */, - 3D80DA681DF820620028D040 /* RCTConvert+MapKit.h in Headers */, 3D80DA691DF820620028D040 /* RCTDatePicker.h in Headers */, 3D80DA6A1DF820620028D040 /* RCTDatePickerManager.h in Headers */, 3D80DA6B1DF820620028D040 /* RCTFont.h in Headers */, - 3D80DA6C1DF820620028D040 /* RCTMap.h in Headers */, - 3D80DA6D1DF820620028D040 /* RCTMapAnnotation.h in Headers */, - 3D80DA6E1DF820620028D040 /* RCTMapManager.h in Headers */, B505583F1E43DFB900F71A00 /* RCTDevSettings.h in Headers */, - 3D80DA6F1DF820620028D040 /* RCTMapOverlay.h in Headers */, 3D80DA701DF820620028D040 /* RCTModalHostView.h in Headers */, 3D80DA711DF820620028D040 /* RCTModalHostViewController.h in Headers */, 3D80DA721DF820620028D040 /* RCTModalHostViewManager.h in Headers */, @@ -2487,7 +2422,6 @@ 2DD0EFE11DA84F2800B0C975 /* RCTStatusBarManager.m in Sources */, 2D3B5EC91D9B095C00451313 /* RCTBorderDrawing.m in Sources */, B50558411E43E13D00F71A00 /* RCTDevMenu.m in Sources */, - 2D3B5ED31D9B097B00451313 /* RCTMapOverlay.m in Sources */, 2D3B5E991D9B089A00451313 /* RCTDisplayLink.m in Sources */, 2D9F8B9B1DE398DB00A16144 /* RCTPlatform.m in Sources */, 2D3B5EBF1D9B093300451313 /* RCTJSCProfiler.m in Sources */, @@ -2509,7 +2443,6 @@ 2D3B5EE31D9B09B700451313 /* RCTSegmentedControl.m in Sources */, A12E9E251E5DEB4D0029001B /* RCTPackagerClient.m in Sources */, 2D3B5EB71D9B091800451313 /* RCTRedBox.m in Sources */, - 2D3B5ED11D9B097500451313 /* RCTMapAnnotation.m in Sources */, 2D3B5EAF1D9B08FB00451313 /* RCTAccessibilityManager.m in Sources */, 2D3B5EF11D9B09E700451313 /* UIView+React.m in Sources */, 2D3B5E931D9B087300451313 /* RCTErrorInfo.m in Sources */, @@ -2521,7 +2454,6 @@ 2D3B5EE11D9B09B000451313 /* RCTScrollView.m in Sources */, 2D3B5ED81D9B098A00451313 /* RCTNavigatorManager.m in Sources */, 2D3B5E951D9B087C00451313 /* RCTAssert.m in Sources */, - 2D3B5ED21D9B097800451313 /* RCTMapManager.m in Sources */, 2D3B5EB61D9B091400451313 /* RCTExceptionsManager.m in Sources */, 2D3B5EEB1D9B09D000451313 /* RCTTabBarItem.m in Sources */, 2D3B5E961D9B088500451313 /* RCTBatchedBridge.m in Sources */, @@ -2535,11 +2467,11 @@ A12E9E261E5DEB510029001B /* RCTPackagerClientResponder.m in Sources */, 2D3B5EEE1D9B09DA00451313 /* RCTView.m in Sources */, 594AD5D01E46D87500B07237 /* RCTScrollContentShadowView.m in Sources */, - 2D3B5ECC1D9B096500451313 /* RCTConvert+MapKit.m in Sources */, 2D3B5E981D9B089500451313 /* RCTConvert.m in Sources */, 2D3B5EA71D9B08CE00451313 /* RCTTouchHandler.m in Sources */, 3D05745A1DE5FFF500184BB4 /* RCTJavaScriptLoader.mm in Sources */, 2D3B5EA41D9B08C200451313 /* RCTPerformanceLogger.m in Sources */, + 2D7FEB891E734B5700D3238C /* systemJSCWrapper.cpp in Sources */, 2D3B5E9E1D9B08AD00451313 /* RCTJSStackFrame.m in Sources */, 2D3B5E941D9B087900451313 /* RCTBundleURLProvider.m in Sources */, 2D3B5EB81D9B091B00451313 /* RCTSourceCode.m in Sources */, @@ -2553,7 +2485,6 @@ A12E9E5D1E5DF8720029001B /* RCTReloadPackagerMethod.m in Sources */, 3D5AC71A1E0056E0000F9153 /* RCTTVNavigationEventEmitter.m in Sources */, 3D7A27E31DE325DA002E3F95 /* RCTJSCErrorHandling.mm in Sources */, - 2D3B5ED01D9B097200451313 /* RCTMap.m in Sources */, 2D3B5EA61D9B08CA00451313 /* RCTTouchEvent.m in Sources */, 2D8C2E331DA40441000EE098 /* RCTMultipartStreamReader.m in Sources */, 2D3B5EF01D9B09E300451313 /* RCTWrapperViewController.m in Sources */, @@ -2608,6 +2539,7 @@ buildActionMask = 2147483647; files = ( 3D80D9191DF6F7CF0028D040 /* JSCWrapper.cpp in Sources */, + 2638623A1E732AB60010FEBF /* systemJSCWrapper.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2616,6 +2548,7 @@ buildActionMask = 2147483647; files = ( 3D80D91A1DF6F7CF0028D040 /* JSCWrapper.cpp in Sources */, + 2638623B1E732AB70010FEBF /* systemJSCWrapper.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2640,7 +2573,6 @@ buildActionMask = 2147483647; files = ( 597AD1BF1E577D7800152581 /* RCTRootContentView.m in Sources */, - 13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */, 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */, 000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */, 14A43DF31C20B1C900794BC8 /* RCTJSCProfiler.m in Sources */, @@ -2654,7 +2586,6 @@ 131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */, 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */, 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */, - 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */, A12E9E8F1E5DFA620029001B /* RCTSamplingProfilerPackagerMethod.mm in Sources */, 13BCE8091C99CB9D00DD7AAD /* RCTRootShadowView.m in Sources */, 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */, @@ -2710,14 +2641,12 @@ 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */, 3D7749441DC1065C007EC8D8 /* RCTPlatform.m in Sources */, A12E9E1E1E5DEA350029001B /* RCTPackagerClientResponder.m in Sources */, - 13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */, 13D9FEEE1CDCD93000158BD7 /* RCTKeyboardObserver.m in Sources */, B233E6EA1D2D845D00BC68BA /* RCTI18nManager.m in Sources */, 13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */, 137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */, 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */, 83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */, - 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */, 191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */, 13C156051AB1A2840079392D /* RCTWebView.m in Sources */, 83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */, @@ -2736,7 +2665,6 @@ 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */, 83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */, - 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, 83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */, A12E9E5B1E5DF8600029001B /* RCTReloadPackagerMethod.m in Sources */, diff --git a/React/ReactCxx.xcodeproj/project.pbxproj b/React/ReactCxx.xcodeproj/project.pbxproj index e30bc533291da0..1c3ebe943e2f52 100644 --- a/React/ReactCxx.xcodeproj/project.pbxproj +++ b/React/ReactCxx.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ 130443DD1E401AF500D93A67 /* RCTConvert+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 130443C31E401A8C00D93A67 /* RCTConvert+Transform.h */; }; 130443DE1E401B0D00D93A67 /* RCTTVView.h in Headers */ = {isa = PBXBuildFile; fileRef = 130443D61E401AD800D93A67 /* RCTTVView.h */; }; 130443DF1E401B0D00D93A67 /* RCTTVView.m in Sources */ = {isa = PBXBuildFile; fileRef = 130443D71E401AD800D93A67 /* RCTTVView.m */; }; + 130E3D881E6A082100ACE484 /* RCTDevSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = 130E3D861E6A082100ACE484 /* RCTDevSettings.h */; }; + 130E3D891E6A082100ACE484 /* RCTDevSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = 130E3D871E6A082100ACE484 /* RCTDevSettings.mm */; }; + 130E3D8A1E6A083600ACE484 /* RCTDevSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = 130E3D861E6A082100ACE484 /* RCTDevSettings.h */; }; + 130E3D8B1E6A083900ACE484 /* RCTDevSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = 130E3D871E6A082100ACE484 /* RCTDevSettings.mm */; }; 13134C841E296B2A00B9F3CB /* RCTCxxBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 13134C731E296B2A00B9F3CB /* RCTCxxBridge.h */; }; 13134C851E296B2A00B9F3CB /* RCTCxxBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 13134C731E296B2A00B9F3CB /* RCTCxxBridge.h */; }; 13134C861E296B2A00B9F3CB /* RCTCxxBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13134C741E296B2A00B9F3CB /* RCTCxxBridge.mm */; }; @@ -113,7 +117,7 @@ 139D84B21E273B5600323FB7 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 139D84A51E273B5600323FB7 /* File.cpp */; }; 139D84B31E273B5600323FB7 /* json.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 139D84A71E273B5600323FB7 /* json.cpp */; }; 13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; }; - 13A0C28A1B74F71200B29F6F /* RCTDevMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.mm */; }; + 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; }; 13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; }; 13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */; }; 13AB5E011DF777F2001A8C30 /* YGNodeList.c in Sources */ = {isa = PBXBuildFile; fileRef = 130A77051DF767AF001F9587 /* YGNodeList.c */; }; @@ -312,7 +316,7 @@ 2D3B5EB11D9B090100451313 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; }; 2D3B5EB21D9B090300451313 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; 2D3B5EB41D9B090A00451313 /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; }; - 2D3B5EB51D9B091100451313 /* RCTDevMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.mm */; }; + 2D3B5EB51D9B091100451313 /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; }; 2D3B5EB61D9B091400451313 /* RCTExceptionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */; }; 2D3B5EB71D9B091800451313 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F17A841B8493E5007D4C75 /* RCTRedBox.m */; }; 2D3B5EB81D9B091B00451313 /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; }; @@ -1506,6 +1510,8 @@ 130A77061DF767AF001F9587 /* YGNodeList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YGNodeList.h; sourceTree = ""; }; 130A77071DF767AF001F9587 /* Yoga.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = Yoga.c; sourceTree = ""; }; 130A77081DF767AF001F9587 /* Yoga.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Yoga.h; sourceTree = ""; }; + 130E3D861E6A082100ACE484 /* RCTDevSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevSettings.h; sourceTree = ""; }; + 130E3D871E6A082100ACE484 /* RCTDevSettings.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTDevSettings.mm; sourceTree = ""; }; 13134C731E296B2A00B9F3CB /* RCTCxxBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCxxBridge.h; sourceTree = ""; }; 13134C741E296B2A00B9F3CB /* RCTCxxBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTCxxBridge.mm; sourceTree = ""; }; 13134C771E296B2A00B9F3CB /* RCTMessageThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMessageThread.h; sourceTree = ""; }; @@ -1552,24 +1558,24 @@ 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = ""; }; 137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = ""; }; 137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = ""; }; - 139D7E391E25C5A300323FB7 /* bignum-dtoa.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "bignum-dtoa.cc"; path = "double-conversion-1.1.1/src/bignum-dtoa.cc"; sourceTree = ""; }; - 139D7E3A1E25C5A300323FB7 /* bignum-dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "bignum-dtoa.h"; path = "double-conversion-1.1.1/src/bignum-dtoa.h"; sourceTree = ""; }; - 139D7E3B1E25C5A300323FB7 /* bignum.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bignum.cc; path = "double-conversion-1.1.1/src/bignum.cc"; sourceTree = ""; }; - 139D7E3C1E25C5A300323FB7 /* bignum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bignum.h; path = "double-conversion-1.1.1/src/bignum.h"; sourceTree = ""; }; - 139D7E3D1E25C5A300323FB7 /* cached-powers.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "cached-powers.cc"; path = "double-conversion-1.1.1/src/cached-powers.cc"; sourceTree = ""; }; - 139D7E3E1E25C5A300323FB7 /* cached-powers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "cached-powers.h"; path = "double-conversion-1.1.1/src/cached-powers.h"; sourceTree = ""; }; - 139D7E3F1E25C5A300323FB7 /* diy-fp.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "diy-fp.cc"; path = "double-conversion-1.1.1/src/diy-fp.cc"; sourceTree = ""; }; - 139D7E401E25C5A300323FB7 /* diy-fp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "diy-fp.h"; path = "double-conversion-1.1.1/src/diy-fp.h"; sourceTree = ""; }; - 139D7E411E25C5A300323FB7 /* double-conversion.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "double-conversion.cc"; path = "double-conversion-1.1.1/src/double-conversion.cc"; sourceTree = ""; }; - 139D7E421E25C5A300323FB7 /* double-conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "double-conversion.h"; path = "double-conversion-1.1.1/src/double-conversion.h"; sourceTree = ""; }; - 139D7E431E25C5A300323FB7 /* fast-dtoa.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "fast-dtoa.cc"; path = "double-conversion-1.1.1/src/fast-dtoa.cc"; sourceTree = ""; }; - 139D7E441E25C5A300323FB7 /* fast-dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "fast-dtoa.h"; path = "double-conversion-1.1.1/src/fast-dtoa.h"; sourceTree = ""; }; - 139D7E451E25C5A300323FB7 /* fixed-dtoa.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "fixed-dtoa.cc"; path = "double-conversion-1.1.1/src/fixed-dtoa.cc"; sourceTree = ""; }; - 139D7E461E25C5A300323FB7 /* fixed-dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "fixed-dtoa.h"; path = "double-conversion-1.1.1/src/fixed-dtoa.h"; sourceTree = ""; }; - 139D7E471E25C5A300323FB7 /* ieee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ieee.h; path = "double-conversion-1.1.1/src/ieee.h"; sourceTree = ""; }; - 139D7E481E25C5A300323FB7 /* strtod.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = strtod.cc; path = "double-conversion-1.1.1/src/strtod.cc"; sourceTree = ""; }; - 139D7E491E25C5A300323FB7 /* strtod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = strtod.h; path = "double-conversion-1.1.1/src/strtod.h"; sourceTree = ""; }; - 139D7E4A1E25C5A300323FB7 /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = utils.h; path = "double-conversion-1.1.1/src/utils.h"; sourceTree = ""; }; + 139D7E391E25C5A300323FB7 /* bignum-dtoa.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "bignum-dtoa.cc"; path = "double-conversion-1.1.5/src/bignum-dtoa.cc"; sourceTree = ""; }; + 139D7E3A1E25C5A300323FB7 /* bignum-dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "bignum-dtoa.h"; path = "double-conversion-1.1.5/src/bignum-dtoa.h"; sourceTree = ""; }; + 139D7E3B1E25C5A300323FB7 /* bignum.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bignum.cc; path = "double-conversion-1.1.5/src/bignum.cc"; sourceTree = ""; }; + 139D7E3C1E25C5A300323FB7 /* bignum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bignum.h; path = "double-conversion-1.1.5/src/bignum.h"; sourceTree = ""; }; + 139D7E3D1E25C5A300323FB7 /* cached-powers.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "cached-powers.cc"; path = "double-conversion-1.1.5/src/cached-powers.cc"; sourceTree = ""; }; + 139D7E3E1E25C5A300323FB7 /* cached-powers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "cached-powers.h"; path = "double-conversion-1.1.5/src/cached-powers.h"; sourceTree = ""; }; + 139D7E3F1E25C5A300323FB7 /* diy-fp.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "diy-fp.cc"; path = "double-conversion-1.1.5/src/diy-fp.cc"; sourceTree = ""; }; + 139D7E401E25C5A300323FB7 /* diy-fp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "diy-fp.h"; path = "double-conversion-1.1.5/src/diy-fp.h"; sourceTree = ""; }; + 139D7E411E25C5A300323FB7 /* double-conversion.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "double-conversion.cc"; path = "double-conversion-1.1.5/src/double-conversion.cc"; sourceTree = ""; }; + 139D7E421E25C5A300323FB7 /* double-conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "double-conversion.h"; path = "double-conversion-1.1.5/src/double-conversion.h"; sourceTree = ""; }; + 139D7E431E25C5A300323FB7 /* fast-dtoa.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "fast-dtoa.cc"; path = "double-conversion-1.1.5/src/fast-dtoa.cc"; sourceTree = ""; }; + 139D7E441E25C5A300323FB7 /* fast-dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "fast-dtoa.h"; path = "double-conversion-1.1.5/src/fast-dtoa.h"; sourceTree = ""; }; + 139D7E451E25C5A300323FB7 /* fixed-dtoa.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "fixed-dtoa.cc"; path = "double-conversion-1.1.5/src/fixed-dtoa.cc"; sourceTree = ""; }; + 139D7E461E25C5A300323FB7 /* fixed-dtoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "fixed-dtoa.h"; path = "double-conversion-1.1.5/src/fixed-dtoa.h"; sourceTree = ""; }; + 139D7E471E25C5A300323FB7 /* ieee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ieee.h; path = "double-conversion-1.1.5/src/ieee.h"; sourceTree = ""; }; + 139D7E481E25C5A300323FB7 /* strtod.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = strtod.cc; path = "double-conversion-1.1.5/src/strtod.cc"; sourceTree = ""; }; + 139D7E491E25C5A300323FB7 /* strtod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = strtod.h; path = "double-conversion-1.1.5/src/strtod.h"; sourceTree = ""; }; + 139D7E4A1E25C5A300323FB7 /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = utils.h; path = "double-conversion-1.1.5/src/utils.h"; sourceTree = ""; }; 139D7E881E25C6D100323FB7 /* libdouble-conversion.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libdouble-conversion.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 139D7ECE1E25DB7D00323FB7 /* libthird-party.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libthird-party.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 139D7ED81E25DBDC00323FB7 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = "glog-0.3.4/src/config.h"; sourceTree = ""; }; @@ -1609,7 +1615,7 @@ 13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTDevLoadingView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevLoadingView.m; sourceTree = ""; }; 13A0C2871B74F71200B29F6F /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTDevMenu.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; - 13A0C2881B74F71200B29F6F /* RCTDevMenu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTDevMenu.mm; sourceTree = ""; }; + 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = ""; }; 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = ""; }; 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = ""; }; 13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTParserUtils.h; sourceTree = ""; }; @@ -2076,6 +2082,8 @@ 13B07FE01A69315300A75B9A /* Modules */ = { isa = PBXGroup; children = ( + 130E3D861E6A082100ACE484 /* RCTDevSettings.h */, + 130E3D871E6A082100ACE484 /* RCTDevSettings.mm */, 369123DF1DDC75850095B341 /* JSCSamplingProfiler.h */, 369123E01DDC75850095B341 /* JSCSamplingProfiler.m */, E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */, @@ -2091,7 +2099,7 @@ 13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */, 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */, 13A0C2871B74F71200B29F6F /* RCTDevMenu.h */, - 13A0C2881B74F71200B29F6F /* RCTDevMenu.mm */, + 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */, 13D9FEE91CDCCECF00158BD7 /* RCTEventEmitter.h */, 13D9FEEA1CDCCECF00158BD7 /* RCTEventEmitter.m */, 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */, @@ -2561,6 +2569,7 @@ 3D302F2D1DF828F800D6DDAE /* RCTBridge+Private.h in Headers */, 3D302F2E1DF828F800D6DDAE /* RCTBridgeDelegate.h in Headers */, 3D302F2F1DF828F800D6DDAE /* RCTBridgeMethod.h in Headers */, + 130E3D8A1E6A083600ACE484 /* RCTDevSettings.h in Headers */, 3D302F301DF828F800D6DDAE /* RCTBridgeModule.h in Headers */, 3D302F311DF828F800D6DDAE /* RCTBundleURLProvider.h in Headers */, 3D302F321DF828F800D6DDAE /* RCTConvert.h in Headers */, @@ -2833,6 +2842,7 @@ 139D7E5C1E25C5A300323FB7 /* utils.h in Headers */, 3D80DA301DF820620028D040 /* RCTJavaScriptExecutor.h in Headers */, 3D80DA311DF820620028D040 /* RCTJavaScriptLoader.h in Headers */, + 130E3D881E6A082100ACE484 /* RCTDevSettings.h in Headers */, 3D80DA321DF820620028D040 /* RCTJSStackFrame.h in Headers */, 130443DC1E401AF400D93A67 /* RCTConvert+Transform.h in Headers */, 3D80DA331DF820620028D040 /* RCTKeyCommands.h in Headers */, @@ -2966,6 +2976,7 @@ isa = PBXNativeTarget; buildConfigurationList = 139D7E8E1E25C6D100323FB7 /* Build configuration list for PBXNativeTarget "double-conversion" */; buildPhases = ( + 190EE32F1E6A43DE00A8543A /* Install Third Party */, 139D7E841E25C6D100323FB7 /* Sources */, 139D7E851E25C6D100323FB7 /* Frameworks */, 139D7E861E25C6D100323FB7 /* CopyFiles */, @@ -3233,6 +3244,20 @@ shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'cp -r /tmp/RCTJSCProfiler \"$1\"' -- {}\nfi"; showEnvVarsInLog = 0; }; + 190EE32F1E6A43DE00A8543A /* Install Third Party */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Install Third Party"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd $SRCROOT/..\n./ios-install-third-party.sh"; + }; 2D6948201DA3042200B3FA97 /* Include RCTJSCProfiler */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3347,6 +3372,7 @@ 2D3B5EEF1D9B09DC00451313 /* RCTViewManager.m in Sources */, 13134C971E296B2A00B9F3CB /* RCTObjcExecutor.mm in Sources */, 2D3B5EE11D9B09B000451313 /* RCTScrollView.m in Sources */, + 130E3D8B1E6A083900ACE484 /* RCTDevSettings.mm in Sources */, 2D3B5ED81D9B098A00451313 /* RCTNavigatorManager.m in Sources */, 2D3B5E951D9B087C00451313 /* RCTAssert.m in Sources */, 2D3B5ED21D9B097800451313 /* RCTMapManager.m in Sources */, @@ -3373,7 +3399,7 @@ 59FBEFB71E46D91C0095D885 /* RCTScrollContentViewManager.m in Sources */, 2D3B5E941D9B087900451313 /* RCTBundleURLProvider.m in Sources */, 2D3B5EB81D9B091B00451313 /* RCTSourceCode.m in Sources */, - 2D3B5EB51D9B091100451313 /* RCTDevMenu.mm in Sources */, + 2D3B5EB51D9B091100451313 /* RCTDevMenu.m in Sources */, 2D3B5EBD1D9B092A00451313 /* RCTTiming.m in Sources */, 2D3B5EA81D9B08D300451313 /* RCTUtils.m in Sources */, 2D3B5EC81D9B095800451313 /* RCTActivityIndicatorViewManager.m in Sources */, @@ -3526,7 +3552,7 @@ 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */, 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */, 13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */, - 13A0C28A1B74F71200B29F6F /* RCTDevMenu.mm in Sources */, + 13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */, 13BCE8091C99CB9D00DD7AAD /* RCTRootShadowView.m in Sources */, 14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */, 006FC4141D9B20820057AAAD /* RCTMultipartDataTask.m in Sources */, @@ -3535,6 +3561,7 @@ 13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */, 130443A21E3FEAA900D93A67 /* RCTFollyConvert.mm in Sources */, 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, + 130E3D891E6A082100ACE484 /* RCTDevSettings.mm in Sources */, 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */, 14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, diff --git a/React/Views/RCTConvert+MapKit.h b/React/Views/RCTConvert+MapKit.h deleted file mode 100644 index e3cc1353297fcd..00000000000000 --- a/React/Views/RCTConvert+MapKit.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -#import - -@class RCTMapAnnotation; -@class RCTMapOverlay; - -@interface RCTConvert (MapKit) - -+ (MKCoordinateSpan)MKCoordinateSpan:(id)json; -+ (MKCoordinateRegion)MKCoordinateRegion:(id)json; -+ (MKMapType)MKMapType:(id)json; - -+ (RCTMapAnnotation *)RCTMapAnnotation:(id)json; -+ (RCTMapOverlay *)RCTMapOverlay:(id)json; - -+ (NSArray *)RCTMapAnnotationArray:(id)json; -+ (NSArray *)RCTMapOverlayArray:(id)json; - -@end diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m deleted file mode 100644 index 354b133b6780d0..00000000000000 --- a/React/Views/RCTConvert+MapKit.m +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTConvert+MapKit.h" -#import "RCTConvert+CoreLocation.h" -#import "RCTMapAnnotation.h" -#import "RCTMapOverlay.h" - -@implementation RCTConvert(MapKit) - -+ (MKCoordinateSpan)MKCoordinateSpan:(id)json -{ - json = [self NSDictionary:json]; - return (MKCoordinateSpan){ - [self CLLocationDegrees:json[@"latitudeDelta"]], - [self CLLocationDegrees:json[@"longitudeDelta"]] - }; -} - -+ (MKCoordinateRegion)MKCoordinateRegion:(id)json -{ - return (MKCoordinateRegion){ - [self CLLocationCoordinate2D:json], - [self MKCoordinateSpan:json] - }; -} - -RCT_ENUM_CONVERTER(MKMapType, (@{ - @"standard": @(MKMapTypeStandard), - @"satellite": @(MKMapTypeSatellite), - @"hybrid": @(MKMapTypeHybrid), -}), MKMapTypeStandard, integerValue) - -+ (RCTMapAnnotation *)RCTMapAnnotation:(id)json -{ - json = [self NSDictionary:json]; - RCTMapAnnotation *annotation = [RCTMapAnnotation new]; - annotation.coordinate = [self CLLocationCoordinate2D:json]; - annotation.draggable = [self BOOL:json[@"draggable"]]; - annotation.title = [self NSString:json[@"title"]]; - annotation.subtitle = [self NSString:json[@"subtitle"]]; - annotation.identifier = [self NSString:json[@"id"]]; - annotation.hasLeftCallout = [self BOOL:json[@"hasLeftCallout"]]; - annotation.hasRightCallout = [self BOOL:json[@"hasRightCallout"]]; - annotation.animateDrop = [self BOOL:json[@"animateDrop"]]; - annotation.tintColor = [self UIColor:json[@"tintColor"]]; - annotation.image = [self UIImage:json[@"image"]]; - annotation.viewIndex = - [self NSInteger:json[@"viewIndex"] ?: @(NSNotFound)]; - annotation.leftCalloutViewIndex = - [self NSInteger:json[@"leftCalloutViewIndex"] ?: @(NSNotFound)]; - annotation.rightCalloutViewIndex = - [self NSInteger:json[@"rightCalloutViewIndex"] ?: @(NSNotFound)]; - annotation.detailCalloutViewIndex = - [self NSInteger:json[@"detailCalloutViewIndex"] ?: @(NSNotFound)]; - return annotation; -} - -RCT_ARRAY_CONVERTER(RCTMapAnnotation) - -+ (RCTMapOverlay *)RCTMapOverlay:(id)json -{ - json = [self NSDictionary:json]; - NSArray *locations = [self NSDictionaryArray:json[@"coordinates"]]; - CLLocationCoordinate2D coordinates[locations.count]; - NSUInteger index = 0; - for (NSDictionary *location in locations) { - coordinates[index++] = [self CLLocationCoordinate2D:location]; - } - - RCTMapOverlay *overlay = [RCTMapOverlay polylineWithCoordinates:coordinates - count:locations.count]; - - overlay.strokeColor = [self UIColor:json[@"strokeColor"]]; - overlay.identifier = [self NSString:json[@"id"]]; - overlay.lineWidth = [self CGFloat:json[@"lineWidth"] ?: @1]; - return overlay; -} - -RCT_ARRAY_CONVERTER(RCTMapOverlay) - -@end diff --git a/React/Views/RCTMap.h b/React/Views/RCTMap.h deleted file mode 100644 index 9c954f99e0e27b..00000000000000 --- a/React/Views/RCTMap.h +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import -#import - -#import -#import - -RCT_EXTERN const CLLocationDegrees RCTMapDefaultSpan; -RCT_EXTERN const NSTimeInterval RCTMapRegionChangeObserveInterval; -RCT_EXTERN const CGFloat RCTMapZoomBoundBuffer; - -@interface RCTMap: MKMapView - -@property (nonatomic, assign) BOOL followUserLocation; -@property (nonatomic, assign) BOOL hasStartedRendering; -@property (nonatomic, assign) BOOL showsAnnotationCallouts; -@property (nonatomic, assign) CGFloat minDelta; -@property (nonatomic, assign) CGFloat maxDelta; -@property (nonatomic, assign) UIEdgeInsets legalLabelInsets; -@property (nonatomic, strong) NSTimer *regionChangeObserveTimer; -@property (nonatomic, copy) NSArray *annotationIDs; -@property (nonatomic, copy) NSArray *overlayIDs; - -@property (nonatomic, copy) RCTBubblingEventBlock onChange; -@property (nonatomic, copy) RCTBubblingEventBlock onPress; -@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationDragStateChange; -@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationFocus; -@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationBlur; - -- (void)setAnnotations:(NSArray *)annotations; -- (void)setOverlays:(NSArray *)overlays; - -@end diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m deleted file mode 100644 index d620419dc33848..00000000000000 --- a/React/Views/RCTMap.m +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTMap.h" - -#import "RCTEventDispatcher.h" -#import "RCTLog.h" -#import "RCTMapAnnotation.h" -#import "RCTMapOverlay.h" -#import "RCTUtils.h" -#import "UIView+React.h" - -const CLLocationDegrees RCTMapDefaultSpan = 0.005; -const NSTimeInterval RCTMapRegionChangeObserveInterval = 0.1; -const CGFloat RCTMapZoomBoundBuffer = 0.01; - -@implementation RCTMap -{ - UIView *_legalLabel; - CLLocationManager *_locationManager; -} - -- (instancetype)init -{ - if ((self = [super init])) { - - _hasStartedRendering = NO; - - // Find Apple link label - for (UIView *subview in self.subviews) { - if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) { - // This check is super hacky, but the whole premise of moving around - // Apple's internal subviews is super hacky - _legalLabel = subview; - break; - } - } - } - return self; -} - -- (void)dealloc -{ - [_regionChangeObserveTimer invalidate]; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as annotation views are managed by `setAnnotations:` method -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - - if (_legalLabel) { - dispatch_async(dispatch_get_main_queue(), ^{ - CGRect frame = self->_legalLabel.frame; - if (self->_legalLabelInsets.left) { - frame.origin.x = self->_legalLabelInsets.left; - } else if (self->_legalLabelInsets.right) { - frame.origin.x = self.frame.size.width - self->_legalLabelInsets.right - frame.size.width; - } - if (self->_legalLabelInsets.top) { - frame.origin.y = self->_legalLabelInsets.top; - } else if (self->_legalLabelInsets.bottom) { - frame.origin.y = self.frame.size.height - self->_legalLabelInsets.bottom - frame.size.height; - } - self->_legalLabel.frame = frame; - }); - } -} - -#pragma mark Accessors - -- (void)setShowsUserLocation:(BOOL)showsUserLocation -{ - if (self.showsUserLocation != showsUserLocation) { - if (showsUserLocation && !_locationManager) { - _locationManager = [CLLocationManager new]; - if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { - [_locationManager requestWhenInUseAuthorization]; - } - } - super.showsUserLocation = showsUserLocation; - } -} - -- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated -{ - // If location is invalid, abort - if (!CLLocationCoordinate2DIsValid(region.center)) { - return; - } - - // If new span values are nil, use old values instead - if (!region.span.latitudeDelta) { - region.span.latitudeDelta = self.region.span.latitudeDelta; - } - if (!region.span.longitudeDelta) { - region.span.longitudeDelta = self.region.span.longitudeDelta; - } - - // Animate to new position - [super setRegion:region animated:animated]; -} - -// TODO: this doesn't preserve order. Should it? If so we should change the -// algorithm. If not, it would be more efficient to use an NSSet -- (void)setAnnotations:(NSArray *)annotations -{ - NSMutableArray *newAnnotationIDs = [NSMutableArray new]; - NSMutableArray *annotationsToDelete = [NSMutableArray new]; - NSMutableArray *annotationsToAdd = [NSMutableArray new]; - - for (RCTMapAnnotation *annotation in annotations) { - if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { - continue; - } - - [newAnnotationIDs addObject:annotation.identifier]; - - // If the current set does not contain the new annotation, mark it to add - if (![_annotationIDs containsObject:annotation.identifier]) { - [annotationsToAdd addObject:annotation]; - } - } - - for (RCTMapAnnotation *annotation in self.annotations) { - if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { - continue; - } - - // If the new set does not contain an existing annotation, mark it to delete - if (![newAnnotationIDs containsObject:annotation.identifier]) { - [annotationsToDelete addObject:annotation]; - } - } - - if (annotationsToDelete.count) { - [self removeAnnotations:(NSArray> *)annotationsToDelete]; - } - - if (annotationsToAdd.count) { - [self addAnnotations:(NSArray> *)annotationsToAdd]; - } - - self.annotationIDs = newAnnotationIDs; -} - -// TODO: this doesn't preserve order. Should it? If so we should change the -// algorithm. If not, it would be more efficient to use an NSSet -- (void)setOverlays:(NSArray *)overlays -{ - NSMutableArray *newOverlayIDs = [NSMutableArray new]; - NSMutableArray *overlaysToDelete = [NSMutableArray new]; - NSMutableArray *overlaysToAdd = [NSMutableArray new]; - - for (RCTMapOverlay *overlay in overlays) { - if (![overlay isKindOfClass:[RCTMapOverlay class]]) { - continue; - } - - [newOverlayIDs addObject:overlay.identifier]; - - // If the current set does not contain the new annotation, mark it to add - if (![_annotationIDs containsObject:overlay.identifier]) { - [overlaysToAdd addObject:overlay]; - } - } - - for (RCTMapOverlay *overlay in self.overlays) { - if (![overlay isKindOfClass:[RCTMapOverlay class]]) { - continue; - } - - // If the new set does not contain an existing annotation, mark it to delete - if (![newOverlayIDs containsObject:overlay.identifier]) { - [overlaysToDelete addObject:overlay]; - } - } - - if (overlaysToDelete.count) { - [self removeOverlays:(NSArray> *)overlaysToDelete]; - } - - if (overlaysToAdd.count) { - [self addOverlays:(NSArray> *)overlaysToAdd - level:MKOverlayLevelAboveRoads]; - } - - self.overlayIDs = newOverlayIDs; -} - -- (BOOL)showsCompass { - if ([MKMapView instancesRespondToSelector:@selector(showsCompass)]) { - return super. showsCompass; - } - return NO; -} - -- (void)setShowsCompass:(BOOL)showsCompass { - if ([MKMapView instancesRespondToSelector:@selector(setShowsCompass:)]) { - super.showsCompass = showsCompass; - } -} - -@end diff --git a/React/Views/RCTMapAnnotation.h b/React/Views/RCTMapAnnotation.h deleted file mode 100644 index b273bfb093d0c6..00000000000000 --- a/React/Views/RCTMapAnnotation.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -@interface RCTMapAnnotation : MKPointAnnotation - -@property (nonatomic, copy) NSString *identifier; -@property (nonatomic, assign) BOOL hasLeftCallout; -@property (nonatomic, assign) BOOL hasRightCallout; -@property (nonatomic, assign) BOOL animateDrop; -@property (nonatomic, strong) UIColor *tintColor; -@property (nonatomic, strong) UIImage *image; -@property (nonatomic, assign) NSUInteger viewIndex; -@property (nonatomic, assign) NSUInteger leftCalloutViewIndex; -@property (nonatomic, assign) NSUInteger rightCalloutViewIndex; -@property (nonatomic, assign) NSUInteger detailCalloutViewIndex; -@property (nonatomic, assign) BOOL draggable; - -@end diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m deleted file mode 100644 index c52c7179ac457d..00000000000000 --- a/React/Views/RCTMapManager.m +++ /dev/null @@ -1,434 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTMapManager.h" - -#import "RCTBridge.h" -#import "RCTConvert+CoreLocation.h" -#import "RCTConvert+MapKit.h" -#import "RCTEventDispatcher.h" -#import "RCTMap.h" -#import "RCTUtils.h" -#import "UIView+React.h" -#import "RCTMapAnnotation.h" -#import "RCTMapOverlay.h" - -#import - -static NSString *const RCTMapViewKey = @"MapView"; - -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 - -static NSString *const RCTMapPinRed = @"#ff3b30"; -static NSString *const RCTMapPinGreen = @"#4cd964"; -static NSString *const RCTMapPinPurple = @"#c969e0"; - -@implementation RCTConvert (MKPinAnnotationColor) - -RCT_ENUM_CONVERTER(MKPinAnnotationColor, (@{ - RCTMapPinRed: @(MKPinAnnotationColorRed), - RCTMapPinGreen: @(MKPinAnnotationColorGreen), - RCTMapPinPurple: @(MKPinAnnotationColorPurple) -}), MKPinAnnotationColorRed, unsignedIntegerValue) - -@end - -#endif - -@interface RCTMapAnnotationView : MKAnnotationView - -@property (nonatomic, strong) UIView *contentView; - -@end - -@implementation RCTMapAnnotationView - -- (void)setContentView:(UIView *)contentView -{ - [_contentView removeFromSuperview]; - _contentView = contentView; - [self addSubview:_contentView]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - self.bounds = (CGRect){ - CGPointZero, - _contentView.frame.size, - }; -} - -@end - -@interface RCTMapManager() - -@end - -@implementation RCTMapManager - -RCT_EXPORT_MODULE() - -- (UIView *)view -{ - RCTMap *map = [RCTMap new]; - map.delegate = self; - return map; -} - -RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL) -RCT_EXPORT_VIEW_PROPERTY(showsPointsOfInterest, BOOL) -RCT_EXPORT_VIEW_PROPERTY(showsCompass, BOOL) -RCT_EXPORT_VIEW_PROPERTY(showsAnnotationCallouts, BOOL) -RCT_EXPORT_VIEW_PROPERTY(followUserLocation, BOOL) -RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL) -RCT_EXPORT_VIEW_PROPERTY(maxDelta, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType) -RCT_EXPORT_VIEW_PROPERTY(annotations, NSArray) -RCT_EXPORT_VIEW_PROPERTY(overlays, NSArray) -RCT_EXPORT_VIEW_PROPERTY(onAnnotationDragStateChange, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onAnnotationFocus, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onAnnotationBlur, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) -RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) -{ -#pragma unused (defaultView) - if (json) { - [view setRegion:[RCTConvert MKCoordinateRegion:json] animated:YES]; - } -} - -#pragma mark MKMapViewDelegate - -- (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view -{ - // TODO: Remove deprecated onAnnotationPress API call later. - if (mapView.onPress && [view.annotation isKindOfClass:[RCTMapAnnotation class]]) { - RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; - mapView.onPress(@{ - @"action": @"annotation-click", - @"annotation": @{ - @"id": annotation.identifier, - @"title": annotation.title ?: @"", - @"subtitle": annotation.subtitle ?: @"", - @"latitude": @(annotation.coordinate.latitude), - @"longitude": @(annotation.coordinate.longitude) - } - }); - } - - if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) { - RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; - if (mapView.onAnnotationFocus) { - mapView.onAnnotationFocus(@{ - @"annotationId": annotation.identifier - }); - } - } -} - -- (void)mapView:(RCTMap *)mapView didDeselectAnnotationView:(MKAnnotationView *)view -{ - if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) { - RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; - if (mapView.onAnnotationBlur) { - mapView.onAnnotationBlur(@{ - @"annotationId": annotation.identifier - }); - } - } -} - -#if !TARGET_OS_TV -- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view - didChangeDragState:(MKAnnotationViewDragState)newState - fromOldState:(MKAnnotationViewDragState)oldState -{ - static NSArray *states; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - states = @[@"idle", @"starting", @"dragging", @"canceling", @"ending"]; - }); - - if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) { - RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; - if (mapView.onAnnotationDragStateChange) { - mapView.onAnnotationDragStateChange(@{ - @"state": states[newState], - @"oldState": states[oldState], - @"annotationId": annotation.identifier, - @"latitude": @(annotation.coordinate.latitude), - @"longitude": @(annotation.coordinate.longitude), - }); - } - } -} -#endif //TARGET_OS_TV - -- (MKAnnotationView *)mapView:(RCTMap *)mapView - viewForAnnotation:(RCTMapAnnotation *)annotation -{ - if (![annotation isKindOfClass:[RCTMapAnnotation class]]) { - return nil; - } - - MKAnnotationView *annotationView; - if (annotation.viewIndex != NSNotFound && - annotation.viewIndex < mapView.reactSubviews.count) { - - NSString *reuseIdentifier = NSStringFromClass([RCTMapAnnotationView class]); - annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier]; - if (!annotationView) { - annotationView = [[RCTMapAnnotationView alloc] initWithAnnotation:annotation - reuseIdentifier:reuseIdentifier]; - } - UIView *reactView = mapView.reactSubviews[annotation.viewIndex]; - ((RCTMapAnnotationView *)annotationView).contentView = reactView; - - } else if (annotation.image) { - - NSString *reuseIdentifier = NSStringFromClass([MKAnnotationView class]); - annotationView = - [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: - [[MKAnnotationView alloc] initWithAnnotation:annotation - reuseIdentifier:reuseIdentifier]; - annotationView.image = annotation.image; - - } else { - - NSString *reuseIdentifier = NSStringFromClass([MKPinAnnotationView class]); - annotationView = - [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?: - [[MKPinAnnotationView alloc] initWithAnnotation:annotation - reuseIdentifier:reuseIdentifier]; - ((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop; - -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 - - if (![annotationView respondsToSelector:@selector(pinTintColor)]) { - NSString *hexColor = annotation.tintColor ? - RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed; - ((MKPinAnnotationView *)annotationView).pinColor = - [RCTConvert MKPinAnnotationColor:hexColor]; - } else - -#endif - - { - ((MKPinAnnotationView *)annotationView).pinTintColor = - annotation.tintColor ?: [MKPinAnnotationView redPinColor]; - } - } - annotationView.canShowCallout = (annotation.title.length > 0); - - if (annotation.leftCalloutViewIndex != NSNotFound && - annotation.leftCalloutViewIndex < mapView.reactSubviews.count) { - annotationView.leftCalloutAccessoryView = - mapView.reactSubviews[annotation.leftCalloutViewIndex]; - } else if (annotation.hasLeftCallout) { - annotationView.leftCalloutAccessoryView = - [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; - } else { - annotationView.leftCalloutAccessoryView = nil; - } - - if (annotation.rightCalloutViewIndex != NSNotFound && - annotation.rightCalloutViewIndex < mapView.reactSubviews.count) { - annotationView.rightCalloutAccessoryView = - mapView.reactSubviews[annotation.rightCalloutViewIndex]; - } else if (annotation.hasRightCallout) { - annotationView.rightCalloutAccessoryView = - [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; - } else { - annotationView.rightCalloutAccessoryView = nil; - } - - //http://stackoverflow.com/questions/32581049/mapkit-ios-9-detailcalloutaccessoryview-usage - if ([annotationView respondsToSelector:@selector(detailCalloutAccessoryView)]) { - if (annotation.detailCalloutViewIndex != NSNotFound && - annotation.detailCalloutViewIndex < mapView.reactSubviews.count) { - UIView *calloutView = mapView.reactSubviews[annotation.detailCalloutViewIndex]; - NSLayoutConstraint *widthConstraint = - [NSLayoutConstraint constraintWithItem:calloutView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 - constant:calloutView.frame.size.width]; - [calloutView addConstraint:widthConstraint]; - NSLayoutConstraint *heightConstraint = - [NSLayoutConstraint constraintWithItem:calloutView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 - constant:calloutView.frame.size.height]; - [calloutView addConstraint:heightConstraint]; - annotationView.detailCalloutAccessoryView = calloutView; - } else { - annotationView.detailCalloutAccessoryView = nil; - } - } - -#if !TARGET_OS_TV - annotationView.draggable = annotation.draggable; -#endif - - return annotationView; -} - -- (void)mapView:(RCTMap *)mapView didAddAnnotationViews:(__unused NSArray *)views { - if (mapView.showsAnnotationCallouts) { - for (id annotation in mapView.annotations) { - [mapView selectAnnotation:annotation animated:YES]; - } - } -} - -- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView rendererForOverlay:(id)overlay -{ - RCTAssert([overlay isKindOfClass:[RCTMapOverlay class]], @"Overlay must be of type RCTMapOverlay"); - MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay]; - polylineRenderer.strokeColor = [(RCTMapOverlay *)overlay strokeColor]; - polylineRenderer.lineWidth = [(RCTMapOverlay *)overlay lineWidth]; - return polylineRenderer; -} - -- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view - calloutAccessoryControlTapped:(UIControl *)control -{ - if (mapView.onPress) { - // Pass to JS - RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation; - mapView.onPress(@{ - @"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right", - @"action": @"callout-click", - @"annotationId": annotation.identifier - }); - } -} - -- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location -{ - if (mapView.followUserLocation) { - MKCoordinateRegion region; - region.span.latitudeDelta = RCTMapDefaultSpan; - region.span.longitudeDelta = RCTMapDefaultSpan; - region.center = location.coordinate; - [mapView setRegion:region animated:YES]; - } -} - -- (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(__unused BOOL)animated -{ - [self _regionChanged:mapView]; - - mapView.regionChangeObserveTimer = - [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval - target:self - selector:@selector(_onTick:) - userInfo:@{ RCTMapViewKey: mapView } - repeats:YES]; - - [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer - forMode:NSRunLoopCommonModes]; -} - -- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(__unused BOOL)animated -{ - [mapView.regionChangeObserveTimer invalidate]; - mapView.regionChangeObserveTimer = nil; - - [self _regionChanged:mapView]; - - // Don't send region did change events until map has - // started rendering, as these won't represent the final location - if (mapView.hasStartedRendering) { - [self _emitRegionChangeEvent:mapView continuous:NO]; - }; -} - -- (void)mapViewWillStartRenderingMap:(RCTMap *)mapView -{ - mapView.hasStartedRendering = YES; - [self _emitRegionChangeEvent:mapView continuous:NO]; -} - -#pragma mark Private - -- (void)_onTick:(NSTimer *)timer -{ - [self _regionChanged:timer.userInfo[RCTMapViewKey]]; -} - -- (void)_regionChanged:(RCTMap *)mapView -{ - BOOL needZoom = NO; - CGFloat newLongitudeDelta = 0.0f; - MKCoordinateRegion region = mapView.region; - - // On iOS 7, it's possible that we observe invalid locations during - // initialization of the map. Filter those out. - if (!CLLocationCoordinate2DIsValid(region.center)) { - return; - } - - // Calculation on float is not 100% accurate. If user zoom to max/min and then - // move, it's likely the map will auto zoom to max/min from time to time. - // So let's try to make map zoom back to 99% max or 101% min so that there is - // some buffer, and moving the map won't constantly hit the max/min bound. - if (mapView.maxDelta > FLT_EPSILON && - region.span.longitudeDelta > mapView.maxDelta) { - needZoom = YES; - newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer); - } else if (mapView.minDelta > FLT_EPSILON && - region.span.longitudeDelta < mapView.minDelta) { - needZoom = YES; - newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer); - } - if (needZoom) { - region.span.latitudeDelta = - region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta; - region.span.longitudeDelta = newLongitudeDelta; - mapView.region = region; - } - - // Continously observe region changes - [self _emitRegionChangeEvent:mapView continuous:YES]; -} - -- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous -{ - if (mapView.onChange) { - MKCoordinateRegion region = mapView.region; - if (!CLLocationCoordinate2DIsValid(region.center)) { - return; - } - - mapView.onChange(@{ - @"continuous": @(continuous), - @"region": @{ - @"latitude": @(RCTZeroIfNaN(region.center.latitude)), - @"longitude": @(RCTZeroIfNaN(region.center.longitude)), - @"latitudeDelta": @(RCTZeroIfNaN(region.span.latitudeDelta)), - @"longitudeDelta": @(RCTZeroIfNaN(region.span.longitudeDelta)), - } - }); - } -} - -@end diff --git a/React/Views/RCTRootShadowView.m b/React/Views/RCTRootShadowView.m index 1d925a4fc9085a..15b5b6f7cb52fa 100644 --- a/React/Views/RCTRootShadowView.m +++ b/React/Views/RCTRootShadowView.m @@ -13,10 +13,6 @@ @implementation RCTRootShadowView -/** - * Init the RCTRootShadowView with RTL status. - * Returns a RTL CSS layout if isRTL is true (Default is LTR CSS layout). - */ - (instancetype)init { self = [super init]; @@ -33,16 +29,6 @@ - (instancetype)init float availableWidth = _availableSize.width == INFINITY ? YGUndefined : _availableSize.width; float availableHeight = _availableSize.height == INFINITY ? YGUndefined : _availableSize.height; - YGUnit widthUnit = YGNodeStyleGetWidth(self.yogaNode).unit; - if (widthUnit == YGUnitUndefined || widthUnit == YGUnitAuto) { - YGNodeStyleSetWidthPercent(self.yogaNode, 100); - } - - YGUnit heightUnit = YGNodeStyleGetHeight(self.yogaNode).unit; - if (heightUnit == YGUnitUndefined || heightUnit == YGUnitAuto) { - YGNodeStyleSetHeightPercent(self.yogaNode, 100); - } - YGNodeCalculateLayout(self.yogaNode, availableWidth, availableHeight, _baseDirection); NSMutableSet *viewsWithNewFrame = [NSMutableSet set]; diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h index 5d9f1b6942261a..4f399461194709 100644 --- a/React/Views/RCTScrollView.h +++ b/React/Views/RCTScrollView.h @@ -46,7 +46,6 @@ @property (nonatomic, assign) BOOL centerContent; @property (nonatomic, assign) int snapToInterval; @property (nonatomic, copy) NSString *snapToAlignment; -@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; // NOTE: currently these event props are only declared so we can export the // event names to JS - we don't call the blocks directly because scroll events diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 4f5fe07359f21c..32bcaeacc94d1d 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -23,9 +23,6 @@ #import "RCTRefreshControl.h" #endif -CGFloat const ZINDEX_DEFAULT = 0; -CGFloat const ZINDEX_STICKY_HEADER = 50; - @interface RCTScrollEvent : NSObject - (instancetype)initWithEventName:(NSString *)eventName @@ -137,11 +134,10 @@ - (NSArray *)arguments /** * Include a custom scroll view subclass because we want to limit certain * default UIKit behaviors such as textFields automatically scrolling - * scroll views that contain them and support sticky headers. + * scroll views that contain them. */ @interface RCTCustomScrollView : UIScrollView -@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; @property (nonatomic, assign) BOOL centerContent; #if !TARGET_OS_TV @property (nonatomic, strong) RCTRefreshControl *rctRefreshControl; @@ -151,9 +147,6 @@ @interface RCTCustomScrollView : UIScrollView @implementation RCTCustomScrollView -{ - __weak UIView *_dockedHeaderView; -} - (instancetype)initWithFrame:(CGRect)frame { @@ -281,98 +274,6 @@ - (void)setContentOffset:(CGPoint)contentOffset super.contentOffset = contentOffset; } -- (void)dockClosestSectionHeader -{ - UIView *contentView = [self contentView]; - CGFloat scrollTop = self.bounds.origin.y + self.contentInset.top; - -#if !TARGET_OS_TV - // If the RefreshControl is refreshing, remove it's height so sticky headers are - // positioned properly when scrolling down while refreshing. - if (_rctRefreshControl != nil && _rctRefreshControl.refreshing) { - scrollTop -= _rctRefreshControl.frame.size.height; - } -#endif - - // Find the section headers that need to be docked - __block UIView *previousHeader = nil; - __block UIView *currentHeader = nil; - __block UIView *nextHeader = nil; - NSUInteger subviewCount = contentView.reactSubviews.count; - [_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock: - ^(NSUInteger idx, BOOL *stop) { - - // If the subviews are out of sync with the sticky header indices don't - // do anything. - if (idx >= subviewCount) { - *stop = YES; - return; - } - - UIView *header = contentView.reactSubviews[idx]; - - // If nextHeader not yet found, search for docked headers - if (!nextHeader) { - CGFloat height = header.bounds.size.height; - CGFloat top = header.center.y - height * header.layer.anchorPoint.y; - if (top > scrollTop) { - nextHeader = header; - } else { - previousHeader = currentHeader; - currentHeader = header; - } - } - - // Reset transforms for header views - header.transform = CGAffineTransformIdentity; - header.layer.zPosition = ZINDEX_DEFAULT; - - }]; - - // If no docked header, bail out - if (!currentHeader) { - return; - } - - // Adjust current header to hug the top of the screen - CGFloat currentFrameHeight = currentHeader.bounds.size.height; - CGFloat currentFrameTop = currentHeader.center.y - currentFrameHeight * currentHeader.layer.anchorPoint.y; - CGFloat yOffset = scrollTop - currentFrameTop; - if (nextHeader) { - // The next header nudges the current header out of the way when it reaches - // the top of the screen - CGFloat nextFrameHeight = nextHeader.bounds.size.height; - CGFloat nextFrameTop = nextHeader.center.y - nextFrameHeight * nextHeader.layer.anchorPoint.y; - CGFloat overlap = currentFrameHeight - (nextFrameTop - scrollTop); - yOffset -= MAX(0, overlap); - } - currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset); - currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER; - _dockedHeaderView = currentHeader; - - if (previousHeader) { - // The previous header sits right above the currentHeader's initial position - // so it scrolls away nicely once the currentHeader has locked into place - CGFloat previousFrameHeight = previousHeader.bounds.size.height; - CGFloat targetCenter = currentFrameTop - previousFrameHeight * (1.0 - previousHeader.layer.anchorPoint.y); - yOffset = targetCenter - previousHeader.center.y; - previousHeader.transform = CGAffineTransformMakeTranslation(0, yOffset); - previousHeader.layer.zPosition = ZINDEX_STICKY_HEADER; - } -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - if (_dockedHeaderView && [self pointInside:point withEvent:event]) { - CGPoint convertedPoint = [_dockedHeaderView convertPoint:point fromView:self]; - UIView *hitView = [_dockedHeaderView hitTest:convertedPoint withEvent:event]; - if (hitView) { - return hitView; - } - } - return [super hitTest:point withEvent:event]; -} - static inline BOOL isRectInvalid(CGRect rect) { return isnan(rect.origin.x) || isinf(rect.origin.x) || isnan(rect.origin.y) || isinf(rect.origin.y) || @@ -524,18 +425,6 @@ - (void)setCenterContent:(BOOL)centerContent _scrollView.centerContent = centerContent; } -- (NSIndexSet *)stickyHeaderIndices -{ - return _scrollView.stickyHeaderIndices; -} - -- (void)setStickyHeaderIndices:(NSIndexSet *)headerIndices -{ - RCTAssert(_scrollView.contentSize.width <= self.frame.size.width, - @"sticky headers are not supported with horizontal scrolled views"); - _scrollView.stickyHeaderIndices = headerIndices; -} - - (void)setClipsToBounds:(BOOL)clipsToBounds { super.clipsToBounds = clipsToBounds; @@ -699,7 +588,6 @@ - (void)removeScrollListener:(NSObject *)scrollListener - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [_scrollView dockClosestSectionHeader]; [self updateClippedSubviews]; NSTimeInterval now = CACurrentMediaTime(); @@ -962,26 +850,6 @@ - (void)reactBridgeDidFinishTransaction _scrollView.contentSize = contentSize; _scrollView.contentOffset = newOffset; } - - if (RCT_DEBUG) { - // Validate that sticky headers are not out of range. - NSUInteger subviewCount = _scrollView.contentView.reactSubviews.count; - NSUInteger lastIndex = NSNotFound; - if (_scrollView.stickyHeaderIndices != nil) { - lastIndex = _scrollView.stickyHeaderIndices.lastIndex; - } - if (lastIndex != NSNotFound && lastIndex >= subviewCount) { - RCTLogWarn(@"Sticky header index %zd was outside the range {0, %zd}", - lastIndex, subviewCount); - } - } -} - -- (void)didSetProps:(NSArray *)changedProps -{ - if ([changedProps containsObject:@"stickyHeaderIndices"]) { - [_scrollView dockClosestSectionHeader]; - } } // Note: setting several properties of UIScrollView has the effect of diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 62f5a41abf8cb6..7b3db9fad22cf1 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -67,7 +67,6 @@ - (UIView *)view #endif RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL) RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL) -RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSIndexSet) RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval) RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 6bc9966384dcc5..61154b1d6e4aab 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -130,17 +130,18 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry @property (nonatomic, assign) YGJustify justifyContent; @property (nonatomic, assign) YGAlign alignSelf; @property (nonatomic, assign) YGAlign alignItems; +@property (nonatomic, assign) YGAlign alignContent; @property (nonatomic, assign) YGPositionType position; @property (nonatomic, assign) YGWrap flexWrap; +@property (nonatomic, assign) YGDisplay display; +@property (nonatomic, assign) float flex; @property (nonatomic, assign) float flexGrow; @property (nonatomic, assign) float flexShrink; @property (nonatomic, assign) YGValue flexBasis; @property (nonatomic, assign) float aspectRatio; -- (void)setFlex:(float)flex; - /** * z-index, used to override sibling order in the view */ diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 8a82794edeb7fb..8de41aaca7ff7f 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -70,20 +70,42 @@ static void RCTPrint(YGNodeRef node) break; \ } -#define DEFINE_PROCESS_META_PROPS(type) \ -static void RCTProcessMetaProps##type(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { \ - RCT_SET_YGVALUE(metaProps[META_PROP_LEFT], YGNodeStyleSet##type, node, YGEdgeStart); \ - RCT_SET_YGVALUE(metaProps[META_PROP_RIGHT], YGNodeStyleSet##type, node, YGEdgeEnd); \ - RCT_SET_YGVALUE(metaProps[META_PROP_TOP], YGNodeStyleSet##type, node, YGEdgeTop); \ - RCT_SET_YGVALUE(metaProps[META_PROP_BOTTOM], YGNodeStyleSet##type, node, YGEdgeBottom); \ - RCT_SET_YGVALUE(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSet##type, node, YGEdgeHorizontal); \ - RCT_SET_YGVALUE(metaProps[META_PROP_VERTICAL], YGNodeStyleSet##type, node, YGEdgeVertical); \ - RCT_SET_YGVALUE(metaProps[META_PROP_ALL], YGNodeStyleSet##type, node, YGEdgeAll); \ +#define RCT_SET_YGVALUE_AUTO(ygvalue, setter, ...) \ +switch (ygvalue.unit) { \ + case YGUnitAuto: \ + setter##Auto(__VA_ARGS__); \ + break; \ + case YGUnitUndefined: \ + setter(__VA_ARGS__, YGUndefined); \ + break; \ + case YGUnitPoint: \ + setter(__VA_ARGS__, ygvalue.value); \ + break; \ + case YGUnitPercent: \ + setter##Percent(__VA_ARGS__, ygvalue.value); \ + break; \ +} + +static void RCTProcessMetaPropsPadding(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { + RCT_SET_YGVALUE(metaProps[META_PROP_LEFT], YGNodeStyleSetPadding, node, YGEdgeStart); + RCT_SET_YGVALUE(metaProps[META_PROP_RIGHT], YGNodeStyleSetPadding, node, YGEdgeEnd); + RCT_SET_YGVALUE(metaProps[META_PROP_TOP], YGNodeStyleSetPadding, node, YGEdgeTop); + RCT_SET_YGVALUE(metaProps[META_PROP_BOTTOM], YGNodeStyleSetPadding, node, YGEdgeBottom); + RCT_SET_YGVALUE(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSetPadding, node, YGEdgeHorizontal); + RCT_SET_YGVALUE(metaProps[META_PROP_VERTICAL], YGNodeStyleSetPadding, node, YGEdgeVertical); + RCT_SET_YGVALUE(metaProps[META_PROP_ALL], YGNodeStyleSetPadding, node, YGEdgeAll); +} + +static void RCTProcessMetaPropsMargin(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_LEFT], YGNodeStyleSetMargin, node, YGEdgeStart); + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_RIGHT], YGNodeStyleSetMargin, node, YGEdgeEnd); + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_TOP], YGNodeStyleSetMargin, node, YGEdgeTop); + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_BOTTOM], YGNodeStyleSetMargin, node, YGEdgeBottom); + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSetMargin, node, YGEdgeHorizontal); + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_VERTICAL], YGNodeStyleSetMargin, node, YGEdgeVertical); + RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_ALL], YGNodeStyleSetMargin, node, YGEdgeAll); } -DEFINE_PROCESS_META_PROPS(Padding); -DEFINE_PROCESS_META_PROPS(Margin); - static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { YGNodeStyleSetBorder(node, YGEdgeStart, metaProps[META_PROP_LEFT].value); YGNodeStyleSetBorder(node, YGEdgeEnd, metaProps[META_PROP_RIGHT].value); @@ -522,9 +544,19 @@ - (float)border##prop##Width \ RCT_BORDER_PROPERTY(Right, RIGHT) // Dimensions - #define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ - (void)set##setProp:(YGValue)value \ +{ \ + RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSet##cssProp, _yogaNode); \ + [self dirtyText]; \ +} \ +- (YGValue)getProp \ +{ \ + return YGNodeStyleGet##cssProp(_yogaNode); \ +} + +#define RCT_MIN_MAX_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ +- (void)set##setProp:(YGValue)value \ { \ RCT_SET_YGVALUE(value, YGNodeStyleSet##cssProp, _yogaNode); \ [self dirtyText]; \ @@ -536,10 +568,10 @@ - (YGValue)getProp \ RCT_DIMENSION_PROPERTY(Width, width, Width) RCT_DIMENSION_PROPERTY(Height, height, Height) -RCT_DIMENSION_PROPERTY(MinWidth, minWidth, MinWidth) -RCT_DIMENSION_PROPERTY(MinHeight, minHeight, MinHeight) -RCT_DIMENSION_PROPERTY(MaxWidth, maxWidth, MaxWidth) -RCT_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight) +RCT_MIN_MAX_DIMENSION_PROPERTY(MinWidth, minWidth, MinWidth) +RCT_MIN_MAX_DIMENSION_PROPERTY(MinHeight, minHeight, MinHeight) +RCT_MIN_MAX_DIMENSION_PROPERTY(MaxWidth, maxWidth, MaxWidth) +RCT_MIN_MAX_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight) // Position @@ -637,14 +669,9 @@ - (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize // Flex -- (void)setFlex:(float)value -{ - YGNodeStyleSetFlex(_yogaNode, value); -} - - (void)setFlexBasis:(YGValue)value { - RCT_SET_YGVALUE(value, YGNodeStyleSetFlexBasis, _yogaNode); + RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSetFlexBasis, _yogaNode); } - (YGValue)flexBasis @@ -662,15 +689,18 @@ - (type)getProp \ return YGNodeStyleGet##cssProp(_yogaNode); \ } +RCT_STYLE_PROPERTY(Flex, flex, Flex, float) RCT_STYLE_PROPERTY(FlexGrow, flexGrow, FlexGrow, float) RCT_STYLE_PROPERTY(FlexShrink, flexShrink, FlexShrink, float) RCT_STYLE_PROPERTY(FlexDirection, flexDirection, FlexDirection, YGFlexDirection) RCT_STYLE_PROPERTY(JustifyContent, justifyContent, JustifyContent, YGJustify) RCT_STYLE_PROPERTY(AlignSelf, alignSelf, AlignSelf, YGAlign) RCT_STYLE_PROPERTY(AlignItems, alignItems, AlignItems, YGAlign) +RCT_STYLE_PROPERTY(AlignContent, alignContent, AlignContent, YGAlign) RCT_STYLE_PROPERTY(Position, position, PositionType, YGPositionType) RCT_STYLE_PROPERTY(FlexWrap, flexWrap, FlexWrap, YGWrap) RCT_STYLE_PROPERTY(Overflow, overflow, Overflow, YGOverflow) +RCT_STYLE_PROPERTY(Display, display, Display, YGDisplay) RCT_STYLE_PROPERTY(Direction, direction, Direction, YGDirection) RCT_STYLE_PROPERTY(AspectRatio, aspectRatio, AspectRatio, float) diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index d551781d2471af..7bbc9741075acb 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -305,10 +305,12 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_EXPORT_SHADOW_PROPERTY(justifyContent, YGJustify) RCT_EXPORT_SHADOW_PROPERTY(alignItems, YGAlign) RCT_EXPORT_SHADOW_PROPERTY(alignSelf, YGAlign) +RCT_EXPORT_SHADOW_PROPERTY(alignContent, YGAlign) RCT_EXPORT_SHADOW_PROPERTY(position, YGPositionType) RCT_EXPORT_SHADOW_PROPERTY(aspectRatio, float) RCT_EXPORT_SHADOW_PROPERTY(overflow, YGOverflow) +RCT_EXPORT_SHADOW_PROPERTY(display, YGDisplay) RCT_EXPORT_SHADOW_PROPERTY(onLayout, RCTDirectEventBlock) diff --git a/React/folly.xcconfig b/React/folly.xcconfig index 481207e2ea042d..37a4baa2979b2d 100644 --- a/React/folly.xcconfig +++ b/React/folly.xcconfig @@ -6,5 +6,5 @@ // Copyright © 2017 Facebook. All rights reserved. // -HEADER_SEARCH_PATHS = $(SRCROOT)/../third-party/boost_1_57_0 $(SRCROOT)/../third-party/folly-2016.09.26.00 $(SRCROOT)/../third-party/glog-0.3.4/src +HEADER_SEARCH_PATHS = $(SRCROOT)/../third-party/boost_1_63_0 $(SRCROOT)/../third-party/folly-2016.09.26.00 $(SRCROOT)/../third-party/glog-0.3.4/src OTHER_CFLAGS = -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 diff --git a/ReactAndroid/DEFS b/ReactAndroid/DEFS index 872f103e9f7284..632167ab730677 100644 --- a/ReactAndroid/DEFS +++ b/ReactAndroid/DEFS @@ -30,10 +30,11 @@ def react_native_integration_tests_target(path): def react_native_dep(path): return '//ReactAndroid/src/main/' + path -JSC_DEPS = [ +JSC_INTERNAL_DEPS = [ '//native/third-party/jsc:jsc', '//native/third-party/jsc:jsc_legacy_profiler', ] +JSC_DEPS = JSC_INTERNAL_DEPS YOGA_TARGET = '//ReactAndroid/src/main/java/com/facebook:yoga' FBGLOGINIT_TARGET = '//ReactAndroid/src/main/jni/first-party/fbgloginit:fbgloginit' diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index 132bd7e6782652..cebce68f6ed744 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -35,16 +35,16 @@ task createNativeDepsDirectories { } task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { - src 'https://github.com/react-native-community/boost-for-react-native/releases/download/v1.57.0-1/boost_1_57_0.tar.gz' + src 'https://github.com/react-native-community/boost-for-react-native/releases/download/v1.63.0-0/boost_1_63_0.tar.gz' onlyIfNewer true overwrite false - dest new File(downloadsDir, 'boost_1_57_0.tar.gz') + dest new File(downloadsDir, 'boost_1_63_0.tar.gz') } task prepareBoost(dependsOn: boostPath ? [] : [downloadBoost], type: Copy) { from boostPath ?: tarTree(resources.gzip(downloadBoost.dest)) from 'src/main/jni/third-party/boost/Android.mk' - include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk' + include 'Android.mk', 'boost_1_63_0/boost/**/*.hpp' includeEmptyDirs = false into "$thirdPartyNdkDir/boost" } @@ -275,7 +275,6 @@ dependencies { compile fileTree(dir: 'src/main/third-party/java/infer-annotations/', include: ['*.jar']) compile 'javax.inject:javax.inject:1' compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.android.support:recyclerview-v7:23.4.0' compile 'com.facebook.fbui.textlayoutbuilder:textlayoutbuilder:1.0.0' compile 'com.facebook.fresco:fresco:1.0.1' compile 'com.facebook.fresco:imagepipeline-okhttp3:1.0.1' diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java index 11c7bc230ed8fa..8054dff33d8161 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java @@ -23,6 +23,7 @@ import com.facebook.react.bridge.JavaScriptModuleRegistry; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; import com.facebook.react.cxxbridge.CatalystInstanceImpl; @@ -36,10 +37,9 @@ public class ReactTestHelper { private static class DefaultReactTestFactory implements ReactTestFactory { private static class ReactInstanceEasyBuilderImpl implements ReactInstanceEasyBuilder { - private final NativeModuleRegistryBuilder mNativeModuleRegistryBuilder = - new NativeModuleRegistryBuilder(null, false); private final JavaScriptModuleRegistry.Builder mJSModuleRegistryBuilder = new JavaScriptModuleRegistry.Builder(); + private NativeModuleRegistryBuilder mNativeModuleRegistryBuilder; private @Nullable Context mContext; @@ -51,6 +51,12 @@ public ReactInstanceEasyBuilder setContext(Context context) { @Override public ReactInstanceEasyBuilder addNativeModule(NativeModule nativeModule) { + if (mNativeModuleRegistryBuilder == null) { + mNativeModuleRegistryBuilder = new NativeModuleRegistryBuilder( + (ReactApplicationContext) mContext, + null, + false); + } mNativeModuleRegistryBuilder.addNativeModule(nativeModule); return this; } @@ -63,6 +69,12 @@ public ReactInstanceEasyBuilder addJSModule(Class moduleInterfaceClass) { @Override public CatalystInstance build() { + if (mNativeModuleRegistryBuilder == null) { + mNativeModuleRegistryBuilder = new NativeModuleRegistryBuilder( + (ReactApplicationContext) mContext, + null, + false); + } JavaScriptExecutor executor = null; try { executor = new JSCJavaScriptExecutor.Factory(new WritableNativeMap()).create(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/CompositeReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CompositeReactPackage.java index 8f78569131ddc7..f2d6e77763b71d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CompositeReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CompositeReactPackage.java @@ -25,7 +25,7 @@ * {@code CompositeReactPackage} allows to create a single package composed of views and modules * from several other packages. */ -public class CompositeReactPackage implements ReactPackage { +public class CompositeReactPackage extends ReactInstancePackage { private final List mChildReactPackages = new ArrayList<>(); @@ -49,6 +49,7 @@ public CompositeReactPackage(ReactPackage arg1, ReactPackage arg2, ReactPackage. */ @Override public List createNativeModules(ReactApplicationContext reactContext) { + // This is for backward compatibility. final Map moduleMap = new HashMap<>(); for (ReactPackage reactPackage: mChildReactPackages) { for (NativeModule nativeModule: reactPackage.createNativeModules(reactContext)) { @@ -58,6 +59,31 @@ public List createNativeModules(ReactApplicationContext reactConte return new ArrayList(moduleMap.values()); } + /** + * {@inheritDoc} + */ + @Override + public List createNativeModules( + ReactApplicationContext reactContext, + ReactInstanceManager reactInstanceManager) { + final Map moduleMap = new HashMap<>(); + for (ReactPackage reactPackage: mChildReactPackages) { + List nativeModules; + if (reactPackage instanceof ReactInstancePackage) { + ReactInstancePackage reactInstancePackage = (ReactInstancePackage) reactPackage; + nativeModules = reactInstancePackage.createNativeModules( + reactContext, + reactInstanceManager); + } else { + nativeModules = reactPackage.createNativeModules(reactContext); + } + for (NativeModule nativeModule: nativeModules) { + moduleMap.put(nativeModule.getName(), nativeModule); + } + } + return new ArrayList(moduleMap.values()); + } + /** * {@inheritDoc} */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.java index 2e53bad4e70d88..3e8532298c5f1e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.java @@ -16,6 +16,8 @@ import com.facebook.react.bridge.ModuleSpec; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactMarker; +import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.react.module.model.ReactModuleInfoProvider; import com.facebook.react.uimanager.ViewManager; import com.facebook.systrace.Systrace; @@ -69,14 +71,20 @@ public abstract List getNativeModules( public final List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); for (ModuleSpec holder : getNativeModules(reactContext)) { + NativeModule nativeModule; SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createNativeModule") .arg("module", holder.getType()) .flush(); try { - modules.add(holder.getProvider().get()); + ReactMarker.logMarker( + ReactMarkerConstants.CREATE_MODULE_START, + holder.getType().getSimpleName()); + nativeModule = holder.getProvider().get(); + ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END); } finally { Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } + modules.add(nativeModule); } return modules; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/NativeModuleRegistryBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/NativeModuleRegistryBuilder.java index d7a6e3f08dd84a..68d53ef5dc0663 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/NativeModuleRegistryBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/NativeModuleRegistryBuilder.java @@ -26,6 +26,7 @@ public class NativeModuleRegistryBuilder { private final ReactApplicationContext mReactApplicationContext; + private final ReactInstanceManager mReactInstanceManager; private final boolean mLazyNativeModulesEnabled; private final Map, ModuleHolder> mModules = new HashMap<>(); @@ -33,8 +34,10 @@ public class NativeModuleRegistryBuilder { public NativeModuleRegistryBuilder( ReactApplicationContext reactApplicationContext, + ReactInstanceManager reactInstanceManager, boolean lazyNativeModulesEnabled) { mReactApplicationContext = reactApplicationContext; + mReactInstanceManager = reactInstanceManager; mLazyNativeModulesEnabled = lazyNativeModulesEnabled; } @@ -61,7 +64,7 @@ public void processPackage(ReactPackage reactPackage) { } ReactMarker.logMarker( ReactMarkerConstants.CREATE_MODULE_START, - moduleSpec.getType().getSimpleName()); + moduleSpec.getType().getName()); NativeModule module = moduleSpec.getProvider().get(); ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END); moduleHolder = new ModuleHolder(module); @@ -94,7 +97,16 @@ public void processPackage(ReactPackage reactPackage) { ReactConstants.TAG, reactPackage.getClass().getSimpleName() + " is not a LazyReactPackage, falling back to old version."); - for (NativeModule nativeModule : reactPackage.createNativeModules(mReactApplicationContext)) { + List nativeModules; + if (reactPackage instanceof ReactInstancePackage) { + ReactInstancePackage reactInstancePackage = (ReactInstancePackage) reactPackage; + nativeModules = reactInstancePackage.createNativeModules( + mReactApplicationContext, + mReactInstanceManager); + } else { + nativeModules = reactPackage.createNativeModules(mReactApplicationContext); + } + for (NativeModule nativeModule : nativeModules) { addNativeModule(nativeModule); } } @@ -127,6 +139,9 @@ public NativeModuleRegistry build() { } } - return new NativeModuleRegistry(mModules, batchCompleteListenerModules); + return new NativeModuleRegistry( + mReactApplicationContext, + mModules, + batchCompleteListenerModules); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index c44678ceefa9ea..32a4f92c93802a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -53,15 +53,15 @@ import com.facebook.react.cxxbridge.NativeModuleRegistry; import com.facebook.react.cxxbridge.ProxyJavaScriptExecutor; import com.facebook.react.cxxbridge.UiThreadUtil; -import com.facebook.react.devsupport.interfaces.DevSupportManager; -import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.facebook.react.devsupport.RedBoxHandler; +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.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; -import com.facebook.react.modules.appregistry.AppRegistry; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.UIImplementationProvider; import com.facebook.react.uimanager.UIManagerModule; @@ -82,7 +82,9 @@ import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START; import static com.facebook.react.bridge.ReactMarkerConstants.SETUP_REACT_CONTEXT_END; import static com.facebook.react.bridge.ReactMarkerConstants.SETUP_REACT_CONTEXT_START; +import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_APPS; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; +import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JSC_CALLS; /** * This class is managing instances of {@link CatalystInstance}. It exposes a way to configure @@ -860,6 +862,7 @@ private ReactApplicationContext createReactContext( final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext); NativeModuleRegistryBuilder nativeModuleRegistryBuilder = new NativeModuleRegistryBuilder( reactContext, + this, mLazyNativeModulesEnabled); JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder(); if (mUseDeveloperSupport) { @@ -930,6 +933,9 @@ private ReactApplicationContext createReactContext( if (mBridgeIdleDebugListener != null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } + if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JSC_CALLS)) { + catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true"); + } reactContext.initializeWithInstance(catalystInstance); catalystInstance.runJSBundle(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstancePackage.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstancePackage.java new file mode 100644 index 00000000000000..24de64936a4a99 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstancePackage.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react; + +import java.util.List; + +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; + +/** + * A simple wrapper for ReactPackage to make it aware of its {@link ReactInstanceManager} + * when creating native modules. This is useful when the package needs to ask + * the instance manager for more information, like {@link DevSupportManager}. + * + * TODO(t11394819): Consolidate this with LazyReactPackage + */ +public abstract class ReactInstancePackage implements ReactPackage { + + public abstract List createNativeModules( + ReactApplicationContext reactContext, + ReactInstanceManager reactInstanceManager); + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + throw new RuntimeException("ReactInstancePackage must be passed in the ReactInstanceManager."); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index c5e2aa04f1e44e..c16e2acd0bcbda 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -11,6 +11,8 @@ import javax.annotation.Nullable; +import java.util.ArrayList; + import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; @@ -27,8 +29,6 @@ import com.facebook.react.uimanager.GuardedFrameCallback; import com.facebook.react.uimanager.UIManagerModule; -import java.util.ArrayList; - /** * Module that exposes interface for creating and managing animated nodes on the "native" side. * @@ -90,43 +90,50 @@ public NativeAnimatedModule(ReactApplicationContext reactContext) { @Override public void initialize() { - // Safe to acquire choreographer here, as initialize() is invoked from UI thread. - mReactChoreographer = ReactChoreographer.getInstance(); - - ReactApplicationContext reactCtx = getReactApplicationContext(); - UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class); + getReactApplicationContext().addLifecycleEventListener(this); + } - final NativeAnimatedNodesManager nodesManager = new NativeAnimatedNodesManager(uiManager); - mAnimatedFrameCallback = new GuardedFrameCallback(reactCtx) { - @Override - protected void doFrameGuarded(final long frameTimeNanos) { + @Override + public void onHostResume() { + if (mReactChoreographer == null) { + // Safe to acquire choreographer here, as onHostResume() is invoked from UI thread. + mReactChoreographer = ReactChoreographer.getInstance(); + + ReactApplicationContext reactCtx = getReactApplicationContext(); + UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class); + + final NativeAnimatedNodesManager nodesManager = new NativeAnimatedNodesManager(uiManager); + mAnimatedFrameCallback = new GuardedFrameCallback(reactCtx) { + @Override + protected void doFrameGuarded(final long frameTimeNanos) { + + ArrayList operations; + synchronized (mOperationsCopyLock) { + operations = mReadyOperations; + mReadyOperations = null; + } - ArrayList operations; - synchronized (mOperationsCopyLock) { - operations = mReadyOperations; - mReadyOperations = null; - } + if (operations != null) { + for (int i = 0, size = operations.size(); i < size; i++) { + operations.get(i).execute(nodesManager); + } + } - if (operations != null) { - for (int i = 0, size = operations.size(); i < size; i++) { - operations.get(i).execute(nodesManager); + if (nodesManager.hasActiveAnimations()) { + nodesManager.runUpdates(frameTimeNanos); } - } - if (nodesManager.hasActiveAnimations()) { - nodesManager.runUpdates(frameTimeNanos); + // TODO: Would be great to avoid adding this callback in case there are no active animations + // and no outstanding tasks on the operations queue. Apparently frame callbacks can only + // be posted from the UI thread and therefore we cannot schedule them directly from + // @ReactMethod methods + Assertions.assertNotNull(mReactChoreographer).postFrameCallback( + ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, + mAnimatedFrameCallback); } - - // TODO: Would be great to avoid adding this callback in case there are no active animations - // and no outstanding tasks on the operations queue. Apparently frame callbacks can only - // be posted from the UI thread and therefore we cannot schedule them directly from - // @ReactMethod methods - Assertions.assertNotNull(mReactChoreographer).postFrameCallback( - ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, - mAnimatedFrameCallback); - } - }; - reactCtx.addLifecycleEventListener(this); + }; + } + enqueueFrameCallback(); } @Override @@ -150,11 +157,6 @@ public void onBatchComplete() { } } - @Override - public void onHostResume() { - enqueueFrameCallback(); - } - @Override public void onHostPause() { clearFrameCallback(); @@ -351,11 +353,11 @@ public void execute(NativeAnimatedNodesManager animatedNodesManager) { } @ReactMethod - public void removeAnimatedEventFromView(final int viewTag, final String eventName) { + public void removeAnimatedEventFromView(final int viewTag, final String eventName, final int animatedValueTag) { mOperations.add(new UIThreadOperation() { @Override public void execute(NativeAnimatedNodesManager animatedNodesManager) { - animatedNodesManager.removeAnimatedEventFromView(viewTag, eventName); + animatedNodesManager.removeAnimatedEventFromView(viewTag, eventName, animatedValueTag); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 12c7f640b95ab5..6251b8f4dd5528 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -31,7 +31,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Queue; @@ -55,12 +57,14 @@ private final SparseArray mAnimatedNodes = new SparseArray<>(); private final SparseArray mActiveAnimations = new SparseArray<>(); private final SparseArray mUpdatedNodes = new SparseArray<>(); - private final Map mEventDrivers = new HashMap<>(); + // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time + // there will be only one driver per mapping so all code code should be optimized around that. + private final Map> mEventDrivers = new HashMap<>(); private final Map> mCustomEventTypes; private final UIImplementation mUIImplementation; private int mAnimatedGraphBFSColor = 0; - // Used to avoid allocating a new array on every frame in runUpdates. - private final List mRunUpdateNodeList = new ArrayList<>(); + // Used to avoid allocating a new array on every frame in `runUpdates` and `onEventDispatch`. + private final List mRunUpdateNodeList = new LinkedList<>(); public NativeAnimatedNodesManager(UIManagerModule uiManager) { mUIImplementation = uiManager.getUIImplementation(); @@ -312,11 +316,32 @@ public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap ev } EventAnimationDriver event = new EventAnimationDriver(pathList, (ValueAnimatedNode) node); - mEventDrivers.put(viewTag + eventName, event); + String key = viewTag + eventName; + if (mEventDrivers.containsKey(key)) { + mEventDrivers.get(key).add(event); + } else { + List drivers = new ArrayList<>(1); + drivers.add(event); + mEventDrivers.put(key, drivers); + } } - public void removeAnimatedEventFromView(int viewTag, String eventName) { - mEventDrivers.remove(viewTag + eventName); + public void removeAnimatedEventFromView(int viewTag, String eventName, int animatedValueTag) { + String key = viewTag + eventName; + if (mEventDrivers.containsKey(key)) { + List driversForKey = mEventDrivers.get(key); + if (driversForKey.size() == 1) { + mEventDrivers.remove(viewTag + eventName); + } else { + ListIterator it = driversForKey.listIterator(); + while (it.hasNext()) { + if (it.next().mValueNode.mTag == animatedValueTag) { + it.remove(); + break; + } + } + } + } } @Override @@ -334,11 +359,14 @@ public void onEventDispatch(Event event) { eventName = customEventType.get("registrationName"); } - EventAnimationDriver eventDriver = mEventDrivers.get(event.getViewTag() + eventName); - if (eventDriver != null) { - event.dispatch(eventDriver); - - updateNodes(Collections.singletonList((AnimatedNode) eventDriver.mValueNode)); + List driversForKey = mEventDrivers.get(event.getViewTag() + eventName); + if (driversForKey != null) { + for (EventAnimationDriver driver : driversForKey) { + event.dispatch(driver); + mRunUpdateNodeList.add(driver.mValueNode); + } + updateNodes(mRunUpdateNodeList); + mRunUpdateNodeList.clear(); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleLogger.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleLogger.java deleted file mode 100644 index 535b71ee3d81ae..00000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleLogger.java +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.bridge; - -/** - * Interface on native modules for the bridge to call for TTI start and end markers. - */ -public interface NativeModuleLogger { - - void startConstantsMapConversion(); - void endConstantsMapConversion(); -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index f83c2ba56b9070..b403b2c9ff645a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -12,8 +12,6 @@ import javax.annotation.Nullable; import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.CopyOnWriteArraySet; import android.app.Activity; @@ -28,10 +26,6 @@ import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.LifecycleState; -import static com.facebook.react.common.LifecycleState.BEFORE_CREATE; -import static com.facebook.react.common.LifecycleState.BEFORE_RESUME; -import static com.facebook.react.common.LifecycleState.RESUMED; - /** * Abstract ContextWrapper for Android application or activity {@link Context} and * {@link CatalystInstance} @@ -180,20 +174,6 @@ public void removeLifecycleEventListener(LifecycleEventListener listener) { mLifecycleEventListeners.remove(listener); } - public Map> getAllPerformanceCounters() { - Map> totalPerfMap = - new HashMap<>(); - if (mCatalystInstance != null) { - for (NativeModule nativeModule : mCatalystInstance.getNativeModules()) { - if (nativeModule instanceof PerformanceCounter) { - PerformanceCounter perfCounterModule = (PerformanceCounter) nativeModule; - totalPerfMap.put(nativeModule.getName(), perfCounterModule.getPerformanceCounters()); - } - } - } - return totalPerfMap; - } - public void addActivityEventListener(ActivityEventListener listener) { mActivityEventListeners.add(listener); } @@ -209,6 +189,7 @@ public void onHostResume(@Nullable Activity activity) { UiThreadUtil.assertOnUiThread(); mLifecycleState = LifecycleState.RESUMED; mCurrentActivity = new WeakReference(activity); + ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_START); for (LifecycleEventListener listener : mLifecycleEventListeners) { try { listener.onHostResume(); @@ -216,6 +197,7 @@ public void onHostResume(@Nullable Activity activity) { handleException(e); } } + ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_END); } public void onNewIntent(@Nullable Activity activity, Intent intent) { @@ -236,6 +218,7 @@ public void onNewIntent(@Nullable Activity activity, Intent intent) { public void onHostPause() { UiThreadUtil.assertOnUiThread(); mLifecycleState = LifecycleState.BEFORE_RESUME; + ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_PAUSE_START); for (LifecycleEventListener listener : mLifecycleEventListeners) { try { listener.onHostPause(); @@ -243,6 +226,7 @@ public void onHostPause() { handleException(e); } } + ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_PAUSE_END); } /** @@ -301,6 +285,10 @@ public void assertOnNativeModulesQueueThread() { Assertions.assertNotNull(mNativeModulesMessageQueueThread).assertIsOnThread(); } + public void assertOnNativeModulesQueueThread(String message) { + Assertions.assertNotNull(mNativeModulesMessageQueueThread).assertIsOnThread(message); + } + public boolean isOnNativeModulesQueueThread() { return Assertions.assertNotNull(mNativeModulesMessageQueueThread).isOnThread(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java index 0edb08d8e428bc..996ef7e4f90e55 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java @@ -60,4 +60,12 @@ public class ReactMarkerConstants { "CREATE_I18N_ASSETS_MODULE_END"; public static final String GET_CONSTANTS_START = "GET_CONSTANTS_START"; public static final String GET_CONSTANTS_END = "GET_CONSTANTS_END"; + public static final String INITIALIZE_MODULE_START = "INITIALIZE_MODULE_START"; + public static final String INITIALIZE_MODULE_END = "INITIALIZE_MODULE_END"; + public static final String ON_HOST_RESUME_START = "ON_HOST_RESUME_START"; + public static final String ON_HOST_RESUME_END = "ON_HOST_RESUME_END"; + public static final String ON_HOST_PAUSE_START = "ON_HOST_PAUSE_START"; + public static final String ON_HOST_PAUSE_END = "ON_HOST_PAUSE_END"; + public static final String CONVERT_CONSTANTS_START = "CONVERT_CONSTANTS_START"; + public static final String CONVERT_CONSTANTS_END = "CONVERT_CONSTANTS_END"; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java index 84cc9accb2ba50..fca4ddc18ac914 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java @@ -46,6 +46,13 @@ public interface MessageQueueThread { @DoNotStrip void assertIsOnThread(); + /** + * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an + * {@link AssertionError}) if the assertion fails. The given message is appended to the error. + */ + @DoNotStrip + void assertIsOnThread(String message); + /** * Quits this MessageQueueThread. If called from this MessageQueueThread, this will be the last * thing the thread runs. If called from a separate thread, this will block until the thread can diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java index 4687fc140b4f27..f78988ca8fc120 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java @@ -99,6 +99,18 @@ public void assertIsOnThread() { SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage); } + /** + * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an + * {@link AssertionError}) if the assertion fails. + */ + @DoNotStrip + @Override + public void assertIsOnThread(String message) { + SoftAssertions.assertCondition( + isOnThread(), + new StringBuilder().append(mAssertionErrorMessage).append(" ").append(message).toString()); + } + /** * Quits this queue's Looper. If that Looper was running on a different Thread than the current * Thread, also waits for the last message being processed to finish and the Thread to die. diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java index 14e5b774bed7bd..0b564b42a9649b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java @@ -20,7 +20,9 @@ import android.content.res.AssetManager; import com.facebook.common.logging.FLog; +import com.facebook.infer.annotation.Assertions; import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.ExecutorToken; import com.facebook.react.bridge.JavaScriptModule; @@ -30,15 +32,13 @@ import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; -import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.bridge.queue.MessageQueueThread; import com.facebook.react.bridge.queue.QueueThreadExceptionHandler; +import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl; import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec; -import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; -import com.facebook.infer.annotation.Assertions; import com.facebook.soloader.SoLoader; import com.facebook.systrace.Systrace; import com.facebook.systrace.TraceListener; @@ -299,7 +299,12 @@ public void destroy() { // TODO: tell all APIs to shut down mDestroyed = true; mHybridData.resetNative(); - mJavaRegistry.notifyCatalystInstanceDestroy(); + mReactQueueConfiguration.getNativeModulesQueueThread().runOnQueue(new Runnable() { + @Override + public void run() { + mJavaRegistry.notifyCatalystInstanceDestroy(); + } + }); boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0); if (!wasIdle && !mBridgeIdleListeners.isEmpty()) { for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { @@ -333,7 +338,12 @@ public void initialize() { mAcceptCalls, "RunJSBundle hasn't completed."); mInitialized = true; - mJavaRegistry.notifyCatalystInstanceInitialized(); + mReactQueueConfiguration.getNativeModulesQueueThread().runOnQueue(new Runnable() { + @Override + public void run() { + mJavaRegistry.notifyCatalystInstanceInitialized(); + } + }); } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java index eda5c23124ae6f..e0e07af6b76825 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java @@ -19,7 +19,6 @@ import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.ExecutorToken; import com.facebook.react.bridge.NativeArray; -import com.facebook.react.bridge.NativeModuleLogger; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReadableNativeArray; @@ -28,6 +27,8 @@ import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; +import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_END; +import static com.facebook.react.bridge.ReactMarkerConstants.CONVERT_CONSTANTS_START; import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_END; import static com.facebook.react.bridge.ReactMarkerConstants.GET_CONSTANTS_START; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; @@ -109,9 +110,7 @@ public NativeArray getConstants() { SystraceMessage.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "WritableNativeMap constants") .arg("moduleName", getName()) .flush(); - if (baseJavaModule instanceof NativeModuleLogger) { - ((NativeModuleLogger) baseJavaModule).startConstantsMapConversion(); - } + ReactMarker.logMarker(CONVERT_CONSTANTS_START, getName()); WritableNativeMap writableNativeMap; try { writableNativeMap = Arguments.makeNativeMap(map); @@ -120,9 +119,7 @@ public NativeArray getConstants() { } WritableNativeArray array = new WritableNativeArray(); array.pushMap(writableNativeMap); - if (baseJavaModule instanceof NativeModuleLogger) { - ((NativeModuleLogger) baseJavaModule).endConstantsMapConversion(); - } + ReactMarker.logMarker(CONVERT_CONSTANTS_END); ReactMarker.logMarker(GET_CONSTANTS_END); return array; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleHolder.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleHolder.java index f32d35c1284e21..52ac2059e11be7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleHolder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleHolder.java @@ -5,11 +5,9 @@ import javax.annotation.Nullable; import javax.inject.Provider; -import java.util.concurrent.ExecutionException; - import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactMarker; -import com.facebook.react.common.futures.SimpleSettableFuture; +import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.systrace.Systrace; import com.facebook.systrace.SystraceMessage; @@ -122,34 +120,9 @@ private void doInitialize(NativeModule module) { section.arg("name", mName); } section.flush(); - callInitializeOnUiThread(module); + ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, mName); + module.initialize(); + ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END); Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); } - - // TODO(t11394264): Use the native module thread here after the old bridge is gone - private static void callInitializeOnUiThread(final NativeModule module) { - if (UiThreadUtil.isOnUiThread()) { - module.initialize(); - return; - } - final SimpleSettableFuture future = new SimpleSettableFuture<>(); - UiThreadUtil.runOnUiThread(new Runnable() { - @Override - public void run() { - Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initializeOnUiThread"); - try { - module.initialize(); - future.set(null); - } catch (Exception e) { - future.setException(e); - } - Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE); - } - }); - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java index f20d0d32cf94d3..93d9e276d7fef7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java @@ -17,6 +17,7 @@ import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.OnBatchCompleteListener; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.systrace.Systrace; @@ -26,12 +27,15 @@ */ public class NativeModuleRegistry { + private final ReactApplicationContext mReactApplicationContext; private final Map, ModuleHolder> mModules; private final ArrayList mBatchCompleteListenerModules; public NativeModuleRegistry( + ReactApplicationContext reactApplicationContext, Map, ModuleHolder> modules, ArrayList batchCompleteListenerModules) { + mReactApplicationContext = reactApplicationContext; mModules = modules; mBatchCompleteListenerModules = batchCompleteListenerModules; } @@ -60,7 +64,7 @@ public NativeModuleRegistry( } /* package */ void notifyCatalystInstanceDestroy() { - UiThreadUtil.assertOnUiThread(); + mReactApplicationContext.assertOnNativeModulesQueueThread(); Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "NativeModuleRegistry_notifyCatalystInstanceDestroy"); @@ -74,8 +78,10 @@ public NativeModuleRegistry( } /* package */ void notifyCatalystInstanceInitialized() { - UiThreadUtil.assertOnUiThread(); - + mReactApplicationContext.assertOnNativeModulesQueueThread("From version React Native v0.44, " + + "native modules are explicitly not initialized on the UI thread. See " + + "https://github.com/facebook/react-native/wiki/Breaking-Changes#d4611211-reactnativeandroidbreaking-move-nativemodule-initialization-off-ui-thread---aaachiuuu " + + " for more details."); ReactMarker.logMarker(ReactMarkerConstants.NATIVE_MODULE_INITIALIZE_START); Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK index b20d067bce7e6d..b2bbafe5fecbc9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK @@ -19,7 +19,7 @@ android_library( react_native_target("java/com/facebook/react/common/network:network"), react_native_target("java/com/facebook/react/devsupport:interfaces"), react_native_target("java/com/facebook/react/module/annotations:annotations"), - react_native_target('java/com/facebook/react/modules/core:core'), + react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/modules/debug:debug"), react_native_target("java/com/facebook/react/modules/debug:interfaces"), react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java new file mode 100644 index 00000000000000..2aef1b589ddd3b --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.devsupport; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.widget.TextView; + +import com.facebook.common.logging.FLog; +import com.facebook.react.R; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.common.ReactConstants; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; + +import javax.annotation.Nullable; + +/** + * Controller to display loading messages on top of the screen. All methods are thread safe. + */ +public class DevLoadingViewController { + private static final int COLOR_DARK_GREEN = Color.parseColor("#035900"); + + private static boolean sEnabled = true; + private final Context mContext; + private final WindowManager mWindowManager; + private TextView mDevLoadingView; + private boolean mIsVisible = false; + + public static void setDevLoadingEnabled(boolean enabled) { + sEnabled = enabled; + } + + public DevLoadingViewController(Context context) { + mContext = context; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDevLoadingView = (TextView) inflater.inflate(R.layout.dev_loading_view, null); + } + + public void showMessage(final String message, final int color, final int backgroundColor) { + if (!sEnabled) { + return; + } + + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + mDevLoadingView.setBackgroundColor(backgroundColor); + mDevLoadingView.setText(message); + mDevLoadingView.setTextColor(color); + + setVisible(true); + } + }); + } + + public void showForUrl(String url) { + URL parsedURL; + try { + parsedURL = new URL(url); + } catch (MalformedURLException e) { + FLog.e(ReactConstants.TAG, "Bundle url format is invalid. \n\n" + e.toString()); + return; + } + + showMessage( + mContext.getString(R.string.catalyst_loading_from_url, parsedURL.getHost() + ":" + parsedURL.getPort()), + Color.WHITE, + COLOR_DARK_GREEN); + } + + public void showForRemoteJSEnabled() { + showMessage(mContext.getString(R.string.catalyst_remotedbg_message), Color.WHITE, COLOR_DARK_GREEN); + } + + public void updateProgress(final @Nullable String status, final @Nullable Integer done, final @Nullable Integer total) { + if (!sEnabled) { + return; + } + + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + StringBuilder message = new StringBuilder(); + message.append(status != null ? status : "Loading"); + if (done != null && total != null && total > 0) { + message.append(String.format(Locale.getDefault(), " %.1f%% (%d/%d)", (float) done / total * 100, done, total)); + } + message.append("\u2026"); // `...` character + + mDevLoadingView.setText(message); + } + }); + } + + public void show() { + if (!sEnabled) { + return; + } + + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + setVisible(true); + } + }); + } + + public void hide() { + if (!sEnabled) { + return; + } + + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + setVisible(false); + } + }); + } + + private void setVisible(boolean visible) { + if (visible && !mIsVisible) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + params.gravity = Gravity.TOP; + mWindowManager.addView(mDevLoadingView, params); + } else if (!visible && mIsVisible) { + mWindowManager.removeView(mDevLoadingView); + } + mIsVisible = visible; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 69f35566bfc5dd..5cebb1bc52fdba 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -17,6 +17,8 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import android.content.Context; import android.os.AsyncTask; @@ -32,6 +34,9 @@ import com.facebook.react.modules.systeminfo.AndroidInfoHelpers; import com.facebook.react.packagerconnection.JSPackagerClient; +import org.json.JSONException; +import org.json.JSONObject; + import okhttp3.Call; import okhttp3.Callback; import okhttp3.ConnectionPool; @@ -39,6 +44,8 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; import okio.Okio; import okio.Sink; @@ -78,6 +85,7 @@ public class DevServerHelper { public interface BundleDownloadCallback { void onSuccess(); + void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total); void onFailure(Exception cause); } @@ -87,7 +95,7 @@ public interface OnServerContentChangeListener { public interface PackagerCommandListener { void onPackagerReloadCommand(); - void onCaptureHeapCommand(); + void onCaptureHeapCommand(@Nullable final JSPackagerClient.Responder responder); void onPokeSamplingProfilerCommand(@Nullable final JSPackagerClient.Responder responder); } @@ -129,10 +137,10 @@ public void onNotification(@Nullable Object params) { commandListener.onPackagerReloadCommand(); } }); - handlers.put("captureHeap", new JSPackagerClient.NotificationOnlyHandler() { + handlers.put("captureHeap", new JSPackagerClient.RequestOnlyHandler() { @Override - public void onNotification(@Nullable Object params) { - commandListener.onCaptureHeapCommand(); + public void onRequest(@Nullable Object params, JSPackagerClient.Responder responder) { + commandListener.onCaptureHeapCommand(responder); } }); handlers.put("pokeSamplingProfiler", new JSPackagerClient.RequestOnlyHandler() { @@ -297,6 +305,7 @@ public void downloadBundleFromURL( final String bundleURL) { final Request request = new Request.Builder() .url(bundleURL) + .addHeader("Accept", "multipart/mixed") .build(); mDownloadBundleFromURLCall = Assertions.assertNotNull(mClient.newCall(request)); mDownloadBundleFromURLCall.enqueue(new Callback() { @@ -316,7 +325,7 @@ public void onFailure(Call call, IOException e) { } @Override - public void onResponse(Call call, Response response) throws IOException { + public void onResponse(Call call, final Response response) throws IOException { // ignore callback if call was cancelled if (mDownloadBundleFromURLCall == null || mDownloadBundleFromURLCall.isCanceled()) { mDownloadBundleFromURLCall = null; @@ -324,37 +333,101 @@ public void onResponse(Call call, Response response) throws IOException { } mDownloadBundleFromURLCall = null; - // Check for server errors. If the server error has the expected form, fail with more info. - if (!response.isSuccessful()) { - String body = response.body().string(); - DebugServerException debugServerException = DebugServerException.parse(body); - if (debugServerException != null) { - callback.onFailure(debugServerException); - } else { - StringBuilder sb = new StringBuilder(); - sb.append("The development server returned response error code: ").append(response.code()).append("\n\n") - .append("URL: ").append(call.request().url().toString()).append("\n\n") - .append("Body:\n") - .append(body); - callback.onFailure(new DebugServerException(sb.toString())); - } - return; - } + final String url = response.request().url().toString(); - Sink output = null; - try { - output = Okio.sink(outputFile); - Okio.buffer(response.body().source()).readAll(output); - callback.onSuccess(); - } finally { - if (output != null) { - output.close(); + // Make sure the result is a multipart response and parse the boundary. + String contentType = response.header("content-type"); + Pattern regex = Pattern.compile("multipart/mixed;.*boundary=\"([^\"]+)\""); + Matcher match = regex.matcher(contentType); + if (match.find()) { + String boundary = match.group(1); + MultipartStreamReader bodyReader = new MultipartStreamReader(response.body().source(), boundary); + boolean completed = bodyReader.readAllParts(new MultipartStreamReader.ChunkCallback() { + @Override + public void execute(Map headers, Buffer body, boolean finished) throws IOException { + // This will get executed for every chunk of the multipart response. The last chunk + // (finished = true) will be the JS bundle, the other ones will be progress events + // encoded as JSON. + if (finished) { + // The http status code for each separate chunk is in the X-Http-Status header. + int status = response.code(); + if (headers.containsKey("X-Http-Status")) { + status = Integer.parseInt(headers.get("X-Http-Status")); + } + processBundleResult(url, status, body, outputFile, callback); + } else { + if (!headers.containsKey("Content-Type") || !headers.get("Content-Type").equals("application/json")) { + return; + } + try { + JSONObject progress = new JSONObject(body.readUtf8()); + String status = null; + if (progress.has("status")) { + status = progress.getString("status"); + } + Integer done = null; + if (progress.has("done")) { + done = progress.getInt("done"); + } + Integer total = null; + if (progress.has("total")) { + total = progress.getInt("total"); + } + callback.onProgress(status, done, total); + } catch (JSONException e) { + FLog.e(ReactConstants.TAG, "Error parsing progress JSON. " + e.toString()); + } + } + } + }); + if (!completed) { + callback.onFailure(new DebugServerException( + "Error while reading multipart response.\n\nResponse code: " + response.code() + "\n\n" + + "URL: " + call.request().url().toString() + "\n\n")); } + } else { + // In case the server doesn't support multipart/mixed responses, fallback to normal download. + processBundleResult(url, response.code(), Okio.buffer(response.body().source()), outputFile, callback); } } }); } + private void processBundleResult( + String url, + int statusCode, + BufferedSource body, + File outputFile, + BundleDownloadCallback callback) throws IOException { + // Check for server errors. If the server error has the expected form, fail with more info. + if (statusCode != 200) { + String bodyString = body.readUtf8(); + DebugServerException debugServerException = DebugServerException.parse(bodyString); + if (debugServerException != null) { + callback.onFailure(debugServerException); + } else { + StringBuilder sb = new StringBuilder(); + sb.append("The development server returned response error code: ").append(statusCode).append("\n\n") + .append("URL: ").append(url).append("\n\n") + .append("Body:\n") + .append(bodyString); + callback.onFailure(new DebugServerException(sb.toString())); + } + return; + } + + Sink output = null; + try { + output = Okio.sink(outputFile); + body.readAll(output); + callback.onSuccess(); + } finally { + if (output != null) { + output.close(); + } + } + } + public void cancelDownloadBundleFromURL() { if (mDownloadBundleFromURLCall != null) { mDownloadBundleFromURLCall.cancel(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 4f7ad2e500bee4..28ab652142d904 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -9,19 +9,6 @@ package com.facebook.react.devsupport; -import javax.annotation.Nullable; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - import android.app.ActivityManager; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -58,6 +45,19 @@ import com.facebook.react.modules.debug.interfaces.DeveloperSettings; import com.facebook.react.packagerconnection.JSPackagerClient; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nullable; + import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -115,10 +115,12 @@ private static enum ErrorType { private final @Nullable String mJSAppBundleName; private final File mJSBundleTempFile; private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler; + private final DevLoadingViewController mDevLoadingViewController; private @Nullable RedBoxDialog mRedBoxDialog; private @Nullable AlertDialog mDevOptionsDialog; private @Nullable DebugOverlayController mDebugOverlayController; + private boolean mDevLoadingViewVisible = false; private @Nullable ReactContext mCurrentContext; private DevInternalSettings mDevSettings; private boolean mIsReceiverRegistered = false; @@ -226,6 +228,7 @@ public void onReceive(Context context, Intent intent) { setDevSupportEnabled(enableOnCreate); mRedBoxHandler = redBoxHandler; + mDevLoadingViewController = new DevLoadingViewController(applicationContext); } @Override @@ -424,7 +427,7 @@ public void onOptionSelected() { new DevOptionHandler() { @Override public void onOptionSelected() { - handleCaptureHeap(); + handleCaptureHeap(null); } }); options.put( @@ -641,7 +644,9 @@ public void handleReloadJS() { } if (mDevSettings.isRemoteJSDebugEnabled()) { - reloadJSInProxyMode(showProgressDialog()); + mDevLoadingViewController.showForRemoteJSEnabled(); + mDevLoadingViewVisible = true; + reloadJSInProxyMode(); } else { String bundleURL = mDevServerHelper.getDevServerBundleURL(Assertions.assertNotNull(mJSAppBundleName)); @@ -682,11 +687,11 @@ public void run() { } @Override - public void onCaptureHeapCommand() { + public void onCaptureHeapCommand(@Nullable final JSPackagerClient.Responder responder) { UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { - handleCaptureHeap(); + handleCaptureHeap(responder); } }); } @@ -701,14 +706,14 @@ public void run() { }); } - private void handleCaptureHeap() { + private void handleCaptureHeap(@Nullable final JSPackagerClient.Responder responder) { if (mCurrentContext == null) { return; } JSCHeapCapture heapCapture = mCurrentContext.getNativeModule(JSCHeapCapture.class); heapCapture.captureHeap( mApplicationContext.getCacheDir().getPath(), - JSCHeapUpload.captureCallback(mDevServerHelper.getHeapCaptureUploadUrl())); + JSCHeapUpload.captureCallback(mDevServerHelper.getHeapCaptureUploadUrl(), responder)); } private void handlePokeSamplingProfiler(@Nullable final JSPackagerClient.Responder responder) { @@ -748,7 +753,7 @@ private void updateLastErrorInfo( mLastErrorType = errorType; } - private void reloadJSInProxyMode(final AlertDialog progressDialog) { + private void reloadJSInProxyMode() { // When using js proxy, there is no need to fetch JS bundle as proxy executor will do that // anyway mDevServerHelper.launchJSDevtools(); @@ -760,7 +765,7 @@ public JavaJSExecutor create() throws Exception { SimpleSettableFuture future = new SimpleSettableFuture<>(); executor.connect( mDevServerHelper.getWebsocketProxyURL(), - getExecutorConnectCallback(progressDialog, future)); + getExecutorConnectCallback(future)); // TODO(t9349129) Don't use timeout try { future.get(90, TimeUnit.SECONDS); @@ -776,18 +781,19 @@ public JavaJSExecutor create() throws Exception { } private WebsocketJavaScriptExecutor.JSExecutorConnectCallback getExecutorConnectCallback( - final AlertDialog progressDialog, final SimpleSettableFuture future) { return new WebsocketJavaScriptExecutor.JSExecutorConnectCallback() { @Override public void onSuccess() { future.set(true); - progressDialog.dismiss(); + mDevLoadingViewController.hide(); + mDevLoadingViewVisible = false; } @Override public void onFailure(final Throwable cause) { - progressDialog.dismiss(); + mDevLoadingViewController.hide(); + mDevLoadingViewVisible = false; FLog.e(ReactConstants.TAG, "Unable to connect to remote debugger", cause); future.setException( new IOException( @@ -796,27 +802,16 @@ public void onFailure(final Throwable cause) { }; } - private AlertDialog showProgressDialog() { - AlertDialog dialog = new AlertDialog.Builder(mApplicationContext) - .setTitle(R.string.catalyst_jsload_title) - .setMessage(mApplicationContext.getString( - mDevSettings.isRemoteJSDebugEnabled() ? - R.string.catalyst_remotedbg_message : - R.string.catalyst_jsload_message)) - .create(); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.show(); - return dialog; - } - public void reloadJSFromServer(final String bundleURL) { - final AlertDialog progressDialog = showProgressDialog(); + mDevLoadingViewController.showForUrl(bundleURL); + mDevLoadingViewVisible = true; mDevServerHelper.downloadBundleFromURL( new DevServerHelper.BundleDownloadCallback() { @Override public void onSuccess() { - progressDialog.dismiss(); + mDevLoadingViewController.hide(); + mDevLoadingViewVisible = false; UiThreadUtil.runOnUiThread( new Runnable() { @Override @@ -826,9 +821,15 @@ public void run() { }); } + @Override + public void onProgress(@Nullable final String status, @Nullable final Integer done, @Nullable final Integer total) { + mDevLoadingViewController.updateProgress(status, done, total); + } + @Override public void onFailure(final Exception cause) { - progressDialog.dismiss(); + mDevLoadingViewController.hide(); + mDevLoadingViewVisible = false; FLog.e(ReactConstants.TAG, "Unable to download JS bundle", cause); UiThreadUtil.runOnUiThread( new Runnable() { @@ -848,13 +849,6 @@ public void run() { }, mJSBundleTempFile, bundleURL); - progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - mDevServerHelper.cancelDownloadBundleFromURL(); - } - }); - progressDialog.setCancelable(true); } private void reload() { @@ -880,6 +874,11 @@ private void reload() { mIsReceiverRegistered = true; } + // show the dev loading if it should be + if (mDevLoadingViewVisible) { + mDevLoadingViewController.show(); + } + mDevServerHelper.openPackagerConnection(this); mDevServerHelper.openInspectorConnection(); if (mDevSettings.isReloadOnJSChangeEnabled()) { @@ -921,6 +920,9 @@ public void onServerContentChanged() { mDevOptionsDialog.dismiss(); } + // hide loading view + mDevLoadingViewController.hide(); + mDevServerHelper.closePackagerConnection(); mDevServerHelper.closeInspectorConnection(); mDevServerHelper.stopPollingOnChangeEndpoint(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapUpload.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapUpload.java index f48d0dae82c9c6..f678a6d66dd58a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapUpload.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/JSCHeapUpload.java @@ -9,10 +9,14 @@ package com.facebook.react.devsupport; +import javax.annotation.Nullable; + import android.util.Log; import java.io.File; import java.io.IOException; -import java.util.List; +import java.util.concurrent.TimeUnit; +import com.facebook.react.packagerconnection.JSPackagerClient; + import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; @@ -25,11 +29,17 @@ * Created by cwdick on 7/22/16. */ public class JSCHeapUpload { - public static JSCHeapCapture.CaptureCallback captureCallback(final String uploadUrl) { + public static JSCHeapCapture.CaptureCallback captureCallback( + final String uploadUrl, + @Nullable final JSPackagerClient.Responder responder) { return new JSCHeapCapture.CaptureCallback() { @Override public void onSuccess(File capture) { - OkHttpClient httpClient = new OkHttpClient.Builder().build(); + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + httpClientBuilder.connectTimeout(1, TimeUnit.MINUTES) + .writeTimeout(5, TimeUnit.MINUTES) + .readTimeout(5, TimeUnit.MINUTES); + OkHttpClient httpClient = httpClientBuilder.build(); RequestBody body = RequestBody.create(MediaType.parse("application/json"), capture); Request request = new Request.Builder() .url(uploadUrl) @@ -39,21 +49,28 @@ public void onSuccess(File capture) { call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { - Log.e("JSCHeapCapture", "Upload of heap capture failed: " + e.toString()); + String message = "Upload of heap capture failed: " + e.toString(); + Log.e("JSCHeapCapture", message); + responder.error(message); } @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { - Log.e("JSCHeapCapture", "Upload of heap capture failed with code: " + Integer.toString(response.code())); + String message = "Upload of heap capture failed with code " + Integer.toString(response.code()) + ": " + response.body().string(); + Log.e("JSCHeapCapture", message); + responder.error(message); } + responder.respond(response.body().string()); } }); } @Override public void onFailure(JSCHeapCapture.CaptureException e) { - Log.e("JSCHeapCapture", "Heap capture failed: " + e.toString()); + String message = "Heap capture failed: " + e.toString(); + Log.e("JSCHeapCapture", message); + responder.error(message); } }; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java new file mode 100644 index 00000000000000..efffbae8cccabe --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.devsupport; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import okio.Buffer; +import okio.BufferedSource; +import okio.ByteString; + +/** + * Utility class to parse the body of a response of type multipart/mixed. + */ +public class MultipartStreamReader { + // Standard line separator for HTTP. + private static final String CRLF = "\r\n"; + + private final BufferedSource mSource; + private final String mBoundary; + + public interface ChunkCallback { + void execute(Map headers, Buffer body, boolean done) throws IOException; + } + + public MultipartStreamReader(BufferedSource source, String boundary) { + mSource = source; + mBoundary = boundary; + } + + private Map parseHeaders(Buffer data) { + Map headers = new HashMap<>(); + + String text = data.readUtf8(); + String[] lines = text.split(CRLF); + for (String line : lines) { + int indexOfSeparator = line.indexOf(":"); + if (indexOfSeparator == -1) { + continue; + } + + String key = line.substring(0, indexOfSeparator).trim(); + String value = line.substring(indexOfSeparator + 1).trim(); + headers.put(key, value); + } + + return headers; + } + + private void emitChunk(Buffer chunk, boolean done, ChunkCallback callback) throws IOException { + ByteString marker = ByteString.encodeUtf8(CRLF + CRLF); + long indexOfMarker = chunk.indexOf(marker); + if (indexOfMarker == -1) { + callback.execute(null, chunk, done); + } else { + Buffer headers = new Buffer(); + Buffer body = new Buffer(); + chunk.read(headers, indexOfMarker); + chunk.skip(marker.size()); + chunk.readAll(body); + callback.execute(parseHeaders(headers), body, done); + } + } + + /** + * Reads all parts of the multipart response and execute the callback for each chunk received. + * @param callback Callback executed when a chunk is received + * @return If the read was successful + */ + public boolean readAllParts(ChunkCallback callback) throws IOException { + ByteString delimiter = ByteString.encodeUtf8(CRLF + "--" + mBoundary + CRLF); + ByteString closeDelimiter = ByteString.encodeUtf8(CRLF + "--" + mBoundary + "--" + CRLF); + + int bufferLen = 4 * 1024; + long chunkStart = 0; + long bytesSeen = 0; + Buffer content = new Buffer(); + + while (true) { + boolean isCloseDelimiter = false; + + // Search only a subset of chunk that we haven't seen before + few bytes + // to allow for the edge case when the delimiter is cut by read call. + long searchStart = Math.max(bytesSeen - closeDelimiter.size(), chunkStart); + long indexOfDelimiter = content.indexOf(delimiter, searchStart); + if (indexOfDelimiter == -1) { + isCloseDelimiter = true; + indexOfDelimiter = content.indexOf(closeDelimiter, searchStart); + } + + if (indexOfDelimiter == -1) { + bytesSeen = content.size(); + long bytesRead = mSource.read(content, bufferLen); + if (bytesRead <= 0) { + return false; + } + continue; + } + + long chunkEnd = indexOfDelimiter; + long length = chunkEnd - chunkStart; + + // Ignore preamble + if (chunkStart > 0) { + Buffer chunk = new Buffer(); + content.skip(chunkStart); + content.read(chunk, length); + emitChunk(chunk, isCloseDelimiter, callback); + } else { + content.skip(chunkEnd); + } + + if (isCloseDelimiter) { + return true; + } + + bytesSeen = chunkStart = delimiter.size(); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java index c6da41a8489fa9..0e504d5d8c6ba9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java @@ -35,8 +35,8 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.common.SystemClock; import com.facebook.react.devsupport.interfaces.DevSupportManager; -import com.facebook.react.jstasks.HeadlessJsTaskEventListener; import com.facebook.react.jstasks.HeadlessJsTaskContext; +import com.facebook.react.jstasks.HeadlessJsTaskEventListener; import com.facebook.react.module.annotations.ReactModule; /** @@ -236,8 +236,6 @@ public int compare(Timer lhs, Timer rhs) { @Override public void initialize() { - // Safe to acquire choreographer here, as initialize() is invoked from UI thread. - mReactChoreographer = ReactChoreographer.getInstance(); getReactApplicationContext().addLifecycleEventListener(this); HeadlessJsTaskContext headlessJsTaskContext = HeadlessJsTaskContext.getInstance(getReactApplicationContext()); @@ -259,6 +257,11 @@ public void onHostDestroy() { @Override public void onHostResume() { + if (mReactChoreographer == null) { + // Safe to acquire choreographer here, as onHostResume() is invoked from UI thread. + mReactChoreographer = ReactChoreographer.getInstance(); + } + isPaused.set(false); // TODO(5195192) Investigate possible problems related to restarting all tasks at the same // moment @@ -268,6 +271,11 @@ public void onHostResume() { @Override public void onHeadlessJsTaskStart(int taskId) { + if (mReactChoreographer == null) { + // Safe to acquire choreographer here, as onHeadlessJsTaskStart() is invoked from UI thread. + mReactChoreographer = ReactChoreographer.getInstance(); + } + if (!isRunningTasks.getAndSet(true)) { setChoreographerCallback(); maybeSetChoreographerIdleCallback(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK index ef375d65712472..78c64da7929e44 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK @@ -13,7 +13,7 @@ android_library( react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/module/annotations:annotations"), - react_native_target('java/com/facebook/react/modules/core:core'), + react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/modules/debug:interfaces"), react_native_target("java/com/facebook/react/uimanager:uimanager"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK index d0756896ff5b15..1e6e6d60026ffc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK @@ -19,6 +19,7 @@ android_library( react_native_dep("third-party/android/support-annotations:android-support-annotations"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/okhttp:okhttp3"), + react_native_dep("third-party/java/okhttp:okhttp3-urlconnection"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), react_native_target("java/com/facebook/react/module/annotations:annotations"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java index af3c791ef8a5eb..e1e227e595b8cb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java @@ -14,20 +14,25 @@ import android.content.Context; import android.support.annotation.Nullable; -import com.facebook.common.soloader.SoLoaderShim; import com.facebook.common.logging.FLog; +import com.facebook.common.soloader.SoLoaderShim; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory; import com.facebook.imagepipeline.core.ImagePipelineConfig; import com.facebook.imagepipeline.listener.RequestListener; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.common.ReactConstants; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.modules.common.ModuleDataCleaner; +import com.facebook.react.modules.network.CookieJarContainer; +import com.facebook.react.modules.network.ForwardingCookieHandler; import com.facebook.react.modules.network.OkHttpClientProvider; import com.facebook.soloader.SoLoader; +import okhttp3.JavaNetCookieJar; import okhttp3.OkHttpClient; /** @@ -37,20 +42,35 @@ */ @ReactModule(name = "FrescoModule") public class FrescoModule extends ReactContextBaseJavaModule implements - ModuleDataCleaner.Cleanable { + ModuleDataCleaner.Cleanable, LifecycleEventListener { + private final boolean mClearOnDestroy; private @Nullable ImagePipelineConfig mConfig; private static boolean sHasBeenInitialized = false; /** * Create a new Fresco module with a default configuration (or the previously given - * configuration via {@link #FrescoModule(ReactApplicationContext, ImagePipelineConfig)}. + * configuration via {@link #FrescoModule(ReactApplicationContext, boolean, ImagePipelineConfig)}. * * @param reactContext the context to use */ public FrescoModule(ReactApplicationContext reactContext) { - this(reactContext, null); + this(reactContext, true, null); + } + + /** + * Create a new Fresco module with a default configuration (or the previously given + * configuration via {@link #FrescoModule(ReactApplicationContext, boolean, ImagePipelineConfig)}. + * + * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be + * {@code true} for pure RN apps and {@code false} for apps that use Fresco outside of RN + * as well + * @param reactContext the context to use + * + */ + public FrescoModule(ReactApplicationContext reactContext, boolean clearOnDestroy) { + this(reactContext, clearOnDestroy, null); } /** @@ -61,16 +81,24 @@ public FrescoModule(ReactApplicationContext reactContext) { * Otherwise, the given Fresco configuration will be ignored. * * @param reactContext the context to use + * @param clearOnDestroy whether to clear the memory cache in onHostDestroy: this should be + * {@code true} for pure RN apps and {@code false} for apps that use Fresco outside of RN + * as well * @param config the Fresco configuration, which will only be used for the first initialization */ - public FrescoModule(ReactApplicationContext reactContext, @Nullable ImagePipelineConfig config) { + public FrescoModule( + ReactApplicationContext reactContext, + boolean clearOnDestroy, + @Nullable ImagePipelineConfig config) { super(reactContext); + mClearOnDestroy = clearOnDestroy; mConfig = config; } @Override public void initialize() { super.initialize(); + getReactApplicationContext().addLifecycleEventListener(this); if (!hasBeenInitialized()) { // Make sure the SoLoaderShim is configured to use our loader for native libraries. // This code can be removed if using Fresco from Maven rather than from source @@ -112,7 +140,7 @@ public static boolean hasBeenInitialized() { return sHasBeenInitialized; } - private static ImagePipelineConfig getDefaultConfig(Context context) { + private static ImagePipelineConfig getDefaultConfig(ReactContext context) { return getDefaultConfigBuilder(context).build(); } @@ -122,18 +150,43 @@ private static ImagePipelineConfig getDefaultConfig(Context context) { * * @return {@link ImagePipelineConfig.Builder} that has been initialized with default values */ - public static ImagePipelineConfig.Builder getDefaultConfigBuilder(Context context) { + public static ImagePipelineConfig.Builder getDefaultConfigBuilder(ReactContext context) { HashSet requestListeners = new HashSet<>(); requestListeners.add(new SystraceRequestListener()); - OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient(); + OkHttpClient client = OkHttpClientProvider.createClient(); + + // make sure to forward cookies for any requests via the okHttpClient + // so that image requests to endpoints that use cookies still work + CookieJarContainer container = (CookieJarContainer) client.cookieJar(); + ForwardingCookieHandler handler = new ForwardingCookieHandler(context); + container.setCookieJar(new JavaNetCookieJar(handler)); + return OkHttpImagePipelineConfigFactory - .newBuilder(context.getApplicationContext(), okHttpClient) - .setNetworkFetcher(new ReactOkHttpNetworkFetcher(okHttpClient)) + .newBuilder(context.getApplicationContext(), client) + .setNetworkFetcher(new ReactOkHttpNetworkFetcher(client)) .setDownsampleEnabled(false) .setRequestListeners(requestListeners); } + @Override + public void onHostResume() { + } + + @Override + public void onHostPause() { + } + + @Override + public void onHostDestroy() { + // According to the javadoc for LifecycleEventListener#onHostDestroy, this is only called when + // the 'last' ReactActivity is being destroyed, which effectively means the app is being + // backgrounded. + if (mClearOnDestroy) { + Fresco.getImagePipeline().clearMemoryCaches(); + } + } + private static class FrescoHandler implements SoLoaderShim.Handler { @Override public void loadLibrary(String libraryName) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java index 9634e63437e302..d64a2c0193fcaa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java @@ -281,6 +281,8 @@ private static String getDefaultOrigin(String uri) { scheme += "https"; } else if (requestURI.getScheme().equals("ws")) { scheme += "http"; + } else if (requestURI.getScheme().equals("http") || requestURI.getScheme().equals("https")) { + scheme += requestURI.getScheme(); } if (requestURI.getPort() != -1) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index a47ecb32626967..7d076813a4e564 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -148,7 +148,7 @@ public NativeModule get() { new ModuleSpec(FrescoModule.class, new Provider() { @Override public NativeModule get() { - return new FrescoModule(context, mConfig != null ? mConfig.getFrescoConfig() : null); + return new FrescoModule(context, true, mConfig != null ? mConfig.getFrescoConfig() : null); } }), new ModuleSpec(I18nManagerModule.class, new Provider() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java index 586b24e0caaa2a..3f5020e3ad4157 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java @@ -11,11 +11,11 @@ import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaConstants; +import com.facebook.yoga.YogaDisplay; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaJustify; import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; -import com.facebook.yoga.YogaValue; import com.facebook.yoga.YogaUnit; import com.facebook.yoga.YogaWrap; import com.facebook.react.uimanager.annotations.ReactProp; @@ -32,22 +32,36 @@ */ public class LayoutShadowNode extends ReactShadowNode { - private static boolean dynamicIsPercent(Dynamic dynamic) { - return dynamic.getType() == ReadableType.String && dynamic.asString().endsWith("%"); - } - - private static float getDynamicAsPercent(Dynamic dynamic) { - final String value = dynamic.asString(); - return Float.parseFloat(value.substring(0, value.length() - 1)); - } - - private static float getDynamicAsFloat(Dynamic dynamic) { - return (float) PixelUtil.toPixelFromDIP(dynamic.asDouble()); + /** + * A Mutable version of com.facebook.yoga.YogaValue + */ + private static class MutableYogaValue { + float value; + YogaUnit unit; + + void setFromDynamic(Dynamic dynamic) { + if (dynamic.isNull()) { + unit = YogaUnit.UNDEFINED; + value = YogaConstants.UNDEFINED; + } else if (dynamic.getType() == ReadableType.String) { + final String s = dynamic.asString(); + if (s.equals("auto")) { + unit = YogaUnit.AUTO; + value = YogaConstants.UNDEFINED; + } else if (s.endsWith("%")) { + unit = YogaUnit.PERCENT; + value = Float.parseFloat(s.substring(0, s.length() - 1)); + } else { + throw new IllegalArgumentException("Unknown value: " + s); + } + } else { + unit = YogaUnit.POINT; + value = PixelUtil.toPixelFromDIP(dynamic.asDouble()); + } + } } - private static boolean isNull(Dynamic d) { - return d == null || d.isNull(); - } + private final MutableYogaValue mTempYogaValue = new MutableYogaValue(); @ReactProp(name = ViewProps.WIDTH) public void setWidth(Dynamic width) { @@ -55,10 +69,18 @@ public void setWidth(Dynamic width) { return; } - if (!isNull(width) && dynamicIsPercent(width)) { - setStyleWidthPercent(getDynamicAsPercent(width)); - } else { - setStyleWidth(isNull(width) ? YogaConstants.UNDEFINED : getDynamicAsFloat(width)); + mTempYogaValue.setFromDynamic(width); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setStyleWidth(mTempYogaValue.value); + break; + case AUTO: + setStyleWidthAuto(); + break; + case PERCENT: + setStyleWidthPercent(mTempYogaValue.value); + break; } width.recycle(); @@ -70,10 +92,15 @@ public void setMinWidth(Dynamic minWidth) { return; } - if (!isNull(minWidth) && dynamicIsPercent(minWidth)) { - setStyleMinWidthPercent(getDynamicAsPercent(minWidth)); - } else { - setStyleMinWidth(isNull(minWidth) ? YogaConstants.UNDEFINED : getDynamicAsFloat(minWidth)); + mTempYogaValue.setFromDynamic(minWidth); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setStyleMinWidth(mTempYogaValue.value); + break; + case PERCENT: + setStyleMinWidthPercent(mTempYogaValue.value); + break; } minWidth.recycle(); @@ -85,10 +112,15 @@ public void setMaxWidth(Dynamic maxWidth) { return; } - if (!isNull(maxWidth) && dynamicIsPercent(maxWidth)) { - setStyleMaxWidthPercent(getDynamicAsPercent(maxWidth)); - } else { - setStyleMaxWidth(isNull(maxWidth) ? YogaConstants.UNDEFINED : getDynamicAsFloat(maxWidth)); + mTempYogaValue.setFromDynamic(maxWidth); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setStyleMaxWidth(mTempYogaValue.value); + break; + case PERCENT: + setStyleMaxWidthPercent(mTempYogaValue.value); + break; } maxWidth.recycle(); @@ -100,10 +132,18 @@ public void setHeight(Dynamic height) { return; } - if (!isNull(height) && dynamicIsPercent(height)) { - setStyleHeightPercent(getDynamicAsPercent(height)); - } else { - setStyleHeight(isNull(height) ? YogaConstants.UNDEFINED : getDynamicAsFloat(height)); + mTempYogaValue.setFromDynamic(height); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setStyleHeight(mTempYogaValue.value); + break; + case AUTO: + setStyleHeightAuto(); + break; + case PERCENT: + setStyleHeightPercent(mTempYogaValue.value); + break; } height.recycle(); @@ -115,10 +155,15 @@ public void setMinHeight(Dynamic minHeight) { return; } - if (!isNull(minHeight) && dynamicIsPercent(minHeight)) { - setStyleMinHeightPercent(getDynamicAsPercent(minHeight)); - } else { - setStyleMinHeight(isNull(minHeight) ? YogaConstants.UNDEFINED : getDynamicAsFloat(minHeight)); + mTempYogaValue.setFromDynamic(minHeight); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setStyleMinHeight(mTempYogaValue.value); + break; + case PERCENT: + setStyleMinHeightPercent(mTempYogaValue.value); + break; } minHeight.recycle(); @@ -130,10 +175,15 @@ public void setMaxHeight(Dynamic maxHeight) { return; } - if (!isNull(maxHeight) && dynamicIsPercent(maxHeight)) { - setStyleMaxHeightPercent(getDynamicAsPercent(maxHeight)); - } else { - setStyleMaxHeight(isNull(maxHeight) ? YogaConstants.UNDEFINED : getDynamicAsFloat(maxHeight)); + mTempYogaValue.setFromDynamic(maxHeight); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setStyleMaxHeight(mTempYogaValue.value); + break; + case PERCENT: + setStyleMaxHeightPercent(mTempYogaValue.value); + break; } maxHeight.recycle(); @@ -169,10 +219,18 @@ public void setFlexBasis(Dynamic flexBasis) { return; } - if (!isNull(flexBasis) && dynamicIsPercent(flexBasis)) { - setFlexBasisPercent(getDynamicAsPercent(flexBasis)); - } else { - setFlexBasis(isNull(flexBasis) ? 0 : getDynamicAsFloat(flexBasis)); + mTempYogaValue.setFromDynamic(flexBasis); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setFlexBasis(mTempYogaValue.value); + break; + case AUTO: + setFlexBasisAuto(); + break; + case PERCENT: + setFlexBasisPercent(mTempYogaValue.value); + break; } flexBasis.recycle(); @@ -226,6 +284,16 @@ public void setAlignItems(@Nullable String alignItems) { alignItems.toUpperCase(Locale.US).replace("-", "_"))); } + @ReactProp(name = ViewProps.ALIGN_CONTENT) + public void setAlignContent(@Nullable String alignContent) { + if (isVirtual()) { + return; + } + setAlignContent( + alignContent == null ? YogaAlign.FLEX_START : YogaAlign.valueOf( + alignContent.toUpperCase(Locale.US).replace("-", "_"))); + } + @ReactProp(name = ViewProps.JUSTIFY_CONTENT) public void setJustifyContent(@Nullable String justifyContent) { if (isVirtual()) { @@ -244,6 +312,15 @@ public void setOverflow(@Nullable String overflow) { overflow.toUpperCase(Locale.US).replace("-", "_"))); } + @ReactProp(name = ViewProps.DISPLAY) + public void setDisplay(@Nullable String display) { + if (isVirtual()) { + return; + } + setDisplay(display == null ? YogaDisplay.FLEX : YogaDisplay.valueOf( + display.toUpperCase(Locale.US).replace("-", "_"))); + } + @ReactPropGroup(names = { ViewProps.MARGIN, ViewProps.MARGIN_VERTICAL, @@ -258,12 +335,18 @@ public void setMargins(int index, Dynamic margin) { return; } - if (!isNull(margin) && dynamicIsPercent(margin)) { - setMarginPercent(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], getDynamicAsPercent(margin)); - } else { - setMargin( - ViewProps.PADDING_MARGIN_SPACING_TYPES[index], - isNull(margin) ? YogaConstants.UNDEFINED : getDynamicAsFloat(margin)); + mTempYogaValue.setFromDynamic(margin); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setMargin(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value); + break; + case AUTO: + setMarginAuto(ViewProps.PADDING_MARGIN_SPACING_TYPES[index]); + break; + case PERCENT: + setMarginPercent(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value); + break; } margin.recycle(); @@ -283,13 +366,15 @@ public void setPaddings(int index, Dynamic padding) { return; } - if (!isNull(padding) && dynamicIsPercent(padding)) { - setPaddingPercent( - ViewProps.PADDING_MARGIN_SPACING_TYPES[index], getDynamicAsPercent(padding)); - } else { - setPadding( - ViewProps.PADDING_MARGIN_SPACING_TYPES[index], - isNull(padding) ? YogaConstants.UNDEFINED : getDynamicAsFloat(padding)); + mTempYogaValue.setFromDynamic(padding); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setPadding(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value); + break; + case PERCENT: + setPaddingPercent(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value); + break; } padding.recycle(); @@ -320,12 +405,15 @@ public void setPositionValues(int index, Dynamic position) { return; } - if (!isNull(position) && dynamicIsPercent(position)) { - setPositionPercent(ViewProps.POSITION_SPACING_TYPES[index], getDynamicAsPercent(position)); - } else { - setPosition( - ViewProps.POSITION_SPACING_TYPES[index], - isNull(position) ? YogaConstants.UNDEFINED : getDynamicAsFloat(position)); + mTempYogaValue.setFromDynamic(position); + switch (mTempYogaValue.unit) { + case POINT: + case UNDEFINED: + setPosition(ViewProps.POSITION_SPACING_TYPES[index], mTempYogaValue.value); + break; + case PERCENT: + setPositionPercent(ViewProps.POSITION_SPACING_TYPES[index], mTempYogaValue.value); + break; } position.recycle(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 6bcfa50a16f9e2..2cad696e502860 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import com.facebook.yoga.YogaAlign; +import com.facebook.yoga.YogaDisplay; import com.facebook.yoga.YogaEdge; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaDirection; @@ -554,6 +555,10 @@ public void setStyleWidthPercent(float percent) { mYogaNode.setWidthPercent(percent); } + public void setStyleWidthAuto() { + mYogaNode.setWidthAuto(); + } + public void setStyleMinWidth(float widthPx) { mYogaNode.setMinWidth(widthPx); } @@ -582,6 +587,10 @@ public void setStyleHeightPercent(float percent) { mYogaNode.setHeightPercent(percent); } + public void setStyleHeightAuto() { + mYogaNode.setHeightAuto(); + } + public void setStyleMinHeight(float widthPx) { mYogaNode.setMinHeight(widthPx); } @@ -614,6 +623,10 @@ public void setFlexBasis(float flexBasis) { mYogaNode.setFlexBasis(flexBasis); } + public void setFlexBasisAuto() { + mYogaNode.setFlexBasisAuto(); + } + public void setFlexBasisPercent(float percent) { mYogaNode.setFlexBasisPercent(percent); } @@ -638,6 +651,10 @@ public void setAlignItems(YogaAlign alignItems) { mYogaNode.setAlignItems(alignItems); } + public void setAlignContent(YogaAlign alignContent) { + mYogaNode.setAlignContent(alignContent); + } + public void setJustifyContent(YogaJustify justifyContent) { mYogaNode.setJustifyContent(justifyContent); } @@ -646,6 +663,10 @@ public void setOverflow(YogaOverflow overflow) { mYogaNode.setOverflow(overflow); } + public void setDisplay(YogaDisplay display) { + mYogaNode.setDisplay(display); + } + public void setMargin(int spacingType, float margin) { mYogaNode.setMargin(YogaEdge.fromInt(spacingType), margin); } @@ -654,6 +675,10 @@ public void setMarginPercent(int spacingType, float percent) { mYogaNode.setMarginPercent(YogaEdge.fromInt(spacingType), percent); } + public void setMarginAuto(int spacingType) { + mYogaNode.setMarginAuto(YogaEdge.fromInt(spacingType)); + } + public final float getPadding(int spacingType) { return mYogaNode.getLayoutPadding(YogaEdge.fromInt(spacingType)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 1d05aa2679332e..725554596559a1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -1,4 +1,4 @@ - /** +/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * @@ -23,7 +23,6 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.GuardedRunnable; import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.NativeModuleLogger; import com.facebook.react.bridge.OnBatchCompleteListener; import com.facebook.react.bridge.PerformanceCounter; import com.facebook.react.bridge.ReactApplicationContext; @@ -43,8 +42,6 @@ import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_END; import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_START; -import static com.facebook.react.bridge.ReactMarkerConstants.UI_MANAGER_MODULE_CONSTANTS_CONVERT_END; -import static com.facebook.react.bridge.ReactMarkerConstants.UI_MANAGER_MODULE_CONSTANTS_CONVERT_START; /** *

Native module to allow JS to create and update native Views.

@@ -77,7 +74,7 @@ */ @ReactModule(name = UIManagerModule.NAME) public class UIManagerModule extends ReactContextBaseJavaModule implements - OnBatchCompleteListener, LifecycleEventListener, PerformanceCounter, NativeModuleLogger { + OnBatchCompleteListener, LifecycleEventListener, PerformanceCounter { protected static final String NAME = "UIManager"; @@ -596,16 +593,6 @@ public int resolveRootTagFromReactTag(int reactTag) { return mUIImplementation.resolveRootTagFromReactTag(reactTag); } - @Override - public void startConstantsMapConversion() { - ReactMarker.logMarker(UI_MANAGER_MODULE_CONSTANTS_CONVERT_START); - } - - @Override - public void endConstantsMapConversion() { - ReactMarker.logMarker(UI_MANAGER_MODULE_CONSTANTS_CONVERT_END); - } - /** * Listener that drops the CSSNode pool on low memory when the app is backgrounded. */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java index 1384379406333e..67265436ae9eec 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java @@ -25,7 +25,9 @@ public class ViewProps { // !!! Keep in sync with LAYOUT_ONLY_PROPS below public static final String ALIGN_ITEMS = "alignItems"; public static final String ALIGN_SELF = "alignSelf"; + public static final String ALIGN_CONTENT = "alignContent"; public static final String OVERFLOW = "overflow"; + public static final String DISPLAY = "display"; public static final String BOTTOM = "bottom"; public static final String COLLAPSABLE = "collapsable"; public static final String FLEX = "flex"; @@ -122,6 +124,8 @@ public class ViewProps { FLEX_WRAP, JUSTIFY_CONTENT, OVERFLOW, + ALIGN_CONTENT, + DISPLAY, /* position */ POSITION, diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java index 2d5702389ea627..78813eefa8b633 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java @@ -82,6 +82,11 @@ public void setSource(ReactImageView view, @Nullable ReadableArray sources) { view.setSource(sources); } + @ReactProp(name = "blurRadius") + public void setBlurRadius(ReactImageView view, float blurRadius) { + view.setBlurRadius(blurRadius); + } + // In JS this is Image.props.loadingIndicatorSource.uri @ReactProp(name = "loadingIndicatorSrc") public void setLoadingIndicatorSource(ReactImageView view, @Nullable String source) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index e039a323a8afd8..259f177f07acc4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -46,6 +46,7 @@ import com.facebook.drawee.view.GenericDraweeView; import com.facebook.imagepipeline.common.ResizeOptions; import com.facebook.imagepipeline.image.ImageInfo; +import com.facebook.imagepipeline.postprocessors.IterativeBoxBlurPostProcessor; import com.facebook.imagepipeline.request.BasePostprocessor; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; @@ -159,6 +160,7 @@ public void process(Bitmap output, Bitmap source) { private boolean mIsDirty; private final AbstractDraweeControllerBuilder mDraweeControllerBuilder; private final RoundedCornerPostprocessor mRoundedCornerPostprocessor; + private @Nullable IterativeBoxBlurPostProcessor mIterativeBoxBlurPostProcessor; private @Nullable ControllerListener mControllerListener; private @Nullable ControllerListener mControllerForTesting; private final @Nullable Object mCallerContext; @@ -226,6 +228,16 @@ public void onFailure(String id, Throwable throwable) { mIsDirty = true; } + public void setBlurRadius(float blurRadius) { + if (blurRadius == 0) { + mIterativeBoxBlurPostProcessor = null; + } else { + mIterativeBoxBlurPostProcessor = + new IterativeBoxBlurPostProcessor((int) PixelUtil.toPixelFromDIP(blurRadius)); + } + mIsDirty = true; + } + public void setBorderColor(int borderColor) { mBorderColor = borderColor; mIsDirty = true; @@ -326,7 +338,7 @@ private void cornerRadii(float[] computedCorners) { computedCorners[2] = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius; computedCorners[3] = mBorderCornerRadii != null && !YogaConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius; } - + public void setHeaders(ReadableMap headers) { mHeaders = headers; } @@ -386,7 +398,13 @@ public void maybeUpdateView() { ? mFadeDurationMs : mImageSource.isResource() ? 0 : REMOTE_IMAGE_FADE_DURATION_MS); - Postprocessor postprocessor = usePostprocessorScaling ? mRoundedCornerPostprocessor : null; + // TODO: t13601664 Support multiple PostProcessors + Postprocessor postprocessor = null; + if (usePostprocessorScaling) { + postprocessor = mRoundedCornerPostprocessor; + } else if (mIterativeBoxBlurPostProcessor != null) { + postprocessor = mIterativeBoxBlurPostProcessor; + } ResizeOptions resizeOptions = doResize ? new ResizeOptions(getWidth(), getHeight()) : null; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostHelper.java index d95b2ee7aa887e..d63643266db40f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/modal/ModalHostHelper.java @@ -4,6 +4,8 @@ import android.annotation.TargetApi; import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Point; import android.view.Display; import android.view.WindowManager; @@ -38,12 +40,26 @@ public static Point getModalHostSize(Context context) { // getSize will return the dimensions of the screen in its current orientation display.getSize(SIZE_POINT); + int[] attrs = {android.R.attr.windowFullscreen}; + Resources.Theme theme = context.getTheme(); + TypedArray ta = theme.obtainStyledAttributes(attrs); + boolean windowFullscreen = ta.getBoolean(0, false); + + // We need to add the status bar height to the height if we have a fullscreen window, + // because Display.getCurrentSizeRange doesn't include it. + Resources resources = context.getResources(); + int statusBarId = resources.getIdentifier("status_bar_height", "dimen", "android"); + int statusBarHeight = 0; + if (windowFullscreen && statusBarId > 0) { + statusBarHeight = (int) resources.getDimension(statusBarId); + } + if (SIZE_POINT.x < SIZE_POINT.y) { // If we are vertical the width value comes from min width and height comes from max height - return new Point(MIN_POINT.x, MAX_POINT.y); + return new Point(MIN_POINT.x, MAX_POINT.y + statusBarHeight); } else { // If we are horizontal the width value comes from max width and height comes from min height - return new Point(MAX_POINT.x, MIN_POINT.y); + return new Point(MAX_POINT.x, MIN_POINT.y + statusBarHeight); } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java index 8df4adcbdd5226..fbfe8a75389937 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java @@ -81,11 +81,10 @@ public class ReactTextShadowNode extends LayoutShadowNode { public static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000; - private static final TextPaint sTextPaintInstance = new TextPaint(); - - static { - sTextPaintInstance.setFlags(TextPaint.ANTI_ALIAS_FLAG); - } + // It's important to pass the ANTI_ALIAS_FLAG flag to the constructor rather than setting it + // later by calling setFlags. This is because the latter approach triggers a bug on Android 4.4.2. + // The bug is that unicode emoticons aren't measured properly which causes text to be clipped. + private static final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); private static class SetSpanOperation { protected int start, end; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 37edf2ec668a9d..97d3b4b31ef6ac 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -367,10 +367,17 @@ public void setColor(ReactEditText view, @Nullable Integer color) { @ReactProp(name = "underlineColorAndroid", customType = "Color") public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineColor) { + // Drawable.mutate() can sometimes crash due to an AOSP bug: + // See https://code.google.com/p/android/issues/detail?id=191754 for more info + Drawable background = view.getBackground(); + Drawable drawableToMutate = background.getConstantState() != null ? + background.mutate() : + background; + if (underlineColor == null) { - view.getBackground().clearColorFilter(); + drawableToMutate.clearColorFilter(); } else { - view.getBackground().setColorFilter(underlineColor, PorterDuff.Mode.SRC_IN); + drawableToMutate.setColorFilter(underlineColor, PorterDuff.Mode.SRC_IN); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index bc18f86b01d678..ec3107f2039829 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -31,6 +31,7 @@ import android.webkit.WebViewClient; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; +import android.webkit.WebSettings; import com.facebook.common.logging.FLog; import com.facebook.react.common.ReactConstants; @@ -476,6 +477,19 @@ public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEv } } + @ReactProp(name = "mixedContentMode") + public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (mixedContentMode == null || "never".equals(mixedContentMode)) { + view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); + } else if ("always".equals(mixedContentMode)) { + view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + } else if ("compatibility".equals(mixedContentMode)) { + view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + } + } + } + @Override protected void addEventEmitters(ThemedReactContext reactContext, WebView view) { // Do not register default touch emitter and let WebView implementation handle touches diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java index a3d7699ce7d443..343f44c45569b3 100644 --- a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java +++ b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java @@ -19,9 +19,10 @@ public class Systrace { public static final long TRACE_TAG_REACT_JAVA_BRIDGE = 0L; - public static final long TRACE_TAG_REACT_FRESCO = 0L; public static final long TRACE_TAG_REACT_APPS = 0L; + public static final long TRACE_TAG_REACT_FRESCO = 0L; public static final long TRACE_TAG_REACT_VIEW = 0L; + public static final long TRACE_TAG_REACT_JSC_CALLS = 0L; public enum EventScope { THREAD('t'), @@ -88,18 +89,18 @@ public static void traceCounter( public static void startAsyncFlow( long tag, final String sectionName, - final int cookie){ + final int cookie) { } public static void stepAsyncFlow( long tag, final String sectionName, - final int cookie){ + final int cookie) { } public static void endAsyncFlow( long tag, final String sectionName, - final int cookie){ + final int cookie) { } } diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java new file mode 100644 index 00000000000000..716a212e76d515 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaConfig.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.yoga; + +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; + +@DoNotStrip +public class YogaConfig { + + static { + SoLoader.loadLibrary("yoga"); + } + + long mNativePointer; + + private native long jni_YGConfigNew(); + public YogaConfig() { + mNativePointer = jni_YGConfigNew(); + if (mNativePointer == 0) { + throw new IllegalStateException("Failed to allocate native memory"); + } + } + + private native void jni_YGConfigFree(long nativePointer); + @Override + protected void finalize() throws Throwable { + try { + jni_YGConfigFree(mNativePointer); + } finally { + super.finalize(); + } + } + + private native void jni_YGConfigSetExperimentalFeatureEnabled( + long nativePointer, + int feature, + boolean enabled); + public void setExperimentalFeatureEnabled(YogaExperimentalFeature feature, boolean enabled) { + jni_YGConfigSetExperimentalFeatureEnabled(mNativePointer, feature.intValue(), enabled); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java index 94f16a991bdc5f..3772c4f11d1084 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaNode.java @@ -35,20 +35,6 @@ public static void setLogger(YogaLogger logger) { jni_YGSetLogger(logger); } - private static native void jni_YGSetExperimentalFeatureEnabled( - int feature, - boolean enabled); - public static void setExperimentalFeatureEnabled( - YogaExperimentalFeature feature, - boolean enabled) { - jni_YGSetExperimentalFeatureEnabled(feature.intValue(), enabled); - } - - private static native boolean jni_YGIsExperimentalFeatureEnabled(int feature); - public static boolean isExperimentalFeatureEnabled(YogaExperimentalFeature feature) { - return jni_YGIsExperimentalFeatureEnabled(feature.intValue()); - } - private YogaNode mParent; private List mChildren; private YogaMeasureFunction mMeasureFunction; @@ -104,6 +90,14 @@ public YogaNode() { } } + private native long jni_YGNodeNewWithConfig(long configPointer); + public YogaNode(YogaConfig config) { + mNativePointer = jni_YGNodeNewWithConfig(config.mNativePointer); + if (mNativePointer == 0) { + throw new IllegalStateException("Failed to allocate native memory"); + } + } + private native void jni_YGNodeFree(long nativePointer); @Override protected void finalize() throws Throwable { diff --git a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp index 15f6d46f53bd63..72e704c8c82e81 100644 --- a/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp +++ b/ReactAndroid/src/main/jni/first-party/yogajni/jni/YGJNI.cpp @@ -150,6 +150,10 @@ static inline YGNodeRef _jlong2YGNodeRef(jlong addr) { return reinterpret_cast(static_cast(addr)); } +static inline YGConfigRef _jlong2YGConfigRef(jlong addr) { + return reinterpret_cast(static_cast(addr)); +} + void jni_YGSetLogger(alias_ref clazz, alias_ref logger) { if (jLogger) { jLogger->releaseAlias(); @@ -171,18 +175,6 @@ void jni_YGLog(alias_ref clazz, jint level, jstring message) { Environment::current()->ReleaseStringUTFChars(message, nMessage); } -void jni_YGSetExperimentalFeatureEnabled(alias_ref clazz, jint feature, jboolean enabled) { - YGSetExperimentalFeatureEnabled(static_cast(feature), enabled); -} - -jboolean jni_YGIsExperimentalFeatureEnabled(alias_ref clazz, jint feature) { - return YGIsExperimentalFeatureEnabled(static_cast(feature)); -} - -jint jni_YGNodeGetInstanceCount(alias_ref clazz) { - return YGNodeGetInstanceCount(); -} - jlong jni_YGNodeNew(alias_ref thiz) { const YGNodeRef node = YGNodeNew(); YGNodeSetContext(node, new weak_ref(make_weak(thiz))); @@ -190,6 +182,13 @@ jlong jni_YGNodeNew(alias_ref thiz) { return reinterpret_cast(node); } +jlong jni_YGNodeNewWithConfig(alias_ref thiz, jlong configPointer) { + const YGNodeRef node = YGNodeNewWithConfig(_jlong2YGConfigRef(configPointer)); + YGNodeSetContext(node, new weak_ref(make_weak(thiz))); + YGNodeSetPrintFunc(node, YGPrint); + return reinterpret_cast(node); +} + void jni_YGNodeFree(alias_ref thiz, jlong nativePointer) { const YGNodeRef node = _jlong2YGNodeRef(nativePointer); delete YGNodeJobject(node); @@ -368,6 +367,24 @@ YG_NODE_JNI_STYLE_UNIT_PROP(MaxHeight); // Yoga specific properties, not compatible with flexbox specification YG_NODE_JNI_STYLE_PROP(jfloat, float, AspectRatio); +jlong jni_YGConfigNew(alias_ref) { + return reinterpret_cast(YGConfigNew()); +} + +void jni_YGConfigFree(alias_ref, jlong nativePointer) { + const YGConfigRef config = _jlong2YGConfigRef(nativePointer); + YGConfigFree(config); +} + +void jni_YGConfigSetExperimentalFeatureEnabled(alias_ref, jlong nativePointer, jint feature, jboolean enabled) { + const YGConfigRef config = _jlong2YGConfigRef(nativePointer); + YGConfigSetExperimentalFeatureEnabled(config, static_cast(feature), enabled); +} + +jint jni_YGNodeGetInstanceCount(alias_ref clazz) { + return YGNodeGetInstanceCount(); +} + #define YGMakeNativeMethod(name) makeNativeMethod(#name, name) jint JNI_OnLoad(JavaVM *vm, void *) { @@ -375,6 +392,7 @@ jint JNI_OnLoad(JavaVM *vm, void *) { registerNatives("com/facebook/yoga/YogaNode", { YGMakeNativeMethod(jni_YGNodeNew), + YGMakeNativeMethod(jni_YGNodeNewWithConfig), YGMakeNativeMethod(jni_YGNodeFree), YGMakeNativeMethod(jni_YGNodeReset), YGMakeNativeMethod(jni_YGNodeInsertChild), @@ -452,8 +470,12 @@ jint JNI_OnLoad(JavaVM *vm, void *) { YGMakeNativeMethod(jni_YGNodeGetInstanceCount), YGMakeNativeMethod(jni_YGSetLogger), YGMakeNativeMethod(jni_YGLog), - YGMakeNativeMethod(jni_YGSetExperimentalFeatureEnabled), - YGMakeNativeMethod(jni_YGIsExperimentalFeatureEnabled), + }); + registerNatives("com/facebook/yoga/YogaConfig", + { + YGMakeNativeMethod(jni_YGConfigNew), + YGMakeNativeMethod(jni_YGConfigFree), + YGMakeNativeMethod(jni_YGConfigSetExperimentalFeatureEnabled), }); }); } diff --git a/ReactAndroid/src/main/jni/third-party/boost/Android.mk b/ReactAndroid/src/main/jni/third-party/boost/Android.mk index 38986d2025c4a8..35de5ae29b06d1 100644 --- a/ReactAndroid/src/main/jni/third-party/boost/Android.mk +++ b/ReactAndroid/src/main/jni/third-party/boost/Android.mk @@ -1,11 +1,11 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_C_INCLUDES := $(LOCAL_PATH)/boost_1_57_0 -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/boost_1_57_0 +LOCAL_C_INCLUDES := $(LOCAL_PATH)/boost_1_63_0 +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/boost_1_63_0 CXX11_FLAGS := -std=gnu++11 LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) LOCAL_MODULE := boost -include $(BUILD_STATIC_LIBRARY) \ No newline at end of file +include $(BUILD_STATIC_LIBRARY) diff --git a/ReactAndroid/src/main/jni/third-party/glibc/BUCK b/ReactAndroid/src/main/jni/third-party/glibc/BUCK index 897e33781299cb..a2c4c5bf8abcef 100644 --- a/ReactAndroid/src/main/jni/third-party/glibc/BUCK +++ b/ReactAndroid/src/main/jni/third-party/glibc/BUCK @@ -52,9 +52,9 @@ prebuilt_cxx_library( name = "rt", exported_platform_linker_flags = [ ( - "android", + "android", # Empty, since `-lc` is implicit [], - ), # Empty, since `-lc` is implicit + ), ( "default", ["-lrt"], diff --git a/ReactAndroid/src/main/jni/xreact/jni/BUCK b/ReactAndroid/src/main/jni/xreact/jni/BUCK index dab0e2a6473b4f..6c2be6899b28a4 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/BUCK +++ b/ReactAndroid/src/main/jni/xreact/jni/BUCK @@ -47,6 +47,7 @@ cxx_library( "//native/fb:fb", "//native/third-party/android-ndk:android", "//xplat/folly:molly", + "//xplat/fbgloginit:fbgloginit", "//xplat/fbsystrace:fbsystrace", react_native_xplat_target("cxxreact:bridge"), react_native_xplat_target("cxxreact:module"), diff --git a/ReactAndroid/src/main/jni/xreact/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/xreact/jni/OnLoad.cpp index 8ea65c6b70e333..dc084b1238d148 100644 --- a/ReactAndroid/src/main/jni/xreact/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/xreact/jni/OnLoad.cpp @@ -2,8 +2,8 @@ #include #include +#include #include -#include #include #include #include @@ -73,9 +73,15 @@ static JSValueRef nativePerformanceNow( static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL; static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL; - // This is equivalent to android.os.SystemClock.elapsedRealtime() in native + // Since SystemClock.uptimeMillis() is commonly used for performance measurement in Java + // and uptimeMillis() internally uses clock_gettime(CLOCK_MONOTONIC), + // we use the same API here. + // We need that to make sure we use the same time system on both JS and Java sides. + // Links to the source code: + // https://android.googlesource.com/platform/frameworks/native/+/jb-mr1-release/libs/utils/SystemClock.cpp + // https://android.googlesource.com/platform/system/core/+/master/libutils/Timers.cpp struct timespec now; - clock_gettime(CLOCK_MONOTONIC_RAW, &now); + clock_gettime(CLOCK_MONOTONIC, &now); int64_t nano = now.tv_sec * NANOSECONDS_IN_SECOND + now.tv_nsec; return Value::makeNumber(ctx, (nano / (double)NANOSECONDS_IN_MILLISECOND)); } @@ -145,7 +151,8 @@ class JReactMarker : public JavaClass { } extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { - return xplat::initialize(vm, [] { + return initialize(vm, [] { + gloginit::initialize(); // Inject some behavior into react/ ReactMarker::logMarker = JReactMarker::logMarker; WebWorkerUtil::createWebWorkerThread = WebWorkers::createWebWorkerThread; diff --git a/ReactAndroid/src/main/res/devsupport/layout/dev_loading_view.xml b/ReactAndroid/src/main/res/devsupport/layout/dev_loading_view.xml new file mode 100644 index 00000000000000..c503c9c7d01e5e --- /dev/null +++ b/ReactAndroid/src/main/res/devsupport/layout/dev_loading_view.xml @@ -0,0 +1,14 @@ + + + diff --git a/ReactAndroid/src/main/res/devsupport/values/strings.xml b/ReactAndroid/src/main/res/devsupport/values/strings.xml index dbc3343ca28445..b6811017a125f6 100644 --- a/ReactAndroid/src/main/res/devsupport/values/strings.xml +++ b/ReactAndroid/src/main/res/devsupport/values/strings.xml @@ -11,8 +11,6 @@ Hide Perf Monitor Dev Settings Catalyst Dev Settings - Please wait… - Fetching JS bundle Unable to download JS bundle. Did you forget to start the development server or connect your device? Connecting to remote debugger Unable to connect with remote debugger @@ -23,4 +21,5 @@ Start/Stop Sampling Profiler Copy Report + Loading from %1$s… diff --git a/ReactAndroid/src/main/third-party/android/support-annotations/BUCK b/ReactAndroid/src/main/third-party/android/support-annotations/BUCK index da6fe49d66dcf5..bc18a5b2726b48 100644 --- a/ReactAndroid/src/main/third-party/android/support-annotations/BUCK +++ b/ReactAndroid/src/main/third-party/android/support-annotations/BUCK @@ -6,6 +6,6 @@ prebuilt_jar( remote_file( name = "support-annotations-binary-aar", - sha1 = "ffbe55fdb2bb456b1485831706a9eac3300bb6b8", - url = "mvn:com.android.support:support-annotations:jar:23.4.0", + sha1 = "1fce89a6428c51467090d7f424e4c9c3dbd55f7e", + url = "mvn:com.android.support:support-annotations:jar:23.0.1", ) diff --git a/ReactAndroid/src/main/third-party/android/support/v4/BUCK b/ReactAndroid/src/main/third-party/android/support/v4/BUCK index 065cc8d724d18f..92f5422141cbe1 100644 --- a/ReactAndroid/src/main/third-party/android/support/v4/BUCK +++ b/ReactAndroid/src/main/third-party/android/support/v4/BUCK @@ -6,18 +6,6 @@ android_prebuilt_aar( remote_file( name = "lib-support-v4-binary-aar", - sha1 = "7a802deefef9561d90a440994c3e6eed81f2c241", - url = "mvn:com.android.support:support-v4:aar:23.4.0", -) - -prebuilt_jar( - name = "lib-support-annotations", - binary_jar = ":lib-support-annotations-binary-aar", - visibility = ["//ReactAndroid/..."], -) - -remote_file( - name = "lib-support-annotations-binary-aar", - sha1 = "ffbe55fdb2bb456b1485831706a9eac3300bb6b8", - url = "mvn:com.android.support:support-annotations:jar:23.4.0", + sha1 = "9e8da0e4ecf9f63258c7fbd273889252cba2d0c3", + url = "mvn:com.android.support:support-v4:aar:23.0.1", ) diff --git a/ReactAndroid/src/main/third-party/android/support/v7/recyclerview/BUCK b/ReactAndroid/src/main/third-party/android/support/v7/recyclerview/BUCK deleted file mode 100644 index 29fc09cf7ca73d..00000000000000 --- a/ReactAndroid/src/main/third-party/android/support/v7/recyclerview/BUCK +++ /dev/null @@ -1,13 +0,0 @@ -android_prebuilt_aar( - name = "recyclerview", - aar = ":recyclerview-binary-aar", - visibility = [ - "//ReactAndroid/...", - ], -) - -remote_file( - name = "recyclerview-binary-aar", - sha1 = "61e4d99d2377402c45a3176120f800e53b20ab1b", - url = "mvn:com.android.support:recyclerview-v7:aar:23.4.0", -) diff --git a/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK b/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK index 0c1403ea8a79b7..5d0cf4b931efd0 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/devsupport/BUCK @@ -16,6 +16,7 @@ rn_robolectric_test( react_native_dep("third-party/java/mockito:mockito"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okhttp:okhttp3-ws"), + react_native_dep("third-party/java/okio:okio"), react_native_dep("third-party/java/robolectric3/robolectric:robolectric"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.java b/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.java new file mode 100644 index 00000000000000..08089511f7bfe1 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/devsupport/MultipartStreamReaderTest.java @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.devsupport; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.io.IOException; +import java.util.Map; + +import okio.Buffer; +import okio.ByteString; + +import static org.fest.assertions.api.Assertions.assertThat; + +@RunWith(RobolectricTestRunner.class) +public class MultipartStreamReaderTest { + + class CallCountTrackingChunkCallback implements MultipartStreamReader.ChunkCallback { + private int mCount = 0; + + @Override + public void execute(Map headers, Buffer body, boolean done) throws IOException { + mCount++; + } + + public int getCallCount() { + return mCount; + } + } + + @Test + public void testSimpleCase() throws IOException { + ByteString response = ByteString.encodeUtf8( + "preable, should be ignored\r\n" + + "--sample_boundary\r\n" + + "Content-Type: application/json; charset=utf-8\r\n" + + "Content-Length: 2\r\n\r\n" + + "{}\r\n" + + "--sample_boundary--\r\n" + + "epilogue, should be ignored"); + + Buffer source = new Buffer(); + source.write(response); + + MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); + + CallCountTrackingChunkCallback callback = new CallCountTrackingChunkCallback() { + @Override + public void execute(Map headers, Buffer body, boolean done) throws IOException { + super.execute(headers, body, done); + + assertThat(done).isTrue(); + assertThat(headers.get("Content-Type")).isEqualTo("application/json; charset=utf-8"); + assertThat(body.readUtf8()).isEqualTo("{}"); + } + }; + boolean success = reader.readAllParts(callback); + + assertThat(callback.getCallCount()).isEqualTo(1); + assertThat(success).isTrue(); + } + + @Test + public void testMultipleParts() throws IOException { + ByteString response = ByteString.encodeUtf8( + "preable, should be ignored\r\n" + + "--sample_boundary\r\n" + + "1\r\n" + + "--sample_boundary\r\n" + + "2\r\n" + + "--sample_boundary\r\n" + + "3\r\n" + + "--sample_boundary--\r\n" + + "epilogue, should be ignored"); + + Buffer source = new Buffer(); + source.write(response); + + MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); + + CallCountTrackingChunkCallback callback = new CallCountTrackingChunkCallback() { + @Override + public void execute(Map headers, Buffer body, boolean done) throws IOException { + super.execute(headers, body, done); + + assertThat(done).isEqualTo(getCallCount() == 3); + assertThat(body.readUtf8()).isEqualTo(String.valueOf(getCallCount())); + } + }; + boolean success = reader.readAllParts(callback); + + assertThat(callback.getCallCount()).isEqualTo(3); + assertThat(success).isTrue(); + } + + @Test + public void testNoDelimiter() throws IOException { + ByteString response = ByteString.encodeUtf8("Yolo"); + + Buffer source = new Buffer(); + source.write(response); + + MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); + + CallCountTrackingChunkCallback callback = new CallCountTrackingChunkCallback(); + boolean success = reader.readAllParts(callback); + + assertThat(callback.getCallCount()).isEqualTo(0); + assertThat(success).isFalse(); + } + + @Test + public void testNoCloseDelimiter() throws IOException { + ByteString response = ByteString.encodeUtf8( + "preable, should be ignored\r\n" + + "--sample_boundary\r\n" + + "Content-Type: application/json; charset=utf-8\r\n" + + "Content-Length: 2\r\n\r\n" + + "{}\r\n" + + "--sample_boundary\r\n" + + "incomplete message..."); + + Buffer source = new Buffer(); + source.write(response); + + MultipartStreamReader reader = new MultipartStreamReader(source, "sample_boundary"); + + CallCountTrackingChunkCallback callback = new CallCountTrackingChunkCallback(); + boolean success = reader.readAllParts(callback); + + assertThat(callback.getCallCount()).isEqualTo(1); + assertThat(success).isFalse(); + } +} diff --git a/ReactCommon/jschelpers/BUCK b/ReactCommon/jschelpers/BUCK index b829b95dfef4cf..82577f4f475648 100644 --- a/ReactCommon/jschelpers/BUCK +++ b/ReactCommon/jschelpers/BUCK @@ -7,51 +7,89 @@ EXPORTED_HEADERS = [ "Value.h", ] +EXPORTED_HEADER_MAP = subdir_glob( + (("", header) for header in EXPORTED_HEADERS), + prefix = "jschelpers", +) + if THIS_IS_FBANDROID: - include_defs('//ReactAndroid/DEFS') + include_defs("//ReactAndroid/DEFS") cxx_library( - name = 'jschelpers', + name = "jscinternalhelpers", force_static = True, compiler_flags = [ - '-Wall', - '-fexceptions', - '-fvisibility=hidden', - '-std=c++1y', + "-Wall", + "-fexceptions", + "-fvisibility=hidden", + "-std=c++1y", ], exported_headers = EXPORTED_HEADERS, - headers = glob(['*.h'], excludes=EXPORTED_HEADERS), - header_namespace = 'jschelpers', - srcs = glob(['*.cpp']), - deps = JSC_DEPS + [ - '//xplat/folly:molly', + headers = glob(["*.h"], excludes=EXPORTED_HEADERS), + header_namespace = "jschelpers", + srcs = glob(["*.cpp"], excludes=["systemJSCWrapper.cpp"]), + deps = JSC_INTERNAL_DEPS + [ + "//xplat/folly:molly", + ], + visibility = [ + "PUBLIC", + ], + ) + + cxx_library( + name = "jschelpers", + force_static = True, + compiler_flags = [ + "-Wall", + "-fexceptions", + "-fvisibility=hidden", + "-std=c++1y", ], + srcs = [], + deps = [ ":jscinternalhelpers" ], visibility = [ - 'PUBLIC', + "PUBLIC", ], ) if THIS_IS_FBOBJC: + ios_library( - name = 'jschelpers', + name = "jscinternalhelpers", inherited_buck_flags = STATIC_LIBRARY_IOS_FLAGS, compiler_flags = [ - '-Wall', - '-fexceptions', - '-fvisibility=hidden', - '-std=c++1y', - ], - exported_headers = EXPORTED_HEADERS, - headers = glob(['*.h'], excludes=EXPORTED_HEADERS), - header_namespace = 'jschelpers', - srcs = glob(['*.cpp']), - frameworks = [ - '$SDKROOT/System/Library/Frameworks/JavaScriptCore.framework', + "-Wall", + "-fexceptions", + "-fvisibility=hidden", + "-std=c++1y", ], + exported_headers = EXPORTED_HEADER_MAP, + headers = subdir_glob([("", "*.h")], excludes=EXPORTED_HEADERS, prefix="jschelpers"), + header_namespace = "jschelpers", + srcs = glob(["*.cpp"], excludes=["systemJSCWrapper.cpp"]), deps = [ - '//xplat/folly:molly', + "//xplat/folly:molly", + ], + visibility = [ + "PUBLIC", + ], + ) + + ios_library( + name = "jschelpers", + inherited_buck_flags = STATIC_LIBRARY_IOS_FLAGS, + compiler_flags = [ + "-Wall", + "-fexceptions", + "-fvisibility=hidden", + "-std=c++1y", + ], + srcs = ["systemJSCWrapper.cpp"], + frameworks = [ + "$SDKROOT/System/Library/Frameworks/JavaScriptCore.framework", ], + deps = [ ":jscinternalhelpers" ], visibility = [ - 'PUBLIC', + "PUBLIC", ], ) diff --git a/ReactCommon/jschelpers/JSCWrapper.cpp b/ReactCommon/jschelpers/JSCWrapper.cpp index 0da38ae845ce18..422fb7f8ea4e06 100644 --- a/ReactCommon/jschelpers/JSCWrapper.cpp +++ b/ReactCommon/jschelpers/JSCWrapper.cpp @@ -11,8 +11,6 @@ #if defined(__APPLE__) -#include - // TODO: use glog in OSS too #if __has_include() #define USE_GLOG 1 @@ -21,36 +19,11 @@ #define USE_GLOG 0 #endif -#include - -// Crash the app (with a descriptive stack trace) if a function that is not supported by -// the system JSC is called. -#define UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(FUNC_NAME) \ -static void Unimplemented_##FUNC_NAME(__unused void* args...) { \ - assert(false); \ -} - -UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSEvaluateBytecodeBundle) -#if WITH_FBJSCEXTENSIONS -UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSStringCreateWithUTF8CStringExpectAscii) -#endif -UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSPokeSamplingProfiler) -UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSStartSamplingProfilingOnMainJSCThread) -UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(configureJSCForIOS) - -bool JSSamplingProfilerEnabled() { - return false; -} - -const int32_t JSNoBytecodeFileFormatVersion = -1; - namespace facebook { namespace react { static const JSCWrapper* s_customWrapper = nullptr; -static JSCWrapper s_systemWrapper = {}; - bool isCustomJSCWrapperSet() { return s_customWrapper != nullptr; } @@ -69,96 +42,6 @@ void setCustomJSCWrapper(const JSCWrapper* wrapper) { s_customWrapper = wrapper; } -const JSCWrapper* systemJSCWrapper() { - // Note that this is not used on Android. All methods are statically linked instead. - // Some fields are lazily initialized - static std::once_flag flag; - std::call_once(flag, []() { - s_systemWrapper = { - .JSGlobalContextCreateInGroup = JSGlobalContextCreateInGroup, - .JSGlobalContextRelease = JSGlobalContextRelease, - .JSGlobalContextSetName = JSGlobalContextSetName, - - .JSContextGetGlobalContext = JSContextGetGlobalContext, - .JSContextGetGlobalObject = JSContextGetGlobalObject, - - .JSEvaluateScript = JSEvaluateScript, - .JSEvaluateBytecodeBundle = - (decltype(&JSEvaluateBytecodeBundle)) - Unimplemented_JSEvaluateBytecodeBundle, - - .JSStringCreateWithUTF8CString = JSStringCreateWithUTF8CString, - .JSStringCreateWithCFString = JSStringCreateWithCFString, - #if WITH_FBJSCEXTENSIONS - .JSStringCreateWithUTF8CStringExpectAscii = - (decltype(&JSStringCreateWithUTF8CStringExpectAscii)) - Unimplemented_JSStringCreateWithUTF8CStringExpectAscii, - #endif - .JSStringCopyCFString = JSStringCopyCFString, - .JSStringGetCharactersPtr = JSStringGetCharactersPtr, - .JSStringGetLength = JSStringGetLength, - .JSStringGetMaximumUTF8CStringSize = JSStringGetMaximumUTF8CStringSize, - .JSStringIsEqualToUTF8CString = JSStringIsEqualToUTF8CString, - .JSStringRelease = JSStringRelease, - .JSStringRetain = JSStringRetain, - - .JSClassCreate = JSClassCreate, - .JSClassRelease = JSClassRelease, - - .JSObjectCallAsConstructor = JSObjectCallAsConstructor, - .JSObjectCallAsFunction = JSObjectCallAsFunction, - .JSObjectGetPrivate = JSObjectGetPrivate, - .JSObjectGetProperty = JSObjectGetProperty, - .JSObjectGetPropertyAtIndex = JSObjectGetPropertyAtIndex, - .JSObjectIsConstructor = JSObjectIsConstructor, - .JSObjectIsFunction = JSObjectIsFunction, - .JSObjectMake = JSObjectMake, - .JSObjectMakeArray = JSObjectMakeArray, - .JSObjectMakeError = JSObjectMakeError, - .JSObjectMakeFunctionWithCallback = JSObjectMakeFunctionWithCallback, - .JSObjectSetPrivate = JSObjectSetPrivate, - .JSObjectSetProperty = JSObjectSetProperty, - - .JSObjectCopyPropertyNames = JSObjectCopyPropertyNames, - .JSPropertyNameArrayGetCount = JSPropertyNameArrayGetCount, - .JSPropertyNameArrayGetNameAtIndex = JSPropertyNameArrayGetNameAtIndex, - .JSPropertyNameArrayRelease = JSPropertyNameArrayRelease, - - .JSValueCreateJSONString = JSValueCreateJSONString, - .JSValueGetType = JSValueGetType, - .JSValueMakeFromJSONString = JSValueMakeFromJSONString, - .JSValueMakeBoolean = JSValueMakeBoolean, - .JSValueMakeNull = JSValueMakeNull, - .JSValueMakeNumber = JSValueMakeNumber, - .JSValueMakeString = JSValueMakeString, - .JSValueMakeUndefined = JSValueMakeUndefined, - .JSValueProtect = JSValueProtect, - .JSValueToBoolean = JSValueToBoolean, - .JSValueToNumber = JSValueToNumber, - .JSValueToObject = JSValueToObject, - .JSValueToStringCopy = JSValueToStringCopy, - .JSValueUnprotect = JSValueUnprotect, - - .JSSamplingProfilerEnabled = JSSamplingProfilerEnabled, - .JSPokeSamplingProfiler = - (decltype(&JSPokeSamplingProfiler)) - Unimplemented_JSPokeSamplingProfiler, - .JSStartSamplingProfilingOnMainJSCThread = - (decltype(&JSStartSamplingProfilingOnMainJSCThread)) - Unimplemented_JSStartSamplingProfilingOnMainJSCThread, - - .configureJSCForIOS = - (decltype(&configureJSCForIOS))Unimplemented_configureJSCForIOS, - - .JSContext = objc_getClass("JSContext"), - .JSValue = objc_getClass("JSValue"), - - .JSBytecodeFileFormatVersion = JSNoBytecodeFileFormatVersion, - }; - }); - return &s_systemWrapper; -} - } } #endif diff --git a/ReactCommon/jschelpers/systemJSCWrapper.cpp b/ReactCommon/jschelpers/systemJSCWrapper.cpp new file mode 100644 index 00000000000000..80000c3fa9a21e --- /dev/null +++ b/ReactCommon/jschelpers/systemJSCWrapper.cpp @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include + +#if defined(__APPLE__) + +#include + +#include + +// Crash the app (with a descriptive stack trace) if a function that is not supported by +// the system JSC is called. +#define UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(FUNC_NAME) \ +static void Unimplemented_##FUNC_NAME(__unused void* args...) { \ + assert(false); \ +} + +UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSEvaluateBytecodeBundle) +#if WITH_FBJSCEXTENSIONS +UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSStringCreateWithUTF8CStringExpectAscii) +#endif +UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSPokeSamplingProfiler) +UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(JSStartSamplingProfilingOnMainJSCThread) +UNIMPLEMENTED_SYSTEM_JSC_FUNCTION(configureJSCForIOS) + +bool JSSamplingProfilerEnabled() { + return false; +} + +const int32_t JSNoBytecodeFileFormatVersion = -1; + +namespace facebook { +namespace react { + +static JSCWrapper s_systemWrapper = {}; + +const JSCWrapper* systemJSCWrapper() { + // Note that this is not used on Android. All methods are statically linked instead. + // Some fields are lazily initialized + static std::once_flag flag; + std::call_once(flag, []() { + s_systemWrapper = { + .JSGlobalContextCreateInGroup = JSGlobalContextCreateInGroup, + .JSGlobalContextRelease = JSGlobalContextRelease, + .JSGlobalContextSetName = JSGlobalContextSetName, + + .JSContextGetGlobalContext = JSContextGetGlobalContext, + .JSContextGetGlobalObject = JSContextGetGlobalObject, + + .JSEvaluateScript = JSEvaluateScript, + .JSEvaluateBytecodeBundle = + (decltype(&JSEvaluateBytecodeBundle)) + Unimplemented_JSEvaluateBytecodeBundle, + + .JSStringCreateWithUTF8CString = JSStringCreateWithUTF8CString, + .JSStringCreateWithCFString = JSStringCreateWithCFString, + #if WITH_FBJSCEXTENSIONS + .JSStringCreateWithUTF8CStringExpectAscii = + (decltype(&JSStringCreateWithUTF8CStringExpectAscii)) + Unimplemented_JSStringCreateWithUTF8CStringExpectAscii, + #endif + .JSStringCopyCFString = JSStringCopyCFString, + .JSStringGetCharactersPtr = JSStringGetCharactersPtr, + .JSStringGetLength = JSStringGetLength, + .JSStringGetMaximumUTF8CStringSize = JSStringGetMaximumUTF8CStringSize, + .JSStringIsEqualToUTF8CString = JSStringIsEqualToUTF8CString, + .JSStringRelease = JSStringRelease, + .JSStringRetain = JSStringRetain, + + .JSClassCreate = JSClassCreate, + .JSClassRelease = JSClassRelease, + + .JSObjectCallAsConstructor = JSObjectCallAsConstructor, + .JSObjectCallAsFunction = JSObjectCallAsFunction, + .JSObjectGetPrivate = JSObjectGetPrivate, + .JSObjectGetProperty = JSObjectGetProperty, + .JSObjectGetPropertyAtIndex = JSObjectGetPropertyAtIndex, + .JSObjectIsConstructor = JSObjectIsConstructor, + .JSObjectIsFunction = JSObjectIsFunction, + .JSObjectMake = JSObjectMake, + .JSObjectMakeArray = JSObjectMakeArray, + .JSObjectMakeError = JSObjectMakeError, + .JSObjectMakeFunctionWithCallback = JSObjectMakeFunctionWithCallback, + .JSObjectSetPrivate = JSObjectSetPrivate, + .JSObjectSetProperty = JSObjectSetProperty, + + .JSObjectCopyPropertyNames = JSObjectCopyPropertyNames, + .JSPropertyNameArrayGetCount = JSPropertyNameArrayGetCount, + .JSPropertyNameArrayGetNameAtIndex = JSPropertyNameArrayGetNameAtIndex, + .JSPropertyNameArrayRelease = JSPropertyNameArrayRelease, + + .JSValueCreateJSONString = JSValueCreateJSONString, + .JSValueGetType = JSValueGetType, + .JSValueMakeFromJSONString = JSValueMakeFromJSONString, + .JSValueMakeBoolean = JSValueMakeBoolean, + .JSValueMakeNull = JSValueMakeNull, + .JSValueMakeNumber = JSValueMakeNumber, + .JSValueMakeString = JSValueMakeString, + .JSValueMakeUndefined = JSValueMakeUndefined, + .JSValueProtect = JSValueProtect, + .JSValueToBoolean = JSValueToBoolean, + .JSValueToNumber = JSValueToNumber, + .JSValueToObject = JSValueToObject, + .JSValueToStringCopy = JSValueToStringCopy, + .JSValueUnprotect = JSValueUnprotect, + + .JSSamplingProfilerEnabled = JSSamplingProfilerEnabled, + .JSPokeSamplingProfiler = + (decltype(&JSPokeSamplingProfiler)) + Unimplemented_JSPokeSamplingProfiler, + .JSStartSamplingProfilingOnMainJSCThread = + (decltype(&JSStartSamplingProfilingOnMainJSCThread)) + Unimplemented_JSStartSamplingProfilingOnMainJSCThread, + + .configureJSCForIOS = + (decltype(&configureJSCForIOS))Unimplemented_configureJSCForIOS, + + .JSContext = objc_getClass("JSContext"), + .JSValue = objc_getClass("JSValue"), + + .JSBytecodeFileFormatVersion = JSNoBytecodeFileFormatVersion, + }; + }); + return &s_systemWrapper; +} + +} } + +#endif diff --git a/ReactCommon/yoga/yoga/YGMacros.h b/ReactCommon/yoga/yoga/YGMacros.h index 10580db5e7ee00..2d8f72868f7950 100644 --- a/ReactCommon/yoga/yoga/YGMacros.h +++ b/ReactCommon/yoga/yoga/YGMacros.h @@ -23,6 +23,14 @@ #define WIN_EXPORT #endif +#ifdef WINARMDLL +#define WIN_STRUCT(type) type* +#define WIN_STRUCT_REF(value) &value +#else +#define WIN_STRUCT(type) type +#define WIN_STRUCT_REF(value) value +#endif + #ifndef FB_ASSERTIONS_ENABLED #define FB_ASSERTIONS_ENABLED 1 #endif diff --git a/ReactCommon/yoga/yoga/Yoga.c b/ReactCommon/yoga/yoga/Yoga.c index f4de9b95c94e32..dd3ebd9d2b0472 100644 --- a/ReactCommon/yoga/yoga/Yoga.c +++ b/ReactCommon/yoga/yoga/Yoga.c @@ -94,6 +94,12 @@ typedef struct YGStyle { float aspectRatio; } YGStyle; +typedef struct YGConfig { + bool experimentalFeatures[YGExperimentalFeatureCount + 1]; + bool useWebDefaults; + float pointScaleFactor; +} YGConfig; + typedef struct YGNode { YGStyle style; YGLayout layout; @@ -107,6 +113,7 @@ typedef struct YGNode { YGMeasureFunc measure; YGBaselineFunc baseline; YGPrintFunc print; + YGConfigRef config; void *context; bool isDirty; @@ -141,6 +148,7 @@ typedef struct YGNode { static const float kDefaultFlexGrow = 0.0f; static const float kDefaultFlexShrink = 0.0f; +static const float kWebDefaultFlexShrink = 1.0f; static YGNode gYGNodeDefaults = { .parent = NULL, @@ -191,6 +199,17 @@ static YGNode gYGNodeDefaults = { }, }; +static YGConfig gYGConfigDefaults = { + .experimentalFeatures = + { + [YGExperimentalFeatureRounding] = false, + [YGExperimentalFeatureMinFlexFix] = false, + [YGExperimentalFeatureWebFlexBasis] = false, + }, + .useWebDefaults = false, + .pointScaleFactor = 1.0f +}; + static void YGNodeMarkDirtyInternal(const YGNodeRef node); YGMalloc gYGMalloc = &malloc; @@ -271,7 +290,7 @@ static inline const YGValue *YGComputedEdgeValue(const YGValue edges[YGEdgeCount return defaultValue; } -static inline float YGValueResolve(const YGValue *const value, const float parentSize) { +static inline float YGResolveValue(const YGValue *const value, const float parentSize) { switch (value->unit) { case YGUnitUndefined: case YGUnitAuto: @@ -284,21 +303,30 @@ static inline float YGValueResolve(const YGValue *const value, const float paren return YGUndefined; } -static inline float YGValueResolveMargin(const YGValue *const value, const float parentSize) { - return value->unit == YGUnitAuto ? 0 : YGValueResolve(value, parentSize); +static inline float YGResolveValueMargin(const YGValue *const value, const float parentSize) { + return value->unit == YGUnitAuto ? 0 : YGResolveValue(value, parentSize); } int32_t gNodeInstanceCount = 0; -YGNodeRef YGNodeNew(void) { +WIN_EXPORT YGNodeRef YGNodeNewWithConfig(const YGConfigRef config) { const YGNodeRef node = gYGMalloc(sizeof(YGNode)); YG_ASSERT(node, "Could not allocate memory for node"); gNodeInstanceCount++; memcpy(node, &gYGNodeDefaults, sizeof(YGNode)); + if (config->useWebDefaults) { + node->style.flexDirection = YGFlexDirectionRow; + node->style.alignContent = YGAlignStretch; + } + node->config = config; return node; } +YGNodeRef YGNodeNew(void) { + return YGNodeNewWithConfig(&gYGConfigDefaults); +} + void YGNodeFree(const YGNodeRef node) { if (node->parent) { YGNodeListDelete(node->parent->children, node); @@ -331,13 +359,27 @@ void YGNodeReset(const YGNodeRef node) { YG_ASSERT(node->parent == NULL, "Cannot reset a node still attached to a parent"); YGNodeListFree(node->children); + + const YGConfigRef config = node->config; memcpy(node, &gYGNodeDefaults, sizeof(YGNode)); + node->config = config; } int32_t YGNodeGetInstanceCount(void) { return gNodeInstanceCount; } +YGConfigRef YGConfigNew(void) { + const YGConfigRef config = gYGMalloc(sizeof(YGConfig)); + YG_ASSERT(config, "Could not allocate memory for config"); + memcpy(config, &gYGConfigDefaults, sizeof(YGConfig)); + return config; +} + +void YGConfigFree(const YGConfigRef config) { + gYGFree(config); +} + static void YGNodeMarkDirtyInternal(const YGNodeRef node) { if (!node->isDirty) { node->isDirty = true; @@ -431,17 +473,17 @@ float YGNodeStyleGetFlexGrow(const YGNodeRef node) { } float YGNodeStyleGetFlexShrink(const YGNodeRef node) { - return YGFloatIsUndefined(node->style.flexShrink) ? kDefaultFlexShrink : node->style.flexShrink; + return YGFloatIsUndefined(node->style.flexShrink) ? (node->config->useWebDefaults ? kWebDefaultFlexShrink : kDefaultFlexShrink) : node->style.flexShrink; } static inline float YGNodeResolveFlexShrink(const YGNodeRef node) { if (!YGFloatIsUndefined(node->style.flexShrink)) { return node->style.flexShrink; } - if (!YGFloatIsUndefined(node->style.flex) && node->style.flex < 0.0f) { + if (!node->config->useWebDefaults && !YGFloatIsUndefined(node->style.flex) && node->style.flex < 0.0f) { return -node->style.flex; } - return kDefaultFlexShrink; + return node->config->useWebDefaults ? kWebDefaultFlexShrink : kDefaultFlexShrink; } static inline const YGValue *YGNodeResolveFlexBasisPtr(const YGNodeRef node) { @@ -449,7 +491,7 @@ static inline const YGValue *YGNodeResolveFlexBasisPtr(const YGNodeRef node) { return &node->style.flexBasis; } if (!YGFloatIsUndefined(node->style.flex) && node->style.flex > 0.0f) { - return &YGValueZero; + return node->config->useWebDefaults ? &YGValueAuto : &YGValueZero; } return &YGValueAuto; } @@ -572,8 +614,8 @@ static inline const YGValue *YGNodeResolveFlexBasisPtr(const YGNodeRef node) { } \ } \ \ - type YGNodeStyleGet##name(const YGNodeRef node, const YGEdge edge) { \ - return node->style.instanceName[edge]; \ + WIN_STRUCT(type) YGNodeStyleGet##name(const YGNodeRef node, const YGEdge edge) { \ + return WIN_STRUCT_REF(node->style.instanceName[edge]); \ } #define YG_NODE_STYLE_EDGE_PROPERTY_IMPL(type, name, paramName, instanceName) \ @@ -678,7 +720,8 @@ bool YGLayoutNodeInternal(const YGNodeRef node, const float parentWidth, const float parentHeight, const bool performLayout, - const char *reason); + const char *reason, + const YGConfigRef config); inline bool YGFloatIsUndefined(const float value) { return isnan(value); @@ -958,10 +1001,10 @@ static inline float YGNodeLeadingMargin(const YGNodeRef node, const YGFlexDirection axis, const float widthSize) { if (YGFlexDirectionIsRow(axis) && node->style.margin[YGEdgeStart].unit != YGUnitUndefined) { - return YGValueResolveMargin(&node->style.margin[YGEdgeStart], widthSize); + return YGResolveValueMargin(&node->style.margin[YGEdgeStart], widthSize); } - return YGValueResolveMargin(YGComputedEdgeValue(node->style.margin, leading[axis], &YGValueZero), + return YGResolveValueMargin(YGComputedEdgeValue(node->style.margin, leading[axis], &YGValueZero), widthSize); } @@ -969,10 +1012,10 @@ static float YGNodeTrailingMargin(const YGNodeRef node, const YGFlexDirection axis, const float widthSize) { if (YGFlexDirectionIsRow(axis) && node->style.margin[YGEdgeEnd].unit != YGUnitUndefined) { - return YGValueResolveMargin(&node->style.margin[YGEdgeEnd], widthSize); + return YGResolveValueMargin(&node->style.margin[YGEdgeEnd], widthSize); } - return YGValueResolveMargin(YGComputedEdgeValue(node->style.margin, trailing[axis], &YGValueZero), + return YGResolveValueMargin(YGComputedEdgeValue(node->style.margin, trailing[axis], &YGValueZero), widthSize); } @@ -980,11 +1023,11 @@ static float YGNodeLeadingPadding(const YGNodeRef node, const YGFlexDirection axis, const float widthSize) { if (YGFlexDirectionIsRow(axis) && node->style.padding[YGEdgeStart].unit != YGUnitUndefined && - YGValueResolve(&node->style.padding[YGEdgeStart], widthSize) >= 0.0f) { - return YGValueResolve(&node->style.padding[YGEdgeStart], widthSize); + YGResolveValue(&node->style.padding[YGEdgeStart], widthSize) >= 0.0f) { + return YGResolveValue(&node->style.padding[YGEdgeStart], widthSize); } - return fmaxf(YGValueResolve(YGComputedEdgeValue(node->style.padding, leading[axis], &YGValueZero), + return fmaxf(YGResolveValue(YGComputedEdgeValue(node->style.padding, leading[axis], &YGValueZero), widthSize), 0.0f); } @@ -993,11 +1036,11 @@ static float YGNodeTrailingPadding(const YGNodeRef node, const YGFlexDirection axis, const float widthSize) { if (YGFlexDirectionIsRow(axis) && node->style.padding[YGEdgeEnd].unit != YGUnitUndefined && - YGValueResolve(&node->style.padding[YGEdgeEnd], widthSize) >= 0.0f) { - return YGValueResolve(&node->style.padding[YGEdgeEnd], widthSize); + YGResolveValue(&node->style.padding[YGEdgeEnd], widthSize) >= 0.0f) { + return YGResolveValue(&node->style.padding[YGEdgeEnd], widthSize); } - return fmaxf(YGValueResolve(YGComputedEdgeValue(node->style.padding, trailing[axis], &YGValueZero), + return fmaxf(YGResolveValue(YGComputedEdgeValue(node->style.padding, trailing[axis], &YGValueZero), widthSize), 0.0f); } @@ -1100,7 +1143,7 @@ static float YGBaseline(const YGNodeRef node) { return baseline + baselineChild->layout.position[YGEdgeTop]; } -static inline YGFlexDirection YGFlexDirectionResolve(const YGFlexDirection flexDirection, +static inline YGFlexDirection YGResolveFlexDirection(const YGFlexDirection flexDirection, const YGDirection direction) { if (direction == YGDirectionRTL) { if (flexDirection == YGFlexDirectionRow) { @@ -1116,7 +1159,7 @@ static inline YGFlexDirection YGFlexDirectionResolve(const YGFlexDirection flexD static YGFlexDirection YGFlexDirectionCross(const YGFlexDirection flexDirection, const YGDirection direction) { return YGFlexDirectionIsColumn(flexDirection) - ? YGFlexDirectionResolve(YGFlexDirectionRow, direction) + ? YGResolveFlexDirection(YGFlexDirectionRow, direction) : YGFlexDirectionColumn; } @@ -1190,7 +1233,7 @@ static float YGNodeLeadingPosition(const YGNodeRef node, const YGValue *leadingPosition = YGComputedEdgeValue(node->style.position, YGEdgeStart, &YGValueUndefined); if (leadingPosition->unit != YGUnitUndefined) { - return YGValueResolve(leadingPosition, axisSize); + return YGResolveValue(leadingPosition, axisSize); } } @@ -1198,7 +1241,7 @@ static float YGNodeLeadingPosition(const YGNodeRef node, YGComputedEdgeValue(node->style.position, leading[axis], &YGValueUndefined); return leadingPosition->unit == YGUnitUndefined ? 0.0f - : YGValueResolve(leadingPosition, axisSize); + : YGResolveValue(leadingPosition, axisSize); } static float YGNodeTrailingPosition(const YGNodeRef node, @@ -1208,7 +1251,7 @@ static float YGNodeTrailingPosition(const YGNodeRef node, const YGValue *trailingPosition = YGComputedEdgeValue(node->style.position, YGEdgeEnd, &YGValueUndefined); if (trailingPosition->unit != YGUnitUndefined) { - return YGValueResolve(trailingPosition, axisSize); + return YGResolveValue(trailingPosition, axisSize); } } @@ -1216,7 +1259,7 @@ static float YGNodeTrailingPosition(const YGNodeRef node, YGComputedEdgeValue(node->style.position, trailing[axis], &YGValueUndefined); return trailingPosition->unit == YGUnitUndefined ? 0.0f - : YGValueResolve(trailingPosition, axisSize); + : YGResolveValue(trailingPosition, axisSize); } static float YGNodeBoundAxisWithinMinAndMax(const YGNodeRef node, @@ -1227,11 +1270,11 @@ static float YGNodeBoundAxisWithinMinAndMax(const YGNodeRef node, float max = YGUndefined; if (YGFlexDirectionIsColumn(axis)) { - min = YGValueResolve(&node->style.minDimensions[YGDimensionHeight], axisSize); - max = YGValueResolve(&node->style.maxDimensions[YGDimensionHeight], axisSize); + min = YGResolveValue(&node->style.minDimensions[YGDimensionHeight], axisSize); + max = YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], axisSize); } else if (YGFlexDirectionIsRow(axis)) { - min = YGValueResolve(&node->style.minDimensions[YGDimensionWidth], axisSize); - max = YGValueResolve(&node->style.maxDimensions[YGDimensionWidth], axisSize); + min = YGResolveValue(&node->style.minDimensions[YGDimensionWidth], axisSize); + max = YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], axisSize); } float boundValue = value; @@ -1247,6 +1290,22 @@ static float YGNodeBoundAxisWithinMinAndMax(const YGNodeRef node, return boundValue; } +static inline YGValue *YGMarginLeadingValue(const YGNodeRef node, const YGFlexDirection axis) { + if (YGFlexDirectionIsRow(axis) && node->style.margin[YGEdgeStart].unit != YGUnitUndefined) { + return &node->style.margin[YGEdgeStart]; + } else { + return &node->style.margin[leading[axis]]; + } +} + +static inline YGValue *YGMarginTrailingValue(const YGNodeRef node, const YGFlexDirection axis) { + if (YGFlexDirectionIsRow(axis) && node->style.margin[YGEdgeEnd].unit != YGUnitUndefined) { + return &node->style.margin[YGEdgeEnd]; + } else { + return &node->style.margin[trailing[axis]]; + } +} + // Like YGNodeBoundAxisWithinMinAndMax but also ensures that the value doesn't go // below the // padding and border amount. @@ -1276,7 +1335,14 @@ static float YGNodeRelativePosition(const YGNodeRef node, : -YGNodeTrailingPosition(node, axis, axisSize); } -static void YGConstrainMaxSizeForMode(const float maxSize, YGMeasureMode *mode, float *size) { +static void YGConstrainMaxSizeForMode(const YGNodeRef node, + const enum YGFlexDirection axis, + const float parentAxisSize, + const float parentWidth, + YGMeasureMode *mode, + float *size) { + const float maxSize = YGResolveValue(&node->style.maxDimensions[dim[axis]], parentAxisSize) + + YGNodeMarginForAxis(node, axis, parentWidth); switch (*mode) { case YGMeasureModeExactly: case YGMeasureModeAtMost: @@ -1296,7 +1362,7 @@ static void YGNodeSetPosition(const YGNodeRef node, const float mainSize, const float crossSize, const float parentWidth) { - const YGFlexDirection mainAxis = YGFlexDirectionResolve(node->style.flexDirection, direction); + const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction); const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction); const float relativePositionMain = YGNodeRelativePosition(node, mainAxis, mainSize); const float relativePositionCross = YGNodeRelativePosition(node, crossAxis, crossSize); @@ -1319,8 +1385,9 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, const float parentWidth, const float parentHeight, const YGMeasureMode heightMode, - const YGDirection direction) { - const YGFlexDirection mainAxis = YGFlexDirectionResolve(node->style.flexDirection, direction); + const YGDirection direction, + const YGConfigRef config) { + const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction); const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis); const float mainAxisSize = isMainAxisRow ? width : height; const float mainAxisParentSize = isMainAxisRow ? parentWidth : parentHeight; @@ -1331,14 +1398,14 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, YGMeasureMode childHeightMeasureMode; const float resolvedFlexBasis = - YGValueResolve(YGNodeResolveFlexBasisPtr(child), mainAxisParentSize); + YGResolveValue(YGNodeResolveFlexBasisPtr(child), mainAxisParentSize); const bool isRowStyleDimDefined = YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, parentWidth); const bool isColumnStyleDimDefined = YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, parentHeight); if (!YGFloatIsUndefined(resolvedFlexBasis) && !YGFloatIsUndefined(mainAxisSize)) { if (YGFloatIsUndefined(child->layout.computedFlexBasis) || - (YGIsExperimentalFeatureEnabled(YGExperimentalFeatureWebFlexBasis) && + (YGConfigIsExperimentalFeatureEnabled(child->config, YGExperimentalFeatureWebFlexBasis) && child->layout.computedFlexBasisGeneration != gCurrentGenerationCount)) { child->layout.computedFlexBasis = fmaxf(resolvedFlexBasis, YGNodePaddingAndBorderForAxis(child, mainAxis, parentWidth)); @@ -1346,12 +1413,12 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, } else if (isMainAxisRow && isRowStyleDimDefined) { // The width is definite, so use that as the flex basis. child->layout.computedFlexBasis = - fmaxf(YGValueResolve(child->resolvedDimensions[YGDimensionWidth], parentWidth), + fmaxf(YGResolveValue(child->resolvedDimensions[YGDimensionWidth], parentWidth), YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, parentWidth)); } else if (!isMainAxisRow && isColumnStyleDimDefined) { // The height is definite, so use that as the flex basis. child->layout.computedFlexBasis = - fmaxf(YGValueResolve(child->resolvedDimensions[YGDimensionHeight], parentHeight), + fmaxf(YGResolveValue(child->resolvedDimensions[YGDimensionHeight], parentHeight), YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn, parentWidth)); } else { // Compute the flex basis and hypothetical main size (i.e. the clamped @@ -1366,12 +1433,12 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, if (isRowStyleDimDefined) { childWidth = - YGValueResolve(child->resolvedDimensions[YGDimensionWidth], parentWidth) + marginRow; + YGResolveValue(child->resolvedDimensions[YGDimensionWidth], parentWidth) + marginRow; childWidthMeasureMode = YGMeasureModeExactly; } if (isColumnStyleDimDefined) { childHeight = - YGValueResolve(child->resolvedDimensions[YGDimensionHeight], parentHeight) + marginColumn; + YGResolveValue(child->resolvedDimensions[YGDimensionHeight], parentHeight) + marginColumn; childHeightMeasureMode = YGMeasureModeExactly; } @@ -1421,14 +1488,10 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, } } - YGConstrainMaxSizeForMode(YGValueResolve(&child->style.maxDimensions[YGDimensionWidth], - parentWidth), - &childWidthMeasureMode, - &childWidth); - YGConstrainMaxSizeForMode(YGValueResolve(&child->style.maxDimensions[YGDimensionHeight], - parentHeight), - &childHeightMeasureMode, - &childHeight); + YGConstrainMaxSizeForMode( + child, YGFlexDirectionRow, parentWidth, parentWidth, &childWidthMeasureMode, &childWidth); + YGConstrainMaxSizeForMode( + child, YGFlexDirectionColumn, parentHeight, parentWidth, &childHeightMeasureMode, &childHeight); // Measure the child YGLayoutNodeInternal(child, @@ -1440,7 +1503,8 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node, parentWidth, parentHeight, false, - "measure"); + "measure", + config); child->layout.computedFlexBasis = fmaxf(child->layout.measuredDimensions[dim[mainAxis]], @@ -1455,8 +1519,9 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node, const float width, const YGMeasureMode widthMode, const float height, - const YGDirection direction) { - const YGFlexDirection mainAxis = YGFlexDirectionResolve(node->style.flexDirection, direction); + const YGDirection direction, + const YGConfigRef config) { + const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction); const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction); const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis); @@ -1469,7 +1534,7 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node, const float marginColumn = YGNodeMarginForAxis(child, YGFlexDirectionColumn, width); if (YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, width)) { - childWidth = YGValueResolve(child->resolvedDimensions[YGDimensionWidth], width) + marginRow; + childWidth = YGResolveValue(child->resolvedDimensions[YGDimensionWidth], width) + marginRow; } else { // If the child doesn't have a specified width, compute the width based // on the left/right @@ -1487,7 +1552,7 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node, if (YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, height)) { childHeight = - YGValueResolve(child->resolvedDimensions[YGDimensionHeight], height) + marginColumn; + YGResolveValue(child->resolvedDimensions[YGDimensionHeight], height) + marginColumn; } else { // If the child doesn't have a specified height, compute the height // based on the top/bottom @@ -1544,7 +1609,8 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node, childWidth, childHeight, false, - "abs-measure"); + "abs-measure", + config); childWidth = child->layout.measuredDimensions[YGDimensionWidth] + YGNodeMarginForAxis(child, YGFlexDirectionRow, width); childHeight = child->layout.measuredDimensions[YGDimensionHeight] + @@ -1560,7 +1626,8 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node, childWidth, childHeight, true, - "abs-layout"); + "abs-layout", + config); if (YGNodeIsTrailingPosDefined(child, mainAxis) && !YGNodeIsLeadingPosDefined(child, mainAxis)) { child->layout.position[leading[mainAxis]] = node->layout.measuredDimensions[dim[mainAxis]] - @@ -1737,6 +1804,12 @@ static void YGZeroOutLayoutRecursivly(const YGNodeRef node) { node->layout.position[YGEdgeBottom] = 0; node->layout.position[YGEdgeLeft] = 0; node->layout.position[YGEdgeRight] = 0; + node->layout.cachedLayout.availableHeight = 0; + node->layout.cachedLayout.availableWidth = 0; + node->layout.cachedLayout.heightMeasureMode = YGMeasureModeExactly; + node->layout.cachedLayout.widthMeasureMode = YGMeasureModeExactly; + node->layout.cachedLayout.computedWidth = 0; + node->layout.cachedLayout.computedHeight = 0; const uint32_t childCount = YGNodeGetChildCount(node); for (uint32_t i = 0; i < childCount; i++) { const YGNodeRef child = YGNodeListGet(node->children, i); @@ -1838,7 +1911,8 @@ static void YGNodelayoutImpl(const YGNodeRef node, const YGMeasureMode heightMeasureMode, const float parentWidth, const float parentHeight, - const bool performLayout) { + const bool performLayout, + const YGConfigRef config) { YG_ASSERT(YGFloatIsUndefined(availableWidth) ? widthMeasureMode == YGMeasureModeUndefined : true, "availableWidth is indefinite so widthMeasureMode must be " "YGMeasureModeUndefined"); @@ -1851,9 +1925,9 @@ static void YGNodelayoutImpl(const YGNodeRef node, const YGDirection direction = YGNodeResolveDirection(node, parentDirection); node->layout.direction = direction; - const YGFlexDirection flexRowDirection = YGFlexDirectionResolve(YGFlexDirectionRow, direction); + const YGFlexDirection flexRowDirection = YGResolveFlexDirection(YGFlexDirectionRow, direction); const YGFlexDirection flexColumnDirection = - YGFlexDirectionResolve(YGFlexDirectionColumn, direction); + YGResolveFlexDirection(YGFlexDirectionColumn, direction); node->layout.margin[YGEdgeStart] = YGNodeLeadingMargin(node, flexRowDirection, parentWidth); node->layout.margin[YGEdgeEnd] = YGNodeTrailingMargin(node, flexRowDirection, parentWidth); @@ -1907,7 +1981,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, } // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM - const YGFlexDirection mainAxis = YGFlexDirectionResolve(node->style.flexDirection, direction); + const YGFlexDirection mainAxis = YGResolveFlexDirection(node->style.flexDirection, direction); const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction); const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis); const YGJustify justifyContent = node->style.justifyContent; @@ -1942,16 +2016,16 @@ static void YGNodelayoutImpl(const YGNodeRef node, // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS const float minInnerWidth = - YGValueResolve(&node->style.minDimensions[YGDimensionWidth], parentWidth) - marginAxisRow - + YGResolveValue(&node->style.minDimensions[YGDimensionWidth], parentWidth) - marginAxisRow - paddingAndBorderAxisRow; const float maxInnerWidth = - YGValueResolve(&node->style.maxDimensions[YGDimensionWidth], parentWidth) - marginAxisRow - + YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], parentWidth) - marginAxisRow - paddingAndBorderAxisRow; const float minInnerHeight = - YGValueResolve(&node->style.minDimensions[YGDimensionHeight], parentHeight) - + YGResolveValue(&node->style.minDimensions[YGDimensionHeight], parentHeight) - marginAxisColumn - paddingAndBorderAxisColumn; const float maxInnerHeight = - YGValueResolve(&node->style.maxDimensions[YGDimensionHeight], parentHeight) - + YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], parentHeight) - marginAxisColumn - paddingAndBorderAxisColumn; const float minInnerMainDim = isMainAxisRow ? minInnerWidth : minInnerHeight; const float maxInnerMainDim = isMainAxisRow ? maxInnerWidth : maxInnerHeight; @@ -2040,7 +2114,8 @@ static void YGNodelayoutImpl(const YGNodeRef node, availableInnerWidth, availableInnerHeight, heightMeasureMode, - direction); + direction, + config); } } @@ -2098,7 +2173,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, if (child->style.positionType != YGPositionTypeAbsolute) { const float outerFlexBasis = - fmaxf(YGValueResolve(&child->style.minDimensions[dim[mainAxis]], mainAxisParentSize), + fmaxf(YGResolveValue(&child->style.minDimensions[dim[mainAxis]], mainAxisParentSize), child->layout.computedFlexBasis) + YGNodeMarginForAxis(child, mainAxis, availableInnerWidth); @@ -2157,7 +2232,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, availableInnerMainDim = minInnerMainDim; } else if (!YGFloatIsUndefined(maxInnerMainDim) && sizeConsumedOnCurrentLine > maxInnerMainDim) { availableInnerMainDim = maxInnerMainDim; - } else if (YGIsExperimentalFeatureEnabled(YGExperimentalFeatureMinFlexFix)) { + } else if (YGConfigIsExperimentalFeatureEnabled(node->config, YGExperimentalFeatureMinFlexFix)) { // TODO: this needs to be moved out of experimental feature, as this is legitimate fix // If the measurement isn't exact, we want to use as little space as possible availableInnerMainDim = sizeConsumedOnCurrentLine; @@ -2341,7 +2416,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, childCrossMeasureMode = YGFloatIsUndefined(childCrossSize) ? YGMeasureModeUndefined : YGMeasureModeAtMost; } else { - childCrossSize = YGValueResolve(currentRelativeChild->resolvedDimensions[dim[crossAxis]], + childCrossSize = YGResolveValue(currentRelativeChild->resolvedDimensions[dim[crossAxis]], availableInnerCrossDim) + marginCross; const bool isLoosePercentageMeasurement = @@ -2372,16 +2447,18 @@ static void YGNodelayoutImpl(const YGNodeRef node, childCrossSize += marginCross; } - YGConstrainMaxSizeForMode( - YGValueResolve(¤tRelativeChild->style.maxDimensions[dim[mainAxis]], - availableInnerWidth), - &childMainMeasureMode, - &childMainSize); - YGConstrainMaxSizeForMode( - YGValueResolve(¤tRelativeChild->style.maxDimensions[dim[crossAxis]], - availableInnerHeight), - &childCrossMeasureMode, - &childCrossSize); + YGConstrainMaxSizeForMode(currentRelativeChild, + mainAxis, + availableInnerMainDim, + availableInnerWidth, + &childMainMeasureMode, + &childMainSize); + YGConstrainMaxSizeForMode(currentRelativeChild, + crossAxis, + availableInnerCrossDim, + availableInnerWidth, + &childCrossMeasureMode, + &childCrossSize); const bool requiresStretchLayout = !YGNodeIsStyleDimDefined(currentRelativeChild, crossAxis, availableInnerCrossDim) && @@ -2406,7 +2483,8 @@ static void YGNodelayoutImpl(const YGNodeRef node, availableInnerWidth, availableInnerHeight, performLayout && !requiresStretchLayout, - "flex"); + "flex", + config); currentRelativeChild = currentRelativeChild->nextChild; } @@ -2428,10 +2506,10 @@ static void YGNodelayoutImpl(const YGNodeRef node, if (measureModeMainDim == YGMeasureModeAtMost && remainingFreeSpace > 0) { if (node->style.minDimensions[dim[mainAxis]].unit != YGUnitUndefined && - YGValueResolve(&node->style.minDimensions[dim[mainAxis]], mainAxisParentSize) >= 0) { + YGResolveValue(&node->style.minDimensions[dim[mainAxis]], mainAxisParentSize) >= 0) { remainingFreeSpace = fmaxf(0, - YGValueResolve(&node->style.minDimensions[dim[mainAxis]], mainAxisParentSize) - + YGResolveValue(&node->style.minDimensions[dim[mainAxis]], mainAxisParentSize) - (availableInnerMainDim - remainingFreeSpace)); } else { remainingFreeSpace = 0; @@ -2442,10 +2520,10 @@ static void YGNodelayoutImpl(const YGNodeRef node, for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) { const YGNodeRef child = YGNodeListGet(node->children, i); if (child->style.positionType == YGPositionTypeRelative) { - if (child->style.margin[leading[mainAxis]].unit == YGUnitAuto) { + if (YGMarginLeadingValue(child, mainAxis)->unit == YGUnitAuto) { numberOfAutoMarginsOnCurrentLine++; } - if (child->style.margin[trailing[mainAxis]].unit == YGUnitAuto) { + if (YGMarginTrailingValue(child, mainAxis)->unit == YGUnitAuto) { numberOfAutoMarginsOnCurrentLine++; } } @@ -2500,7 +2578,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, // We need to do that only for relative elements. Absolute elements // do not take part in that phase. if (child->style.positionType == YGPositionTypeRelative) { - if (child->style.margin[leading[mainAxis]].unit == YGUnitAuto) { + if (YGMarginLeadingValue(child, mainAxis)->unit == YGUnitAuto) { mainDim += remainingFreeSpace / numberOfAutoMarginsOnCurrentLine; } @@ -2508,7 +2586,7 @@ static void YGNodelayoutImpl(const YGNodeRef node, child->layout.position[pos[mainAxis]] += mainDim; } - if (child->style.margin[trailing[mainAxis]].unit == YGUnitAuto) { + if (YGMarginTrailingValue(child, mainAxis)->unit == YGUnitAuto) { mainDim += remainingFreeSpace / numberOfAutoMarginsOnCurrentLine; } @@ -2601,8 +2679,8 @@ static void YGNodelayoutImpl(const YGNodeRef node, // forcing the cross-axis size to be the computed cross size for the // current line. if (alignItem == YGAlignStretch && - child->style.margin[leading[crossAxis]].unit != YGUnitAuto && - child->style.margin[trailing[crossAxis]].unit != YGUnitAuto) { + YGMarginLeadingValue(child, crossAxis)->unit != YGUnitAuto && + YGMarginTrailingValue(child, crossAxis)->unit != YGUnitAuto) { // If the child defines a definite size for its cross axis, there's // no need to stretch. if (!YGNodeIsStyleDimDefined(child, crossAxis, availableInnerCrossDim)) { @@ -2618,12 +2696,16 @@ static void YGNodelayoutImpl(const YGNodeRef node, YGMeasureMode childMainMeasureMode = YGMeasureModeExactly; YGMeasureMode childCrossMeasureMode = YGMeasureModeExactly; - YGConstrainMaxSizeForMode(YGValueResolve(&child->style.maxDimensions[dim[mainAxis]], - availableInnerMainDim), + YGConstrainMaxSizeForMode(child, + mainAxis, + availableInnerMainDim, + availableInnerWidth, &childMainMeasureMode, &childMainSize); - YGConstrainMaxSizeForMode(YGValueResolve(&child->style.maxDimensions[dim[crossAxis]], - availableInnerCrossDim), + YGConstrainMaxSizeForMode(child, + crossAxis, + availableInnerCrossDim, + availableInnerWidth, &childCrossMeasureMode, &childCrossSize); @@ -2644,18 +2726,19 @@ static void YGNodelayoutImpl(const YGNodeRef node, availableInnerWidth, availableInnerHeight, true, - "stretch"); + "stretch", + config); } } else { const float remainingCrossDim = containerCrossAxis - YGNodeDimWithMargin(child, crossAxis, availableInnerWidth); - if (child->style.margin[leading[crossAxis]].unit == YGUnitAuto && - child->style.margin[trailing[crossAxis]].unit == YGUnitAuto) { + if (YGMarginLeadingValue(child, crossAxis)->unit == YGUnitAuto && + YGMarginTrailingValue(child, crossAxis)->unit == YGUnitAuto) { leadingCrossDim += remainingCrossDim / 2; - } else if (child->style.margin[trailing[crossAxis]].unit == YGUnitAuto) { + } else if (YGMarginTrailingValue(child, crossAxis)->unit == YGUnitAuto) { // No-Op - } else if (child->style.margin[leading[crossAxis]].unit == YGUnitAuto) { + } else if (YGMarginLeadingValue(child, crossAxis)->unit == YGUnitAuto) { leadingCrossDim += remainingCrossDim; } else if (alignItem == YGAlignFlexStart) { // No-Op @@ -2811,7 +2894,8 @@ static void YGNodelayoutImpl(const YGNodeRef node, availableInnerWidth, availableInnerHeight, true, - "stretch"); + "multiline-stretch", + config); } } break; @@ -2899,7 +2983,8 @@ static void YGNodelayoutImpl(const YGNodeRef node, availableInnerWidth, isMainAxisRow ? measureModeMainDim : measureModeCrossDim, availableInnerHeight, - direction); + direction, + config); } // STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN @@ -3041,7 +3126,8 @@ bool YGLayoutNodeInternal(const YGNodeRef node, const float parentWidth, const float parentHeight, const bool performLayout, - const char *reason) { + const char *reason, + const YGConfigRef config) { YGLayout *layout = &node->layout; gDepth++; @@ -3170,7 +3256,8 @@ bool YGLayoutNodeInternal(const YGNodeRef node, heightMeasureMode, parentWidth, parentHeight, - performLayout); + performLayout, + config); if (gPrintChanges) { printf("%s%d.}%s", YGSpacer(gDepth), gDepth, needToVisitNode ? "*" : ""); @@ -3226,62 +3313,60 @@ bool YGLayoutNodeInternal(const YGNodeRef node, return (needToVisitNode || cachedResults == NULL); } -static float gPointScaleFactor = 1.0; - -void YGSetPointScaleFactor(float pixelsInPoint) { - YG_ASSERT(pixelsInPoint >= 0.0, "Scale factor should not be less than zero"); +void YGConfigSetPointScaleFactor(const YGConfigRef config, const float pixelsInPoint) { + YG_ASSERT(pixelsInPoint >= 0.0f, "Scale factor should not be less than zero"); // We store points for Pixel as we will use it for rounding - if (pixelsInPoint == 0.0) { + if (pixelsInPoint == 0.0f) { // Zero is used to skip rounding - gPointScaleFactor = 0.0; + config->pointScaleFactor = 0.0f; } else { - gPointScaleFactor = 1.0 / pixelsInPoint; + config->pointScaleFactor = 1.0f / pixelsInPoint; } } -static void YGRoundToPixelGrid(const YGNodeRef node) { - if (gPointScaleFactor == 0.0) { +static void YGRoundToPixelGrid(const YGNodeRef node, const float pointScaleFactor) { + if (pointScaleFactor == 0.0f) { return; } const float nodeLeft = node->layout.position[YGEdgeLeft]; const float nodeTop = node->layout.position[YGEdgeTop]; // To round correctly to the pixel grid, first we calculate left and top coordinates - float fractialLeft = fmodf(nodeLeft, gPointScaleFactor); - float fractialTop = fmodf(nodeTop, gPointScaleFactor); + float fractialLeft = fmodf(nodeLeft, pointScaleFactor); + float fractialTop = fmodf(nodeTop, pointScaleFactor); float roundedLeft = nodeLeft - fractialLeft; float roundedTop = nodeTop - fractialTop; - + // To do the actual rounding we check if leftover fraction is bigger or equal than half of the grid step - if (fractialLeft >= gPointScaleFactor / 2.0) { - roundedLeft += gPointScaleFactor; - fractialLeft -= gPointScaleFactor; + if (fractialLeft >= pointScaleFactor / 2.0f) { + roundedLeft += pointScaleFactor; + fractialLeft -= pointScaleFactor; } - if (fractialTop >= gPointScaleFactor / 2.0) { - roundedTop += gPointScaleFactor; - fractialTop -= gPointScaleFactor; + if (fractialTop >= pointScaleFactor / 2.0f) { + roundedTop += pointScaleFactor; + fractialTop -= pointScaleFactor; } node->layout.position[YGEdgeLeft] = roundedLeft; node->layout.position[YGEdgeTop] = roundedTop; - + // Now we round width and height in the same way accounting for fractial leftovers from rounding position const float adjustedWidth = fractialLeft + node->layout.dimensions[YGDimensionWidth]; const float adjustedHeight = fractialTop + node->layout.dimensions[YGDimensionHeight]; - float roundedWidth = adjustedWidth - fmodf(adjustedWidth, gPointScaleFactor); - float roundedHeight = adjustedHeight - fmodf(adjustedHeight, gPointScaleFactor); - - if (adjustedWidth - roundedWidth >= gPointScaleFactor / 2.0) { - roundedWidth += gPointScaleFactor; + float roundedWidth = adjustedWidth - fmodf(adjustedWidth, pointScaleFactor); + float roundedHeight = adjustedHeight - fmodf(adjustedHeight, pointScaleFactor); + + if (adjustedWidth - roundedWidth >= pointScaleFactor / 2.0f) { + roundedWidth += pointScaleFactor; } - if (adjustedHeight - roundedHeight >= gPointScaleFactor / 2.0) { - roundedHeight += gPointScaleFactor; + if (adjustedHeight - roundedHeight >= pointScaleFactor / 2.0f) { + roundedHeight += pointScaleFactor; } node->layout.dimensions[YGDimensionWidth] = roundedWidth; node->layout.dimensions[YGDimensionHeight] = roundedHeight; const uint32_t childCount = YGNodeListCount(node->children); for (uint32_t i = 0; i < childCount; i++) { - YGRoundToPixelGrid(YGNodeGetChild(node, i)); + YGRoundToPixelGrid(YGNodeGetChild(node, i), pointScaleFactor); } } @@ -3301,24 +3386,30 @@ void YGNodeCalculateLayout(const YGNodeRef node, float width = YGUndefined; YGMeasureMode widthMeasureMode = YGMeasureModeUndefined; if (YGNodeIsStyleDimDefined(node, YGFlexDirectionRow, parentWidth)) { - width = YGValueResolve(node->resolvedDimensions[dim[YGFlexDirectionRow]], parentWidth) + + width = YGResolveValue(node->resolvedDimensions[dim[YGFlexDirectionRow]], parentWidth) + YGNodeMarginForAxis(node, YGFlexDirectionRow, parentWidth); widthMeasureMode = YGMeasureModeExactly; - } else if (YGValueResolve(&node->style.maxDimensions[YGDimensionWidth], parentWidth) >= 0.0f) { - width = YGValueResolve(&node->style.maxDimensions[YGDimensionWidth], parentWidth); + } else if (YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], parentWidth) >= 0.0f) { + width = YGResolveValue(&node->style.maxDimensions[YGDimensionWidth], parentWidth); widthMeasureMode = YGMeasureModeAtMost; + } else { + width = parentWidth; + widthMeasureMode = YGFloatIsUndefined(width) ? YGMeasureModeUndefined : YGMeasureModeExactly; } float height = YGUndefined; YGMeasureMode heightMeasureMode = YGMeasureModeUndefined; if (YGNodeIsStyleDimDefined(node, YGFlexDirectionColumn, parentHeight)) { - height = YGValueResolve(node->resolvedDimensions[dim[YGFlexDirectionColumn]], parentHeight) + + height = YGResolveValue(node->resolvedDimensions[dim[YGFlexDirectionColumn]], parentHeight) + YGNodeMarginForAxis(node, YGFlexDirectionColumn, parentWidth); heightMeasureMode = YGMeasureModeExactly; - } else if (YGValueResolve(&node->style.maxDimensions[YGDimensionHeight], parentHeight) >= + } else if (YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], parentHeight) >= 0.0f) { - height = YGValueResolve(&node->style.maxDimensions[YGDimensionHeight], parentHeight); + height = YGResolveValue(&node->style.maxDimensions[YGDimensionHeight], parentHeight); heightMeasureMode = YGMeasureModeAtMost; + } else { + height = parentHeight; + heightMeasureMode = YGFloatIsUndefined(height) ? YGMeasureModeUndefined : YGMeasureModeExactly; } if (YGLayoutNodeInternal(node, @@ -3330,12 +3421,12 @@ void YGNodeCalculateLayout(const YGNodeRef node, parentWidth, parentHeight, true, - "initia" - "l")) { - YGNodeSetPosition(node, node->layout.direction, node->layout.dimensions[YGDimensionWidth], node->layout.dimensions[YGDimensionHeight], parentWidth); + "initial", + node->config)) { + YGNodeSetPosition(node, node->layout.direction, parentWidth, parentHeight, parentWidth); - if (YGIsExperimentalFeatureEnabled(YGExperimentalFeatureRounding)) { - YGRoundToPixelGrid(node); + if (YGConfigIsExperimentalFeatureEnabled(node->config, YGExperimentalFeatureRounding)) { + YGRoundToPixelGrid(node, node->config->pointScaleFactor); } if (gPrintTree) { @@ -3355,14 +3446,23 @@ void YGLog(YGLogLevel level, const char *format, ...) { va_end(args); } -static bool experimentalFeatures[YGExperimentalFeatureCount + 1]; +void YGConfigSetExperimentalFeatureEnabled(const YGConfigRef config, + const YGExperimentalFeature feature, + const bool enabled) { + config->experimentalFeatures[feature] = enabled; +} + +inline bool YGConfigIsExperimentalFeatureEnabled(const YGConfigRef config, + const YGExperimentalFeature feature) { + return config->experimentalFeatures[feature]; +} -void YGSetExperimentalFeatureEnabled(YGExperimentalFeature feature, bool enabled) { - experimentalFeatures[feature] = enabled; +void YGConfigSetUseWebDefaults(const YGConfigRef config, const bool enabled) { + config->useWebDefaults = enabled; } -inline bool YGIsExperimentalFeatureEnabled(YGExperimentalFeature feature) { - return experimentalFeatures[feature]; +bool YGConfigGetUseWebDefaults(const YGConfigRef config) { + return config->useWebDefaults; } void YGSetMemoryFuncs(YGMalloc ygmalloc, YGCalloc yccalloc, YGRealloc ygrealloc, YGFree ygfree) { diff --git a/ReactCommon/yoga/yoga/Yoga.h b/ReactCommon/yoga/yoga/Yoga.h index 64b0cda07ec6f7..d5c5258a7dca15 100644 --- a/ReactCommon/yoga/yoga/Yoga.h +++ b/ReactCommon/yoga/yoga/Yoga.h @@ -46,6 +46,7 @@ typedef struct YGValue { static const YGValue YGValueUndefined = {YGUndefined, YGUnitUndefined}; static const YGValue YGValueAuto = {YGUndefined, YGUnitAuto}; +typedef struct YGConfig *YGConfigRef; typedef struct YGNode *YGNodeRef; typedef YGSize (*YGMeasureFunc)(YGNodeRef node, float width, @@ -63,6 +64,7 @@ typedef void (*YGFree)(void *ptr); // YGNode WIN_EXPORT YGNodeRef YGNodeNew(void); +WIN_EXPORT YGNodeRef YGNodeNewWithConfig(const YGConfigRef config); WIN_EXPORT void YGNodeFree(const YGNodeRef node); WIN_EXPORT void YGNodeFreeRecursive(const YGNodeRef node); WIN_EXPORT void YGNodeReset(const YGNodeRef node); @@ -139,7 +141,7 @@ WIN_EXPORT void YGNodeCopyStyle(const YGNodeRef dstNode, const YGNodeRef srcNode WIN_EXPORT void YGNodeStyleSet##name##Percent(const YGNodeRef node, \ const YGEdge edge, \ const float paramName); \ - WIN_EXPORT type YGNodeStyleGet##name(const YGNodeRef node, const YGEdge edge); + WIN_EXPORT WIN_STRUCT(type) YGNodeStyleGet##name(const YGNodeRef node, const YGEdge edge); #define YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO(type, name) \ WIN_EXPORT void YGNodeStyleSet##name##Auto(const YGNodeRef node, const YGEdge edge); @@ -221,10 +223,23 @@ WIN_EXPORT void YGLog(YGLogLevel level, const char *message, ...); // Set this to number of pixels in 1 point to round calculation results // If you want to avoid rounding - set PointScaleFactor to 0 -WIN_EXPORT void YGSetPointScaleFactor(float pixelsInPoint); +WIN_EXPORT void YGConfigSetPointScaleFactor(const YGConfigRef config, const float pixelsInPoint); + +// YGConfig +WIN_EXPORT YGConfigRef YGConfigNew(void); +WIN_EXPORT void YGConfigFree(const YGConfigRef config); + +WIN_EXPORT void YGConfigSetExperimentalFeatureEnabled(const YGConfigRef config, + const YGExperimentalFeature feature, + const bool enabled); +WIN_EXPORT bool YGConfigIsExperimentalFeatureEnabled(const YGConfigRef config, + const YGExperimentalFeature feature); + +// Using the web defaults is the prefered configuration for new projects. +// Usage of non web defaults should be considered as legacy. +WIN_EXPORT void YGConfigSetUseWebDefaults(const YGConfigRef config, const bool enabled); -WIN_EXPORT void YGSetExperimentalFeatureEnabled(YGExperimentalFeature feature, bool enabled); -WIN_EXPORT bool YGIsExperimentalFeatureEnabled(YGExperimentalFeature feature); +WIN_EXPORT bool YGConfigGetUseWebDefaults(const YGConfigRef config); WIN_EXPORT void YGSetMemoryFuncs(YGMalloc ygmalloc, YGCalloc yccalloc, YGRealloc ygrealloc, YGFree ygfree); diff --git a/blog/2016-09-08-exponent-talks-unraveling-navigation.md b/blog/2016-09-08-exponent-talks-unraveling-navigation.md index 116aee4bac38d8..2d66764bd42377 100644 --- a/blog/2016-09-08-exponent-talks-unraveling-navigation.md +++ b/blog/2016-09-08-exponent-talks-unraveling-navigation.md @@ -1,5 +1,5 @@ --- -title: Exponent Talks: Adam on Unraveling Navigation +title: Expo Talks: Adam on Unraveling Navigation author: Héctor Ramos authorTitle: Developer Advocate at Facebook authorURL: https://twitter.com/hectorramos @@ -9,4 +9,4 @@ youtubeVideoId: oeSjTxVkMhc category: videos --- -[Adam Miskiewicz](https://twitter.com/skevy) from [Exponent](http://getexponent.com/) talks about mobile navigation and the [`ex-navigation`](https://github.com/exponentjs/ex-navigation) React Native library at Exponent's office hours last week. +[Adam Miskiewicz](https://twitter.com/skevy) from [Expo](https://expo.io/) talks about mobile navigation and the [`ex-navigation`](https://github.com/exponent/ex-navigation) React Native library at Expo's office hours last week. diff --git a/blog/2016-12-05-easier-upgrades.md b/blog/2016-12-05-easier-upgrades.md index 65066f7caddea9..955bd6f2131abf 100644 --- a/blog/2016-12-05-easier-upgrades.md +++ b/blog/2016-12-05-easier-upgrades.md @@ -112,12 +112,6 @@ Upgrading based on Git is a big improvement in developer experience and it is im One more reason is the recent [Yeoman wipeout](https://twitter.com/martinkonicek/status/800730190141857793) by Martin Konicek. We didn't want to get these Yeoman dependencies back into the `react-native` package to be able to evaluate the old template in order to create the patch. -## Looking ahead - -The logic behind `react-native-git-upgrade` described above is going to power the standard `react-native upgrade` starting with React Native version 0.40.0. - -This means that if you are upgrading __from version 0.40.0 or higher__, you will be able to simply run `react-native upgrade` in your project without having to install anything globally. The `react-native-git-upgrade` is provided so anyone can enjoy this improved experience today while using older versions of React Native. - ## Try it out and provide feedback As a conclusion, I would say, enjoy the feature and feel free [to suggest improvements, report issues](https://github.com/facebook/react-native/issues) and especially [send pull requests](https://github.com/facebook/react-native/pulls). Each environment is a bit different and each React Native project is different, and we need your feedback to make this work well for everyone. diff --git a/blog/2017-01-07-monthly-release-cadence.md b/blog/2017-01-07-monthly-release-cadence.md index f5c7048246da16..216740c9b30963 100644 --- a/blog/2017-01-07-monthly-release-cadence.md +++ b/blog/2017-01-07-monthly-release-cadence.md @@ -10,7 +10,7 @@ category: announcements Shortly after React Native was introduced, we started releasing every two weeks to help the community adopt new features, while keeping versions stable for production use. At Facebook we had to stabilize the codebase every two weeks for the release of our production iOS apps, so we decided to release the open source versions at the same pace. Now, many of the Facebook apps ship once per week, especially on Android. Because we ship from master weekly, we need to keep it quite stable. So the bi-weekly release cadence doesn't even benefit internal contributors anymore. -We frequently hear feedback from the community that the release rate is hard to keep up with. Tools like [Exponent](https://getexponent.com/) had to skip every other release in order to manage the rapid change in version. So it seems clear that the bi-weekly releases did not serve the community well. +We frequently hear feedback from the community that the release rate is hard to keep up with. Tools like [Expo](https://expo.io/) had to skip every other release in order to manage the rapid change in version. So it seems clear that the bi-weekly releases did not serve the community well. ### Now releasing monthly diff --git a/blog/2017-02-14-using-native-driver-for-animated.md b/blog/2017-02-14-using-native-driver-for-animated.md index 625f3f92e8fd24..1624243eed1472 100644 --- a/blog/2017-02-14-using-native-driver-for-animated.md +++ b/blog/2017-02-14-using-native-driver-for-animated.md @@ -16,7 +16,7 @@ The Animated API was designed with a very important constraint in mind, it is se ## A bit of history... -This project started about a year ago, when Exponent built the li.st app on Android. [Krzysztof Magiera](https://twitter.com/kzzzf) was contracted to build the initial implementation on Android. It ended up working well and li.st was the first app to ship with native driven animations using Animated. A few months later, [Brandon Withrow](https://github.com/buba447) built the initial implementation on iOS. After that, [Ryan Gomba](https://twitter.com/ryangomba) and myself worked on adding missing features like support for `Animated.event` as well as squash bugs we found when using it in production apps. This was truly a community effort and I would like to thanks everyone that was involved as well as Exponent for sponsoring a large part of the development. It is now used by `Touchable` components in React Native as well as for navigation animations in the newly released [React Navigation](https://github.com/react-community/react-navigation) library. +This project started about a year ago, when Expo built the li.st app on Android. [Krzysztof Magiera](https://twitter.com/kzzzf) was contracted to build the initial implementation on Android. It ended up working well and li.st was the first app to ship with native driven animations using Animated. A few months later, [Brandon Withrow](https://github.com/buba447) built the initial implementation on iOS. After that, [Ryan Gomba](https://twitter.com/ryangomba) and myself worked on adding missing features like support for `Animated.event` as well as squash bugs we found when using it in production apps. This was truly a community effort and I would like to thanks everyone that was involved as well as Expo for sponsoring a large part of the development. It is now used by `Touchable` components in React Native as well as for navigation animations in the newly released [React Navigation](https://github.com/react-community/react-navigation) library. ## How does it work? diff --git a/blog/2017-03-13-better-list-views.md b/blog/2017-03-13-better-list-views.md new file mode 100644 index 00000000000000..c18e6f29b1cbd7 --- /dev/null +++ b/blog/2017-03-13-better-list-views.md @@ -0,0 +1,120 @@ +--- +title: Better List Views in React Native +author: Spencer Ahrens +authorTitle: Software Engineer at Facebook +authorURL: https://github.com/sahrens +authorImage: https://avatars1.githubusercontent.com/u/1509831 +authorTwitter: sahrens2012 +category: engineering +--- + +Many of you have started playing with some of our new List components already after our [teaser announcement in the community group](https://www.facebook.com/groups/react.native.community/permalink/921378591331053), but we are officially announcing them today! No more `ListView`s or `DataSource`s, stale rows, ignored bugs, or excessive memory consumption - with the latest React Native March 2017 release candidate (`0.43-rc.1`) you can pick from the new suite of components what best fits your use-case, with great perf and feature sets out of the box: + +### [``](https://facebook.github.io/react-native/releases/next/docs/flatlist.html) ### + +This is the workhorse component for simple, performant lists. Provide an array of data and a `renderItem` function and you're good to go: + +``` + +``` + +### [``](https://facebook.github.io/react-native/releases/next/docs/sectionlist.html) ### + +If you want to render a set of data broken into logical sections, maybe with section headers (e.g. in an alphabetical address book), and potentially with heterogeneous data and rendering (such as a profile view with some buttons followed by a composer, then a photo grid, then a friend grid, and finally a list of stories), this is the way to go. + +``` +

} + sections={[ // homogenous rendering between sections + {data: [...], key: ...}, + {data: [...], key: ...}, + {data: [...], key: ...}, + ]} +/> + + +``` + +### [``](https://facebook.github.io/react-native/releases/next/docs/virtualizedlist.html) ## + +The implementation behind the scenes with a more flexible API. Especially handy if your data is not in a plain array (e.g. an immutable list). + +## Features ## + +Lists are used in many contexts, so we packed the new components full of features to handle the majority of use cases out of the box: + +* Scroll loading (`onEndReached`). +* Pull to refresh (`onRefresh` / `refreshing`). +* [Configurable](https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Lists/ViewabilityHelper.js) viewability (VPV) callbacks (`onViewableItemsChanged` / `viewabilityConfig`). +* Horizontal mode (`horizontal`). +* Intelligent item and section separators. +* Multi-column support (`numColumns`) +* `scrollToEnd`, `scrollToIndex`, and `scrollToItem` +* Better Flow typing. + +### Some Caveats ### + +- The internal state of item subtrees is not preserved when content scrolls out of the render window. Make sure all your data is captured in the item data or external stores like Flux, Redux, or Relay. + +- These components are based on `PureComponent` which means that they will not re-render if `props` remains shallow-equal. Make sure that everything your `renderItem` function depends on directly is passed as a prop that is not `===` after updates, otherwise your UI may not update on changes. This includes the `data` prop and parent component state. For example: + + ```javascript + this.setState((oldState) => ({ + selected: { // New instance breaks `===` + ...oldState.selected, // copy old data + [item.key]: !oldState.selected[item.key], // toggle + }})) + } + selected={ + !!this.state.selected[item.key] // renderItem depends on state + } + />} + selected={ // Can be any prop that doesn't collide with existing props + this.state.selected // A change to selected should re-render FlatList + } + /> + ``` + +- In order to constrain memory and enable smooth scrolling, content is rendered asynchronously offscreen. This means it's possible to scroll faster than the fill rate and momentarily see blank content. This is a tradeoff that can be adjusted to suit the needs of each application, and we are working on improving it behind the scenes. + +- By default, these new lists look for a `key` prop on each item and use that for the React key. Alternatively, you can provide a custom `keyExtractor` prop. + +## Performance ## + +Besides simplifying the API, the new list components also have significant performance enhancements, the main one being nearly constant memory usage for any number of rows. This is done by 'virtualizing' elements that are outside of the render window by completely unmounting them from the component hierarchy and reclaiming the JS memory from the react components, along with the native memory from the shadow tree and the UI views. This has a catch which is that internal component state will not be preserved, so **make sure you track any important state outside of the components themselves, e.g. in Relay or Redux or Flux store.** + +Limiting the render window also reduces the amount of work that needs to be done by React and the native platform, e.g from view traversals. Even if you are rendering the last of a million elements, with these new lists there is no need to iterate through all those elements in order to render. You can even jump to the middle with `scrollToIndex` without excessive rendering. + +We've also made some improvements with scheduling which should help with application responsiveness. Items at the edge of the render window are rendered infrequently and at a lower priority after any active gestures or animations or other interactions have completed. + +## Advanced Usage ## + +Unlike `ListView`, all items in the render window are re-rendered any time any props change. Often this is fine because the windowing reduces the number of items to a constant number, but if your items are on the complex side, you should make sure to follow React best practices for performance and use `React.PureComponent` and/or `shouldComponentUpdate` as appropriate within your components to limit re-renders of the recursive subtree. + +If you can calculate the height of your rows without rendering them, you can improve the user experience by providing the `getItemLayout` prop. This makes it much smoother to scroll to specific items with e.g. `scrollToIndex`, and will improve the scroll indicator UI because the height of the content can be determined without rendering it. + +If you have an alternative data type, like an immutable list, `` is the way to go. It takes a `getItem` prop that lets you return the item data for any given index and has looser flow typing. + +There are also a bunch of parameters you can tweak if you have an unusual use case. For example, you can use `windowSize` to trade off memory usage vs. user experience, `maxToRenderPerBatch` to adjust fill rate vs. responsiveness, `onEndReachedThreshold` to control when scroll loading happens, and more. + +## Future Work ## + +* Migration of existing surfaces (ultimately deprecation of `ListView`). +* More features as we see/hear the need (let us know!). +* Sticky section header support. +* More performance optimizations. +* Support functional item components with state. diff --git a/blog/2017-03-13-idx-the-existential-function.md b/blog/2017-03-13-idx-the-existential-function.md new file mode 100644 index 00000000000000..d10ba1cde0683b --- /dev/null +++ b/blog/2017-03-13-idx-the-existential-function.md @@ -0,0 +1,48 @@ +--- +title: idx: The Existential Function +author: Timothy Yung +authorTitle: Engineering Manager at Facebook +authorURL: https://github.com/yungsters +authorImage: https://pbs.twimg.com/profile_images/1592444107/image.jpg +authorTwitter: yungsters +category: engineering +--- + +At Facebook, we often need to access deeply nested values in data structures fetched with GraphQL. On the way to accessing these deeply nested values, it is common for one or more intermediate fields to be nullable. These intermediate fields may be null for a variety of reasons, from failed privacy checks to the mere fact that null happens to be the most flexible way to represent non-fatal errors. + +Unfortunately, accessing these deeply nested values is currently tedious and verbose. + +```javascript +props.user && +props.user.friends && +props.user.friends[0] && +props.user.friends[0].friends +``` + +There is [an ECMAScript proposal to introduce the existential operator](https://github.com/claudepache/es-optional-chaining) which will make this much more convenient. But until a time when that proposal is finalized, we want a solution that improves our quality of life, maintains existing language semantics, and encourages type safety with Flow. + +We came up with an existential _function_ we call `idx`. + +```javascript +idx(props, _ => _.user.friends[0].friends) +``` + +The invocation in this code snippet behaves similarly to the boolean expression in the code snippet above, except with significantly less repetition. The `idx` function takes exactly two arguments: + +- Any value, typically an object or array into which you want to access a nested value. +- A function that receives the first argument and accesses a nested value on it. + +In theory, the `idx` function will try-catch errors that are the result of accessing properties on null or undefined. If such an error is caught, it will return either null or undefined. (And you can see how this might be implemented in [idx.js](https://github.com/facebookincubator/idx/blob/master/packages/idx/src/idx.js).) + +In practice, try-catching every nested property access is slow, and differentiating between specific kinds of TypeErrors is fragile. To deal with these shortcomings, we created a Babel plugin that transforms the above `idx` invocation into the following expression: + +```javascript +props.user == null ? props.user : +props.user.friends == null ? props.user.friends : +props.user.friends[0] == null ? props.user.friends[0] : +props.user.friends[0].friends +``` + +Finally, we added a custom Flow type declaration for `idx` that allows the traversal in the second argument to be properly type-checked while permitting nested access on nullable properties. + +The function, Babel plugin, and Flow declaration are now [available on GitHub](https://github.com/facebookincubator/idx). They are used by installing the **idx** and **babel-plugin-idx** npm packages, and adding “idx” to the list of plugins in your `.babelrc` file. diff --git a/blog/2017-03-13-introducing-create-react-native-app.md b/blog/2017-03-13-introducing-create-react-native-app.md new file mode 100644 index 00000000000000..4ff6714e8d05a6 --- /dev/null +++ b/blog/2017-03-13-introducing-create-react-native-app.md @@ -0,0 +1,34 @@ +--- +title: Introducing Create React Native App +author: Adam Perry +authorTitle: Software Engineer at Expo +authorURL: https://github.com/dikaiosune +authorImage: https://avatars2.githubusercontent.com/u/6812281 +authorTwitter: dika10sune +category: engineering +--- + +Today we’re announcing [Create React Native App](https://github.com/react-community/create-react-native-app): a new tool that makes it significantly easier to get started with a React Native project! It’s heavily inspired by the design of [Create React App](https://github.com/facebookincubator/create-react-app) and is the product of a collaboration between [Facebook](https://code.facebook.com) and [Expo](https://expo.io) (formerly Exponent). + +Many developers struggle with installing and configuring React Native’s current native build dependencies, especially for Android. With Create React Native App, there’s no need to use Xcode or Android Studio, and you can develop for your iOS device using Linux or Windows. This is accomplished using the Expo app, which loads and runs CRNA projects written in pure JavaScript without compiling any native code. + +Try creating a new project (replace with suitable yarn commands if you have it installed): + +``` +$ npm i -g create-react-native-app +$ create-react-native-app my-project +$ cd my-project +$ npm start +``` + +This will start the React Native packager and print a QR code. Open it in the [Expo app](https://expo.io) to load your JavaScript. Calls to `console.log` are forwarded to your terminal. You can make use of any standard React Native APIs as well as the [Expo SDK](https://docs.expo.io/versions/latest/sdk/index.html). + +## What about native code? + +Many React Native projects have Java or Objective-C/Swift dependencies that need to be compiled. The Expo app does include APIs for camera, video, contacts, and more, and bundles popular libraries like [Airbnb’s react-native-maps](https://docs.expo.io/versions/v14.0.0/sdk/map-view.html), or [Facebook authentication](https://docs.expo.io/versions/latest/sdk/facebook.html). However if you need a native code dependency that Expo doesn’t bundle then you’ll probably need to have your own build configuration for it. Just like Create React App, “ejecting” is supported by CRNA. + +You can run `npm run eject` to get a project very similar to what `react-native init` would generate. At that point you’ll need Xcode and/or Android Studio just as you would if you started with `react-native init` , adding libraries with `react-native link` will work, and you’ll have full control over the native code compilation process. + +## Questions? Feedback? + +Create React Native App is now stable enough for general use, which means we’re very eager to hear about your experience using it! You can find me [on Twitter](https://twitter.com/dika10sune) or open an issue on [the GitHub repository](https://github.com/react-community/create-react-native-app). Pull requests are very welcome! diff --git a/docs/Accessibility.md b/docs/Accessibility.md index b0921506497aed..c7c5f291824ef7 100644 --- a/docs/Accessibility.md +++ b/docs/Accessibility.md @@ -5,7 +5,7 @@ layout: docs category: Guides permalink: docs/accessibility.html next: timers -previous: animations +previous: debugging --- ## Native App Accessibility (iOS and Android) diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md index 5afff0e9bab1fe..d6ebe9ae0f6237 100644 --- a/docs/AndroidBuildingFromSource.md +++ b/docs/AndroidBuildingFromSource.md @@ -4,6 +4,7 @@ title: Building React Native from source layout: docs category: Guides (Android) permalink: docs/android-building-from-source.html +banner: ejected next: activityindicator previous: android-ui-performance --- diff --git a/docs/AndroidUIPerformance.md b/docs/AndroidUIPerformance.md index 1593602470ac29..b19442995b1276 100644 --- a/docs/AndroidUIPerformance.md +++ b/docs/AndroidUIPerformance.md @@ -1,165 +1,7 @@ --- id: android-ui-performance title: Profiling Android UI Performance -layout: docs -category: Guides (Android) +layout: redirect permalink: docs/android-ui-performance.html -next: android-building-from-source -previous: signed-apk-android +destinationUrl: performance.html --- - -We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible. Remember, Android supports 10k+ different phones and is generalized to support software rendering: the framework architecture and need to generalize across many hardware targets unfortunately means you get less for free relative to iOS. But sometimes, there are things you can improve (and many times it's not native code's fault at all!). - -The first step for debugging this jank is to answer the fundamental question of where your time is being spent during each 16ms frame. For that, we'll be using a standard Android profiling tool called systrace. But first... - -> Make sure that JS dev mode is OFF! -> -> You should see `__DEV__ === false, development-level warning are OFF, performance optimizations are ON` in your application logs (which you can view using `adb logcat`) - -## Profiling with Systrace - -Systrace is a standard Android marker-based profiling tool (and is installed when you install the Android platform-tools package). Profiled code blocks are surrounded by markers start/end markers which are then visualized in a colorful chart format. Both the Android SDK and React Native framework provide standard markers that you can visualize. - -### Collecting a trace - -> NOTE: -> -> Systrace support was added in react-native `v0.15`. You will need to build with that version to collect a trace. - -First, connect a device that exhibits the stuttering you want to investigate to your computer via USB and get it to the point right before the navigation/animation you want to profile. Run systrace as follows - -``` -$ /platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a -``` - -A quick breakdown of this command: - -- `time` is the length of time the trace will be collected in seconds -- `sched`, `gfx`, and `view` are the android SDK tags (collections of markers) we care about: `sched` gives you information about what's running on each core of your phone, `gfx` gives you graphics info such as frame boundaries, and `view` gives you information about measure, layout, and draw passes -- `-a ` enables app-specific markers, specifically the ones built into the React Native framework. `your_package_name` can be found in the `AndroidManifest.xml` of your app and looks like `com.example.app` - -Once the trace starts collecting, perform the animation or interaction you care about. At the end of the trace, systrace will give you a link to the trace which you can open in your browser. - -## Reading the trace - -After opening the trace in your browser (preferably Chrome), you should see something like this: - -![Example](img/SystraceExample.png) - -If your trace .html file isn't opening correctly, check your browser console for the following: - -![ObjectObserveError](img/ObjectObserveError.png) - -Since Object.observe was deprecated in recent browsers, you may have to open the file from the Google Chrome Tracing tool. You can do so by: - -- Opening tab in chrome chrome://tracing -- Selecting load -- Selecting the html file generated from the previous command. - -**HINT**: Use the WASD keys to strafe and zoom - -### Enable VSync highlighting - -The first thing you should do is highlight the 16ms frame boundaries if you haven't already done that. Check this checkbox at the top right of the screen: - -![Enable VSync Highlighting](img/SystraceHighlightVSync.png) - -You should see zebra stripes as in the screenshot above. If you don't, try profiling on a different device: Samsung has been known to have issues displaying vsyncs while the Nexus series is generally pretty reliable. - -### Find your process - -Scroll until you see (part of) the name of your package. In this case, I was profiling `com.facebook.adsmanager`, which shows up as `book.adsmanager` because of silly thread name limits in the kernel. - -On the left side, you'll see a set of threads which correspond to the timeline rows on the right. There are three/four threads we care about for our purposes: the UI thread (which has your package name or the name UI Thread), `mqt_js` and `mqt_native_modules`. If you're running on Android 5+, we also care about the Render Thread. - -### UI Thread - -This is where standard android measure/layout/draw happens. The thread name on the right will be your package name (in my case book.adsmanager) or UI Thread. The events that you see on this thread should look something like this and have to do with `Choreographer`, `traversals`, and `DispatchUI`: - -![UI Thread Example](img/SystraceUIThreadExample.png) - -### JS Thread - -This is where JS is executed. The thread name will be either `mqt_js` or `<...>` depending on how cooperative the kernel on your device is being. To identify it if it doesn't have a name, look for things like `JSCall`, `Bridge.executeJSCall`, etc: - -![JS Thread Example](img/SystraceJSThreadExample.png) - -### Native Modules Thread - -This is where native module calls (e.g. the `UIManager`) are executed. The thread name will be either `mqt_native_modules` or `<...>`. To identify it in the latter case, look for things like `NativeCall`, `callJavaModuleMethod`, and `onBatchComplete`: - -![Native Modules Thread Example](img/SystraceNativeModulesThreadExample.png) - -### Bonus: Render Thread - -If you're using Android L (5.0) and up, you will also have a render thread in your application. This thread generates the actual OpenGL commands used to draw your UI. The thread name will be either `RenderThread` or `<...>`. To identify it in the latter case, look for things like `DrawFrame` and `queueBuffer`: - -![Render Thread Example](img/SystraceRenderThreadExample.png) - -## Identifying a culprit - -A smooth animation should look something like the following: - -![Smooth Animation](img/SystraceWellBehaved.png) - -Each change in color is a frame -- remember that in order to display a frame, all our UI work needs to be done by the end of that 16ms period. Notice that no thread is working close to the frame boundary. An application rendering like this is rendering at 60FPS. - -If you noticed chop, however, you might see something like this: - -![Choppy Animation from JS](img/SystraceBadJS.png) - -Notice that the JS thread is executing basically all the time, and across frame boundaries! This app is not rendering at 60FPS. In this case, **the problem lies in JS**. - -You might also see something like this: - -![Choppy Animation from UI](img/SystraceBadUI.png) - -In this case, the UI and render threads are the ones that have work crossing frame boundaries. The UI that we're trying to render on each frame is requiring too much work to be done. In this case, **the problem lies in the native views being rendered**. - -At this point, you'll have some very helpful information to inform your next steps. - -## JS Issues - -If you identified a JS problem, look for clues in the specific JS that you're executing. In the scenario above, we see `RCTEventEmitter` being called multiple times per frame. Here's a zoom-in of the JS thread from the trace above: - -![Too much JS](img/SystraceBadJS2.png) - -This doesn't seem right. Why is it being called so often? Are they actually different events? The answers to these questions will probably depend on your product code. And many times, you'll want to look into [shouldComponentUpdate](https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate). - -> **TODO**: Add more tools for profiling JS - -## Native UI Issues - -If you identified a native UI problem, there are usually two scenarios: - -1. the UI you're trying to draw each frame involves to much work on the GPU, or -2. You're constructing new UI during the animation/interaction (e.g. loading in new content during a scroll). - -### Too much GPU work - -In the first scenario, you'll see a trace that has the UI thread and/or Render Thread looking like this: - -![Overloaded GPU](img/SystraceBadUI.png) - -Notice the long amount of time spent in `DrawFrame` that crosses frame boundaries. This is time spent waiting for the GPU to drain its command buffer from the previous frame. - -To mitigate this, you should: - -- investigate using `renderToHardwareTextureAndroid` for complex, static content that is being animated/transformed (e.g. the `Navigator` slide/alpha animations) -- make sure that you are **not** using `needsOffscreenAlphaCompositing`, which is disabled by default, as it greatly increases the per-frame load on the GPU in most cases. - -If these don't help and you want to dig deeper into what the GPU is actually doing, you can check out [Tracer for OpenGL ES](http://developer.android.com/tools/help/gltracer.html). - -### Creating new views on the UI thread - -In the second scenario, you'll see something more like this: - -![Creating Views](img/SystraceBadCreateUI.png) - -Notice that first the JS thread thinks for a bit, then you see some work done on the native modules thread, followed by an expensive traversal on the UI thread. - -There isn't an easy way to mitigate this unless you're able to postpone creating new UI until after the interaction, or you are able to simplify the UI you're creating. The react native team is working on a infrastructure level solution for this that will allow new UI to be created and configured off the main thread, allowing the interaction to continue smoothly. - -## Still stuck? - -If you are confused or stuck, please post ask on [Stack Overflow with the react-native tag](http://stackoverflow.com/tags/react-native). If you are unable to get a response there, or find an issue with a core component, please [File a Github issue](https://github.com/facebook/react-native/issues). diff --git a/docs/Animations.md b/docs/Animations.md index 04b41ef67892bb..c97fe2b460b077 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -4,7 +4,7 @@ title: Animations layout: docs category: Guides permalink: docs/animations.html -next: accessibility +next: navigation previous: handling-touches --- diff --git a/docs/Colors.md b/docs/Colors.md index d7831eef1d5fed..c896703f8ec6ae 100644 --- a/docs/Colors.md +++ b/docs/Colors.md @@ -4,26 +4,41 @@ title: Colors layout: docs category: Guides permalink: docs/colors.html -next: images -previous: integration-with-existing-apps +next: platform-specific-code +previous: images --- -The following formats are supported: +Components in React Native are [styled using JavaScript](docs/styles.html). Color properties usually match how [CSS works on the web](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). - - `'#f0f'` (#rgb) - - `'#f0fc'` (#rgba) - - `'#ff00ff'` (#rrggbb) - - `'#ff00ff00'` (#rrggbbaa) - - `'rgb(255, 255, 255)'` - - `'rgba(255, 255, 255, 1.0)'` - - `'hsl(360, 100%, 100%)'` - - `'hsla(360, 100%, 100%, 1.0)'` - - `'transparent'` - - `'red'` - - `0xff00ff00` (0xrrggbbaa) +### Red-green-blue +React Native supports `rgb()` and `rgba()` in both hexadecimal and functional notation: -For the named colors, React Native follows the [CSS3 specification](http://www.w3.org/TR/css3-color/#svg-color): +- `'#f0f'` (#rgb) +- `'#ff00ff'` (#rrggbb) + +- `'rgb(255, 0, 255)'` +- `'rgba(255, 255, 255, 1.0)'` + +- `'#f0ff'` (#rgba) +- `'#ff00ff00'` (#rrggbbaa) + +### Hue-saturation-lightness + +`hsl()` and `hsla()` is supported in functional notation: + +- `'hsl(360, 100%, 100%)'` +- `'hsla(360, 100%, 100%, 1.0)'` + +### `transparent` + +This is a shortcut for `rgba(0,0,0,0)`: + +- `'transparent'` + +### Named colors + +You can also use color names as values. React Native follows the [CSS3 specification](http://www.w3.org/TR/css3-color/#svg-color): - aliceblue (#f0f8ff) - antiquewhite (#faebd7) diff --git a/docs/CommunicationIOS.md b/docs/CommunicationIOS.md index dd6ec7ffff41e5..95b3ea0bed122c 100644 --- a/docs/CommunicationIOS.md +++ b/docs/CommunicationIOS.md @@ -4,8 +4,9 @@ title: Communication between native and React Native layout: docs category: Guides (iOS) permalink: docs/communication-ios.html +banner: ejected next: native-modules-android -previous: running-on-simulator-ios +previous: linking-libraries-ios --- In [Integrating with Existing Apps guide](docs/integration-with-existing-apps.html) and [Native UI Components guide](docs/native-components-ios.html) we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques. diff --git a/docs/Debugging.md b/docs/Debugging.md index 410d153fb901db..9323b272e1bc59 100644 --- a/docs/Debugging.md +++ b/docs/Debugging.md @@ -4,10 +4,14 @@ title: Debugging layout: docs category: Guides permalink: docs/debugging.html -next: testing -previous: direct-manipulation +next: accessibility +previous: platform-specific-code --- +## Enabling Keyboard Shortcuts + +React Native supports a few keyboard shortcuts in the iOS Simulator. They are described below. To enable them, open the Hardware menu, select Keyboard, and make sure that "Connect Hardware Keyboard" is checked. + ## Accessing the In-App Developer Menu You can access the developer menu by shaking your device or by selecting "Shake Gesture" inside the Hardware menu in the iOS Simulator. You can also use the **`Command`**`⌘` + **`D`** keyboard shortcut when your app is running in the iPhone Simulator, or **`Command`**`⌘` + **`M`** when running in an Android emulator. @@ -20,8 +24,6 @@ You can access the developer menu by shaking your device or by selecting "Shake Instead of recompiling your app every time you make a change, you can reload your app's JavaScript code instantly. To do so, select "Reload" from the Developer Menu. You can also press **`Command`**`⌘` + **`R`** in the iOS Simulator, or press **`R`** twice on Android emulators. -> If the **`Command`**`⌘` + **`R`** keyboard shortcut does not seem to reload the iOS Simulator, go to the Hardware menu, select Keyboard, and make sure that "Connect Hardware Keyboard" is checked. - ### Automatic reloading You can speed up your development times by having your app reload automatically any time your code changes. Automatic reloading can be enabled by selecting "Enable Live Reload" from the Developer Menu. diff --git a/docs/DirectManipulation.md b/docs/DirectManipulation.md index 7cc795a3efdfd3..8c692544d5791e 100644 --- a/docs/DirectManipulation.md +++ b/docs/DirectManipulation.md @@ -4,8 +4,8 @@ title: Direct Manipulation layout: docs category: Guides permalink: docs/direct-manipulation.html -next: debugging -previous: timers +next: performance +previous: javascript-environment --- It is sometimes necessary to make changes directly to a component diff --git a/docs/GestureResponderSystem.md b/docs/GestureResponderSystem.md index 3f4604577f9934..a53faef6c59d26 100644 --- a/docs/GestureResponderSystem.md +++ b/docs/GestureResponderSystem.md @@ -4,8 +4,8 @@ title: Gesture Responder System layout: docs category: Guides permalink: docs/gesture-responder-system.html -next: native-modules-ios -previous: platform-specific-code +next: testing +previous: performance --- The gesture responder system manages the lifecycle of gestures in your app. A touch can go through several phases as the app determines what the user's intention is. For example, the app needs to determine if the touch is scrolling, sliding on a widget, or tapping. This can even change during the duration of a touch. There can also be multiple simultaneous touches. diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 4e7a00cf27d051..2aa0b2623e0112 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -65,19 +65,25 @@ one to start with, since the setup is a bit different. ## Installing Dependencies -You will need Node.js, Watchman, the React Native command line interface, and Xcode. +You will need Node, Watchman, the React Native command line interface, and Xcode. ## Installing Dependencies -You will need Node.js, Watchman, the React Native command line interface, and Android Studio. +You will need Node, Watchman, the React Native command line interface, a JDK, and Android Studio. - + ## Installing Dependencies -You will need Node.js, the React Native command line interface, and Android Studio. +You will need Node, the React Native command line interface, a JDK, and Android Studio. + + + +## Installing Dependencies + +You will need Node, the React Native command line interface, Python2, a JDK, and Android Studio. @@ -90,33 +96,41 @@ brew install node brew install watchman ``` -> [Watchman](https://facebook.github.io/watchman) is a tool by Facebook for watching -changes in the filesystem. It is highly recommended you install it for better performance. +If you have already installed Node on your system, make sure it is version 4 or newer. + +[Watchman](https://facebook.github.io/watchman) is a tool by Facebook for watching changes in the filesystem. It is highly recommended you install it for better performance. ### Node -Follow the [installation instructions for your Linux distribution](https://nodejs.org/en/download/package-manager/) to install Node.js 4 or newer. +Follow the [installation instructions for your Linux distribution](https://nodejs.org/en/download/package-manager/) to install Node 4 or newer. -### Node +### Node, Python2, JDK -We recommend installing Node.js and Python2 via [Chocolatey](https://chocolatey.org), a popular package manager for Windows. Open a Command Prompt as Administrator, then run: +We recommend installing Node and Python2 via [Chocolatey](https://chocolatey.org), a popular package manager for Windows. + +Android Studio, which we will install next, requires a recent version of the [Java SE Development Kit (JDK)](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) which can be installed using Chocolatey. + +Open a Command Prompt as Administrator, then run: ``` choco install nodejs.install choco install python2 +choco install jdk8 ``` +If you have already installed Node on your system, make sure it is version 4 or newer. If you already have a JDK on your system, make sure it is version 8 or newer. + > You can find additional installation options on [Node.js's Downloads page](https://nodejs.org/en/download/). ### The React Native CLI -Node.js comes with npm, which lets you install the React Native command line interface. +Node comes with npm, which lets you install the React Native command line interface. Run the following command in a Terminal: @@ -130,7 +144,7 @@ npm install -g react-native-cli ### The React Native CLI -Node.js comes with npm, which lets you install the React Native command line interface. +Node comes with npm, which lets you install the React Native command line interface. Run the following command in a Terminal: @@ -146,32 +160,33 @@ npm install -g react-native-cli The easiest way to install Xcode is via the [Mac App Store](https://itunes.apple.com/us/app/xcode/id497799835?mt=12). Installing Xcode will also install the iOS Simulator and all the necessary tools to build your iOS app. +If you have already installed Xcode on your system, make sure it is version 8 or higher. + +You will also need to install the Xcode Command Line Tools. Open Xcode, then choose "Preferences..." from the Xcode menu. Go to the Locations panel and install the tools by selecting the most recent version in the Command Line Tools dropdown. + +![Xcode Command Line Tools](img/XcodeCommandLineTools.png) + ### Android Development Environment Setting up your development environment can be somewhat tedious if you're new to Android development. If you're already familiar with Android development, there are a few things you may need to configure. In either case, please make sure to carefully follow the next few steps. -#### 1. Download and install Android Studio - -[Android Studio](https://developer.android.com/studio/install.html) provides the Android SDK and AVD (emulator) required to run and test your React Native apps. + - +> Android Studio requires a recent version of the [Java SE Development Kit (JDK)](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html). Go ahead and install JDK 8 or newer if needed. -> Android Studio requires the [Java SE Development Kit(JDK)](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html), version 8. You can type `javac -version` in a terminal to see what version you have, if any. + -``` -$ javac -version -javac 1.8.0_111 -``` +#### 1. Download and install Android Studio -> The version string `1.8.x_xxx` corresponds to JDK 8. +Android Studio provides the Android SDK and AVD (emulator) required to run and test your React Native apps. [Download Android Studio](https://developer.android.com/studio/index.html), then follow the [installation instructions](https://developer.android.com/studio/install.html). Choose `Custom` installation when prompted by the Setup Wizard, and proceed to the next step. #### 2. Install the AVD and HAXM -Choose `Custom` installation when running Android Studio for the first time. Make sure the boxes next to all of the following are checked: +Android Virtual Devices allow you to run Android apps on your computer without the need for an actual Android phone or tablet. Choose `Custom` installation when running Android Studio for the first time. Make sure the boxes next to all of the following are checked: - `Android SDK` - `Android SDK Platform` @@ -186,7 +201,7 @@ Then, click "Next" to install all of these components. #### 2. Install the AVD and configure VM acceleration -Choose `Custom` installation when running Android Studio for the first time. Make sure the boxes next to all of the following are checked: +Android Virtual Devices allow you to run Android apps on your computer without the need for an actual Android phone or tablet. Choose `Custom` installation when running Android Studio for the first time. Make sure the boxes next to all of the following are checked: - `Android SDK` - `Android SDK Platform` @@ -206,10 +221,11 @@ Select the "SDK Platforms" tab from within the SDK Manager, then check the box n - `Google APIs` - `Android SDK Platform 23` -- `Intel x86 Atom System Image` - `Intel x86 Atom_64 System Image` - `Google APIs Intel x86 Atom_64 System Image` +![Android SDK Manager](img/AndroidSDKManager.png) + Next, select the "SDK Tools" tab and check the box next to "Show Package Details" here as well. Look for and expand the "Android SDK Build Tools" entry, then make sure that `Android SDK Build-Tools 23.0.1` is selected. Finally, click "Apply" to download and install the Android SDK and related build tools. @@ -222,7 +238,7 @@ The React Native command line interface requires the `ANDROID_HOME` environment -Add the following lines to your `~/.bashrc` (or equivalent) config file: +Add the following lines to your `~/.profile` (or equivalent) config file: ``` export ANDROID_HOME=${HOME}/Library/Android/sdk @@ -230,11 +246,13 @@ export PATH=${PATH}:${ANDROID_HOME}/tools export PATH=${PATH}:${ANDROID_HOME}/platform-tools ``` +Type `source ~/.profile` to load the config into your current shell. + > Please make sure you export the correct path for `ANDROID_HOME`. If you installed the Android SDK using Homebrew, it would be located at `/usr/local/opt/android-sdk`. -Add the following lines to your `~/.bashrc` (or equivalent) config file: +Add the following lines to your `~/.profile` (or equivalent) config file: ``` export ANDROID_HOME=${HOME}/Android/Sdk @@ -242,6 +260,8 @@ export PATH=${PATH}:${ANDROID_HOME}/tools export PATH=${PATH}:${ANDROID_HOME}/platform-tools ``` +Type `source ~/.profile` to load the config into your current shell. + > Please make sure you export the correct path for `ANDROID_HOME` if you did not install the Android SDK using Android Studio. @@ -276,9 +296,15 @@ You can see the list of available AVDs by opening the "AVD Manager" from within android avd ``` -Once in the "AVD Manager", select your AVD and click "Start...". +Once in the "AVD Manager", select your AVD and click "Edit...". Choose "Android 6.0 - API Level 23" under Device, and "Intel Atom (x86_64)" under CPU/ABI. Click OK, then select your new AVD and click "Start...", and finally, "Launch". + +![Android AVD Configuration](img/AndroidAVDConfiguration.png) -> Android Studio should have set up an Android Virtual Device for you during installation, but it is very common to run into an issue where Android Studio fails to install the AVD. You may follow the [Android Studio User Guide](https://developer.android.com/studio/run/managing-avds.html) to create a new AVD manually if needed. +> It is very common to run into an issue where Android Studio fails to create a default AVD. You may follow the [Android Studio User Guide](https://developer.android.com/studio/run/managing-avds.html) to create a new AVD manually if needed. + +### Using a real device + +If you have a physical Android device, you can use it for development in place of an AVD. Plug it in to your computer using a USB cable and [enable USB debugging](https://developer.android.com/training/basics/firstapp/running-app.html) before proceeding to the next step. @@ -296,6 +322,8 @@ react-native run-ios You should see your new app running in the iOS Simulator shortly. +![AwesomeProject on iOS](img/iOSSuccess.png) + `react-native run-ios` is just one way to run your app. You can also run it directly from within Xcode or [Nuclide](https://nuclide.io/). @@ -308,7 +336,9 @@ cd AwesomeProject react-native run-android ``` -If everything is set up correctly, you should see your new app running in your AVD shortly. +If everything is set up correctly, you should see your new app running in your Android emulator shortly. + +![AwesomeProject on Android](img/AndroidSuccess.png) `react-native run-android` is just one way to run your app - you can also run it directly from within Android Studio or [Nuclide](https://nuclide.io/). @@ -348,7 +378,7 @@ cd AwesomeProject react-native start ``` -Open a new command prompt and run `react-native run-android` inside the same folder to launch the app on your AVD. +Open a new command prompt and run `react-native run-android` inside the same folder to launch the app on your Android emulator. ``` react-native run-android @@ -370,11 +400,7 @@ react-native run-android If everything is set up correctly, you should see your new app running in your Android emulator shortly. - - -> If you're targeting API level 23, the app might crash on first launch with an error smilar to `Unable to add window android.view.ViewRootImpl$W@c51fa6 -- permission denied for this window type`. To fix this, you need to go to `System settings > Apps > Configure apps > Draw over other apps` and grant the permission for the app. - -NOTE: Many React Native modules haven't been tested on Marshmallow and might break. Please thoroughly test the app if you target API level 23 and file a bug report if you find that something is broken. +![AwesomeProject on Android](img/AndroidSuccess.png) diff --git a/docs/HandlingTouches.md b/docs/HandlingTouches.md index aa73bb58b9a354..4aade17e0d70ab 100644 --- a/docs/HandlingTouches.md +++ b/docs/HandlingTouches.md @@ -5,7 +5,7 @@ layout: docs category: Guides permalink: docs/handling-touches.html next: animations -previous: images +previous: more-resources --- Users interact with mobile apps mainly through touch. They can use a combination of gestures, such as tapping on a button, scrolling a list, or zooming on a map. diff --git a/docs/HeadlessJSAndroid.md b/docs/HeadlessJSAndroid.md index 32de1ef1e5e85b..005e43376b7937 100644 --- a/docs/HeadlessJSAndroid.md +++ b/docs/HeadlessJSAndroid.md @@ -4,6 +4,7 @@ title: Headless JS layout: docs category: Guides (Android) permalink: docs/headless-js-android.html +banner: ejected next: signed-apk-android previous: native-components-android --- diff --git a/docs/HeightAndWidth.md b/docs/HeightAndWidth.md index 0b9901a387a1a0..32c4b631630022 100644 --- a/docs/HeightAndWidth.md +++ b/docs/HeightAndWidth.md @@ -28,7 +28,7 @@ class FixedDimensionsBasics extends Component { ); } -}; +} AppRegistry.registerComponent('AwesomeProject', () => FixedDimensionsBasics); ``` @@ -58,7 +58,7 @@ class FlexDimensionsBasics extends Component { ); } -}; +} AppRegistry.registerComponent('AwesomeProject', () => FlexDimensionsBasics); ``` diff --git a/docs/Images.md b/docs/Images.md index c8db2866958800..3a3e4456b9bd05 100644 --- a/docs/Images.md +++ b/docs/Images.md @@ -4,8 +4,8 @@ title: Images layout: docs category: Guides permalink: docs/images.html -next: handling-touches -previous: colors +next: colors +previous: navigation --- ## Static Image Resources diff --git a/docs/IntegrationWithExistingApps.md b/docs/IntegrationWithExistingApps.md index 4a4b5810fc3f66..3afa8669bb3f58 100644 --- a/docs/IntegrationWithExistingApps.md +++ b/docs/IntegrationWithExistingApps.md @@ -4,8 +4,9 @@ title: Integration With Existing Apps layout: docs category: Guides permalink: docs/integration-with-existing-apps.html -next: colors -previous: more-resources +banner: ejected +next: running-on-device +previous: testing ---
diff --git a/docs/JavaScriptEnvironment.md b/docs/JavaScriptEnvironment.md index b956e8b7dd6d13..538f13e725e47e 100644 --- a/docs/JavaScriptEnvironment.md +++ b/docs/JavaScriptEnvironment.md @@ -4,8 +4,8 @@ title: JavaScript Environment layout: docs category: Guides permalink: docs/javascript-environment.html -next: navigation -previous: testing +next: direct-manipulation +previous: timers --- ## JavaScript Runtime diff --git a/docs/LinkingLibraries.md b/docs/LinkingLibraries.md index e4dfeeed71387c..f3c360a58c0e87 100644 --- a/docs/LinkingLibraries.md +++ b/docs/LinkingLibraries.md @@ -4,6 +4,7 @@ title: Linking Libraries layout: docs category: Guides (iOS) permalink: docs/linking-libraries-ios.html +banner: ejected next: running-on-simulator-ios previous: native-components-ios --- diff --git a/docs/MoreResources.md b/docs/MoreResources.md index 1b4286ed17a803..cc32bb1b3bf664 100644 --- a/docs/MoreResources.md +++ b/docs/MoreResources.md @@ -4,8 +4,8 @@ title: More Resources layout: docs category: The Basics permalink: docs/more-resources.html -next: integration-with-existing-apps -previous: using-navigators +next: handling-touches +previous: networking --- If you just read through this website, you should be able to build a pretty cool React Native app. But React Native isn't just a product made by one company - it's a community of thousands of developers. So if you're interested in React Native, here's some related stuff you might want to check out. @@ -32,7 +32,7 @@ The folks who built the app for Facebook's F8 conference in 2016 also [open-sour [CodePush](https://microsoft.github.io/code-push/) is a service from Microsoft that makes it easy to deploy live updates to your React Native app. If you don't like going through the app store process to deploy little tweaks, and you also don't like setting up your own backend, give CodePush a try. -[Exponent](http://docs.getexponent.com/versions/v6.0.0/index.html) is a development environment plus application that focuses on letting you build React Native apps in the Exponent development environment, without ever touching Xcode or Android Studio. If you wish React Native was even more JavaScripty and webby, check out Exponent. +[Expo](https://docs.expo.io) is a development environment plus application that focuses on letting you build React Native apps in the Expo development environment, without ever touching Xcode or Android Studio. If you wish React Native was even more JavaScripty and webby, check out Expo. [Deco](https://www.decosoftware.com/) is an all-in-one development environment specifically designed for React Native. It can automatically set up a new project, search for open source components, and insert them. You can also tweak your app graphically in real time. Check it out if you use macOS. diff --git a/docs/NativeComponentsAndroid.md b/docs/NativeComponentsAndroid.md index 7311713e0cbd57..c32622d44ffe19 100644 --- a/docs/NativeComponentsAndroid.md +++ b/docs/NativeComponentsAndroid.md @@ -4,6 +4,7 @@ title: Native UI Components layout: docs category: Guides (Android) permalink: docs/native-components-android.html +banner: ejected next: headless-js-android previous: native-modules-android --- diff --git a/docs/NativeComponentsIOS.md b/docs/NativeComponentsIOS.md index dbe7e6467545e5..9050c8be06d71c 100644 --- a/docs/NativeComponentsIOS.md +++ b/docs/NativeComponentsIOS.md @@ -4,6 +4,7 @@ title: Native UI Components layout: docs category: Guides (iOS) permalink: docs/native-components-ios.html +banner: ejected next: linking-libraries-ios previous: native-modules-ios --- diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md index fff9814d4c9e17..de71c1d0951038 100644 --- a/docs/NativeModulesAndroid.md +++ b/docs/NativeModulesAndroid.md @@ -4,6 +4,7 @@ title: Native Modules layout: docs category: Guides (Android) permalink: docs/native-modules-android.html +banner: ejected next: native-components-android previous: communication-ios --- @@ -131,7 +132,7 @@ public class AnExampleReactPackage implements ReactPackage { return modules; } - + } ``` @@ -282,60 +283,6 @@ measureLayout(); Native modules should not have any assumptions about what thread they are being called on, as the current assignment is subject to change in the future. If a blocking call is required, the heavy work should be dispatched to an internally managed worker thread, and any callbacks distributed from there. -### Sending Events to JavaScript - -Native modules can signal events to JavaScript without being invoked directly. The easiest way to do this is to use the `RCTDeviceEventEmitter` which can be obtained from the `ReactContext` as in the code snippet below. - -```java -... -private void sendEvent(ReactContext reactContext, - String eventName, - @Nullable WritableMap params) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); -} -... -WritableMap params = Arguments.createMap(); -... -sendEvent(reactContext, "keyboardWillShow", params); -``` - -JavaScript modules can then register to receive events by `addListenerOn` using the `Subscribable` mixin - -```js -import { DeviceEventEmitter } from 'react-native'; -... - -var ScrollResponderMixin = { - mixins: [Subscribable.Mixin], - - - componentWillMount: function() { - ... - this.addListenerOn(DeviceEventEmitter, - 'keyboardWillShow', - this.scrollResponderKeyboardWillShow); - ... - }, - scrollResponderKeyboardWillShow:function(e: Event) { - this.keyboardWillOpenTo = e; - this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e); - }, -``` - -You can also directly use the `DeviceEventEmitter` module to listen for events. - -```js -... -componentWillMount: function() { - DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) { - // handle event. - }); -} -... -``` - ### Getting activity result from `startActivityForResult` You'll need to listen to `onActivityResult` if you want to get results from an activity you started with `startActivityForResult`. To do this, you must extend `BaseActivityEventListener` or implement `ActivityEventListener`. The former is preferred as it is more resilient to API changes. Then, you need to register the listener in the module's constructor, @@ -369,9 +316,9 @@ public class ImagePickerModule extends ReactContextBaseJavaModule { private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND"; private Promise mPickerPromise; - + private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() { - + @Override public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) { if (requestCode == IMAGE_PICKER_REQUEST) { diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md index 8f6db49291cc6a..2f87678a400193 100644 --- a/docs/NativeModulesIOS.md +++ b/docs/NativeModulesIOS.md @@ -4,8 +4,9 @@ title: Native Modules layout: docs category: Guides (iOS) permalink: docs/native-modules-ios.html +banner: ejected next: native-components-ios -previous: gesture-responder-system +previous: upgrading --- Sometimes an app needs access to platform API, and React Native doesn't have a corresponding module yet. Maybe you want to reuse some existing Objective-C, Swift or C++ code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions. @@ -402,13 +403,13 @@ You will receive a warning if you expend resources unnecessarily by emitting an } // Will be called when this module's first listener is added. --(void)startObserving { +-(void)startObserving { hasListeners = YES; // Set up any upstream listeners or background tasks as necessary } // Will be called when this module's last listener is removed, or on dealloc. --(void)stopObserving { +-(void)stopObserving { hasListeners = NO; // Remove upstream listeners, stop unnecessary background tasks } diff --git a/docs/Navigation.md b/docs/Navigation.md index 4f1326a4a96822..4be9f26f676789 100644 --- a/docs/Navigation.md +++ b/docs/Navigation.md @@ -4,19 +4,71 @@ title: Navigation layout: docs category: Guides permalink: docs/navigation.html -next: performance -previous: javascript-environment +next: images +previous: animations --- -This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use `Navigator`. If you are only targeting iOS and would like to stick to the native look and feel, check out `NavigatorIOS`. If you are looking for greater control over your navigation stack, you can't go wrong with `NavigationExperimental`. +This guide covers the various navigation components available in React Native. If you are just getting started with navigation, you will probably want to use React Navigation. + +If you are only targeting iOS and would like to stick to the native look and feel, check out `NavigatorIOS`. The `Navigator` component is older but has been thoroughly tested in production. + +## React Navigation + +The community solution to navigation is a standalone library that allows developers to set up the screens of an app with just a few lines of code. + +The first step is to install in your app: + +``` +npm install --save react-navigation +``` + +Then you can quickly create an app with a home screen and a profile screen: + +``` +import { + StackNavigator, +} from 'react-navigation'; + +const App = StackNavigator({ + Main: {screen: MainScreen}, + Profile: {screen: ProfileScreen}, +}); +``` + +Each screen component can set navigation options such as the header title. It can use action creators on the `navigation` prop to link to other screens: + +``` +class MainScreen extends React.Component { + static navigationOptions = { + title: 'Welcome', + }; + render() { + const { navigate } = this.props.navigation; + return ( +
- +