diff --git a/Examples/UIExplorer/DatePickerExample.js b/Examples/UIExplorer/DatePickerExample.js new file mode 100644 index 000000000..a212546f4 --- /dev/null +++ b/Examples/UIExplorer/DatePickerExample.js @@ -0,0 +1,157 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule DatePickerExample + */ +'use strict'; + +var React = require('react-native'); +var { + DatePickerIOS, + StyleSheet, + Text, + TextInput, + View, +} = React; + +var DatePickerExample = React.createClass({ + getDefaultProps: function () { + return { + date: new Date(), + timeZoneOffsetInHours: (-1) * (new Date()).getTimezoneOffset() / 60, + }; + }, + + getInitialState: function() { + return { + date: this.props.date, + timeZoneOffsetInHours: this.props.timeZoneOffsetInHours, + }; + }, + + onDateChange: function(date) { + this.setState({date: date}); + }, + + onTimezoneChange: function(event) { + var offset = parseInt(event.nativeEvent.text, 10); + if (isNaN(offset)) { + return; + } + this.setState({timeZoneOffsetInHours: offset}); + }, + + render: function() { + // Ideally, the timezone input would be a picker rather than a + // text input, but we don't have any pickers yet :( + return ( + + + { + this.state.date.toLocaleDateString() + + ' ' + + this.state.date.toLocaleTimeString() + } + + + + hours from UTC + + + + + + + + + ); + }, +}); + +var WithLabel = React.createClass({ + render: function() { + return ( + + + + {this.props.label} + + + {this.props.children} + + ); + } +}); + +var Heading = React.createClass({ + render: function() { + return ( + + + {this.props.label} + + + ); + } +}); + +exports.title = ''; +exports.description = 'Select dates and times using the native UIDatePicker.'; +exports.examples = [ +{ + title: '', + render: function() { + return ; + }, +}]; + +var styles = StyleSheet.create({ + textinput: { + height: 26, + width: 50, + borderWidth: 0.5, + borderColor: '#0f0f0f', + padding: 4, + fontSize: 13, + }, + labelContainer: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: 2, + }, + labelView: { + marginRight: 10, + paddingVertical: 2, + }, + label: { + fontWeight: 'bold', + }, + headingContainer: { + padding: 4, + backgroundColor: '#f6f7f8', + }, + heading: { + fontWeight: 'bold', + fontSize: 14, + }, +}); diff --git a/Examples/UIExplorer/GeoLocationExample.js b/Examples/UIExplorer/GeoLocationExample.js index 1ab5f290c..fac3dd205 100644 --- a/Examples/UIExplorer/GeoLocationExample.js +++ b/Examples/UIExplorer/GeoLocationExample.js @@ -1,7 +1,7 @@ /** * Copyright 2004-present Facebook. All Rights Reserved. * - * @providesModule GeoLocationExample + * @providesModule GeolocationExample */ /* eslint no-console: 0 */ 'use strict'; @@ -15,19 +15,19 @@ var { } = React; exports.framework = 'React'; -exports.title = 'GeoLocation'; -exports.description = 'Examples of using the GeoLocation API.'; +exports.title = 'Geolocation'; +exports.description = 'Examples of using the Geolocation API.'; exports.examples = [ { title: 'navigator.geolocation', render: function() { - return ; + return ; }, } ]; -var GeoLocationExample = React.createClass({ +var GeolocationExample = React.createClass({ getInitialState: function() { return { initialPosition: 'unknown', diff --git a/Examples/UIExplorer/SliderExample.js b/Examples/UIExplorer/SliderExample.js new file mode 100644 index 000000000..0ff4f6713 --- /dev/null +++ b/Examples/UIExplorer/SliderExample.js @@ -0,0 +1,57 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule SliderExample + */ +'use strict'; + +var React = require('react-native'); +var { + Slider, + Text, + StyleSheet, + View, +} = React; + +var SliderExample = React.createClass({ + getInitialState() { + return { + value: 0, + }; + }, + + render() { + return ( + + + {this.state.value} + + this.setState({value: value})} /> + + ); + } +}); + +var styles = StyleSheet.create({ + slider: { + height: 10, + margin: 10, + }, + text: { + fontSize: 14, + textAlign: 'center', + fontWeight: 'bold', + margin: 10, + }, +}); + +exports.title = ''; +exports.description = 'Slider input for numeric values'; +exports.examples = [ + { + title: 'Slider', + render() { return ; } + } +]; diff --git a/Examples/UIExplorer/SwitchExample.js b/Examples/UIExplorer/SwitchExample.js new file mode 100644 index 000000000..9aa63833f --- /dev/null +++ b/Examples/UIExplorer/SwitchExample.js @@ -0,0 +1,141 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule SwitchExample + */ +'use strict'; + +var React = require('react-native'); +var { + SwitchIOS, + Text, + View +} = React; + +var BasicSwitchExample = React.createClass({ + getInitialState() { + return { + trueSwitchIsOn: true, + falseSwitchIsOn: false, + }; + }, + render() { + return ( + + this.setState({falseSwitchIsOn: value})} + style={{marginBottom: 10}} + value={this.state.falseSwitchIsOn} /> + this.setState({trueSwitchIsOn: value})} + value={this.state.trueSwitchIsOn} /> + + ); + } +}); + +var DisabledSwitchExample = React.createClass({ + render() { + return ( + + + + + ); + }, +}); + +var ColorSwitchExample = React.createClass({ + getInitialState() { + return { + colorTrueSwitchIsOn: true, + colorFalseSwitchIsOn: false, + }; + }, + 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" + tintColor="#ff0000" + value={this.state.colorTrueSwitchIsOn} /> + + ); + }, +}); + +var EventSwitchExample = React.createClass({ + getInitialState() { + return { + eventSwitchIsOn: false, + eventSwitchRegressionIsOn: true, + }; + }, + render() { + 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} /> + {this.state.eventSwitchRegressionIsOn ? "On" : "Off"} + + + ); + } +}); + +exports.title = ''; +exports.description = 'Native boolean input'; +exports.examples = [ + { + title: 'Switches can be set to true or false', + render() { return ; } + }, + { + title: 'Switches can be disabled', + render() { return ; } + }, + { + title: 'Custom colors can be provided', + render() { return ; } + }, + { + title: 'Change events can be detected', + render() { return ; } + }, + { + title: 'Switches are controlled components', + render() { return ; } + } +]; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 942ec24b0..ea13d28e3 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; }; 134180021AA9153C003F314A /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FFF1AA91531003F314A /* libReactKit.a */; }; 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; }; + 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; @@ -46,6 +47,13 @@ remoteGlobalIDString = 58B511DB1A9E6C8500147676; remoteInfo = RCTNetwork; }; + 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -53,6 +61,7 @@ 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = ""; }; 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; + 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -67,6 +76,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */, 1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */, 134180011AA9153C003F314A /* libRCTText.a in Frameworks */, 134180021AA9153C003F314A /* libReactKit.a in Frameworks */, @@ -80,6 +90,7 @@ 1316A21D1AA397F400C0188E /* Libraries */ = { isa = PBXGroup; children = ( + 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */, 134180261AA91779003F314A /* RCTNetwork.xcodeproj */, 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */, @@ -120,6 +131,14 @@ name = Products; sourceTree = ""; }; + 134A8A211AACED6A00945AAE /* Products */ = { + isa = PBXGroup; + children = ( + 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; 13B07FAE1A68108700A75B9A /* UIExplorer */ = { isa = PBXGroup; children = ( @@ -191,6 +210,10 @@ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; projectReferences = ( + { + ProductGroup = 134A8A211AACED6A00945AAE /* Products */; + ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */; + }, { ProductGroup = 13417FE41AA91428003F314A /* Products */; ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */; @@ -244,6 +267,13 @@ remoteRef = 1341802A1AA91779003F314A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/UIExplorer/UIExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..e668abba3 --- /dev/null +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 606840567..e04edcab6 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -31,8 +31,11 @@ var EXAMPLES = [ require('./TouchableExample'), require('./ActivityIndicatorExample'), require('./ScrollViewExample'), - require('./GeoLocationExample'), + require('./DatePickerExample'), + require('./GeolocationExample'), require('./TabBarExample'), + require('./SwitchExample'), + require('./SliderExample'), ]; var UIExplorerList = React.createClass({ diff --git a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js index 5bba89830..df762c5ee 100644 --- a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js +++ b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js @@ -15,7 +15,6 @@ var View = require('View'); var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); var keyMirror = require('keyMirror'); -var keyOf = require('keyOf'); var merge = require('merge'); var SpinnerSize = keyMirror({ diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js new file mode 100644 index 000000000..b4a6d5bb1 --- /dev/null +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -0,0 +1,155 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule DatePickerIOS + * + * This is a controlled component version of RKDatePickerIOS + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var PropTypes = require('ReactPropTypes'); +var React = require('React'); +var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var RKDatePickerIOSConsts = require('NativeModules').RKUIManager.RCTDatePicker.Constants; +var StyleSheet = require('StyleSheet'); +var View = require('View'); + +var createReactIOSNativeComponentClass = + require('createReactIOSNativeComponentClass'); +var merge = require('merge'); + +var DATEPICKER = 'datepicker'; + +/** + * Use `DatePickerIOS` to render a date/time picker (selector) on iOS. This is + * a controlled component, so you must hook in to the `onDateChange` callback + * and update the `date` prop in order for the component to update, otherwise + * the user's change will be reverted immediately to reflect `props.date` as the + * source of truth. + */ +var DatePickerIOS = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + /** + * The currently selected date. + */ + date: PropTypes.instanceOf(Date).isRequired, + + /** + * Date change handler. + * + * This is called when the user changes the date or time in the UI. + * The first and only argument is a Date object representing the new + * date and time. + */ + onDateChange: PropTypes.func.isRequired, + + /** + * Maximum date. + * + * Restricts the range of possible date/time values. + */ + maximumDate: PropTypes.instanceOf(Date), + + /** + * Minimum date. + * + * Restricts the range of possible date/time values. + */ + minimumDate: PropTypes.instanceOf(Date), + + /** + * The date picker mode. + * + * Valid modes on iOS are: 'date', 'time', 'datetime'. + */ + mode: PropTypes.oneOf(Object.keys(RKDatePickerIOSConsts.DatePickerModes)), + + /** + * The interval at which minutes can be selected. + */ + minuteInterval: PropTypes.oneOf([1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]), + + /** + * Timezone offset in seconds. + * + * By default, the date picker will use the device's timezone. With this + * parameter, it is possible to force a certain timezone offset. For + * instance, to show times in Pacific Standard Time, pass -7 * 60. + */ + timeZoneOffsetInMinutes: PropTypes.number, + }, + + getDefaultProps: function() { + return { + mode: 'datetime', + }; + }, + + _onChange: function(event) { + var nativeTimeStamp = event.nativeEvent.timestamp; + this.props.onDateChange && this.props.onDateChange( + new Date(nativeTimeStamp) + ); + this.props.onChange && this.props.onChange(event); + + // We expect the onChange* handlers to be in charge of updating our `date` + // prop. That way they can also disallow/undo/mutate the selection of + // certain values. In other words, the embedder of this component should + // be the source of truth, not the native component. + var propsTimeStamp = this.props.date.getTime(); + if (nativeTimeStamp !== propsTimeStamp) { + this.refs[DATEPICKER].setNativeProps({ + date: propsTimeStamp, + }); + } + }, + + render: function() { + var props = this.props; + return ( + + + + ); + } +}); + +var styles = StyleSheet.create({ + rkDatePickerIOS: { + height: RKDatePickerIOSConsts.ComponentHeight, + width: RKDatePickerIOSConsts.ComponentWidth, + }, +}); + +var rkDatePickerIOSAttributes = merge(ReactIOSViewAttributes.UIView, { + date: true, + maximumDate: true, + minimumDate: true, + mode: true, + minuteInterval: true, + timeZoneOffsetInMinutes: true, +}); + +var RKDatePickerIOS = createReactIOSNativeComponentClass({ + validAttributes: rkDatePickerIOSAttributes, + uiViewClassName: 'RCTDatePicker', +}); + +module.exports = DatePickerIOS; diff --git a/Libraries/Components/ListView/ListView.js b/Libraries/Components/ListView/ListView.js index aa2f82e28..1eb7eef5d 100644 --- a/Libraries/Components/ListView/ListView.js +++ b/Libraries/Components/ListView/ListView.js @@ -19,29 +19,42 @@ var isEmpty = require('isEmpty'); var PropTypes = React.PropTypes; +var DEFAULT_PAGE_SIZE = 1; +var DEFAULT_INITIAL_ROWS = 10; +var DEFAULT_SCROLL_RENDER_AHEAD = 1000; +var DEFAULT_END_REACHED_THRESHOLD = 1000; +var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; +var RENDER_INTERVAL = 20; +var SCROLLVIEW_REF = 'listviewscroll'; + + /** * ListView - A core component designed for efficient display of vertically * scrolling lists of changing data. The minimal API is to create a - * `ListViewDataSource`, populate it with a simple array of data blobs, and + * `ListView.DataSource`, populate it with a simple array of data blobs, and * instantiate a `ListView` component with that data source and a `renderRow` * callback which takes a blob from the data array and returns a renderable - * component. Minimal example: + * component. * - * getInitialState: function() { - * var ds = new ListViewDataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - * return { - * dataSource: ds.cloneWithRows(['row 1', 'row 2']), - * }; - * }, + * Minimal example: * - * render: function() { - * return ( - * {rowData}} - * /> - * ); - * }, + * ``` + * getInitialState: function() { + * var ds = new ListViewDataSource({rowHasChanged: (r1, r2) => r1 !== r2}); + * return { + * dataSource: ds.cloneWithRows(['row 1', 'row 2']), + * }; + * }, + * + * render: function() { + * return ( + * {rowData}} + * /> + * ); + * }, + * ``` * * ListView also supports more advanced features, including sections with sticky * section headers, header and footer support, callbacks on reaching the end of @@ -61,19 +74,8 @@ var PropTypes = React.PropTypes; * event-loop (customizable with the `pageSize` prop). This breaks up the * work into smaller chunks to reduce the chance of dropping frames while * rendering rows. - * - * Check out `ListViewSimpleExample.js`, `ListViewDataSource.js`, and the Movies - * app for more info and example usage. */ -var DEFAULT_PAGE_SIZE = 1; -var DEFAULT_INITIAL_ROWS = 10; -var DEFAULT_SCROLL_RENDER_AHEAD = 1000; -var DEFAULT_END_REACHED_THRESHOLD = 1000; -var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; -var RENDER_INTERVAL = 20; -var SCROLLVIEW_REF = 'listviewscroll'; - var ListView = React.createClass({ mixins: [ScrollResponder.Mixin, TimerMixin], diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 527b10bd2..ded8ce8d9 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -19,80 +19,6 @@ var invariant = require('invariant'); var logError = require('logError'); var merge = require('merge'); -/** - * NavigatorIOS wraps UIKit navigation and allows you to add back-swipe - * functionality across your app. - * - * See UIExplorerApp and NavigatorIOSExample for a full example - * - * ======================= NavigatorIOS Routes ================================ - * A route is an object used to describe each page in the navigator. The first - * route is provided to NavigatorIOS as `initialRoute`: - * - * ``` - * render: function() { - * return ( - * - * ); - * }, - * ``` - * - * Now MyView will be rendered by the navigator. It will recieve the route - * object in the `route` prop, a navigator, and all of the props specified in - * `passProps`. - * - * See the initialRoute propType for a complete definition of a route. - * - * ====================== NavigatorIOS Navigator ============================== - * A `navigator` is an object of navigation functions that a view can call. It - * is passed as a prop to any component rendered by NavigatorIOS. - * - * ``` - * var MyView = React.createClass({ - * _handleBackButtonPress: function() { - * this.props.navigator.pop(); - * }, - * _handleNextButtonPress: function() { - * this.props.navigator.push(nextRoute); - * }, - * ... - * }); - * ``` - * - * A navigation object contains the following functions: - * - `push(route)` - Navigate forward to a new route - * - `pop()` - Go back one page - * - `popN(n)` - Go back N pages at once. When N=1, behavior matches `pop()` - * - `replace(route)` - Replace the route for the current page and immediately - * load the view for the new route - * - `replacePrevious(route)` - Replace the route/view for the previous page - * - `replacePreviousAndPop(route)` - Replaces the previous route/view and - * transitions back to it - * - `resetTo(route)` - Replaces the top item and popToTop - * - `popToRoute(route)` - Go back to the item for a particular route object - * - `popToTop()` - Go back to the top item - * - * Navigator functions are also available on the NavigatorIOS component: - * - * ``` - * var MyView = React.createClass({ - * _handleNavigationRequest: function() { - * this.refs.nav.push(otherRoute); - * }, - * render: () => ( - * - * ), - * }); - * ``` - * - */ var TRANSITIONER_REF = 'transitionerRef'; var PropTypes = React.PropTypes; @@ -153,6 +79,83 @@ var NavigatorTransitionerIOS = React.createClass({ * `` also removes children that will no longer be needed * (after the pop of a child has been fully completed/animated out). */ + +/** + * NavigatorIOS wraps UIKit navigation and allows you to add back-swipe + * functionality across your app. + * + * #### Routes + * A route is an object used to describe each page in the navigator. The first + * route is provided to NavigatorIOS as `initialRoute`: + * + * ``` + * render: function() { + * return ( + * + * ); + * }, + * ``` + * + * Now MyView will be rendered by the navigator. It will recieve the route + * object in the `route` prop, a navigator, and all of the props specified in + * `passProps`. + * + * See the initialRoute propType for a complete definition of a route. + * + * #### Navigator + * + * A `navigator` is an object of navigation functions that a view can call. It + * is passed as a prop to any component rendered by NavigatorIOS. + * + * ``` + * var MyView = React.createClass({ + * _handleBackButtonPress: function() { + * this.props.navigator.pop(); + * }, + * _handleNextButtonPress: function() { + * this.props.navigator.push(nextRoute); + * }, + * ... + * }); + * ``` + * + * A navigation object contains the following functions: + * + * - `push(route)` - Navigate forward to a new route + * - `pop()` - Go back one page + * - `popN(n)` - Go back N pages at once. When N=1, behavior matches `pop()` + * - `replace(route)` - Replace the route for the current page and immediately + * load the view for the new route + * - `replacePrevious(route)` - Replace the route/view for the previous page + * - `replacePreviousAndPop(route)` - Replaces the previous route/view and + * transitions back to it + * - `resetTo(route)` - Replaces the top item and popToTop + * - `popToRoute(route)` - Go back to the item for a particular route object + * - `popToTop()` - Go back to the top item + * + * Navigator functions are also available on the NavigatorIOS component: + * + * ``` + * var MyView = React.createClass({ + * _handleNavigationRequest: function() { + * this.refs.nav.push(otherRoute); + * }, + * render: () => ( + * + * ), + * }); + * ``` + * + */ var NavigatorIOS = React.createClass({ propTypes: { @@ -199,7 +202,7 @@ var NavigatorIOS = React.createClass({ /** * Styles for the navigation item containing the component */ - wrapperStyle: View.stylePropType, + wrapperStyle: View.propTypes.style, }).isRequired, @@ -207,7 +210,7 @@ var NavigatorIOS = React.createClass({ * The default wrapper style for components in the navigator. * A common use case is to set the backgroundColor for every page */ - itemWrapperStyle: View.stylePropType, + itemWrapperStyle: View.propTypes.style, /** * The color used for buttons in the navigation bar diff --git a/Libraries/Components/ScrollView/ScrollView.ios.js b/Libraries/Components/ScrollView/ScrollView.js similarity index 58% rename from Libraries/Components/ScrollView/ScrollView.ios.js rename to Libraries/Components/ScrollView/ScrollView.js index 08202f445..09f7b94d6 100644 --- a/Libraries/Components/ScrollView/ScrollView.ios.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -5,26 +5,27 @@ */ 'use strict'; -var ArrayOfPropType = require('ArrayOfPropType'); +var EdgeInsetsPropType = require('EdgeInsetsPropType'); +var Platform = require('Platform'); +var PointPropType = require('PointPropType'); +var RCTScrollView = require('NativeModules').RKUIManager.RCTScrollView; +var RCTScrollViewConsts = RCTScrollView.Constants; var React = require('React'); -var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var ReactIOSTagHandles = require('ReactIOSTagHandles'); -var RKScrollView = require('NativeModules').RKUIManager.RCTScrollView; -var RKScrollViewConsts = RKScrollView.Constants; +var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var RKUIManager = require('NativeModulesDeprecated').RKUIManager; var ScrollResponder = require('ScrollResponder'); -var ScrollViewPropTypes = require('ScrollViewPropTypes'); -var StyleSheetPropType = require('StyleSheetPropType'); var StyleSheet = require('StyleSheet'); +var StyleSheetPropType = require('StyleSheetPropType'); var View = require('View'); var ViewStylePropTypes = require('ViewStylePropTypes'); var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var deepDiffer = require('deepDiffer'); var flattenStyle = require('flattenStyle'); +var insetsDiffer = require('insetsDiffer'); var invariant = require('invariant'); -var merge = require('merge'); -var nativePropType = require('nativePropType'); -var validAttributesFromPropTypes = require('validAttributesFromPropTypes'); +var pointsDiffer = require('pointsDiffer'); var PropTypes = React.PropTypes; @@ -32,13 +33,13 @@ var SCROLLVIEW = 'ScrollView'; var INNERVIEW = 'InnerScrollView'; var keyboardDismissModeConstants = { - 'none': RKScrollViewConsts.KeyboardDismissMode.None, // default - 'interactive': RKScrollViewConsts.KeyboardDismissMode.Interactive, - 'onDrag': RKScrollViewConsts.KeyboardDismissMode.OnDrag, + 'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default + 'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive, + 'onDrag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag, }; /** - * `React` component that wraps platform `RKScrollView` while providing + * Component that wraps platform ScrollView while providing * integration with touch locking "responder" system. * * Doesn't yet support other contained responders from blocking this scroll @@ -46,28 +47,49 @@ var keyboardDismissModeConstants = { */ var ScrollView = React.createClass({ + + // Only for compatibility with Android which is not yet up to date, + // DO NOT ADD NEW CALL SITES! + statics: { + keyboardDismissMode: { + None: 'none', + Interactive: 'interactive', + OnDrag: 'onDrag', + }, + }, + propTypes: { - ...ScrollViewPropTypes, + automaticallyAdjustContentInsets: PropTypes.bool, // true + contentInset: EdgeInsetsPropType, // zeros + contentOffset: PointPropType, // zeros + onScroll: PropTypes.func, + onScrollAnimationEnd: PropTypes.func, + scrollEnabled: PropTypes.bool, // tre + scrollIndicatorInsets: EdgeInsetsPropType, // zeros + showsHorizontalScrollIndicator: PropTypes.bool, + showsVerticalScrollIndicator: PropTypes.bool, + style: StyleSheetPropType(ViewStylePropTypes), + throttleScrollCallbackMS: PropTypes.number, // null /** * When true, the scroll view bounces horizontally when it reaches the end * even if the content is smaller than the scroll view itself. The default * value is true when `horizontal={true}` and false otherwise. */ - alwaysBounceHorizontal: nativePropType(PropTypes.bool), + alwaysBounceHorizontal: PropTypes.bool, /** * When true, the scroll view bounces vertically when it reaches the end * even if the content is smaller than the scroll view itself. The default * value is false when `horizontal={true}` and true otherwise. */ - alwaysBounceVertical: nativePropType(PropTypes.bool), + alwaysBounceVertical: PropTypes.bool, /** * When true, the scroll view automatically centers the content when the * content is smaller than the scroll view bounds; when the content is * larger than the scroll view, this property has no effect. The default * value is false. */ - centerContent: nativePropType(PropTypes.bool), + centerContent: PropTypes.bool, /** * These styles will be applied to the scroll view content container which * wraps all of the child views. Example: @@ -90,7 +112,7 @@ var ScrollView = React.createClass({ * - Normal: 0.998 (the default) * - Fast: 0.9 */ - decelerationRate: nativePropType(PropTypes.number), + decelerationRate: PropTypes.number, /** * When true, the scroll view's children are arranged horizontally in a row * instead of vertically in a column. The default value is false. @@ -115,26 +137,26 @@ var ScrollView = React.createClass({ * taps, and the keyboard will not dismiss automatically. The default value * is false. */ - keyboardShouldPersistTaps: nativePropType(PropTypes.bool), + keyboardShouldPersistTaps: PropTypes.bool, /** * The maximum allowed zoom scale. The default value is 1.0. */ - maximumZoomScale: nativePropType(PropTypes.number), + maximumZoomScale: PropTypes.number, /** * The minimum allowed zoom scale. The default value is 1.0. */ - minimumZoomScale: nativePropType(PropTypes.number), + minimumZoomScale: PropTypes.number, /** * When true, the scroll view stops on multiples of the scroll view's size * when scrolling. This can be used for horizontal pagination. The default * value is false. */ - pagingEnabled: nativePropType(PropTypes.bool), + pagingEnabled: PropTypes.bool, /** * When true, the scroll view scrolls to top when the status bar is tapped. * The default value is true. */ - scrollsToTop: nativePropType(PropTypes.bool), + scrollsToTop: PropTypes.bool, /** * An array of child indices determining which children get docked to the * top of the screen when scrolling. For example, passing @@ -142,7 +164,7 @@ var ScrollView = React.createClass({ * top of the scroll view. This property is not supported in conjunction * with `horizontal={true}`. */ - stickyHeaderIndices: nativePropType(ArrayOfPropType(PropTypes.number)), + stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), /** * Experimental: When true, offscreen child views (whose `overflow` value is * `hidden`) are removed from their native backing superview when offscreen. @@ -153,7 +175,7 @@ var ScrollView = React.createClass({ /** * The current scale of the scroll view content. The default value is 1.0. */ - zoomScale: nativePropType(PropTypes.number), + zoomScale: PropTypes.number, }, mixins: [ScrollResponder.Mixin], @@ -171,7 +193,11 @@ var ScrollView = React.createClass({ }, scrollTo: function(destY, destX) { - RKUIManager.scrollTo(ReactIOSTagHandles.rootNodeIDToTag[this._rootNodeID], destX, destY); + RKUIManager.scrollTo( + ReactIOSTagHandles.rootNodeIDToTag[this._rootNodeID], + destX || 0, + destY || 0 + ); }, render: function() { @@ -223,36 +249,47 @@ var ScrollView = React.createClass({ this.props.alwaysBounceVertical : !this.props.horizontal; - var props = merge( - this.props, { - alwaysBounceHorizontal, - alwaysBounceVertical, - keyboardDismissMode: this.props.keyboardDismissMode ? - keyboardDismissModeConstants[this.props.keyboardDismissMode] : - undefined, - style: [styles.base, this.props.style], - onTouchStart: this.scrollResponderHandleTouchStart, - onTouchMove: this.scrollResponderHandleTouchMove, - onTouchEnd: this.scrollResponderHandleTouchEnd, - onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, - onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, - onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin, - onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd, - onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, - onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, - onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, - onScroll: this.scrollResponderHandleScroll, - onResponderGrant: this.scrollResponderHandleResponderGrant, - onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, - onResponderTerminate: this.scrollResponderHandleTerminate, - onResponderRelease: this.scrollResponderHandleResponderRelease, - onResponderReject: this.scrollResponderHandleResponderReject, + var props = { + ...this.props, + alwaysBounceHorizontal, + alwaysBounceVertical, + keyboardDismissMode: this.props.keyboardDismissMode ? + keyboardDismissModeConstants[this.props.keyboardDismissMode] : + undefined, + style: [styles.base, this.props.style], + onTouchStart: this.scrollResponderHandleTouchStart, + onTouchMove: this.scrollResponderHandleTouchMove, + onTouchEnd: this.scrollResponderHandleTouchEnd, + onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, + onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, + onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin, + onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd, + onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, + onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, + onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, + onScroll: this.scrollResponderHandleScroll, + onResponderGrant: this.scrollResponderHandleResponderGrant, + onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, + onResponderTerminate: this.scrollResponderHandleTerminate, + onResponderRelease: this.scrollResponderHandleResponderRelease, + onResponderReject: this.scrollResponderHandleResponderReject, + }; + + var ScrollViewClass; + if (Platform.OS === 'ios') { + ScrollViewClass = RCTScrollView; + } else if (Platform.OS === 'android') { + if (this.props.horizontal) { + ScrollViewClass = AndroidHorizontalScrollView; + } else { + ScrollViewClass = AndroidScrollView; } - ); + } + return ( - + {contentContainer} - + ); } }); @@ -267,12 +304,46 @@ var styles = StyleSheet.create({ }, }); -var RKScrollView = createReactIOSNativeComponentClass({ - validAttributes: merge( - ReactIOSViewAttributes.UIView, - validAttributesFromPropTypes(ScrollView.propTypes) - ), - uiViewClassName: 'RCTScrollView', -}); +var validAttributes = { + ...ReactIOSViewAttributes.UIView, + alwaysBounceHorizontal: true, + alwaysBounceVertical: true, + automaticallyAdjustContentInsets: true, + centerContent: true, + contentInset: insetsDiffer, + contentOffset: pointsDiffer, + decelerationRate: true, + horizontal: true, + keyboardDismissMode: true, + keyboardShouldPersistTaps: true, + maximumZoomScale: true, + minimumZoomScale: true, + pagingEnabled: true, + removeClippedSubviews: true, + scrollEnabled: true, + scrollIndicatorInsets: insetsDiffer, + scrollsToTop: true, + showsHorizontalScrollIndicator: true, + showsVerticalScrollIndicator: true, + stickyHeaderIndices: deepDiffer, + throttleScrollCallbackMS: true, + zoomScale: true, +}; + +if (Platform.OS === 'android') { + var AndroidScrollView = createReactIOSNativeComponentClass({ + validAttributes: validAttributes, + uiViewClassName: 'AndroidScrollView', + }); + var AndroidHorizontalScrollView = createReactIOSNativeComponentClass({ + validAttributes: validAttributes, + uiViewClassName: 'AndroidHorizontalScrollView', + }); +} else if (Platform.OS === 'ios') { + var RCTScrollView = createReactIOSNativeComponentClass({ + validAttributes: validAttributes, + uiViewClassName: 'RCTScrollView', + }); +} module.exports = ScrollView; diff --git a/Libraries/Components/ScrollViewPropTypes.js b/Libraries/Components/ScrollViewPropTypes.js deleted file mode 100644 index f72dc9f05..000000000 --- a/Libraries/Components/ScrollViewPropTypes.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule ScrollViewPropTypes - */ -'use strict'; - -var EdgeInsetsPropType = require('EdgeInsetsPropType'); -var PointPropType = require('PointPropType'); -var PropTypes = require('ReactPropTypes'); -var StyleSheetPropType = require('StyleSheetPropType'); -var ViewStylePropTypes = require('ViewStylePropTypes'); - -var nativePropType = require('nativePropType'); - -var ScrollViewPropTypes = { - automaticallyAdjustContentInsets: nativePropType(PropTypes.bool), // true - contentInset: nativePropType(EdgeInsetsPropType), // zeroes - contentOffset: nativePropType(PointPropType), // zeroes - onScroll: PropTypes.func, - onScrollAnimationEnd: PropTypes.func, - scrollEnabled: nativePropType(PropTypes.bool), // true - scrollIndicatorInsets: nativePropType(EdgeInsetsPropType), // zeros - showsHorizontalScrollIndicator: nativePropType(PropTypes.bool), - showsVerticalScrollIndicator: nativePropType(PropTypes.bool), - style: StyleSheetPropType(ViewStylePropTypes), - throttleScrollCallbackMS: nativePropType(PropTypes.number), // null -}; - -module.exports = ScrollViewPropTypes; diff --git a/Libraries/Components/Slider/Slider.js b/Libraries/Components/Slider/Slider.js new file mode 100644 index 000000000..80f631c45 --- /dev/null +++ b/Libraries/Components/Slider/Slider.js @@ -0,0 +1,83 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule Slider + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var PropTypes = require('ReactPropTypes'); +var React = require('React'); +var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var StyleSheet = require('StyleSheet'); +var View = require('View'); + +var createReactIOSNativeComponentClass = + require('createReactIOSNativeComponentClass'); +var merge = require('merge'); + +var Slider = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + /** + * Used to style and layout the `Slider`. See `StyleSheet.js` and + * `ViewStylePropTypes.js` for more info. + */ + style: View.propTypes.style, + + /** + * Initial value of the slider. The value should be between 0 and 1. + * Default value is 0. + * + * *This is not a controlled component*, e.g. if you don't update + * the value, the component won't be reseted to it's inital value. + */ + value: PropTypes.number, + + /** + * Callback continuously called while the user is dragging the slider. + */ + onValueChange: PropTypes.func, + + /** + * Callback called when the user finishes changing the value (e.g. when + * the slider is released). + */ + onSlidingComplete: PropTypes.func, + }, + + _onValueChange: function(event) { + this.props.onChange && this.props.onChange(event); + if (event.nativeEvent.continuous) { + this.props.onValueChange && + this.props.onValueChange(event.nativeEvent.value); + } else { + this.props.onSlidingComplete && event.nativeEvent.value !== undefined && + this.props.onSlidingComplete(event.nativeEvent.value); + } + }, + + render: function() { + return ( + + ); + } +}); + +var styles = StyleSheet.create({ + slider: { + height: 40, + }, +}); + +var RKSlider = createReactIOSNativeComponentClass({ + validAttributes: merge(ReactIOSViewAttributes.UIView, {value: true}), + uiViewClassName: 'RCTSlider', +}); + +module.exports = Slider; diff --git a/Libraries/Components/SwitchIOS/SwitchIOS.android.js b/Libraries/Components/SwitchIOS/SwitchIOS.android.js new file mode 100644 index 000000000..72c24fa28 --- /dev/null +++ b/Libraries/Components/SwitchIOS/SwitchIOS.android.js @@ -0,0 +1,41 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule SwitchIOS + */ + +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); + +var DummySwitchIOS = React.createClass({ + render: function() { + return ( + + SwitchIOS is not supported on this platform! + + ); + }, +}); + +var styles = StyleSheet.create({ + dummySwitchIOS: { + width: 120, + height: 50, + backgroundColor: '#ffbcbc', + borderWidth: 1, + borderColor: 'red', + alignItems: 'center', + justifyContent: 'center', + }, + text: { + color: '#333333', + margin: 5, + fontSize: 10, + } +}); + +module.exports = DummySwitchIOS; diff --git a/Libraries/Components/SwitchIOS/SwitchIOS.ios.js b/Libraries/Components/SwitchIOS/SwitchIOS.ios.js new file mode 100644 index 000000000..391b4da3a --- /dev/null +++ b/Libraries/Components/SwitchIOS/SwitchIOS.ios.js @@ -0,0 +1,117 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule SwitchIOS + * + * This is a controlled component version of RKSwitch. + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var PropTypes = require('ReactPropTypes'); +var React = require('React'); +var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); +var StyleSheet = require('StyleSheet'); + +var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); +var merge = require('merge'); + +var SWITCH = 'switch'; + +/** + * Use `SwitchIOS` to render a boolean input on iOS. This is + * a controlled component, so you must hook in to the `onValueChange` callback + * and update the `value` prop in order for the component to update, otherwise + * the user's change will be reverted immediately to reflect `props.value` as the + * source of truth. + */ +var SwitchIOS = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + /** + * The value of the switch, if true the switch will be turned on. + * Default value is false. + */ + value: PropTypes.bool, + + /** + * If true the user won't be able to toggle the switch. + * Default value is false. + */ + disabled: PropTypes.bool, + + /** + * Callback that is called when the user toggles the switch. + */ + onValueChange: PropTypes.func, + + /** + * Background color when the switch is turned on. + */ + onTintColor: PropTypes.string, + + /** + * Background color for the switch round button. + */ + thumbTintColor: PropTypes.string, + + /** + * Background color when the switch is turned off. + */ + tintColor: PropTypes.string, + }, + + getDefaultProps: function() { + return { + value: false, + disabled: false, + }; + }, + + _onChange: function(event) { + this.props.onChange && this.props.onChange(event); + this.props.onValueChange && this.props.onValueChange(event.nativeEvent.value); + + // The underlying switch might have changed, but we're controlled, + // and so want to ensure it represents our value. + this.refs[SWITCH].setNativeProps({on: this.props.value}); + }, + + render: function() { + return ( + + ); + } +}); + +var styles = StyleSheet.create({ + rkSwitch: { + height: 31, + width: 51, + }, +}); + +var rkSwitchAttributes = merge(ReactIOSViewAttributes.UIView, { + onTintColor: true, + tintColor: true, + thumbTintColor: true, + on: true, + enabled: true, +}); + +var RKSwitch = createReactIOSNativeComponentClass({ + validAttributes: rkSwitchAttributes, + uiViewClassName: 'RCTSwitch', +}); + +module.exports = SwitchIOS; diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js index 5e6cc5307..763be7438 100644 --- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js +++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js @@ -18,12 +18,12 @@ var merge = require('merge'); var TabBarItemIOS = React.createClass({ propTypes: { - icon: Image.sourcePropType.isRequired, + icon: Image.propTypes.source.isRequired, onPress: React.PropTypes.func.isRequired, selected: React.PropTypes.bool.isRequired, badgeValue: React.PropTypes.string, title: React.PropTypes.string, - style: View.stylePropType, + style: View.propTypes.style, }, getInitialState: function() { diff --git a/Libraries/Components/TextInput/TextInput.ios.js b/Libraries/Components/TextInput/TextInput.ios.js index aadd6c94a..20eda688b 100644 --- a/Libraries/Components/TextInput/TextInput.ios.js +++ b/Libraries/Components/TextInput/TextInput.ios.js @@ -26,39 +26,6 @@ var getObjectValues = require('getObjectValues'); var invariant = require('invariant'); var merge = require('merge'); -/** - * - A foundational component for inputting text into the app via a - * keyboard. Props provide configurability for several features, such as auto- - * correction, auto-capitalization, placeholder text, and different keyboard - * types, such as a numeric keypad. - * - * The simplest use case is to plop down a `TextInput` and subscribe to the - * `onChangeText` events to read the user input. There are also other events, such - * as `onSubmitEditing` and `onFocus` that can be subscribed to. A simple - * example: - * - * - * this.setState({input: text})} - * /> - * {'user input: ' + this.state.input} - * - * - * The `value` prop can be used to set the value of the input in order to make - * the state of the component clear, but does not behave as a true - * controlled component by default because all operations are asynchronous. - * Setting `value` once is like setting the default value, but you can change it - * continuously based on `onChangeText` events as well. If you really want to - * force the component to always revert to the value you are setting, you can - * set `controlled={true}`. - * - * The `multiline` prop is not supported in all releases, and some props are - * multiline only. - * - * More example code in `TextInputExample.js`. - */ - var autoCapitalizeConsts = RKUIManager.UIText.AutocapitalizationType; var clearButtonModeConsts = RKUIManager.UITextField.clearButtonMode; @@ -92,6 +59,39 @@ var notMultiline = { onSubmitEditing: true, }; +/** + * A foundational component for inputting text into the app via a + * keyboard. Props provide configurability for several features, such as auto- + * correction, auto-capitalization, placeholder text, and different keyboard + * types, such as a numeric keypad. + * + * The simplest use case is to plop down a `TextInput` and subscribe to the + * `onChangeText` events to read the user input. There are also other events, such + * as `onSubmitEditing` and `onFocus` that can be subscribed to. A simple + * example: + * + * ``` + * + * this.setState({input: text})} + * /> + * {'user input: ' + this.state.input} + * + * ``` + * + * The `value` prop can be used to set the value of the input in order to make + * the state of the component clear, but does not behave as a true + * controlled component by default because all operations are asynchronous. + * Setting `value` once is like setting the default value, but you can change it + * continuously based on `onChangeText` events as well. If you really want to + * force the component to always revert to the value you are setting, you can + * set `controlled={true}`. + * + * The `multiline` prop is not supported in all releases, and some props are + * multiline only. + */ + var TextInput = React.createClass({ propTypes: { /** @@ -188,7 +188,7 @@ var TextInput = React.createClass({ 'always', ]), - style: Text.stylePropType, + style: Text.propTypes.style, }, /** diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 9381f52d6..a48e2f299 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -20,35 +20,35 @@ var keyOf = require('keyOf'); var merge = require('merge'); var onlyChild = require('onlyChild'); +var DEFAULT_PROPS = { + activeOpacity: 0.8, + underlayColor: 'black', +}; + /** - * TouchableHighlight - A wrapper for making views respond properly to touches. + * A wrapper for making views respond properly to touches. * On press down, the opacity of the wrapped view is decreased, which allows * the underlay color to show through, darkening or tinting the view. The * underlay comes from adding a view to the view hierarchy, which can sometimes * cause unwanted visual artifacts if not used correctly, for example if the * backgroundColor of the wrapped view isn't explicitly set to an opaque color. + * * Example: * - * renderButton: function() { - * return ( - * - * - * - * ); - * }, - * - * More example code in TouchableExample.js, and more in-depth discussion in - * Touchable.js. See also TouchableWithoutFeedback.js. + * ``` + * renderButton: function() { + * return ( + * + * + * + * ); + * }, + * ``` */ -var DEFAULT_PROPS = { - activeOpacity: 0.8, - underlayColor: 'black', -}; - var TouchableHighlight = React.createClass({ propTypes: { ...TouchableFeedbackPropType, @@ -67,7 +67,7 @@ var TouchableHighlight = React.createClass({ * active. */ underlayColor: React.PropTypes.string, - style: View.stylePropType, + style: View.propTypes.style, }, mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin], diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index cb68d6df3..1b2f3898f 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -17,25 +17,25 @@ var keyOf = require('keyOf'); var onlyChild = require('onlyChild'); /** - * TouchableOpacity - A wrapper for making views respond properly to touches. + * A wrapper for making views respond properly to touches. * On press down, the opacity of the wrapped view is decreased, dimming it. * This is done without actually changing the view hierarchy, and in general is - * easy to add to an app without weird side-effects. Example: + * easy to add to an app without weird side-effects. * - * renderButton: function() { - * return ( - * - * - * - * ); - * }, + * Example: * - * More example code in TouchableExample.js, and more in-depth discussion in - * Touchable.js. See also TouchableHighlight.js and - * TouchableWithoutFeedback.js. + * ``` + * renderButton: function() { + * return ( + * + * + * + * ); + * }, + * ``` */ var TouchableOpacity = React.createClass({ diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 826c395bc..b75988f4a 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -12,8 +12,13 @@ var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); var StyleSheetPropType = require('StyleSheetPropType'); var ViewStylePropTypes = require('ViewStylePropTypes'); + +var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); + +var stylePropType = StyleSheetPropType(ViewStylePropTypes); + /** - * - The most fundamental component for building UI, `View` is a + * The most fundamental component for building UI, `View` is a * container that supports layout with flexbox, style, some touch handling, and * accessibility controls, and is designed to be nested inside other views and * to have 0 to many children of any type. `View` maps directly to the native @@ -21,11 +26,13 @@ var ViewStylePropTypes = require('ViewStylePropTypes'); * `UIView`, `
`, `android.view`, etc. This example creates a `View` that * wraps two colored boxes and custom component in a row with padding. * - * - * - * - * - * + * ``` + * + * + * + * + * + * ``` * * By default, `View`s have a primary flex direction of 'column', so children * will stack up vertically by default. `View`s also expand to fill the parent @@ -39,20 +46,8 @@ var ViewStylePropTypes = require('ViewStylePropTypes'); * `View`s are designed to be used with `StyleSheet`s for clarity and * performance, although inline styles are also supported. It is common for * `StyleSheet`s to be combined dynamically. See `StyleSheet.js` for more info. - * - * Check out `ViewExample.js`, `LayoutExample.js`, and other apps for more code - * examples. */ - -var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); - -var stylePropType = StyleSheetPropType(ViewStylePropTypes); - var View = React.createClass({ - statics: { - stylePropType, - }, - mixins: [NativeMethodsMixin], /** @@ -141,12 +136,12 @@ var RKView = createReactIOSNativeComponentClass({ validAttributes: ReactIOSViewAttributes.RKView, uiViewClassName: 'RCTView', }); +RKView.propTypes = View.propTypes; var ViewToExport = RKView; if (__DEV__) { ViewToExport = View; } -ViewToExport.stylePropType = stylePropType; module.exports = ViewToExport; diff --git a/Libraries/GeoLocation/GeoLocation.js b/Libraries/GeoLocation/Geolocation.ios.js similarity index 65% rename from Libraries/GeoLocation/GeoLocation.js rename to Libraries/GeoLocation/Geolocation.ios.js index 9a7f792c4..1b5719212 100644 --- a/Libraries/GeoLocation/GeoLocation.js +++ b/Libraries/GeoLocation/Geolocation.ios.js @@ -1,12 +1,12 @@ /** * Copyright 2004-present Facebook. All Rights Reserved. * - * @providesModule GeoLocation + * @providesModule Geolocation */ 'use strict'; var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); -var RCTLocationObserver = require('NativeModules').RKLocationObserver; +var RCTLocationObserver = require('NativeModulesDeprecated').RKLocationObserver; var invariant = require('invariant'); var logError = require('logError'); @@ -16,13 +16,6 @@ var subscriptions = []; var updatesEnabled = false; -var ensureObserving = function() { - if (!updatesEnabled) { - RCTLocationObserver.startObserving(); - updatesEnabled = true; - } -}; - /** * /!\ ATTENTION /!\ * You need to add NSLocationWhenInUseUsageDescription key @@ -30,43 +23,51 @@ var ensureObserving = function() { * to *fail silently*! * \!/ \!/ * - * GeoLocation follows the MDN specification: + * Geolocation follows the MDN specification: * https://developer.mozilla.org/en-US/docs/Web/API/Geolocation */ -class GeoLocation { - static getCurrentPosition(geo_success, geo_error, geo_options) { +var Geolocation = { + + getCurrentPosition: function(geo_success, geo_error, geo_options) { invariant( typeof geo_success === 'function', 'Must provide a valid geo_success callback.' ); - if (geo_options) { - warning('geo_options are not yet supported.'); - } - ensureObserving(); RCTLocationObserver.getCurrentPosition( geo_success, - geo_error || logError + geo_error || logError, + geo_options || {} ); - } - static watchPosition(callback) { - ensureObserving(); + }, + + watchPosition: function(success, error, options) { + if (!updatesEnabled) { + RCTLocationObserver.startObserving(options || {}); + updatesEnabled = true; + } var watchID = subscriptions.length; - subscriptions.push( + subscriptions.push([ RCTDeviceEventEmitter.addListener( - 'geoLocationDidChange', - callback - ) - ); + 'geolocationDidChange', + success + ), + error ? RCTDeviceEventEmitter.addListener( + 'geolocationError', + error + ) : null, + ]); return watchID; - } - static clearWatch(watchID) { + }, + + clearWatch: function(watchID) { var sub = subscriptions[watchID]; if (!sub) { // Silently exit when the watchID is invalid or already cleared // This is consistent with timers return; } - sub.remove(); + sub[0].remove(); + sub[1] && sub[1].remove(); subscriptions[watchID] = undefined; var noWatchers = true; for (var ii = 0; ii < subscriptions.length; ii++) { @@ -75,10 +76,11 @@ class GeoLocation { } } if (noWatchers) { - GeoLocation.stopObserving(); + Geolocation.stopObserving(); } - } - static stopObserving() { + }, + + stopObserving: function() { if (updatesEnabled) { RCTLocationObserver.stopObserving(); updatesEnabled = false; @@ -89,10 +91,8 @@ class GeoLocation { } } subscriptions = []; - } else { - warning('Tried to stop observing when not observing.'); } } } -module.exports = GeoLocation; +module.exports = Geolocation; diff --git a/Libraries/GeoLocation/RCTGeolocation.xcodeproj/project.pbxproj b/Libraries/GeoLocation/RCTGeolocation.xcodeproj/project.pbxproj new file mode 100644 index 000000000..94f9c3f91 --- /dev/null +++ b/Libraries/GeoLocation/RCTGeolocation.xcodeproj/project.pbxproj @@ -0,0 +1,256 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 134814061AA4E45400B7C361 /* RCTLocationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 134814051AA4E45400B7C361 /* RCTLocationObserver.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814041AA4E45400B7C361 /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocationObserver.h; sourceTree = ""; }; + 134814051AA4E45400B7C361 /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = ""; }; + 134814201AA4EA6300B7C361 /* libRCTGeolocation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTGeolocation.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + 134814041AA4E45400B7C361 /* RCTLocationObserver.h */, + 134814051AA4E45400B7C361 /* RCTLocationObserver.m */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* RCTGeolocation */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTGeolocation" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RCTGeolocation; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libRCTGeolocation.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTGeolocation" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* RCTGeolocation */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 134814061AA4E45400B7C361 /* RCTLocationObserver.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../ReactKit/**", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = RCTGeolocation; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../ReactKit/**", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = RCTGeolocation; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTGeolocation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTGeolocation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/ReactKit/Modules/RCTLocationObserver.h b/Libraries/GeoLocation/RCTLocationObserver.h similarity index 100% rename from ReactKit/Modules/RCTLocationObserver.h rename to Libraries/GeoLocation/RCTLocationObserver.h diff --git a/Libraries/GeoLocation/RCTLocationObserver.m b/Libraries/GeoLocation/RCTLocationObserver.m new file mode 100644 index 000000000..019653640 --- /dev/null +++ b/Libraries/GeoLocation/RCTLocationObserver.m @@ -0,0 +1,319 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTLocationObserver.h" + +#import +#import +#import + +#import "RCTAssert.h" +#import "RCTBridge.h" +#import "RCTConvert.h" +#import "RCTEventDispatcher.h" +#import "RCTLog.h" + +typedef NS_ENUM(NSInteger, RCTPositionErrorCode) { + RCTPositionErrorDenied = 1, + RCTPositionErrorUnavailable, + RCTPositionErrorTimeout, +}; + +#define RCT_DEFAULT_LOCATION_ACCURACY kCLLocationAccuracyHundredMeters + +typedef struct { + NSTimeInterval timeout; + NSTimeInterval maximumAge; + CLLocationAccuracy accuracy; +} RCTLocationOptions; + +static RCTLocationOptions RCTLocationOptionsWithJSON(id json) +{ + NSDictionary *options = [RCTConvert NSDictionary:json]; + return (RCTLocationOptions){ + .timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY, + .maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY, + .accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY + }; +} + +static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /* nil for default */) +{ + if (!msg) { + switch (code) { + case RCTPositionErrorDenied: + msg = @"User denied access to location services."; + break; + case RCTPositionErrorUnavailable: + msg = @"Unable to retrieve location."; + break; + case RCTPositionErrorTimeout: + msg = @"The location request timed out."; + break; + } + } + + return @{ + @"code": @(code), + @"message": msg, + @"PERMISSION_DENIED": @(RCTPositionErrorDenied), + @"POSITION_UNAVAILABLE": @(RCTPositionErrorUnavailable), + @"TIMEOUT": @(RCTPositionErrorTimeout) + }; +} + +@interface RCTLocationRequest : NSObject + +@property (nonatomic, copy) RCTResponseSenderBlock successBlock; +@property (nonatomic, copy) RCTResponseSenderBlock errorBlock; +@property (nonatomic, assign) RCTLocationOptions options; +@property (nonatomic, strong) NSTimer *timeoutTimer; + +@end + +@implementation RCTLocationRequest + +- (void)dealloc +{ + [_timeoutTimer invalidate]; +} + +@end + +@interface RCTLocationObserver () + +@end + +@implementation RCTLocationObserver +{ + CLLocationManager *_locationManager; + NSDictionary *_lastLocationEvent; + NSMutableArray *_pendingRequests; + BOOL _observingLocation; + RCTLocationOptions _observerOptions; +} + +@synthesize bridge = _bridge; + +#pragma mark - Lifecycle + +- (instancetype)init +{ + if ((self = [super init])) { + + _locationManager = [[CLLocationManager alloc] init]; + _locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY; + _locationManager.delegate = self; + + _pendingRequests = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc +{ + [_locationManager stopUpdatingLocation]; +} + +#pragma mark - Private API + +- (void)beginLocationUpdates +{ + // Request location access permission + if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [_locationManager requestWhenInUseAuthorization]; + } + + // Start observing location + [_locationManager startUpdatingLocation]; +} + +#pragma mark - Timeout handler + +- (void)timeout:(NSTimer *)timer +{ + RCTLocationRequest *request = timer.userInfo; + NSString *message = [NSString stringWithFormat: @"Unable to fetch location within %zds.", (NSInteger)(timer.timeInterval * 1000.0)]; + request.errorBlock(@[RCTPositionError(RCTPositionErrorTimeout, message)]); + [_pendingRequests removeObject:request]; + + // Stop updating if no pending requests + if (_pendingRequests.count == 0 && !_observingLocation) { + [_locationManager stopUpdatingLocation]; + } +} + +#pragma mark - Public API + +- (void)startObserving:(NSDictionary *)optionsJSON +{ + RCT_EXPORT(); + + dispatch_async(dispatch_get_main_queue(), ^{ + + // Select best options + _observerOptions = RCTLocationOptionsWithJSON(optionsJSON); + for (RCTLocationRequest *request in _pendingRequests) { + _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy); + } + + _locationManager.desiredAccuracy = _observerOptions.accuracy; + [self beginLocationUpdates]; + _observingLocation = YES; + + }); +} + +- (void)stopObserving +{ + RCT_EXPORT(); + + dispatch_async(dispatch_get_main_queue(), ^{ + + // Stop observing + _observingLocation = NO; + + // Stop updating if no pending requests + if (_pendingRequests.count == 0) { + [_locationManager stopUpdatingLocation]; + } + + }); +} + +- (void)getCurrentPosition:(RCTResponseSenderBlock)successBlock + withErrorCallback:(RCTResponseSenderBlock)errorBlock + options:(NSDictionary *)optionsJSON +{ + RCT_EXPORT(); + + if (!successBlock) { + RCTLogError(@"%@.getCurrentPosition called with nil success parameter.", [self class]); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + + if (![CLLocationManager locationServicesEnabled]) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorUnavailable, @"Location services disabled.") + ]); + return; + } + } + + if (![CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { + if (errorBlock) { + errorBlock(@[ + RCTPositionError(RCTPositionErrorDenied, nil) + ]); + return; + } + } + + // Get options + RCTLocationOptions options = RCTLocationOptionsWithJSON(optionsJSON); + + // Check if previous recorded location exists and is good enough + if (_lastLocationEvent && + CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge && + [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) { + + // Call success block with most recent known location + successBlock(@[_lastLocationEvent]); + return; + } + + // Create request + RCTLocationRequest *request = [[RCTLocationRequest alloc] init]; + request.successBlock = successBlock; + request.errorBlock = errorBlock ?: ^(NSArray *args){}; + request.options = options; + request.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:options.timeout + target:self + selector:@selector(timeout:) + userInfo:request + repeats:NO]; + [_pendingRequests addObject:request]; + + // Configure location manager and begin updating location + _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy); + [self beginLocationUpdates]; + + }); +} + +#pragma mark - CLLocationManagerDelegate + +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations +{ + // Create event + CLLocation *location = [locations lastObject]; + _lastLocationEvent = @{ + @"coords": @{ + @"latitude": @(location.coordinate.latitude), + @"longitude": @(location.coordinate.longitude), + @"altitude": @(location.altitude), + @"accuracy": @(location.horizontalAccuracy), + @"altitudeAccuracy": @(location.verticalAccuracy), + @"heading": @(location.course), + @"speed": @(location.speed), + }, + @"timestamp": @(CFAbsoluteTimeGetCurrent() * 1000.0) // in ms + }; + + // Send event + if (_observingLocation) { + [_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationDidChange" + body:_lastLocationEvent]; + } + + // Fire all queued callbacks + for (RCTLocationRequest *request in _pendingRequests) { + request.successBlock(@[_lastLocationEvent]); + } + [_pendingRequests removeAllObjects]; + + // Stop updating if not not observing + if (!_observingLocation) { + [_locationManager stopUpdatingLocation]; + } + + // Reset location accuracy + _locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY; +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error +{ + // Check error type + NSDictionary *jsError = nil; + switch (error.code) { + case kCLErrorDenied: + jsError = RCTPositionError(RCTPositionErrorDenied, nil); + break; + case kCLErrorNetwork: + jsError = RCTPositionError(RCTPositionErrorUnavailable, @"Unable to retrieve location due to a network failure"); + break; + case kCLErrorLocationUnknown: + default: + jsError = RCTPositionError(RCTPositionErrorUnavailable, nil); + break; + } + + // Send event + if (_observingLocation) { + [_bridge.eventDispatcher sendDeviceEventWithName:@"geolocationError" + body:jsError]; + } + + // Fire all queued error callbacks + for (RCTLocationRequest *request in _pendingRequests) { + request.errorBlock(@[jsError]); + } + [_pendingRequests removeAllObjects]; + + // Reset location accuracy + _locationManager.desiredAccuracy = RCT_DEFAULT_LOCATION_ACCURACY; +} + +@end diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 5926cb144..f8ab583a9 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -10,7 +10,6 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var NativeModulesDeprecated = require('NativeModulesDeprecated'); var PropTypes = require('ReactPropTypes'); var ImageResizeMode = require('ImageResizeMode'); -var ImageSourcePropType = require('ImageSourcePropType'); var ImageStylePropTypes = require('ImageStylePropTypes'); var React = require('React'); var ReactIOSViewAttributes = require('ReactIOSViewAttributes'); @@ -25,31 +24,40 @@ var merge = require('merge'); var warning = require('warning'); /** - * - A react component for displaying different types of images, + * A react component for displaying different types of images, * including network images, static resources, temporary local images, and - * images from local disk, such as the camera roll. Example usage: + * images from local disk, such as the camera roll. * - * renderImages: function() { - * return ( - * - * - * - * - * ); - * }, + * Example usage: * - * More example code in ImageExample.js + * ``` + * renderImages: function() { + * return ( + * + * + * + * + * ); + * }, + * ``` */ var Image = React.createClass({ propTypes: { - source: ImageSourcePropType, + source: PropTypes.shape({ + /** + * A string representing the resource identifier for the image, which + * could be an http address, a local file path, or the name of a static image + * resource (which should be wrapped in the `ix` function). + */ + uri: PropTypes.string, + }).isRequired, /** * accessible - Whether this element should be revealed as an accessible * element. @@ -78,7 +86,6 @@ var Image = React.createClass({ statics: { resizeMode: ImageResizeMode, - sourcePropType: ImageSourcePropType, }, mixins: [NativeMethodsMixin], diff --git a/Libraries/Image/ImageSourcePropType.js b/Libraries/Image/ImageSourcePropType.js deleted file mode 100644 index 84eaf6629..000000000 --- a/Libraries/Image/ImageSourcePropType.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule ImageSourcePropType - * @flow - */ -'use strict'; - -var { PropTypes } = require('React'); - -var ImageSourcePropType = PropTypes.shape({ - /** - * uri - A string representing the resource identifier for the image, which - * could be an http address, a local file path, or the name of a static image - * resource (which should be wrapped in the `ix` function). - */ - uri: PropTypes.string.isRequired, - /** - * width/height - Used to store the size of the image itself, but unused by - * the component - use normal style layout properties to define the - * size of the frame. - */ - width: PropTypes.number, - height: PropTypes.number, -}); - -module.exports = ImageSourcePropType; diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 3c507129b..422447d46 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -140,7 +140,7 @@ function setupXHR() { function setupGeolocation() { GLOBAL.navigator = GLOBAL.navigator || {}; - GLOBAL.navigator.geolocation = require('GeoLocation'); + GLOBAL.navigator.geolocation = require('Geolocation'); } setupDocumentShim(); diff --git a/Libraries/ReactIOS/nativePropType.js b/Libraries/ReactIOS/nativePropType.js deleted file mode 100644 index d61c7f191..000000000 --- a/Libraries/ReactIOS/nativePropType.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule nativePropType - */ -'use strict' - -/** - * A simple wrapper for prop types to mark them as native, which will allow them - * to be passed over the bridge to be applied to the native component if - * processed by `validAttributesFromPropTypes`. - */ -function nativePropType(propType) { - propType.isNative = true; - return propType; -} - -module.exports = nativePropType; diff --git a/Libraries/StyleSheet/ArrayOfPropType.js b/Libraries/StyleSheet/ArrayOfPropType.js deleted file mode 100644 index 89d224d8b..000000000 --- a/Libraries/StyleSheet/ArrayOfPropType.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule ArrayOfPropType - */ -'use strict' - -var ReactPropTypes = require('ReactPropTypes'); - -var deepDiffer = require('deepDiffer'); - -var ArrayOfPropType = (type, differ) => { - var checker = ReactPropTypes.arrayOf(type); - checker.differ = differ ? differ : deepDiffer; - return checker; -}; - -module.exports = ArrayOfPropType; diff --git a/Libraries/StyleSheet/EdgeInsetsPropType.js b/Libraries/StyleSheet/EdgeInsetsPropType.js index dcf38de79..a57abcaf8 100644 --- a/Libraries/StyleSheet/EdgeInsetsPropType.js +++ b/Libraries/StyleSheet/EdgeInsetsPropType.js @@ -17,6 +17,4 @@ var EdgeInsetsPropType = createStrictShapeTypeChecker({ right: PropTypes.number, }); -EdgeInsetsPropType.differ = insetsDiffer; - module.exports = EdgeInsetsPropType; diff --git a/Libraries/StyleSheet/PointPropType.js b/Libraries/StyleSheet/PointPropType.js index b281b96e0..c409bd660 100644 --- a/Libraries/StyleSheet/PointPropType.js +++ b/Libraries/StyleSheet/PointPropType.js @@ -15,6 +15,4 @@ var PointPropType = createStrictShapeTypeChecker({ y: PropTypes.number, }); -PointPropType.differ = pointsDiffer; - module.exports = PointPropType; diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 71e022d9b..6171ecbde 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -28,46 +28,42 @@ var viewConfig = { }; /** - * - A react component for displaying text which supports nesting, + * A react component for displaying text which supports nesting, * styling, and touch handling. In the following example, the nested title and * body text will inherit the `fontFamily` from `styles.baseText`, but the title * provides its own additional styles. The title and body will stack on top of * each other on account of the literal newlines: * - * renderText: function() { - * return ( - * - * - * {this.state.titleText + '\n\n'} - * - * - * {this.state.bodyText} - * + * ``` + * renderText: function() { + * return ( + * + * + * {this.state.titleText + '\n\n'} * - * ); + * + * {this.state.bodyText} + * + * + * ); + * }, + * ... + * var styles = StyleSheet.create({ + * baseText: { + * fontFamily: 'Cochin', * }, - * ... - * var styles = StyleSheet.create({ - * baseText: { - * fontFamily: 'Cochin', - * }, - * titleText: { - * fontSize: 20, - * fontWeight: 'bold', - * }, - * }; - * - * More example code in `TextExample.ios.js` + * titleText: { + * fontSize: 20, + * fontWeight: 'bold', + * }, + * }; + * ``` */ var Text = React.createClass({ mixins: [Touchable.Mixin, NativeMethodsMixin], - statics: { - stylePropType: stylePropType, - }, - propTypes: { /** * Used to truncate the text with an elipsis after computing the text diff --git a/Libraries/Utilities/validAttributesFromPropTypes.js b/Libraries/Utilities/validAttributesFromPropTypes.js deleted file mode 100644 index d08d17cfa..000000000 --- a/Libraries/Utilities/validAttributesFromPropTypes.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule validAttributesFromPropTypes - */ -'use strict' - -function validAttributesFromPropTypes(propTypes) { - var validAttributes = {}; - for (var key in propTypes) { - var propType = propTypes[key]; - if (propType && propType.isNative) { - var diff = propType.differ; - validAttributes[key] = diff ? {diff} : true; - } - } - return validAttributes; -} - -module.exports = validAttributesFromPropTypes; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 78c93960b..7f7edaf72 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -8,6 +8,7 @@ var ReactNative = { ...require('React'), AppRegistry: require('AppRegistry'), + DatePickerIOS: require('DatePickerIOS'), ExpandingText: require('ExpandingText'), Image: require('Image'), LayoutAnimation: require('LayoutAnimation'), @@ -17,8 +18,10 @@ var ReactNative = { PixelRatio: require('PixelRatio'), ScrollView: require('ScrollView'), ActivityIndicatorIOS: require('ActivityIndicatorIOS'), + Slider: require('Slider'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), + SwitchIOS: require('SwitchIOS'), Text: require('Text'), TextInput: require('TextInput'), TimerMixin: require('TimerMixin'), diff --git a/ReactKit/Base/RCTConvert.h b/ReactKit/Base/RCTConvert.h index a5aa59fe6..16cd6967b 100644 --- a/ReactKit/Base/RCTConvert.h +++ b/ReactKit/Base/RCTConvert.h @@ -19,6 +19,8 @@ + (float)float:(id)json; + (int)int:(id)json; ++ (NSArray *)NSArray:(id)json; ++ (NSDictionary *)NSDictionary:(id)json; + (NSString *)NSString:(id)json; + (NSNumber *)NSNumber:(id)json; + (NSInteger)NSInteger:(id)json; diff --git a/ReactKit/Base/RCTConvert.m b/ReactKit/Base/RCTConvert.m index bec60ff6c..760f8284d 100644 --- a/ReactKit/Base/RCTConvert.m +++ b/ReactKit/Base/RCTConvert.m @@ -99,6 +99,8 @@ RCT_CONVERTER(double, double, doubleValue) RCT_CONVERTER(float, float, floatValue) RCT_CONVERTER(int, int, intValue) +RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json]) +RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json]) RCT_CONVERTER(NSString *, NSString, description) RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue])) RCT_CONVERTER(NSInteger, NSInteger, integerValue) diff --git a/ReactKit/Modules/RCTLocationObserver.m b/ReactKit/Modules/RCTLocationObserver.m deleted file mode 100644 index fd9c7ac07..000000000 --- a/ReactKit/Modules/RCTLocationObserver.m +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#import "RCTLocationObserver.h" - -#import -#import - -#import "RCTAssert.h" -#import "RCTBridge.h" -#import "RCTEventDispatcher.h" -#import "RCTLog.h" - -// TODO (#5906496): Shouldn't these be configurable? -const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters - -@interface RCTPendingLocationRequest : NSObject - -@property (nonatomic, copy) RCTResponseSenderBlock successBlock; -@property (nonatomic, copy) RCTResponseSenderBlock errorBlock; - -@end - -@implementation RCTPendingLocationRequest @end - -@interface RCTLocationObserver () - -@end - -@implementation RCTLocationObserver -{ - CLLocationManager *_locationManager; - NSDictionary *_lastLocationEvent; - NSMutableDictionary *_pendingRequests; -} - -@synthesize bridge = _bridge; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if ((self = [super init])) { - _pendingRequests = [[NSMutableDictionary alloc] init]; - } - return self; -} - -- (void)dealloc -{ - [_locationManager stopUpdatingLocation]; -} - -#pragma mark - Public API - -- (void)startObserving -{ - RCT_EXPORT(); - - dispatch_async(dispatch_get_main_queue(), ^{ - - // Create the location manager if this object does not - // already have one, and it must be created and accessed - // on the main thread - if (nil == _locationManager) { - _locationManager = [[CLLocationManager alloc] init]; - } - - _locationManager.delegate = self; - _locationManager.desiredAccuracy = RCTLocationAccuracy; - - // Set a movement threshold for new events. - _locationManager.distanceFilter = RCTLocationAccuracy; // meters - - if([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { - [_locationManager requestWhenInUseAuthorization]; - } - - [_locationManager startUpdatingLocation]; - - }); -} - -- (void)stopObserving -{ - RCT_EXPORT(); - - dispatch_async(dispatch_get_main_queue(), ^{ - [_locationManager stopUpdatingLocation]; - _lastLocationEvent = nil; - }); -} - -#pragma mark - CLLocationManagerDelegate - -- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations -{ - CLLocation *loc = [locations lastObject]; - NSDictionary *event = @{ - @"coords": @{ - @"latitude": @(loc.coordinate.latitude), - @"longitude": @(loc.coordinate.longitude), - @"altitude": @(loc.altitude), - @"accuracy": @(RCTLocationAccuracy), - @"altitudeAccuracy": @(RCTLocationAccuracy), - @"heading": @(loc.course), - @"speed": @(loc.speed), - }, - @"timestamp": @(CACurrentMediaTime()) - }; - [_bridge.eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event]; - NSArray *pendingRequestsCopy; - - // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize - @synchronized(self) { - - pendingRequestsCopy = [_pendingRequests allValues]; - [_pendingRequests removeAllObjects]; - - _lastLocationEvent = event; - } - - for (RCTPendingLocationRequest *request in pendingRequestsCopy) { - if (request.successBlock) { - request.successBlock(@[event]); - } - } -} - -- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error -{ - NSArray *pendingRequestsCopy; - - // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize - @synchronized(self) { - pendingRequestsCopy = [_pendingRequests allValues]; - [_pendingRequests removeAllObjects]; - } - - NSString *errorMsg = @"User denied location service or location service not available."; - for (RCTPendingLocationRequest *request in pendingRequestsCopy) { - if (request.errorBlock) { - request.errorBlock(@[errorMsg]); - } - } -} - -- (void)getCurrentPosition:(RCTResponseSenderBlock)geoSuccess withErrorCallback:(RCTResponseSenderBlock)geoError -{ - RCT_EXPORT(); - - NSDictionary *lastLocationCopy; - // TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize - @synchronized(self) { - if (![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { - if (geoError) { - NSString *errorMsg = @"User denied location service or location service not available."; - geoError(@[errorMsg]); - return; - } - } - - // If a request for the current position comes in before the OS has informed us, we wait for the first - // OS event and then call our callbacks. This obviates the need for handling of the otherwise - // common failure case of requesting the geolocation until it succeeds, assuming we would have - // instead returned an error if it wasn't yet available. - if (!_lastLocationEvent) { - NSInteger requestID = [_pendingRequests count]; - RCTPendingLocationRequest *request = [[RCTPendingLocationRequest alloc] init]; - request.successBlock = geoSuccess; - request.errorBlock = geoError; - _pendingRequests[@(requestID)] = request; - return; - } else { - lastLocationCopy = [_lastLocationEvent copy]; - } - } - if (geoSuccess) { - geoSuccess(@[lastLocationCopy]); - } -} - -@end diff --git a/ReactKit/Modules/RCTUIManager.m b/ReactKit/Modules/RCTUIManager.m index 47b2e6c84..a22f24540 100644 --- a/ReactKit/Modules/RCTUIManager.m +++ b/ReactKit/Modules/RCTUIManager.m @@ -74,8 +74,9 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t _property = [RCTConvert NSString:config[@"property"]]; // TODO: this should be provided in ms, not seconds - _duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration; - _delay = [RCTConvert NSTimeInterval:config[@"delay"]]; + // (this will require changing all call sites to ms as well) + _duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0 ?: duration; + _delay = [RCTConvert NSTimeInterval:config[@"delay"]] * 1000.0; _animationType = [RCTConvert RCTAnimationType:config[@"type"]]; if (_animationType == RCTAnimationTypeSpring) { _springDamping = [RCTConvert CGFloat:config[@"springDamping"]]; @@ -135,7 +136,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t if ((self = [super init])) { // TODO: this should be provided in ms, not seconds - NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]]; + // (this will require changing all call sites to ms as well) + NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]] * 1000.0; _createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]]; _updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]]; diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj index 899d865b4..48cac5ded 100644 --- a/ReactKit/ReactKit.xcodeproj/project.pbxproj +++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj @@ -34,7 +34,10 @@ 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; }; 13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; }; 13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */; }; - 5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */; }; + 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; + 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; }; + 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; }; + 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; @@ -123,8 +126,14 @@ 13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ReactKit.h"; sourceTree = ""; }; 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ReactKit.m"; sourceTree = ""; }; 13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = ""; }; - 5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocationObserver.h; sourceTree = ""; }; - 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = ""; }; + 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; }; + 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; + 14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = ""; }; + 14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = ""; }; + 14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = ""; }; + 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitchManager.m; sourceTree = ""; }; + 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSliderManager.h; sourceTree = ""; }; + 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSliderManager.m; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; @@ -187,8 +196,6 @@ 13B07FE01A69315300A75B9A /* Modules */ = { isa = PBXGroup; children = ( - 5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */, - 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */, 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */, 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */, 13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */, @@ -206,7 +213,15 @@ 13B07FF31A6947C200A75B9A /* Views */ = { isa = PBXGroup; children = ( + 14F362071AABD06A001CE568 /* RCTSwitch.h */, + 14F362081AABD06A001CE568 /* RCTSwitch.m */, + 14F362091AABD06A001CE568 /* RCTSwitchManager.h */, + 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */, + 14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */, + 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */, 13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */, + 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */, + 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */, 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */, 13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */, 13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */, @@ -393,14 +408,16 @@ 13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */, 13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */, 83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */, - 5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */, 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */, 13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */, 83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */, 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, + 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, + 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */, 13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */, + 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */, 13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */, 137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */, 13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */, @@ -409,6 +426,7 @@ 13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */, 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */, + 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, diff --git a/ReactKit/Views/RCTDatePickerManager.h b/ReactKit/Views/RCTDatePickerManager.h new file mode 100644 index 000000000..65459b80c --- /dev/null +++ b/ReactKit/Views/RCTDatePickerManager.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTViewManager.h" + +@interface RCTDatePickerManager : RCTViewManager + +@end diff --git a/ReactKit/Views/RCTDatePickerManager.m b/ReactKit/Views/RCTDatePickerManager.m new file mode 100644 index 000000000..39010214e --- /dev/null +++ b/ReactKit/Views/RCTDatePickerManager.m @@ -0,0 +1,51 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTDatePickerManager.h" + +#import "RCTBridge.h" +#import "RCTConvert.h" +#import "RCTEventDispatcher.h" +#import "UIView+ReactKit.h" + +@implementation RCTDatePickerManager + +- (UIView *)view +{ + UIDatePicker *picker = [[UIDatePicker alloc] init]; + [picker addTarget:self + action:@selector(onChange:) + forControlEvents:UIControlEventValueChanged]; + return picker; +} + +RCT_EXPORT_VIEW_PROPERTY(date) +RCT_EXPORT_VIEW_PROPERTY(minimumDate) +RCT_EXPORT_VIEW_PROPERTY(maximumDate) +RCT_EXPORT_VIEW_PROPERTY(minuteInterval) +RCT_REMAP_VIEW_PROPERTY(mode, datePickerMode) +RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone) + +- (void)onChange:(UIDatePicker *)sender +{ + NSDictionary *event = @{ + @"target": sender.reactTag, + @"timestamp": @([sender.date timeIntervalSince1970] * 1000.0) + }; + [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; +} + +- (NSDictionary *)constantsToExport +{ + UIDatePicker *dp = [[UIDatePicker alloc] init]; + return @{ + @"ComponentHeight": @(CGRectGetHeight(dp.frame)), + @"ComponentWidth": @(CGRectGetWidth(dp.frame)), + @"DatePickerModes": @{ + @"time": @(UIDatePickerModeTime), + @"date": @(UIDatePickerModeDate), + @"datetime": @(UIDatePickerModeDateAndTime), + } + }; +} + +@end diff --git a/ReactKit/Views/RCTShadowView.h b/ReactKit/Views/RCTShadowView.h index 9a09bad4f..f869ca614 100644 --- a/ReactKit/Views/RCTShadowView.h +++ b/ReactKit/Views/RCTShadowView.h @@ -32,8 +32,8 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *); @property (nonatomic, weak, readonly) RCTShadowView *superview; @property (nonatomic, assign, readonly) css_node_t *cssNode; @property (nonatomic, copy) NSString *moduleName; -@property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propogate to children -@property (nonatomic, strong) UIColor *backgroundColor; // Used to propogate to children +@property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propagate to children +@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children @property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle; /** diff --git a/ReactKit/Views/RCTSliderManager.h b/ReactKit/Views/RCTSliderManager.h new file mode 100644 index 000000000..1088ec569 --- /dev/null +++ b/ReactKit/Views/RCTSliderManager.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTViewManager.h" + +@interface RCTSliderManager : RCTViewManager + +@end diff --git a/ReactKit/Views/RCTSliderManager.m b/ReactKit/Views/RCTSliderManager.m new file mode 100644 index 000000000..8561c0a97 --- /dev/null +++ b/ReactKit/Views/RCTSliderManager.m @@ -0,0 +1,43 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTSliderManager.h" + +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" +#import "UIView+ReactKit.h" + +@implementation RCTSliderManager + +- (UIView *)view +{ + UISlider *slider = [[UISlider alloc] init]; + [slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; + [slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpInside]; + return slider; +} + +- (void)sliderValueChanged:(UISlider *)sender +{ + NSDictionary *event = @{ + @"target": sender.reactTag, + @"value": @(sender.value), + @"continuous": @YES, + }; + + [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; +} + +- (void)sliderTouchEnd:(UISlider *)sender +{ + NSDictionary *event = @{ + @"target": sender.reactTag, + @"value": @(sender.value), + @"continuous": @NO, + }; + + [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event]; +} + +RCT_EXPORT_VIEW_PROPERTY(value); + +@end diff --git a/ReactKit/Views/RCTSwitch.h b/ReactKit/Views/RCTSwitch.h new file mode 100644 index 000000000..7866eb866 --- /dev/null +++ b/ReactKit/Views/RCTSwitch.h @@ -0,0 +1,10 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + + +#import + +@interface RCTSwitch : UISwitch + +@property (nonatomic, assign) BOOL wasOn; + +@end diff --git a/ReactKit/Views/RCTSwitch.m b/ReactKit/Views/RCTSwitch.m new file mode 100644 index 000000000..70233fede --- /dev/null +++ b/ReactKit/Views/RCTSwitch.m @@ -0,0 +1,15 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTSwitch.h" + +#import "RCTEventDispatcher.h" +#import "UIView+ReactKit.h" + +@implementation RCTSwitch + +- (void)setOn:(BOOL)on animated:(BOOL)animated { + _wasOn = on; + [super setOn:on animated:animated]; +} + +@end diff --git a/ReactKit/Views/RCTSwitchManager.h b/ReactKit/Views/RCTSwitchManager.h new file mode 100644 index 000000000..f6833d106 --- /dev/null +++ b/ReactKit/Views/RCTSwitchManager.h @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTViewManager.h" + +@interface RCTSwitchManager : RCTViewManager + +@end diff --git a/ReactKit/Views/RCTSwitchManager.m b/ReactKit/Views/RCTSwitchManager.m new file mode 100644 index 000000000..61eab8199 --- /dev/null +++ b/ReactKit/Views/RCTSwitchManager.m @@ -0,0 +1,39 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "RCTSwitchManager.h" + +#import "RCTBridge.h" +#import "RCTEventDispatcher.h" +#import "RCTSwitch.h" +#import "UIView+ReactKit.h" + +@implementation RCTSwitchManager + +- (UIView *)view +{ + RCTSwitch *switcher = [[RCTSwitch alloc] init]; + [switcher addTarget:self + action:@selector(onChange:) + forControlEvents:UIControlEventValueChanged]; + return switcher; +} + +- (void)onChange:(RCTSwitch *)sender +{ + if (sender.wasOn != sender.on) { + [self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:@{ + @"target": sender.reactTag, + @"value": @(sender.on) + }]; + + sender.wasOn = sender.on; + } +} + +RCT_EXPORT_VIEW_PROPERTY(onTintColor); +RCT_EXPORT_VIEW_PROPERTY(tintColor); +RCT_EXPORT_VIEW_PROPERTY(thumbTintColor); +RCT_EXPORT_VIEW_PROPERTY(on); +RCT_EXPORT_VIEW_PROPERTY(enabled); + +@end diff --git a/ReactKit/Views/RCTViewNodeProtocol.h b/ReactKit/Views/RCTViewNodeProtocol.h index 0e3e08099..71422b2c8 100644 --- a/ReactKit/Views/RCTViewNodeProtocol.h +++ b/ReactKit/Views/RCTViewNodeProtocol.h @@ -1,7 +1,6 @@ // Copyright 2004-present Facebook. All Rights Reserved. /** - * Logical node in a tree of application components. Both `ShadowView`s and * `UIView+ReactKit`s conform to this. Allows us to write utilities that * reason about trees generally.