diff --git a/.eslintrc b/.eslintrc index 5a7692b800d282..612fdaf405c76f 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": { diff --git a/.gitignore b/.gitignore index 74ab1dc77337a4..fe61f843f91de6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ project.xcworkspace /Examples/**/android/app/build/ /ReactAndroid/build/ +# Buck +.buckd +buck-out + # Android .idea .gradle 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/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/ImageEditingExample.js b/Examples/UIExplorer/ImageEditingExample.js index 29b2dfd84166b6..1a8c931221d526 100644 --- a/Examples/UIExplorer/ImageEditingExample.js +++ b/Examples/UIExplorer/ImageEditingExample.js @@ -22,6 +22,7 @@ var { Image, ImageEditor, NativeModules, + Platform, ScrollView, StyleSheet, Text, @@ -30,8 +31,6 @@ var { View, } = React; -var RCTScrollViewConsts = UIManager.RCTScrollView.Constants; - var PAGE_SIZE = 20; type ImageOffset = { @@ -67,22 +66,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() { @@ -190,29 +188,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, @@ -248,19 +266,15 @@ class ImageCropper extends React.Component { } render() { - var decelerationRate = - RCTScrollViewConsts && RCTScrollViewConsts.DecelerationRate ? - RCTScrollViewConsts.DecelerationRate.Fast : - 0; - return ( - - - - + + + + ); }, @@ -320,19 +320,19 @@ exports.examples = [ diff --git a/Examples/UIExplorer/ListViewExample.js b/Examples/UIExplorer/ListViewExample.js index c4df7c7a969c3b..ef16151ed22147 100644 --- a/Examples/UIExplorer/ListViewExample.js +++ b/Examples/UIExplorer/ListViewExample.js @@ -30,7 +30,7 @@ var UIExplorerPage = require('./UIExplorerPage'); var ListViewSimpleExample = React.createClass({ statics: { - title: ' - Simple', + title: '', description: 'Performant, scrollable list of data.' }, @@ -50,7 +50,7 @@ var ListViewSimpleExample = React.createClass({ render: function() { return ( - Simple'} + title={this.props.navigator ? null : ''} noSpacer={true} noScroll={true}> ), }}/>; } }, + { + title: 'Annotation focus example', + render() { + return { + alert('Annotation gets focus'); + }, + onBlur: () => { + alert('Annotation lost focus'); + } + }}/>; + } + }, { title: 'Draggable pin', render() { diff --git a/Examples/UIExplorer/PickerAndroidExample.js b/Examples/UIExplorer/PickerAndroidExample.js index 91a2bd5a405eb5..0f76f6e8480a34 100644 --- a/Examples/UIExplorer/PickerAndroidExample.js +++ b/Examples/UIExplorer/PickerAndroidExample.js @@ -16,25 +16,31 @@ 'use strict'; const React = require('react-native'); +const StyleSheet = require('StyleSheet'); const UIExplorerBlock = require('UIExplorerBlock'); const UIExplorerPage = require('UIExplorerPage'); const { - PickerAndroid, + Picker, Text, TouchableWithoutFeedback, } = React; -const Item = PickerAndroid.Item; +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.', + }, -const PickerAndroidExample = React.createClass({ getInitialState: function() { return { selected1: 'key1', selected2: 'key1', selected3: 'key1', - selected4: 'key1', color: 'red', - mode: PickerAndroid.MODE_DIALOG, + mode: Picker.MODE_DIALOG, }; }, @@ -42,101 +48,93 @@ const PickerAndroidExample = React.createClass({ render: function() { return ( - + - - - - + + + + - - - - + + + + - - - - - - - - - - - - Tap here to switch between dialog/dropdown. - + + + - - - - + + + - - - - + + + + - You can not change the value of this picker because it doesn't set a selected prop on - its items. + Cannot change the value of this picker because it doesn't update selectedValue. - - - - - - + + + + - - - - + + + + ); }, changeMode: function() { - const newMode = this.state.mode === PickerAndroid.MODE_DIALOG - ? PickerAndroid.MODE_DROPDOWN - : PickerAndroid.MODE_DIALOG; + const newMode = this.state.mode === Picker.MODE_DIALOG + ? Picker.MODE_DROPDOWN + : Picker.MODE_DIALOG; this.setState({mode: newMode}); }, - onSelect: function(key, value) { + onValueChange: function(key: string, value: string) { const newState = {}; newState[key] = value; this.setState(newState); }, }); -exports.title = ''; -exports.displayName = 'PickerAndroidExample'; -exports.description = 'The Android Picker component provides multiple options to choose from'; -exports.examples = [ - { - title: 'PickerAndroidExample', - render(): ReactElement { return ; } +var styles = StyleSheet.create({ + picker: { + width: 100, }, -]; +}); + +module.exports = PickerExample; diff --git a/Examples/UIExplorer/RefreshControlExample.js b/Examples/UIExplorer/RefreshControlExample.js index b8feefb661db07..f56c9fed51884b 100644 --- a/Examples/UIExplorer/RefreshControlExample.js +++ b/Examples/UIExplorer/RefreshControlExample.js @@ -69,7 +69,7 @@ const RefreshControlExample = React.createClass({ isRefreshing: false, loaded: 0, rowData: Array.from(new Array(20)).map( - (val, i) => ({text: 'Initial row' + i, clicks: 0})), + (val, i) => ({text: 'Initial row ' + i, clicks: 0})), }; }, @@ -108,7 +108,7 @@ const RefreshControlExample = React.createClass({ // prepend 10 items const rowData = Array.from(new Array(10)) .map((val, i) => ({ - text: 'Loaded row' + (+this.state.loaded + i), + text: 'Loaded row ' + (+this.state.loaded + i), clicks: 0, })) .concat(this.state.rowData); 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/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index 7b0bf2c0b3334b..dd3b2c9c89f7a6 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -23,6 +23,7 @@ var { Text, TouchableHighlight, TouchableOpacity, + UIManager, View, } = React; @@ -85,6 +86,13 @@ 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', }]; var TextOnPressBox = React.createClass({ @@ -133,18 +141,18 @@ 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 +177,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 +206,40 @@ 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 heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'}; var styles = StyleSheet.create({ @@ -241,6 +283,14 @@ var styles = StyleSheet.create({ borderColor: '#f0f0f0', backgroundColor: '#f9f9f9', }, + forceTouchBox: { + padding: 10, + margin: 10, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#f0f0f0', + backgroundColor: '#f9f9f9', + alignItems: 'center', + }, textBlock: { fontWeight: '500', color: 'blue', 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_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/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js index 01e8a331e62ad5..367b256de49f34 100644 --- a/Examples/UIExplorer/UIExplorerList.android.js +++ b/Examples/UIExplorer/UIExplorerList.android.js @@ -25,12 +25,12 @@ var UIExplorerListBase = require('./UIExplorerListBase'); var COMPONENTS = [ require('./ImageExample'), require('./ListViewExample'), + require('./PickerAndroidExample'), require('./ProgressBarAndroidExample'), + require('./PullToRefreshViewAndroidExample.android'), + require('./RefreshControlExample'), require('./ScrollViewSimpleExample'), require('./SwitchExample'), - require('./RefreshControlExample'), - require('./PickerAndroidExample'), - require('./PullToRefreshViewAndroidExample.android'), require('./TextExample.android'), require('./TextInputExample.android'), require('./ToolbarAndroidExample'), @@ -47,13 +47,16 @@ var APIS = [ require('./BorderExample'), require('./CameraRollExample'), require('./ClipboardExample'), + require('./DatePickerAndroidExample'), require('./GeolocationExample'), + require('./ImageEditingExample'), require('./IntentAndroidExample.android'), require('./LayoutEventsExample'), require('./LayoutExample'), require('./NetInfoExample'), require('./PanResponderExample'), require('./PointerEventsExample'), + require('./TimePickerAndroidExample'), require('./TimerExample'), require('./ToastAndroidExample.android'), require('./XHRExample'), diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index f33d97c86134c1..3fa57d818bfbbc 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -71,6 +71,7 @@ var APIS = [ require('./CameraRollExample'), require('./ClipboardExample'), require('./GeolocationExample'), + require('./ImageEditingExample'), require('./LayoutExample'), require('./NetInfoExample'), require('./PanResponderExample'), @@ -82,7 +83,6 @@ var APIS = [ require('./TransformExample'), require('./VibrationIOSExample'), require('./XHRExample.ios'), - require('./ImageEditingExample'), ]; type Props = { 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/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} ]