Updates from Thu, July 23
This commit is contained in:
commit
03dccfbf71
|
@ -136,10 +136,8 @@ class GameEndOverlay extends React.Component {
|
||||||
return (
|
return (
|
||||||
<View style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
<Text style={styles.overlayMessage}>{message}</Text>
|
<Text style={styles.overlayMessage}>{message}</Text>
|
||||||
<TouchableBounce onPress={this.props.onRestart}>
|
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||||
<View style={styles.tryAgain}>
|
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableBounce>
|
</TouchableBounce>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react-native');
|
var React = require('react-native');
|
||||||
var UIExplorerList = require('./UIExplorerList');
|
var UIExplorerList = require('./UIExplorerList.ios');
|
||||||
var {
|
var {
|
||||||
AppRegistry,
|
AppRegistry,
|
||||||
NavigatorIOS,
|
NavigatorIOS,
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
AppRegistry,
|
||||||
|
Settings,
|
||||||
|
StyleSheet,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var { TestModule } = React.addons;
|
||||||
|
|
||||||
|
import type { NavigationContext } from 'NavigationContext';
|
||||||
|
|
||||||
|
var UIExplorerListBase = require('./UIExplorerListBase');
|
||||||
|
|
||||||
|
var COMPONENTS = [
|
||||||
|
require('./ActivityIndicatorIOSExample'),
|
||||||
|
require('./DatePickerIOSExample'),
|
||||||
|
require('./ImageExample'),
|
||||||
|
require('./LayoutEventsExample'),
|
||||||
|
require('./ListViewExample'),
|
||||||
|
require('./ListViewGridLayoutExample'),
|
||||||
|
require('./ListViewPagingExample'),
|
||||||
|
require('./MapViewExample'),
|
||||||
|
require('./Navigator/NavigatorExample'),
|
||||||
|
require('./NavigatorIOSColorsExample'),
|
||||||
|
require('./NavigatorIOSExample'),
|
||||||
|
require('./PickerIOSExample'),
|
||||||
|
require('./ProgressViewIOSExample'),
|
||||||
|
require('./ScrollViewExample'),
|
||||||
|
require('./SegmentedControlIOSExample'),
|
||||||
|
require('./SliderIOSExample'),
|
||||||
|
require('./SwitchIOSExample'),
|
||||||
|
require('./TabBarIOSExample'),
|
||||||
|
require('./TextExample.ios'),
|
||||||
|
require('./TextInputExample'),
|
||||||
|
require('./TouchableExample'),
|
||||||
|
require('./ViewExample'),
|
||||||
|
require('./WebViewExample'),
|
||||||
|
];
|
||||||
|
|
||||||
|
var APIS = [
|
||||||
|
require('./AccessibilityIOSExample'),
|
||||||
|
require('./ActionSheetIOSExample'),
|
||||||
|
require('./AdSupportIOSExample'),
|
||||||
|
require('./AlertIOSExample'),
|
||||||
|
require('./AnimationExample/AnExApp'),
|
||||||
|
require('./AppStateIOSExample'),
|
||||||
|
require('./AsyncStorageExample'),
|
||||||
|
require('./BorderExample'),
|
||||||
|
require('./CameraRollExample.ios'),
|
||||||
|
require('./GeolocationExample'),
|
||||||
|
require('./LayoutExample'),
|
||||||
|
require('./NetInfoExample'),
|
||||||
|
require('./PanResponderExample'),
|
||||||
|
require('./PointerEventsExample'),
|
||||||
|
require('./PushNotificationIOSExample'),
|
||||||
|
require('./StatusBarIOSExample'),
|
||||||
|
require('./TimerExample'),
|
||||||
|
require('./VibrationIOSExample'),
|
||||||
|
require('./XHRExample'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Register suitable examples for snapshot tests
|
||||||
|
COMPONENTS.concat(APIS).forEach((Example) => {
|
||||||
|
if (Example.displayName) {
|
||||||
|
var Snapshotter = React.createClass({
|
||||||
|
componentDidMount: function() {
|
||||||
|
// View is still blank after first RAF :\
|
||||||
|
global.requestAnimationFrame(() =>
|
||||||
|
global.requestAnimationFrame(() => TestModule.verifySnapshot(
|
||||||
|
TestModule.markTestPassed
|
||||||
|
)
|
||||||
|
));
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var Renderable = UIExplorerListBase.makeRenderable(Example);
|
||||||
|
return <Renderable />;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
navigator: {
|
||||||
|
navigationContext: NavigationContext,
|
||||||
|
push: (route: {title: string, component: ReactClass<any,any,any>}) => void,
|
||||||
|
},
|
||||||
|
onExternalExampleRequested: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
class UIExplorerList extends React.Component {
|
||||||
|
props: Props;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<UIExplorerListBase
|
||||||
|
components={COMPONENTS}
|
||||||
|
apis={APIS}
|
||||||
|
searchText={Settings.get('searchText')}
|
||||||
|
renderAdditionalView={this.renderAdditionalView.bind(this)}
|
||||||
|
search={this.search.bind(this)}
|
||||||
|
onPressRow={this.onPressRow.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.navigator.navigationContext.addListener('didfocus', function(event) {
|
||||||
|
if (event.data.route.title === 'UIExplorer') {
|
||||||
|
Settings.set({visibleExample: null});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
var visibleExampleTitle = Settings.get('visibleExample');
|
||||||
|
if (visibleExampleTitle) {
|
||||||
|
var predicate = (example) => example.title === visibleExampleTitle;
|
||||||
|
var foundExample = APIS.find(predicate) || COMPONENTS.find(predicate);
|
||||||
|
if (foundExample) {
|
||||||
|
setTimeout(() => this._openExample(foundExample), 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component {
|
||||||
|
return renderTextInput(styles.searchTextInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(text: mixed) {
|
||||||
|
Settings.set({searchText: text});
|
||||||
|
}
|
||||||
|
|
||||||
|
_openExample(example: any) {
|
||||||
|
if (example.external) {
|
||||||
|
this.props.onExternalExampleRequested(example);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Component = UIExplorerListBase.makeRenderable(example);
|
||||||
|
this.props.navigator.push({
|
||||||
|
title: Component.title,
|
||||||
|
component: Component,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressRow(example: any) {
|
||||||
|
Settings.set({visibleExample: example.title});
|
||||||
|
this._openExample(example);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
searchTextInput: {
|
||||||
|
height: 30,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = UIExplorerList;
|
|
@ -0,0 +1,195 @@
|
||||||
|
/**
|
||||||
|
* The examples provided by Facebook are for non-commercial testing and
|
||||||
|
* evaluation purposes only.
|
||||||
|
*
|
||||||
|
* Facebook reserves all rights not expressly granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
ListView,
|
||||||
|
PixelRatio,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableHighlight,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
var createExamplePage = require('./createExamplePage');
|
||||||
|
|
||||||
|
var ds = new ListView.DataSource({
|
||||||
|
rowHasChanged: (r1, r2) => r1 !== r2,
|
||||||
|
sectionHeaderHasChanged: (h1, h2) => h1 !== h2,
|
||||||
|
});
|
||||||
|
|
||||||
|
class UIExplorerListBase extends React.Component {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
dataSource: ds.cloneWithRowsAndSections({
|
||||||
|
components: [],
|
||||||
|
apis: [],
|
||||||
|
}),
|
||||||
|
searchText: this.props.searchText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.search(this.state.searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var topView = this.props.renderAdditionalView &&
|
||||||
|
this.props.renderAdditionalView(this.renderRow.bind(this), this.renderTextInput.bind(this));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.listContainer}>
|
||||||
|
{topView}
|
||||||
|
<ListView
|
||||||
|
style={styles.list}
|
||||||
|
dataSource={this.state.dataSource}
|
||||||
|
renderRow={this.renderRow.bind(this)}
|
||||||
|
renderSectionHeader={this._renderSectionHeader}
|
||||||
|
keyboardShouldPersistTaps={true}
|
||||||
|
automaticallyAdjustContentInsets={false}
|
||||||
|
keyboardDismissMode="on-drag"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTextInput(searchTextInputStyle: any) {
|
||||||
|
return (
|
||||||
|
<View style={styles.searchRow}>
|
||||||
|
<TextInput
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
|
clearButtonMode="always"
|
||||||
|
onChangeText={this.search.bind(this)}
|
||||||
|
placeholder="Search..."
|
||||||
|
style={[styles.searchTextInput, searchTextInputStyle]}
|
||||||
|
testID="explorer_search"
|
||||||
|
value={this.state.searchText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderSectionHeader(data: any, section: string) {
|
||||||
|
return (
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<Text style={styles.sectionHeaderTitle}>
|
||||||
|
{section.toUpperCase()}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRow(example: any, i: number) {
|
||||||
|
return (
|
||||||
|
<View key={i}>
|
||||||
|
<TouchableHighlight onPress={() => this.onPressRow(example)}>
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={styles.rowTitleText}>
|
||||||
|
{example.title}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.rowDetailText}>
|
||||||
|
{example.description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
<View style={styles.separator} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(text: mixed): void {
|
||||||
|
this.props.search && this.props.search(text);
|
||||||
|
|
||||||
|
var regex = new RegExp(text, 'i');
|
||||||
|
var filter = (component) => regex.test(component.title);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
dataSource: ds.cloneWithRowsAndSections({
|
||||||
|
components: this.props.components.filter(filter),
|
||||||
|
apis: this.props.apis.filter(filter),
|
||||||
|
}),
|
||||||
|
searchText: text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressRow(example: any): void {
|
||||||
|
this.props.onPressRow && this.props.onPressRow(example);
|
||||||
|
}
|
||||||
|
|
||||||
|
static makeRenderable(example: any): ReactClass<any, any, any> {
|
||||||
|
return example.examples ?
|
||||||
|
createExamplePage(null, example) :
|
||||||
|
example;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
listContainer: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
backgroundColor: '#eeeeee',
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
padding: 5,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
},
|
||||||
|
sectionHeaderTitle: {
|
||||||
|
fontWeight: '500',
|
||||||
|
fontSize: 11,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 8,
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: 1 / PixelRatio.get(),
|
||||||
|
backgroundColor: '#bbbbbb',
|
||||||
|
marginLeft: 15,
|
||||||
|
},
|
||||||
|
rowTitleText: {
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
rowDetailText: {
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#888888',
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
searchRow: {
|
||||||
|
backgroundColor: '#eeeeee',
|
||||||
|
paddingTop: 75,
|
||||||
|
paddingLeft: 10,
|
||||||
|
paddingRight: 10,
|
||||||
|
paddingBottom: 10,
|
||||||
|
},
|
||||||
|
searchTextInput: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderColor: '#cccccc',
|
||||||
|
borderRadius: 3,
|
||||||
|
borderWidth: 1,
|
||||||
|
paddingLeft: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = UIExplorerListBase;
|
|
@ -479,7 +479,13 @@ class SpringAnimation extends Animation {
|
||||||
if (this._tension !== 0) {
|
if (this._tension !== 0) {
|
||||||
isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
|
isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOvershooting || (isVelocity && isDisplacement)) {
|
if (isOvershooting || (isVelocity && isDisplacement)) {
|
||||||
|
if (this._tension !== 0) {
|
||||||
|
// Ensure that we end up with a round value
|
||||||
|
this._onUpdate(this._toValue);
|
||||||
|
}
|
||||||
|
|
||||||
this.__debouncedOnEnd({finished: true});
|
this.__debouncedOnEnd({finished: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,19 @@ describe('Animated', () => {
|
||||||
Animated.spring(anim, {toValue: 0, velocity: 0}).start(callback);
|
Animated.spring(anim, {toValue: 0, velocity: 0}).start(callback);
|
||||||
expect(callback).toBeCalled();
|
expect(callback).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('send toValue when a spring stops', () => {
|
||||||
|
var anim = new Animated.Value(0);
|
||||||
|
var listener = jest.genMockFunction();
|
||||||
|
anim.addListener(listener);
|
||||||
|
Animated.spring(anim, {toValue: 15}).start();
|
||||||
|
jest.runAllTimers();
|
||||||
|
var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value;
|
||||||
|
expect(lastValue).not.toBe(15);
|
||||||
|
expect(lastValue).toBeCloseTo(15);
|
||||||
|
expect(anim.__getValue()).toBe(15);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -470,7 +470,11 @@ var ScrollResponderMixin = {
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollResponderKeyboardDidShow: function(e: Event) {
|
scrollResponderKeyboardDidShow: function(e: Event) {
|
||||||
this.keyboardWillOpenTo = null;
|
// TODO(7693961): The event for DidShow is not available on iOS yet.
|
||||||
|
// Use the one from WillShow and do not assign.
|
||||||
|
if (e) {
|
||||||
|
this.keyboardWillOpenTo = e;
|
||||||
|
}
|
||||||
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
|
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -53,44 +53,54 @@ var INNERVIEW = 'InnerScrollView';
|
||||||
* Doesn't yet support other contained responders from blocking this scroll
|
* Doesn't yet support other contained responders from blocking this scroll
|
||||||
* view from becoming the responder.
|
* view from becoming the responder.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var ScrollView = React.createClass({
|
var ScrollView = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
automaticallyAdjustContentInsets: PropTypes.bool, // true
|
/**
|
||||||
contentInset: EdgeInsetsPropType, // zeros
|
* Controls whether iOS should automatically adjust the content inset
|
||||||
contentOffset: PointPropType, // zeros
|
* for scroll views that are placed behind a navigation bar or
|
||||||
onScroll: PropTypes.func,
|
* tab bar/ toolbar. The default value is true.
|
||||||
onScrollAnimationEnd: PropTypes.func,
|
* @platform ios
|
||||||
scrollEnabled: PropTypes.bool, // true
|
*/
|
||||||
scrollIndicatorInsets: EdgeInsetsPropType, // zeros
|
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||||
showsHorizontalScrollIndicator: PropTypes.bool,
|
/**
|
||||||
showsVerticalScrollIndicator: PropTypes.bool,
|
* The amount by which the scroll view content is inset from the edges
|
||||||
style: StyleSheetPropType(ViewStylePropTypes),
|
* of the scroll view. Defaults to `{0, 0, 0, 0}`.
|
||||||
scrollEventThrottle: PropTypes.number, // null
|
* @platform ios
|
||||||
|
*/
|
||||||
|
contentInset: EdgeInsetsPropType,
|
||||||
|
/**
|
||||||
|
* Used to manually set the starting scroll offset.
|
||||||
|
* The default value is `{x: 0, y: 0}`.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
contentOffset: PointPropType,
|
||||||
/**
|
/**
|
||||||
* When true, the scroll view bounces when it reaches the end of the
|
* When true, the scroll view bounces when it reaches the end of the
|
||||||
* content if the content is larger then the scroll view along the axis of
|
* content if the content is larger then the scroll view along the axis of
|
||||||
* the scroll direction. When false, it disables all bouncing even if
|
* the scroll direction. When false, it disables all bouncing even if
|
||||||
* the `alwaysBounce*` props are true. The default value is true.
|
* the `alwaysBounce*` props are true. The default value is true.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
bounces: PropTypes.bool,
|
bounces: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* When true, gestures can drive zoom past min/max and the zoom will animate
|
* When true, gestures can drive zoom past min/max and the zoom will animate
|
||||||
* to the min/max value at gesture end, otherwise the zoom will not exceed
|
* to the min/max value at gesture end, otherwise the zoom will not exceed
|
||||||
* the limits.
|
* the limits.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
bouncesZoom: PropTypes.bool,
|
bouncesZoom: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* When true, the scroll view bounces horizontally when it reaches the end
|
* 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
|
* even if the content is smaller than the scroll view itself. The default
|
||||||
* value is true when `horizontal={true}` and false otherwise.
|
* value is true when `horizontal={true}` and false otherwise.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
alwaysBounceHorizontal: PropTypes.bool,
|
alwaysBounceHorizontal: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* When true, the scroll view bounces vertically when it reaches the end
|
* 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
|
* even if the content is smaller than the scroll view itself. The default
|
||||||
* value is false when `horizontal={true}` and true otherwise.
|
* value is false when `horizontal={true}` and true otherwise.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
alwaysBounceVertical: PropTypes.bool,
|
alwaysBounceVertical: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
|
@ -98,6 +108,7 @@ var ScrollView = React.createClass({
|
||||||
* content is smaller than the scroll view bounds; when the content is
|
* content is smaller than the scroll view bounds; when the content is
|
||||||
* larger than the scroll view, this property has no effect. The default
|
* larger than the scroll view, this property has no effect. The default
|
||||||
* value is false.
|
* value is false.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
centerContent: PropTypes.bool,
|
centerContent: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
|
@ -121,6 +132,7 @@ var ScrollView = React.createClass({
|
||||||
* decelerates after the user lifts their finger. Reasonable choices include
|
* decelerates after the user lifts their finger. Reasonable choices include
|
||||||
* - Normal: 0.998 (the default)
|
* - Normal: 0.998 (the default)
|
||||||
* - Fast: 0.9
|
* - Fast: 0.9
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
decelerationRate: PropTypes.number,
|
decelerationRate: PropTypes.number,
|
||||||
/**
|
/**
|
||||||
|
@ -131,17 +143,19 @@ var ScrollView = React.createClass({
|
||||||
/**
|
/**
|
||||||
* When true, the ScrollView will try to lock to only vertical or horizontal
|
* When true, the ScrollView will try to lock to only vertical or horizontal
|
||||||
* scrolling while dragging. The default value is false.
|
* scrolling while dragging. The default value is false.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
directionalLockEnabled: PropTypes.bool,
|
directionalLockEnabled: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* When false, once tracking starts, won't try to drag if the touch moves.
|
* When false, once tracking starts, won't try to drag if the touch moves.
|
||||||
* The default value is true.
|
* The default value is true.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
canCancelContentTouches: PropTypes.bool,
|
canCancelContentTouches: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* Determines whether the keyboard gets dismissed in response to a drag.
|
* Determines whether the keyboard gets dismissed in response to a drag.
|
||||||
* - 'none' (the default), drags do not dismiss the keyboard.
|
* - 'none' (the default), drags do not dismiss the keyboard.
|
||||||
* - 'onDrag', the keyboard is dismissed when a drag begins.
|
* - 'on-drag', the keyboard is dismissed when a drag begins.
|
||||||
* - 'interactive', the keyboard is dismissed interactively with the drag
|
* - 'interactive', the keyboard is dismissed interactively with the drag
|
||||||
* and moves in synchrony with the touch; dragging upwards cancels the
|
* and moves in synchrony with the touch; dragging upwards cancels the
|
||||||
* dismissal.
|
* dismissal.
|
||||||
|
@ -156,35 +170,83 @@ var ScrollView = React.createClass({
|
||||||
* is up dismisses the keyboard. When true, the scroll view will not catch
|
* is up dismisses the keyboard. When true, the scroll view will not catch
|
||||||
* taps, and the keyboard will not dismiss automatically. The default value
|
* taps, and the keyboard will not dismiss automatically. The default value
|
||||||
* is false.
|
* is false.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
keyboardShouldPersistTaps: PropTypes.bool,
|
keyboardShouldPersistTaps: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* The maximum allowed zoom scale. The default value is 1.0.
|
* The maximum allowed zoom scale. The default value is 1.0.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
maximumZoomScale: PropTypes.number,
|
maximumZoomScale: PropTypes.number,
|
||||||
/**
|
/**
|
||||||
* The minimum allowed zoom scale. The default value is 1.0.
|
* The minimum allowed zoom scale. The default value is 1.0.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
minimumZoomScale: PropTypes.number,
|
minimumZoomScale: PropTypes.number,
|
||||||
|
/**
|
||||||
|
* Fires at most once per frame during scrolling. The frequency of the
|
||||||
|
* events can be contolled using the `scrollEventThrottle` prop.
|
||||||
|
*/
|
||||||
|
onScroll: PropTypes.func,
|
||||||
|
/**
|
||||||
|
* Called when a scrolling animation ends.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
onScrollAnimationEnd: PropTypes.func,
|
||||||
/**
|
/**
|
||||||
* When true, the scroll view stops on multiples of the scroll view's size
|
* 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
|
* when scrolling. This can be used for horizontal pagination. The default
|
||||||
* value is false.
|
* value is false.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
pagingEnabled: PropTypes.bool,
|
pagingEnabled: PropTypes.bool,
|
||||||
|
/**
|
||||||
|
* When false, the content does not scroll.
|
||||||
|
* The default value is true.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
scrollEnabled: PropTypes.bool,
|
||||||
|
/**
|
||||||
|
* This controls how often the scroll event will be fired while scrolling
|
||||||
|
* (in events per seconds). A higher number yields better accuracy for code
|
||||||
|
* that is tracking the scroll position, but can lead to scroll performance
|
||||||
|
* problems due to the volume of information being send over the bridge.
|
||||||
|
* The default value is zero, which means the scroll event will be sent
|
||||||
|
* only once each time the view is scrolled.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
scrollEventThrottle: PropTypes.number,
|
||||||
|
/**
|
||||||
|
* The amount by which the scroll view indicators are inset from the edges
|
||||||
|
* of the scroll view. This should normally be set to the same value as
|
||||||
|
* the `contentInset`. Defaults to `{0, 0, 0, 0}`.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
scrollIndicatorInsets: EdgeInsetsPropType,
|
||||||
/**
|
/**
|
||||||
* When true, the scroll view scrolls to top when the status bar is tapped.
|
* When true, the scroll view scrolls to top when the status bar is tapped.
|
||||||
* The default value is true.
|
* The default value is true.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
scrollsToTop: PropTypes.bool,
|
scrollsToTop: PropTypes.bool,
|
||||||
|
/**
|
||||||
|
* When true, shows a horizontal scroll indicator.
|
||||||
|
*/
|
||||||
|
showsHorizontalScrollIndicator: PropTypes.bool,
|
||||||
|
/**
|
||||||
|
* When true, shows a vertical scroll indicator.
|
||||||
|
*/
|
||||||
|
showsVerticalScrollIndicator: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* An array of child indices determining which children get docked to the
|
* An array of child indices determining which children get docked to the
|
||||||
* top of the screen when scrolling. For example, passing
|
* top of the screen when scrolling. For example, passing
|
||||||
* `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
|
* `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
|
||||||
* top of the scroll view. This property is not supported in conjunction
|
* top of the scroll view. This property is not supported in conjunction
|
||||||
* with `horizontal={true}`.
|
* with `horizontal={true}`.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
|
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
|
||||||
|
style: StyleSheetPropType(ViewStylePropTypes),
|
||||||
/**
|
/**
|
||||||
* Experimental: When true, offscreen child views (whose `overflow` value is
|
* Experimental: When true, offscreen child views (whose `overflow` value is
|
||||||
* `hidden`) are removed from their native backing superview when offscreen.
|
* `hidden`) are removed from their native backing superview when offscreen.
|
||||||
|
@ -194,6 +256,7 @@ var ScrollView = React.createClass({
|
||||||
removeClippedSubviews: PropTypes.bool,
|
removeClippedSubviews: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
* The current scale of the scroll view content. The default value is 1.0.
|
* The current scale of the scroll view content. The default value is 1.0.
|
||||||
|
* @platform ios
|
||||||
*/
|
*/
|
||||||
zoomScale: PropTypes.number,
|
zoomScale: PropTypes.number,
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,20 +11,12 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var AnimationExperimental = require('AnimationExperimental');
|
var Animated = require('Animated');
|
||||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||||
var POPAnimation = require('POPAnimation');
|
|
||||||
var React = require('React');
|
var React = require('React');
|
||||||
var Touchable = require('Touchable');
|
var Touchable = require('Touchable');
|
||||||
|
|
||||||
var merge = require('merge');
|
var merge = require('merge');
|
||||||
var onlyChild = require('onlyChild');
|
|
||||||
|
|
||||||
var invariant = require('invariant');
|
|
||||||
invariant(
|
|
||||||
AnimationExperimental || POPAnimation,
|
|
||||||
'Please add the RCTAnimationExperimental framework to your project, or add //Libraries/FBReactKit:RCTPOPAnimation to your BUCK file if running internally within Facebook.'
|
|
||||||
);
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
animationID: ?number;
|
animationID: ?number;
|
||||||
|
@ -58,40 +50,23 @@ var TouchableBounce = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function(): State {
|
getInitialState: function(): State {
|
||||||
return merge(this.touchableGetInitialState(), {animationID: null});
|
return {
|
||||||
|
...this.touchableGetInitialState(),
|
||||||
|
scale: new Animated.Value(1),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
bounceTo: function(
|
bounceTo: function(
|
||||||
value: number,
|
value: number,
|
||||||
velocity: number,
|
velocity: number,
|
||||||
bounciness: number,
|
bounciness: number,
|
||||||
fromValue?: ?number,
|
|
||||||
callback?: ?Function
|
callback?: ?Function
|
||||||
) {
|
) {
|
||||||
if (POPAnimation) {
|
Animated.spring(this.state.scale, {
|
||||||
this.state.animationID && this.removeAnimation(this.state.animationID);
|
toValue: value,
|
||||||
var anim = {
|
velocity,
|
||||||
property: POPAnimation.Properties.scaleXY,
|
bounciness,
|
||||||
dynamicsTension: 0,
|
}).start(callback);
|
||||||
toValue: [value, value],
|
|
||||||
velocity: [velocity, velocity],
|
|
||||||
springBounciness: bounciness,
|
|
||||||
fromValue: fromValue ? [fromValue, fromValue] : undefined,
|
|
||||||
};
|
|
||||||
this.state.animationID = POPAnimation.createSpringAnimation(anim);
|
|
||||||
this.addAnimation(this.state.animationID, callback);
|
|
||||||
} else {
|
|
||||||
AnimationExperimental.startAnimation(
|
|
||||||
{
|
|
||||||
node: this,
|
|
||||||
duration: 300,
|
|
||||||
easing: 'easeOutBack',
|
|
||||||
property: 'scaleXY',
|
|
||||||
toValue: { x: value, y: value},
|
|
||||||
},
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,13 +84,14 @@ var TouchableBounce = React.createClass({
|
||||||
touchableHandlePress: function() {
|
touchableHandlePress: function() {
|
||||||
var onPressWithCompletion = this.props.onPressWithCompletion;
|
var onPressWithCompletion = this.props.onPressWithCompletion;
|
||||||
if (onPressWithCompletion) {
|
if (onPressWithCompletion) {
|
||||||
onPressWithCompletion(
|
onPressWithCompletion(() => {
|
||||||
this.bounceTo.bind(this, 1, 10, 10, 0.93, this.props.onPressAnimationComplete)
|
this.state.scale.setValue(0.93);
|
||||||
);
|
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bounceTo(1, 10, 10, undefined, this.props.onPressAnimationComplete);
|
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
|
||||||
this.props.onPress && this.props.onPress();
|
this.props.onPress && this.props.onPress();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -127,18 +103,21 @@ var TouchableBounce = React.createClass({
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function(): ReactElement {
|
||||||
var child = onlyChild(this.props.children);
|
return (
|
||||||
return React.cloneElement(child, {
|
<Animated.View
|
||||||
accessible: true,
|
style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
|
||||||
testID: this.props.testID,
|
accessible={true}
|
||||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
testID={this.props.testID}
|
||||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||||
onResponderGrant: this.touchableHandleResponderGrant,
|
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||||
onResponderMove: this.touchableHandleResponderMove,
|
onResponderGrant={this.touchableHandleResponderGrant}
|
||||||
onResponderRelease: this.touchableHandleResponderRelease,
|
onResponderMove={this.touchableHandleResponderMove}
|
||||||
onResponderTerminate: this.touchableHandleResponderTerminate
|
onResponderRelease={this.touchableHandleResponderRelease}
|
||||||
});
|
onResponderTerminate={this.touchableHandleResponderTerminate}>
|
||||||
|
{this.props.children}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,11 @@ var mapWithSeparator = require('mapWithSeparator');
|
||||||
var ElementProperties = React.createClass({
|
var ElementProperties = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
hierarchy: PropTypes.array.isRequired,
|
hierarchy: PropTypes.array.isRequired,
|
||||||
style: PropTypes.array.isRequired,
|
style: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
|
@ -20,7 +20,7 @@ var StyleSheet = require('StyleSheet');
|
||||||
var UIManager = require('NativeModules').UIManager;
|
var UIManager = require('NativeModules').UIManager;
|
||||||
var View = require('View');
|
var View = require('View');
|
||||||
|
|
||||||
var REACT_DEVTOOLS_HOOK: ?Object = typeof window !== 'undefined' ? window.__REACT_DEVTOOLS_BACKEND__ : null;
|
var REACT_DEVTOOLS_HOOK: ?Object = typeof window !== 'undefined' ? window.__REACT_DEVTOOLS_GLOBAL_HOOK__ : null;
|
||||||
|
|
||||||
if (REACT_DEVTOOLS_HOOK) {
|
if (REACT_DEVTOOLS_HOOK) {
|
||||||
// required for devtools to be able to edit react native styles
|
// required for devtools to be able to edit react native styles
|
||||||
|
@ -34,7 +34,7 @@ class Inspector extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
devtoolsBackend: null,
|
devtoolsAgent: null,
|
||||||
panelPos: 'bottom',
|
panelPos: 'bottom',
|
||||||
inspecting: true,
|
inspecting: true,
|
||||||
perfing: false,
|
perfing: false,
|
||||||
|
@ -45,14 +45,10 @@ class Inspector extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (REACT_DEVTOOLS_HOOK) {
|
if (REACT_DEVTOOLS_HOOK) {
|
||||||
this.attachToDevtools = this.attachToDevtools.bind(this);
|
this.attachToDevtools = this.attachToDevtools.bind(this);
|
||||||
REACT_DEVTOOLS_HOOK.addStartupListener(this.attachToDevtools);
|
REACT_DEVTOOLS_HOOK.on('react-devtools', this.attachToDevtools);
|
||||||
// if devtools is already started
|
// if devtools is already started
|
||||||
// TODO(jared): should addStartupListener just go ahead and call the
|
if (REACT_DEVTOOLS_HOOK.reactDevtoolsAgent) {
|
||||||
// listener if the devtools is already started? might be unexpected...
|
this.attachToDevtools(REACT_DEVTOOLS_HOOK.reactDevtoolsAgent);
|
||||||
// is there some name other than `addStartupListener` that would be
|
|
||||||
// better?
|
|
||||||
if (REACT_DEVTOOLS_HOOK.backend) {
|
|
||||||
this.attachToDevtools(REACT_DEVTOOLS_HOOK.backend);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,13 +58,13 @@ class Inspector extends React.Component {
|
||||||
this._subs.map(fn => fn());
|
this._subs.map(fn => fn());
|
||||||
}
|
}
|
||||||
if (REACT_DEVTOOLS_HOOK) {
|
if (REACT_DEVTOOLS_HOOK) {
|
||||||
REACT_DEVTOOLS_HOOK.removeStartupListener(this.attachToDevtools);
|
REACT_DEVTOOLS_HOOK.off('react-devtools', this.attachToDevtools);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachToDevtools(backend: Object) {
|
attachToDevtools(agent: Object) {
|
||||||
var _hideWait = null;
|
var _hideWait = null;
|
||||||
var hlSub = backend.sub('highlight', ({node, name, props}) => {
|
var hlSub = agent.sub('highlight', ({node, name, props}) => {
|
||||||
clearTimeout(_hideWait);
|
clearTimeout(_hideWait);
|
||||||
UIManager.measure(node, (x, y, width, height, left, top) => {
|
UIManager.measure(node, (x, y, width, height, left, top) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -80,7 +76,10 @@ class Inspector extends React.Component {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var hideSub = backend.sub('hideHighlight', () => {
|
var hideSub = agent.sub('hideHighlight', () => {
|
||||||
|
if (this.state.inspected === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// we wait to actually hide in order to avoid flicker
|
// we wait to actually hide in order to avoid flicker
|
||||||
_hideWait = setTimeout(() => {
|
_hideWait = setTimeout(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -90,12 +89,12 @@ class Inspector extends React.Component {
|
||||||
});
|
});
|
||||||
this._subs = [hlSub, hideSub];
|
this._subs = [hlSub, hideSub];
|
||||||
|
|
||||||
backend.on('shutdown', () => {
|
agent.on('shutdown', () => {
|
||||||
this.setState({devtoolsBackend: null});
|
this.setState({devtoolsAgent: null});
|
||||||
this._subs = null;
|
this._subs = null;
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
devtoolsBackend: backend,
|
devtoolsAgent: agent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,8 +113,8 @@ class Inspector extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchInstance(instance: Object, frame: Object, pointerY: number) {
|
onTouchInstance(instance: Object, frame: Object, pointerY: number) {
|
||||||
if (this.state.devtoolsBackend) {
|
if (this.state.devtoolsAgent) {
|
||||||
this.state.devtoolsBackend.selectFromReactInstance(instance, true);
|
this.state.devtoolsAgent.selectFromReactInstance(instance, true);
|
||||||
}
|
}
|
||||||
var hierarchy = InspectorUtils.getOwnerHierarchy(instance);
|
var hierarchy = InspectorUtils.getOwnerHierarchy(instance);
|
||||||
var publicInstance = instance.getPublicInstance();
|
var publicInstance = instance.getPublicInstance();
|
||||||
|
@ -159,7 +158,7 @@ class Inspector extends React.Component {
|
||||||
/>}
|
/>}
|
||||||
<View style={[styles.panelContainer, panelContainerStyle]}>
|
<View style={[styles.panelContainer, panelContainerStyle]}>
|
||||||
<InspectorPanel
|
<InspectorPanel
|
||||||
devtoolsIsOpen={!!this.state.devtoolsBackend}
|
devtoolsIsOpen={!!this.state.devtoolsAgent}
|
||||||
inspecting={this.state.inspecting}
|
inspecting={this.state.inspecting}
|
||||||
perfing={this.state.perfing}
|
perfing={this.state.perfing}
|
||||||
setPerfing={this.setPerfing.bind(this)}
|
setPerfing={this.setPerfing.bind(this)}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "RCTURLRequestDelegate.h"
|
||||||
|
#import "RCTURLRequestHandler.h"
|
||||||
|
|
||||||
|
typedef void (^RCTURLRequestCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error);
|
||||||
|
typedef void (^RCTURLRequestCancellationBlock)(void);
|
||||||
|
typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
|
||||||
|
typedef void (^RCTURLRequestProgressBlock)(double progress, double total);
|
||||||
|
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
|
||||||
|
|
||||||
|
@interface RCTDownloadTask : NSObject <RCTURLRequestDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) NSURLRequest *request;
|
||||||
|
@property (nonatomic, readonly) NSNumber *requestID;
|
||||||
|
@property (nonatomic, readonly) id requestToken;
|
||||||
|
@property (nonatomic, readonly) NSURLResponse *response;
|
||||||
|
@property (nonatomic, readonly) RCTURLRequestCompletionBlock completionBlock;
|
||||||
|
|
||||||
|
@property (nonatomic, copy) RCTURLRequestProgressBlock downloadProgressBlock;
|
||||||
|
@property (nonatomic, copy) RCTURLRequestIncrementalDataBlock incrementalDataBlock;
|
||||||
|
@property (nonatomic, copy) RCTURLRequestResponseBlock responseBlock;
|
||||||
|
@property (nonatomic, copy) RCTURLRequestProgressBlock uploadProgressBlock;
|
||||||
|
|
||||||
|
- (instancetype)initWithRequest:(NSURLRequest *)request
|
||||||
|
handler:(id<RCTURLRequestHandler>)handler
|
||||||
|
completionBlock:(RCTURLRequestCompletionBlock)completionBlock NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
- (void)cancel;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "RCTDownloadTask.h"
|
||||||
|
|
||||||
|
#import "RCTAssert.h"
|
||||||
|
|
||||||
|
@implementation RCTDownloadTask
|
||||||
|
{
|
||||||
|
NSMutableData *_data;
|
||||||
|
id<RCTURLRequestHandler> _handler;
|
||||||
|
RCTDownloadTask *_selfReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithRequest:(NSURLRequest *)request
|
||||||
|
handler:(id<RCTURLRequestHandler>)handler
|
||||||
|
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
|
||||||
|
{
|
||||||
|
RCTAssertParam(request);
|
||||||
|
RCTAssertParam(handler);
|
||||||
|
RCTAssertParam(completionBlock);
|
||||||
|
|
||||||
|
static NSUInteger requestID = 0;
|
||||||
|
|
||||||
|
if ((self = [super init])) {
|
||||||
|
if (!(_requestToken = [handler sendRequest:request withDelegate:self])) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
_requestID = @(requestID++);
|
||||||
|
_request = request;
|
||||||
|
_handler = handler;
|
||||||
|
_completionBlock = completionBlock;
|
||||||
|
_selfReference = self;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)invalidate
|
||||||
|
{
|
||||||
|
_selfReference = nil;
|
||||||
|
_completionBlock = nil;
|
||||||
|
_downloadProgressBlock = nil;
|
||||||
|
_incrementalDataBlock = nil;
|
||||||
|
_responseBlock = nil;
|
||||||
|
_uploadProgressBlock = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_NOT_IMPLEMENTED(-init)
|
||||||
|
|
||||||
|
- (void)cancel
|
||||||
|
{
|
||||||
|
if ([_handler respondsToSelector:@selector(cancelRequest:)]) {
|
||||||
|
[_handler cancelRequest:_requestToken];
|
||||||
|
}
|
||||||
|
[self invalidate];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
|
||||||
|
{
|
||||||
|
RCTAssert([requestToken isEqual:_requestToken], @"Unrecognized request token: %@", requestToken);
|
||||||
|
if (_uploadProgressBlock) {
|
||||||
|
_uploadProgressBlock(bytesSent, _request.HTTPBody.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
|
||||||
|
{
|
||||||
|
RCTAssert([requestToken isEqual:_requestToken], @"Unrecognized request token: %@", requestToken);
|
||||||
|
_response = response;
|
||||||
|
if (_responseBlock) {
|
||||||
|
_responseBlock(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
|
||||||
|
{
|
||||||
|
RCTAssert([requestToken isEqual:_requestToken], @"Unrecognized request token: %@", requestToken);
|
||||||
|
if (!_data) {
|
||||||
|
_data = [[NSMutableData alloc] init];
|
||||||
|
}
|
||||||
|
[_data appendData:data];
|
||||||
|
if (_incrementalDataBlock) {
|
||||||
|
_incrementalDataBlock(data);
|
||||||
|
}
|
||||||
|
if (_downloadProgressBlock && _response.expectedContentLength > 0) {
|
||||||
|
_downloadProgressBlock(_data.length, _response.expectedContentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
||||||
|
{
|
||||||
|
_completionBlock(_response, _data, error);
|
||||||
|
[self invalidate];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -10,6 +10,9 @@
|
||||||
#import "RCTURLRequestHandler.h"
|
#import "RCTURLRequestHandler.h"
|
||||||
#import "RCTInvalidating.h"
|
#import "RCTInvalidating.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the default RCTURLRequestHandler implementation for HTTP requests.
|
||||||
|
*/
|
||||||
@interface RCTHTTPRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
|
@interface RCTHTTPRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -50,8 +50,8 @@ RCT_EXPORT_MODULE()
|
||||||
return [@[@"http", @"https", @"file"] containsObject:[request.URL.scheme lowercaseString]];
|
return [@[@"http", @"https", @"file"] containsObject:[request.URL.scheme lowercaseString]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)sendRequest:(NSURLRequest *)request
|
- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
|
||||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||||
{
|
{
|
||||||
// Lazy setup
|
// Lazy setup
|
||||||
if (!_session && [self isValid]) {
|
if (!_session && [self isValid]) {
|
||||||
|
@ -68,9 +68,10 @@ RCT_EXPORT_MODULE()
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)cancelRequest:(NSURLSessionDataTask *)requestToken
|
- (void)cancelRequest:(NSURLSessionDataTask *)task
|
||||||
{
|
{
|
||||||
[requestToken cancel];
|
[task cancel];
|
||||||
|
[_delegates removeObjectForKey:task];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - NSURLSession delegate
|
#pragma mark - NSURLSession delegate
|
||||||
|
@ -81,10 +82,9 @@ RCT_EXPORT_MODULE()
|
||||||
totalBytesSent:(int64_t)totalBytesSent
|
totalBytesSent:(int64_t)totalBytesSent
|
||||||
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
||||||
{
|
{
|
||||||
[[_delegates objectForKey:task] URLRequest:task didUploadProgress:(double)totalBytesSent total:(double)totalBytesExpectedToSend];
|
[[_delegates objectForKey:task] URLRequest:task didSendDataWithProgress:totalBytesSent];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)URLSession:(NSURLSession *)session
|
- (void)URLSession:(NSURLSession *)session
|
||||||
dataTask:(NSURLSessionDataTask *)task
|
dataTask:(NSURLSessionDataTask *)task
|
||||||
didReceiveResponse:(NSURLResponse *)response
|
didReceiveResponse:(NSURLResponse *)response
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; };
|
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; };
|
||||||
|
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; };
|
||||||
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
|
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
|
||||||
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
|
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1372B7351AB03E7B00659ED6 /* RCTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTReachability.h; sourceTree = "<group>"; };
|
1372B7351AB03E7B00659ED6 /* RCTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTReachability.h; sourceTree = "<group>"; };
|
||||||
1372B7361AB03E7B00659ED6 /* RCTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTReachability.m; sourceTree = "<group>"; };
|
1372B7361AB03E7B00659ED6 /* RCTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTReachability.m; sourceTree = "<group>"; };
|
||||||
|
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = "<group>"; };
|
||||||
|
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = "<group>"; };
|
||||||
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
|
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
|
||||||
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = "<group>"; };
|
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = "<group>"; };
|
||||||
58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -48,6 +51,8 @@
|
||||||
58B511D21A9E6C8500147676 = {
|
58B511D21A9E6C8500147676 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */,
|
||||||
|
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
|
||||||
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
|
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
|
||||||
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
|
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
|
||||||
58B512061A9E6CE300147676 /* RCTNetworking.h */,
|
58B512061A9E6CE300147676 /* RCTNetworking.h */,
|
||||||
|
@ -124,6 +129,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
|
||||||
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */,
|
1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */,
|
||||||
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
|
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
|
||||||
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
|
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
|
||||||
|
|
|
@ -14,4 +14,3 @@
|
||||||
@interface RCTNetworking : NSObject <RCTBridgeModule>
|
@interface RCTNetworking : NSObject <RCTBridgeModule>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -11,18 +11,19 @@
|
||||||
|
|
||||||
#import "RCTAssert.h"
|
#import "RCTAssert.h"
|
||||||
#import "RCTConvert.h"
|
#import "RCTConvert.h"
|
||||||
|
#import "RCTDownloadTask.h"
|
||||||
#import "RCTURLRequestHandler.h"
|
#import "RCTURLRequestHandler.h"
|
||||||
#import "RCTEventDispatcher.h"
|
#import "RCTEventDispatcher.h"
|
||||||
#import "RCTHTTPRequestHandler.h"
|
#import "RCTHTTPRequestHandler.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
|
typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
|
||||||
|
|
||||||
@interface RCTNetworking ()<RCTURLRequestDelegate>
|
@interface RCTNetworking ()
|
||||||
|
|
||||||
- (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback;
|
|
||||||
|
|
||||||
|
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary *)data
|
||||||
|
callback:(RCTHTTPQueryResult)callback;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +31,7 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
|
||||||
*/
|
*/
|
||||||
@interface RCTHTTPFormDataHelper : NSObject
|
@interface RCTHTTPFormDataHelper : NSObject
|
||||||
|
|
||||||
@property (nonatomic, weak) RCTNetworking *dataManager;
|
@property (nonatomic, weak) RCTNetworking *networker;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -42,51 +43,49 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
|
||||||
NSString *boundary;
|
NSString *boundary;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)process:(NSArray *)formData callback:(void (^)(NSError *error, NSDictionary *result))callback
|
static NSString *RCTGenerateFormBoundary()
|
||||||
{
|
{
|
||||||
if (![formData count]) {
|
const size_t boundaryLength = 70;
|
||||||
callback(nil, nil);
|
const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./";
|
||||||
return;
|
|
||||||
|
char *bytes = malloc(boundaryLength);
|
||||||
|
size_t charCount = strlen(boundaryChars);
|
||||||
|
for (int i = 0; i < boundaryLength; i++) {
|
||||||
|
bytes[i] = boundaryChars[arc4random_uniform((u_int32_t)charCount)];
|
||||||
}
|
}
|
||||||
|
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (RCTURLRequestCancellationBlock)process:(NSArray *)formData
|
||||||
|
callback:(RCTHTTPQueryResult)callback
|
||||||
|
{
|
||||||
|
if (formData.count == 0) {
|
||||||
|
return callback(nil, nil);
|
||||||
|
}
|
||||||
|
|
||||||
parts = [formData mutableCopy];
|
parts = [formData mutableCopy];
|
||||||
_callback = callback;
|
_callback = callback;
|
||||||
multipartBody = [[NSMutableData alloc] init];
|
multipartBody = [[NSMutableData alloc] init];
|
||||||
boundary = [self generateBoundary];
|
boundary = RCTGenerateFormBoundary();
|
||||||
|
|
||||||
NSDictionary *currentPart = [parts objectAtIndex: 0];
|
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *error, NSDictionary *result) {
|
||||||
[_dataManager processDataForHTTPQuery:currentPart callback:^(NSError *e, NSDictionary *r) {
|
return [self handleResult:result error:error];
|
||||||
[self handleResult:r error:e];
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateBoundary
|
- (RCTURLRequestCancellationBlock)handleResult:(NSDictionary *)result
|
||||||
{
|
error:(NSError *)error
|
||||||
NSString *const boundaryChars = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./";
|
|
||||||
const NSUInteger boundaryLength = 70;
|
|
||||||
|
|
||||||
NSMutableString *output = [NSMutableString stringWithCapacity:boundaryLength];
|
|
||||||
NSUInteger numchars = [boundaryChars length];
|
|
||||||
for (NSUInteger i = 0; i < boundaryLength; i++) {
|
|
||||||
[output appendFormat:@"%C", [boundaryChars characterAtIndex:arc4random_uniform((u_int32_t)numchars)]];
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)handleResult:(NSDictionary *)result error:(NSError *)error
|
|
||||||
{
|
{
|
||||||
if (error) {
|
if (error) {
|
||||||
_callback(error, nil);
|
return _callback(error, nil);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
NSDictionary *currentPart = parts[0];
|
|
||||||
[parts removeObjectAtIndex:0];
|
|
||||||
|
|
||||||
// Start with boundary.
|
// Start with boundary.
|
||||||
[multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
[multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
||||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
|
||||||
// Print headers.
|
// Print headers.
|
||||||
NSMutableDictionary *headers = [(NSDictionary*)currentPart[@"headers"] mutableCopy];
|
NSMutableDictionary *headers = [parts[0][@"headers"] mutableCopy];
|
||||||
NSString *partContentType = result[@"contentType"];
|
NSString *partContentType = result[@"contentType"];
|
||||||
if (partContentType != nil) {
|
if (partContentType != nil) {
|
||||||
[headers setObject:partContentType forKey:@"content-type"];
|
[headers setObject:partContentType forKey:@"content-type"];
|
||||||
|
@ -101,110 +100,18 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result);
|
||||||
[multipartBody appendData:result[@"body"]];
|
[multipartBody appendData:result[@"body"]];
|
||||||
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
|
||||||
if ([parts count]) {
|
[parts removeObjectAtIndex:0];
|
||||||
NSDictionary *nextPart = [parts objectAtIndex: 0];
|
if (parts.count) {
|
||||||
[_dataManager processDataForHTTPQuery:nextPart callback:^(NSError *e, NSDictionary *r) {
|
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *err, NSDictionary *res) {
|
||||||
[self handleResult:r error:e];
|
return [self handleResult:res error:err];
|
||||||
}];
|
}];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've processed the last item. Finish and return.
|
// We've processed the last item. Finish and return.
|
||||||
[multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
|
[multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
|
||||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary];
|
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary];
|
||||||
_callback(nil, @{@"body": multipartBody, @"contentType": contentType});
|
return _callback(nil, @{@"body": multipartBody, @"contentType": contentType});
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to package in-flight requests together with their response data.
|
|
||||||
*/
|
|
||||||
@interface RCTActiveURLRequest : NSObject
|
|
||||||
|
|
||||||
@property (nonatomic, strong) NSNumber *requestID;
|
|
||||||
@property (nonatomic, strong) NSURLRequest *request;
|
|
||||||
@property (nonatomic, strong) id<RCTURLRequestHandler> handler;
|
|
||||||
@property (nonatomic, assign) BOOL incrementalUpdates;
|
|
||||||
@property (nonatomic, strong) NSURLResponse *response;
|
|
||||||
@property (nonatomic, strong) NSMutableData *data;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation RCTActiveURLRequest
|
|
||||||
|
|
||||||
- (instancetype)init
|
|
||||||
{
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_data = [[NSMutableData alloc] init];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to load request body data using a handler.
|
|
||||||
*/
|
|
||||||
@interface RCTDataLoader : NSObject <RCTURLRequestDelegate>
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError *error);
|
|
||||||
|
|
||||||
@implementation RCTDataLoader
|
|
||||||
{
|
|
||||||
RCTDataLoaderCallback _callback;
|
|
||||||
RCTActiveURLRequest *_request;
|
|
||||||
id _requestToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithRequest:(NSURLRequest *)request
|
|
||||||
handler:(id<RCTURLRequestHandler>)handler
|
|
||||||
callback:(RCTDataLoaderCallback)callback
|
|
||||||
{
|
|
||||||
RCTAssertParam(request);
|
|
||||||
RCTAssertParam(handler);
|
|
||||||
RCTAssertParam(callback);
|
|
||||||
|
|
||||||
if ((self = [super init])) {
|
|
||||||
_callback = callback;
|
|
||||||
_request = [[RCTActiveURLRequest alloc] init];
|
|
||||||
_request.request = request;
|
|
||||||
_request.handler = handler;
|
|
||||||
_request.incrementalUpdates = NO;
|
|
||||||
_requestToken = [handler sendRequest:request withDelegate:self];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init
|
|
||||||
{
|
|
||||||
return [self initWithRequest:nil handler:nil callback:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total
|
|
||||||
{
|
|
||||||
RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
|
|
||||||
{
|
|
||||||
RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
|
|
||||||
_request.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
|
|
||||||
{
|
|
||||||
RCTAssert([requestToken isEqual:_requestToken], @"Shouldn't ever happen");
|
|
||||||
[_request.data appendData:data];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
|
||||||
{
|
|
||||||
RCTAssert(_callback != nil, @"The callback property must be set");
|
|
||||||
_callback(_request.data, _request.response.MIMEType, error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -214,8 +121,7 @@ typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError
|
||||||
*/
|
*/
|
||||||
@implementation RCTNetworking
|
@implementation RCTNetworking
|
||||||
{
|
{
|
||||||
NSInteger _currentRequestID;
|
NSMutableDictionary *_tasksByRequestID;
|
||||||
NSMapTable *_activeRequests;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize bridge = _bridge;
|
@synthesize bridge = _bridge;
|
||||||
|
@ -226,16 +132,13 @@ RCT_EXPORT_MODULE()
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
{
|
{
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_currentRequestID = 0;
|
_tasksByRequestID = [[NSMutableDictionary alloc] init];
|
||||||
_activeRequests = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
|
|
||||||
valueOptions:NSPointerFunctionsStrongMemory
|
|
||||||
capacity:0];
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)buildRequest:(NSDictionary *)query
|
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
|
||||||
completionBlock:(void (^)(NSURLRequest *request))block
|
completionBlock:(void (^)(NSURLRequest *request))block
|
||||||
{
|
{
|
||||||
NSURL *URL = [RCTConvert NSURL:query[@"url"]];
|
NSURL *URL = [RCTConvert NSURL:query[@"url"]];
|
||||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
||||||
|
@ -243,11 +146,11 @@ RCT_EXPORT_MODULE()
|
||||||
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
|
request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
|
||||||
|
|
||||||
NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]];
|
NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]];
|
||||||
[self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) {
|
return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) {
|
||||||
if (error) {
|
if (error) {
|
||||||
RCTLogError(@"Error processing request body: %@", error);
|
RCTLogError(@"Error processing request body: %@", error);
|
||||||
// Ideally we'd circle back to JS here and notify an error/abort on the request.
|
// Ideally we'd circle back to JS here and notify an error/abort on the request.
|
||||||
return;
|
return (RCTURLRequestCancellationBlock)nil;
|
||||||
}
|
}
|
||||||
request.HTTPBody = result[@"body"];
|
request.HTTPBody = result[@"body"];
|
||||||
NSString *contentType = result[@"contentType"];
|
NSString *contentType = result[@"contentType"];
|
||||||
|
@ -262,6 +165,7 @@ RCT_EXPORT_MODULE()
|
||||||
}
|
}
|
||||||
|
|
||||||
block(request);
|
block(request);
|
||||||
|
return (RCTURLRequestCancellationBlock)nil;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,71 +220,56 @@ RCT_EXPORT_MODULE()
|
||||||
* - @"contentType" (NSString): the content type header of the request
|
* - @"contentType" (NSString): the content type header of the request
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
- (void)processDataForHTTPQuery:(NSDictionary *)query callback:(void (^)(NSError *error, NSDictionary *result))callback
|
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary *)query callback:
|
||||||
|
(RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))callback
|
||||||
{
|
{
|
||||||
if (!query) {
|
if (!query) {
|
||||||
callback(nil, nil);
|
return callback(nil, nil);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
NSData *body = [RCTConvert NSData:query[@"string"]];
|
NSData *body = [RCTConvert NSData:query[@"string"]];
|
||||||
if (body) {
|
if (body) {
|
||||||
callback(nil, @{@"body": body});
|
return callback(nil, @{@"body": body});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
||||||
if (request) {
|
if (request) {
|
||||||
|
|
||||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
return;
|
return callback(nil, nil);
|
||||||
}
|
}
|
||||||
(void)[[RCTDataLoader alloc] initWithRequest:request handler:handler callback:^(NSData *data, NSString *MIMEType, NSError *error) {
|
|
||||||
if (data) {
|
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
|
||||||
callback(nil, @{@"body": data, @"contentType": MIMEType});
|
RCTDownloadTask *task = [[RCTDownloadTask alloc] initWithRequest:request handler:handler completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||||
} else {
|
cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
|
||||||
callback(error, nil);
|
|
||||||
}
|
|
||||||
}];
|
}];
|
||||||
return;
|
|
||||||
|
__weak RCTDownloadTask *weakTask = task;
|
||||||
|
return ^{
|
||||||
|
[weakTask cancel];
|
||||||
|
if (cancellationBlock) {
|
||||||
|
cancellationBlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
|
NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
|
||||||
if (formData != nil) {
|
if (formData) {
|
||||||
RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init];
|
RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init];
|
||||||
formDataHelper.dataManager = self;
|
formDataHelper.networker = self;
|
||||||
[formDataHelper process:formData callback:callback];
|
return [formDataHelper process:formData callback:callback];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Nothing in the data payload, at least nothing we could understand anyway.
|
// Nothing in the data payload, at least nothing we could understand anyway.
|
||||||
// Ignore and treat it as if it were null.
|
// Ignore and treat it as if it were null.
|
||||||
callback(nil, nil);
|
return callback(nil, nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)sendRequest:(NSURLRequest *)request
|
- (void)sendData:(NSData *)data forTask:(RCTDownloadTask *)task
|
||||||
incrementalUpdates:(BOOL)incrementalUpdates
|
|
||||||
responseSender:(RCTResponseSenderBlock)responseSender
|
|
||||||
{
|
|
||||||
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
|
||||||
id token = [handler sendRequest:request withDelegate:self];
|
|
||||||
if (token) {
|
|
||||||
RCTActiveURLRequest *activeRequest = [[RCTActiveURLRequest alloc] init];
|
|
||||||
activeRequest.requestID = @(++_currentRequestID);
|
|
||||||
activeRequest.request = request;
|
|
||||||
activeRequest.handler = handler;
|
|
||||||
activeRequest.incrementalUpdates = incrementalUpdates;
|
|
||||||
[_activeRequests setObject:activeRequest forKey:token];
|
|
||||||
responseSender(@[activeRequest.requestID]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendData:(NSData *)data forRequestToken:(id)requestToken
|
|
||||||
{
|
{
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
|
||||||
|
|
||||||
// Get text encoding
|
// Get text encoding
|
||||||
NSURLResponse *response = request.response;
|
NSURLResponse *response = task.response;
|
||||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||||
if (response.textEncodingName) {
|
if (response.textEncodingName) {
|
||||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||||
|
@ -393,82 +282,81 @@ RCT_EXPORT_MODULE()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray *responseJSON = @[request.requestID, responseText ?: @""];
|
NSArray *responseJSON = @[task.requestID, responseText ?: @""];
|
||||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData"
|
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkData"
|
||||||
body:responseJSON];
|
body:responseJSON];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - RCTURLRequestDelegate
|
- (void)sendRequest:(NSURLRequest *)request
|
||||||
|
incrementalUpdates:(BOOL)incrementalUpdates
|
||||||
- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total
|
responseSender:(RCTResponseSenderBlock)responseSender
|
||||||
{
|
{
|
||||||
dispatch_async(_methodQueue, ^{
|
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
||||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
if (!handler) {
|
||||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NSArray *responseJSON = @[request.requestID, @(progress), @(total)];
|
__block RCTDownloadTask *task;
|
||||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didUploadProgress" body:responseJSON];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
|
RCTURLRequestProgressBlock uploadProgressBlock = ^(double progress, double total) {
|
||||||
{
|
dispatch_async(_methodQueue, ^{
|
||||||
dispatch_async(_methodQueue, ^{
|
NSArray *responseJSON = @[task.requestID, @(progress), @(total)];
|
||||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
[_bridge.eventDispatcher sendDeviceEventWithName:@"didSendNetworkData" body:responseJSON];
|
||||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
request.response = response;
|
void (^responseBlock)(NSURLResponse *) = ^(NSURLResponse *response) {
|
||||||
|
dispatch_async(_methodQueue, ^{
|
||||||
|
NSHTTPURLResponse *httpResponse = nil;
|
||||||
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||||
|
// Might be a local file request
|
||||||
|
httpResponse = (NSHTTPURLResponse *)response;
|
||||||
|
}
|
||||||
|
NSArray *responseJSON = @[task.requestID,
|
||||||
|
@(httpResponse.statusCode ?: 200),
|
||||||
|
httpResponse.allHeaderFields ?: @{},
|
||||||
|
];
|
||||||
|
|
||||||
NSHTTPURLResponse *httpResponse = nil;
|
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
|
||||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
body:responseJSON];
|
||||||
// Might be a local file request
|
});
|
||||||
httpResponse = (NSHTTPURLResponse *)response;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
NSArray *responseJSON = @[request.requestID,
|
void (^incrementalDataBlock)(NSData *) = incrementalUpdates ? ^(NSData *data) {
|
||||||
@(httpResponse.statusCode ?: 200),
|
dispatch_async(_methodQueue, ^{
|
||||||
httpResponse.allHeaderFields ?: @{},
|
[self sendData:data forTask:task];
|
||||||
];
|
});
|
||||||
|
} : nil;
|
||||||
|
|
||||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
|
RCTURLRequestCompletionBlock completionBlock =
|
||||||
body:responseJSON];
|
^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||||
});
|
dispatch_async(_methodQueue, ^{
|
||||||
}
|
if (!incrementalUpdates) {
|
||||||
|
[self sendData:data forTask:task];
|
||||||
|
}
|
||||||
|
NSArray *responseJSON = @[task.requestID,
|
||||||
|
RCTNullIfNil(error.localizedDescription),
|
||||||
|
];
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
|
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
|
||||||
{
|
body:responseJSON];
|
||||||
dispatch_async(_methodQueue, ^{
|
|
||||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
|
||||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
|
||||||
|
|
||||||
if (request.incrementalUpdates) {
|
[_tasksByRequestID removeObjectForKey:task.requestID];
|
||||||
[self sendData:data forRequestToken:requestToken];
|
});
|
||||||
} else {
|
};
|
||||||
[request.data appendData:data];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
|
task = [[RCTDownloadTask alloc] initWithRequest:request
|
||||||
{
|
handler:handler
|
||||||
dispatch_async(_methodQueue, ^{
|
completionBlock:completionBlock];
|
||||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:requestToken];
|
|
||||||
RCTAssert(request != nil, @"Unrecognized request token: %@", requestToken);
|
|
||||||
|
|
||||||
if (!request.incrementalUpdates) {
|
task.incrementalDataBlock = incrementalDataBlock;
|
||||||
[self sendData:request.data forRequestToken:requestToken];
|
task.responseBlock = responseBlock;
|
||||||
}
|
task.uploadProgressBlock = uploadProgressBlock;
|
||||||
|
|
||||||
NSArray *responseJSON = @[
|
if (task.requestID) {
|
||||||
request.requestID,
|
_tasksByRequestID[task.requestID] = task;
|
||||||
RCTNullIfNil(error.localizedDescription),
|
responseSender(@[task.requestID]);
|
||||||
];
|
}
|
||||||
|
|
||||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"didCompleteNetworkResponse"
|
|
||||||
body:responseJSON];
|
|
||||||
|
|
||||||
[_activeRequests removeObjectForKey:requestToken];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - JS API
|
#pragma mark - JS API
|
||||||
|
@ -476,31 +364,22 @@ RCT_EXPORT_MODULE()
|
||||||
RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
|
RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
|
||||||
responseSender:(RCTResponseSenderBlock)responseSender)
|
responseSender:(RCTResponseSenderBlock)responseSender)
|
||||||
{
|
{
|
||||||
|
// TODO: buildRequest returns a cancellation block, but there's currently
|
||||||
|
// no way to invoke it, if, for example the request is cancelled while
|
||||||
|
// loading a large file to build the request body
|
||||||
[self buildRequest:query completionBlock:^(NSURLRequest *request) {
|
[self buildRequest:query completionBlock:^(NSURLRequest *request) {
|
||||||
|
|
||||||
BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
|
BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
|
||||||
[self sendRequest:request incrementalUpdates:incrementalUpdates
|
[self sendRequest:request
|
||||||
|
incrementalUpdates:incrementalUpdates
|
||||||
responseSender:responseSender];
|
responseSender:responseSender];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(cancelRequest:(NSNumber *)requestID)
|
RCT_EXPORT_METHOD(cancelRequest:(NSNumber *)requestID)
|
||||||
{
|
{
|
||||||
id requestToken = nil;
|
[_tasksByRequestID[requestID] cancel];
|
||||||
RCTActiveURLRequest *activeRequest = nil;
|
[_tasksByRequestID removeObjectForKey:requestID];
|
||||||
for (id token in _activeRequests) {
|
|
||||||
RCTActiveURLRequest *request = [_activeRequests objectForKey:token];
|
|
||||||
if ([request.requestID isEqualToNumber:requestID]) {
|
|
||||||
activeRequest = request;
|
|
||||||
requestToken = token;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id<RCTURLRequestHandler> handler = activeRequest.handler;
|
|
||||||
if ([handler respondsToSelector:@selector(cancelRequest:)]) {
|
|
||||||
[activeRequest.handler cancelRequest:requestToken];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -35,7 +35,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
|
||||||
_didCreateRequest(requestId: number): void {
|
_didCreateRequest(requestId: number): void {
|
||||||
this._requestId = requestId;
|
this._requestId = requestId;
|
||||||
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
|
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
|
||||||
'didUploadProgress',
|
'didSendNetworkData',
|
||||||
(args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
|
(args) => this._didUploadProgress.call(this, args[0], args[1], args[2])
|
||||||
));
|
));
|
||||||
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
|
this._subscriptions.push(RCTDeviceEventEmitter.addListener(
|
||||||
|
|
|
@ -21,3 +21,6 @@ declare var fetch: any;
|
||||||
declare var Headers: any;
|
declare var Headers: any;
|
||||||
declare var Request: any;
|
declare var Request: any;
|
||||||
declare var Response: any;
|
declare var Response: any;
|
||||||
|
declare module requestAnimationFrame {
|
||||||
|
declare var exports: (callback: any) => any;
|
||||||
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
@protocol RCTURLRequestDelegate <NSObject>
|
@protocol RCTURLRequestDelegate <NSObject>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this when you first receives a response from the server. This should
|
* Call this when you send request data to the server. This is used to track
|
||||||
* include response headers, etc.
|
* upload progress, so should be called multiple times for large request bodies.
|
||||||
*/
|
*/
|
||||||
- (void)URLRequest:(id)requestToken didUploadProgress:(double)progress total:(double)total;
|
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this when you first receives a response from the server. This should
|
* Call this when you first receives a response from the server. This should
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Copyright (c) 2015-present, Facebook, Inc.
|
# Copyright (c) 2015-present, Facebook, Inc.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Copyright (c) 2015-present, Facebook, Inc.
|
# Copyright (c) 2015-present, Facebook, Inc.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
|
|
Loading…
Reference in New Issue