diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 00000000000000..9f6c686b2fc18a --- /dev/null +++ b/.buckconfig @@ -0,0 +1,9 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 + +[alias] + movies = //Examples/Movies/android/app:app diff --git a/.buckjavaargs b/.buckjavaargs new file mode 100644 index 00000000000000..11452478196f70 --- /dev/null +++ b/.buckjavaargs @@ -0,0 +1 @@ +-Xmx512m -XX:+HeapDumpOnOutOfMemoryError diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000000..3b281f2f7d5a26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.eslintrc b/.eslintrc index 5a7692b800d282..d707517a0d6271 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,6 +19,7 @@ "__DEV__": true, "__dirname": false, "__fbBatchedBridgeConfig": false, + "alert": false, "cancelAnimationFrame": false, "clearImmediate": true, "clearInterval": false, @@ -26,8 +27,11 @@ "console": false, "document": false, "escape": false, + "Event": false, + "EventTarget": false, "exports": false, "fetch": false, + "FormData": false, "global": false, "jest": false, "Map": true, @@ -43,7 +47,29 @@ "setTimeout": false, "window": false, "XMLHttpRequest": false, - "pit": false + "pit": false, + + // Flow global types. + "ReactComponent": false, + "ReactClass": false, + "ReactElement": false, + "ReactPropsCheckType": false, + "ReactPropsChainableTypeChecker": false, + "ReactPropTypes": false, + "SyntheticEvent": false, + "$Either": false, + "$All": false, + "$Tuple": false, + "$Supertype": false, + "$Subtype": false, + "$Shape": false, + "$Diff": false, + "$Keys": false, + "$Enum": false, + "$Exports": false, + "$FlowIssue": false, + "$FlowFixMe": false, + "$FixMe": false }, "rules": { @@ -81,13 +107,12 @@ "curly": 1, // specify curly brace conventions for all control statements "default-case": 0, // require default case in switch statements (off by default) "dot-notation": 1, // encourages use of dot notation whenever possible - "eqeqeq": 1, // require the use of === and !== + "eqeqeq": [1, "allow-null"], // require the use of === and !== "guard-for-in": 0, // make sure for-in loops have an if statement (off by default) "no-alert": 1, // disallow the use of alert, confirm, and prompt "no-caller": 1, // disallow use of arguments.caller or arguments.callee "no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default) "no-else-return": 0, // disallow else after a return in an if (off by default) - "no-empty-label": 1, // disallow use of labels for anything other then loops and switches "no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default) "no-eval": 1, // disallow use of eval() "no-extend-native": 1, // disallow adding to native types @@ -156,6 +181,8 @@ // These rules are purely matters of style and are quite subjective. "key-spacing": 0, + "keyword-spacing": 1, // enforce spacing before and after keywords + "jsx-quotes": [1, "prefer-double"], "comma-spacing": 0, "no-multi-spaces": 0, "brace-style": 0, // enforce one true brace style (off by default) @@ -179,11 +206,9 @@ "quote-props": 0, // require quotes around object literal property names (off by default) "semi": 1, // require or disallow use of semicolons instead of ASI "sort-vars": 0, // sort variables within the same declaration block (off by default) - "space-after-keywords": 1, // require a space after certain keywords (off by default) "space-in-brackets": 0, // require or disallow spaces inside brackets (off by default) "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) "space-infix-ops": 1, // require spaces around operators - "space-return-throw-case": 1, // require a space after return, throw, and case "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default) "max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default) "one-var": 0, // allow just one var statement per function (off by default) @@ -201,7 +226,6 @@ "react/display-name": 0, "react/jsx-boolean-value": 0, - "react/jsx-quotes": [1, "double", "avoid-escape"], "react/jsx-no-undef": 1, "react/jsx-sort-props": 0, "react/jsx-uses-react": 0, diff --git a/.flowconfig b/.flowconfig index 7f4891b33340d4..3d5e14681dcdd7 100644 --- a/.flowconfig +++ b/.flowconfig @@ -14,17 +14,21 @@ # Ignore react and fbjs where there are overlaps, but don't ignore # anything that react-native relies on -.*/node_modules/fbjs-haste/.*/__tests__/.* -.*/node_modules/fbjs-haste/__forks__/Map.js -.*/node_modules/fbjs-haste/__forks__/Promise.js -.*/node_modules/fbjs-haste/__forks__/fetch.js -.*/node_modules/fbjs-haste/core/ExecutionEnvironment.js -.*/node_modules/fbjs-haste/core/isEmpty.js -.*/node_modules/fbjs-haste/crypto/crc32.js -.*/node_modules/fbjs-haste/stubs/ErrorUtils.js -.*/node_modules/react-haste/React.js -.*/node_modules/react-haste/renderers/dom/ReactDOM.js -.*/node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js +.*/node_modules/fbjs/lib/Map.js +.*/node_modules/fbjs/lib/fetch.js +.*/node_modules/fbjs/lib/ExecutionEnvironment.js +.*/node_modules/fbjs/lib/ErrorUtils.js + +# Flow has a built-in definition for the 'react' module which we prefer to use +# over the currently-untyped source +.*/node_modules/react/react.js +.*/node_modules/react/lib/React.js +.*/node_modules/react/lib/ReactDOM.js + +.*/__mocks__/.* +.*/__tests__/.* + +.*/commoner/test/source/widget/share.js # Ignore commoner tests .*/node_modules/commoner/test/.* @@ -46,18 +50,21 @@ Libraries/react-native/react-native-interface.js [options] module.system=haste +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + munge_underscores=true module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.18.1 +0.21.0 diff --git a/.gitignore b/.gitignore index c4b7a9b476d943..cd545b912fdc72 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,16 @@ DerivedData *.xcuserstate project.xcworkspace -# Xcode, Gradle -build/ +# Gradle +/build/ +/Examples/**/android/app/build/ +/ReactAndroid/build/ + +# Buck +.buckd +buck-out +/ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/ +/ReactAndroid/src/main/jni/prebuilt/lib/x86/ # Android .idea diff --git a/.travis.yml b/.travis.yml index ea84c8bc8ab5c1..32ad533ff3a557 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,21 @@ language: objective-c -osx_image: xcode7 +osx_image: xcode7.2 cache: - directories: - - node_modules - - .nvm + directories: + - node_modules + - .nvm install: - brew reinstall nvm - mkdir -p .nvm - export NVM_DIR="$PWD/.nvm" - source $(brew --prefix nvm)/nvm.sh - - nvm install 4 + - nvm install 5 - rm -Rf "${TMPDIR}/jest_preprocess_cache" - npm config set spin=false - - npm install -g flow-bin@`node -p "require('fs').readFileSync('.flowconfig', 'utf8').split('[version]')[1].trim()"` + - npm config set progress=false - npm install script: @@ -23,41 +23,20 @@ script: if [ "$TEST_TYPE" = objc ] then - ./scripts/objc-test.sh + travis_retry ./scripts/objc-test.sh elif [ "$TEST_TYPE" = js ] then - flow check && npm test -- '\/Libraries\/' + npm install github@0.2.4 + cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; npm run flow --silent -- --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" node bots/code-analysis-bot.js + npm run flow && npm test - elif [ "$TEST_TYPE" = packager ] + elif [ "$TEST_TYPE" = e2e-objc ] then - npm test -- '\/packager\/' + travis_retry ./scripts/e2e-test.sh --ios - elif [ "$TEST_TYPE" = cli ] - then - - npm test -- '\/(local|private|react-native)-cli\/' - - elif [ "$TEST_TYPE" = build_website ] - then - - cd website - $(which npm) install - ./setup.sh - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_BRANCH" = master ]; then - # Automatically publish the website - echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" >~/.netrc - ./publish.sh - else - # Make sure the website builds without error - node server/generate.js - fi - - elif [ "$TEST_TYPE" = e2e ] - then - ./scripts/e2e-test.sh else echo "Unknown test type: $TEST_TYPE" exit 1 @@ -67,19 +46,16 @@ env: matrix: - TEST_TYPE=objc - TEST_TYPE=js - - TEST_TYPE=packager - - TEST_TYPE=cli - - TEST_TYPE=build_website - - TEST_TYPE=e2e - global: - # $GITHUB_TOKEN - - secure: "HlmG8M2DmBUSBh6KH1yVIe/8gR4iibg4WfcHq1x/xYQxGbvleq7NOo04V6eFHnl9cvZCu+PKH0841WLnGR7c4BBf47GVu/o16nXzggPumHKy++lDzxFPlJ1faMDfjg/5vjbAxRUe7D3y98hQSeGHH4tedc8LvTaFLVu7iiGqvjU=" - # $APPETIZE_TOKEN - - secure: "egsvVSpszTzrNd6bN62DsVAzMiSZI/OHgdizfPryqvqWBf655ztE6XFQSEFNpuIAzSKDDF25ioT8iPfVsbC1iK6HDWHfmqYxML0L+OoU0gi+hV2oKUBFZDZ1fwSnFoWuBdNdMDpLlUxvJp6N1WyfNOB2dxuZUt8eTt48Hi3+Hpc=" - # $S3_TOKEN - - secure: "lY8JZPA0A7zT7L5KF9BBg34XYWIeR/RJiEvE7l7oVr88KnEPtyd//79eHhhVKnUnav7zsk5QJwkcX0MxKTp/dp4G0Am+zOX+sfA8kQrJ+2/+FzFW7AEsW/kHByfaIEIly9DQvUFt4I4oMm8nQZysJLahDgNWglyI3RTuJp//hcY=" + - TEST_TYPE=e2e-objc branches: only: - master - /^.*-stable$/ + +notifications: + email: + recipients: + - bestander@gmail.com + on_failure: change + on_success: change diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aebef40cc862be..b5d0c744d1173c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,12 +17,30 @@ The core team will be monitoring for pull requests. When we get one, we'll run s *Before* submitting a pull request, please make sure the following is done… 1. Fork the repo and create your branch from `master`. -2. If you've added code that should be tested, add tests! +2. **Describe your test plan in your commit.** If you've added code that should be tested, add tests! 3. If you've changed APIs, update the documentation. -4. Ensure tests pass on Travis. -5. Make sure your code lints (`node linter.js `). -6. Squash your commits (`git rebase -i`). -7. If you haven't already, complete the CLA. +4. Add the copyright notice to the top of any new files you've added. +5. Ensure tests pass on Travis and Circle CI. +6. Make sure your code lints (`node linter.js `). +7. Squash your commits (`git rebase -i`). +8. If you haven't already, sign the [CLA](https://code.facebook.com/cla). + +#### Copyright Notice for files + +Copy and paste this to the top of your new file(s): + +```JS +/** + * 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. + */ +``` + +If you've added a new module, add a `@providesModule ` at the end of the comment. This will allow the haste package manager to find it. ### Contributor License Agreement (CLA) @@ -67,6 +85,14 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe * Do not use the optional parameters of `setTimeout` and `setInterval` * 80 character line length +#### JSX + +* Prefer `'` over `"` for string literal props +* When wrapping opening tags over multiple lines, place one prop per line +* `{}` of props should hug their values (no spaces) +* Place the closing `>` of opening tags on the same line as the last prop +* Place the closing `/>` of self-closing tags on their own line and left-align them with the opening `<` + #### Objective-C * Space after `@property` declarations diff --git a/Examples/.eslintrc b/Examples/.eslintrc new file mode 100644 index 00000000000000..8dc9d22c11ecf4 --- /dev/null +++ b/Examples/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "no-alert": 0 + } +} diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index 90111d38b87147..a6c97a3e3edaa5 100644 --- a/Examples/2048/Game2048.js +++ b/Examples/2048/Game2048.js @@ -53,6 +53,8 @@ class Board extends React.Component { } class Tile extends React.Component { + state: any; + static _getPosition(index): number { return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN); } @@ -147,6 +149,7 @@ class GameEndOverlay extends React.Component { class Game2048 extends React.Component { startX: number; startY: number; + state: any; constructor(props: {}) { super(props); @@ -237,7 +240,7 @@ var styles = StyleSheet.create({ marginBottom: 20, }, tryAgain: { - backgroundColor: '#887766', + backgroundColor: '#887761', padding: 20, borderRadius: 5, }, @@ -279,7 +282,7 @@ var styles = StyleSheet.create({ backgroundColor: '#eeeecc', }, tile8: { - backgroundColor: '#ffbb88', + backgroundColor: '#ffbb87', }, tile16: { backgroundColor: '#ff9966', diff --git a/Examples/2048/GameBoard.js b/Examples/2048/GameBoard.js index 1d7f34085b1074..6ed92e6b578ddf 100644 --- a/Examples/2048/GameBoard.js +++ b/Examples/2048/GameBoard.js @@ -17,7 +17,7 @@ 'use strict'; // NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js -// with no modificiation except to format it for CommonJS and fix lint/flow errors +// with no modification except to format it for CommonJS and fix lint/flow errors var rotateLeft = function (matrix) { var rows = matrix.length; diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js index 62062cb2af224c..c90931c08768ee 100644 --- a/Examples/Movies/MovieCell.js +++ b/Examples/Movies/MovieCell.js @@ -18,7 +18,6 @@ var React = require('react-native'); var { Image, - PixelRatio, Platform, StyleSheet, Text, @@ -99,8 +98,7 @@ var styles = StyleSheet.create({ }, cellBorder: { backgroundColor: 'rgba(0, 0, 0, 0.1)', - // Trick to get the thinest line the device can display - height: 1 / PixelRatio.get(), + height: StyleSheet.hairlineWidth, marginLeft: 4, }, }); diff --git a/Examples/Movies/MovieScreen.js b/Examples/Movies/MovieScreen.js index 09d6544dff5922..ff228a8a3e7c57 100644 --- a/Examples/Movies/MovieScreen.js +++ b/Examples/Movies/MovieScreen.js @@ -18,7 +18,6 @@ var React = require('react-native'); var { Image, - PixelRatio, ScrollView, StyleSheet, Text, @@ -152,7 +151,7 @@ var styles = StyleSheet.create({ }, separator: { backgroundColor: 'rgba(0, 0, 0, 0.1)', - height: 1 / PixelRatio.get(), + height: StyleSheet.hairlineWidth, marginVertical: 10, }, castTitle: { diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 1bc6bb906ceaad..2e681ed34f50b5 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -27,7 +27,7 @@ var { } = React; var TimerMixin = require('react-timer-mixin'); -var invariant = require('invariant'); +var invariant = require('fbjs/lib/invariant'); var dismissKeyboard = require('dismissKeyboard'); var MovieCell = require('./MovieCell'); @@ -316,7 +316,7 @@ var SearchScreen = React.createClass({ onSearchChange={this.onSearchChange} isLoading={this.state.isLoading} onFocus={() => - this.refs.listview && this.refs.listview.getScrollResponder().scrollTo(0, 0)} + this.refs.listview && this.refs.listview.getScrollResponder().scrollTo({ x: 0, y: 0 })} /> {content} diff --git a/Examples/Movies/android/app/BUCK b/Examples/Movies/android/app/BUCK new file mode 100644 index 00000000000000..25c73fd5441544 --- /dev/null +++ b/Examples/Movies/android/app/BUCK @@ -0,0 +1,43 @@ +include_defs('//ReactAndroid/DEFS') + +android_binary( + name = 'app', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//keystores:debug', + deps = [ + ':movies-lib', + ], +) + +android_library( + name = 'movies-lib', + srcs = glob(['src/main/java/**/*.java']), + deps = [ + react_native_target('java/com/facebook/csslayout:csslayout'), + react_native_target('java/com/facebook/react:react'), + react_native_target('java/com/facebook/react/devsupport:devsupport'), + react_native_target('java/com/facebook/react/modules/core:core'), + react_native_target('java/com/facebook/react/shell:shell'), + react_native_target('java/com/facebook/react/touch:touch'), + react_native_target('java/com/facebook/react/uimanager:uimanager'), + react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), + react_native_target('java/com/facebook/react/views/image:image'), + react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'), + react_native_target('java/com/facebook/react/views/scroll:scroll'), + react_native_target('java/com/facebook/react/views/text:text'), + react_native_target('java/com/facebook/react/views/view:view'), + # .so files are prebuilt by Gradle with `./gradlew :ReactAndroid:packageReactNdkLibsForBuck` + react_native_target('jni/prebuilt:reactnative-libs'), + react_native_target('jni/prebuilt:android-jsc'), + react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + ':res', + ], +) + + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.facebook.react.movies', +) diff --git a/Examples/Movies/android/app/src/main/AndroidManifest.xml b/Examples/Movies/android/app/src/main/AndroidManifest.xml index 2123d078fdf14a..8aaec60819da5b 100644 --- a/Examples/Movies/android/app/src/main/AndroidManifest.xml +++ b/Examples/Movies/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ package="com.facebook.react.movies"> + + getPackages() { + return Arrays.asList( + new MainReactPackage() + ); } } diff --git a/Examples/TicTacToe/TicTacToeApp.js b/Examples/TicTacToe/TicTacToeApp.js index 15652ee4de6482..3b562194a04427 100755 --- a/Examples/TicTacToe/TicTacToeApp.js +++ b/Examples/TicTacToe/TicTacToeApp.js @@ -89,7 +89,6 @@ class Board { } } } - return this.winner() === null; } } @@ -306,7 +305,7 @@ var styles = StyleSheet.create({ textAlign: 'center', }, newGame: { - backgroundColor: '#887766', + backgroundColor: '#887765', padding: 20, borderRadius: 5, }, diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index 31aedca627c68c..b749400c037ab2 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -20,6 +20,7 @@ var { ActionSheetIOS, StyleSheet, Text, + UIManager, View, } = React; @@ -27,7 +28,7 @@ var BUTTONS = [ 'Option 0', 'Option 1', 'Option 2', - 'Destruct', + 'Delete', 'Cancel', ]; var DESTRUCTIVE_INDEX = 3; @@ -65,6 +66,39 @@ var ActionSheetExample = React.createClass({ } }); +var ActionSheetTintExample = React.createClass({ + getInitialState() { + return { + clicked: 'none', + }; + }, + + render() { + return ( + + + Click to show the ActionSheet + + + Clicked button: {this.state.clicked} + + + ); + }, + + showActionSheet() { + ActionSheetIOS.showActionSheetWithOptions({ + options: BUTTONS, + cancelButtonIndex: CANCEL_INDEX, + destructiveButtonIndex: DESTRUCTIVE_INDEX, + tintColor: 'green', + }, + (buttonIndex) => { + this.setState({ clicked: BUTTONS[buttonIndex] }); + }); + } +}); + var ShareActionSheetExample = React.createClass({ getInitialState() { return { @@ -87,11 +121,14 @@ var ShareActionSheetExample = React.createClass({ showShareActionSheet() { ActionSheetIOS.showShareActionSheetWithOptions({ - url: 'https://code.facebook.com', - }, - (error) => { - console.error(error); + url: this.props.url, + message: 'message to go with the shared url', + subject: 'a subject to go in the email heading', + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ] }, + (error) => alert(error), (success, method) => { var text; if (success) { @@ -104,6 +141,50 @@ var ShareActionSheetExample = React.createClass({ } }); +var ShareScreenshotExample = React.createClass({ + getInitialState() { + return { + text: '' + }; + }, + + render() { + return ( + + + Click to show the Share ActionSheet + + + {this.state.text} + + + ); + }, + + showShareActionSheet() { + // Take the snapshot (returns a temp file uri) + UIManager.takeSnapshot('window').then((uri) => { + // Share image data + ActionSheetIOS.showShareActionSheetWithOptions({ + url: uri, + excludedActivityTypes: [ + 'com.apple.UIKit.activity.PostToTwitter' + ] + }, + (error) => alert(error), + (success, method) => { + var text; + if (success) { + text = `Shared via ${method}`; + } else { + text = 'You didn\'t share'; + } + this.setState({text}); + }); + }).catch((error) => alert(error)); + } +}); + var style = StyleSheet.create({ button: { marginBottom: 10, @@ -118,8 +199,26 @@ exports.examples = [ title: 'Show Action Sheet', render(): ReactElement { return ; } }, + { + title: 'Show Action Sheet with tinted buttons', + render(): ReactElement { return ; } + }, { title: 'Show Share Action Sheet', - render(): ReactElement { return ; } + render(): ReactElement { + return ; + } + }, + { + title: 'Share Local Image', + render(): ReactElement { + return ; + } + }, + { + title: 'Share Screenshot', + render(): ReactElement { + return ; + } } ]; diff --git a/Examples/UIExplorer/AlertExample.js b/Examples/UIExplorer/AlertExample.js new file mode 100644 index 00000000000000..37f47dd9b01ddd --- /dev/null +++ b/Examples/UIExplorer/AlertExample.js @@ -0,0 +1,136 @@ +/** + * 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. + * + */ + +'use strict'; + +var React = require('react-native'); +var { + Alert, + StyleSheet, + Text, + TouchableHighlight, + View, +} = React; + +var UIExplorerBlock = require('./UIExplorerBlock'); + +// corporate ipsum > lorem ipsum +var alertMessage = 'Credibly reintermediate next-generation potentialities after goal-oriented ' + + 'catalysts for change. Dynamically revolutionize.'; + +/** + * Simple alert examples. + */ +var SimpleAlertExampleBlock = React.createClass({ + + render: function() { + return ( + + Alert.alert( + 'Alert Title', + alertMessage, + )}> + + Alert with message and default button + + + Alert.alert( + 'Alert Title', + alertMessage, + [ + {text: 'OK', onPress: () => console.log('OK Pressed!')}, + ] + )}> + + Alert with one button + + + Alert.alert( + 'Alert Title', + alertMessage, + [ + {text: 'Cancel', onPress: () => console.log('Cancel Pressed!')}, + {text: 'OK', onPress: () => console.log('OK Pressed!')}, + ] + )}> + + Alert with two buttons + + + Alert.alert( + 'Alert Title', + null, + [ + {text: 'Foo', onPress: () => console.log('Foo Pressed!')}, + {text: 'Bar', onPress: () => console.log('Bar Pressed!')}, + {text: 'Baz', onPress: () => console.log('Baz Pressed!')}, + ] + )}> + + Alert with three buttons + + + Alert.alert( + 'Foo Title', + alertMessage, + '..............'.split('').map((dot, index) => ({ + text: 'Button ' + index, + onPress: () => console.log('Pressed ' + index) + })) + )}> + + Alert with too many buttons + + + + ); + }, +}); + +var AlertExample = React.createClass({ + statics: { + title: 'Alert', + description: 'Alerts display a concise and informative message ' + + 'and prompt the user to make a decision.', + }, + render: function() { + return ( + + + + ); + } +}); + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +}); + +module.exports = { + AlertExample, + SimpleAlertExampleBlock, +}; diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js index e09c883c137eba..d7f0bcf0d464fa 100644 --- a/Examples/UIExplorer/AlertIOSExample.js +++ b/Examples/UIExplorer/AlertIOSExample.js @@ -24,103 +24,87 @@ var { AlertIOS, } = React; +var { SimpleAlertExampleBlock } = require('./AlertExample'); + exports.framework = 'React'; exports.title = 'AlertIOS'; exports.description = 'iOS alerts and action sheets'; exports.examples = [{ title: 'Alerts', + render() { + return ; + } +}, +{ + title: 'Prompt Options', + render(): ReactElement { + return ; + } +}, +{ + title: 'Prompt Types', render() { return ( - AlertIOS.alert( - 'Foo Title', - 'My Alert Msg' - )}> - - Alert with message and default button - - - AlertIOS.alert( - null, - null, - [ - {text: 'Button', onPress: () => console.log('Button Pressed!')}, - ] - )}> - - Alert with only one button - - - AlertIOS.alert( - 'Foo Title', - 'My Alert Msg', - [ - {text: 'Foo', onPress: () => console.log('Foo Pressed!')}, - {text: 'Bar', onPress: () => console.log('Bar Pressed!')}, - ] - )}> + AlertIOS.prompt('Plain Text Entry')}> + - Alert with two buttons + + plain-text + + - AlertIOS.alert( - 'Foo Title', - null, - [ - {text: 'Foo', onPress: () => console.log('Foo Pressed!')}, - {text: 'Bar', onPress: () => console.log('Bar Pressed!')}, - {text: 'Baz', onPress: () => console.log('Baz Pressed!')}, - ] - )}> + AlertIOS.prompt('Secure Text', null, null, 'secure-text')}> + - Alert with 3 buttons + + secure-text + + - AlertIOS.alert( - 'Foo Title', - 'My Alert Msg', - '..............'.split('').map((dot, index) => ({ - text: 'Button ' + index, - onPress: () => console.log('Pressed ' + index) - })) - )}> + AlertIOS.prompt('Login & Password', null, null, 'login-password')}> + - Alert with too many buttons + + login-password + + ); } -}, -{ - title: 'Prompt', - render(): React.Component { - return - } }]; -class PromptExample extends React.Component { +class PromptOptions extends React.Component { + state: any; + customButtons: Array; + constructor(props) { super(props); - this.promptResponse = this.promptResponse.bind(this); - this.state = { - promptValue: undefined, - }; + // $FlowFixMe this seems to be a Flow bug, `saveResponse` is defined below + this.saveResponse = this.saveResponse.bind(this); - this.title = 'Type a value'; - this.defaultValue = 'Default value'; - this.buttons = [{ - text: 'Custom cancel', - }, { + this.customButtons = [{ text: 'Custom OK', - onPress: this.promptResponse + onPress: this.saveResponse + }, { + text: 'Custom Cancel', + style: 'cancel', }]; + + this.state = { + promptValue: undefined, + }; } render() { @@ -132,7 +116,7 @@ class PromptExample extends React.Component { + onPress={() => AlertIOS.prompt('Type a value', null, this.saveResponse)}> @@ -143,7 +127,7 @@ class PromptExample extends React.Component { + onPress={() => AlertIOS.prompt('Type a value', null, this.customButtons)}> @@ -154,22 +138,22 @@ class PromptExample extends React.Component { + onPress={() => AlertIOS.prompt('Type a value', null, this.saveResponse, undefined, 'Default value')}> - prompt with title, default value & callback + prompt with title, callback & default value + onPress={() => AlertIOS.prompt('Type a value', null, this.customButtons, 'login-password', 'admin@site.com')}> - prompt with title, default value & custom buttons + prompt with title, custom buttons, login/password & default value @@ -177,13 +161,8 @@ class PromptExample extends React.Component { ); } - prompt() { - // Flow's apply support is broken: #7035621 - ((AlertIOS.prompt: any).apply: any)(AlertIOS, arguments); - } - - promptResponse(promptValue) { - this.setState({ promptValue }); + saveResponse(promptValue) { + this.setState({ promptValue: JSON.stringify(promptValue) }); } } diff --git a/Examples/UIExplorer/AnimatedExample.js b/Examples/UIExplorer/AnimatedExample.js index e417cacbefcc93..1068ac00026809 100644 --- a/Examples/UIExplorer/AnimatedExample.js +++ b/Examples/UIExplorer/AnimatedExample.js @@ -39,6 +39,8 @@ exports.examples = [ 'mounts.', render: function() { class FadeInView extends React.Component { + state: any; + constructor(props) { super(props); this.state = { @@ -66,6 +68,8 @@ exports.examples = [ } } class FadeInExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = { @@ -195,6 +199,7 @@ exports.examples = [ {['Composite', 'Easing', 'Animations!'].map( (text, ii) => ( diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js index d713b5f66d903e..c87bc29823bce4 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js @@ -32,6 +32,10 @@ var CIRCLE_MARGIN = 18; var NUM_CIRCLES = 30; class Circle extends React.Component { + state: any; + props: any; + longTimer: number; + _onLongPress: () => void; _toggleIsActive: () => void; constructor(props: Object): void { @@ -156,6 +160,13 @@ class Circle extends React.Component { } class AnExApp extends React.Component { + state: any; + props: any; + + static title = 'Animated - Gratuitous App'; + static description = 'Bunch of Animations - tap a circle to ' + + 'open a view with more animations, or longPress and drag to reorder circles.'; + _onMove: (position: Point) => void; constructor(props: any): void { super(props); @@ -266,10 +277,6 @@ function moveToClosest({activeKey, keys, restLayouts}, position) { } } -AnExApp.title = 'Animated - Gratuitous App'; -AnExApp.description = 'Bunch of Animations - tap a circle to ' + - 'open a view with more animations, or longPress and drag to reorder circles.'; - var styles = StyleSheet.create({ container: { flex: 1, diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js index bebad819d5c579..39f15a8ec8ea9b 100644 --- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js +++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js @@ -36,6 +36,8 @@ var BOBBLE_SPOTS = [...Array(NUM_BOBBLES)].map((_, i) => { // static positions }); class AnExBobble extends React.Component { + state: any; + constructor(props: Object) { super(props); this.state = {}; @@ -95,6 +97,7 @@ class AnExBobble extends React.Component { return ( + {this.state.appState} + + ); + } + return ( + + {JSON.stringify(this.state.previousAppStates)} + + ); + } +}); + +exports.title = 'AppState'; +exports.description = 'app background status'; +exports.examples = [ + { + title: 'AppState.currentState', + description: 'Can be null on app initialization', + render() { return {AppState.currentState}; } + }, + { + title: 'Subscribed AppState:', + description: 'This changes according to the current state, so you can only ever see it rendered as "active"', + render(): ReactElement { return ; } + }, + { + title: 'Previous states:', + render(): ReactElement { return ; } + }, +]; diff --git a/Examples/UIExplorer/AsyncStorageExample.js b/Examples/UIExplorer/AsyncStorageExample.js index a091c0caaede74..a3ca7643747416 100644 --- a/Examples/UIExplorer/AsyncStorageExample.js +++ b/Examples/UIExplorer/AsyncStorageExample.js @@ -80,7 +80,7 @@ var BasicStorageExample = React.createClass({ {' '} Messages: - {this.state.messages.map((m) => {m})} + {this.state.messages.map((m) => {m})} ); }, diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js index c6c22f5531f038..2dedfdd8e6ae1d 100644 --- a/Examples/UIExplorer/BorderExample.js +++ b/Examples/UIExplorer/BorderExample.js @@ -98,6 +98,34 @@ var styles = StyleSheet.create({ marginRight: 10, backgroundColor: 'lightgrey', }, + border9: { + borderWidth: 10, + borderTopLeftRadius: 10, + borderBottomRightRadius: 20, + borderColor: 'black', + }, + border10: { + borderWidth: 10, + backgroundColor: 'white', + borderTopLeftRadius: 10, + borderBottomRightRadius: 20, + borderColor: 'black', + elevation: 10, + }, + border11: { + width: 0, + height: 0, + borderStyle: 'solid', + overflow: 'hidden', + borderTopWidth: 50, + borderRightWidth: 0, + borderBottomWidth: 50, + borderLeftWidth: 100, + borderTopColor: 'transparent', + borderRightColor: 'transparent', + borderBottomColor: 'transparent', + borderLeftColor: 'red', + }, }); exports.title = 'Border'; @@ -180,4 +208,26 @@ exports.examples = [ ); } }, + { + title: 'Corner Radii', + description: 'borderTopLeftRadius & borderBottomRightRadius', + render() { + return ; + } + }, + { + title: 'Corner Radii / Elevation', + description: 'borderTopLeftRadius & borderBottomRightRadius & elevation', + platform: 'android', + render() { + return ; + } + }, + { + title: 'CSS Trick - Triangle', + description: 'create a triangle by manipulating border colors and widths', + render() { + return ; + } + }, ]; diff --git a/Examples/UIExplorer/BoxShadowExample.js b/Examples/UIExplorer/BoxShadowExample.js new file mode 100644 index 00000000000000..12568c1b67bc3e --- /dev/null +++ b/Examples/UIExplorer/BoxShadowExample.js @@ -0,0 +1,85 @@ +/** + * 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. + */ +'use strict'; + +var React = require('react-native'); +var { + Image, + StyleSheet, + View +} = React; + +var styles = StyleSheet.create({ + box: { + width: 100, + height: 100, + borderWidth: 2, + }, + shadow1: { + shadowOpacity: 0.5, + shadowRadius: 3, + shadowOffset: {width: 2, height: 2}, + }, + shadow2: { + shadowOpacity: 1.0, + shadowColor: 'red', + shadowRadius: 0, + shadowOffset: {width: 3, height: 3}, + }, +}); + +exports.title = 'Box Shadow'; +exports.description = 'Demonstrates some of the shadow styles available to Views.'; +exports.examples = [ + { + title: 'Basic shadow', + description: 'shadowOpacity: 0.5, shadowOffset: {2, 2}', + render() { + return ; + } + }, + { + title: 'Colored shadow', + description: 'shadowColor: \'red\', shadowRadius: 0', + render() { + return ; + } + }, + { + title: 'Shaped shadow', + description: 'borderRadius: 50', + render() { + return ; + } + }, + { + title: 'Image shadow', + description: 'Image shadows are derived exactly from the pixels.', + render() { + return ; + } + }, + { + title: 'Child shadow', + description: 'For views without an opaque background color, shadow will be derived from the subviews.', + render() { + return + + ; + } + }, +]; diff --git a/Examples/UIExplorer/CameraRollExample.ios.js b/Examples/UIExplorer/CameraRollExample.js similarity index 72% rename from Examples/UIExplorer/CameraRollExample.ios.js rename to Examples/UIExplorer/CameraRollExample.js index d783d9d8e243f6..d431560722f26e 100644 --- a/Examples/UIExplorer/CameraRollExample.ios.js +++ b/Examples/UIExplorer/CameraRollExample.js @@ -15,24 +15,25 @@ */ 'use strict'; -var React = require('react-native'); -var { +const React = require('react-native'); +const { CameraRoll, Image, SliderIOS, StyleSheet, - SwitchIOS, + Switch, Text, View, TouchableOpacity } = React; -var CameraRollView = require('./CameraRollView.ios'); -var AssetScaledImageExampleView = require('./AssetScaledImageExample'); +const CameraRollView = require('./CameraRollView'); -var CAMERA_ROLL_VIEW = 'camera_roll_view'; +const AssetScaledImageExampleView = require('./AssetScaledImageExample'); -var CameraRollExample = React.createClass({ +const CAMERA_ROLL_VIEW = 'camera_roll_view'; + +const CameraRollExample = React.createClass({ getInitialState() { return { @@ -45,7 +46,7 @@ var CameraRollExample = React.createClass({ render() { return ( - {(this.state.bigImages ? 'Big' : 'Small') + ' Images'} @@ -65,22 +66,24 @@ var CameraRollExample = React.createClass({ }, loadAsset(asset){ - this.props.navigator.push({ - title: 'Camera Roll Image', - component: AssetScaledImageExampleView, - backButtonTitle: 'Back', - passProps: { asset: asset }, - }); + if (this.props.navigator) { + this.props.navigator.push({ + title: 'Camera Roll Image', + component: AssetScaledImageExampleView, + backButtonTitle: 'Back', + passProps: { asset: asset }, + }); + } }, _renderImage(asset) { - var imageSize = this.state.bigImages ? 150 : 75; - var imageStyle = [styles.image, {width: imageSize, height: imageSize}]; - var location = asset.node.location.longitude ? + const imageSize = this.state.bigImages ? 150 : 75; + const imageStyle = [styles.image, {width: imageSize, height: imageSize}]; + const location = asset.node.location.longitude ? JSON.stringify(asset.node.location) : 'Unknown location'; return ( - - + + + + Tap to put "Hello World" in the clipboard + + + {this.state.content} + + + ); + } +}); + +exports.title = 'Clipboard'; +exports.description = 'Show Clipboard contents.'; +exports.examples = [ + { + title: 'Clipboard.setString() and getString()', + render() { + return ; + } + } +]; diff --git a/Examples/UIExplorer/DatePickerAndroidExample.js b/Examples/UIExplorer/DatePickerAndroidExample.js new file mode 100644 index 00000000000000..e59517d9132928 --- /dev/null +++ b/Examples/UIExplorer/DatePickerAndroidExample.js @@ -0,0 +1,117 @@ +/** + * 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. + */ +'use strict'; + +var React = require('react-native'); +var { + DatePickerAndroid, + StyleSheet, + Text, + TouchableWithoutFeedback, +} = React; + +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); + +var DatePickerAndroidExample = React.createClass({ + + statics: { + title: 'DatePickerAndroid', + description: 'Standard Android date picker dialog', + }, + + getInitialState() { + return { + presetDate: new Date(2020, 4, 5), + allDate: new Date(2020, 4, 5), + simpleText: 'pick a date', + minText: 'pick a date, no earlier than today', + maxText: 'pick a date, no later than today', + presetText: 'pick a date, preset to 2020/5/5', + allText: 'pick a date between 2020/5/1 and 2020/5/10', + }; + }, + + async showPicker(stateKey, options) { + try { + var newState = {}; + const {action, year, month, day} = await DatePickerAndroid.open(options); + if (action === DatePickerAndroid.dismissedAction) { + newState[stateKey + 'Text'] = 'dismissed'; + } else { + var date = new Date(year, month, day); + newState[stateKey + 'Text'] = date.toLocaleDateString(); + newState[stateKey + 'Date'] = date; + } + this.setState(newState); + } catch ({code, message}) { + console.warn(`Error in example '${stateKey}': `, message); + } + }, + + render() { + return ( + + + + {this.state.simpleText} + + + + + {this.state.presetText} + + + + + {this.state.minText} + + + + + {this.state.maxText} + + + + + {this.state.allText} + + + + ); + }, +}); + +var styles = StyleSheet.create({ + text: { + color: 'black', + }, +}); + +module.exports = DatePickerAndroidExample; diff --git a/Examples/UIExplorer/ExampleTypes.js b/Examples/UIExplorer/ExampleTypes.js index 4513f19e228bd6..ac9deaadfaf768 100644 --- a/Examples/UIExplorer/ExampleTypes.js +++ b/Examples/UIExplorer/ExampleTypes.js @@ -18,7 +18,6 @@ export type Example = { title: string, - /* $FlowFixMe(>=0.16.0) */ render: () => ?ReactElement, description?: string, platform?: string; diff --git a/Examples/UIExplorer/GeolocationExample.js b/Examples/UIExplorer/GeolocationExample.js index d9dd4e842b74a1..61d7d3778bda63 100644 --- a/Examples/UIExplorer/GeolocationExample.js +++ b/Examples/UIExplorer/GeolocationExample.js @@ -49,11 +49,15 @@ var GeolocationExample = React.createClass({ componentDidMount: function() { navigator.geolocation.getCurrentPosition( - (initialPosition) => this.setState({initialPosition}), + (position) => { + var initialPosition = JSON.stringify(position); + this.setState({initialPosition}); + }, (error) => alert(error.message), {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000} ); - this.watchID = navigator.geolocation.watchPosition((lastPosition) => { + this.watchID = navigator.geolocation.watchPosition((position) => { + var lastPosition = JSON.stringify(position); this.setState({lastPosition}); }); }, @@ -67,11 +71,11 @@ var GeolocationExample = React.createClass({ Initial position: - {JSON.stringify(this.state.initialPosition)} + {this.state.initialPosition} Current position: - {JSON.stringify(this.state.lastPosition)} + {this.state.lastPosition} ); diff --git a/Examples/UIExplorer/ImageCapInsetsExample.js b/Examples/UIExplorer/ImageCapInsetsExample.js index 8e17ac9285a709..f75f7da98d30eb 100644 --- a/Examples/UIExplorer/ImageCapInsetsExample.js +++ b/Examples/UIExplorer/ImageCapInsetsExample.js @@ -35,6 +35,7 @@ var ImageCapInsetsExample = React.createClass({ @@ -45,6 +46,7 @@ var ImageCapInsetsExample = React.createClass({ @@ -66,7 +68,6 @@ var styles = StyleSheet.create({ width: 250, height: 150, borderWidth: 1, - resizeMode: Image.resizeMode.stretch, }, text: { fontSize: 13.5, diff --git a/Examples/UIExplorer/ImageEditingExample.js b/Examples/UIExplorer/ImageEditingExample.js index affd3679677266..868bbd25f333da 100644 --- a/Examples/UIExplorer/ImageEditingExample.js +++ b/Examples/UIExplorer/ImageEditingExample.js @@ -20,15 +20,16 @@ var React = require('react-native'); var { CameraRoll, Image, + ImageEditor, NativeModules, + Platform, ScrollView, StyleSheet, Text, TouchableHighlight, + UIManager, View, } = React; -var ImageEditingManager = NativeModules.ImageEditingManager; -var RCTScrollViewConsts = NativeModules.UIManager.RCTScrollView.Constants; var PAGE_SIZE = 20; @@ -42,14 +43,17 @@ type ImageSize = { height: number; }; -type TransformData = { +type ImageCropData = { offset: ImageOffset; size: ImageSize; -} + displaySize?: ?ImageSize; + resizeMode?: ?any; +}; class SquareImageCropper extends React.Component { + state: any; _isMounted: boolean; - _transformData: TransformData; + _transformData: ImageCropData; constructor(props) { super(props); @@ -63,22 +67,21 @@ class SquareImageCropper extends React.Component { this._fetchRandomPhoto(); } - _fetchRandomPhoto() { - CameraRoll.getPhotos( - {first: PAGE_SIZE}, - (data) => { - if (!this._isMounted) { - return; - } - var edges = data.edges; - var edge = edges[Math.floor(Math.random() * edges.length)]; - var randomPhoto = edge && edge.node && edge.node.image; - if (randomPhoto) { - this.setState({randomPhoto}); - } - }, - (error) => undefined - ); + async _fetchRandomPhoto() { + try { + const data = await CameraRoll.getPhotos({first: PAGE_SIZE}); + if (!this._isMounted) { + return; + } + var edges = data.edges; + var edge = edges[Math.floor(Math.random() * edges.length)]; + var randomPhoto = edge && edge.node && edge.node.image; + if (randomPhoto) { + this.setState({randomPhoto}); + } + } catch (error) { + console.warn("Can't get a photo from camera roll", error); + } } componentWillUnmount() { @@ -166,7 +169,7 @@ class SquareImageCropper extends React.Component { } _crop() { - ImageEditingManager.cropImage( + ImageEditor.cropImage( this.state.randomPhoto.uri, this._transformData, (croppedImageURI) => this.setState({croppedImageURI}), @@ -186,29 +189,49 @@ class SquareImageCropper extends React.Component { } class ImageCropper extends React.Component { - _scaledImageSize: ImageSize; _contentOffset: ImageOffset; + _maximumZoomScale: number; + _minimumZoomScale: number; + _scaledImageSize: ImageSize; + _horizontal: boolean; componentWillMount() { // Scale an image to the minimum size that is large enough to completely // fill the crop box. var widthRatio = this.props.image.width / this.props.size.width; var heightRatio = this.props.image.height / this.props.size.height; - if (widthRatio < heightRatio) { + this._horizontal = widthRatio > heightRatio; + if (this._horizontal) { this._scaledImageSize = { - width: this.props.size.width, - height: this.props.image.height / widthRatio, + width: this.props.image.width / heightRatio, + height: this.props.size.height, }; } else { this._scaledImageSize = { - width: this.props.image.width / heightRatio, - height: this.props.size.height, + width: this.props.size.width, + height: this.props.image.height / widthRatio, }; + if (Platform.OS === 'android') { + // hack to work around Android ScrollView a) not supporting zoom, and + // b) not supporting vertical scrolling when nested inside another + // vertical ScrollView (which it is, when displayed inside UIExplorer) + this._scaledImageSize.width *= 2; + this._scaledImageSize.height *= 2; + this._horizontal = true; + } } this._contentOffset = { x: (this._scaledImageSize.width - this.props.size.width) / 2, y: (this._scaledImageSize.height - this.props.size.height) / 2, }; + this._maximumZoomScale = Math.min( + this.props.image.width / this._scaledImageSize.width, + this.props.image.height / this._scaledImageSize.height + ); + this._minimumZoomScale = Math.max( + this.props.size.width / this._scaledImageSize.width, + this.props.size.height / this._scaledImageSize.height + ); this._updateTransformData( this._contentOffset, this._scaledImageSize, @@ -230,7 +253,7 @@ class ImageCropper extends React.Component { var sizeRatioX = croppedImageSize.width / scaledImageSize.width; var sizeRatioY = croppedImageSize.height / scaledImageSize.height; - this.props.onTransformDataChange && this.props.onTransformDataChange({ + var cropData: ImageCropData = { offset: { x: this.props.image.width * offsetRatioX, y: this.props.image.height * offsetRatioY, @@ -239,23 +262,20 @@ class ImageCropper extends React.Component { width: this.props.image.width * sizeRatioX, height: this.props.image.height * sizeRatioY, }, - }); + }; + this.props.onTransformDataChange && this.props.onTransformDataChange(cropData); } render() { - var decelerationRate = - RCTScrollViewConsts && RCTScrollViewConsts.DecelerationRate ? - RCTScrollViewConsts.DecelerationRate.Fast : - 0; - return ( + this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)} + onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)} + onLoadEnd={() => this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`)} + /> + + + {this.state.events.join('\n')} + + + ); + }, + + _loadEventFired(event) { + this.setState((state) => { + return state.events = [...state.events, event]; + }); + } +}); + +var NetworkImageExample = React.createClass({ getInitialState: function() { return { error: false, @@ -58,6 +95,38 @@ var NetworkImageExample = React.createClass({ } }); +var ImageSizeExample = React.createClass({ + getInitialState: function() { + return { + width: 0, + height: 0, + }; + }, + componentDidMount: function() { + Image.getSize(this.props.source.uri, (width, height) => { + this.setState({width, height}); + }); + }, + render: function() { + return ( + + + + Actual dimensions:{'\n'} + Width: {this.state.width}, Height: {this.state.height} + + + ); + }, +}); + exports.displayName = (undefined: ?string); exports.framework = 'React'; exports.title = ''; @@ -79,19 +148,27 @@ exports.examples = [ }, { title: 'Plain Static Image', - description: 'Static assets should be required by prefixing with `image!` ' + - 'and are located in the app bundle.', + description: 'Static assets should be placed in the source code tree, and ' + + 'required in the same way as JavaScript modules.', render: function() { return ( - - - - + + + + ); }, }, + { + title: 'Image Loading Events', + render: function() { + return ( + + ); + }, + }, { title: 'Error Handler', render: function() { @@ -110,6 +187,20 @@ exports.examples = [ }, platform: 'ios', }, + { + title: 'defaultSource', + description: 'Show a placeholder image when a network image is loading', + render: function() { + return ( + + ); + }, + platform: 'ios', + }, { title: 'Border Color', render: function() { @@ -243,19 +334,19 @@ exports.examples = [ @@ -361,6 +452,13 @@ exports.examples = [ }, platform: 'ios', }, + { + title: 'Image Size', + render: function() { + return ; + }, + platform: 'ios', + }, ]; var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'}; diff --git a/Examples/UIExplorer/LayoutEventsExample.js b/Examples/UIExplorer/LayoutEventsExample.js index bcbb5cbb7f937f..a6b0cea89c0f93 100644 --- a/Examples/UIExplorer/LayoutEventsExample.js +++ b/Examples/UIExplorer/LayoutEventsExample.js @@ -24,19 +24,30 @@ var { View, } = React; +type Layout = { + x: number; + y: number; + width: number; + height: number; +}; + type LayoutEvent = { nativeEvent: { - layout: { - x: number; - y: number; - width: number; - height: number; - }; + layout: Layout, }; }; +type State = { + containerStyle?: { width: number }, + extraText?: string, + imageLayout?: Layout, + textLayout?: Layout, + viewLayout?: Layout, + viewStyle: { margin: number }, +}; + var LayoutEventExample = React.createClass({ - getInitialState: function() { + getInitialState(): State { return { viewStyle: { margin: 20, diff --git a/Examples/UIExplorer/IntentAndroidExample.android.js b/Examples/UIExplorer/LinkingExample.js similarity index 87% rename from Examples/UIExplorer/IntentAndroidExample.android.js rename to Examples/UIExplorer/LinkingExample.js index e655bd9650c802..fbf36bfc071dc8 100644 --- a/Examples/UIExplorer/IntentAndroidExample.android.js +++ b/Examples/UIExplorer/LinkingExample.js @@ -15,7 +15,7 @@ var React = require('react-native'); var { - IntentAndroid, + Linking, StyleSheet, Text, TouchableNativeFeedback, @@ -30,9 +30,9 @@ var OpenURLButton = React.createClass({ }, handleClick: function() { - IntentAndroid.canOpenURL(this.props.url, (supported) => { + Linking.canOpenURL(this.props.url).then(supported => { if (supported) { - IntentAndroid.openURL(this.props.url); + Linking.openURL(this.props.url); } else { console.log('Don\'t know how to open URI: ' + this.props.url); } @@ -54,8 +54,8 @@ var OpenURLButton = React.createClass({ var IntentAndroidExample = React.createClass({ statics: { - title: 'IntentAndroid', - description: 'Shows how to use Android Intents to open URLs.', + title: 'Linking', + description: 'Shows how to use Linking to open URLs.', }, render: function() { @@ -64,7 +64,9 @@ var IntentAndroidExample = React.createClass({ + + ); }, diff --git a/Examples/UIExplorer/ListViewExample.js b/Examples/UIExplorer/ListViewExample.js index 6e18c6fb655361..ef16151ed22147 100644 --- a/Examples/UIExplorer/ListViewExample.js +++ b/Examples/UIExplorer/ListViewExample.js @@ -21,6 +21,7 @@ var { ListView, TouchableHighlight, StyleSheet, + RecyclerViewBackedScrollView, Text, View, } = React; @@ -29,7 +30,7 @@ var UIExplorerPage = require('./UIExplorerPage'); var ListViewSimpleExample = React.createClass({ statics: { - title: ' - Simple', + title: '', description: 'Performant, scrollable list of data.' }, @@ -49,12 +50,14 @@ var ListViewSimpleExample = React.createClass({ render: function() { return ( - Simple'} + title={this.props.navigator ? null : ''} noSpacer={true} noScroll={true}> } + renderSeparator={(sectionID, rowID) => } /> ); @@ -62,9 +65,7 @@ var ListViewSimpleExample = React.createClass({ _renderRow: function(rowData: string, sectionID: number, rowID: number) { var rowHash = Math.abs(hashCode(rowData)); - var imgSource = { - uri: THUMB_URLS[rowHash % THUMB_URLS.length], - }; + var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length]; return ( this._pressRow(rowID)}> @@ -74,7 +75,6 @@ var ListViewSimpleExample = React.createClass({ {rowData + ' - ' + LOREM_IPSUM.substr(0, rowHash % 301 + 10)} - ); @@ -98,18 +98,18 @@ var ListViewSimpleExample = React.createClass({ }); var THUMB_URLS = [ - 'Thumbnails/like.png', - 'Thumbnails/dislike.png', - 'Thumbnails/call.png', - 'Thumbnails/fist.png', - 'Thumbnails/bandaged.png', - 'Thumbnails/flowers.png', - 'Thumbnails/heart.png', - 'Thumbnails/liking.png', - 'Thumbnails/party.png', - 'Thumbnails/poke.png', - 'Thumbnails/superlike.png', - 'Thumbnails/victory.png', + require('./Thumbnails/like.png'), + require('./Thumbnails/dislike.png'), + require('./Thumbnails/call.png'), + require('./Thumbnails/fist.png'), + require('./Thumbnails/bandaged.png'), + require('./Thumbnails/flowers.png'), + require('./Thumbnails/heart.png'), + require('./Thumbnails/liking.png'), + require('./Thumbnails/party.png'), + require('./Thumbnails/poke.png'), + require('./Thumbnails/superlike.png'), + require('./Thumbnails/victory.png'), ]; var LOREM_IPSUM = 'Lorem ipsum dolor sit amet, ius ad pertinax oportere accommodare, an vix civibus corrumpit referrentur. Te nam case ludus inciderint, te mea facilisi adipiscing. Sea id integre luptatum. In tota sale consequuntur nec. Erat ocurreret mei ei. Eu paulo sapientem vulputate est, vel an accusam intellegam interesset. Nam eu stet pericula reprimique, ea vim illud modus, putant invidunt reprehendunt ne qui.'; diff --git a/Examples/UIExplorer/ListViewGridLayoutExample.js b/Examples/UIExplorer/ListViewGridLayoutExample.js index 6af00aa8a9474b..4d8017335d5642 100644 --- a/Examples/UIExplorer/ListViewGridLayoutExample.js +++ b/Examples/UIExplorer/ListViewGridLayoutExample.js @@ -26,18 +26,18 @@ var { } = React; var THUMB_URLS = [ - 'Thumbnails/like.png', - 'Thumbnails/dislike.png', - 'Thumbnails/call.png', - 'Thumbnails/fist.png', - 'Thumbnails/bandaged.png', - 'Thumbnails/flowers.png', - 'Thumbnails/heart.png', - 'Thumbnails/liking.png', - 'Thumbnails/party.png', - 'Thumbnails/poke.png', - 'Thumbnails/superlike.png', - 'Thumbnails/victory.png', + require('./Thumbnails/like.png'), + require('./Thumbnails/dislike.png'), + require('./Thumbnails/call.png'), + require('./Thumbnails/fist.png'), + require('./Thumbnails/bandaged.png'), + require('./Thumbnails/flowers.png'), + require('./Thumbnails/heart.png'), + require('./Thumbnails/liking.png'), + require('./Thumbnails/party.png'), + require('./Thumbnails/poke.png'), + require('./Thumbnails/superlike.png'), + require('./Thumbnails/victory.png'), ]; var ListViewGridLayoutExample = React.createClass({ @@ -67,6 +67,9 @@ var ListViewGridLayoutExample = React.createClass({ ); @@ -74,9 +77,7 @@ var ListViewGridLayoutExample = React.createClass({ _renderRow: function(rowData: string, sectionID: number, rowID: number) { var rowHash = Math.abs(hashCode(rowData)); - var imgSource = { - uri: THUMB_URLS[rowHash % THUMB_URLS.length], - }; + var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length]; return ( this._pressRow(rowID)} underlayColor="transparent"> diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js index b82f272c9dbbe6..a1406c52ff95ab 100644 --- a/Examples/UIExplorer/ListViewPagingExample.js +++ b/Examples/UIExplorer/ListViewPagingExample.js @@ -11,6 +11,7 @@ * 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. * + * @provides ListViewPagingExample * @flow */ 'use strict'; @@ -26,21 +27,25 @@ var { View, } = React; -var PAGE_SIZE = 4; +var NativeModules = require('NativeModules'); +var { + UIManager, +} = NativeModules; + var THUMB_URLS = [ - 'Thumbnails/like.png', - 'Thumbnails/dislike.png', - 'Thumbnails/call.png', - 'Thumbnails/fist.png', - 'Thumbnails/bandaged.png', - 'Thumbnails/flowers.png', - 'Thumbnails/heart.png', - 'Thumbnails/liking.png', - 'Thumbnails/party.png', - 'Thumbnails/poke.png', - 'Thumbnails/superlike.png', - 'Thumbnails/victory.png', - ]; + require('./Thumbnails/like.png'), + require('./Thumbnails/dislike.png'), + require('./Thumbnails/call.png'), + require('./Thumbnails/fist.png'), + require('./Thumbnails/bandaged.png'), + require('./Thumbnails/flowers.png'), + require('./Thumbnails/heart.png'), + require('./Thumbnails/liking.png'), + require('./Thumbnails/party.png'), + require('./Thumbnails/poke.png'), + require('./Thumbnails/superlike.png'), + require('./Thumbnails/victory.png'), +]; var NUM_SECTIONS = 100; var NUM_ROWS_PER_SECTION = 10; @@ -48,6 +53,10 @@ var Thumb = React.createClass({ getInitialState: function() { return {thumbIndex: this._getThumbIdx(), dir: 'row'}; }, + componentWillMount: function() { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); + }, _getThumbIdx: function() { return Math.floor(Math.random() * THUMB_URLS.length); }, @@ -64,9 +73,9 @@ var Thumb = React.createClass({ - - - + + + {this.state.dir === 'column' ? Oooo, look at this new text! So awesome it may just be crazy. @@ -172,8 +181,8 @@ var ListViewPagingExample = React.createClass({ renderSectionHeader={this.renderSectionHeader} renderRow={this.renderRow} initialListSize={10} - pageSize={PAGE_SIZE} - scrollRenderAheadDistance={2000} + pageSize={4} + scrollRenderAheadDistance={500} /> ); }, diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 5720175747593c..d11d3006ac470b 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -17,10 +17,13 @@ var React = require('react-native'); var { + Image, MapView, + PropTypes, StyleSheet, Text, TextInput, + TouchableOpacity, View, } = React; @@ -34,22 +37,20 @@ var regionText = { var MapRegionInput = React.createClass({ propTypes: { - region: React.PropTypes.shape({ - latitude: React.PropTypes.number.isRequired, - longitude: React.PropTypes.number.isRequired, - latitudeDelta: React.PropTypes.number.isRequired, - longitudeDelta: React.PropTypes.number.isRequired, + region: PropTypes.shape({ + latitude: PropTypes.number.isRequired, + longitude: PropTypes.number.isRequired, + latitudeDelta: PropTypes.number, + longitudeDelta: PropTypes.number, }), - onChange: React.PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, }, - getInitialState: function() { + getInitialState() { return { region: { latitude: 0, longitude: 0, - latitudeDelta: 0, - longitudeDelta: 0, } }; }, @@ -91,7 +92,9 @@ var MapRegionInput = React.createClass({ {'Latitude delta'} ); @@ -208,6 +215,42 @@ var MapViewExample = React.createClass({ }); +var AnnotationExample = React.createClass({ + + getInitialState() { + return { + 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 ( + + ); + }, + +}); + var styles = StyleSheet.create({ map: { height: 150, @@ -242,12 +285,123 @@ exports.description = 'Base component to display maps'; exports.examples = [ { title: 'Map', - render(): ReactElement { return ; } + render() { + return ; + } + }, + { + title: 'showsUserLocation + followUserLocation', + render() { + return ( + + ); + } }, { - title: 'Map shows user location', + title: 'Callout example', render() { - return ; + return { + alert('You Are Here'); + }}> + + + ), + }}/>; } - } + }, + { + title: 'Annotation focus example', + render() { + return { + alert('Annotation gets focus'); + }, + onBlur: () => { + alert('Annotation lost focus'); + } + }}/>; + } + }, + { + title: 'Draggable pin', + render() { + return { + console.log('Drag state: ' + event.state); + }, + }}/>; + } + }, + { + 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/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js new file mode 100644 index 00000000000000..15251a4cf2f7e7 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js @@ -0,0 +1,164 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + ScrollView, + Text, + TouchableHighlight, + TouchableOpacity +} = React; + +var _getRandomRoute = function() { + return { + title: '#' + Math.ceil(Math.random() * 1000), + }; +}; + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +var BreadcrumbNavSample = React.createClass({ + + componentWillMount: function() { + this._navBarRouteMapper = { + rightContentForRoute: function(route, navigator) { + return null; + }, + titleContentForRoute: function(route, navigator) { + return ( + navigator.push(_getRandomRoute())}> + {route.title} + + ); + }, + iconForRoute: function(route, navigator) { + return ( + { navigator.popToRoute(route); }} + style={styles.crumbIconPlaceholder} + /> + ); + }, + separatorForRoute: function(route, navigator) { + return ( + + ); + } + }; + }, + + _renderScene: function(route, navigator) { + return ( + + { navigator.push(_getRandomRoute()); }} + text="Push" + /> + { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]); }} + text="Reset w/ 2 scenes" + /> + { navigator.popToTop(); }} + text="Pop to top" + /> + { navigator.replace(_getRandomRoute()); }} + text="Replace" + /> + { this.props.navigator.pop(); }} + text="Close breadcrumb example" + /> + + ); + }, + + render: function() { + return ( + + } + /> + ); + }, + + + +}); + +var styles = StyleSheet.create({ + scene: { + paddingTop: 50, + flex: 1, + }, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + container: { + overflow: 'hidden', + backgroundColor: '#dddddd', + flex: 1, + }, + titleText: { + fontSize: 18, + color: '#666666', + textAlign: 'center', + fontWeight: 'bold', + lineHeight: 32, + }, + crumbIconPlaceholder: { + flex: 1, + backgroundColor: '#666666', + }, + crumbSeparatorPlaceholder: { + flex: 1, + backgroundColor: '#aaaaaa', + }, +}); + +module.exports = BreadcrumbNavSample; diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js new file mode 100644 index 00000000000000..aa2b9fd7df6cc6 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js @@ -0,0 +1,218 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + ScrollView, + TabBarIOS, + Text, + TouchableHighlight, + View, +} = React; + +var _getRandomRoute = function() { + return { + randNumber: Math.ceil(Math.random() * 1000), + }; +}; + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +var ROUTE_STACK = [ + _getRandomRoute(), + _getRandomRoute(), + _getRandomRoute(), +]; +var INIT_ROUTE_INDEX = 1; + +class JumpingNavBar extends React.Component { + constructor(props) { + super(props); + this.state = { + tabIndex: props.initTabIndex, + }; + } + handleWillFocus(route) { + var tabIndex = ROUTE_STACK.indexOf(route); + this.setState({ tabIndex, }); + } + render() { + return ( + + + { + this.props.onTabIndex(0); + this.setState({ tabIndex: 0, }); + }}> + + + { + this.props.onTabIndex(1); + this.setState({ tabIndex: 1, }); + }}> + + + { + this.props.onTabIndex(2); + this.setState({ tabIndex: 2, }); + }}> + + + + + ); + } +} + +var JumpingNavSample = React.createClass({ + render: function() { + return ( + { + this._navigator = navigator; + }} + initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]} + initialRouteStack={ROUTE_STACK} + renderScene={this.renderScene} + configureScene={() => ({ + ...Navigator.SceneConfigs.HorizontalSwipeJump, + })} + navigationBar={ + { this.navBar = navBar; }} + initTabIndex={INIT_ROUTE_INDEX} + routeStack={ROUTE_STACK} + onTabIndex={(index) => { + this._navigator.jumpTo(ROUTE_STACK[index]); + }} + /> + } + /> + ); + }, + + renderScene: function(route, navigator) { + var backBtn; + var forwardBtn; + if (ROUTE_STACK.indexOf(route) !== 0) { + backBtn = ( + { + navigator.jumpBack(); + }} + text="jumpBack" + /> + ); + } + if (ROUTE_STACK.indexOf(route) !== ROUTE_STACK.length - 1) { + forwardBtn = ( + { + navigator.jumpForward(); + }} + text="jumpForward" + /> + ); + } + return ( + + #{route.randNumber} + {backBtn} + {forwardBtn} + { + navigator.jumpTo(ROUTE_STACK[1]); + }} + text="jumpTo middle route" + /> + { + this.props.navigator.pop(); + }} + text="Exit Navigation Example" + /> + { + this.props.navigator.push({ + message: 'Came from jumping example', + }); + }} + text="Nav Menu" + /> + + ); + }, +}); + +var styles = StyleSheet.create({ + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + appContainer: { + overflow: 'hidden', + backgroundColor: '#dddddd', + flex: 1, + }, + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#EAEAEA', + }, + tabs: { + height: 50, + } +}); + +module.exports = JumpingNavSample; diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js new file mode 100644 index 00000000000000..ed4169751418e9 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js @@ -0,0 +1,212 @@ +/** + * 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. + */ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + ScrollView, + StyleSheet, + Text, + TouchableHighlight, +} = React; + +var BreadcrumbNavSample = require('./BreadcrumbNavSample'); +var NavigationBarSample = require('./NavigationBarSample'); +var JumpingNavSample = require('./JumpingNavSample'); + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +class NavMenu extends React.Component { + render() { + return ( + + {this.props.message} + { + this.props.navigator.push({ + message: 'Swipe right to dismiss', + sceneConfig: Navigator.SceneConfigs.FloatFromRight, + }); + }} + text="Float in from right" + /> + { + this.props.navigator.push({ + message: 'Swipe down to dismiss', + sceneConfig: Navigator.SceneConfigs.FloatFromBottom, + }); + }} + text="Float in from bottom" + /> + { + this.props.navigator.pop(); + }} + text="Pop" + /> + { + this.props.navigator.popToTop(); + }} + text="Pop to top" + /> + { + this.props.navigator.push({ id: 'navbar' }); + }} + text="Navbar Example" + /> + { + this.props.navigator.push({ id: 'jumping' }); + }} + text="Jumping Example" + /> + { + this.props.navigator.push({ id: 'breadcrumbs' }); + }} + text="Breadcrumbs Example" + /> + { + this.props.onExampleExit(); + }} + text="Exit Example" + /> + + ); + } +} + +var TabBarExample = React.createClass({ + + statics: { + title: ' (Experimental)', + description: 'Experimental navigator that ports the API of the current ' + + 'Navigator component', + }, + + renderScene: function(route, nav) { + switch (route.id) { + case 'navbar': + return ; + case 'breadcrumbs': + return ; + case 'jumping': + return ; + default: + return ( + + ); + } + }, + + render: function() { + return ( + { + if (route.sceneConfig) { + return route.sceneConfig; + } + return Navigator.SceneConfigs.FloatFromBottom; + }} + /> + ); + }, + + + componentWillUnmount: function() { + this._listeners && this._listeners.forEach(listener => listener.remove()); + }, + + _setNavigatorRef: function(navigator) { + if (navigator !== this._navigator) { + this._navigator = navigator; + + if (navigator) { + var callback = (event) => { + console.log( + `TabBarExample: event ${event.type}`, + { + route: JSON.stringify(event.data.route), + target: event.target, + type: event.type, + } + ); + }; + // Observe focus change events from the owner. + this._listeners = [ + navigator.navigationContext.addListener('willfocus', callback), + navigator.navigationContext.addListener('didfocus', callback), + ]; + } + } + }, +}); + +var styles = StyleSheet.create({ + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + container: { + flex: 1, + }, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#ccc', + } +}); + +TabBarExample.external = true; + +module.exports = TabBarExample; diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js new file mode 100644 index 00000000000000..9b9081dd0c7f5c --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js @@ -0,0 +1,203 @@ +/** + * 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. +*/ +'use strict'; + + +var React = require('react-native'); +var { + NavigationExperimental, + ScrollView, + StyleSheet, + Text, + TouchableHighlight, + TouchableOpacity, +} = React; + +var cssVar = require('cssVar'); + +var Navigator = NavigationExperimental.LegacyNavigator; + +class NavButton extends React.Component { + render() { + return ( + + {this.props.text} + + ); + } +} + +var NavigationBarRouteMapper = { + + LeftButton: function(route, navigator, index, navState) { + if (index === 0) { + return null; + } + + var previousRoute = navState.routeStack[index - 1]; + return ( + navigator.pop()} + style={styles.navBarLeftButton}> + + {previousRoute.title} + + + ); + }, + + RightButton: function(route, navigator, index, navState) { + return ( + navigator.push(newRandomRoute())} + style={styles.navBarRightButton}> + + Next + + + ); + }, + + Title: function(route, navigator, index, navState) { + return ( + + {route.title} [{index}] + + ); + }, + +}; + +function newRandomRoute() { + return { + title: '#' + Math.ceil(Math.random() * 1000), + }; +} + +var NavigationBarSample = React.createClass({ + + componentWillMount: function() { + var navigator = this.props.navigator; + + var callback = (event) => { + console.log( + `NavigationBarSample : event ${event.type}`, + { + route: JSON.stringify(event.data.route), + target: event.target, + type: event.type, + } + ); + }; + + // Observe focus change events from this component. + this._listeners = [ + navigator.navigationContext.addListener('willfocus', callback), + navigator.navigationContext.addListener('didfocus', callback), + ]; + }, + + componentWillUnmount: function() { + this._listeners && this._listeners.forEach(listener => listener.remove()); + }, + + render: function() { + return ( + ( + + {route.content} + { + navigator.immediatelyResetRouteStack([ + newRandomRoute(), + newRandomRoute(), + newRandomRoute(), + ]); + }} + text="Reset w/ 3 scenes" + /> + { + this.props.navigator.pop(); + }} + text="Exit NavigationBar Example" + /> + + )} + navigationBar={ + + } + /> + ); + }, + +}); + +var styles = StyleSheet.create({ + messageText: { + fontSize: 17, + fontWeight: '500', + padding: 15, + marginTop: 50, + marginLeft: 15, + }, + button: { + backgroundColor: 'white', + padding: 15, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#CDCDCD', + }, + buttonText: { + fontSize: 17, + fontWeight: '500', + }, + navBar: { + backgroundColor: 'white', + }, + navBarText: { + fontSize: 16, + marginVertical: 10, + }, + navBarTitleText: { + color: cssVar('fbui-bluegray-60'), + fontWeight: '500', + marginVertical: 9, + }, + navBarLeftButton: { + paddingLeft: 10, + }, + navBarRightButton: { + paddingRight: 10, + }, + navBarButtonText: { + color: cssVar('fbui-accent-blue'), + }, + scene: { + flex: 1, + paddingTop: 20, + backgroundColor: '#EAEAEA', + }, +}); + +module.exports = NavigationBarSample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js new file mode 100644 index 00000000000000..e74d80e2630696 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js @@ -0,0 +1,128 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + Animated, + NavigationExperimental, + StyleSheet, + ScrollView, +} = React; +var NavigationExampleRow = require('./NavigationExampleRow'); +var { + AnimatedView: NavigationAnimatedView, + Card: NavigationCard, + RootContainer: NavigationRootContainer, + Reducer: NavigationReducer, + Header: NavigationHeader, +} = NavigationExperimental; + +const NavigationBasicReducer = NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (action.type === 'push') { + return (state) => state || {key: action.key}; + } + return null; + }, + getReducerForState: (initialState) => (state) => state || initialState, + initialState: { + key: 'AnimatedExampleStackKey', + index: 0, + children: [ + {key: 'First Route'}, + ], + }, +}); + +class NavigationAnimatedExample extends React.Component { + componentWillMount() { + this._renderNavigated = this._renderNavigated.bind(this); + } + render() { + return ( + { this.navRootContainer = navRootContainer; }} + persistenceKey="NavigationAnimExampleState" + renderNavigation={this._renderNavigated} + /> + ); + } + handleBackAction() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + } + _renderNavigated(navigationState, onNavigate) { + if (!navigationState) { + return null; + } + return ( + ( + state.key} + /> + )} + setTiming={(pos, navState) => { + Animated.timing(pos, {toValue: navState.index, duration: 1000}).start(); + }} + renderScene={(props) => ( + + + + { + onNavigate({ + type: 'push', + key: 'Route #' + props.navigationParentState.children.length + }); + }} + /> + + + + )} + /> + ); + } +} + +const styles = StyleSheet.create({ + animatedView: { + flex: 1, + }, + scrollView: { + marginTop: 64 + }, +}); + +module.exports = NavigationAnimatedExample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js new file mode 100644 index 00000000000000..447ab2051fba08 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js @@ -0,0 +1,100 @@ +/** + * 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. +*/ +'use strict'; + +const React = require('react-native'); +const { + NavigationExperimental, + ScrollView, + StyleSheet, +} = React; +const NavigationExampleRow = require('./NavigationExampleRow'); +const { + RootContainer: NavigationRootContainer, + Reducer: NavigationReducer, +} = NavigationExperimental; +const StackReducer = NavigationReducer.StackReducer; + +const NavigationBasicReducer = NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (action.type === 'push') { + return (state) => state || {key: action.key}; + } + return null; + }, + getReducerForState: (initialState) => (state) => state || initialState, + initialState: { + key: 'BasicExampleStackKey', + index: 0, + children: [ + {key: 'First Route'}, + ], + }, +}); + +const NavigationBasicExample = React.createClass({ + render: function() { + return ( + { this.navRootContainer = navRootContainer; }} + renderNavigation={(navState, onNavigate) => { + if (!navState) { return null; } + return ( + + + { + onNavigate({ type: 'push', key: 'page #' + navState.children.length }); + }} + /> + { + onNavigate(NavigationRootContainer.getBackAction()); + }} + /> + + + ); + }} + /> + ); + }, + + handleBackAction: function() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + }, + +}); + +const styles = StyleSheet.create({ + topView: { + backgroundColor: '#E9E9EF', + flex: 1, + paddingTop: 30, + }, +}); + +module.exports = NavigationBasicExample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js new file mode 100644 index 00000000000000..e7feb7c60b9b2e --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js @@ -0,0 +1,151 @@ +/** + * 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. +*/ +'use strict'; + +const NavigationExampleRow = require('./NavigationExampleRow'); +const NavigationRootContainer = require('NavigationRootContainer'); +const React = require('react-native'); + +const { + NavigationExperimental, + StyleSheet, + ScrollView, +} = React; + +const NavigationCardStack = NavigationExperimental.CardStack; +const NavigationStateUtils = NavigationExperimental.StateUtils; + +function reduceNavigationState(initialState) { + return (currentState, action) => { + switch (action.type) { + case 'RootContainerInitialAction': + return initialState; + + case 'push': + return NavigationStateUtils.push(currentState, {key: action.key}); + + case 'back': + case 'pop': + return currentState.index > 0 ? + NavigationStateUtils.pop(currentState) : + currentState; + + default: + return currentState; + } + }; +} + +const ExampleReducer = reduceNavigationState({ + index: 0, + children: [{key: 'First Route'}], +}); + +class NavigationCardStackExample extends React.Component { + + constructor(props, context) { + super(props, context); + + this._renderNavigation = this._renderNavigation.bind(this); + this._renderScene = this._renderScene.bind(this); + this._toggleDirection = this._toggleDirection.bind(this); + + this.state = {isHorizontal: true}; + } + + render() { + return ( + + ); + } + + _renderNavigation(navigationState, onNavigate) { + return ( + + ); + } + + _renderScene(props) { + const {navigationParentState, onNavigate} = props; + return ( + + + + { + onNavigate({ + type: 'push', + key: 'Route ' + navigationParentState.children.length, + }); + }} + /> + { + onNavigate({ + type: 'pop', + }); + }} + /> + + + ); + } + + _toggleDirection() { + this.setState({ + isHorizontal: !this.state.isHorizontal, + }); + } + + _onNavigate(action) { + if (action && action.type === 'back') { + this._pop(); + } + } +} + +const styles = StyleSheet.create({ + main: { + flex: 1, + }, + scrollView: { + marginTop: 64 + }, +}); + +module.exports = NavigationCardStackExample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js new file mode 100644 index 00000000000000..bc1d184db5de79 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js @@ -0,0 +1,280 @@ +/** + * 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 + */ +'use strict'; + +const React = require('react-native'); +const { + NavigationExperimental, + ScrollView, + StyleSheet, + View, +} = React; +const { + AnimatedView: NavigationAnimatedView, + Card: NavigationCard, + Container: NavigationContainer, + RootContainer: NavigationRootContainer, + Header: NavigationHeader, + Reducer: NavigationReducer, + View: NavigationView, +} = NavigationExperimental; +const NavigationExampleRow = require('./NavigationExampleRow'); +const NavigationExampleTabBar = require('./NavigationExampleTabBar'); + +import type {NavigationParentState} from 'NavigationStateUtils'; + +type Action = { + isExitAction?: boolean, +}; + +const ExampleExitAction = () => ({ + isExitAction: true, +}); +ExampleExitAction.match = (action: Action) => ( + action && action.isExitAction === true +); + +const PageAction = (type) => ({ + type, + isPageAction: true, +}); +PageAction.match = (action) => ( + action && action.isPageAction === true +); + +const ExampleProfilePageAction = (type) => ({ + ...PageAction(type), + isProfilePageAction: true, +}); +ExampleProfilePageAction.match = (action) => ( + action && action.isProfilePageAction === true +); + +const ExampleInfoAction = () => PageAction('InfoPage'); + +const ExampleNotifProfileAction = () => ExampleProfilePageAction('NotifProfilePage'); + +const _jsInstanceUniqueId = '' + Date.now(); +let _uniqueIdCount = 0; +function pageStateActionMap(action) { + return { + key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++), + type: action.type, + }; +} + +const ExampleAppReducer = NavigationReducer.TabsReducer({ + key: 'AppNavigationState', + initialIndex: 0, + tabReducers: [ + NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (PageAction.match(action) && !ExampleProfilePageAction.match(action)) { + return (state) => (state || pageStateActionMap(action)); + } + return null; + }, + initialState: { + key: 'notifs', + index: 0, + children: [ + {key: 'base', type: 'NotifsPage'}, + ], + }, + }), + NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (PageAction.match(action) && !ExampleProfilePageAction.match(action)) { + return (state) => (state || pageStateActionMap(action)); + } + return null; + }, + initialState: { + key: 'settings', + index: 0, + children: [ + {key: 'base', type: 'SettingsPage'}, + ], + }, + }), + NavigationReducer.StackReducer({ + getPushedReducerForAction: (action) => { + if (PageAction.match(action) || ExampleProfilePageAction.match(action)) { + return (state) => (state || pageStateActionMap(action)); + } + return null; + }, + initialState: { + key: 'profile', + index: 0, + children: [ + {key: 'base', type: 'ProfilePage'}, + ], + }, + }), + ], +}); + +function stateTypeTitleMap(pageState) { + switch (pageState.type) { + case 'ProfilePage': + return 'Profile Page'; + case 'NotifsPage': + return 'Notifications'; + case 'SettingsPage': + return 'Settings'; + case 'InfoPage': + return 'Info Page'; + case 'NotifProfilePage': + return 'Page in Profile'; + } +} + +class ExampleTabScreen extends React.Component { + render() { + return ( + + ); + } + _renderHeader(props) { + return ( + stateTypeTitleMap(state)} + /> + ); + } + _renderScene(props) { + return ( + + + { + this.props.onNavigate(ExampleInfoAction()); + }} + /> + { + this.props.onNavigate(ExampleNotifProfileAction()); + }} + /> + { + this.props.onNavigate(ExampleExitAction()); + }} + /> + + + ); + } +} +ExampleTabScreen = NavigationContainer.create(ExampleTabScreen); + +class NavigationCompositionExample extends React.Component { + navRootContainer: NavigationRootContainer; + + render() { + return ( + { this.navRootContainer = navRootContainer; }} + renderNavigation={this.renderApp.bind(this)} + /> + ); + } + handleBackAction(): boolean { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + } + renderApp(navigationState: NavigationParentState, onNavigate: Function) { + if (!navigationState) { + return null; + } + return ( + + + + + ); + } +} + +class ExampleMainView extends React.Component { + render() { + return ( + ( + + )} + /> + ); + } + _handleNavigation(tabKey, action) { + if (ExampleExitAction.match(action)) { + this.props.onExampleExit(); + return; + } + this.props.onNavigate(action); + } +} +ExampleMainView = NavigationContainer.create(ExampleMainView); + +const styles = StyleSheet.create({ + topView: { + flex: 1, + }, + tabsContent: { + flex: 1, + }, + scrollView: { + marginTop: 64 + }, + tabContent: { + flex: 1, + }, +}); + +module.exports = NavigationCompositionExample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js b/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js new file mode 100644 index 00000000000000..4e431e30a722b2 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js @@ -0,0 +1,65 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + Text, + PixelRatio, + StyleSheet, + View, + TouchableHighlight, +} = React; + +var NavigationExampleRow = React.createClass({ + render: function() { + 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/NavigationExperimental/NavigationExampleTabBar.js b/Examples/UIExplorer/NavigationExperimental/NavigationExampleTabBar.js new file mode 100644 index 00000000000000..6dbaab095b9bd6 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationExampleTabBar.js @@ -0,0 +1,81 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + Text, + TouchableOpacity, + View, +} = React; +const { + Container: NavigationContainer, + Reducer: NavigationReducer, +} = NavigationExperimental; +const { + JumpToAction, +} = NavigationReducer.TabsReducer; + +var NavigationExampleTabBar = React.createClass({ + render: function() { + return ( + + {this.props.tabs.map(this._renderTab)} + + ); + }, + _renderTab: function(tab, index) { + var textStyle = [styles.tabButtonText]; + if (this.props.index === index) { + textStyle.push(styles.selectedTabButtonText); + } + return ( + { + this.props.onNavigate(JumpToAction(index)); + }}> + + {tab.key} + + + ); + }, +}); + +NavigationExampleTabBar = NavigationContainer.create(NavigationExampleTabBar); + +const styles = StyleSheet.create({ + tabBar: { + height: 50, + flexDirection: 'row', + }, + tabButton: { + flex: 1, + }, + tabButtonText: { + paddingTop: 20, + textAlign: 'center', + fontSize: 17, + fontWeight: '500', + }, + selectedTabButtonText: { + color: 'blue', + }, +}); + +module.exports = NavigationExampleTabBar; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js new file mode 100644 index 00000000000000..0781570da28df0 --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js @@ -0,0 +1,152 @@ +/** + * 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. +*/ +'use strict'; + +var React = require('react-native'); +var { + AsyncStorage, + ScrollView, + StyleSheet, + View, +} = React; +var NavigationExampleRow = require('./NavigationExampleRow'); + +/* + * 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: + */ +var EXAMPLES = { + 'Tabs': require('./NavigationTabsExample'), + 'Basic': require('./NavigationBasicExample'), + 'Animated Example': require('./NavigationAnimatedExample'), + 'Composition': require('./NavigationCompositionExample'), + 'Card Stack Example': require('./NavigationCardStackExample'), + 'Tic Tac Toe': require('./NavigationTicTacToeExample'), +}; + +var EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample'; + +var NavigationExperimentalExample = React.createClass({ + statics: { + title: 'Navigation (Experimental)', + description: 'Upcoming navigation APIs and animated navigation views', + external: true, + }, + + getInitialState: function() { + return { + example: null, + }; + }, + + componentDidMount() { + AsyncStorage.getItem(EXAMPLE_STORAGE_KEY, (err, example) => { + if (err || !example || !EXAMPLES[example]) { + this.setState({ + example: 'menu', + }); + return; + } + this.setState({ + example, + }); + }); + }, + + setExample: function(example) { + this.setState({ + example, + }); + AsyncStorage.setItem(EXAMPLE_STORAGE_KEY, example); + }, + + _renderMenu: function() { + var exitRow = null; + if (this.props.onExampleExit) { + exitRow = ( + + ); + } + return ( + + + {this._renderExampleList()} + {exitRow} + + + ); + }, + + _renderExampleList: function() { + return Object.keys(EXAMPLES).map(exampleName => ( + { + this.setExample(exampleName); + }} + /> + )); + }, + + _exitInnerExample: function() { + this.setExample('menu'); + }, + + handleBackAction: function() { + 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: function() { + if (this.state.example === 'menu') { + return this._renderMenu(); + } + if (EXAMPLES[this.state.example]) { + var 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/NavigationExperimental/NavigationTabsExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js new file mode 100644 index 00000000000000..ae4de59a6da8fa --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js @@ -0,0 +1,111 @@ +/** + * 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. +*/ +'use strict'; + +const React = require('react-native'); +const { + NavigationExperimental, + ScrollView, + StyleSheet, + View, +} = React; +const { + Container: NavigationContainer, + RootContainer: NavigationRootContainer, + Reducer: NavigationReducer, +} = NavigationExperimental; + +const NavigationExampleRow = require('./NavigationExampleRow'); +const NavigationExampleTabBar = require('./NavigationExampleTabBar'); + +class ExmpleTabPage extends React.Component { + render() { + const currentTabRoute = this.props.tabs[this.props.index]; + return ( + + + {this.props.tabs.map((tab, index) => ( + { + this.props.onNavigate(NavigationReducer.TabsReducer.JumpToAction(index)); + }} + /> + ))} + + + ); + } +} +ExmpleTabPage = NavigationContainer.create(ExmpleTabPage); + +const ExampleTabsReducer = NavigationReducer.TabsReducer({ + tabReducers: [ + (lastRoute) => lastRoute || {key: 'one'}, + (lastRoute) => lastRoute || {key: 'two'}, + (lastRoute) => lastRoute || {key: 'three'}, + ], +}); + +class NavigationTabsExample extends React.Component { + render() { + return ( + { this.navRootContainer = navRootContainer; }} + renderNavigation={(navigationState) => { + if (!navigationState) { return null; } + return ( + + + + + ); + }} + /> + ); + } + handleBackAction() { + return ( + this.navRootContainer && + this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction()) + ); + } +} + +const styles = StyleSheet.create({ + topView: { + flex: 1, + paddingTop: 30, + }, + tabPage: { + backgroundColor: '#E9E9EF', + }, +}); + +module.exports = NavigationTabsExample; diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js new file mode 100644 index 00000000000000..d0604ec08eae3e --- /dev/null +++ b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js @@ -0,0 +1,314 @@ +/** + * 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 NavigationTicTacToeExample + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + NavigationExperimental, + StyleSheet, + Text, + TouchableHighlight, + View, +} = React; +const { + Container: NavigationContainer, + RootContainer: NavigationRootContainer, +} = NavigationExperimental; + +type GameGrid = Array>; + +const evenOddPlayerMap = ['O', 'X']; +const rowLeterMap = ['a', 'b', 'c']; + +function parseGame(game: string): GameGrid { + const gameTurns = game ? game.split('-') : []; + const grid = Array(3); + for (let i = 0; i < 3; i++) { + const row = Array(3); + for (let j = 0; j < 3; j++) { + const turnIndex = gameTurns.indexOf(rowLeterMap[i]+j); + if (turnIndex === -1) { + row[j] = null; + } else { + row[j] = evenOddPlayerMap[turnIndex % 2]; + } + } + grid[i] = row; + } + return grid; +} + +function playTurn(game: string, row: number, col: number): string { + const turn = rowLeterMap[row] + col; + return game ? (game + '-' + turn) : turn; +} + +function getWinner(gameString: string): ?string { + const game = parseGame(gameString); + for (var i = 0; i < 3; i++) { + if (game[i][0] !== null && game[i][0] === game[i][1] && + game[i][0] === game[i][2]) { + return game[i][0]; + } + } + for (var i = 0; i < 3; i++) { + if (game[0][i] !== null && game[0][i] === game[1][i] && + game[0][i] === game[2][i]) { + return game[0][i]; + } + } + if (game[0][0] !== null && game[0][0] === game[1][1] && + game[0][0] === game[2][2]) { + return game[0][0]; + } + if (game[0][2] !== null && game[0][2] === game[1][1] && + game[0][2] === game[2][0]) { + return game[0][2]; + } + return null; +} + +function isGameOver(gameString: string): boolean { + if (getWinner(gameString)) { + return true; + } + const game = parseGame(gameString); + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + if (game[i][j] === null) { + return false; + } + } + } + return true; +} + +class Cell extends React.Component { + props: any; + cellStyle() { + switch (this.props.player) { + case 'X': + return styles.cellX; + case 'O': + return styles.cellO; + default: + return null; + } + } + textStyle() { + switch (this.props.player) { + case 'X': + return styles.cellTextX; + case 'O': + return styles.cellTextO; + default: + return {}; + } + } + render() { + return ( + + + + {this.props.player} + + + + ); + } +} + +function GameEndOverlay(props) { + if (!isGameOver(props.game)) { + return ; + } + const winner = getWinner(props.game); + return ( + + + {winner ? winner + ' wins!' : 'It\'s a tie!'} + + props.onNavigate(GameActions.Reset())} + underlayColor="transparent" + activeOpacity={0.5}> + + New Game + + + + ); +} +GameEndOverlay = NavigationContainer.create(GameEndOverlay); + +function TicTacToeGame(props) { + var rows = parseGame(props.game).map((cells, row) => + + {cells.map((player, col) => + props.onNavigate(GameActions.Turn(row, col))} + /> + )} + + ); + return ( + + + Close + + EXTREME T3 + + {rows} + + + + ); +} +TicTacToeGame = NavigationContainer.create(TicTacToeGame); + +const GameActions = { + Turn: (row, col) => ({type: 'TicTacToeTurnAction', row, col }), + Reset: (row, col) => ({type: 'TicTacToeResetAction' }), +}; + +function GameReducer(lastGame: ?string, action: Object): string { + if (!lastGame) { + lastGame = ''; + } + if (action.type === 'TicTacToeResetAction') { + return ''; + } + if (!isGameOver(lastGame) && action.type === 'TicTacToeTurnAction') { + return playTurn(lastGame, action.row, action.col); + } + return lastGame; +} + +class NavigationTicTacToeExample extends React.Component { + static GameView = TicTacToeGame; + static GameReducer = GameReducer; + static GameActions = GameActions; + render() { + return ( + ( + + )} + /> + ); + } +} + +const styles = StyleSheet.create({ + closeButton: { + position: 'absolute', + left: 10, + top: 30, + fontSize: 14, + }, + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'white' + }, + title: { + fontFamily: 'Chalkduster', + fontSize: 39, + marginBottom: 20, + }, + board: { + padding: 5, + backgroundColor: '#47525d', + borderRadius: 10, + }, + row: { + flexDirection: 'row', + }, + cell: { + width: 80, + height: 80, + borderRadius: 5, + backgroundColor: '#7b8994', + margin: 5, + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + cellX: { + backgroundColor: '#72d0eb', + }, + cellO: { + backgroundColor: '#7ebd26', + }, + cellText: { + fontSize: 50, + fontFamily: 'AvenirNext-Bold', + }, + cellTextX: { + color: '#19a9e5', + }, + cellTextO: { + color: '#b9dc2f', + }, + overlay: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + backgroundColor: 'rgba(221, 221, 221, 0.5)', + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + overlayMessage: { + fontSize: 40, + marginBottom: 20, + marginLeft: 20, + marginRight: 20, + fontFamily: 'AvenirNext-DemiBold', + textAlign: 'center', + }, + newGame: { + backgroundColor: '#887766', + padding: 20, + borderRadius: 5, + }, + newGameText: { + color: 'white', + fontSize: 20, + fontFamily: 'AvenirNext-DemiBold', + }, +}); + +module.exports = NavigationTicTacToeExample; diff --git a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js index 62196e5f2f4d1f..e05a8c39e8397a 100644 --- a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js +++ b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js @@ -15,7 +15,6 @@ var React = require('react-native'); var { - PixelRatio, Navigator, StyleSheet, ScrollView, @@ -131,7 +130,7 @@ var styles = StyleSheet.create({ button: { backgroundColor: 'white', padding: 15, - borderBottomWidth: 1 / PixelRatio.get(), + borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#CDCDCD', }, buttonText: { diff --git a/Examples/UIExplorer/Navigator/JumpingNavSample.js b/Examples/UIExplorer/Navigator/JumpingNavSample.js index eecabcbce9ca13..54fe312c22021e 100644 --- a/Examples/UIExplorer/Navigator/JumpingNavSample.js +++ b/Examples/UIExplorer/Navigator/JumpingNavSample.js @@ -16,7 +16,6 @@ var React = require('react-native'); var { Navigator, - PixelRatio, StyleSheet, ScrollView, TabBarIOS, @@ -185,7 +184,7 @@ var styles = StyleSheet.create({ button: { backgroundColor: 'white', padding: 15, - borderBottomWidth: 1 / PixelRatio.get(), + borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#CDCDCD', }, buttonText: { diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js index 77e0740028a784..25f45f0ab30366 100644 --- a/Examples/UIExplorer/Navigator/NavigationBarSample.js +++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js @@ -16,7 +16,6 @@ var React = require('react-native'); var { - PixelRatio, Navigator, ScrollView, StyleSheet, @@ -164,7 +163,7 @@ var styles = StyleSheet.create({ button: { backgroundColor: 'white', padding: 15, - borderBottomWidth: 1 / PixelRatio.get(), + borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#CDCDCD', }, buttonText: { diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/Navigator/NavigatorExample.js index aa156410933ce4..7346af4542d495 100644 --- a/Examples/UIExplorer/Navigator/NavigatorExample.js +++ b/Examples/UIExplorer/Navigator/NavigatorExample.js @@ -10,13 +10,14 @@ * 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 NavigatorExample + */ 'use strict'; var React = require('react-native'); var { Navigator, - PixelRatio, ScrollView, StyleSheet, Text, @@ -190,7 +191,7 @@ var styles = StyleSheet.create({ button: { backgroundColor: 'white', padding: 15, - borderBottomWidth: 1 / PixelRatio.get(), + borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#CDCDCD', }, buttonText: { diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js index 4078cac6db6421..1735633c9d3497 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -16,7 +16,7 @@ var React = require('react-native'); var { NavigatorIOS, - StatusBarIOS, + StatusBar, StyleSheet, Text, View @@ -45,7 +45,7 @@ var NavigatorIOSColors = React.createClass({ render: function() { // Set StatusBar with light contents to get better contrast - StatusBarIOS.setStyle('light-content'); + StatusBar.setBarStyle('light-content'); return ( ', rightButtonTitle: 'Done', onRightButtonPress: () => { - StatusBarIOS.setStyle('default'); + StatusBar.setBarStyle('default'); this.props.onExampleExit(); }, passProps: { diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js index 4a2011a654611c..3c6f19f94dfcc1 100644 --- a/Examples/UIExplorer/NavigatorIOSExample.js +++ b/Examples/UIExplorer/NavigatorIOSExample.js @@ -15,12 +15,12 @@ */ 'use strict'; -var React = require('react-native'); -var ViewExample = require('./ViewExample'); -var createExamplePage = require('./createExamplePage'); -var { +const React = require('react-native'); +const ViewExample = require('./ViewExample'); +const createExamplePage = require('./createExamplePage'); +const { AlertIOS, - PixelRatio, + NavigatorIOS, ScrollView, StyleSheet, Text, @@ -28,8 +28,7 @@ var { View, } = React; -var EmptyPage = React.createClass({ - +const EmptyPage = React.createClass({ render: function() { return ( @@ -39,41 +38,24 @@ var EmptyPage = React.createClass({ ); }, - }); -var NavigatorIOSExample = React.createClass({ - - statics: { - title: '', - description: 'iOS navigation capabilities', - }, - +const NavigatorIOSExamplePage = React.createClass({ render: function() { var recurseTitle = 'Recurse Navigation'; - if (!this.props.topExampleRoute) { + if (!this.props.depth || this.props.depth === 1) { recurseTitle += ' - more examples here'; } return ( - - - - - See <UIExplorerApp> for top-level usage. - - - - - {this._renderRow(recurseTitle, () => { this.props.navigator.push({ title: NavigatorIOSExample.title, - component: NavigatorIOSExample, + component: NavigatorIOSExamplePage, backButtonTitle: 'Custom Back', - passProps: {topExampleRoute: this.props.topExampleRoute || this.props.route}, + passProps: {depth: this.props.depth ? this.props.depth + 1 : 1}, }); })} {this._renderRow('Push View Example', () => { @@ -123,40 +105,39 @@ var NavigatorIOSExample = React.createClass({ {this._renderRow('Pop to top', () => { this.props.navigator.popToTop(); })} - {this._renderRow('Replace here', () => { - var prevRoute = this.props.route; - this.props.navigator.replace({ - title: 'New Navigation', - component: EmptyPage, - rightButtonTitle: 'Undo', - onRightButtonPress: () => this.props.navigator.replace(prevRoute), - passProps: { - text: 'The component is replaced, but there is currently no ' + - 'way to change the right button or title of the current route', - } - }); - })} + {this._renderReplace()} {this._renderReplacePrevious()} {this._renderReplacePreviousAndPop()} - {this._renderPopToTopNavExample()} + {this._renderRow('Exit NavigatorIOS Example', this.props.onExampleExit)} ); }, - _renderPopToTopNavExample: function() { - if (!this.props.topExampleRoute) { + _renderReplace: function() { + if (!this.props.depth) { + // this is to avoid replacing the top of the stack return null; } - return this._renderRow('Pop to top NavigatorIOSExample', () => { - this.props.navigator.popToRoute(this.props.topExampleRoute); + return this._renderRow('Replace here', () => { + var prevRoute = this.props.route; + this.props.navigator.replace({ + title: 'New Navigation', + component: EmptyPage, + rightButtonTitle: 'Undo', + onRightButtonPress: () => this.props.navigator.replace(prevRoute), + passProps: { + text: 'The component is replaced, but there is currently no ' + + 'way to change the right button or title of the current route', + } + }); }); }, _renderReplacePrevious: function() { - if (!this.props.topExampleRoute) { - // this is to avoid replacing the UIExplorerList at the top of the stack + if (!this.props.depth || this.props.depth < 2) { + // this is to avoid replacing the top of the stack return null; } return this._renderRow('Replace previous', () => { @@ -172,8 +153,8 @@ var NavigatorIOSExample = React.createClass({ }, _renderReplacePreviousAndPop: function() { - if (!this.props.topExampleRoute) { - // this is to avoid replacing the UIExplorerList at the top of the stack + if (!this.props.depth || this.props.depth < 2) { + // this is to avoid replacing the top of the stack return null; } return this._renderRow('Replace previous and pop', () => { @@ -204,7 +185,34 @@ var NavigatorIOSExample = React.createClass({ }, }); -var styles = StyleSheet.create({ +const NavigatorIOSExample = React.createClass({ + statics: { + title: '', + description: 'iOS navigation capabilities', + external: true, + }, + + render: function() { + const {onExampleExit} = this.props; + return ( + + ); + }, +}); + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, customWrapperStyle: { backgroundColor: '#bbdddd', }, @@ -227,7 +235,7 @@ var styles = StyleSheet.create({ }, line: { backgroundColor: '#bbbbbb', - height: 1 / PixelRatio.get(), + height: StyleSheet.hairlineWidth, }, row: { backgroundColor: 'white', @@ -236,7 +244,7 @@ var styles = StyleSheet.create({ paddingVertical: 15, }, separator: { - height: 1 / PixelRatio.get(), + height: StyleSheet.hairlineWidth, backgroundColor: '#bbbbbb', marginLeft: 15, }, diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js index 6ab1805dfee417..97af9e1e00bed4 100644 --- a/Examples/UIExplorer/NetInfoExample.js +++ b/Examples/UIExplorer/NetInfoExample.js @@ -15,83 +15,84 @@ */ 'use strict'; -var React = require('react-native'); -var { +const React = require('react-native'); +const { NetInfo, Text, - View + View, + TouchableWithoutFeedback, } = React; -var ReachabilitySubscription = React.createClass({ +const ConnectionInfoSubscription = React.createClass({ getInitialState() { return { - reachabilityHistory: [], + connectionInfoHistory: [], }; }, componentDidMount: function() { NetInfo.addEventListener( - 'change', - this._handleReachabilityChange + 'change', + this._handleConnectionInfoChange ); }, componentWillUnmount: function() { NetInfo.removeEventListener( - 'change', - this._handleReachabilityChange + 'change', + this._handleConnectionInfoChange ); }, - _handleReachabilityChange: function(reachability) { - var reachabilityHistory = this.state.reachabilityHistory.slice(); - reachabilityHistory.push(reachability); + _handleConnectionInfoChange: function(connectionInfo) { + const connectionInfoHistory = this.state.connectionInfoHistory.slice(); + connectionInfoHistory.push(connectionInfo); this.setState({ - reachabilityHistory, + connectionInfoHistory, }); }, render() { return ( - - {JSON.stringify(this.state.reachabilityHistory)} - + + {JSON.stringify(this.state.connectionInfoHistory)} + ); } }); -var ReachabilityCurrent = React.createClass({ +const ConnectionInfoCurrent = React.createClass({ getInitialState() { return { - reachability: null, + connectionInfo: null, }; }, componentDidMount: function() { NetInfo.addEventListener( - 'change', - this._handleReachabilityChange + 'change', + this._handleConnectionInfoChange ); NetInfo.fetch().done( - (reachability) => { this.setState({reachability}); } + (connectionInfo) => { this.setState({connectionInfo}); } ); }, componentWillUnmount: function() { NetInfo.removeEventListener( - 'change', - this._handleReachabilityChange + 'change', + this._handleConnectionInfoChange ); }, - _handleReachabilityChange: function(reachability) { + _handleConnectionInfoChange: function(connectionInfo) { this.setState({ - reachability, + connectionInfo, }); }, render() { return ( - - {this.state.reachability} - + + {this.state.connectionInfo} + ); } }); -var IsConnected = React.createClass({ +const IsConnected = React.createClass({ getInitialState() { return { isConnected: null, @@ -99,17 +100,17 @@ var IsConnected = React.createClass({ }, componentDidMount: function() { NetInfo.isConnected.addEventListener( - 'change', - this._handleConnectivityChange + 'change', + this._handleConnectivityChange ); NetInfo.isConnected.fetch().done( - (isConnected) => { this.setState({isConnected}); } + (isConnected) => { this.setState({isConnected}); } ); }, componentWillUnmount: function() { NetInfo.isConnected.removeEventListener( - 'change', - this._handleConnectivityChange + 'change', + this._handleConnectivityChange ); }, _handleConnectivityChange: function(isConnected) { @@ -119,9 +120,37 @@ var IsConnected = React.createClass({ }, render() { return ( - - {this.state.isConnected ? 'Online' : 'Offline'} - + + {this.state.isConnected ? 'Online' : 'Offline'} + + ); + } +}); + +const IsConnectionExpensive = React.createClass({ + getInitialState() { + return { + isConnectionExpensive: (null : ?boolean), + }; + }, + _checkIfExpensive() { + NetInfo.isConnectionExpensive().then( + isConnectionExpensive => { this.setState({isConnectionExpensive}); } + ); + }, + render() { + return ( + + + + Click to see if connection is expensive: + {this.state.isConnectionExpensive === true ? 'Expensive' : + this.state.isConnectionExpensive === false ? 'Not expensive' + : 'Unknown'} + + + + ); } }); @@ -135,13 +164,19 @@ exports.examples = [ render(): ReactElement { return ; } }, { - title: 'NetInfo.reachabilityIOS', - description: 'Asynchronously load and observe iOS reachability', - render(): ReactElement { return ; } + title: 'NetInfo.update', + description: 'Asynchronously load and observe connectionInfo', + render(): ReactElement { return ; } + }, + { + title: 'NetInfo.updateHistory', + description: 'Observed updates to connectionInfo', + render(): ReactElement { return ; } }, { - title: 'NetInfo.reachabilityIOS', - description: 'Observed updates to iOS reachability', - render(): ReactElement { return ; } + platform: 'android', + title: 'NetInfo.isConnectionExpensive (Android)', + description: 'Asynchronously check isConnectionExpensive', + render(): ReactElement { return ; } }, ]; diff --git a/Examples/UIExplorer/PanResponderExample.js b/Examples/UIExplorer/PanResponderExample.js index c2e6a667ce0da8..47833af3f5a8fe 100644 --- a/Examples/UIExplorer/PanResponderExample.js +++ b/Examples/UIExplorer/PanResponderExample.js @@ -11,7 +11,7 @@ * 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-weak + * @flow weak */ 'use strict'; @@ -79,7 +79,8 @@ var PanResponderExample = React.createClass({ }, _highlight: function() { - this.circle && this.circle.setNativeProps({ + const circle = this.circle; + circle && circle.setNativeProps({ style: { backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR) } @@ -87,7 +88,8 @@ var PanResponderExample = React.createClass({ }, _unHighlight: function() { - this.circle && this.circle.setNativeProps({ + const circle = this.circle; + circle && circle.setNativeProps({ style: { backgroundColor: processColor(CIRCLE_COLOR) } diff --git a/Examples/UIExplorer/PickerAndroidExample.js b/Examples/UIExplorer/PickerAndroidExample.js new file mode 100644 index 00000000000000..cdbc649626b4de --- /dev/null +++ b/Examples/UIExplorer/PickerAndroidExample.js @@ -0,0 +1,138 @@ +/** + * 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 + */ +'use strict'; + +const React = require('react-native'); +const StyleSheet = require('StyleSheet'); +const UIExplorerBlock = require('UIExplorerBlock'); +const UIExplorerPage = require('UIExplorerPage'); + +const { + Picker, + Text, + TouchableWithoutFeedback, +} = React; +const Item = Picker.Item; + +const PickerExample = React.createClass({ + + statics: { + title: '', + description: 'Provides multiple options to choose from, using either a dropdown menu or a dialog.', + }, + + getInitialState: function() { + return { + selected1: 'key1', + selected2: 'key1', + selected3: 'key1', + color: 'red', + mode: Picker.MODE_DIALOG, + }; + }, + + render: function() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cannot change the value of this picker because it doesn't update selectedValue. + + + + + + + + + + + + + + + + ); + }, + + changeMode: function() { + const newMode = this.state.mode === Picker.MODE_DIALOG + ? Picker.MODE_DROPDOWN + : Picker.MODE_DIALOG; + this.setState({mode: newMode}); + }, + + onValueChange: function(key: string, value: string) { + const newState = {}; + newState[key] = value; + this.setState(newState); + }, +}); + +var styles = StyleSheet.create({ + picker: { + width: 100, + }, +}); + +module.exports = PickerExample; diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/PickerIOSExample.js index 31c81ccccdad5c..51b8f9f30e762e 100644 --- a/Examples/UIExplorer/PickerIOSExample.js +++ b/Examples/UIExplorer/PickerIOSExample.js @@ -87,24 +87,21 @@ var PickerExample = React.createClass({ key={carMake} value={carMake} label={CAR_MAKES_AND_MODELS[carMake].name} - /> - ) - )} + /> + ))} Please choose a model of {make.name}: this.setState({modelIndex})}> - {CAR_MAKES_AND_MODELS[this.state.carMake].models.map( - (modelName, modelIndex) => ( - - )) - } + {CAR_MAKES_AND_MODELS[this.state.carMake].models.map((modelName, modelIndex) => ( + + ))} You selected: {selectionString} @@ -112,6 +109,34 @@ var PickerExample = React.createClass({ }, }); +var PickerStyleExample = React.createClass({ + getInitialState: function() { + return { + carMake: 'cadillac', + modelIndex: 0, + }; + }, + + render: function() { + var make = CAR_MAKES_AND_MODELS[this.state.carMake]; + var selectionString = make.name + ' ' + make.models[this.state.modelIndex]; + return ( + this.setState({carMake, modelIndex: 0})}> + {Object.keys(CAR_MAKES_AND_MODELS).map((carMake) => ( + + ))} + + ); + }, +}); + exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Render lists of selectable options with UIPickerView.'; @@ -121,4 +146,10 @@ exports.examples = [ render: function(): ReactElement { return ; }, +}, +{ + title: ' with custom styling', + render: function(): ReactElement { + return ; + }, }]; diff --git a/Examples/UIExplorer/ProgressBarAndroidExample.android.js b/Examples/UIExplorer/ProgressBarAndroidExample.android.js index b58cd73ca586f7..19b182bc769289 100644 --- a/Examples/UIExplorer/ProgressBarAndroidExample.android.js +++ b/Examples/UIExplorer/ProgressBarAndroidExample.android.js @@ -60,6 +60,10 @@ var ProgressBarAndroidExample = React.createClass({ + + + + diff --git a/Examples/UIExplorer/PushNotificationIOSExample.js b/Examples/UIExplorer/PushNotificationIOSExample.js index bd6109f1f09bd4..9fcf8cd1c343ed 100644 --- a/Examples/UIExplorer/PushNotificationIOSExample.js +++ b/Examples/UIExplorer/PushNotificationIOSExample.js @@ -84,6 +84,8 @@ class NotificationExample extends React.Component { } class NotificationPermissionExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = {permissions: null}; @@ -126,7 +128,7 @@ exports.description = 'Apple PushNotification and badge value'; exports.examples = [ { title: 'Badge Number', - render(): React.Component { + render(): ReactElement { PushNotificationIOS.requestPermissions(); return ( @@ -145,13 +147,13 @@ exports.examples = [ }, { title: 'Push Notifications', - render(): React.Component { + render(): ReactElement { return ; } }, { title: 'Notifications Permissions', - render(): React.Component { + render(): ReactElement { return ; } }]; diff --git a/Examples/UIExplorer/RCTRootViewIOSExample.js b/Examples/UIExplorer/RCTRootViewIOSExample.js index 174dcf2bb947c2..5aa56cf881e44d 100644 --- a/Examples/UIExplorer/RCTRootViewIOSExample.js +++ b/Examples/UIExplorer/RCTRootViewIOSExample.js @@ -15,19 +15,20 @@ */ 'use strict'; -var React = require('react-native'); -var { + +const React = require('react-native'); +const { StyleSheet, Text, View, } = React; -var requireNativeComponent = require('requireNativeComponent'); -var UpdatePropertiesExampleView = requireNativeComponent('UpdatePropertiesExampleView'); -var FlexibleSizeExampleView = requireNativeComponent('FlexibleSizeExampleView'); +const requireNativeComponent = require('requireNativeComponent'); class AppPropertiesUpdateExample extends React.Component { render() { + // Do not require this unless we are actually rendering. + const UpdatePropertiesExampleView = requireNativeComponent('UpdatePropertiesExampleView'); return ( @@ -45,6 +46,8 @@ class AppPropertiesUpdateExample extends React.Component { class RootViewSizeFlexibilityExample extends React.Component { render() { + // Do not require this unless we are actually rendering. + const FlexibleSizeExampleView = requireNativeComponent('FlexibleSizeExampleView'); return ( @@ -60,7 +63,7 @@ class RootViewSizeFlexibilityExample extends React.Component { } } -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5FCFF', @@ -79,7 +82,7 @@ exports.description = 'Examples that show useful methods when embedding React Na exports.examples = [ { title: 'Updating app properties in runtime', - render(): React.Component { + render(): ReactElement { return ( ); @@ -87,7 +90,7 @@ exports.examples = [ }, { title: 'RCTRootView\'s size flexibility', - render(): React.Component { + render(): ReactElement { return ( ); diff --git a/Examples/UIExplorer/RefreshControlExample.js b/Examples/UIExplorer/RefreshControlExample.js new file mode 100644 index 00000000000000..f56c9fed51884b --- /dev/null +++ b/Examples/UIExplorer/RefreshControlExample.js @@ -0,0 +1,125 @@ +/** +* 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. +* +*/ +'use strict'; + +const React = require('react-native'); +const { + ScrollView, + StyleSheet, + RefreshControl, + Text, + TouchableWithoutFeedback, + View, +} = React; + +const styles = StyleSheet.create({ + row: { + borderColor: 'grey', + borderWidth: 1, + padding: 20, + backgroundColor: '#3a5795', + margin: 5, + }, + text: { + alignSelf: 'center', + color: '#fff', + }, + scrollview: { + flex: 1, + }, +}); + +const Row = React.createClass({ + _onClick: function() { + this.props.onClick(this.props.data); + }, + render: function() { + return ( + + + + {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'} + + + + ); + }, +}); + +const RefreshControlExample = React.createClass({ + statics: { + title: '', + description: 'Adds pull-to-refresh support to a scrollview.' + }, + + getInitialState() { + return { + isRefreshing: false, + loaded: 0, + rowData: Array.from(new Array(20)).map( + (val, i) => ({text: 'Initial row ' + i, clicks: 0})), + }; + }, + + _onClick(row) { + row.clicks++; + this.setState({ + rowData: this.state.rowData, + }); + }, + + render() { + const rows = this.state.rowData.map((row, ii) => { + return ; + }); + return ( + + }> + {rows} + + ); + }, + + _onRefresh() { + this.setState({isRefreshing: true}); + setTimeout(() => { + // prepend 10 items + const rowData = Array.from(new Array(10)) + .map((val, i) => ({ + text: 'Loaded row ' + (+this.state.loaded + i), + clicks: 0, + })) + .concat(this.state.rowData); + + this.setState({ + loaded: this.state.loaded + 10, + isRefreshing: false, + rowData: rowData, + }); + }, 5000); + }, +}); + +module.exports = RefreshControlExample; diff --git a/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js new file mode 100644 index 00000000000000..9c48357033fb99 --- /dev/null +++ b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js @@ -0,0 +1,77 @@ +/** + * 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 + */ +'use strict'; + +const React = require('react-native'); +const { + StyleSheet, + Text, + TouchableHighlight, + View, +} = React; + +class RootViewSizeFlexibilityExampleApp extends React.Component { + state: any; + + constructor(props: {toggled: boolean}) { + super(props); + this.state = { toggled: false }; + } + + _onPressButton() { + this.setState({ toggled: !this.state.toggled }); + } + + render() { + const viewStyle = this.state.toggled ? styles.bigContainer : styles.smallContainer; + + return ( + + + + + React Native Button + + + + + ); + } + +} + +const styles = StyleSheet.create({ + bigContainer: { + flex: 1, + height: 60, + backgroundColor: 'gray', + }, + smallContainer: { + flex: 1, + height: 40, + backgroundColor: 'gray', + }, + center: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + whiteText: { + color: 'white', + } +}); + +module.exports = RootViewSizeFlexibilityExampleApp; diff --git a/Examples/UIExplorer/ScrollViewExample.js b/Examples/UIExplorer/ScrollViewExample.js index 53d19148b67f50..469c9a63c11377 100644 --- a/Examples/UIExplorer/ScrollViewExample.js +++ b/Examples/UIExplorer/ScrollViewExample.js @@ -19,6 +19,8 @@ var React = require('react-native'); var { ScrollView, StyleSheet, + Text, + TouchableOpacity, View, Image } = React; @@ -31,27 +33,45 @@ exports.examples = [ title: '', description: 'To make content scrollable, wrap it within a component', render: function() { + var _scrollView: ScrollView; return ( - { console.log('onScroll!'); }} - scrollEventThrottle={200} - style={styles.scrollView}> - {THUMBS.map(createThumbRow)} - + + { _scrollView = scrollView; }} + automaticallyAdjustContentInsets={false} + onScroll={() => { console.log('onScroll!'); }} + scrollEventThrottle={200} + style={styles.scrollView}> + {THUMBS.map(createThumbRow)} + + { _scrollView.scrollTo({y: 0}); }}> + Scroll to top + + ); } }, { title: ' (horizontal = true)', description: 'You can display \'s child components horizontally rather than vertically', render: function() { + var _scrollView: ScrollView; return ( - - {THUMBS.map(createThumbRow)} - + + { _scrollView = scrollView; }} + automaticallyAdjustContentInsets={false} + horizontal={true} + style={[styles.scrollView, styles.horizontalScrollView]}> + {THUMBS.map(createThumbRow)} + + { _scrollView.scrollTo({x: 0}); }}> + Scroll to start + + ); } }]; diff --git a/Examples/UIExplorer/SetPropertiesExampleApp.js b/Examples/UIExplorer/SetPropertiesExampleApp.js index 748bbc3ccb4a6d..f5dc9c8eab39b0 100644 --- a/Examples/UIExplorer/SetPropertiesExampleApp.js +++ b/Examples/UIExplorer/SetPropertiesExampleApp.js @@ -13,17 +13,18 @@ * * @flow */ - 'use strict'; -var React = require('React'); -var Text = require('Text'); -var View = require('View'); +const React = require('react-native'); +const { + Text, + View, +} = React; -var SetPropertiesExampleApp = React.createClass({ +class SetPropertiesExampleApp extends React.Component { - render: function() { - var wrapperStyle = { + render() { + const wrapperStyle = { backgroundColor: this.props.color, flex: 1, alignItems: 'center', @@ -37,7 +38,8 @@ var SetPropertiesExampleApp = React.createClass({ ); - }, -}); + } + +} module.exports = SetPropertiesExampleApp; diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/SliderIOSExample.js index 245fe10ad57e7b..baee69ae8f021f 100644 --- a/Examples/UIExplorer/SliderIOSExample.js +++ b/Examples/UIExplorer/SliderIOSExample.js @@ -83,5 +83,39 @@ exports.examples = [ render(): ReactElement { return ; } - } + }, + { + title: 'Custom min/max track tint color', + render(): ReactElement { + return ( + + ); + } + }, + { + title: 'Custom thumb image', + render(): ReactElement { + return ; + } + }, + { + title: 'Custom track image', + render(): ReactElement { + return ; + } + }, + { + title: 'Custom min/max track image', + render(): ReactElement { + return ( + + ); + } + }, ]; diff --git a/Examples/UIExplorer/SnapshotExample.js b/Examples/UIExplorer/SnapshotExample.js new file mode 100644 index 00000000000000..d843acee80d3fb --- /dev/null +++ b/Examples/UIExplorer/SnapshotExample.js @@ -0,0 +1,73 @@ +/** + * 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 + */ +'use strict'; + +var React = require('react-native'); +var { + Image, + StyleSheet, + Text, + UIManager, + View, +} = React; + +var ScreenshotExample = React.createClass({ + getInitialState() { + return { + uri: undefined, + }; + }, + + render() { + return ( + + + Click to take a screenshot + + + + ); + }, + + takeScreenshot() { + UIManager + .takeSnapshot('window', {format: 'jpeg', quality: 0.8}) // See UIManager.js for options + .then((uri) => this.setState({uri})) + .catch((error) => alert(error)); + } +}); + +var style = StyleSheet.create({ + button: { + marginBottom: 10, + fontWeight: '500', + }, + image: { + flex: 1, + height: 300, + resizeMode: 'contain', + backgroundColor: 'black', + }, +}); + +exports.title = 'Snapshot / Screenshot'; +exports.description = 'API to capture images from the screen.'; +exports.examples = [ + { + title: 'Take screenshot', + render(): ReactElement { return ; } + }, +]; diff --git a/Examples/UIExplorer/StatusBarExample.js b/Examples/UIExplorer/StatusBarExample.js new file mode 100644 index 00000000000000..e33dbd71a98b39 --- /dev/null +++ b/Examples/UIExplorer/StatusBarExample.js @@ -0,0 +1,311 @@ +/** + * 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 + */ +'use strict'; + +const React = require('react-native'); +const { + StyleSheet, + View, + Text, + TouchableHighlight, + StatusBar, +} = React; + +type BarStyle = 'default' | 'light-content'; +type ShowHideTransition = 'fade' | 'slide'; + +type State = { + animated: boolean, + backgroundColor: string, + hidden?: boolean, + showHideTransition: ShowHideTransition, + translucent?: boolean, + barStyle?: BarStyle, + networkActivityIndicatorVisible?: boolean +}; + +exports.framework = 'React'; +exports.title = ''; +exports.description = 'Component for controlling the status bar'; + +const colors = [ + '#ff0000', + '#00ff00', + '#0000ff', +]; + +const barStyles = [ + 'default', + 'light-content', +]; + +const showHideTransitions = [ + 'fade', + 'slide', +]; + +function getValue(values: Array, index: number): any { + return values[index % values.length]; +} + +const StatusBarExample = React.createClass({ + getInitialState(): State { + return { + animated: true, + backgroundColor: getValue(colors, 0), + showHideTransition: getValue(showHideTransitions, 0), + }; + }, + + _colorIndex: 0, + _barStyleIndex: 0, + _showHideTransitionIndex: 0, + + render() { + return ( + + + ); + }, +}); + +const StatusBarStaticExample = React.createClass({ + _colorIndex: 0, + _barStyleIndex: 0, + _showHideTransitionIndex: 0, + + getInitialState() { + return { + backgroundColor: getValue(colors, 0), + barStyle: getValue(barStyles, 0), + hidden: false, + networkActivityIndicatorVisible: false, + translucent: false, + }; + }, + + render() { + return ( + + + { + const hidden = !this.state.hidden; + StatusBar.setHidden(hidden, 'slide'); + this.setState({hidden}); + }}> + + hidden: {this.state.hidden ? 'true' : 'false'} + + + + iOS + + { + this._barStyleIndex++; + const barStyle = getValue(barStyles, this._barStyleIndex); + StatusBar.setBarStyle(barStyle, true); + this.setState({barStyle}); + }}> + + style: '{getValue(barStyles, this._barStyleIndex)}' + + + + + { + const networkActivityIndicatorVisible = !this.state.networkActivityIndicatorVisible; + StatusBar.setNetworkActivityIndicatorVisible(networkActivityIndicatorVisible); + this.setState({networkActivityIndicatorVisible}); + }}> + + + networkActivityIndicatorVisible: + {this.state.networkActivityIndicatorVisible ? 'true' : 'false'} + + + + + Android + + { + this._colorIndex++; + const backgroundColor = getValue(colors, this._colorIndex); + StatusBar.setBackgroundColor(backgroundColor, true); + this.setState({backgroundColor}); + }}> + + backgroundColor: '{getValue(colors, this._colorIndex)}' + + + + + { + const translucent = !this.state.translucent; + const backgroundColor = !this.state.translucent ? 'rgba(0, 0, 0, 0.4)' : 'black'; + StatusBar.setTranslucent(translucent); + StatusBar.setBackgroundColor(backgroundColor, true); + this.setState({ + translucent, + backgroundColor, + }); + }}> + + translucent: {this.state.translucent ? 'true' : 'false'} + + + + + ); + }, +}); + +exports.examples = [{ + title: 'StatusBar', + render() { + return ; + }, +}, { + title: 'StatusBar static API', + render() { + return ; + }, +}]; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + borderRadius: 5, + backgroundColor: '#eeeeee', + padding: 10, + }, + title: { + marginTop: 16, + marginBottom: 8, + fontWeight: 'bold', + } +}); diff --git a/Examples/UIExplorer/StatusBarIOSExample.js b/Examples/UIExplorer/StatusBarIOSExample.js index 28d5e2a7c07692..a7765d8563ac47 100644 --- a/Examples/UIExplorer/StatusBarIOSExample.js +++ b/Examples/UIExplorer/StatusBarIOSExample.js @@ -33,7 +33,7 @@ exports.examples = [{ return ( {['default', 'light-content'].map((style) => - StatusBarIOS.setStyle(style)}> setStyle('{style}') @@ -49,7 +49,7 @@ exports.examples = [{ return ( {['default', 'light-content'].map((style) => - StatusBarIOS.setStyle(style, true)}> setStyle('{style}', true) @@ -65,7 +65,7 @@ exports.examples = [{ return ( {['none', 'fade', 'slide'].map((animation) => - + StatusBarIOS.setHidden(true, animation)}> diff --git a/Examples/UIExplorer/SwitchAndroidExample.android.js b/Examples/UIExplorer/SwitchAndroidExample.android.js deleted file mode 100644 index 7b6f38e1602cd5..00000000000000 --- a/Examples/UIExplorer/SwitchAndroidExample.android.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - */ -'use strict'; - -var React = require('React'); - -var SwitchAndroid = require('SwitchAndroid'); -var Text = require('Text'); -var UIExplorerBlock = require('UIExplorerBlock'); -var UIExplorerPage = require('UIExplorerPage'); - -var SwitchAndroidExample = React.createClass({ - statics: { - title: '', - description: 'Standard Android two-state toggle component.' - }, - - getInitialState : function() { - return { - trueSwitchIsOn: true, - falseSwitchIsOn: false, - colorTrueSwitchIsOn: true, - colorFalseSwitchIsOn: false, - eventSwitchIsOn: false, - }; - }, - - render: function() { - return ( - - - this.setState({falseSwitchIsOn: value})} - style={{marginBottom: 10}} - value={this.state.falseSwitchIsOn} /> - this.setState({trueSwitchIsOn: value})} - value={this.state.trueSwitchIsOn} /> - - - - - - - this.setState({eventSwitchIsOn: value})} - style={{marginBottom: 10}} - value={this.state.eventSwitchIsOn} /> - this.setState({eventSwitchIsOn: value})} - style={{marginBottom: 10}} - value={this.state.eventSwitchIsOn} /> - {this.state.eventSwitchIsOn ? 'On' : 'Off'} - - - - - - - ); - } -}); - -module.exports = SwitchAndroidExample; diff --git a/Examples/UIExplorer/SwitchIOSExample.js b/Examples/UIExplorer/SwitchExample.js similarity index 91% rename from Examples/UIExplorer/SwitchIOSExample.js rename to Examples/UIExplorer/SwitchExample.js index feedfbff3ad2a4..5ab41fe15a2f0e 100644 --- a/Examples/UIExplorer/SwitchIOSExample.js +++ b/Examples/UIExplorer/SwitchExample.js @@ -17,7 +17,7 @@ var React = require('react-native'); var { - SwitchIOS, + Switch, Text, View } = React; @@ -32,11 +32,11 @@ var BasicSwitchExample = React.createClass({ render() { return ( - this.setState({falseSwitchIsOn: value})} style={{marginBottom: 10}} value={this.state.falseSwitchIsOn} /> - this.setState({trueSwitchIsOn: value})} value={this.state.trueSwitchIsOn} /> @@ -48,11 +48,11 @@ var DisabledSwitchExample = React.createClass({ render() { return ( - - @@ -70,14 +70,14 @@ var ColorSwitchExample = React.createClass({ render() { return ( - this.setState({colorFalseSwitchIsOn: value})} onTintColor="#00ff00" style={{marginBottom: 10}} thumbTintColor="#0000ff" tintColor="#ff0000" value={this.state.colorFalseSwitchIsOn} /> - this.setState({colorTrueSwitchIsOn: value})} onTintColor="#00ff00" thumbTintColor="#0000ff" @@ -99,22 +99,22 @@ var EventSwitchExample = React.createClass({ return ( - this.setState({eventSwitchIsOn: value})} style={{marginBottom: 10}} value={this.state.eventSwitchIsOn} /> - this.setState({eventSwitchIsOn: value})} style={{marginBottom: 10}} value={this.state.eventSwitchIsOn} /> {this.state.eventSwitchIsOn ? 'On' : 'Off'} - this.setState({eventSwitchRegressionIsOn: value})} style={{marginBottom: 10}} value={this.state.eventSwitchRegressionIsOn} /> - this.setState({eventSwitchRegressionIsOn: value})} style={{marginBottom: 10}} value={this.state.eventSwitchRegressionIsOn} /> @@ -125,10 +125,7 @@ var EventSwitchExample = React.createClass({ } }); -exports.title = ''; -exports.displayName = 'SwitchExample'; -exports.description = 'Native boolean input'; -exports.examples = [ +var examples = [ { title: 'Switches can be set to true or false', render(): ReactElement { return ; } @@ -137,16 +134,24 @@ exports.examples = [ title: 'Switches can be disabled', render(): ReactElement { return ; } }, - { - title: 'Custom colors can be provided', - render(): ReactElement { return ; } - }, { title: 'Change events can be detected', render(): ReactElement { return ; } }, { title: 'Switches are controlled components', - render(): ReactElement { return ; } + render(): ReactElement { return ; } } ]; + +if (React.Platform.OS === 'ios') { + examples.push({ + title: 'Custom colors can be provided', + render(): ReactElement { return ; } + }); +} + +exports.title = ''; +exports.displayName = 'SwitchExample'; +exports.description = 'Native boolean input'; +exports.examples = examples; diff --git a/Examples/UIExplorer/TextExample.android.js b/Examples/UIExplorer/TextExample.android.js index 18defae2702703..7c0c7868f4c435 100644 --- a/Examples/UIExplorer/TextExample.android.js +++ b/Examples/UIExplorer/TextExample.android.js @@ -365,6 +365,11 @@ var TextExample = React.createClass({ This text contains an inline image . Neat, huh? + + + Demo text shadow + + ); } diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index 57a2f032cdbf06..3de37c42349d37 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -419,7 +419,18 @@ exports.examples = [ return ( - This text contains an inline image . Neat, huh? + This text contains an inline image . Neat, huh? + + + ); + }, +}, { + title: 'Text shadow', + render: function() { + return ( + + + Demo text shadow ); diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js index 31156b50d5f861..1a436a91236d9a 100644 --- a/Examples/UIExplorer/TextInputExample.android.js +++ b/Examples/UIExplorer/TextInputExample.android.js @@ -72,6 +72,29 @@ var TextEventsExample = React.createClass({ } }); +class AutoExpandingTextInput extends React.Component { + constructor(props) { + super(props); + this.state = {text: '', height: 0}; + } + render() { + return ( + { + this.setState({ + text: event.nativeEvent.text, + height: event.nativeEvent.contentSize.height, + }); + }} + style={[styles.default, {height: Math.max(35, this.state.height)}]} + value={this.state.text} + /> + ); + } +} + class RewriteExample extends React.Component { constructor(props) { super(props); @@ -155,6 +178,60 @@ class TokenizedTextExample extends React.Component { } } +var BlurOnSubmitExample = React.createClass({ + focusNextField(nextField) { + this.refs[nextField].focus(); + }, + + render: function() { + return ( + + this.focusNextField('2')} + /> + this.focusNextField('3')} + /> + this.focusNextField('4')} + /> + this.focusNextField('5')} + /> + + + ); + } +}); + var styles = StyleSheet.create({ multiline: { height: 60, @@ -185,7 +262,13 @@ exports.examples = [ { title: 'Auto-focus', render: function() { - return ; + return ( + + ); } }, { @@ -242,6 +325,7 @@ exports.examples = [ 'default', 'email-address', 'numeric', + 'phone-pad', ]; var examples = keyboardTypes.map((type) => { return ( @@ -256,6 +340,10 @@ exports.examples = [ return {examples}; } }, + { + title: 'Blur on submit', + render: function(): ReactElement { return ; }, + }, { title: 'Event handling', render: function(): ReactElement { return ; }, @@ -298,6 +386,11 @@ exports.examples = [ Darker backgroundColor + + ); } @@ -347,25 +440,19 @@ exports.examples = [ placeholder="multiline, aligned top-left" placeholderTextColor="red" multiline={true} - textAlign="start" - textAlignVertical="top" - style={styles.multiline} + style={[styles.multiline, {textAlign: "left", textAlignVertical: "top"}]} /> + style={[styles.multiline, {color: 'blue'}, {textAlign: "right", textAlignVertical: "bottom"}]}> multiline with children, aligned bottom-right @@ -390,6 +477,20 @@ exports.examples = [ ); } }, + { + title: 'Auto-expanding', + render: function() { + return ( + + + + ); + } + }, { title: 'Attributed text', render: function() { diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js index 5f13d822fd5448..239197dbacbb9e 100644 --- a/Examples/UIExplorer/TextInputExample.ios.js +++ b/Examples/UIExplorer/TextInputExample.ios.js @@ -96,7 +96,34 @@ var TextEventsExample = React.createClass({ } }); +class AutoExpandingTextInput extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = {text: '', height: 0}; + } + render() { + return ( + { + this.setState({ + text: event.nativeEvent.text, + height: event.nativeEvent.contentSize.height, + }); + }} + style={[styles.default, {height: Math.max(35, this.state.height)}]} + value={this.state.text} + /> + ); + } +} + class RewriteExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = {text: ''}; @@ -125,7 +152,32 @@ class RewriteExample extends React.Component { } } +class RewriteExampleInvalidCharacters extends React.Component { + state: any; + + constructor(props) { + super(props); + this.state = {text: ''}; + } + render() { + return ( + + { + this.setState({text: text.replace(/\s/g, '')}); + }} + style={styles.default} + value={this.state.text} + /> + + ); + } +} + class TokenizedTextExample extends React.Component { + state: any; + constructor(props) { super(props); this.state = {text: 'Hello #World'}; @@ -158,7 +210,7 @@ class TokenizedTextExample extends React.Component { //highlight hashtags parts = parts.map((text) => { if (/^#/.test(text)) { - return {text}; + return {text}; } else { return text; } @@ -181,52 +233,52 @@ class TokenizedTextExample extends React.Component { var BlurOnSubmitExample = React.createClass({ focusNextField(nextField) { - this.refs[nextField].focus() + this.refs[nextField].focus(); }, render: function() { return ( this.focusNextField('2')} /> this.focusNextField('3')} /> this.focusNextField('4')} /> this.focusNextField('5')} /> ); @@ -304,7 +356,13 @@ exports.examples = [ { title: 'Auto-focus', render: function() { - return ; + return ( + + ); } }, { @@ -313,6 +371,12 @@ exports.examples = [ return ; } }, + { + title: 'Live Re-Write (no spaces allowed)', + render: function() { + return ; + } + }, { title: 'Auto-capitalize', render: function() { @@ -486,6 +550,25 @@ exports.examples = [ ); } }, + { + title: 'Colored highlight/cursor for text input', + render: function() { + return ( + + + + + ); + } + }, { title: 'Clear button mode', render: function () { @@ -544,6 +627,27 @@ exports.examples = [ ); } }, + { + title: 'Blur on submit', + render: function(): ReactElement { return ; }, + }, + { + title: 'Multiline blur on submit', + render: function() { + return ( + + alert(event.nativeEvent.text)} + /> + + ); + } + }, { title: 'Multiline', render: function() { @@ -583,13 +687,23 @@ exports.examples = [ } }, { - title: 'Attributed text', + title: 'Auto-expanding', render: function() { - return ; + return ( + + + + ); } }, { - title: 'Blur on submit', - render: function(): ReactElement { return ; }, + title: 'Attributed text', + render: function() { + return ; + } }, ]; diff --git a/Examples/UIExplorer/TimePickerAndroidExample.js b/Examples/UIExplorer/TimePickerAndroidExample.js new file mode 100644 index 00000000000000..06733fc35c881e --- /dev/null +++ b/Examples/UIExplorer/TimePickerAndroidExample.js @@ -0,0 +1,112 @@ +/** + * 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. + */ +'use strict'; + +var React = require('react-native'); +var { + TimePickerAndroid, + StyleSheet, + Text, + TouchableWithoutFeedback, +} = React; + +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); + +var TimePickerAndroidExample = React.createClass({ + + statics: { + title: 'TimePickerAndroid', + description: 'Standard Android time picker dialog', + }, + + getInitialState() { + // *Text, *Hour and *Minute are set by successCallback -- this updates the text with the time + // picked by the user and makes it so the next time they open it the hour and minute they picked + // before is displayed. + return { + isoFormatText: 'pick a time (24-hour format)', + presetHour: 4, + presetMinute: 4, + presetText: 'pick a time, default: 4:04AM', + simpleText: 'pick a time', + }; + }, + + async showPicker(stateKey, options) { + try { + const {action, minute, hour} = await TimePickerAndroid.open(options); + var newState = {}; + if (action === TimePickerAndroid.timeSetAction) { + newState[stateKey + 'Text'] = _formatTime(hour, minute); + newState[stateKey + 'Hour'] = hour; + newState[stateKey + 'Minute'] = minute; + } else if (action === TimePickerAndroid.dismissedAction) { + newState[stateKey + 'Text'] = 'dismissed'; + } + this.setState(newState); + } catch ({code, message}) { + console.warn(`Error in example '${stateKey}': `, message); + } + }, + + render() { + return ( + + + + {this.state.simpleText} + + + + + {this.state.presetText} + + + + + + {this.state.isoFormatText} + + + + ); + }, +}); + +/** + * Returns e.g. '3:05'. + */ +function _formatTime(hour, minute) { + return hour + ':' + (minute < 10 ? '0' + minute : minute); +} + +var styles = StyleSheet.create({ + text: { + color: 'black', + }, +}); + +module.exports = TimePickerAndroidExample; + diff --git a/Examples/UIExplorer/TimerExample.js b/Examples/UIExplorer/TimerExample.js index 55272a8220af29..51a8f1af557ce6 100644 --- a/Examples/UIExplorer/TimerExample.js +++ b/Examples/UIExplorer/TimerExample.js @@ -171,9 +171,20 @@ exports.examples = [ } return ( - {timer} + {this.state.showTimer && this._renderTimer()} - {toggleText} + {this.state.showTimer ? 'Unmount timer' : 'Mount new timer'} + + + ); + }, + + _renderTimer: function() { + return ( + + + this.refs.interval.clear() }> + Clear interval ); diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index f8ced267db8afa..5b28f2e134d40c 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -23,6 +23,9 @@ var { Text, TouchableHighlight, TouchableOpacity, + UIManager, + Platform, + TouchableNativeFeedback, View, } = React; @@ -54,7 +57,7 @@ exports.examples = [ activeOpacity={1} animationVelocity={0} underlayColor="rgb(210, 230, 255)" - onPress={() => console.log('custom THW text - hightlight')}> + onPress={() => console.log('custom THW text - highlight')}> Tap Here For Custom Highlight! @@ -85,7 +88,28 @@ exports.examples = [ render: function(): ReactElement { return ; }, -}]; +}, { + title: '3D Touch / Force Touch', + description: 'iPhone 6s and 6s plus support 3D touch, which adds a force property to touches', + render: function(): ReactElement { + return ; + }, + platform: 'ios', +}, { + title: 'Touchable Hit Slop', + description: ' components accept hitSlop prop which extends the touch area ' + + 'without changing the view bounds.', + render: function(): ReactElement { + return ; + }, + }, { + title: 'Disabled Touchable*', + description: ' components accept disabled prop which prevents ' + + 'any interaction with component', + render: function(): ReactElement { + return ; + }, + }]; var TextOnPressBox = React.createClass({ getInitialState: function() { @@ -133,18 +157,21 @@ var TouchableFeedbackEvents = React.createClass({ return ( - this._appendEvent('press')} - onPressIn={() => this._appendEvent('pressIn')} - onPressOut={() => this._appendEvent('pressOut')} - onLongPress={() => this._appendEvent('longPress')}> - - Press Me - - - + this._appendEvent('press')} + onPressIn={() => this._appendEvent('pressIn')} + onPressOut={() => this._appendEvent('pressOut')} + onLongPress={() => this._appendEvent('longPress')}> + + Press Me + + + {this.state.eventLog.map((e, ii) => {e})} @@ -169,21 +196,21 @@ var TouchableDelayEvents = React.createClass({ return ( - this._appendEvent('press')} - delayPressIn={400} - onPressIn={() => this._appendEvent('pressIn - 400ms delay')} - delayPressOut={1000} - onPressOut={() => this._appendEvent('pressOut - 1000ms delay')} - delayLongPress={800} - onLongPress={() => this._appendEvent('longPress - 800ms delay')}> - - Press Me - - - + this._appendEvent('press')} + delayPressIn={400} + onPressIn={() => this._appendEvent('pressIn - 400ms delay')} + delayPressOut={1000} + onPressOut={() => this._appendEvent('pressOut - 1000ms delay')} + delayLongPress={800} + onLongPress={() => this._appendEvent('longPress - 800ms delay')}> + + Press Me + + + {this.state.eventLog.map((e, ii) => {e})} @@ -198,6 +225,148 @@ var TouchableDelayEvents = React.createClass({ }, }); +var ForceTouchExample = React.createClass({ + getInitialState: function() { + return { + force: 0, + }; + }, + _renderConsoleText: function() { + return View.forceTouchAvailable ? + 'Force: ' + this.state.force.toFixed(3) : + '3D Touch is not available on this device'; + }, + render: function() { + return ( + + + {this._renderConsoleText()} + + + true} + onResponderMove={(event) => this.setState({force: event.nativeEvent.force})} + onResponderRelease={(event) => this.setState({force: 0})}> + + Press Me + + + + + ); + }, +}); + +var TouchableHitSlop = React.createClass({ + getInitialState: function() { + return { + timesPressed: 0, + }; + }, + onPress: function() { + this.setState({ + timesPressed: this.state.timesPressed + 1, + }); + }, + render: function() { + var log = ''; + if (this.state.timesPressed > 1) { + log = this.state.timesPressed + 'x onPress'; + } else if (this.state.timesPressed > 0) { + log = 'onPress'; + } + + return ( + + + + + Press Outside This View + + + + + + {log} + + + + ); + } +}); + +var TouchableDisabled = React.createClass({ + render: function() { + return ( + + + Disabled TouchableOpacity + + + + Enabled TouchableOpacity + + + console.log('custom THW text - highlight')}> + + Disabled TouchableHighlight + + + + console.log('custom THW text - highlight')}> + + Disabled TouchableHighlight + + + + {Platform.OS === 'android' && + console.log('custom TNF has been clicked')} + background={TouchableNativeFeedback.SelectableBackground()}> + + + Enabled TouchableNativeFeedback + + + + } + + {Platform.OS === 'android' && + console.log('custom TNF has been clicked')} + background={TouchableNativeFeedback.SelectableBackground()}> + + + Disabled TouchableNativeFeedback + + + + } + + ); + } +}); + var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'}; var styles = StyleSheet.create({ @@ -216,9 +385,23 @@ var styles = StyleSheet.create({ text: { fontSize: 16, }, + block: { + padding: 10, + }, button: { color: '#007AFF', }, + disabledButton: { + color: '#007AFF', + opacity: 0.5, + }, + nativeFeedbackButton: { + textAlign: 'center', + margin: 10, + }, + hitSlopButton: { + color: 'white', + }, wrapper: { borderRadius: 8, }, @@ -226,10 +409,14 @@ var styles = StyleSheet.create({ borderRadius: 8, padding: 6, }, + hitSlopWrapper: { + backgroundColor: 'red', + marginVertical: 30, + }, logBox: { padding: 20, margin: 10, - borderWidth: 1 / PixelRatio.get(), + borderWidth: StyleSheet.hairlineWidth, borderColor: '#f0f0f0', backgroundColor: '#f9f9f9', }, @@ -237,9 +424,17 @@ var styles = StyleSheet.create({ padding: 10, margin: 10, height: 120, - borderWidth: 1 / PixelRatio.get(), + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#f0f0f0', + backgroundColor: '#f9f9f9', + }, + forceTouchBox: { + padding: 10, + margin: 10, + borderWidth: StyleSheet.hairlineWidth, borderColor: '#f0f0f0', backgroundColor: '#f9f9f9', + alignItems: 'center', }, textBlock: { fontWeight: '500', diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/TransformExample.js index 7da9eb5ac4e424..01a2813d41c592 100644 --- a/Examples/UIExplorer/TransformExample.js +++ b/Examples/UIExplorer/TransformExample.js @@ -10,6 +10,7 @@ * 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 */ 'use strict'; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 05416d4245efee..95f9215cee9840 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -8,12 +8,18 @@ /* Begin PBXBuildFile section */ 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; }; + 13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */; }; + 1323F1891C04AB9F0091BED0 /* bunny.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1851C04AB9F0091BED0 /* bunny.png */; }; + 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1861C04AB9F0091BED0 /* flux@3x.png */; }; + 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1871C04AB9F0091BED0 /* hawk.png */; }; + 1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */; }; + 1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */ = {isa = PBXBuildFile; fileRef = 1323F18E1C04ABEB0091BED0 /* Thumbnails */; }; 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; }; 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; - 134366AF1BFD2C76000A2DE1 /* Thumbnails in Resources */ = {isa = PBXBuildFile; fileRef = 134366AE1BFD2C76000A2DE1 /* Thumbnails */; }; 134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; }; 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; + 134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */; }; 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; }; 138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; }; 1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; }; @@ -22,6 +28,7 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 13B6C1A31C34225900D3FAF5 /* RCTURLUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */; }; 13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; }; 13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; }; 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; }; @@ -29,7 +36,7 @@ 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */; }; 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */; }; - 1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */; }; 1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */; }; 1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */; }; @@ -51,9 +58,9 @@ 14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; }; 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; 272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; }; + 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */; }; 27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; - 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; }; 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; }; @@ -173,12 +180,18 @@ /* Begin PBXFileReference section */ 004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = ""; }; + 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitNotificationRaceTests.m; sourceTree = ""; }; + 1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = ""; }; + 1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flux@3x.png"; sourceTree = ""; }; + 1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hawk.png; sourceTree = ""; }; + 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = uie_thumb_big.png; sourceTree = ""; }; + 1323F18E1C04ABEB0091BED0 /* Thumbnails */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Thumbnails; sourceTree = ""; }; 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 134366AE1BFD2C76000A2DE1 /* Thumbnails */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Thumbnails; sourceTree = ""; }; 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; }; 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; + 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitTests.m; sourceTree = ""; }; 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = ""; }; 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = ""; }; 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = ""; }; @@ -190,6 +203,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = UIExplorer/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = UIExplorer/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = ""; }; + 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTURLUtilsTests.m; sourceTree = ""; }; 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = ""; }; 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = ""; }; @@ -206,7 +220,7 @@ 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtilTests.m; sourceTree = ""; }; 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAllocationTests.m; sourceTree = ""; }; 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeTests.m; sourceTree = ""; }; - 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutorTests.m; sourceTree = ""; }; + 1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSCExecutorTests.m; sourceTree = ""; }; 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_NSURLTests.m; sourceTree = ""; }; 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIFontTests.m; sourceTree = ""; }; 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcherTests.m; sourceTree = ""; }; @@ -226,10 +240,10 @@ 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 272E6B3B1BEA849E001FCF37 /* UpdatePropertiesExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UpdatePropertiesExampleView.h; path = UIExplorer/NativeExampleViews/UpdatePropertiesExampleView.h; sourceTree = ""; }; 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UpdatePropertiesExampleView.m; path = UIExplorer/NativeExampleViews/UpdatePropertiesExampleView.m; sourceTree = ""; }; + 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootViewIntegrationTests.m; sourceTree = ""; }; 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FlexibleSizeExampleView.m; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m; sourceTree = ""; }; 27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = ""; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; - 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; }; 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = ""; }; @@ -312,6 +326,18 @@ name = Libraries; sourceTree = ""; }; + 1323F18D1C04ABAC0091BED0 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 13B07FB61A68108700A75B9A /* Info.plist */, + 1323F1851C04AB9F0091BED0 /* bunny.png */, + 1323F1861C04AB9F0091BED0 /* flux@3x.png */, + 1323F1871C04AB9F0091BED0 /* hawk.png */, + 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; 13417FE41AA91428003F314A /* Products */ = { isa = PBXGroup; children = ( @@ -375,10 +401,10 @@ 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, 13B07FB51A68108700A75B9A /* Images.xcassets */, - 134366AE1BFD2C76000A2DE1 /* Thumbnails */, - 13B07FB61A68108700A75B9A /* Info.plist */, 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 13B07FB71A68108700A75B9A /* main.m */, + 1323F18E1C04ABEB0091BED0 /* Thumbnails */, + 1323F18D1C04ABAC0091BED0 /* Supporting Files */, ); name = UIExplorer; sourceTree = ""; @@ -386,9 +412,10 @@ 143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = { isa = PBXGroup; children = ( + 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */, 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */, 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */, - 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */, + 1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */, 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */, 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */, 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */, @@ -399,6 +426,8 @@ 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */, 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */, 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */, + 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */, + 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */, 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */, 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */, 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, @@ -434,6 +463,7 @@ 143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */ = { isa = PBXGroup; children = ( + 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */, 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */, 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */, 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */, @@ -445,7 +475,6 @@ 143BC5971B21E3E100462512 /* Supporting Files */ = { isa = PBXGroup; children = ( - 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */, 143BC5981B21E3E100462512 /* Info.plist */, ); name = "Supporting Files"; @@ -829,9 +858,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 134366AF1BFD2C76000A2DE1 /* Thumbnails in Resources */, + 1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */, + 1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */, + 1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + 1323F1891C04AB9F0091BED0 /* bunny.png in Resources */, + 1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -839,7 +872,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -855,14 +887,17 @@ 1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */, 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */, 1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */, - 1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */, + 1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */, + 13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */, 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */, + 134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */, 1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */, 1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */, 13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */, 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */, 13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */, 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */, + 13B6C1A31C34225900D3FAF5 /* RCTURLUtilsTests.m in Sources */, 8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */, 8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */, ); @@ -886,6 +921,7 @@ 3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */, 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */, 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */, + 27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme index e2f84182e74e9d..52560f0d68562e 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme @@ -1,7 +1,7 @@ + version = "1.3"> @@ -51,10 +51,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -90,11 +90,11 @@ diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 577e6dd94b5d1c..b0d19a59c170ac 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -16,6 +16,7 @@ #import "RCTBridge.h" #import "RCTJavaScriptLoader.h" +#import "RCTLinkingManager.h" #import "RCTRootView.h" @interface AppDelegate() @@ -80,6 +81,14 @@ - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge return sourceURL; } + +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} + - (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback { diff --git a/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib b/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib index 3b7dcb4a082b02..0556e6641fb041 100644 --- a/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib +++ b/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib @@ -1,7 +1,8 @@ - + - + + @@ -13,17 +14,20 @@ + diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/Contents.json new file mode 100644 index 00000000000000..da4a164c918651 --- /dev/null +++ b/Examples/UIExplorer/UIExplorer/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/Contents.json deleted file mode 100644 index e1ccff627e540a..00000000000000 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x", - "filename" : "uie_comment_highlighted@2x.png" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/Contents.json deleted file mode 100644 index 6f75231d5e6a89..00000000000000 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x", - "filename" : "uie_comment_normal@2x.png" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json deleted file mode 100644 index cdd15d023904d4..00000000000000 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "uie_thumb_big.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/Contents.json deleted file mode 100644 index 06a7acabf52244..00000000000000 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x", - "filename" : "uie_thumb_normal@2x.png" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/Contents.json deleted file mode 100644 index a00a3dc54d03f8..00000000000000 --- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x", - "filename" : "uie_thumb_selected@2x.png" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 9e91b1cef8cc6b..d2e82af921427a 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -18,10 +18,28 @@ 1.0 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.reactjs.ios + CFBundleURLSchemes + + rnuiexplorer + + + CFBundleVersion 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSLocationWhenInUseUsageDescription You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName @@ -38,11 +56,5 @@ UIViewControllerBasedStatusBarAppearance - NSAppTransportSecurity - - - NSAllowsArbitraryLoads - - diff --git a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m index 134d128cf83548..ce899207027a2e 100644 --- a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m +++ b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m @@ -52,8 +52,7 @@ @implementation FlexibleSizeExampleView - (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { + if ((self = [super initWithFrame:frame])) { _sizeUpdated = NO; AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; diff --git a/Examples/UIExplorer/UIExplorerActions.js b/Examples/UIExplorer/UIExplorerActions.js new file mode 100644 index 00000000000000..8f7a59f4e23e30 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerActions.js @@ -0,0 +1,51 @@ +/** + * 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 + */ +'use strict'; + +export type UIExplorerListWithFilterAction = { + type: 'UIExplorerListWithFilterAction', + filter: ?string; +}; + +export type UIExplorerExampleAction = { + type: 'UIExplorerExampleAction', + openExample: string; +}; + +import type {BackAction} from 'NavigationRootContainer'; + +export type UIExplorerAction = BackAction | UIExplorerListWithFilterAction | UIExplorerExampleAction; + +function ExampleListWithFilter(filter: ?string): UIExplorerListWithFilterAction { + return { + type: 'UIExplorerListWithFilterAction', + filter, + }; +} + +function ExampleAction(openExample: string): UIExplorerExampleAction { + return { + type: 'UIExplorerExampleAction', + openExample, + }; +} + +const UIExplorerActions = { + ExampleListWithFilter, + ExampleAction, +}; + +module.exports = UIExplorerActions; diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js index c9bd2418fe448a..9e26c5f7b798d3 100644 --- a/Examples/UIExplorer/UIExplorerApp.android.js +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -16,110 +16,162 @@ */ 'use strict'; -var React = require('react-native'); -var { +const React = require('react-native'); +const { AppRegistry, BackAndroid, Dimensions, DrawerLayoutAndroid, + NavigationExperimental, StyleSheet, ToolbarAndroid, View, + StatusBar, } = React; -var UIExplorerList = require('./UIExplorerList.android'); +const { + RootContainer: NavigationRootContainer, +} = NavigationExperimental; +const UIExplorerActions = require('./UIExplorerActions'); +const UIExplorerExampleList = require('./UIExplorerExampleList'); +const UIExplorerList = require('./UIExplorerList'); +const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer'); +const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap'); var DRAWER_WIDTH_LEFT = 56; -var UIExplorerApp = React.createClass({ - getInitialState: function() { - return { - example: this._getUIExplorerHome(), - }; - }, - - _getUIExplorerHome: function() { - return { - title: 'UIExplorer', - component: this._renderHome(), - }; - }, +class UIExplorerApp extends React.Component { + componentWillMount() { + BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress.bind(this)); + } - componentWillMount: function() { - BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress); - }, + render() { + return ( + { this._navigationRootRef = navRootRef; }} + reducer={UIExplorerNavigationReducer} + renderNavigation={this._renderApp.bind(this)} + /> + ); + } - render: function() { + _renderApp(navigationState, onNavigate) { + if (!navigationState) { + return null; + } return ( { + this._overrideBackPressForDrawerLayout = true; + }} + onDrawerClose={() => { + this._overrideBackPressForDrawerLayout = false; + }} ref={(drawer) => { this.drawer = drawer; }} - renderNavigationView={this._renderNavigationView}> - {this._renderNavigation()} + renderNavigationView={this._renderDrawerContent.bind(this, onNavigate)}> + {this._renderNavigation(navigationState, onNavigate)} - ); - }, + ); + } - _renderNavigationView: function() { + _renderDrawerContent(onNavigate) { return ( - { + this.drawer && this.drawer.closeDrawer(); + onNavigate(action); + }} /> ); - }, + } - onSelectExample: function(example) { - this.drawer.closeDrawer(); - if (example.title === this._getUIExplorerHome().title) { - example = this._getUIExplorerHome(); + _renderNavigation(navigationState, onNavigate) { + if (navigationState.externalExample) { + var Component = UIExplorerList.Modules[navigationState.externalExample]; + return ( + { + onNavigate(NavigationRootContainer.getBackAction()); + }} + ref={(example) => { this._exampleRef = example; }} + /> + ); } - this.setState({ - example: example, - }); - }, - - _renderHome: function() { - var onSelectExample = this.onSelectExample; - return React.createClass({ - render: function() { - return ( - + - ); - } - }); - }, - - _renderNavigation: function() { - var Component = this.state.example.component; + this.drawer.openDrawer()} + style={styles.toolbar} + title={title} + /> + { this._exampleRef = example; }} + /> + + ); + } return ( + this.drawer.openDrawer()} style={styles.toolbar} - title={this.state.example.title} + title={title} + /> + - ); - }, + } - _handleBackButtonPress: function() { - if (this.state.example.title !== this._getUIExplorerHome().title) { - this.onSelectExample(this._getUIExplorerHome()); + _handleBackButtonPress() { + if (this._overrideBackPressForDrawerLayout) { + // This hack is necessary because drawer layout provides an imperative API + // with open and close methods. This code would be cleaner if the drawer + // layout provided an `isOpen` prop and allowed us to pass a `onDrawerClose` handler. + this.drawer && this.drawer.closeDrawer(); + return true; + } + if ( + this._exampleRef && + this._exampleRef.handleBackAction && + this._exampleRef.handleBackAction() + ) { return true; } + if (this._navigationRootRef) { + return this._navigationRootRef.handleNavigation( + NavigationRootContainer.getBackAction() + ); + } return false; - }, -}); + } +} -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ container: { flex: 1, }, diff --git a/Examples/UIExplorer/UIExplorerApp.ios.js b/Examples/UIExplorer/UIExplorerApp.ios.js index c8c4a834f1fc82..3072b36f71fef2 100644 --- a/Examples/UIExplorer/UIExplorerApp.ios.js +++ b/Examples/UIExplorer/UIExplorerApp.ios.js @@ -16,133 +16,194 @@ */ 'use strict'; -var React = require('react-native'); -var UIExplorerList = require('./UIExplorerList.ios'); -var { +const React = require('react-native'); +const UIExplorerActions = require('./UIExplorerActions'); +const UIExplorerList = require('./UIExplorerList.ios'); +const UIExplorerExampleList = require('./UIExplorerExampleList'); +const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer'); +const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap'); + +const { + Alert, + Animated, AppRegistry, - NavigatorIOS, + NavigationExperimental, + SnapshotViewIOS, StyleSheet, Text, TouchableHighlight, - View + View, } = React; -var UIExplorerApp = React.createClass({ +const { + AnimatedView: NavigationAnimatedView, + Card: NavigationCard, + Header: NavigationHeader, + Reducer: NavigationReducer, + RootContainer: NavigationRootContainer, +} = NavigationExperimental; - getInitialState: function() { - return { - openExternalExample: (null: ?React.Component), - }; - }, +import type { Value } from 'Animated'; + +import type { NavigationStateRendererProps } from 'NavigationAnimatedView'; + +import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer'; + +import type { UIExplorerExample } from './UIExplorerList.ios'; - render: function() { - if (this.state.openExternalExample) { - var Example = this.state.openExternalExample; +function PathActionMap(path: string): ?Object { + // Warning! Hacky parsing for example code. Use a library for this! + const exampleParts = path.split('/example/'); + const exampleKey = exampleParts[1]; + if (exampleKey) { + if (!UIExplorerList.Modules[exampleKey]) { + Alert.alert(`${exampleKey} example could not be found!`); + return null; + } + return UIExplorerActions.ExampleAction(exampleKey); + } + return null; +} + +function URIActionMap(uri: ?string): ?Object { + // Warning! Hacky parsing for example code. Use a library for this! + if (!uri) { + return null; + } + const parts = uri.split('rnuiexplorer:/'); + if (!parts[1]) { + return null; + } + const path = parts[1]; + return PathActionMap(path); +} + + +class UIExplorerApp extends React.Component { + _navigationRootRef: ?NavigationRootContainer; + _renderNavigation: Function; + _renderOverlay: Function; + _renderScene: Function; + _renderCard: Function; + componentWillMount() { + this._renderNavigation = this._renderNavigation.bind(this); + this._renderOverlay = this._renderOverlay.bind(this); + this._renderScene = this._renderScene.bind(this); + this._renderCard = this._renderCard.bind(this); + } + render() { + return ( + { this._navigationRootRef = navRootRef; }} + renderNavigation={this._renderNavigation} + linkingActionMap={URIActionMap} + /> + ); + } + _renderNavigation(navigationState: UIExplorerNavigationState, onNavigate: Function) { + if (!navigationState) { + return null; + } + if (navigationState.externalExample) { + var Component = UIExplorerList.Modules[navigationState.externalExample]; return ( - { - this.setState({ openExternalExample: null, }); + onNavigate(NavigationRootContainer.getBackAction()); }} /> ); } - + const {stack} = navigationState; return ( - { - this.setState({ openExternalExample: example, }); - }, - } - }} - itemWrapperStyle={styles.itemWrapper} - tintColor="#008888" + renderOverlay={this._renderOverlay} + renderScene={this._renderCard} /> ); } -}); -var styles = StyleSheet.create({ - container: { - flex: 1, - }, - itemWrapper: { - backgroundColor: '#eaeaea', - }, - bigContainer: { - flex: 1, - height: 60, - backgroundColor: 'gray', - }, - smallContainer: { - flex: 1, - height: 40, - backgroundColor: 'gray', - }, - center: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - whiteText: { - color: 'white', + _renderOverlay(props: NavigationStateRendererProps): ReactElement { + return ( + + ); } -}); - -var SetPropertiesExampleApp = React.createClass({ - - render: function() { - var wrapperStyle = { - backgroundColor: this.props.color, - flex: 1, - alignItems: 'center', - justifyContent: 'center' - }; + _renderCard(props: NavigationStateRendererProps): ReactElement { return ( - - - Embedded React Native view - - + + {this._renderScene(props.navigationState)} + ); - }, -}); + } -var RootViewSizeFlexibilityExampleApp = React.createClass({ + _renderScene(state: Object): ?ReactElement { + if (state.key === 'AppList') { + return ( + + ); + } - getInitialState: function () { - return { toggled: false }; - }, + const Example = UIExplorerList.Modules[state.key]; + if (Example) { + const Component = UIExplorerExampleList.makeRenderable(Example); + return ( + + + + ); + } + return null; + } +} - _onPressButton: function() { - this.setState({ toggled: !this.state.toggled }); +const styles = StyleSheet.create({ + container: { + flex: 1, }, - - render: function() { - var viewStyle = this.state.toggled ? styles.bigContainer : styles.smallContainer; - - return ( - - - - - React Native Button - - - - - ); + exampleContainer: { + flex: 1, + paddingTop: 60, }, }); -AppRegistry.registerComponent('SetPropertiesExampleApp', () => SetPropertiesExampleApp); -AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => RootViewSizeFlexibilityExampleApp); +AppRegistry.registerComponent('SetPropertiesExampleApp', () => require('./SetPropertiesExampleApp')); +AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => require('./RootViewSizeFlexibilityExampleApp')); AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp); -UIExplorerList.registerComponents(); + +// Register suitable examples for snapshot tests +UIExplorerList.ComponentExamples.concat(UIExplorerList.APIExamples).forEach((Example: UIExplorerExample) => { + const ExampleModule = Example.module; + if (ExampleModule.displayName) { + var Snapshotter = React.createClass({ + render: function() { + var Renderable = UIExplorerExampleList.makeRenderable(ExampleModule); + return ( + + + + ); + }, + }); + AppRegistry.registerComponent(ExampleModule.displayName, () => Snapshotter); + } +}); module.exports = UIExplorerApp; diff --git a/Examples/UIExplorer/UIExplorerExampleList.js b/Examples/UIExplorer/UIExplorerExampleList.js new file mode 100644 index 00000000000000..07a07f381f4f1c --- /dev/null +++ b/Examples/UIExplorer/UIExplorerExampleList.js @@ -0,0 +1,218 @@ +/** + * 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 + */ +'use strict'; + +const React = require('react-native'); +const UIExplorerActions = require('./UIExplorerActions'); +const { + ListView, + NavigationExperimental, + StyleSheet, + Text, + TextInput, + TouchableHighlight, + View, +} = React; +const createExamplePage = require('./createExamplePage'); +const { + Container: NavigationContainer, +} = NavigationExperimental; + +import type { + UIExplorerExample, +} from './UIExplorerList.ios' + +const ds = new ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2, + sectionHeaderHasChanged: (h1, h2) => h1 !== h2, +}); + +class UIExplorerExampleList extends React.Component { + constuctor(props: { + disableTitleRow: ?boolean, + onNavigate: Function, + filter: ?string, + list: { + ComponentExamples: Array, + APIExamples: Array, + }, + style: ?any, + }) { + + } + render(): ?ReactElement { + const filterText = this.props.filter || ''; + const filterRegex = new RegExp(String(filterText), 'i'); + const filter = (example) => filterRegex.test(example.module.title); + + const dataSource = ds.cloneWithRowsAndSections({ + components: this.props.list.ComponentExamples.filter(filter), + apis: this.props.list.APIExamples.filter(filter), + }); + return ( + + {this._renderTitleRow()} + {this._renderTextInput()} + + + ); + } + + _renderTitleRow(): ?ReactElement { + if (!this.props.displayTitleRow) { + return null; + } + return this._renderRow( + 'UIExplorer', + 'React Native Examples', + 'home_key', + () => { + this.props.onNavigate( + UIExplorerActions.ExampleListWithFilter('') + ); + } + ); + } + + _renderTextInput(): ?ReactElement { + if (this.props.disableSearch) { + return null; + } + return ( + + { + this.props.onNavigate(UIExplorerActions.ExampleListWithFilter(text)); + }} + placeholder="Search..." + style={[styles.searchTextInput, this.props.searchTextInputStyle]} + testID="explorer_search" + value={this.props.filter} + /> + + ); + } + + _renderSectionHeader(data: any, section: string): ?ReactElement { + return ( + + {section.toUpperCase()} + + ); + } + + _renderExampleRow(example: {key: string, module: Object}): ?ReactElement { + return this._renderRow( + example.module.title, + example.module.description, + example.key, + () => this._handleRowPress(example.key) + ); + } + + _renderRow(title: string, description: string, key: ?string, handler: ?Function): ?ReactElement { + return ( + + + + + {title} + + + {description} + + + + + + ); + } + + _handleRowPress(exampleKey: string): void { + this.props.onNavigate(UIExplorerActions.ExampleAction(exampleKey)) + } +} + +function makeRenderable(example: any): ReactClass { + return example.examples ? + createExamplePage(null, example) : + example; +} + +UIExplorerExampleList = NavigationContainer.create(UIExplorerExampleList); +UIExplorerExampleList.makeRenderable = makeRenderable; + +var styles = StyleSheet.create({ + listContainer: { + flex: 1, + }, + list: { + backgroundColor: '#eeeeee', + }, + sectionHeader: { + padding: 5, + fontWeight: '500', + fontSize: 11, + }, + group: { + backgroundColor: 'white', + }, + row: { + backgroundColor: 'white', + justifyContent: 'center', + paddingHorizontal: 15, + paddingVertical: 8, + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: '#bbbbbb', + marginLeft: 15, + }, + rowTitleText: { + fontSize: 17, + fontWeight: '500', + }, + rowDetailText: { + fontSize: 15, + color: '#888888', + lineHeight: 20, + }, + searchRow: { + backgroundColor: '#eeeeee', + padding: 10, + }, + searchTextInput: { + backgroundColor: 'white', + borderColor: '#cccccc', + borderRadius: 3, + borderWidth: 1, + paddingLeft: 8, + height: 35, + }, +}); + +module.exports = UIExplorerExampleList; diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m new file mode 100644 index 00000000000000..0f34cfb26e54a4 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m @@ -0,0 +1,109 @@ +/** + * 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. + */ + +#import + +#import "RCTAssert.h" +#import "RCTLog.h" +#import "RCTBridge.h" + +@interface RCTLoggingTests : XCTestCase + +@end + +@implementation RCTLoggingTests +{ + RCTBridge *_bridge; + + dispatch_semaphore_t _logSem; + RCTLogLevel _lastLogLevel; + RCTLogSource _lastLogSource; + NSString *_lastLogMessage; +} + +- (void)setUp +{ +#if RUNNING_ON_CI + NSURL *scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; + RCTAssert(scriptURL != nil, @"Could not locate main.jsBundle"); +#else + NSString *app = @"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp"; + NSURL *scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]]; +#endif + + _bridge = [[RCTBridge alloc] initWithBundleURL:scriptURL moduleProvider:NULL launchOptions:nil]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5]; + while (date.timeIntervalSinceNow > 0 && _bridge.loading) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } + XCTAssertFalse(_bridge.loading); + + _logSem = dispatch_semaphore_create(0); + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (source == RCTLogSourceJavaScript) { + _lastLogLevel = level; + _lastLogSource = source; + _lastLogMessage = message; + dispatch_semaphore_signal(_logSem); + } + }); +} + +- (void)tearDown +{ + [_bridge invalidate]; + _bridge = nil; + + RCTSetLogFunction(RCTDefaultLogFunction); +} + +- (void)testLogging +{ + [_bridge enqueueJSCall:@"LoggingTestModule.logToConsole" args:@[@"Invoking console.log"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelInfo); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.log"); + + [_bridge enqueueJSCall:@"LoggingTestModule.warning" args:@[@"Generating warning"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelWarning); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Warning: Generating warning"); + + [_bridge enqueueJSCall:@"LoggingTestModule.invariant" args:@[@"Invariant failed"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Invariant failed"); + + [_bridge enqueueJSCall:@"LoggingTestModule.logErrorToConsole" args:@[@"Invoking console.error"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.error"); + + [_bridge enqueueJSCall:@"LoggingTestModule.throwError" args:@[@"Throwing an error"]]; + dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(_lastLogLevel, RCTLogLevelError); + XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript); + XCTAssertEqualObjects(_lastLogMessage, @"Throwing an error"); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m new file mode 100644 index 00000000000000..9e665b4e12d31f --- /dev/null +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m @@ -0,0 +1,172 @@ +/** + * 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. + */ + +//vs + +/** + * 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. + */ + +#import +#import + +#import "RCTAssert.h" + +#import "RCTEventDispatcher.h" +#import "RCTRootView.h" +#import "RCTRootViewDelegate.h" + +#import + +#define RCT_TEST_DATA_CONFIGURATION_BLOCK(appName, testType, input, block) \ +- (void)test##appName##_##testType##_##input \ +{ \ + [_runner runTest:_cmd \ + module:@#appName \ + initialProps:@{@#input:@YES} \ +configurationBlock:block]; \ +} + +#define RCT_TEST_CONFIGURATION_BLOCK(appName, block) \ +- (void)test##appName \ +{ \ + [_runner runTest:_cmd \ + module:@#appName \ + initialProps:nil \ +configurationBlock:block]; \ +} + +#define RCTNone RCTRootViewSizeFlexibilityNone +#define RCTHeight RCTRootViewSizeFlexibilityHeight +#define RCTWidth RCTRootViewSizeFlexibilityWidth +#define RCTBoth RCTRootViewSizeFlexibilityWidthAndHeight + +typedef void (^ControlBlock)(RCTRootView*); + +@interface SizeFlexibilityTestDelegate : NSObject +@end + +@implementation SizeFlexibilityTestDelegate + +- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView +{ + [rootView.bridge.eventDispatcher sendAppEventWithName:@"rootViewDidChangeIntrinsicSize" + body:@{@"width": @(rootView.intrinsicSize.width), + @"height": @(rootView.intrinsicSize.height)}]; +} + +@end + +static SizeFlexibilityTestDelegate *sizeFlexibilityDelegate() +{ + static SizeFlexibilityTestDelegate *delegate; + if (delegate == nil) { + delegate = [SizeFlexibilityTestDelegate new]; + } + + return delegate; +} + +static ControlBlock simpleSizeFlexibilityBlock(RCTRootViewSizeFlexibility sizeFlexibility) +{ + return ^(RCTRootView *rootView){ + rootView.delegate = sizeFlexibilityDelegate(); + rootView.sizeFlexibility = sizeFlexibility; + }; +} + +static ControlBlock multipleSizeFlexibilityUpdatesBlock(RCTRootViewSizeFlexibility finalSizeFlexibility) +{ + return ^(RCTRootView *rootView){ + + NSInteger arr[4] = {RCTNone, + RCTHeight, + RCTWidth, + RCTBoth}; + + rootView.delegate = sizeFlexibilityDelegate(); + + for (int i = 0; i < 4; ++i) { + if (arr[i] != finalSizeFlexibility) { + rootView.sizeFlexibility = arr[i]; + } + } + + rootView.sizeFlexibility = finalSizeFlexibility; + }; +} + +static ControlBlock reactContentSizeUpdateBlock(RCTRootViewSizeFlexibility sizeFlexibility) +{ + return ^(RCTRootView *rootView){ + rootView.delegate = sizeFlexibilityDelegate(); + rootView.sizeFlexibility = sizeFlexibility; + }; +} + +static ControlBlock propertiesUpdateBlock() +{ + return ^(RCTRootView *rootView){ + rootView.appProperties = @{@"markTestPassed":@YES}; + }; +} + +@interface RCTRootViewIntegrationTests : XCTestCase + +@end + +@implementation RCTRootViewIntegrationTests +{ + RCTTestRunner *_runner; +} + +- (void)setUp +{ +#if __LP64__ + RCTAssert(NO, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); +#endif + + NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion; + RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); + _runner = RCTInitRunnerForApp(@"IntegrationTests/RCTRootViewIntegrationTestApp", nil); +} + +#pragma mark Logic Tests + +// This list should be kept in sync with RCTRootViewIntegrationTestApp.js + +// Simple size flexibility tests - test if the content is measured properly +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, none, simpleSizeFlexibilityBlock(RCTNone)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, height, simpleSizeFlexibilityBlock(RCTHeight)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, width, simpleSizeFlexibilityBlock(RCTWidth)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, both, simpleSizeFlexibilityBlock(RCTBoth)); + +// Consider multiple size flexibility updates in a row. Test if the view's flexibility mode eventually is set to the expected value +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, none, multipleSizeFlexibilityUpdatesBlock(RCTNone)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, height, multipleSizeFlexibilityUpdatesBlock(RCTHeight)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, width, multipleSizeFlexibilityUpdatesBlock(RCTWidth)); +RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, both, multipleSizeFlexibilityUpdatesBlock(RCTBoth)); + +// Test if the 'rootViewDidChangeIntrinsicSize' delegate method is called after the RN app decides internally to resize +RCT_TEST_CONFIGURATION_BLOCK(ReactContentSizeUpdateTest, reactContentSizeUpdateBlock(RCTBoth)) + +// Test if setting 'appProperties' property updates the RN app +RCT_TEST_CONFIGURATION_BLOCK(PropertiesUpdateTest, propertiesUpdateBlock()) + +@end diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png index 5786896ee94950..2c6573453937f4 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png index c38d68b526b033..45b9547a82be78 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png index bfd5872677fdd3..f57db959a03a7a 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png index 43718fb495b2e7..71d9849217254c 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png index 8ec70eb0ce8e80..bf417eb6f461ef 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m index 2f3639cdc08d07..81c11503ca2a19 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m @@ -52,7 +52,7 @@ - (void)test##name \ RCT_TEST(LayoutExample) RCT_TEST(TextExample) RCT_TEST(SwitchExample) -RCT_TEST(SliderExample) +//RCT_TEST(SliderExample) // Disabled: #8985988 //RCT_TEST(TabBarExample) // Disabled: #8985988 - (void)testZZZNotInRecordMode diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 5a370a01ce065e..68be333e51095d 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -15,88 +15,171 @@ */ 'use strict'; -var React = require('react-native'); -var { - StyleSheet, - View, -} = React; -var UIExplorerListBase = require('./UIExplorerListBase'); +export type UIExplorerExample = { + key: string; + module: React.Component; +}; -var COMPONENTS = [ - require('./ImageExample'), - require('./ProgressBarAndroidExample'), - require('./ScrollViewSimpleExample'), - require('./SwitchAndroidExample'), - require('./TextExample.android'), - require('./TextInputExample.android'), - require('./ToolbarAndroidExample'), - require('./TouchableExample'), - require('./ViewExample'), - require('./ViewPagerAndroidExample.android'), +var ComponentExamples: Array = [ + { + key: 'ImageExample', + module: require('./ImageExample'), + }, + { + key: 'ListViewExample', + module: require('./ListViewExample'), + }, + { + key: 'PickerAndroidExample', + module: require('./PickerAndroidExample'), + }, + { + key: 'ProgressBarAndroidExample', + module: require('./ProgressBarAndroidExample'), + }, + { + key: 'RefreshControlExample', + module: require('./RefreshControlExample'), + }, + { + key: 'ScrollViewSimpleExample', + module: require('./ScrollViewSimpleExample'), + }, + { + key: 'StatusBarExample', + module: require('./StatusBarExample'), + }, + { + key: 'SwitchExample', + module: require('./SwitchExample'), + }, + { + key: 'TextExample', + module: require('./TextExample'), + }, + { + key: 'TextInputExample', + module: require('./TextInputExample'), + }, + { + key: 'ToolbarAndroidExample', + module: require('./ToolbarAndroidExample'), + }, + { + key: 'TouchableExample', + module: require('./TouchableExample'), + }, + { + key: 'ViewExample', + module: require('./ViewExample'), + }, + { + key: 'ViewPagerAndroidExample', + module: require('./ViewPagerAndroidExample'), + }, + { + key: 'WebViewExample', + module: require('./WebViewExample'), + }, ]; -var APIS = [ - require('./AccessibilityAndroidExample.android'), - require('./BorderExample'), - require('./GeolocationExample'), - require('./IntentAndroidExample.android'), - require('./LayoutEventsExample'), - require('./LayoutExample'), - require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./TimerExample'), - require('./ToastAndroidExample.android'), - require('./XHRExample'), +const APIExamples = [ + { + key: 'AccessibilityAndroidExample', + module: require('./AccessibilityAndroidExample'), + }, + { + key: 'AlertExample', + module: require('./AlertExample').AlertExample, + }, + { + key: 'AppStateExample', + module: require('./AppStateExample'), + }, + { + key: 'BorderExample', + module: require('./BorderExample'), + }, + { + key: 'CameraRollExample', + module: require('./CameraRollExample'), + }, + { + key: 'ClipboardExample', + module: require('./ClipboardExample'), + }, + { + key: 'DatePickerAndroidExample', + module: require('./DatePickerAndroidExample'), + }, + { + key: 'GeolocationExample', + module: require('./GeolocationExample'), + }, + { + key: 'ImageEditingExample', + module: require('./ImageEditingExample'), + }, + { + key: 'LayoutEventsExample', + module: require('./LayoutEventsExample'), + }, + { + key: 'LinkingExample', + module: require('./LinkingExample'), + }, + { + key: 'LayoutExample', + module: require('./LayoutExample'), + }, + { + key: 'NavigationExperimentalExample', + module: require('./NavigationExperimental/NavigationExperimentalExample'), + }, + { + key: 'NetInfoExample', + module: require('./NetInfoExample'), + }, + { + key: 'PanResponderExample', + module: require('./PanResponderExample'), + }, + { + key: 'PointerEventsExample', + module: require('./PointerEventsExample'), + }, + { + key: 'TimePickerAndroidExample', + module: require('./TimePickerAndroidExample'), + }, + { + key: 'TimerExample', + module: require('./TimerExample'), + }, + { + key: 'ToastAndroidExample', + module: require('./ToastAndroidExample'), + }, + { + key: 'VibrationExample', + module: require('./VibrationExample'), + }, + { + key: 'XHRExample', + module: require('./XHRExample'), + }, ]; -type Props = { - onSelectExample: Function, - isInDrawer: bool, -}; - -class UIExplorerList extends React.Component { - props: Props; - - render() { - return ( - - ); - } +const Modules = {}; - renderAdditionalView(renderRow, renderTextInput): React.Component { - if (this.props.isInDrawer) { - var homePage = renderRow({ - title: 'UIExplorer', - description: 'List of examples', - }, -1); - return ( - - {homePage} - - ); - } - return renderTextInput(styles.searchTextInput); - } - - onPressRow(example: any) { - var Component = UIExplorerListBase.makeRenderable(example); - this.props.onSelectExample({ - title: Component.title, - component: Component, - }); - } -} - -var styles = StyleSheet.create({ - searchTextInput: { - padding: 2, - }, +APIExamples.concat(ComponentExamples).forEach(Example => { + Modules[Example.key] = Example.module; }); +const UIExplorerList = { + APIExamples, + ComponentExamples, + Modules, +}; + module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 940bd79ba42eea..8d770d1f40be7a 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -15,144 +15,255 @@ */ 'use strict'; -var React = require('react-native'); -var { - AppRegistry, - Settings, - SnapshotViewIOS, - StyleSheet, -} = React; - -import type { NavigationContext } from 'NavigationContext'; - -var UIExplorerListBase = require('./UIExplorerListBase'); - -var COMPONENTS = [ - require('./ActivityIndicatorIOSExample'), - require('./DatePickerIOSExample'), - require('./ImageExample'), - require('./LayoutEventsExample'), - require('./ListViewExample'), - require('./ListViewGridLayoutExample'), - require('./ListViewPagingExample'), - require('./MapViewExample'), - require('./ModalExample'), - require('./Navigator/NavigatorExample'), - require('./NavigatorIOSColorsExample'), - require('./NavigatorIOSExample'), - require('./PickerIOSExample'), - require('./ProgressViewIOSExample'), - require('./ScrollViewExample'), - require('./SegmentedControlIOSExample'), - require('./SliderIOSExample'), - require('./SwitchIOSExample'), - require('./TabBarIOSExample'), - require('./TextExample.ios'), - require('./TextInputExample.ios'), - require('./TouchableExample'), - require('./TransparentHitTestExample'), - require('./ViewExample'), - require('./WebViewExample'), -]; +export type UIExplorerExample = { + key: string; + module: Object; +}; -var APIS = [ - require('./AccessibilityIOSExample'), - require('./ActionSheetIOSExample'), - require('./AdSupportIOSExample'), - require('./AlertIOSExample'), - require('./AnimatedExample'), - require('./AnimatedGratuitousApp/AnExApp'), - require('./AppStateIOSExample'), - require('./AsyncStorageExample'), - require('./BorderExample'), - require('./CameraRollExample.ios'), - require('./GeolocationExample'), - require('./LayoutExample'), - require('./NetInfoExample'), - require('./PanResponderExample'), - require('./PointerEventsExample'), - require('./PushNotificationIOSExample'), - require('./RCTRootViewIOSExample'), - require('./StatusBarIOSExample'), - require('./TimerExample'), - require('./VibrationIOSExample'), - require('./XHRExample.ios'), - require('./ImageEditingExample'), +var ComponentExamples: Array = [ + { + key: 'ActivityIndicatorIOSExample', + module: require('./ActivityIndicatorIOSExample'), + }, + { + key: 'DatePickerIOSExample', + module: require('./DatePickerIOSExample'), + }, + { + key: 'ImageExample', + module: require('./ImageExample'), + }, + { + key: 'LayoutEventsExample', + module: require('./LayoutEventsExample'), + }, + { + key: 'ListViewExample', + module: require('./ListViewExample'), + }, + { + key: 'ListViewGridLayoutExample', + module: require('./ListViewGridLayoutExample'), + }, + { + key: 'ListViewPagingExample', + module: require('./ListViewPagingExample'), + }, + { + key: 'MapViewExample', + module: require('./MapViewExample'), + }, + { + key: 'ModalExample', + module: require('./ModalExample'), + }, + { + key: 'NavigatorExample', + module: require('./Navigator/NavigatorExample'), + }, + { + key: 'NavigatorIOSColorsExample', + module: require('./NavigatorIOSColorsExample'), + }, + { + key: 'NavigatorIOSExample', + module: require('./NavigatorIOSExample'), + }, + { + key: 'PickerIOSExample', + module: require('./PickerIOSExample'), + }, + { + key: 'ProgressViewIOSExample', + module: require('./ProgressViewIOSExample'), + }, + { + key: 'RefreshControlExample', + module: require('./RefreshControlExample'), + }, + { + key: 'ScrollViewExample', + module: require('./ScrollViewExample'), + }, + { + key: 'SegmentedControlIOSExample', + module: require('./SegmentedControlIOSExample'), + }, + { + key: 'SliderIOSExample', + module: require('./SliderIOSExample'), + }, + { + key: 'StatusBarExample', + module: require('./StatusBarExample'), + }, + { + key: 'SwitchExample', + module: require('./SwitchExample'), + }, + { + key: 'TabBarIOSExample', + module: require('./TabBarIOSExample'), + }, + { + key: 'TextExample', + module: require('./TextExample.ios'), + }, + { + key: 'TextInputExample', + module: require('./TextInputExample.ios'), + }, + { + key: 'TouchableExample', + module: require('./TouchableExample'), + }, + { + key: 'TransparentHitTestExample', + module: require('./TransparentHitTestExample'), + }, + { + key: 'ViewExample', + module: require('./ViewExample'), + }, + { + key: 'WebViewExample', + module: require('./WebViewExample'), + }, ]; -type Props = { - navigator: { - navigationContext: NavigationContext, - push: (route: {title: string, component: ReactClass}) => void, +var APIExamples: Array = [ + { + key: 'AccessibilityIOSExample', + module: require('./AccessibilityIOSExample'), }, - onExternalExampleRequested: Function, -}; - -class UIExplorerList extends React.Component { - props: Props; - - render() { - return ( - - ); - } - - renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component { - return renderTextInput(styles.searchTextInput); - } - - search(text: mixed) { - Settings.set({searchText: text}); - } - - _openExample(example: any) { - if (example.external) { - this.props.onExternalExampleRequested(example); - return; - } - - var Component = UIExplorerListBase.makeRenderable(example); - this.props.navigator.push({ - title: Component.title, - component: Component, - }); - } - - onPressRow(example: any) { - this._openExample(example); - } + { + key: 'ActionSheetIOSExample', + module: require('./ActionSheetIOSExample'), + }, + { + key: 'AdSupportIOSExample', + module: require('./AdSupportIOSExample'), + }, + { + key: 'AlertIOSExample', + module: require('./AlertIOSExample'), + }, + { + key: 'AnimatedExample', + module: require('./AnimatedExample'), + }, + { + key: 'AnExApp', + module: require('./AnimatedGratuitousApp/AnExApp'), + }, + { + key: 'AppStateIOSExample', + module: require('./AppStateIOSExample'), + }, + { + key: 'AppStateExample', + module: require('./AppStateExample'), + }, + { + key: 'AsyncStorageExample', + module: require('./AsyncStorageExample'), + }, + { + key: 'BorderExample', + module: require('./BorderExample'), + }, + { + key: 'BoxShadowExample', + module: require('./BoxShadowExample'), + }, + { + key: 'CameraRollExample', + module: require('./CameraRollExample'), + }, + { + key: 'ClipboardExample', + module: require('./ClipboardExample'), + }, + { + key: 'GeolocationExample', + module: require('./GeolocationExample'), + }, + { + key: 'ImageEditingExample', + module: require('./ImageEditingExample'), + }, + { + key: 'LayoutExample', + module: require('./LayoutExample'), + }, + { + key: 'LinkingExample', + module: require('./LinkingExample'), + }, + { + key: 'NavigationExperimentalExample', + module: require('./NavigationExperimental/NavigationExperimentalExample'), + }, + { + key: 'NavigationExperimentalLegacyNavigatorExample', + module: require('./NavigationExperimental/LegacyNavigator/LegacyNavigatorExample'), + }, + { + key: 'NetInfoExample', + module: require('./NetInfoExample'), + }, + { + key: 'PanResponderExample', + module: require('./PanResponderExample'), + }, + { + key: 'PointerEventsExample', + module: require('./PointerEventsExample'), + }, + { + key: 'PushNotificationIOSExample', + module: require('./PushNotificationIOSExample'), + }, + { + key: 'RCTRootViewIOSExample', + module: require('./RCTRootViewIOSExample'), + }, + { + key: 'SnapshotExample', + module: require('./SnapshotExample'), + }, + { + key: 'StatusBarIOSExample', + module: require('./StatusBarIOSExample'), + }, + { + key: 'TimerExample', + module: require('./TimerExample'), + }, + { + key: 'TransformExample', + module: require('./TransformExample'), + }, + { + key: 'VibrationExample', + module: require('./VibrationExample'), + }, + { + key: 'XHRExample', + module: require('./XHRExample.ios'), + }, +]; - // Register suitable examples for snapshot tests - static registerComponents() { - COMPONENTS.concat(APIS).forEach((Example) => { - if (Example.displayName) { - var Snapshotter = React.createClass({ - render: function() { - var Renderable = UIExplorerListBase.makeRenderable(Example); - return ( - - - - ); - }, - }); - AppRegistry.registerComponent(Example.displayName, () => Snapshotter); - } - }); - } -} +const Modules = {}; -var styles = StyleSheet.create({ - searchTextInput: { - height: 30, - }, +APIExamples.concat(ComponentExamples).forEach(Example => { + Modules[Example.key] = Example.module; }); +const UIExplorerList = { + APIExamples, + ComponentExamples, + Modules, +}; + module.exports = UIExplorerList; diff --git a/Examples/UIExplorer/UIExplorerListBase.js b/Examples/UIExplorer/UIExplorerListBase.js deleted file mode 100644 index 4b26a6976dc6c6..00000000000000 --- a/Examples/UIExplorer/UIExplorerListBase.js +++ /dev/null @@ -1,195 +0,0 @@ -/** - * 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 - */ -'use strict'; - -var React = require('react-native'); -var { - ListView, - PixelRatio, - StyleSheet, - Text, - TextInput, - TouchableHighlight, - View, -} = React; -var createExamplePage = require('./createExamplePage'); - -var ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => r1 !== r2, - sectionHeaderHasChanged: (h1, h2) => h1 !== h2, -}); - -class UIExplorerListBase extends React.Component { - constructor(props: any) { - super(props); - this.state = { - dataSource: ds.cloneWithRowsAndSections({ - components: [], - apis: [], - }), - searchText: this.props.searchText, - }; - } - - componentDidMount(): void { - this.search(this.state.searchText); - } - - render() { - var topView = this.props.renderAdditionalView && - this.props.renderAdditionalView(this.renderRow.bind(this), this.renderTextInput.bind(this)); - - return ( - - {topView} - - - ); - } - - renderTextInput(searchTextInputStyle: any) { - return ( - - - - ); - } - - _renderSectionHeader(data: any, section: string) { - return ( - - - {section.toUpperCase()} - - - ); - } - - renderRow(example: any, i: number) { - return ( - - this.onPressRow(example)}> - - - {example.title} - - - {example.description} - - - - - - ); - } - - search(text: mixed): void { - this.props.search && this.props.search(text); - - var regex = new RegExp(text, 'i'); - var filter = (component) => regex.test(component.title); - - this.setState({ - dataSource: ds.cloneWithRowsAndSections({ - components: this.props.components.filter(filter), - apis: this.props.apis.filter(filter), - }), - searchText: text, - }); - } - - onPressRow(example: any): void { - this.props.onPressRow && this.props.onPressRow(example); - } - - static makeRenderable(example: any): ReactClass { - return example.examples ? - createExamplePage(null, example) : - example; - } -} - -var styles = StyleSheet.create({ - listContainer: { - flex: 1, - }, - list: { - backgroundColor: '#eeeeee', - }, - sectionHeader: { - padding: 5, - }, - group: { - backgroundColor: 'white', - }, - sectionHeaderTitle: { - fontWeight: '500', - fontSize: 11, - }, - row: { - backgroundColor: 'white', - justifyContent: 'center', - paddingHorizontal: 15, - paddingVertical: 8, - }, - separator: { - height: 1 / PixelRatio.get(), - backgroundColor: '#bbbbbb', - marginLeft: 15, - }, - rowTitleText: { - fontSize: 17, - fontWeight: '500', - }, - rowDetailText: { - fontSize: 15, - color: '#888888', - lineHeight: 20, - }, - searchRow: { - backgroundColor: '#eeeeee', - paddingTop: 75, - paddingLeft: 10, - paddingRight: 10, - paddingBottom: 10, - }, - searchTextInput: { - backgroundColor: 'white', - borderColor: '#cccccc', - borderRadius: 3, - borderWidth: 1, - paddingLeft: 8, - }, -}); - -module.exports = UIExplorerListBase; diff --git a/Examples/UIExplorer/UIExplorerNavigationReducer.js b/Examples/UIExplorer/UIExplorerNavigationReducer.js new file mode 100644 index 00000000000000..3dee22c2c2ebfc --- /dev/null +++ b/Examples/UIExplorer/UIExplorerNavigationReducer.js @@ -0,0 +1,102 @@ +/** + * 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 + */ +'use strict'; + +const React = require('react-native'); +// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? +const UIExplorerList = require('./UIExplorerList'); +const { + NavigationExperimental, +} = React; +const { + Reducer: NavigationReducer, +} = NavigationExperimental; +const StackReducer = NavigationReducer.StackReducer; + +import type {NavigationState} from 'NavigationStateUtils'; + +import type {UIExplorerAction} from './UIExplorerActions'; + +export type UIExplorerNavigationState = { + externalExample: ?string; + stack: NavigationState; +}; + +const UIExplorerStackReducer = StackReducer({ + getPushedReducerForAction: (action) => { + if (action.type === 'UIExplorerExampleAction' && UIExplorerList.Modules[action.openExample]) { + return (state) => state || {key: action.openExample}; + } + return null; + }, + getReducerForState: (initialState) => (state) => state || initialState, + initialState: { + key: 'UIExplorerMainStack', + index: 0, + children: [ + {key: 'AppList'}, + ], + }, +}); + +function UIExplorerNavigationReducer(lastState: ?UIExplorerNavigationState, action: any): UIExplorerNavigationState { + if (!lastState) { + return { + externalExample: null, + stack: UIExplorerStackReducer(null, action), + }; + } + if (action.type === 'UIExplorerListWithFilterAction') { + return { + externalExample: null, + stack: { + key: 'UIExplorerMainStack', + index: 0, + children: [ + { + key: 'AppList', + filter: action.filter, + }, + ], + }, + }; + } + if (action.type === 'BackAction' && lastState.externalExample) { + return { + ...lastState, + externalExample: null, + }; + } + if (action.type === 'UIExplorerExampleAction') { + const ExampleModule = UIExplorerList.Modules[action.openExample]; + if (ExampleModule && ExampleModule.external) { + return { + ...lastState, + externalExample: action.openExample, + }; + } + } + const newStack = UIExplorerStackReducer(lastState.stack, action); + if (newStack !== lastState.stack) { + return { + externalExample: null, + stack: newStack, + } + } + return lastState; +} + +module.exports = UIExplorerNavigationReducer; diff --git a/Examples/UIExplorer/UIExplorerStateTitleMap.js b/Examples/UIExplorer/UIExplorerStateTitleMap.js new file mode 100644 index 00000000000000..111d2c49ab214e --- /dev/null +++ b/Examples/UIExplorer/UIExplorerStateTitleMap.js @@ -0,0 +1,33 @@ +/** + * 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 + */ +'use strict'; + +// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? +const UIExplorerList = require('./UIExplorerList'); + +import type {NavigationState} from 'NavigationStateUtils'; + +function StateTitleMap(state: NavigationState): string { + if (UIExplorerList.Modules[state.key]) { + return UIExplorerList.Modules[state.key].title + } + if (state.key === 'AppList') { + return 'UIExplorer'; + } + return 'Unknown'; +} + +module.exports = StateTitleMap; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m index fcd7961143c3df..39c0404e02954d 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m @@ -14,7 +14,7 @@ @implementation LayoutSubviewsOrderingTest /** * This test exists to insure that didLayoutSubviews is always called immediately after layoutSubviews for a VC:View * pair. In Catalyst we have multiple levels of ViewController containment, and we rely on this ordering - * to insure that layoutGuides are set on RKViewControllers before Views further down in the heirarchy have + * to insure that layoutGuides are set on RKViewControllers before Views further down in the hierarchy have * their layoutSubviews called (and need to use the aforementioned layoutGuides) */ - (void)testLayoutSubviewsOrdering @@ -63,7 +63,7 @@ - (void)testLayoutSubviewsOrdering } }] viewDidLayoutSubviews]; - // setup View heirarchy and force layout + // setup View hierarchy and force layout parentVC.view = parentView; childVC.view = childView; [parentVC addChildViewController:childVC]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 4fe7294fb3e9cb..a52400ffcb3f3f 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -16,24 +16,18 @@ #import #import "RCTBridge.h" -#import "RCTContextExecutor.h" +#import "RCTBridge+Private.h" +#import "RCTJSCExecutor.h" #import "RCTModuleMethod.h" #import "RCTRootView.h" #define RUN_RUNLOOP_WHILE(CONDITION) \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:5]; \ -while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \ -} \ -_Pragma("clang diagnostic pop") - -@interface RCTBridge (RCTAllocationTests) - -@property (nonatomic, weak) RCTBridge *batchedBridge; - -@end +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + } \ +} @interface RCTJavaScriptContext : NSObject @@ -74,13 +68,40 @@ - (void)invalidate @interface RCTAllocationTests : XCTestCase @end -@implementation RCTAllocationTests +@implementation RCTAllocationTests { + NSURL *_bundleURL; +} + +- (void)setUp +{ + [super setUp]; + + NSString *bundleContents = + @"var __fbBatchedBridge = {" + " callFunctionReturnFlushedQueue: function() {}," + " invokeCallbackAndReturnFlushedQueue: function() {}," + " flushedQueue: function() {}," + "};"; + + NSURL *tempDir = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]; + [[NSFileManager defaultManager] createDirectoryAtURL:tempDir withIntermediateDirectories:YES attributes:nil error:NULL]; + + _bundleURL = [tempDir URLByAppendingPathComponent:@"rctallocationtests-bundle.js"]; + [bundleContents writeToURL:_bundleURL atomically:YES encoding:NSUTF8StringEncoding error:NULL]; +} + +- (void)tearDown +{ + [super tearDown]; + + [[NSFileManager defaultManager] removeItemAtURL:_bundleURL error:NULL]; +} - (void)testBridgeIsDeallocated { __weak RCTBridge *weakBridge; @autoreleasepool { - RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:nil + RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:_bundleURL moduleName:@"" initialProperties:nil launchOptions:nil]; @@ -96,7 +117,7 @@ - (void)testModulesAreInvalidated { AllocationTestModule *module = [AllocationTestModule new]; @autoreleasepool { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:^{ return @[module]; } @@ -114,11 +135,11 @@ - (void)testModulesAreDeallocated __weak AllocationTestModule *weakModule; @autoreleasepool { AllocationTestModule *module = [AllocationTestModule new]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil - moduleProvider:^{ - return @[module]; - } - launchOptions:nil]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL + moduleProvider:^{ + return @[module]; + } + launchOptions:nil]; weakModule = module; XCTAssertNotNil(weakModule, @"AllocationTestModule should have been created"); (void)bridge; @@ -132,7 +153,7 @@ - (void)testModuleMethodsAreDeallocated { __weak RCTModuleMethod *weakMethod; @autoreleasepool { - __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]]; + __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]]; weakMethod = method; XCTAssertNotNil(method, @"RCTModuleMethod should have been created"); } @@ -145,7 +166,7 @@ - (void)testJavaScriptExecutorIsDeallocated { __weak id weakExecutor; @autoreleasepool { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; weakExecutor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; @@ -161,11 +182,11 @@ - (void)testJavaScriptContextIsDeallocated { __weak id weakContext; @autoreleasepool { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; - RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"])); + RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"_context"])); XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created"); (void)bridge; } @@ -176,7 +197,7 @@ - (void)testJavaScriptContextIsDeallocated - (void)testContentViewIsInvalidated { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; __weak UIView *rootContentView; @@ -195,7 +216,7 @@ - (void)testUnderlyingBridgeIsDeallocated RCTBridge *bridge; __weak id batchedBridge; @autoreleasepool { - bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:nil launchOptions:nil]; + bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; batchedBridge = bridge.batchedBridge; XCTAssertTrue([batchedBridge isValid], @"RCTBatchedBridge should be valid"); [bridge reload]; @@ -205,6 +226,15 @@ - (void)testUnderlyingBridgeIsDeallocated XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated"); XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated"); + + // Wait to complete the test until the new batchedbridge is also deallocated + @autoreleasepool { + batchedBridge = bridge.batchedBridge; + bridge = nil; + } + + RUN_RUNLOOP_WHILE(batchedBridge != nil); + XCTAssertNil(batchedBridge); } @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index c201f5799389c7..08a1a1e37fec81 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -16,18 +16,22 @@ #import #import "RCTBridge.h" +#import "RCTBridge+Private.h" #import "RCTBridgeModule.h" #import "RCTJavaScriptExecutor.h" #import "RCTUtils.h" -@interface RCTBridge (Testing) - -@property (nonatomic, strong, readonly) RCTBridge *batchedBridge; - -- (void)handleBuffer:(id)buffer; -- (void)setUp; - -@end +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION)) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ + } \ +} @interface TestExecutor : NSObject @@ -37,6 +41,8 @@ @interface TestExecutor : NSObject @implementation TestExecutor +@synthesize valid = _valid; + RCT_EXPORT_MODULE() - (void)setUp {} @@ -51,13 +57,25 @@ - (instancetype)init - (BOOL)isValid { - return YES; + return _valid; +} + +- (void)flushedQueue:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); +} + +- (void)callFunctionOnModule:(__unused NSString *)module + method:(__unused NSString *)method + arguments:(__unused NSArray *)args + callback:(RCTJavaScriptCallback)onComplete +{ + onComplete(nil, nil); } -- (void)executeJSCall:(__unused NSString *)name - method:(__unused NSString *)method - arguments:(__unused NSArray *)arguments - callback:(RCTJavaScriptCallback)onComplete +- (void)invokeCallbackID:(__unused NSNumber *)cbID + arguments:(__unused NSArray *)args + callback:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); } @@ -82,13 +100,18 @@ - (void)injectJSONText:(NSString *)script onComplete(nil); } -- (void)invalidate {} +- (void)invalidate +{ + _valid = NO; +} @end @interface RCTBridgeTests : XCTestCase { RCTBridge *_bridge; + __weak TestExecutor *_jsExecutor; + BOOL _testMethodCalled; } @end @@ -108,33 +131,32 @@ - (void)setUp launchOptions:nil]; _bridge.executorClass = [TestExecutor class]; + // Force to recreate the executor with the new class // - reload: doesn't work here since bridge hasn't loaded yet. [_bridge invalidate]; [_bridge setUp]; + + _jsExecutor = _bridge.batchedBridge.javaScriptExecutor; + XCTAssertNotNil(_jsExecutor); } - (void)tearDown { [super tearDown]; + + _testMethodCalled = NO; + [_bridge invalidate]; + RUN_RUNLOOP_WHILE(_jsExecutor.isValid); + _bridge = nil; } -#define RUN_RUNLOOP_WHILE(CONDITION) \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:0.1]; \ -while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \ -} \ -_Pragma("clang diagnostic pop") - - (void)testHookRegistration { - TestExecutor *executor = [_bridge.batchedBridge valueForKey:@"_javaScriptExecutor"]; - NSString *injectedStuff; - RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"])); + RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"])); + XCTAssertNotNil(injectedStuff); __block NSNumber *testModuleID = nil; __block NSDictionary *testConstants = nil; @@ -159,27 +181,27 @@ - (void)testHookRegistration - (void)testCallNativeMethod { - TestExecutor *executor = [_bridge.batchedBridge valueForKey:@"_javaScriptExecutor"]; - NSString *injectedStuff; - RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"])); + RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"])); + XCTAssertNotNil(injectedStuff); __block NSNumber *testModuleID = nil; - __block NSDictionary *testConstants = nil; __block NSNumber *testMethodID = nil; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) { if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) { testModuleID = @(i); - testConstants = moduleConfig[1]; testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]); *stop = YES; } }]; + XCTAssertNotNil(testModuleID); + XCTAssertNotNil(testMethodID); + NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42]; - NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567]; + NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]]; [_bridge.batchedBridge handleBuffer:buffer]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m index 5761849d69d447..e34d9f29c2aaf9 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m @@ -18,27 +18,44 @@ #import #import "RCTEventDispatcher.h" -@interface RCTTestEvent : RCTBaseEvent - -@property (nonatomic, assign) BOOL canCoalesce; - +@interface RCTTestEvent : NSObject +@property (atomic, assign, readwrite) BOOL canCoalesce; @end @implementation RCTTestEvent +{ + NSDictionary *_body; +} + +@synthesize viewTag = _viewTag; +@synthesize eventName = _eventName; - (instancetype)initWithViewTag:(NSNumber *)viewTag eventName:(NSString *)eventName body:(NSDictionary *)body { - if (self = [super initWithViewTag:viewTag eventName:eventName body:body]) { - self.canCoalesce = YES; + if (self = [super init]) { + _viewTag = viewTag; + _eventName = eventName; + _body = body; + _canCoalesce = YES; } return self; } +- (id)coalesceWithEvent:(id)newEvent +{ + return newEvent; +} + + (NSString *)moduleDotMethod { return @"RCTDeviceEventEmitter.emit"; } +- (NSArray *)arguments +{ + return @[_eventName, _body]; +} + @end @interface RCTEventDispatcherTests : XCTestCase @@ -62,7 +79,7 @@ - (void)setUp _bridge = [OCMockObject mockForClass:[RCTBridge class]]; _eventDispatcher = [RCTEventDispatcher new]; - ((id)_eventDispatcher).bridge = _bridge; + [_eventDispatcher setValue:_bridge forKey:@"bridge"]; _eventName = RCTNormalizeInputEventName(@"sampleEvent"); _body = @{ @"foo": @"bar" }; @@ -76,7 +93,7 @@ - (void)setUp - (void)testLegacyEventsAreImmediatelyDispatched { [[_bridge expect] enqueueJSCall:_JSMethod - args:@[_eventName, _body]]; + args:[_testEvent arguments]]; [_eventDispatcher sendDeviceEventWithName:_eventName body:_body]; @@ -87,7 +104,7 @@ - (void)testNonCoalescingEventsAreImmediatelyDispatched { _testEvent.canCoalesce = NO; [[_bridge expect] enqueueJSCall:_JSMethod - args:@[_eventName, _body]]; + args:[_testEvent arguments]]; [_eventDispatcher sendEvent:_testEvent]; @@ -97,15 +114,32 @@ - (void)testNonCoalescingEventsAreImmediatelyDispatched - (void)testCoalescedEventShouldBeDispatchedOnFrameUpdate { [_eventDispatcher sendEvent:_testEvent]; - + [_bridge verify]; [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" - args:@[_eventName, _body]]; + args:[_testEvent arguments]]; [(id)_eventDispatcher didUpdateFrame:nil]; [_bridge verify]; } +- (void)testNonCoalescingEventForcesColescedEventsToBeImmediatelyDispatched +{ + RCTTestEvent *nonCoalescingEvent = [[RCTTestEvent alloc] initWithViewTag:nil + eventName:_eventName + body:@{}]; + nonCoalescingEvent.canCoalesce = NO; + [_eventDispatcher sendEvent:_testEvent]; + + [[_bridge expect] enqueueJSCall:[[_testEvent class] moduleDotMethod] + args:[_testEvent arguments]]; + [[_bridge expect] enqueueJSCall:[[nonCoalescingEvent class] moduleDotMethod] + args:[nonCoalescingEvent arguments]]; + + [_eventDispatcher sendEvent:nonCoalescingEvent]; + [_bridge verify]; +} + - (void)testBasicCoalescingReturnsLastEvent { RCTTestEvent *ignoredEvent = [[RCTTestEvent alloc] initWithViewTag:nil @@ -116,7 +150,7 @@ - (void)testBasicCoalescingReturnsLastEvent [_eventDispatcher sendEvent:_testEvent]; [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" - args:@[_eventName, _body]]; + args:[_testEvent arguments]]; [(id)_eventDispatcher didUpdateFrame:nil]; @@ -134,10 +168,10 @@ - (void)testDifferentEventTypesDontCoalesce [_eventDispatcher sendEvent:_testEvent]; [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" - args:@[firstEventName, _body]]; + args:[firstEvent arguments]]; [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit" - args:@[_eventName, _body]]; + args:[_testEvent arguments]]; [(id)_eventDispatcher didUpdateFrame:nil]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m index 69162a764e0766..9d5814c56a5b4b 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m @@ -16,6 +16,14 @@ #import "RCTUtils.h" #import "RCTNetworking.h" +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + } \ +} + extern BOOL RCTIsGzippedData(NSData *data); @interface RCTNetworking (Private) @@ -61,18 +69,21 @@ - (void)testDontRezipZippedData - (void)testRequestBodyEncoding { NSDictionary *query = @{ - @"url": @"http://example.com", - @"method": @"POST", - @"data": @{@"string": @"Hello World"}, - @"headers": @{@"Content-Encoding": @"gzip"}, - }; + @"url": @"http://example.com", + @"method": @"POST", + @"data": @{@"string": @"Hello World"}, + @"headers": @{@"Content-Encoding": @"gzip"}, + }; RCTNetworking *networker = [RCTNetworking new]; + [networker setValue:dispatch_get_main_queue() forKey:@"methodQueue"]; __block NSURLRequest *request = nil; [networker buildRequest:query completionBlock:^(NSURLRequest *_request) { request = _request; }]; + RUN_RUNLOOP_WHILE(request == nil); + XCTAssertNotNil(request); XCTAssertNotNil(request.HTTPBody); XCTAssertTrue(RCTIsGzippedData(request.HTTPBody)); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h index 6ee68b5c3803e4..fe68f0592a0baa 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h @@ -15,7 +15,7 @@ #import "RCTImageLoader.h" typedef BOOL (^RCTImageURLLoaderCanLoadImageURLHandler)(NSURL *requestURL); -typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler); +typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler); @interface RCTConcreteImageURLLoader : NSObject @@ -26,7 +26,7 @@ typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)( @end typedef BOOL (^RCTImageDataDecoderCanDecodeImageDataHandler)(NSData *imageData); -typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler); +typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler); @interface RCTConcreteImageDecoder : NSObject diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m index e41b09f3d7f6f9..e3b4cb0861f86e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m @@ -47,7 +47,7 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL return _canLoadImageURLHandler(requestURL); } -- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler +- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler { return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler); } @@ -92,7 +92,7 @@ - (BOOL)canDecodeImageData:(NSData *)imageData return _canDecodeImageDataHandler(imageData); } -- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler +- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler { return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler); } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m index 04f367312a105a..5a7cc12bd28eee 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m @@ -41,16 +41,15 @@ - (void)testImageLoading id loader = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) { return YES; - } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) { + } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) { progressHandler(1, 1); completionHandler(nil, image); return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil]; - [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) { + [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { XCTAssertEqual(progress, 1); XCTAssertEqual(total, 1); } completionBlock:^(NSError *loadError, id loadedImage) { @@ -65,7 +64,7 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority id loader1 = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) { return YES; - } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) { + } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) { progressHandler(1, 1); completionHandler(nil, image); return nil; @@ -73,15 +72,14 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority id loader2 = [[RCTImageLoaderTestsURLLoader2 alloc] initWithPriority:0.5 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) { return YES; - } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) { + } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) { XCTFail(@"Should not have used loader2"); return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil]; - [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) { + [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) { XCTAssertEqual(progress, 1); XCTAssertEqual(total, 1); } completionBlock:^(NSError *loadError, id loadedImage) { @@ -97,16 +95,15 @@ - (void)testImageDecoding id decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) { return YES; - } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) { + } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) { XCTAssertEqualObjects(imageData, data); completionHandler(nil, image); return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil]; - RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) { + RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); XCTAssertNil(decodeError); }]; @@ -120,7 +117,7 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority id decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) { return YES; - } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) { + } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) { XCTAssertEqualObjects(imageData, data); completionHandler(nil, image); return nil; @@ -128,15 +125,14 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority id decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) { return YES; - } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) { + } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) { XCTFail(@"Should not have used decoder2"); return nil; }]; - RCTImageLoader *imageLoader = [RCTImageLoader new]; - NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil]; - RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) { + RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) { XCTAssertEqualObjects(decodedImage, image); XCTAssertNil(decodeError); }]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m index f1effc8d64e1a8..810e079ab211af 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m @@ -46,19 +46,19 @@ - (void)testLandscapeSourceLandscapeTarget { CGRect expected = {CGPointZero, {100, 20}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch); RCTAssertEqualRects(expected, result); } { - CGRect expected = {CGPointZero, {100, 10}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit); + CGRect expected = {{0, 5}, {100, 10}}; + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain); RCTAssertEqualRects(expected, result); } { CGRect expected = {{-50, 0}, {200, 20}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover); RCTAssertEqualRects(expected, result); } } @@ -70,19 +70,19 @@ - (void)testPortraitSourceLandscapeTarget { CGRect expected = {CGPointZero, {100, 20}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch); RCTAssertEqualRects(expected, result); } { - CGRect expected = {CGPointZero, {2, 20}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit); + CGRect expected = {{49, 0}, {2, 20}}; + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain); RCTAssertEqualRects(expected, result); } { CGRect expected = {{0, -490}, {100, 1000}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover); RCTAssertEqualRects(expected, result); } } @@ -94,19 +94,19 @@ - (void)testPortraitSourcePortraitTarget { CGRect expected = {CGPointZero, {20, 50}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch); RCTAssertEqualRects(expected, result); } { - CGRect expected = {CGPointZero, {5, 50}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit); + CGRect expected = {{7,0}, {5, 50}}; + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain); RCTAssertEqualRects(expected, result); } { CGRect expected = {{0, -75}, {20, 200}}; - CGRect result = RCTTargetRect(content, target, 2, UIViewContentModeScaleAspectFill); + CGRect result = RCTTargetRect(content, target, 2, RCTResizeModeCover); RCTAssertEqualRects(expected, result); } } @@ -118,7 +118,7 @@ - (void)testRounding { CGRect expected = {{0, -75}, {20, 200}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover); RCTAssertEqualRects(expected, result); } } @@ -129,8 +129,56 @@ - (void)testScaling CGSize target = {3, 3}; CGRect expected = {CGPointZero, {3, 3}}; - CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); + CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch); RCTAssertEqualRects(expected, result); } +- (void)testPlaceholderImage +{ + CGSize size = {45, 22}; + CGFloat expectedScale = 1.0; + UIImage *image = RCTGetPlaceholderImage(size, nil); + RCTAssertEqualSizes(size, image.size); + XCTAssertEqual(expectedScale, image.scale); +} + +- (void)testPlaceholderNonintegralSize +{ + CGSize size = {3.0/2, 7.0/3}; + CGFloat expectedScale = 6; + CGSize pixelSize = { + round(size.width * expectedScale), + round(size.height * expectedScale) + }; + UIImage *image = RCTGetPlaceholderImage(size, nil); + RCTAssertEqualSizes(size, image.size); + XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage)); + XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage)); + XCTAssertEqual(expectedScale, image.scale); +} + +- (void)testPlaceholderSquareImage +{ + CGSize size = {333, 333}; + CGFloat expectedScale = 1.0/333; + CGSize pixelSize = {1, 1}; + UIImage *image = RCTGetPlaceholderImage(size, nil); + RCTAssertEqualSizes(size, image.size); + XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage)); + XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage)); + XCTAssertEqual(expectedScale, image.scale); +} + +- (void)testPlaceholderNonsquareImage +{ + CGSize size = {640, 480}; + CGFloat expectedScale = 1.0/160; + CGSize pixelSize = {4, 3}; + UIImage *image = RCTGetPlaceholderImage(size, nil); + RCTAssertEqualSizes(size, image.size); + XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage)); + XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage)); + XCTAssertEqual(expectedScale, image.scale); +} + @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSCExecutorTests.m similarity index 88% rename from Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m rename to Examples/UIExplorer/UIExplorerUnitTests/RCTJSCExecutorTests.m index d00d650467bca0..5d08f8f46f7477 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSCExecutorTests.m @@ -16,24 +16,24 @@ #import -#import "RCTContextExecutor.h" +#import "RCTJSCExecutor.h" #import "RCTUtils.h" #define RUN_PERF_TESTS 0 -@interface RCTContextExecutorTests : XCTestCase +@interface RCTJSCExecutorTests : XCTestCase @end -@implementation RCTContextExecutorTests +@implementation RCTJSCExecutorTests { - RCTContextExecutor *_executor; + RCTJSCExecutor *_executor; } - (void)setUp { [super setUp]; - _executor = [RCTContextExecutor new]; + _executor = [RCTJSCExecutor new]; [_executor setUp]; } @@ -97,8 +97,8 @@ - (void)testDeserializationPerf - (void)testJavaScriptCallSpeed { /** - * Since we almost don't change the RCTContextExecutor logic, and this test is - * very likely to become flaky (specially accross different devices) leave it + * Since we almost don't change the RCTJSCExecutor logic, and this test is + * very likely to become flaky (specially across different devices) leave it * to be run manually * * Previous Values: If you change the executor code, you should update this values @@ -123,8 +123,13 @@ - (void)testJavaScriptCallSpeed } \ } \ }; \ + var Bridge = { \ + callFunctionReturnFlushedQueue: function(module, method, args) { \ + modules[module].apply(modules[module], args); \ + } \ + }; \ function require(module) { \ - return modules[module]; \ + return Bridge; \ } \ "; @@ -138,12 +143,11 @@ function require(module) { \ for (int j = 0; j < runs; j++) { @autoreleasepool { double start = _get_time_nanoseconds(); - [_executor executeJSCall:@"module" - method:@"method" - arguments:params - callback:^(id json, __unused NSError *unused) { - XCTAssert([json isEqual:@YES], @"Invalid return"); - }]; + [_executor callFunctionOnModule:@"module" + method:@"method" + arguments:params + callback:^(__unused id json, __unused NSError *unused) { + }]; double run = _get_time_nanoseconds() - start; if ((j % frequency) == frequency - 1) { // Warmup total += run; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m index fd8343c08c84dc..acacc3a34c4638 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m @@ -78,4 +78,35 @@ - (void)testLeadingWhitespace XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL)); } +- (void)testNotJSONSerializable +{ + NSDictionary *obj = @{@"foo": [NSDate date]}; + NSString *json = @"{\"foo\":null}"; + XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL)); +} + +- (void)testNaN +{ + NSDictionary *obj = @{@"foo": @(NAN)}; + NSString *json = @"{\"foo\":0}"; + XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL)); +} + +- (void)testNotUTF8Convertible +{ + //see https://gist.github.com/0xced/56035d2f57254cf518b5 + NSString *string = [[NSString alloc] initWithBytes:"\xd8\x00" length:2 encoding:NSUTF16StringEncoding]; + NSDictionary *obj = @{@"foo": string}; + NSString *json = @"{\"foo\":null}"; + XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL)); +} + +- (void)testErrorPointer +{ + NSDictionary *obj = @{@"foo": [NSDate date]}; + NSError *error; + XCTAssertNil(RCTJSONStringify(obj, &error)); + XCTAssertNotNil(error); +} + @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m index 3f53812473e719..53d9d0f743365d 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m @@ -23,14 +23,14 @@ @interface RCTMethodArgumentTests : XCTestCase @implementation RCTMethodArgumentTests -extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes); +extern SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **argTypes); - (void)testOneArgument { NSArray *arguments; - NSString *methodName = @"foo:(NSInteger)foo"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:"); + NSString *methodSignature = @"foo:(NSInteger)foo"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); XCTAssertEqual(arguments.count, (NSUInteger)1); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); } @@ -38,9 +38,9 @@ - (void)testOneArgument - (void)testTwoArguments { NSArray *arguments; - NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(NSInteger)foo bar:(BOOL)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -49,9 +49,9 @@ - (void)testTwoArguments - (void)testSpaces { NSArray *arguments; - NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo : (NSInteger)foo bar : (BOOL) bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -60,9 +60,9 @@ - (void)testSpaces - (void)testNewlines { NSArray *arguments; - NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo : (NSInteger)foo\nbar : (BOOL) bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -71,9 +71,9 @@ - (void)testNewlines - (void)testUnnamedArgs { NSArray *arguments; - NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo::"); + NSString *methodSignature = @"foo:(NSInteger)foo:(BOOL)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo::"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -82,9 +82,9 @@ - (void)testUnnamedArgs - (void)testUntypedUnnamedArgs { NSArray *arguments; - NSString *methodName = @"foo:foo:bar:bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:::"); + NSString *methodSignature = @"foo:foo:bar:bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:::"); XCTAssertEqual(arguments.count, (NSUInteger)3); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"id"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"id"); @@ -94,9 +94,9 @@ - (void)testUntypedUnnamedArgs - (void)testAttributes { NSArray *arguments; - NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(__attribute__((unused)) NSString *)foo bar:(__unused BOOL)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -105,9 +105,9 @@ - (void)testAttributes - (void)testNullability { NSArray *arguments; - NSString *methodName = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:baz:"); + NSString *methodSignature = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:baz:"); XCTAssertEqual(arguments.count, (NSUInteger)3); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber"); @@ -120,9 +120,9 @@ - (void)testNullability - (void)testSemicolonStripping { NSArray *arguments; - NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(NSString *)foo bar:(BOOL)bar;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL"); @@ -131,9 +131,9 @@ - (void)testSemicolonStripping - (void)testUnused { NSArray *arguments; - NSString *methodName = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar"; - RCTParseObjCMethodName(&methodName, &arguments); - XCTAssertEqualObjects(methodName, @"foo:bar:"); + NSString *methodSignature = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:"); XCTAssertEqual(arguments.count, (NSUInteger)2); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString"); XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber"); @@ -141,4 +141,44 @@ - (void)testUnused XCTAssertFalse(((RCTMethodArgument *)arguments[1]).unused); } +- (void)testGenericArray +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSArray *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArray"); +} + +- (void)testNestedGenericArray +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSArray *> *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArrayArray"); +} + +- (void)testGenericSet +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSSet *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberSet"); +} + +- (void)testGenericDictionary +{ + NSArray *arguments; + NSString *methodSignature = @"foo:(NSDictionary *)foo;"; + SEL selector = RCTParseMethodSignature(methodSignature, &arguments); + XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:"); + XCTAssertEqual(arguments.count, (NSUInteger)1); + XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberDictionary"); +} + @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m new file mode 100644 index 00000000000000..77a997a4baa59c --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m @@ -0,0 +1,129 @@ +/** + * 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. + */ + +#import +#import + +#import "RCTBridge.h" +#import "RCTBridge+Private.h" +#import "RCTBridgeModule.h" +#import "RCTUtils.h" +#import "RCTUIManager.h" +#import "RCTViewManager.h" + +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION)) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ + } \ +} + +@interface RCTTestViewManager : RCTViewManager +@end + +@implementation RCTTestViewManager + +RCT_EXPORT_MODULE() + +- (NSArray *)customDirectEventTypes +{ + return @[@"foo"]; +} + +@end + + +@interface RCTNotificationObserverModule : NSObject + +@property (nonatomic, assign) BOOL didDetectViewManagerInit; + +@end + +@implementation RCTNotificationObserverModule + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInitViewManager:) name:RCTDidInitializeModuleNotification object:nil]; +} + +- (void)didInitViewManager:(NSNotification *)note +{ + id module = note.userInfo[@"module"]; + if ([module isKindOfClass:[RCTTestViewManager class]]) { + _didDetectViewManagerInit = YES; + } +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end + + +@interface RCTModuleInitNotificationRaceTests : XCTestCase +{ + RCTBridge *_bridge; + RCTNotificationObserverModule *_notificationObserver; +} +@end + +@implementation RCTModuleInitNotificationRaceTests + +- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge +{ + return nil; +} + +- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge +{ + return @[[RCTTestViewManager new], _notificationObserver]; +} + +- (void)setUp +{ + [super setUp]; + + _notificationObserver = [RCTNotificationObserverModule new]; + _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil]; +} + +- (void)tearDown +{ + [super tearDown]; + + _notificationObserver = nil; + id jsExecutor = _bridge.batchedBridge.javaScriptExecutor; + [_bridge invalidate]; + RUN_RUNLOOP_WHILE(jsExecutor.isValid); + _bridge = nil; +} + +- (void)testViewManagerNotInitializedBeforeSetBridgeModule +{ + RUN_RUNLOOP_WHILE(!_notificationObserver.didDetectViewManagerInit); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m new file mode 100644 index 00000000000000..83a549736809ff --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m @@ -0,0 +1,261 @@ +/** + * 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. + */ + +#import +#import + +#import "RCTBridge.h" +#import "RCTBridge+Private.h" +#import "RCTBridgeModule.h" +#import "RCTUtils.h" + +#define RUN_RUNLOOP_WHILE(CONDITION) \ +{ \ + NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \ + while ((CONDITION)) { \ + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \ + if ([timeout timeIntervalSinceNow] <= 0) { \ + XCTFail(@"Runloop timed out before condition was met"); \ + break; \ + } \ + } \ +} + + +@interface RCTTestInjectedModule : NSObject +@end + +@implementation RCTTestInjectedModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +@end + + +@interface RCTTestCustomInitModule : NSObject + +@property (nonatomic, assign) BOOL initializedOnMainThread; + +@end + +@implementation RCTTestCustomInitModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (id)init +{ + if ((self = [super init])) { + _initializedOnMainThread = [NSThread isMainThread]; + } + return self; +} + +@end + + +@interface RCTTestCustomSetBridgeModule : NSObject + +@property (nonatomic, assign) BOOL setBridgeOnMainThread; + +@end + +@implementation RCTTestCustomSetBridgeModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + _setBridgeOnMainThread = [NSThread isMainThread]; +} + +@end + + +@interface RCTTestExportConstantsModule : NSObject + +@property (nonatomic, assign) BOOL exportedConstants; +@property (nonatomic, assign) BOOL exportedConstantsOnMainThread; + +@end + +@implementation RCTTestExportConstantsModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +- (NSDictionary *)constantsToExport +{ + _exportedConstants = YES; + _exportedConstantsOnMainThread = [NSThread isMainThread]; + return @{ @"foo": @"bar" }; +} + +@end + + +@interface RCTLazyInitModule : NSObject +@end + +@implementation RCTLazyInitModule + +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + +RCT_EXPORT_MODULE() + +@end + + +@interface RCTModuleInitTests : XCTestCase +{ + RCTBridge *_bridge; + BOOL _injectedModuleInitNotificationSent; + BOOL _customInitModuleNotificationSent; + BOOL _customSetBridgeModuleNotificationSent; + BOOL _exportConstantsModuleNotificationSent; + BOOL _lazyInitModuleNotificationSent; + BOOL _lazyInitModuleNotificationSentOnMainThread; + BOOL _viewManagerModuleNotificationSent; + RCTTestInjectedModule *_injectedModule; +} +@end + +@implementation RCTModuleInitTests + +- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge +{ + return nil; +} + +- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge +{ + return @[_injectedModule]; +} + +- (void)setUp +{ + [super setUp]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moduleDidInit:) name:RCTDidInitializeModuleNotification object:nil]; + + _injectedModuleInitNotificationSent = NO; + _customInitModuleNotificationSent = NO; + _customSetBridgeModuleNotificationSent = NO; + _exportConstantsModuleNotificationSent = NO; + _lazyInitModuleNotificationSent = NO; + _viewManagerModuleNotificationSent = NO; + _injectedModule = [RCTTestInjectedModule new]; + _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil]; +} + +- (void)tearDown +{ + [super tearDown]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTDidInitializeModuleNotification object:nil]; + + id jsExecutor = _bridge.batchedBridge.javaScriptExecutor; + [_bridge invalidate]; + RUN_RUNLOOP_WHILE(jsExecutor.isValid); + _bridge = nil; +} + +- (void)moduleDidInit:(NSNotification *)note +{ + id module = note.userInfo[@"module"]; + if ([module isKindOfClass:[RCTTestInjectedModule class]]) { + _injectedModuleInitNotificationSent = YES; + } else if ([module isKindOfClass:[RCTTestCustomInitModule class]]) { + _customInitModuleNotificationSent = YES; + } else if ([module isKindOfClass:[RCTTestCustomSetBridgeModule class]]) { + _customSetBridgeModuleNotificationSent = YES; + } else if ([module isKindOfClass:[RCTTestExportConstantsModule class]]) { + _exportConstantsModuleNotificationSent = YES; + } else if ([module isKindOfClass:[RCTLazyInitModule class]]) { + _lazyInitModuleNotificationSent = YES; + _lazyInitModuleNotificationSentOnMainThread = [NSThread isMainThread]; + } +} + +- (void)testInjectedModulesInitializedDuringBridgeInit +{ + XCTAssertEqual(_injectedModule, [_bridge moduleForClass:[RCTTestInjectedModule class]]); + XCTAssertEqual(_injectedModule.bridge, _bridge.batchedBridge); + XCTAssertNotNil(_injectedModule.methodQueue); + RUN_RUNLOOP_WHILE(!_injectedModuleInitNotificationSent); + XCTAssertTrue(_injectedModuleInitNotificationSent); +} + +- (void)testCustomInitModuleInitializedAtBridgeStartup +{ + RUN_RUNLOOP_WHILE(!_customInitModuleNotificationSent); + XCTAssertTrue(_customInitModuleNotificationSent); + RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]]; + XCTAssertTrue(module.initializedOnMainThread); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +- (void)testCustomSetBridgeModuleInitializedAtBridgeStartup +{ + RUN_RUNLOOP_WHILE(!_customSetBridgeModuleNotificationSent); + XCTAssertTrue(_customSetBridgeModuleNotificationSent); + RCTTestCustomSetBridgeModule *module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]]; + XCTAssertTrue(module.setBridgeOnMainThread); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +- (void)testExportConstantsModuleInitializedAtBridgeStartup +{ + RUN_RUNLOOP_WHILE(!_exportConstantsModuleNotificationSent); + XCTAssertTrue(_exportConstantsModuleNotificationSent); + RCTTestExportConstantsModule *module = [_bridge moduleForClass:[RCTTestExportConstantsModule class]]; + RUN_RUNLOOP_WHILE(!module.exportedConstants); + XCTAssertTrue(module.exportedConstants); + XCTAssertTrue(module.exportedConstantsOnMainThread); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +- (void)testLazyInitModuleNotInitializedDuringBridgeInit +{ + XCTAssertFalse(_lazyInitModuleNotificationSent); + + __block RCTLazyInitModule *module; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + module = [_bridge moduleForClass:[RCTLazyInitModule class]]; + }); + + RUN_RUNLOOP_WHILE(!module); + XCTAssertTrue(_lazyInitModuleNotificationSent); + XCTAssertFalse(_lazyInitModuleNotificationSentOnMainThread); + XCTAssertNotNil(module); + XCTAssertEqual(module.bridge, _bridge.batchedBridge); + XCTAssertNotNil(module.methodQueue); +} + +@end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m index 93ac3b5aa047b4..f247da4ffc755f 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m @@ -47,10 +47,10 @@ - (void)doFooWithBar:(__unused NSString *)bar { } - (void)testNonnull { - NSString *methodName = @"doFooWithBar:(nonnull NSString *)bar"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithBar:(nonnull NSString *)bar"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertFalse(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[@"Hello World"]]; })); @@ -72,40 +72,40 @@ - (void)testNumbersNonnull { // Specifying an NSNumber param without nonnull isn't allowed XCTAssertTrue(RCTLogsError(^{ - NSString *methodName = @"doFooWithNumber:(NSNumber *)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithNumber:(NSNumber *)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; // Invoke method to trigger parsing [method invokeWithBridge:nil module:self arguments:@[@1]]; })); } { - NSString *methodName = @"doFooWithNumber:(nonnull NSNumber *)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithNumber:(nonnull NSNumber *)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertTrue(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]]; })); } { - NSString *methodName = @"doFooWithDouble:(double)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithDouble:(double)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertTrue(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]]; })); } { - NSString *methodName = @"doFooWithInteger:(NSInteger)n"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithInteger:(NSInteger)n"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; XCTAssertTrue(RCTLogsError(^{ [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]]; })); @@ -114,10 +114,10 @@ - (void)testNumbersNonnull - (void)testStructArgument { - NSString *methodName = @"doFooWithCGRect:(CGRect)s"; - RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + NSString *methodSignature = @"doFooWithCGRect:(CGRect)s"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; CGRect r = CGRectMake(10, 20, 30, 40); [method invokeWithBridge:nil module:self arguments:@[@[@10, @20, @30, @40]]]; @@ -126,13 +126,13 @@ - (void)testStructArgument - (void)testWhitespaceTolerance { - NSString *methodName = @"doFoo : \t (NSString *)foo"; + NSString *methodSignature = @"doFoo : \t (NSString *)foo"; __block RCTModuleMethod *method; XCTAssertFalse(RCTLogsError(^{ - method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature + JSMethodName:nil + moduleClass:[self class]]; })); XCTAssertEqualObjects(method.JSMethodName, @"doFoo"); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index f2715947a4eaf8..cfe3959bc756e6 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -17,11 +17,23 @@ #import "RCTShadowView.h" @interface RCTShadowViewTests : XCTestCase - +@property (nonatomic, strong) RCTShadowView *parentView; @end @implementation RCTShadowViewTests +- (void)setUp +{ + [super setUp]; + + self.parentView = [self _shadowViewWithStyle:^(css_style_t *style) { + style->flex_direction = CSS_FLEX_DIRECTION_COLUMN; + style->dimensions[0] = 440; + style->dimensions[1] = 440; + }]; + self.parentView.reactTag = @1; // must be valid rootView tag +} + // Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy. // // ==================================== @@ -69,33 +81,87 @@ - (void)testApplyingLayoutRecursivelyToShadowView style->flex = 1; }]; - RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) { - style->flex_direction = CSS_FLEX_DIRECTION_COLUMN; - style->padding[0] = 10; - style->padding[1] = 10; - style->padding[2] = 10; - style->padding[3] = 10; - style->dimensions[0] = 440; - style->dimensions[1] = 440; - }]; + self.parentView.cssNode->style.padding[0] = 10; + self.parentView.cssNode->style.padding[1] = 10; + self.parentView.cssNode->style.padding[2] = 10; + self.parentView.cssNode->style.padding[3] = 10; + + [self.parentView insertReactSubview:headerView atIndex:0]; + [self.parentView insertReactSubview:mainView atIndex:1]; + [self.parentView insertReactSubview:footerView atIndex:2]; - [parentView insertReactSubview:headerView atIndex:0]; - [parentView insertReactSubview:mainView atIndex:1]; - [parentView insertReactSubview:footerView atIndex:2]; + [self.parentView collectRootUpdatedFrames]; - parentView.reactTag = @1; // must be valid rootView tag - [parentView collectRootUpdatedFrames]; + XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440))); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10))); - XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440))); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10))); + XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 10, 420, 100))); + XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 420, 200))); + XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 330, 420, 100))); - XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100))); - XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200))); - XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100))); + XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 100, 200))); + XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(120, 120, 200, 200))); + XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200))); +} - XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200))); - XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200))); - XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200))); +- (void)testAssignsSuggestedWidthDimension +{ + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->position[CSS_LEFT] = 0; + style->position[CSS_TOP] = 0; + style->dimensions[CSS_HEIGHT] = 10; + } + assertRelativeLayout:CGRectMake(0, 0, 3, 10) + withIntrinsicContentSize:CGSizeMake(3, UIViewNoIntrinsicMetric)]; +} + +- (void)testAssignsSuggestedHeightDimension +{ + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->position[CSS_LEFT] = 0; + style->position[CSS_TOP] = 0; + style->dimensions[CSS_WIDTH] = 10; + } + assertRelativeLayout:CGRectMake(0, 0, 10, 4) + withIntrinsicContentSize:CGSizeMake(UIViewNoIntrinsicMetric, 4)]; +} + +- (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions +{ + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->position[CSS_LEFT] = 0; + style->position[CSS_TOP] = 0; + style->dimensions[CSS_WIDTH] = 10; + style->dimensions[CSS_HEIGHT] = 10; + } + assertRelativeLayout:CGRectMake(0, 0, 10, 10) + withIntrinsicContentSize:CGSizeMake(3, 4)]; +} + +- (void)testDoesNotAssignSuggestedDimensionsWhenStyledWithFlexAttribute +{ + float parentWidth = self.parentView.cssNode->style.dimensions[CSS_WIDTH]; + float parentHeight = self.parentView.cssNode->style.dimensions[CSS_HEIGHT]; + [self _withShadowViewWithStyle:^(css_style_t *style) { + style->flex = 1; + } + assertRelativeLayout:CGRectMake(0, 0, parentWidth, parentHeight) + withIntrinsicContentSize:CGSizeMake(3, 4)]; +} + +- (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock + assertRelativeLayout:(CGRect)expectedRect + withIntrinsicContentSize:(CGSize)contentSize +{ + RCTShadowView *view = [self _shadowViewWithStyle:styleBlock]; + [self.parentView insertReactSubview:view atIndex:0]; + view.intrinsicContentSize = contentSize; + [self.parentView collectRootUpdatedFrames]; + CGRect actualRect = [view measureLayoutRelativeToAncestor:self.parentView]; + XCTAssertTrue(CGRectEqualToRect(expectedRect, actualRect), + @"Expected layout to be %@, got %@", + NSStringFromCGRect(expectedRect), + NSStringFromCGRect(actualRect)); } - (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m new file mode 100644 index 00000000000000..c2f6db568765d6 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m @@ -0,0 +1,96 @@ +/** + * 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. + */ + +#import +#import "RCTUtils.h" + +@interface RCTURLUtilsTests : XCTestCase + +@end + +@implementation RCTURLUtilsTests + +- (void)testGetQueryParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar&bar=foo"]; + NSString *foo = RCTGetURLQueryParam(URL, @"foo"); + NSString *bar = RCTGetURLQueryParam(URL, @"bar"); + XCTAssertEqualObjects(foo, @"bar"); + XCTAssertEqualObjects(bar, @"foo"); +} + +- (void)testGetEncodedParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=You%20%26%20Me"]; + NSString *foo = RCTGetURLQueryParam(URL, @"foo"); + XCTAssertEqualObjects(foo, @"You & Me"); +} + +- (void)testQueryParamNotFound +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar"]; + NSString *bar = RCTGetURLQueryParam(URL, @"bar"); + XCTAssertNil(bar); +} + +- (void)testDuplicateParamTakesLatter +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar&foo=foo"]; + NSString *foo = RCTGetURLQueryParam(URL, @"foo"); + XCTAssertEqualObjects(foo, @"foo"); +} + +- (void)testNilURLGetQueryParam +{ + NSURL *URL = nil; + NSString *foo = RCTGetURLQueryParam(URL, @"foo"); + XCTAssertNil(foo); +} + +- (void)testReplaceParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar&bar=foo"]; + NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"foo"); + XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=foo&bar=foo"); +} + +- (void)testReplaceEncodedParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=You%20%26%20Me"]; + NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"Me & You"); + XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=Me%20%26%20You"); +} + +- (void)testAppendParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?bar=foo"]; + NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"bar"); + XCTAssertEqualObjects(result.absoluteString, @"http://example.com?bar=foo&foo=bar"); +} + +- (void)testRemoveParam +{ + NSURL *URL = [NSURL URLWithString:@"http://example.com?bar=foo&foo=bar"]; + NSURL *result = RCTURLByReplacingQueryParam(URL, @"bar", nil); + XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=bar"); +} + +- (void)testNilURLAppendQueryParam +{ + NSURL *URL = nil; + NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"bar"); + XCTAssertNil(result); +} + +@end diff --git a/Examples/UIExplorer/VibrationExample.js b/Examples/UIExplorer/VibrationExample.js new file mode 100644 index 00000000000000..11f17d368b86b7 --- /dev/null +++ b/Examples/UIExplorer/VibrationExample.js @@ -0,0 +1,54 @@ +/** + * 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 + */ +'use strict'; + +var React = require('react-native'); +var { + StyleSheet, + View, + Text, + TouchableHighlight, + Vibration, +} = React; + +exports.framework = 'React'; +exports.title = 'Vibration'; +exports.description = 'Vibration API'; +exports.examples = [{ + title: 'Vibration.vibrate()', + render() { + return ( + Vibration.vibrate()}> + + Vibrate + + + ); + }, +}]; + +var styles = StyleSheet.create({ + wrapper: { + borderRadius: 5, + marginBottom: 5, + }, + button: { + backgroundColor: '#eeeeee', + padding: 10, + }, +}); diff --git a/Examples/UIExplorer/ViewExample.js b/Examples/UIExplorer/ViewExample.js index c20d7e11996ad5..deed55067e2058 100644 --- a/Examples/UIExplorer/ViewExample.js +++ b/Examples/UIExplorer/ViewExample.js @@ -40,16 +40,6 @@ var ViewBorderStyleExample = React.createClass({ }, render() { - if (Platform.OS !== 'android') { - return ( - - - borderStyle is only supported on android for now. - - - ); - } - return ( diff --git a/Examples/UIExplorer/ViewPagerAndroidExample.android.js b/Examples/UIExplorer/ViewPagerAndroidExample.android.js index e4f33458e95b36..0e672ee8cf99b7 100644 --- a/Examples/UIExplorer/ViewPagerAndroidExample.android.js +++ b/Examples/UIExplorer/ViewPagerAndroidExample.android.js @@ -25,6 +25,8 @@ var { ViewPagerAndroid, } = React; +import type { ViewPagerScrollState } from 'ViewPagerAndroid'; + var PAGES = 5; var BGCOLOR = ['#fdc08e', '#fff6b9', '#99d1b7', '#dde5fe', '#f79273']; var IMAGE_URIS = [ @@ -114,6 +116,10 @@ var ViewPagerAndroidExample = React.createClass({ this.setState({progress: e.nativeEvent}); }, + onPageScrollStateChanged: function(state : ViewPagerScrollState) { + this.setState({scrollState: state}); + }, + move: function(delta) { var page = this.state.page + delta; this.go(page); @@ -155,6 +161,7 @@ var ViewPagerAndroidExample = React.createClass({ initialPage={0} onPageScroll={this.onPageScroll} onPageSelected={this.onPageSelected} + onPageScrollStateChanged={this.onPageScrollStateChanged} ref={viewPager => { this.viewPager = viewPager; }}> {pages} @@ -170,6 +177,7 @@ var ViewPagerAndroidExample = React.createClass({ enabled={true} onPress={() => this.setState({animationsAreEnabled: true})} /> } + ScrollState[ {this.state.scrollState} ]