25
.flowconfig
|
@ -7,12 +7,19 @@
|
|||
# Some modules have their own node_modules with overlap
|
||||
.*/node_modules/node-haste/.*
|
||||
|
||||
# Ignore react-tools where there are overlaps, but don't ignore anything that
|
||||
# react-native relies on
|
||||
.*/node_modules/react-tools/src/React.js
|
||||
.*/node_modules/react-tools/src/renderers/shared/event/EventPropagators.js
|
||||
.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
|
||||
.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js
|
||||
# Ignore react and fbjs where there are overlaps, but don't ignore
|
||||
# anything that react-native relies on
|
||||
.*/node_modules/fbjs-haste/.*/__tests__/.*
|
||||
.*/node_modules/fbjs-haste/__forks__/Map.js
|
||||
.*/node_modules/fbjs-haste/__forks__/Promise.js
|
||||
.*/node_modules/fbjs-haste/__forks__/fetch.js
|
||||
.*/node_modules/fbjs-haste/core/ExecutionEnvironment.js
|
||||
.*/node_modules/fbjs-haste/core/isEmpty.js
|
||||
.*/node_modules/fbjs-haste/crypto/crc32.js
|
||||
.*/node_modules/fbjs-haste/stubs/ErrorUtils.js
|
||||
.*/node_modules/react-haste/React.js
|
||||
.*/node_modules/react-haste/renderers/dom/ReactDOM.js
|
||||
.*/node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/commoner/test/.*
|
||||
|
@ -43,9 +50,9 @@ suppress_type=$FlowIssue
|
|||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-7]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-7]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
[version]
|
||||
0.17.0
|
||||
0.18.1
|
||||
|
|
|
@ -82,3 +82,4 @@ env:
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^.*-stable$/
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
launchOptions:launchOptions];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [[UIViewController alloc] init];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
launchOptions:launchOptions];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [[UIViewController alloc] init];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
|
|
|
@ -69,7 +69,7 @@ public class MoviesActivity extends Activity implements DefaultHardwareBackBtnHa
|
|||
super.onResume();
|
||||
|
||||
if (mReactInstanceManager != null) {
|
||||
mReactInstanceManager.onResume(this);
|
||||
mReactInstanceManager.onResume(this, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
launchOptions:launchOptions];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [[UIViewController alloc] init];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
|
|
|
@ -24,9 +24,9 @@ var {
|
|||
} = React;
|
||||
|
||||
var BUTTONS = [
|
||||
'Button Index: 0',
|
||||
'Button Index: 1',
|
||||
'Button Index: 2',
|
||||
'Option 0',
|
||||
'Option 1',
|
||||
'Option 2',
|
||||
'Destruct',
|
||||
'Cancel',
|
||||
];
|
||||
|
@ -47,7 +47,7 @@ var ActionSheetExample = React.createClass({
|
|||
Click to show the ActionSheet
|
||||
</Text>
|
||||
<Text>
|
||||
Clicked button at index: "{this.state.clicked}"
|
||||
Clicked button: {this.state.clicked}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -132,7 +132,7 @@ class PromptExample extends React.Component {
|
|||
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
onPress={this.prompt.bind(this, this.title, this.promptResponse)}>
|
||||
onPress={this.prompt.bind(this, this.title, null, null, this.promptResponse)}>
|
||||
|
||||
<View style={styles.button}>
|
||||
<Text>
|
||||
|
@ -143,7 +143,7 @@ class PromptExample extends React.Component {
|
|||
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
onPress={this.prompt.bind(this, this.title, this.buttons)}>
|
||||
onPress={this.prompt.bind(this, this.title, null, this.buttons, null)}>
|
||||
|
||||
<View style={styles.button}>
|
||||
<Text>
|
||||
|
@ -154,7 +154,7 @@ class PromptExample extends React.Component {
|
|||
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
onPress={this.prompt.bind(this, this.title, this.defaultValue, this.promptResponse)}>
|
||||
onPress={this.prompt.bind(this, this.title, this.defaultValue, null, this.promptResponse)}>
|
||||
|
||||
<View style={styles.button}>
|
||||
<Text>
|
||||
|
@ -165,7 +165,7 @@ class PromptExample extends React.Component {
|
|||
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
onPress={this.prompt.bind(this, this.title, this.defaultValue, this.buttons)}>
|
||||
onPress={this.prompt.bind(this, this.title, this.defaultValue, this.buttons, null)}>
|
||||
|
||||
<View style={styles.button}>
|
||||
<Text>
|
||||
|
|
|
@ -91,6 +91,13 @@ var styles = StyleSheet.create({
|
|||
width: 100,
|
||||
height: 100
|
||||
},
|
||||
border8: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderColor: 'black',
|
||||
marginRight: 10,
|
||||
backgroundColor: 'lightgrey',
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = 'Border';
|
||||
|
@ -159,4 +166,18 @@ exports.examples = [
|
|||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Single Borders',
|
||||
description: 'top, left, bottom right',
|
||||
render() {
|
||||
return (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={[styles.box, styles.border8, {borderTopWidth: 5}]} />
|
||||
<View style={[styles.box, styles.border8, {borderLeftWidth: 5}]} />
|
||||
<View style={[styles.box, styles.border8, {borderBottomWidth: 5}]} />
|
||||
<View style={[styles.box, styles.border8, {borderRightWidth: 5}]} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
|
|
@ -143,6 +143,7 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
innerContainer: {
|
||||
borderRadius: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
row: {
|
||||
alignItems: 'center',
|
||||
|
@ -158,6 +159,7 @@ var styles = StyleSheet.create({
|
|||
borderRadius: 5,
|
||||
flex: 1,
|
||||
height: 44,
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
|
|
@ -20,6 +20,31 @@ var React = require('React');
|
|||
var UIExplorerBlock = require('UIExplorerBlock');
|
||||
var UIExplorerPage = require('UIExplorerPage');
|
||||
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var MovingBar = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
progress: 0
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.setInterval(
|
||||
() => {
|
||||
var progress = (this.state.progress + 0.02) % 1;
|
||||
this.setState({progress: progress});
|
||||
}, 50
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <ProgressBar progress={this.state.progress} {...this.props} />;
|
||||
},
|
||||
});
|
||||
|
||||
var ProgressBarAndroidExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
|
@ -54,6 +79,26 @@ var ProgressBarAndroidExample = React.createClass({
|
|||
<UIExplorerBlock title="Large Inverse ProgressBar">
|
||||
<ProgressBar styleAttr="LargeInverse" />
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Horizontal Indeterminate ProgressBar">
|
||||
<ProgressBar styleAttr="Horizontal" />
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Horizontal ProgressBar">
|
||||
<MovingBar styleAttr="Horizontal" indeterminate={false} />
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Large Red ProgressBar">
|
||||
<ProgressBar styleAttr="Large" color="red" />
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Horizontal Black Indeterminate ProgressBar">
|
||||
<ProgressBar styleAttr="Horizontal" color="black" />
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Horizontal Blue ProgressBar">
|
||||
<MovingBar styleAttr="Horizontal" indeterminate={false} color="blue" />
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@ var SliderExample = React.createClass({
|
|||
{this.state.value}
|
||||
</Text>
|
||||
<SliderIOS
|
||||
style={styles.slider}
|
||||
{...this.props}
|
||||
onValueChange={(value) => this.setState({value: value})} />
|
||||
</View>
|
||||
);
|
||||
|
@ -62,7 +62,26 @@ exports.displayName = 'SliderExample';
|
|||
exports.description = 'Slider input for numeric values';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'SliderIOS',
|
||||
render(): ReactElement { return <SliderExample />; }
|
||||
title: 'Default settings',
|
||||
render(): ReactElement {
|
||||
return <SliderExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'minimumValue: -1, maximumValue: 2',
|
||||
render(): ReactElement {
|
||||
return (
|
||||
<SliderExample
|
||||
minimumValue={-1}
|
||||
maximumValue={2}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'step: 0.25',
|
||||
render(): ReactElement {
|
||||
return <SliderExample step={0.25} />;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -160,6 +160,22 @@ var TextExample = React.createClass({
|
|||
</View>
|
||||
</View>
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Custom Fonts">
|
||||
<View style={{flexDirection: 'row', alignItems: 'flex-start'}}>
|
||||
<View style={{flex: 1}}>
|
||||
<Text style={{fontFamily: 'notoserif'}}>
|
||||
NotoSerif Regular
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'notoserif', fontStyle: 'italic', fontWeight: 'bold'}}>
|
||||
NotoSerif Bold Italic
|
||||
</Text>
|
||||
<Text style={{fontFamily: 'notoserif', fontStyle: 'italic'}}>
|
||||
NotoSerif Italic (Missing Font file)
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</UIExplorerBlock>
|
||||
|
||||
<UIExplorerBlock title="Font Size">
|
||||
<Text style={{fontSize: 23}}>
|
||||
Size 23
|
||||
|
|
|
@ -243,6 +243,21 @@ exports.examples = [
|
|||
</Text>
|
||||
)
|
||||
</Text>
|
||||
<Text style={{opacity:0.7}}>
|
||||
(opacity
|
||||
<Text>
|
||||
(is inherited
|
||||
<Text style={{opacity:0.7}}>
|
||||
(and accumulated
|
||||
<Text style={{backgroundColor:'#ffaaaa'}}>
|
||||
(and also applies to the background)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
<Text style={{fontSize: 12}}>
|
||||
<Entity>Entity Name</Entity>
|
||||
</Text>
|
||||
|
|
|
@ -78,15 +78,79 @@ class RewriteExample extends React.Component {
|
|||
this.state = {text: ''};
|
||||
}
|
||||
render() {
|
||||
var limit = 20;
|
||||
var remainder = limit - this.state.text.length;
|
||||
var remainderColor = remainder > 5 ? 'blue' : 'red';
|
||||
return (
|
||||
<TextInput
|
||||
onChangeText={(text) => {
|
||||
text = text.replace(/ /g, '_');
|
||||
this.setState({text});
|
||||
}}
|
||||
style={styles.singleLine}
|
||||
value={this.state.text}
|
||||
/>
|
||||
<View style={styles.rewriteContainer}>
|
||||
<TextInput
|
||||
multiline={false}
|
||||
maxLength={limit}
|
||||
onChangeText={(text) => {
|
||||
text = text.replace(/ /g, '_');
|
||||
this.setState({text});
|
||||
}}
|
||||
style={styles.default}
|
||||
value={this.state.text}
|
||||
/>
|
||||
<Text style={[styles.remainder, {color: remainderColor}]}>
|
||||
{remainder}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenizedTextExample extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {text: 'Hello #World'};
|
||||
}
|
||||
render() {
|
||||
|
||||
//define delimiter
|
||||
let delimiter = /\s+/;
|
||||
|
||||
//split string
|
||||
let _text = this.state.text;
|
||||
let token, index, parts = [];
|
||||
while (_text) {
|
||||
delimiter.lastIndex = 0;
|
||||
token = delimiter.exec(_text);
|
||||
if (token === null) {
|
||||
break;
|
||||
}
|
||||
index = token.index;
|
||||
if (token[0].length === 0) {
|
||||
index = 1;
|
||||
}
|
||||
parts.push(_text.substr(0, index));
|
||||
parts.push(token[0]);
|
||||
index = index + token[0].length;
|
||||
_text = _text.slice(index);
|
||||
}
|
||||
parts.push(_text);
|
||||
|
||||
//highlight hashtags
|
||||
parts = parts.map((text) => {
|
||||
if (/^#/.test(text)) {
|
||||
return <Text key={text} style={styles.hashtag}>{text}</Text>;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text});
|
||||
}}>
|
||||
<Text>{parts}</Text>
|
||||
</TextInput>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +173,10 @@ var styles = StyleSheet.create({
|
|||
singleLineWithHeightTextInput: {
|
||||
height: 30,
|
||||
},
|
||||
hashtag: {
|
||||
color: 'blue',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<TextInput>';
|
||||
|
@ -322,4 +390,10 @@ exports.examples = [
|
|||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Attributed text',
|
||||
render: function() {
|
||||
return <TokenizedTextExample />;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
|
|
@ -42,6 +42,7 @@ var TextEventsExample = React.createClass({
|
|||
curText: '<No Event>',
|
||||
prevText: '<No Event>',
|
||||
prev2Text: '<No Event>',
|
||||
prev3Text: '<No Event>',
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -51,6 +52,7 @@ var TextEventsExample = React.createClass({
|
|||
curText: text,
|
||||
prevText: state.curText,
|
||||
prev2Text: state.prevText,
|
||||
prev3Text: state.prev2Text,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
@ -73,12 +75,16 @@ var TextEventsExample = React.createClass({
|
|||
onSubmitEditing={(event) => this.updateText(
|
||||
'onSubmitEditing text: ' + event.nativeEvent.text
|
||||
)}
|
||||
onKeyPress={(event) => {
|
||||
this.updateText('onKeyPress key: ' + event.nativeEvent.key);
|
||||
}}
|
||||
style={styles.default}
|
||||
/>
|
||||
<Text style={styles.eventLabel}>
|
||||
{this.state.curText}{'\n'}
|
||||
(prev: {this.state.prevText}){'\n'}
|
||||
(prev2: {this.state.prev2Text})
|
||||
(prev2: {this.state.prev2Text}){'\n'}
|
||||
(prev3: {this.state.prev3Text})
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
@ -114,6 +120,114 @@ class RewriteExample extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
class TokenizedTextExample extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {text: 'Hello #World'};
|
||||
}
|
||||
render() {
|
||||
|
||||
//define delimiter
|
||||
let delimiter = /\s+/;
|
||||
|
||||
//split string
|
||||
let _text = this.state.text;
|
||||
let token, index, parts = [];
|
||||
while (_text) {
|
||||
delimiter.lastIndex = 0;
|
||||
token = delimiter.exec(_text);
|
||||
if (token === null) {
|
||||
break;
|
||||
}
|
||||
index = token.index;
|
||||
if (token[0].length === 0) {
|
||||
index = 1;
|
||||
}
|
||||
parts.push(_text.substr(0, index));
|
||||
parts.push(token[0]);
|
||||
index = index + token[0].length;
|
||||
_text = _text.slice(index);
|
||||
}
|
||||
parts.push(_text);
|
||||
|
||||
//highlight hashtags
|
||||
parts = parts.map((text) => {
|
||||
if (/^#/.test(text)) {
|
||||
return <Text style={styles.hashtag}>{text}</Text>;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
onChangeText={(text) => {
|
||||
this.setState({text});
|
||||
}}>
|
||||
<Text>{parts}</Text>
|
||||
</TextInput>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var BlurOnSubmitExample = React.createClass({
|
||||
focusNextField(nextField) {
|
||||
this.refs[nextField].focus()
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
ref='1'
|
||||
style={styles.default}
|
||||
placeholder='blurOnSubmit = false'
|
||||
returnKeyType='next'
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('2')}
|
||||
/>
|
||||
<TextInput
|
||||
ref='2'
|
||||
style={styles.default}
|
||||
keyboardType='email-address'
|
||||
placeholder='blurOnSubmit = false'
|
||||
returnKeyType='next'
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('3')}
|
||||
/>
|
||||
<TextInput
|
||||
ref='3'
|
||||
style={styles.default}
|
||||
keyboardType='url'
|
||||
placeholder='blurOnSubmit = false'
|
||||
returnKeyType='next'
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('4')}
|
||||
/>
|
||||
<TextInput
|
||||
ref='4'
|
||||
style={styles.default}
|
||||
keyboardType='numeric'
|
||||
placeholder='blurOnSubmit = false'
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => this.focusNextField('5')}
|
||||
/>
|
||||
<TextInput
|
||||
ref='5'
|
||||
style={styles.default}
|
||||
keyboardType='numbers-and-punctuation'
|
||||
placeholder='blurOnSubmit = true'
|
||||
returnKeyType='done'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
page: {
|
||||
paddingBottom: 300,
|
||||
|
@ -172,6 +286,10 @@ var styles = StyleSheet.create({
|
|||
textAlign: 'right',
|
||||
width: 24,
|
||||
},
|
||||
hashtag: {
|
||||
color: 'blue',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
|
@ -437,5 +555,15 @@ exports.examples = [
|
|||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Attributed text',
|
||||
render: function() {
|
||||
return <TokenizedTextExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Blur on submit',
|
||||
render: function(): ReactElement { return <BlurOnSubmitExample />; },
|
||||
},
|
||||
];
|
||||
|
|
|
@ -31,7 +31,7 @@ var ToastExample = React.createClass({
|
|||
|
||||
statics: {
|
||||
title: 'Toast Example',
|
||||
description: 'Example that demostrates the use of an Android Toast to provide feedback.',
|
||||
description: 'Example that demonstrates the use of an Android Toast to provide feedback.',
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
|
|
@ -101,6 +101,12 @@ var ToolbarAndroidExample = React.createClass({
|
|||
title="Bunny and Hawk"
|
||||
style={styles.toolbar} />
|
||||
</UIExplorerBlock>
|
||||
<UIExplorerBlock title="Toolbar with custom overflowIcon">
|
||||
<ToolbarAndroid
|
||||
actions={toolbarActions}
|
||||
overflowIcon={require('./bunny.png')}
|
||||
style={styles.toolbar} />
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
} = React;
|
||||
|
||||
var TransparentHitTestExample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<TouchableOpacity onPress={() => alert('Hi!')}>
|
||||
<Text>HELLO!</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
backgroundColor: 'green',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
opacity: 0.0}} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<TransparentHitTestExample>';
|
||||
exports.displayName = 'TransparentHitTestExample';
|
||||
exports.description = 'Transparent view receiving touch events';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'TransparentHitTestExample',
|
||||
render(): ReactElement { return <TransparentHitTestExample />; }
|
||||
}
|
||||
];
|
|
@ -51,6 +51,7 @@
|
|||
14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
|
||||
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
|
||||
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
|
||||
3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; };
|
||||
3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; };
|
||||
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
|
||||
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
|
||||
|
@ -222,6 +223,7 @@
|
|||
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = ../../Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj; sourceTree = "<group>"; };
|
||||
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
|
||||
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
|
||||
3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; };
|
||||
3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = "<group>"; };
|
||||
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
|
||||
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
|
||||
|
@ -436,6 +438,7 @@
|
|||
143BC5971B21E3E100462512 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */,
|
||||
143BC5981B21E3E100462512 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
|
@ -817,6 +820,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -966,6 +970,7 @@
|
|||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.internal.uiexplorer.local;
|
||||
PRODUCT_NAME = UIExplorer;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -985,6 +990,7 @@
|
|||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.internal.uiexplorer.local;
|
||||
PRODUCT_NAME = UIExplorer;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -1076,6 +1082,7 @@
|
|||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
|
@ -1103,6 +1110,7 @@
|
|||
WARNING_CFLAGS = (
|
||||
"-Wextra",
|
||||
"-Wall",
|
||||
"-Wincompatible-pointer-types",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
|
@ -1133,6 +1141,7 @@
|
|||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
|
@ -1160,6 +1169,7 @@
|
|||
WARNING_CFLAGS = (
|
||||
"-Wextra",
|
||||
"-Wall",
|
||||
"-Wincompatible-pointer-types",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
|
|
|
@ -73,5 +73,6 @@ var styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp);
|
||||
UIExplorerList.registerComponents();
|
||||
|
||||
module.exports = UIExplorerApp;
|
||||
|
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 277 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
@ -36,7 +36,7 @@
|
|||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert(version.majorVersion == 8 || version.minorVersion >= 3, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp", nil);
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ RCT_TEST(IntegrationTestHarnessTest)
|
|||
RCT_TEST(TimersTest)
|
||||
RCT_TEST(AsyncStorageTest)
|
||||
RCT_TEST(AppEventsTest)
|
||||
RCT_TEST(ImageSnapshotTest)
|
||||
//RCT_TEST(ImageSnapshotTest) // Disabled: #8985988
|
||||
RCT_TEST(SimpleSnapshotTest)
|
||||
|
||||
// Disable due to flakiness: #8686784
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||
RCTAssert(version.majorVersion == 8 || version.minorVersion >= 3, @"Snapshot tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios", nil);
|
||||
_runner.recordMode = NO;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ RCT_TEST(LayoutExample)
|
|||
RCT_TEST(TextExample)
|
||||
RCT_TEST(SwitchExample)
|
||||
RCT_TEST(SliderExample)
|
||||
RCT_TEST(TabBarExample)
|
||||
//RCT_TEST(TabBarExample) // Disabled: #8985988
|
||||
|
||||
- (void)testZZZNotInRecordMode
|
||||
{
|
||||
|
|
|
@ -50,6 +50,7 @@ var COMPONENTS = [
|
|||
require('./TextExample.ios'),
|
||||
require('./TextInputExample.ios'),
|
||||
require('./TouchableExample'),
|
||||
require('./TransparentHitTestExample'),
|
||||
require('./ViewExample'),
|
||||
require('./WebViewExample'),
|
||||
];
|
||||
|
@ -78,23 +79,6 @@ var APIS = [
|
|||
require('./ImageEditingExample'),
|
||||
];
|
||||
|
||||
// Register suitable examples for snapshot tests
|
||||
COMPONENTS.concat(APIS).forEach((Example) => {
|
||||
if (Example.displayName) {
|
||||
var Snapshotter = React.createClass({
|
||||
render: function() {
|
||||
var Renderable = UIExplorerListBase.makeRenderable(Example);
|
||||
return (
|
||||
<SnapshotViewIOS>
|
||||
<Renderable />
|
||||
</SnapshotViewIOS>
|
||||
);
|
||||
},
|
||||
});
|
||||
AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
|
||||
}
|
||||
});
|
||||
|
||||
type Props = {
|
||||
navigator: {
|
||||
navigationContext: NavigationContext,
|
||||
|
@ -143,6 +127,25 @@ class UIExplorerList extends React.Component {
|
|||
onPressRow(example: any) {
|
||||
this._openExample(example);
|
||||
}
|
||||
|
||||
// Register suitable examples for snapshot tests
|
||||
static registerComponents() {
|
||||
COMPONENTS.concat(APIS).forEach((Example) => {
|
||||
if (Example.displayName) {
|
||||
var Snapshotter = React.createClass({
|
||||
render: function() {
|
||||
var Renderable = UIExplorerListBase.makeRenderable(Example);
|
||||
return (
|
||||
<SnapshotViewIOS>
|
||||
<Renderable />
|
||||
</SnapshotViewIOS>
|
||||
);
|
||||
},
|
||||
});
|
||||
AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
- (void)testLayoutSubviewsOrdering
|
||||
{
|
||||
// create some Views and ViewControllers
|
||||
UIViewController *parentVC = [[UIViewController alloc] init];
|
||||
UIView *parentView = [[UIView alloc] init];
|
||||
UIViewController *childVC = [[UIViewController alloc] init];
|
||||
UIView *childView = [[UIView alloc] init];
|
||||
UIViewController *parentVC = [UIViewController new];
|
||||
UIView *parentView = [UIView new];
|
||||
UIViewController *childVC = [UIViewController new];
|
||||
UIView *childView = [UIView new];
|
||||
|
||||
// The ordering we expect is:
|
||||
// parentView::layoutSubviews
|
||||
|
|
|
@ -136,19 +136,25 @@ _Pragma("clang diagnostic pop")
|
|||
NSString *injectedStuff;
|
||||
RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]));
|
||||
|
||||
NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL);
|
||||
NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"];
|
||||
NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"];
|
||||
NSDictionary *constants = testModuleConfig[@"constants"];
|
||||
NSDictionary *methods = testModuleConfig[@"methods"];
|
||||
__block NSNumber *testModuleID = nil;
|
||||
__block NSDictionary *testConstants = nil;
|
||||
__block NSNumber *testMethodID = nil;
|
||||
|
||||
NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
|
||||
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, BOOL *stop) {
|
||||
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
|
||||
testModuleID = @(i);
|
||||
testConstants = moduleConfig[1];
|
||||
testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
XCTAssertNotNil(moduleConfig);
|
||||
XCTAssertNotNil(remoteModuleConfig);
|
||||
XCTAssertNotNil(testModuleConfig);
|
||||
XCTAssertNotNil(constants);
|
||||
XCTAssertEqualObjects(constants[@"eleventyMillion"], @42);
|
||||
XCTAssertNotNil(methods);
|
||||
XCTAssertNotNil(methods[@"testMethod"]);
|
||||
XCTAssertNotNil(testModuleID);
|
||||
XCTAssertNotNil(testConstants);
|
||||
XCTAssertEqualObjects(testConstants[@"eleventyMillion"], @42);
|
||||
XCTAssertNotNil(testMethodID);
|
||||
}
|
||||
|
||||
- (void)testCallNativeMethod
|
||||
|
@ -158,13 +164,19 @@ _Pragma("clang diagnostic pop")
|
|||
NSString *injectedStuff;
|
||||
RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]));
|
||||
|
||||
NSDictionary *moduleConfig = RCTJSONParse(injectedStuff, NULL);
|
||||
NSDictionary *remoteModuleConfig = moduleConfig[@"remoteModuleConfig"];
|
||||
NSDictionary *testModuleConfig = remoteModuleConfig[@"TestModule"];
|
||||
NSNumber *testModuleID = testModuleConfig[@"moduleID"];
|
||||
NSDictionary *methods = testModuleConfig[@"methods"];
|
||||
NSDictionary *testMethod = methods[@"testMethod"];
|
||||
NSNumber *testMethodID = testMethod[@"methodID"];
|
||||
__block NSNumber *testModuleID = nil;
|
||||
__block NSDictionary *testConstants = nil;
|
||||
__block NSNumber *testMethodID = nil;
|
||||
|
||||
NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
|
||||
[remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) {
|
||||
if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
|
||||
testModuleID = @(i);
|
||||
testConstants = moduleConfig[1];
|
||||
testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
|
||||
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567];
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
} \
|
||||
|
||||
#define TEST_BUNDLE_PATH(name, _input, _expectedPath) \
|
||||
TEST_PATH(name, _input, [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:_expectedPath])
|
||||
TEST_PATH(name, _input, [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:_expectedPath])
|
||||
|
||||
// Basic tests
|
||||
TEST_URL(basic, @"http://example.com", @"http://example.com")
|
||||
|
|
|
@ -64,6 +64,8 @@ static BOOL RCTLogsError(void (^block)(void))
|
|||
- (void)doFooWithInteger:(__unused NSInteger)n { }
|
||||
- (void)doFooWithCGRect:(CGRect)s { _s = s; }
|
||||
|
||||
- (void)doFoo : (__unused NSString *)foo { }
|
||||
|
||||
- (void)testNumbersNonnull
|
||||
{
|
||||
{
|
||||
|
@ -121,4 +123,22 @@ static BOOL RCTLogsError(void (^block)(void))
|
|||
XCTAssertTrue(CGRectEqualToRect(r, _s));
|
||||
}
|
||||
|
||||
- (void)testWhitespaceTolerance
|
||||
{
|
||||
NSString *methodName = @"doFoo : \t (NSString *)foo";
|
||||
|
||||
__block RCTModuleMethod *method;
|
||||
XCTAssertFalse(RCTLogsError(^{
|
||||
method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
|
||||
JSMethodName:nil
|
||||
moduleClass:[self class]];
|
||||
}));
|
||||
|
||||
XCTAssertEqualObjects(method.JSMethodName, @"doFoo");
|
||||
|
||||
XCTAssertFalse(RCTLogsError(^{
|
||||
[method invokeWithBridge:nil module:self arguments:@[@"bar"]];
|
||||
}));
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -83,7 +83,8 @@
|
|||
[parentView insertReactSubview:mainView atIndex:1];
|
||||
[parentView insertReactSubview:footerView atIndex:2];
|
||||
|
||||
[parentView collectRootUpdatedFrames:nil parentConstraint:CGSizeZero];
|
||||
parentView.reactTag = @1; // must be valid rootView tag
|
||||
[parentView collectRootUpdatedFrames:nil];
|
||||
|
||||
XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
|
||||
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
|
||||
|
|
|
@ -96,6 +96,7 @@ var WebViewExample = React.createClass({
|
|||
url={this.state.url}
|
||||
javaScriptEnabledAndroid={true}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
|
||||
startInLoadingState={true}
|
||||
scalesPageToFit={this.state.scalesPageToFit}
|
||||
/>
|
||||
|
@ -118,6 +119,11 @@ var WebViewExample = React.createClass({
|
|||
this.refs[WEBVIEW_REF].reload();
|
||||
},
|
||||
|
||||
onShouldStartLoadWithRequest: function(event) {
|
||||
// Implement any custom loading logic here, don't forget to return!
|
||||
return true;
|
||||
},
|
||||
|
||||
onNavigationStateChange: function(navState) {
|
||||
this.setState({
|
||||
backButtonEnabled: navState.canGoBack,
|
||||
|
|
|
@ -25,6 +25,8 @@ var {
|
|||
View,
|
||||
} = React;
|
||||
|
||||
var XHRExampleHeaders = require('./XHRExampleHeaders');
|
||||
|
||||
// TODO t7093728 This is a simlified XHRExample.ios.js.
|
||||
// Once we have Camera roll, Toast, Intent (for opening URLs)
|
||||
// we should make this consistent with iOS.
|
||||
|
@ -259,10 +261,9 @@ class FormUploader extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
exports.framework = 'React';
|
||||
exports.title = 'XMLHttpRequest';
|
||||
exports.description = 'Example that demostrates upload and download requests ' +
|
||||
exports.description = 'Example that demonstrates upload and download requests ' +
|
||||
'using XMLHttpRequest.';
|
||||
exports.examples = [{
|
||||
title: 'File Download',
|
||||
|
@ -274,6 +275,11 @@ exports.examples = [{
|
|||
render() {
|
||||
return <FormUploader/>;
|
||||
}
|
||||
}, {
|
||||
title: 'Headers',
|
||||
render() {
|
||||
return <XHRExampleHeaders/>;
|
||||
}
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
|
|
|
@ -30,6 +30,8 @@ var {
|
|||
View,
|
||||
} = React;
|
||||
|
||||
var XHRExampleHeaders = require('./XHRExampleHeaders');
|
||||
|
||||
class Downloader extends React.Component {
|
||||
|
||||
xhr: XMLHttpRequest;
|
||||
|
@ -368,6 +370,11 @@ exports.examples = [{
|
|||
render() {
|
||||
return <FetchTest/>;
|
||||
}
|
||||
}, {
|
||||
title: 'Headers',
|
||||
render() {
|
||||
return <XHRExampleHeaders/>;
|
||||
}
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
class XHRExampleHeaders extends React.Component {
|
||||
|
||||
xhr: XMLHttpRequest;
|
||||
cancelled: boolean;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cancelled = false;
|
||||
this.state = {
|
||||
status: '',
|
||||
headers: '',
|
||||
contentSize: 1,
|
||||
downloaded: 0,
|
||||
};
|
||||
}
|
||||
|
||||
download() {
|
||||
this.xhr && this.xhr.abort();
|
||||
|
||||
var xhr = this.xhr || new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === xhr.DONE) {
|
||||
if (this.cancelled) {
|
||||
this.cancelled = false;
|
||||
return;
|
||||
}
|
||||
if (xhr.status === 200) {
|
||||
this.setState({
|
||||
status: 'Download complete!',
|
||||
headers: xhr.getAllResponseHeaders()
|
||||
});
|
||||
} else if (xhr.status !== 0) {
|
||||
this.setState({
|
||||
status: 'Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
status: 'Error: ' + xhr.responseText,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open('GET', 'https://httpbin.org/response-headers?header1=value&header2=value1&header2=value2');
|
||||
xhr.send();
|
||||
this.xhr = xhr;
|
||||
|
||||
this.setState({status: 'Downloading...'});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.cancelled = true;
|
||||
this.xhr && this.xhr.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
var button = this.state.status === 'Downloading...' ? (
|
||||
<View style={styles.wrapper}>
|
||||
<View style={styles.button}>
|
||||
<Text>...</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<TouchableHighlight
|
||||
style={styles.wrapper}
|
||||
onPress={this.download.bind(this)}>
|
||||
<View style={styles.button}>
|
||||
<Text>Get headers</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
{button}
|
||||
<Text>{this.state.headers}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
borderRadius: 5,
|
||||
marginBottom: 5,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#eeeeee',
|
||||
padding: 8,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = XHRExampleHeaders;
|
|
@ -69,7 +69,7 @@ public class UIExplorerActivity extends Activity implements DefaultHardwareBackB
|
|||
super.onResume();
|
||||
|
||||
if (mReactInstanceManager != null) {
|
||||
mReactInstanceManager.onResume(this);
|
||||
mReactInstanceManager.onResume(this, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
CGPoint _endPoint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 5) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
CGRect _rect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array
|
||||
- (instancetype)initWithArray:(NSArray<id /* imagesource + numbers */> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 6) {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
CGFloat _radiusRatio;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 7) {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
CGColorRef _color;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)array
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
_color = CGColorRetain([RCTConvert CGColor:array offset:1]);
|
||||
|
|
|
@ -566,8 +566,14 @@ function Pattern(url, width, height, left, top) {
|
|||
this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height];
|
||||
}
|
||||
|
||||
var ReactART = {
|
||||
// This doesn't work on iOS and is just a placeholder to get Spectrum running.
|
||||
// I will try to eliminate this dependency in Spectrum and remove it from
|
||||
// ReactART proper.
|
||||
function CSSBackgroundPattern() {
|
||||
return new Color('rgba(0,0,0,0)');
|
||||
}
|
||||
|
||||
var ReactART = {
|
||||
LinearGradient: LinearGradient,
|
||||
RadialGradient: RadialGradient,
|
||||
Pattern: Pattern,
|
||||
|
@ -578,7 +584,7 @@ var ReactART = {
|
|||
ClippingRectangle: ClippingRectangle,
|
||||
Shape: Shape,
|
||||
Text: Text,
|
||||
|
||||
CSSBackgroundPattern: CSSBackgroundPattern
|
||||
};
|
||||
|
||||
module.exports = ReactART;
|
||||
|
|
|
@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [[ARTGroup alloc] init];
|
||||
return [ARTGroup new];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [[ARTNode alloc] init];
|
||||
return [ARTNode new];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
|
|
|
@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [[ARTRenderable alloc] init];
|
||||
return [ARTRenderable new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
|
||||
|
|
|
@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [[ARTShape alloc] init];
|
||||
return [ARTShape new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
|
||||
|
|
|
@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[ARTSurfaceView alloc] init];
|
||||
return [ARTSurfaceView new];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [[ARTText alloc] init];
|
||||
return [ARTText new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
|
||||
|
|
|
@ -27,7 +27,6 @@ var ActionSheetIOS = {
|
|||
);
|
||||
RCTActionSheetManager.showActionSheetWithOptions(
|
||||
options,
|
||||
() => {}, // RKActionSheet compatibility hack
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
|
|
@ -21,65 +21,137 @@
|
|||
|
||||
@implementation RCTActionSheetManager
|
||||
{
|
||||
NSMutableDictionary *_callbacks;
|
||||
// Use NSMapTable, as UIAlertViews do not implement <NSCopying>
|
||||
// which is required for NSDictionary keys
|
||||
NSMapTable *_callbacks;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_callbacks = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
- (CGRect)sourceRectInView:(UIView *)sourceView
|
||||
anchorViewTag:(NSNumber *)anchorViewTag
|
||||
{
|
||||
if (anchorViewTag) {
|
||||
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
|
||||
return [anchorView convertRect:anchorView.bounds toView:sourceView];
|
||||
} else {
|
||||
return (CGRect){sourceView.center, {1, 1}};
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(__unused RCTResponseSenderBlock)failureCallback
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_callbacks) {
|
||||
_callbacks = [NSMapTable strongToStrongObjectsMapTable];
|
||||
}
|
||||
|
||||
NSString *title = [RCTConvert NSString:options[@"title"]];
|
||||
NSArray<NSString *> *buttons = [RCTConvert NSStringArray:options[@"options"]];
|
||||
NSInteger destructiveButtonIndex = options[@"destructiveButtonIndex"] ? [RCTConvert NSInteger:options[@"destructiveButtonIndex"]] : -1;
|
||||
NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1;
|
||||
|
||||
UIViewController *controller = RCTKeyWindow().rootViewController;
|
||||
if (controller == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
UIView *sourceView = controller.view;
|
||||
CGRect sourceRect = [self sourceRectInView:sourceView anchorViewTag:anchorViewTag];
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||
|
||||
if ([UIAlertController class] == nil) {
|
||||
|
||||
UIActionSheet *actionSheet = [UIActionSheet new];
|
||||
|
||||
actionSheet.title = title;
|
||||
for (NSString *option in buttons) {
|
||||
[actionSheet addButtonWithTitle:option];
|
||||
}
|
||||
actionSheet.destructiveButtonIndex = destructiveButtonIndex;
|
||||
actionSheet.cancelButtonIndex = cancelButtonIndex;
|
||||
actionSheet.delegate = self;
|
||||
|
||||
[_callbacks setObject:callback forKey:actionSheet];
|
||||
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||
[actionSheet showFromRect:sourceRect inView:sourceView animated:YES];
|
||||
} else {
|
||||
[actionSheet showInView:sourceView];
|
||||
}
|
||||
|
||||
} else
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
UIAlertController *alertController =
|
||||
[UIAlertController alertControllerWithTitle:title
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
NSInteger index = 0;
|
||||
for (NSString *option in buttons) {
|
||||
UIAlertActionStyle style = UIAlertActionStyleDefault;
|
||||
if (index == destructiveButtonIndex) {
|
||||
style = UIAlertActionStyleDestructive;
|
||||
} else if (index == cancelButtonIndex) {
|
||||
style = UIAlertActionStyleCancel;
|
||||
}
|
||||
|
||||
NSInteger localIndex = index;
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:option
|
||||
style:style
|
||||
handler:^(__unused UIAlertAction *action){
|
||||
callback(@[@(localIndex)]);
|
||||
}]];
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
alertController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
alertController.popoverPresentationController.sourceView = sourceView;
|
||||
alertController.popoverPresentationController.sourceRect = sourceRect;
|
||||
if (!anchorViewTag) {
|
||||
alertController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseErrorBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
UIActionSheet *actionSheet = [UIActionSheet new];
|
||||
|
||||
actionSheet.title = options[@"title"];
|
||||
|
||||
for (NSString *option in options[@"options"]) {
|
||||
[actionSheet addButtonWithTitle:option];
|
||||
}
|
||||
|
||||
if (options[@"destructiveButtonIndex"]) {
|
||||
actionSheet.destructiveButtonIndex = [options[@"destructiveButtonIndex"] integerValue];
|
||||
}
|
||||
if (options[@"cancelButtonIndex"]) {
|
||||
actionSheet.cancelButtonIndex = [options[@"cancelButtonIndex"] integerValue];
|
||||
}
|
||||
|
||||
actionSheet.delegate = self;
|
||||
|
||||
_callbacks[RCTKeyForInstance(actionSheet)] = successCallback;
|
||||
|
||||
UIWindow *appWindow = RCTSharedApplication().delegate.window;
|
||||
if (appWindow == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
[actionSheet showInView:appWindow];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseSenderBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
NSMutableArray *items = [NSMutableArray array];
|
||||
NSMutableArray<id /* NSString or NSURL */> *items = [NSMutableArray array];
|
||||
NSString *message = [RCTConvert NSString:options[@"message"]];
|
||||
if (message) {
|
||||
[items addObject:message];
|
||||
|
@ -89,22 +161,18 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
|||
[items addObject:URL];
|
||||
}
|
||||
if (items.count == 0) {
|
||||
failureCallback(@[@"No `url` or `message` to share"]);
|
||||
return;
|
||||
}
|
||||
if (RCTRunningInAppExtension()) {
|
||||
failureCallback(@[@"Unable to show action sheet from app extension"]);
|
||||
RCTLogError(@"No `url` or `message` to share");
|
||||
return;
|
||||
}
|
||||
|
||||
UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
UIViewController *ctrl = RCTSharedApplication().delegate.window.rootViewController;
|
||||
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
UIViewController *controller = RCTKeyWindow().rootViewController;
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||
|
||||
if (![UIActivityViewController instancesRespondToSelector:@selector(setCompletionWithItemsHandler:)]) {
|
||||
// Legacy iOS 7 implementation
|
||||
share.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||
shareController.completionHandler = ^(NSString *activityType, BOOL completed) {
|
||||
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
|
||||
};
|
||||
} else
|
||||
|
@ -113,57 +181,37 @@ RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
|||
|
||||
{
|
||||
// iOS 8 version
|
||||
share.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
|
||||
shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
|
||||
if (activityError) {
|
||||
failureCallback(@[RCTNullIfNil(activityError.localizedDescription)]);
|
||||
failureCallback(activityError);
|
||||
} else {
|
||||
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
if ([share respondsToSelector:@selector(popoverPresentationController)]) {
|
||||
share.popoverPresentationController.sourceView = ctrl.view;
|
||||
shareController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
if (anchorViewTag) {
|
||||
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
|
||||
share.popoverPresentationController.sourceRect = [anchorView convertRect:anchorView.bounds toView:ctrl.view];
|
||||
} else {
|
||||
CGRect sourceRect = CGRectMake(ctrl.view.center.x, ctrl.view.center.y, 1, 1);
|
||||
share.popoverPresentationController.sourceRect = sourceRect;
|
||||
share.popoverPresentationController.permittedArrowDirections = 0;
|
||||
if (!anchorViewTag) {
|
||||
shareController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
shareController.popoverPresentationController.sourceView = controller.view;
|
||||
shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag];
|
||||
}
|
||||
|
||||
[ctrl presentViewController:share animated:YES completion:nil];
|
||||
[controller presentViewController:shareController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark UIActionSheetDelegate Methods
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
NSString *key = RCTKeyForInstance(actionSheet);
|
||||
RCTResponseSenderBlock callback = _callbacks[key];
|
||||
RCTResponseSenderBlock callback = [_callbacks objectForKey:actionSheet];
|
||||
if (callback) {
|
||||
callback(@[@(buttonIndex)]);
|
||||
[_callbacks removeObjectForKey:key];
|
||||
[_callbacks removeObjectForKey:actionSheet];
|
||||
} else {
|
||||
RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title);
|
||||
}
|
||||
|
||||
[RCTSharedApplication().delegate.window makeKeyWindow];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
static NSString *RCTKeyForInstance(id instance)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%p", instance];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -202,13 +202,22 @@ function createInterpolationFromStringOutputRange(
|
|||
// [200, 250],
|
||||
// [0, 0.5],
|
||||
// ]
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
var outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
|
||||
outputRange.forEach(value => {
|
||||
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
|
||||
* against this possibility.
|
||||
*/
|
||||
value.match(stringShapeRegex).forEach((number, i) => {
|
||||
outputRanges[i].push(+number);
|
||||
});
|
||||
});
|
||||
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
var interpolations = outputRange[0].match(stringShapeRegex).map((value, i) => {
|
||||
return Interpolation.create({
|
||||
...config,
|
||||
|
|
|
@ -119,6 +119,8 @@ class CameraRoll {
|
|||
/**
|
||||
* Saves the image to the camera roll / gallery.
|
||||
*
|
||||
* The CameraRoll API is not yet implemented for Android.
|
||||
*
|
||||
* @param {string} tag On Android, this is a local URI, such
|
||||
* as `"file:///sdcard/img.png"`.
|
||||
*
|
||||
|
|
|
@ -15,11 +15,6 @@
|
|||
|
||||
@interface RCTBridge (RCTAssetsLibraryImageLoader)
|
||||
|
||||
/**
|
||||
* The shared Assets Library image loader
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTAssetsLibraryImageLoader *assetsLibraryImageLoader;
|
||||
|
||||
/**
|
||||
* The shared asset library instance.
|
||||
*/
|
||||
|
|
|
@ -41,10 +41,15 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"assets-library"];
|
||||
return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
|
||||
|
@ -69,7 +74,8 @@ RCT_EXPORT_MODULE()
|
|||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
|
||||
#if RCT_DEV
|
||||
#if RCT_DEV
|
||||
|
||||
CGSize sizeBeingLoaded = size;
|
||||
if (useMaximumSize) {
|
||||
CGSize pointSize = representation.dimensions;
|
||||
|
@ -78,7 +84,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
CGSize screenSize;
|
||||
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) {
|
||||
screenSize = UIScreen.mainScreen.nativeBounds.size;
|
||||
screenSize = [UIScreen mainScreen].nativeBounds.size;
|
||||
} else {
|
||||
CGSize mainScreenSize = [UIScreen mainScreen].bounds.size;
|
||||
CGFloat mainScreenScale = [[UIScreen mainScreen] scale];
|
||||
|
@ -87,9 +93,11 @@ RCT_EXPORT_MODULE()
|
|||
CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height);
|
||||
|
||||
if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) {
|
||||
RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@", representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
|
||||
RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@",
|
||||
representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
UIImage *image;
|
||||
NSError *error = nil;
|
||||
|
@ -106,8 +114,7 @@ RCT_EXPORT_MODULE()
|
|||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
if (cancelled) {
|
||||
|
@ -115,8 +122,7 @@ RCT_EXPORT_MODULE()
|
|||
}
|
||||
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
}];
|
||||
|
||||
return ^{
|
||||
|
@ -128,14 +134,9 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
|
||||
|
||||
- (RCTAssetsLibraryImageLoader *)assetsLibraryImageLoader
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])];
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
return [self.assetsLibraryImageLoader assetsLibrary];
|
||||
return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -154,7 +155,11 @@ static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
|
|||
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
|
||||
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
|
||||
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error)
|
||||
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
|
||||
CGSize size,
|
||||
CGFloat scale,
|
||||
UIViewContentMode resizeMode,
|
||||
NSError **error)
|
||||
{
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
|
|
|
@ -7,7 +7,17 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@interface RCTConvert (ALAssetGroup)
|
||||
|
||||
+ (ALAssetsGroupType)ALAssetsGroupType:(id)json;
|
||||
+ (ALAssetsFilter *)ALAssetsFilter:(id)json;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTCameraRollManager : NSObject <RCTBridgeModule>
|
||||
|
||||
|
|
|
@ -9,17 +9,70 @@
|
|||
|
||||
#import "RCTCameraRollManager.h"
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTAssetsLibraryImageLoader.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTConvert (ALAssetGroup)
|
||||
|
||||
RCT_ENUM_CONVERTER(ALAssetsGroupType, (@{
|
||||
|
||||
// New values
|
||||
@"album": @(ALAssetsGroupAlbum),
|
||||
@"all": @(ALAssetsGroupAll),
|
||||
@"event": @(ALAssetsGroupEvent),
|
||||
@"faces": @(ALAssetsGroupFaces),
|
||||
@"library": @(ALAssetsGroupLibrary),
|
||||
@"photo-stream": @(ALAssetsGroupPhotoStream),
|
||||
@"saved-photos": @(ALAssetsGroupSavedPhotos),
|
||||
|
||||
// Legacy values
|
||||
@"Album": @(ALAssetsGroupAlbum),
|
||||
@"All": @(ALAssetsGroupAll),
|
||||
@"Event": @(ALAssetsGroupEvent),
|
||||
@"Faces": @(ALAssetsGroupFaces),
|
||||
@"Library": @(ALAssetsGroupLibrary),
|
||||
@"PhotoStream": @(ALAssetsGroupPhotoStream),
|
||||
@"SavedPhotos": @(ALAssetsGroupSavedPhotos),
|
||||
|
||||
}), ALAssetsGroupSavedPhotos, integerValue)
|
||||
|
||||
+ (ALAssetsFilter *)ALAssetsFilter:(id)json
|
||||
{
|
||||
static NSDictionary *options;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
options = @{
|
||||
|
||||
// New values
|
||||
@"photos": [ALAssetsFilter allPhotos],
|
||||
@"videos": [ALAssetsFilter allVideos],
|
||||
@"all": [ALAssetsFilter allAssets],
|
||||
|
||||
// Legacy values
|
||||
@"Photos": [ALAssetsFilter allPhotos],
|
||||
@"Videos": [ALAssetsFilter allVideos],
|
||||
@"All": [ALAssetsFilter allAssets],
|
||||
};
|
||||
});
|
||||
|
||||
ALAssetsFilter *filter = options[json ?: @"photos"];
|
||||
if (!filter) {
|
||||
RCTLogError(@"Invalid filter option: '%@'. Expected one of 'photos',"
|
||||
"'videos' or 'all'.", json);
|
||||
}
|
||||
return filter ?: [ALAssetsFilter allPhotos];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTCameraRollManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
@ -35,18 +88,23 @@ RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag
|
|||
errorCallback(loadError);
|
||||
return;
|
||||
}
|
||||
[_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
errorCallback(saveError);
|
||||
} else {
|
||||
successCallback(@[assetURL.absoluteString]);
|
||||
}
|
||||
}];
|
||||
// It's unclear if writeImageToSavedPhotosAlbum is thread-safe
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
errorCallback(saveError);
|
||||
} else {
|
||||
successCallback(@[assetURL.absoluteString]);
|
||||
}
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)callCallback:(RCTResponseSenderBlock)callback withAssets:(NSArray *)assets hasNextPage:(BOOL)hasNextPage
|
||||
- (void)callCallback:(RCTResponseSenderBlock)callback
|
||||
withAssets:(NSArray<NSDictionary *> *)assets
|
||||
hasNextPage:(BOOL)hasNextPage
|
||||
{
|
||||
if (!assets.count) {
|
||||
callback(@[@{
|
||||
|
@ -69,45 +127,21 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
|||
callback:(RCTResponseSenderBlock)callback
|
||||
errorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||
{
|
||||
NSUInteger first = [params[@"first"] integerValue];
|
||||
NSString *afterCursor = params[@"after"];
|
||||
NSString *groupTypesStr = params[@"groupTypes"];
|
||||
NSString *groupName = params[@"groupName"];
|
||||
NSString *assetType = params[@"assetType"];
|
||||
ALAssetsGroupType groupTypes;
|
||||
|
||||
if ([groupTypesStr isEqualToString:@"Album"]) {
|
||||
groupTypes = ALAssetsGroupAlbum;
|
||||
} else if ([groupTypesStr isEqualToString:@"All"]) {
|
||||
groupTypes = ALAssetsGroupAll;
|
||||
} else if ([groupTypesStr isEqualToString:@"Event"]) {
|
||||
groupTypes = ALAssetsGroupEvent;
|
||||
} else if ([groupTypesStr isEqualToString:@"Faces"]) {
|
||||
groupTypes = ALAssetsGroupFaces;
|
||||
} else if ([groupTypesStr isEqualToString:@"Library"]) {
|
||||
groupTypes = ALAssetsGroupLibrary;
|
||||
} else if ([groupTypesStr isEqualToString:@"PhotoStream"]) {
|
||||
groupTypes = ALAssetsGroupPhotoStream;
|
||||
} else {
|
||||
groupTypes = ALAssetsGroupSavedPhotos;
|
||||
}
|
||||
NSUInteger first = [RCTConvert NSInteger:params[@"first"]];
|
||||
NSString *afterCursor = [RCTConvert NSString:params[@"after"]];
|
||||
NSString *groupName = [RCTConvert NSString:params[@"groupName"]];
|
||||
ALAssetsFilter *assetType = [RCTConvert ALAssetsFilter:params[@"assetType"]];
|
||||
ALAssetsGroupType groupTypes = [RCTConvert ALAssetsGroupType:params[@"groupTypes"]];
|
||||
|
||||
BOOL __block foundAfter = NO;
|
||||
BOOL __block hasNextPage = NO;
|
||||
BOOL __block calledCallback = NO;
|
||||
NSMutableArray *assets = [NSMutableArray new];
|
||||
NSMutableArray<NSDictionary *> *assets = [NSMutableArray new];
|
||||
|
||||
[_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
|
||||
if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) {
|
||||
|
||||
if (assetType == nil || [assetType isEqualToString:@"Photos"]) {
|
||||
[group setAssetsFilter:ALAssetsFilter.allPhotos];
|
||||
} else if ([assetType isEqualToString:@"Videos"]) {
|
||||
[group setAssetsFilter:ALAssetsFilter.allVideos];
|
||||
} else if ([assetType isEqualToString:@"All"]) {
|
||||
[group setAssetsFilter:ALAssetsFilter.allAssets];
|
||||
}
|
||||
|
||||
[group setAssetsFilter:assetType];
|
||||
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) {
|
||||
if (result) {
|
||||
NSString *uri = ((NSURL *)[result valueForProperty:ALAssetPropertyAssetURL]).absoluteString;
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
|
||||
@implementation RCTImagePickerManager
|
||||
{
|
||||
NSMutableArray *_pickers;
|
||||
NSMutableArray *_pickerCallbacks;
|
||||
NSMutableArray *_pickerCancelCallbacks;
|
||||
NSMutableArray<UIImagePickerController *> *_pickers;
|
||||
NSMutableArray<RCTResponseSenderBlock> *_pickerCallbacks;
|
||||
NSMutableArray<RCTResponseSenderBlock> *_pickerCancelCallbacks;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE(ImagePickerIOS);
|
||||
|
@ -42,7 +42,7 @@ RCT_EXPORT_MODULE(ImagePickerIOS);
|
|||
|
||||
RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
|
||||
NSArray<NSString *> *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
|
||||
callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]);
|
||||
}
|
||||
|
||||
|
@ -59,9 +59,8 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config
|
|||
cancelCallback(@[@"Camera access is unavailable in an app extension"]);
|
||||
return;
|
||||
}
|
||||
|
||||
UIWindow *keyWindow = RCTSharedApplication().keyWindow;
|
||||
UIViewController *rootViewController = keyWindow.rootViewController;
|
||||
|
||||
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
|
||||
|
||||
UIImagePickerController *imagePicker = [UIImagePickerController new];
|
||||
imagePicker.delegate = self;
|
||||
|
@ -86,15 +85,14 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
|
|||
cancelCallback(@[@"Image picker is currently unavailable in an app extension"]);
|
||||
return;
|
||||
}
|
||||
|
||||
UIWindow *keyWindow = RCTSharedApplication().keyWindow;
|
||||
UIViewController *rootViewController = keyWindow.rootViewController;
|
||||
|
||||
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
|
||||
|
||||
UIImagePickerController *imagePicker = [UIImagePickerController new];
|
||||
imagePicker.delegate = self;
|
||||
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
||||
|
||||
NSMutableArray *allowedTypes = [NSMutableArray new];
|
||||
NSMutableArray<NSString *> *allowedTypes = [NSMutableArray new];
|
||||
if ([config[@"showImages"] boolValue]) {
|
||||
[allowedTypes addObject:(NSString *)kUTTypeImage];
|
||||
}
|
||||
|
@ -121,8 +119,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info
|
|||
[_pickerCallbacks removeObjectAtIndex:index];
|
||||
[_pickerCancelCallbacks removeObjectAtIndex:index];
|
||||
|
||||
UIWindow *keyWindow = RCTSharedApplication().keyWindow;
|
||||
UIViewController *rootViewController = keyWindow.rootViewController;
|
||||
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
|
||||
[rootViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]);
|
||||
|
@ -137,8 +134,7 @@ didFinishPickingMediaWithInfo:(NSDictionary *)info
|
|||
[_pickerCallbacks removeObjectAtIndex:index];
|
||||
[_pickerCancelCallbacks removeObjectAtIndex:index];
|
||||
|
||||
UIWindow *keyWindow = RCTSharedApplication().keyWindow;
|
||||
UIViewController *rootViewController = keyWindow.rootViewController;
|
||||
UIViewController *rootViewController = RCTKeyWindow().rootViewController;
|
||||
[rootViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
|
||||
callback(@[]);
|
||||
|
|
|
@ -24,35 +24,44 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
||||
{
|
||||
return [requestURL.scheme.lowercaseString isEqualToString:@"ph"];
|
||||
return [requestURL.scheme caseInsensitiveCompare:@"ph"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
// Using PhotoKit for iOS 8+
|
||||
// The 'ph://' prefix is used by FBMediaKit to differentiate between
|
||||
// assets-library. It is prepended to the local ID so that it is in the
|
||||
// form of an, NSURL which is what assets-library uses.
|
||||
NSString *phAssetID = [imageURL.absoluteString substringFromIndex:[@"ph://" length]];
|
||||
NSString *phAssetID = [imageURL.absoluteString substringFromIndex:@"ph://".length];
|
||||
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
|
||||
if (results.count == 0) {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
completionHandler(error, nil);
|
||||
completionHandler(RCTErrorWithMessage(errorText), nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = [results firstObject];
|
||||
|
||||
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
|
||||
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
static const double multiplier = 1e6;
|
||||
progressHandler(progress * multiplier, multiplier);
|
||||
};
|
||||
|
||||
if (progressHandler) {
|
||||
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
static const double multiplier = 1e6;
|
||||
progressHandler(progress * multiplier, multiplier);
|
||||
};
|
||||
}
|
||||
|
||||
// Note: PhotoKit defaults to a deliveryMode of PHImageRequestOptionsDeliveryModeOpportunistic
|
||||
// which means it may call back multiple times - we probably don't want that
|
||||
imageOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
|
||||
if (useMaximumSize) {
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
|
@ -66,7 +75,12 @@ RCT_EXPORT_MODULE()
|
|||
contentMode = PHImageContentModeAspectFit;
|
||||
}
|
||||
|
||||
PHImageRequestID requestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
PHImageRequestID requestID =
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset
|
||||
targetSize:targetSize
|
||||
contentMode:contentMode
|
||||
options:imageOptions
|
||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (result) {
|
||||
completionHandler(nil, result);
|
||||
} else {
|
||||
|
|
|
@ -74,8 +74,8 @@ var ActivityIndicatorIOS = React.createClass({
|
|||
return (
|
||||
<View
|
||||
onLayout={onLayout}
|
||||
style={[styles.container, sizeStyle, style]}>
|
||||
<RCTActivityIndicatorView {...props} />
|
||||
style={[styles.container, style]}>
|
||||
<RCTActivityIndicatorView {...props} style={sizeStyle} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -80,6 +80,13 @@ var MapView = React.createClass({
|
|||
*/
|
||||
showsUserLocation: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* If `false` points of interest won't be displayed on the map.
|
||||
* Default value is `true`.
|
||||
* @platform ios
|
||||
*/
|
||||
showsPointsOfInterest: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* If `false` the user won't be able to pinch/zoom the map.
|
||||
* Default value is `true`.
|
||||
|
|
|
@ -15,7 +15,7 @@ var React = require('React');
|
|||
var ReactPropTypes = require('ReactPropTypes');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
|
||||
var createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var STYLE_ATTRIBUTES = [
|
||||
'Horizontal',
|
||||
|
@ -26,6 +26,18 @@ var STYLE_ATTRIBUTES = [
|
|||
'LargeInverse'
|
||||
];
|
||||
|
||||
var indeterminateType = function(props, propName, componentName) {
|
||||
var checker = function() {
|
||||
var indeterminate = props[propName];
|
||||
var styleAttr = props.styleAttr;
|
||||
if (!indeterminate && styleAttr !== 'Horizontal') {
|
||||
return new Error('indeterminate=false is only valid for styleAttr=Horizontal');
|
||||
}
|
||||
};
|
||||
|
||||
return ReactPropTypes.bool(props, propName, componentName) || checker();
|
||||
};
|
||||
|
||||
/**
|
||||
* React component that wraps the Android-only `ProgressBar`. This component is used to indicate
|
||||
* that the app is loading or there is some activity in the app.
|
||||
|
@ -62,6 +74,19 @@ var ProgressBarAndroid = React.createClass({
|
|||
* - LargeInverse
|
||||
*/
|
||||
styleAttr: ReactPropTypes.oneOf(STYLE_ATTRIBUTES),
|
||||
/**
|
||||
* If the progress bar will show indeterminate progress. Note that this
|
||||
* can only be false if styleAttr is Horizontal.
|
||||
*/
|
||||
indeterminate: indeterminateType,
|
||||
/**
|
||||
* The progress value (between 0 and 1).
|
||||
*/
|
||||
progress: ReactPropTypes.number,
|
||||
/**
|
||||
* Color of the progress bar.
|
||||
*/
|
||||
color: ReactPropTypes.string,
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
*/
|
||||
|
@ -71,6 +96,7 @@ var ProgressBarAndroid = React.createClass({
|
|||
getDefaultProps: function() {
|
||||
return {
|
||||
styleAttr: 'Large',
|
||||
indeterminate: true
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -81,12 +107,6 @@ var ProgressBarAndroid = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
var AndroidProgressBar = createReactNativeComponentClass({
|
||||
validAttributes: {
|
||||
...ReactNativeViewAttributes.UIView,
|
||||
styleAttr: true,
|
||||
},
|
||||
uiViewClassName: 'AndroidProgressBar',
|
||||
});
|
||||
var AndroidProgressBar = requireNativeComponent('AndroidProgressBar', ProgressBarAndroid);
|
||||
|
||||
module.exports = ProgressBarAndroid;
|
||||
|
|
|
@ -41,6 +41,13 @@ var SliderIOS = React.createClass({
|
|||
*/
|
||||
value: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Step value of the slider. The value should be
|
||||
* between 0 and (maximumValue - minimumValue).
|
||||
* Default value is 0.
|
||||
*/
|
||||
step: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Initial minimum value of the slider. Default value is 0.
|
||||
*/
|
||||
|
@ -63,6 +70,12 @@ var SliderIOS = React.createClass({
|
|||
*/
|
||||
maximumTrackTintColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* If true the user won't be able to move the slider.
|
||||
* Default value is false.
|
||||
*/
|
||||
disabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Callback continuously called while the user is dragging the slider.
|
||||
*/
|
||||
|
@ -75,27 +88,33 @@ var SliderIOS = React.createClass({
|
|||
onSlidingComplete: PropTypes.func,
|
||||
},
|
||||
|
||||
_onValueChange: function(event: 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);
|
||||
}
|
||||
getDefaultProps: function() : any {
|
||||
return {
|
||||
disabled: false,
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
let onValueChange = this.props.onValueChange && ((event: Event) => {
|
||||
this.props.onValueChange &&
|
||||
this.props.onValueChange(event.nativeEvent.value);
|
||||
});
|
||||
|
||||
let onSlidingComplete = this.props.onSlidingComplete && ((event: Event) => {
|
||||
this.props.onSlidingComplete &&
|
||||
this.props.onSlidingComplete(event.nativeEvent.value);
|
||||
});
|
||||
|
||||
let {style, ...props} = this.props;
|
||||
style = [styles.slider, this.props.style];
|
||||
|
||||
return (
|
||||
<RCTSlider
|
||||
style={[styles.slider, this.props.style]}
|
||||
value={this.props.value}
|
||||
maximumValue={this.props.maximumValue}
|
||||
minimumValue={this.props.minimumValue}
|
||||
minimumTrackTintColor={this.props.minimumTrackTintColor}
|
||||
maximumTrackTintColor={this.props.maximumTrackTintColor}
|
||||
onChange={this._onValueChange}
|
||||
{...props}
|
||||
style={style}
|
||||
onValueChange={onValueChange}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -107,8 +126,6 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
|
||||
nativeOnly: { onChange: true },
|
||||
});
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
|
||||
|
||||
module.exports = SliderIOS;
|
||||
|
|
|
@ -47,6 +47,10 @@ if (Platform.OS === 'android') {
|
|||
var RCTTextField = requireNativeComponent('RCTTextField', null);
|
||||
}
|
||||
|
||||
type DefaultProps = {
|
||||
blurOnSubmit: boolean;
|
||||
};
|
||||
|
||||
type Event = Object;
|
||||
|
||||
/**
|
||||
|
@ -82,6 +86,11 @@ type Event = Object;
|
|||
* ```
|
||||
*/
|
||||
var TextInput = React.createClass({
|
||||
statics: {
|
||||
/* TODO(brentvatne) docs are needed for this */
|
||||
State: TextInputState,
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* Can tell TextInput to automatically capitalize certain characters.
|
||||
|
@ -172,7 +181,6 @@ var TextInput = React.createClass({
|
|||
/**
|
||||
* Limits the maximum number of characters that can be entered. Use this
|
||||
* instead of implementing the logic in JS to avoid flicker.
|
||||
* @platform ios
|
||||
*/
|
||||
maxLength: PropTypes.number,
|
||||
/**
|
||||
|
@ -217,6 +225,13 @@ var TextInput = React.createClass({
|
|||
* Callback that is called when the text input's submit button is pressed.
|
||||
*/
|
||||
onSubmitEditing: PropTypes.func,
|
||||
/**
|
||||
* Callback that is called when a key is pressed.
|
||||
* Pressed key value is passed as an argument to the callback handler.
|
||||
* Fires before onChange callbacks.
|
||||
* @platform ios
|
||||
*/
|
||||
onKeyPress: PropTypes.func,
|
||||
/**
|
||||
* Invoked on mount and layout changes with `{x, y, width, height}`.
|
||||
*/
|
||||
|
@ -276,6 +291,12 @@ var TextInput = React.createClass({
|
|||
* @platform ios
|
||||
*/
|
||||
selectTextOnFocus: PropTypes.bool,
|
||||
/**
|
||||
* If true, the text field will blur when submitted.
|
||||
* The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
blurOnSubmit: PropTypes.bool,
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
|
@ -291,6 +312,12 @@ var TextInput = React.createClass({
|
|||
underlineColorAndroid: PropTypes.string,
|
||||
},
|
||||
|
||||
getDefaultProps: function(): DefaultProps {
|
||||
return {
|
||||
blurOnSubmit: true,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We
|
||||
* make `this` look like an actual native component class.
|
||||
|
@ -473,6 +500,7 @@ var TextInput = React.createClass({
|
|||
mostRecentEventCount={this.state.mostRecentEventCount}
|
||||
multiline={this.props.multiline}
|
||||
numberOfLines={this.props.numberOfLines}
|
||||
maxLength={this.props.maxLength}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
onChange={this._onChange}
|
||||
|
@ -522,13 +550,16 @@ var TextInput = React.createClass({
|
|||
this.props.onChange && this.props.onChange(event);
|
||||
this.props.onChangeText && this.props.onChangeText(text);
|
||||
this.setState({mostRecentEventCount: eventCount}, () => {
|
||||
// This is a controlled component, so make sure to force the native value
|
||||
// to match. Most usage shouldn't need this, but if it does this will be
|
||||
// more correct but might flicker a bit and/or cause the cursor to jump.
|
||||
if (text !== this.props.value && typeof this.props.value === 'string') {
|
||||
this.refs.input.setNativeProps({
|
||||
text: this.props.value,
|
||||
});
|
||||
// NOTE: this doesn't seem to be needed on iOS - keeping for now in case it's required on Android
|
||||
if (Platform.OS === 'android') {
|
||||
// This is a controlled component, so make sure to force the native value
|
||||
// to match. Most usage shouldn't need this, but if it does this will be
|
||||
// more correct but might flicker a bit and/or cause the cursor to jump.
|
||||
if (text !== this.props.value && typeof this.props.value === 'string') {
|
||||
this.refs.input.setNativeProps({
|
||||
text: this.props.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -103,6 +103,10 @@ var ToolbarAndroid = React.createClass({
|
|||
* Callback called when the icon is selected.
|
||||
*/
|
||||
onIconClicked: ReactPropTypes.func,
|
||||
/**
|
||||
* Sets the overflow icon.
|
||||
*/
|
||||
overflowIcon: optionalImageSource,
|
||||
/**
|
||||
* Sets the toolbar subtitle.
|
||||
*/
|
||||
|
@ -135,6 +139,9 @@ var ToolbarAndroid = React.createClass({
|
|||
if (this.props.navIcon) {
|
||||
nativeProps.navIcon = resolveAssetSource(this.props.navIcon);
|
||||
}
|
||||
if (this.props.overflowIcon) {
|
||||
nativeProps.overflowIcon = resolveAssetSource(this.props.overflowIcon);
|
||||
}
|
||||
if (this.props.actions) {
|
||||
nativeProps.actions = [];
|
||||
for (var i = 0; i < this.props.actions.length; i++) {
|
||||
|
@ -169,6 +176,7 @@ var toolbarAttributes = {
|
|||
actions: true,
|
||||
logo: true,
|
||||
navIcon: true,
|
||||
overflowIcon: true,
|
||||
subtitle: true,
|
||||
subtitleColor: true,
|
||||
title: true,
|
||||
|
|
|
@ -20,6 +20,11 @@ function BoundingDimensions(width, height) {
|
|||
this.height = height;
|
||||
}
|
||||
|
||||
BoundingDimensions.prototype.destructor = function() {
|
||||
this.width = null;
|
||||
this.height = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element Element to return `BoundingDimensions` for.
|
||||
* @return {BoundingDimensions} Bounding dimensions of `element`.
|
||||
|
|
|
@ -21,6 +21,11 @@ function Position(left, top) {
|
|||
this.top = top;
|
||||
}
|
||||
|
||||
Position.prototype.destructor = function() {
|
||||
this.left = null;
|
||||
this.top = null;
|
||||
};
|
||||
|
||||
PooledClass.addPoolingTo(Position, twoArgumentPooler);
|
||||
|
||||
module.exports = Position;
|
||||
|
|
|
@ -178,7 +178,6 @@ var View = React.createClass({
|
|||
* `TouchableHighlight` or `TouchableOpacity`. Check out `Touchable.js`,
|
||||
* `ScrollResponder.js` and `ResponderEventPlugin.js` for more discussion.
|
||||
*/
|
||||
onMoveShouldSetResponder: PropTypes.func,
|
||||
onResponderGrant: PropTypes.func,
|
||||
onResponderMove: PropTypes.func,
|
||||
onResponderReject: PropTypes.func,
|
||||
|
@ -187,6 +186,8 @@ var View = React.createClass({
|
|||
onResponderTerminationRequest: PropTypes.func,
|
||||
onStartShouldSetResponder: PropTypes.func,
|
||||
onStartShouldSetResponderCapture: PropTypes.func,
|
||||
onMoveShouldSetResponder: PropTypes.func,
|
||||
onMoveShouldSetResponderCapture: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
|
|
|
@ -125,7 +125,10 @@ var ViewPagerAndroid = React.createClass({
|
|||
}],
|
||||
collapsable: false,
|
||||
};
|
||||
if (child.type && child.type.displayName && (child.type.displayName !== 'View')) {
|
||||
if (child.type &&
|
||||
child.type.displayName &&
|
||||
(child.type.displayName !== 'RCTView') &&
|
||||
(child.type.displayName !== 'View')) {
|
||||
console.warn('Each ViewPager child must be a <View>. Was ' + child.type.displayName);
|
||||
}
|
||||
return ReactElement.createElement(child.type, newProps);
|
||||
|
|
|
@ -31,6 +31,10 @@ var WebViewState = keyMirror({
|
|||
ERROR: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Note that WebView is only supported on iOS for now,
|
||||
* see https://facebook.github.io/react-native/docs/known-issues.html
|
||||
*/
|
||||
var WebView = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -75,6 +75,12 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
|
|||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* Renders a native WebView.
|
||||
*
|
||||
* Note that WebView is only supported on iOS for now,
|
||||
* see https://facebook.github.io/react-native/docs/known-issues.html
|
||||
*/
|
||||
var WebView = React.createClass({
|
||||
statics: {
|
||||
JSNavigationScheme: JSNavigationScheme,
|
||||
|
@ -107,6 +113,12 @@ var WebView = React.createClass({
|
|||
* user can change the scale
|
||||
*/
|
||||
scalesPageToFit: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Allows custom handling of any webview requests by a JS handler. Return true
|
||||
* or false from this method to continue loading the request.
|
||||
*/
|
||||
onShouldStartLoadWithRequest: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -152,6 +164,12 @@ var WebView = React.createClass({
|
|||
webViewStyles.push(styles.hidden);
|
||||
}
|
||||
|
||||
var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
|
||||
var shouldStart = this.props.onShouldStartLoadWithRequest &&
|
||||
this.props.onShouldStartLoadWithRequest(event.nativeEvent);
|
||||
RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
|
||||
});
|
||||
|
||||
var webView =
|
||||
<RCTWebView
|
||||
ref={RCT_WEBVIEW_REF}
|
||||
|
@ -167,6 +185,7 @@ var WebView = React.createClass({
|
|||
onLoadingStart={this.onLoadingStart}
|
||||
onLoadingFinish={this.onLoadingFinish}
|
||||
onLoadingError={this.onLoadingError}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
scalesPageToFit={this.props.scalesPageToFit}
|
||||
/>;
|
||||
|
||||
|
|
|
@ -277,6 +277,7 @@ var ListView = React.createClass({
|
|||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (this.props.dataSource !== nextProps.dataSource) {
|
||||
this._sentEndForContentLength = null;
|
||||
this.setState((state, props) => {
|
||||
var rowsToRender = Math.min(
|
||||
state.curRenderedRowsCount + props.pageSize,
|
||||
|
@ -500,9 +501,11 @@ var ListView = React.createClass({
|
|||
},
|
||||
|
||||
_getDistanceFromEnd: function(scrollProperties) {
|
||||
return scrollProperties.contentLength -
|
||||
scrollProperties.visibleLength -
|
||||
scrollProperties.offset;
|
||||
var maxLength = Math.max(
|
||||
scrollProperties.contentLength,
|
||||
scrollProperties.visibleLength
|
||||
);
|
||||
return maxLength - scrollProperties.visibleLength - scrollProperties.offset;
|
||||
},
|
||||
|
||||
_updateVisibleRows: function(updatedFrames) {
|
||||
|
@ -590,6 +593,12 @@ var ListView = React.createClass({
|
|||
this._renderMoreRowsIfNeeded();
|
||||
}
|
||||
|
||||
if (this.props.onEndReached &&
|
||||
this._getDistanceFromEnd(this.scrollProperties) > this.props.onEndReachedThreshold) {
|
||||
// Scrolled out of the end zone, so it should be able to trigger again.
|
||||
this._sentEndForContentLength = null;
|
||||
}
|
||||
|
||||
this.props.onScroll && this.props.onScroll(e);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -30,6 +30,8 @@ var NavigationEvent = require('NavigationEvent');
|
|||
var NavigationEventEmitter = require('NavigationEventEmitter');
|
||||
var NavigationTreeNode = require('NavigationTreeNode');
|
||||
|
||||
var Set = require('Set');
|
||||
|
||||
var emptyFunction = require('emptyFunction');
|
||||
var invariant = require('invariant');
|
||||
|
||||
|
@ -41,6 +43,13 @@ var {
|
|||
CAPTURING_PHASE,
|
||||
} = NavigationEvent;
|
||||
|
||||
// Event types that do not support event bubbling, capturing and
|
||||
// reconciliation API (e.g event.preventDefault(), event.stopPropagation()).
|
||||
var LegacyEventTypes = new Set([
|
||||
'willfocus',
|
||||
'didfocus',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Class that contains the info and methods for app navigation.
|
||||
*/
|
||||
|
@ -63,8 +72,8 @@ class NavigationContext {
|
|||
this._emitCounter = 0;
|
||||
this._emitQueue = [];
|
||||
|
||||
this.addListener('willfocus', this._onFocus, this);
|
||||
this.addListener('didfocus', this._onFocus, this);
|
||||
this.addListener('willfocus', this._onFocus);
|
||||
this.addListener('didfocus', this._onFocus);
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
|
@ -73,6 +82,17 @@ class NavigationContext {
|
|||
return parent ? parent.getValue() : null;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get top(): ?NavigationContext {
|
||||
var result = null;
|
||||
var parentNode = this.__node.getParent();
|
||||
while (parentNode) {
|
||||
result = parentNode.getValue();
|
||||
parentNode = parentNode.getParent();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get currentRoute(): any {
|
||||
return this._currentRoute;
|
||||
|
@ -85,14 +105,18 @@ class NavigationContext {
|
|||
addListener(
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
context: ?Object,
|
||||
useCapture: ?boolean
|
||||
): EventSubscription {
|
||||
if (LegacyEventTypes.has(eventType)) {
|
||||
useCapture = false;
|
||||
}
|
||||
|
||||
var emitter = useCapture ?
|
||||
this._captureEventEmitter :
|
||||
this._bubbleEventEmitter;
|
||||
|
||||
if (emitter) {
|
||||
return emitter.addListener(eventType, listener, context);
|
||||
return emitter.addListener(eventType, listener, this);
|
||||
} else {
|
||||
return {remove: emptyFunction};
|
||||
}
|
||||
|
@ -109,50 +133,65 @@ class NavigationContext {
|
|||
|
||||
this._emitCounter++;
|
||||
|
||||
var targets = [this];
|
||||
var parentTarget = this.parent;
|
||||
while (parentTarget) {
|
||||
targets.unshift(parentTarget);
|
||||
parentTarget = parentTarget.parent;
|
||||
if (LegacyEventTypes.has(eventType)) {
|
||||
// Legacy events does not support event bubbling and reconciliation.
|
||||
this.__emit(
|
||||
eventType,
|
||||
data,
|
||||
null,
|
||||
{
|
||||
defaultPrevented: false,
|
||||
eventPhase: AT_TARGET,
|
||||
propagationStopped: true,
|
||||
target: this,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
var targets = [this];
|
||||
var parentTarget = this.parent;
|
||||
while (parentTarget) {
|
||||
targets.unshift(parentTarget);
|
||||
parentTarget = parentTarget.parent;
|
||||
}
|
||||
|
||||
var propagationStopped = false;
|
||||
var defaultPrevented = false;
|
||||
var callback = (event) => {
|
||||
propagationStopped = propagationStopped || event.isPropagationStopped();
|
||||
defaultPrevented = defaultPrevented || event.defaultPrevented;
|
||||
};
|
||||
|
||||
// Capture phase
|
||||
targets.some((currentTarget) => {
|
||||
if (propagationStopped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var extraInfo = {
|
||||
defaultPrevented,
|
||||
eventPhase: CAPTURING_PHASE,
|
||||
propagationStopped,
|
||||
target: this,
|
||||
};
|
||||
|
||||
currentTarget.__emit(eventType, data, callback, extraInfo);
|
||||
}, this);
|
||||
|
||||
// bubble phase
|
||||
targets.reverse().some((currentTarget) => {
|
||||
if (propagationStopped) {
|
||||
return true;
|
||||
}
|
||||
var extraInfo = {
|
||||
defaultPrevented,
|
||||
eventPhase: BUBBLING_PHASE,
|
||||
propagationStopped,
|
||||
target: this,
|
||||
};
|
||||
currentTarget.__emit(eventType, data, callback, extraInfo);
|
||||
}, this);
|
||||
}
|
||||
|
||||
var propagationStopped = false;
|
||||
var defaultPrevented = false;
|
||||
var callback = (event) => {
|
||||
propagationStopped = propagationStopped || event.isPropagationStopped();
|
||||
defaultPrevented = defaultPrevented || event.defaultPrevented;
|
||||
};
|
||||
|
||||
// capture phase
|
||||
targets.some((currentTarget) => {
|
||||
if (propagationStopped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var extraInfo = {
|
||||
defaultPrevented,
|
||||
eventPhase: CAPTURING_PHASE,
|
||||
propagationStopped,
|
||||
target: this,
|
||||
};
|
||||
|
||||
currentTarget.__emit(eventType, data, callback, extraInfo);
|
||||
}, this);
|
||||
|
||||
// bubble phase
|
||||
targets.reverse().some((currentTarget) => {
|
||||
if (propagationStopped) {
|
||||
return true;
|
||||
}
|
||||
var extraInfo = {
|
||||
defaultPrevented,
|
||||
eventPhase: BUBBLING_PHASE,
|
||||
propagationStopped,
|
||||
target: this,
|
||||
};
|
||||
currentTarget.__emit(eventType, data, callback, extraInfo);
|
||||
}, this);
|
||||
|
||||
if (didEmitCallback) {
|
||||
var event = NavigationEvent.pool(eventType, this, data);
|
||||
propagationStopped && event.stopPropagation();
|
||||
|
@ -189,9 +228,15 @@ class NavigationContext {
|
|||
case CAPTURING_PHASE: // phase = 1
|
||||
emitter = this._captureEventEmitter;
|
||||
break;
|
||||
|
||||
case AT_TARGET: // phase = 2
|
||||
emitter = this._bubbleEventEmitter;
|
||||
break;
|
||||
|
||||
case BUBBLING_PHASE: // phase = 3
|
||||
emitter = this._bubbleEventEmitter;
|
||||
break;
|
||||
|
||||
default:
|
||||
invariant(false, 'invalid event phase %s', extraInfo.eventPhase);
|
||||
}
|
||||
|
@ -214,8 +259,10 @@ class NavigationContext {
|
|||
_onFocus(event: NavigationEvent): void {
|
||||
invariant(
|
||||
event.data && event.data.hasOwnProperty('route'),
|
||||
'didfocus event should provide route'
|
||||
'event type "%s" should provide route',
|
||||
event.type
|
||||
);
|
||||
|
||||
this._currentRoute = event.data.route;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,15 @@ describe('NavigationContext', () => {
|
|||
expect(child.parent).toBe(parent);
|
||||
});
|
||||
|
||||
it('has `top`', () => {
|
||||
var top = new NavigationContext();
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
top.appendChild(parent);
|
||||
parent.appendChild(child);
|
||||
expect(child.top).toBe(top);
|
||||
});
|
||||
|
||||
it('captures event', () => {
|
||||
var parent = new NavigationContext();
|
||||
var child = new NavigationContext();
|
||||
|
@ -67,8 +76,8 @@ describe('NavigationContext', () => {
|
|||
});
|
||||
};
|
||||
|
||||
parent.addListener('yo', listener, null, true);
|
||||
child.addListener('yo', listener, null, true);
|
||||
parent.addListener('yo', listener, true);
|
||||
child.addListener('yo', listener, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
|
@ -133,8 +142,8 @@ describe('NavigationContext', () => {
|
|||
|
||||
var counter = 0;
|
||||
|
||||
parent.addListener('yo', event => event.stopPropagation(), null, true);
|
||||
child.addListener('yo', event => counter++, null, true);
|
||||
parent.addListener('yo', event => event.stopPropagation(), true);
|
||||
child.addListener('yo', event => counter++, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
|
@ -162,8 +171,8 @@ describe('NavigationContext', () => {
|
|||
parent.appendChild(child);
|
||||
|
||||
var val;
|
||||
parent.addListener('yo', event => event.preventDefault(), null, true);
|
||||
child.addListener('yo', event => val = event.defaultPrevented, null, true);
|
||||
parent.addListener('yo', event => event.preventDefault(), true);
|
||||
child.addListener('yo', event => val = event.defaultPrevented, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
|
@ -205,9 +214,9 @@ describe('NavigationContext', () => {
|
|||
child.emit('didyo');
|
||||
});
|
||||
|
||||
parent.addListener('yo', listener, null, true);
|
||||
parent.addListener('didyo', listener, null, true);
|
||||
child.addListener('yo', listener, null, true);
|
||||
parent.addListener('yo', listener, true);
|
||||
parent.addListener('didyo', listener, true);
|
||||
child.addListener('yo', listener, true);
|
||||
|
||||
child.emit('yo');
|
||||
|
||||
|
|
|
@ -273,6 +273,8 @@ var Navigator = React.createClass({
|
|||
},
|
||||
|
||||
getInitialState: function() {
|
||||
this._navigationBarNavigator = this.props.navigationBarNavigator || this;
|
||||
|
||||
this._renderedSceneMap = new Map();
|
||||
|
||||
var routeStack = this.props.initialRouteStack || [this.props.initialRoute];
|
||||
|
@ -346,6 +348,12 @@ var Navigator = React.createClass({
|
|||
this._navigationContext.dispose();
|
||||
this._navigationContext = null;
|
||||
}
|
||||
|
||||
this.spring.destroy();
|
||||
|
||||
if (this._interactionHandle) {
|
||||
this.clearInteractionHandle(this._interactionHandle);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -411,6 +419,9 @@ var Navigator = React.createClass({
|
|||
* happening, we only set values for the transition and the gesture will catch up later
|
||||
*/
|
||||
_handleSpringUpdate: function() {
|
||||
if (!this.isMounted()) {
|
||||
return;
|
||||
}
|
||||
// Prioritize handling transition in progress over a gesture:
|
||||
if (this.state.transitionFromIndex != null) {
|
||||
this._transitionBetween(
|
||||
|
@ -432,6 +443,10 @@ var Navigator = React.createClass({
|
|||
* This happens at the end of a transition started by transitionTo, and when the spring catches up to a pending gesture
|
||||
*/
|
||||
_completeTransition: function() {
|
||||
if (!this.isMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.spring.getCurrentValue() !== 1 && this.spring.getCurrentValue() !== 0) {
|
||||
// The spring has finished catching up to a gesture in progress. Remove the pending progress
|
||||
// and we will be in a normal activeGesture state
|
||||
|
@ -591,8 +606,11 @@ var Navigator = React.createClass({
|
|||
|
||||
_handleMoveShouldSetPanResponder: function(e, gestureState) {
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
if (!sceneConfig) {
|
||||
return false;
|
||||
}
|
||||
this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState);
|
||||
return !! this._expectingGestureGrant;
|
||||
return !!this._expectingGestureGrant;
|
||||
},
|
||||
|
||||
_doesGestureOverswipe: function(gestureName) {
|
||||
|
@ -1070,7 +1088,7 @@ var Navigator = React.createClass({
|
|||
}
|
||||
return React.cloneElement(this.props.navigationBar, {
|
||||
ref: (navBar) => { this._navBar = navBar; },
|
||||
navigator: this,
|
||||
navigator: this._navigationBarNavigator,
|
||||
navState: this.state,
|
||||
});
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
function setupDevtools() {
|
||||
var messageListeners = [];
|
||||
var closeListeners = [];
|
||||
var ws = new window.WebSocket('ws://localhost:8081/devtools');
|
||||
var ws = new window.WebSocket('ws://localhost:8097/devtools');
|
||||
// this is accessed by the eval'd backend code
|
||||
var FOR_BACKEND = { // eslint-disable-line no-unused-vars
|
||||
resolveRNStyle: require('flattenStyle'),
|
||||
|
@ -31,11 +31,11 @@ function setupDevtools() {
|
|||
},
|
||||
};
|
||||
ws.onclose = () => {
|
||||
console.warn('devtools socket closed');
|
||||
setTimeout(setupDevtools, 200);
|
||||
closeListeners.forEach(fn => fn());
|
||||
};
|
||||
ws.onerror = error => {
|
||||
console.warn('devtools socket errored', error);
|
||||
setTimeout(setupDevtools, 200);
|
||||
closeListeners.forEach(fn => fn());
|
||||
};
|
||||
ws.onopen = function () {
|
||||
|
@ -58,7 +58,7 @@ function setupDevtools() {
|
|||
// FOR_BACKEND is used by the eval'd code
|
||||
eval(text); // eslint-disable-line no-eval
|
||||
} catch (e) {
|
||||
console.error('Failed to eval' + e.message);
|
||||
console.error('Failed to eval: ' + e.message);
|
||||
return;
|
||||
}
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
|
||||
|
|
|
@ -278,6 +278,10 @@ var self = {};
|
|||
}
|
||||
this._initBody(body)
|
||||
}
|
||||
|
||||
Request.prototype.clone = function() {
|
||||
return new Request(this)
|
||||
}
|
||||
|
||||
function decode(body) {
|
||||
var form = new FormData()
|
||||
|
@ -320,6 +324,15 @@ var self = {};
|
|||
this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
|
||||
this.url = options.url || ''
|
||||
}
|
||||
|
||||
Response.prototype.clone = function() {
|
||||
return new Response(this._bodyInit, {
|
||||
status: this.status,
|
||||
statusText: this.statusText,
|
||||
headers: new Headers(this.headers),
|
||||
url: this.url
|
||||
})
|
||||
}
|
||||
|
||||
Body.call(Response.prototype)
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ static NSDictionary *RCTPositionError(RCTPositionErrorCode code, NSString *msg /
|
|||
{
|
||||
CLLocationManager *_locationManager;
|
||||
NSDictionary *_lastLocationEvent;
|
||||
NSMutableArray *_pendingRequests;
|
||||
NSMutableArray<RCTLocationRequest *> *_pendingRequests;
|
||||
BOOL _observingLocation;
|
||||
RCTLocationOptions _observerOptions;
|
||||
}
|
||||
|
@ -249,7 +249,8 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
|||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
|
||||
- (void)locationManager:(CLLocationManager *)manager
|
||||
didUpdateLocations:(NSArray<CLLocation *> *)locations
|
||||
{
|
||||
// Create event
|
||||
CLLocation *location = locations.lastObject;
|
||||
|
|
|
@ -42,8 +42,8 @@ RCT_EXPORT_MODULE()
|
|||
if (imageCount > 1) {
|
||||
|
||||
NSTimeInterval duration = 0;
|
||||
NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
NSMutableArray<NSNumber *> *delays = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
NSMutableArray<id /* CGIMageRef */> *images = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
for (size_t i = 0; i < imageCount; i++) {
|
||||
|
||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
|
||||
|
@ -75,7 +75,7 @@ RCT_EXPORT_MODULE()
|
|||
}
|
||||
CFRelease(imageSource);
|
||||
|
||||
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
|
||||
NSMutableArray<NSNumber *> *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
|
||||
NSTimeInterval runningDuration = 0;
|
||||
for (NSNumber *delayNumber in delays) {
|
||||
[keyTimes addObject:@(runningDuration / duration)];
|
||||
|
|
|
@ -28,7 +28,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
|
|||
|
||||
/**
|
||||
* Loads the specified image at the highest available resolution.
|
||||
* Can be called from any thread, will always call callback on main thread.
|
||||
* Can be called from any thread, will call back on an unspecified thread.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#import "RCTImageLoader.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
@ -18,17 +19,6 @@
|
|||
#import "RCTNetworking.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
|
||||
{
|
||||
if ([NSThread isMainThread]) {
|
||||
callback(error, image);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
callback(error, image);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@implementation UIImage (React)
|
||||
|
||||
- (CAKeyframeAnimation *)reactKeyframeAnimation
|
||||
|
@ -45,8 +35,8 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
|
|||
|
||||
@implementation RCTImageLoader
|
||||
{
|
||||
NSArray *_loaders;
|
||||
NSArray *_decoders;
|
||||
NSArray<id<RCTImageURLLoader>> *_loaders;
|
||||
NSArray<id<RCTImageDataDecoder>> *_decoders;
|
||||
NSURLCache *_cache;
|
||||
}
|
||||
|
||||
|
@ -57,14 +47,14 @@ RCT_EXPORT_MODULE()
|
|||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
// Get image loaders and decoders
|
||||
NSMutableArray *loaders = [NSMutableArray array];
|
||||
NSMutableArray *decoders = [NSMutableArray array];
|
||||
NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
|
||||
NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
|
||||
[loaders addObject:module];
|
||||
[loaders addObject:(id<RCTImageURLLoader>)module];
|
||||
}
|
||||
if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
|
||||
[decoders addObject:module];
|
||||
[decoders addObject:(id<RCTImageDataDecoder>)module];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +174,7 @@ RCT_EXPORT_MODULE()
|
|||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
{
|
||||
if (imageTag.length == 0) {
|
||||
|
@ -192,23 +182,20 @@ RCT_EXPORT_MODULE()
|
|||
return ^{};
|
||||
}
|
||||
|
||||
// Ensure progress is dispatched on main thread
|
||||
RCTImageLoaderProgressBlock progressHandler = nil;
|
||||
if (progressBlock) {
|
||||
progressHandler = ^(int64_t progress, int64_t total) {
|
||||
if ([NSThread isMainThread]) {
|
||||
progressBlock(progress, total);
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
progressBlock(progress, total);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure completion is dispatched on main thread
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) {
|
||||
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
|
||||
if ([NSThread isMainThread]) {
|
||||
|
||||
// Most loaders do not return on the main thread, so caller is probably not
|
||||
// expecting it, and may do expensive post-processing in the callback
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (!cancelled) {
|
||||
completionBlock(error, image);
|
||||
}
|
||||
});
|
||||
} else if (!cancelled) {
|
||||
completionBlock(error, image);
|
||||
}
|
||||
};
|
||||
|
||||
// Find suitable image URL loader
|
||||
|
@ -220,7 +207,7 @@ RCT_EXPORT_MODULE()
|
|||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
progressHandler:progressHandler
|
||||
completionHandler:completionHandler];
|
||||
completionHandler:completionHandler] ?: ^{};
|
||||
}
|
||||
|
||||
// Check if networking module is available
|
||||
|
@ -237,7 +224,16 @@ RCT_EXPORT_MODULE()
|
|||
__weak RCTImageLoader *weakSelf = self;
|
||||
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
|
||||
RCTURLRequestCompletionBlock processResponse =
|
||||
^(NSURLResponse *response, NSData *data, __unused NSError *error) {
|
||||
^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
|
||||
// Check for system errors
|
||||
if (error) {
|
||||
completionHandler(error, nil);
|
||||
return;
|
||||
} else if (!data) {
|
||||
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for http errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
|
@ -296,9 +292,7 @@ RCT_EXPORT_MODULE()
|
|||
processResponse(response, data, nil);
|
||||
|
||||
}];
|
||||
if (progressBlock) {
|
||||
task.downloadProgressBlock = progressBlock;
|
||||
}
|
||||
task.downloadProgressBlock = progressHandler;
|
||||
[task start];
|
||||
|
||||
return ^{
|
||||
|
@ -306,6 +300,7 @@ RCT_EXPORT_MODULE()
|
|||
if (decodeCancel) {
|
||||
decodeCancel();
|
||||
}
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -317,27 +312,36 @@ RCT_EXPORT_MODULE()
|
|||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
|
||||
if (imageDecoder) {
|
||||
|
||||
return [imageDecoder decodeImageData:data
|
||||
size:size
|
||||
scale:scale
|
||||
resizeMode:resizeMode
|
||||
completionHandler:completionBlock];
|
||||
completionHandler:completionHandler];
|
||||
} else {
|
||||
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image) {
|
||||
completionBlock(nil, image);
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
|
||||
NSError *finalError = RCTErrorWithMessage(errorMessage);
|
||||
completionBlock(finalError, nil);
|
||||
completionHandler(finalError, nil);
|
||||
}
|
||||
});
|
||||
return ^{};
|
||||
|
||||
return ^{
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -183,24 +183,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
|||
resizeMode:self.contentMode
|
||||
progressBlock:progressHandler
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
if (image.reactKeyframeAnimation) {
|
||||
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.image = image;
|
||||
}
|
||||
if (error) {
|
||||
if (_onError) {
|
||||
_onError(@{ @"error": error.localizedDescription });
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (image.reactKeyframeAnimation) {
|
||||
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
|
||||
} else {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
self.image = image;
|
||||
}
|
||||
} else {
|
||||
if (_onLoad) {
|
||||
_onLoad(nil);
|
||||
if (error) {
|
||||
if (_onError) {
|
||||
_onError(@{ @"error": error.localizedDescription });
|
||||
}
|
||||
} else {
|
||||
if (_onLoad) {
|
||||
_onLoad(nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_onLoadEnd) {
|
||||
_onLoadEnd(nil);
|
||||
}
|
||||
if (_onLoadEnd) {
|
||||
_onLoadEnd(nil);
|
||||
}
|
||||
});
|
||||
}];
|
||||
} else {
|
||||
[self clearImage];
|
||||
|
|
|
@ -38,7 +38,13 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|||
CGFloat scale = [RCTConvert CGFloat:_source[@"scale"]] ?: 1;
|
||||
|
||||
__weak RCTShadowVirtualImage *weakSelf = self;
|
||||
[_bridge.imageLoader loadImageWithTag:imageTag size:CGSizeZero scale:scale resizeMode:UIViewContentModeScaleToFill progressBlock:nil completionBlock:^(NSError *error, UIImage *image) {
|
||||
[_bridge.imageLoader loadImageWithTag:imageTag
|
||||
size:CGSizeZero
|
||||
scale:scale
|
||||
resizeMode:UIViewContentModeScaleToFill
|
||||
progressBlock:nil
|
||||
completionBlock:^(NSError *error, UIImage *image) {
|
||||
|
||||
dispatch_async(_bridge.uiManager.methodQueue, ^{
|
||||
RCTShadowVirtualImage *strongSelf = weakSelf;
|
||||
strongSelf->_image = image;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#import "RCTXCAssetImageLoader.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTXCAssetImageLoader
|
||||
|
@ -20,34 +22,34 @@ RCT_EXPORT_MODULE()
|
|||
return RCTIsXCAssetURL(requestURL);
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
|
||||
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
||||
{
|
||||
__block BOOL cancelled = NO;
|
||||
__block volatile uint32_t cancelled = 0;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *imageName = RCTBundlePathForURL(imageURL);
|
||||
UIImage *image = [UIImage imageNamed:imageName];
|
||||
if (image) {
|
||||
if (progressHandler) {
|
||||
progressHandler(1, 1);
|
||||
}
|
||||
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, image);
|
||||
}
|
||||
completionHandler(nil, image);
|
||||
} else {
|
||||
if (completionHandler) {
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
|
||||
completionHandler(RCTErrorWithMessage(message), nil);
|
||||
}
|
||||
});
|
||||
|
||||
return ^{
|
||||
cancelled = YES;
|
||||
OSAtomicOr32Barrier(1, &cancelled);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ describe('resolveAssetSource', () => {
|
|||
describe('bundle was loaded from file on iOS', () => {
|
||||
beforeEach(() => {
|
||||
NativeModules.SourceCode.scriptURL =
|
||||
'file:///Path/To/Simulator/main.bundle';
|
||||
'file:///Path/To/Sample.app/main.bundle';
|
||||
Platform.OS = 'ios';
|
||||
});
|
||||
|
||||
|
@ -126,7 +126,7 @@ describe('resolveAssetSource', () => {
|
|||
__packager_asset: true,
|
||||
width: 100,
|
||||
height: 200,
|
||||
uri: 'assets/module/a/logo.png',
|
||||
uri: '/Path/To/Sample.app/assets/module/a/logo.png',
|
||||
scale: 1,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ var PixelRatio = require('PixelRatio');
|
|||
var Platform = require('Platform');
|
||||
var SourceCode = require('NativeModules').SourceCode;
|
||||
|
||||
var _serverURL;
|
||||
var _serverURL, _offlinePath;
|
||||
|
||||
function getDevServerURL() {
|
||||
if (_serverURL === undefined) {
|
||||
|
@ -44,6 +44,20 @@ function getDevServerURL() {
|
|||
return _serverURL;
|
||||
}
|
||||
|
||||
function getOfflinePath() {
|
||||
if (_offlinePath === undefined) {
|
||||
var scriptURL = SourceCode.scriptURL;
|
||||
var match = scriptURL && scriptURL.match(/^file:\/\/(\/.*\/)/);
|
||||
if (match) {
|
||||
_offlinePath = match[1];
|
||||
} else {
|
||||
_offlinePath = '';
|
||||
}
|
||||
}
|
||||
|
||||
return _offlinePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path at which the asset can be found in the archive
|
||||
*/
|
||||
|
@ -59,7 +73,7 @@ function getPathInArchive(asset) {
|
|||
.replace(/^assets_/, ''); // Remove "assets_" prefix
|
||||
} else {
|
||||
// E.g. 'assets/AwesomeModule/icon@2x.png'
|
||||
return getScaledAssetPath(asset);
|
||||
return getOfflinePath() + getScaledAssetPath(asset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,12 +21,13 @@ var sourceMapPromise;
|
|||
|
||||
var exceptionID = 0;
|
||||
|
||||
function reportException(e: Error, isFatal: bool, stack?: any) {
|
||||
/**
|
||||
* Handles the developer-visible aspect of errors and exceptions
|
||||
*/
|
||||
function reportException(e: Error, isFatal: bool) {
|
||||
var currentExceptionID = ++exceptionID;
|
||||
if (RCTExceptionsManager) {
|
||||
if (!stack) {
|
||||
stack = parseErrorStack(e);
|
||||
}
|
||||
var stack = parseErrorStack(e);
|
||||
if (isFatal) {
|
||||
RCTExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
|
||||
} else {
|
||||
|
@ -47,6 +48,9 @@ function reportException(e: Error, isFatal: bool, stack?: any) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs exceptions to the (native) console and displays them
|
||||
*/
|
||||
function handleException(e: Error, isFatal: boolean) {
|
||||
// Workaround for reporting errors caused by `throw 'some string'`
|
||||
// Unfortunately there is no way to figure out the stacktrace in this
|
||||
|
@ -55,19 +59,9 @@ function handleException(e: Error, isFatal: boolean) {
|
|||
if (!e.message) {
|
||||
e = new Error(e);
|
||||
}
|
||||
var stack = parseErrorStack(e);
|
||||
var msg =
|
||||
'Error: ' + e.message +
|
||||
'\n stack: \n' + stackToString(stack) +
|
||||
'\n URL: ' + (e: any).sourceURL +
|
||||
'\n line: ' + (e: any).line +
|
||||
'\n message: ' + e.message;
|
||||
if (console.errorOriginal) {
|
||||
console.errorOriginal(msg);
|
||||
} else {
|
||||
console.error(msg);
|
||||
}
|
||||
reportException(e, isFatal, stack);
|
||||
|
||||
(console._errorOriginal || console.error)(e.message);
|
||||
reportException(e, isFatal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,54 +69,35 @@ function handleException(e: Error, isFatal: boolean) {
|
|||
* setting `console.reportErrorsAsExceptions = false;` in your app.
|
||||
*/
|
||||
function installConsoleErrorReporter() {
|
||||
if (console.reportException) {
|
||||
// Enable reportErrorsAsExceptions
|
||||
if (console._errorOriginal) {
|
||||
return; // already installed
|
||||
}
|
||||
console.reportException = reportException;
|
||||
console.errorOriginal = console.error.bind(console);
|
||||
console._errorOriginal = console.error.bind(console);
|
||||
console.error = function reactConsoleError() {
|
||||
// Note that when using the built-in context executor on iOS (i.e., not
|
||||
// Chrome debugging), console.error is already stubbed out to cause a
|
||||
// redbox via RCTNativeLoggingHook.
|
||||
console.errorOriginal.apply(null, arguments);
|
||||
console._errorOriginal.apply(null, arguments);
|
||||
if (!console.reportErrorsAsExceptions) {
|
||||
return;
|
||||
}
|
||||
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
|
||||
if (str.slice(0, 10) === '"Warning: ') {
|
||||
// React warnings use console.error so that a stack trace is shown, but
|
||||
// we don't (currently) want these to show a redbox
|
||||
// (Note: Logic duplicated in polyfills/console.js.)
|
||||
return;
|
||||
|
||||
if (arguments[0] && arguments[0].stack) {
|
||||
reportException(arguments[0], /* isFatal */ false);
|
||||
} else {
|
||||
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
|
||||
if (str.slice(0, 10) === '"Warning: ') {
|
||||
// React warnings use console.error so that a stack trace is shown, but
|
||||
// we don't (currently) want these to show a redbox
|
||||
// (Note: Logic duplicated in polyfills/console.js.)
|
||||
return;
|
||||
}
|
||||
var error : any = new Error('console.error: ' + str);
|
||||
error.framesToPop = 1;
|
||||
reportException(error, /* isFatal */ false);
|
||||
}
|
||||
var error: any = new Error('console.error: ' + str);
|
||||
error.framesToPop = 1;
|
||||
reportException(error, /* isFatal */ false);
|
||||
};
|
||||
if (console.reportErrorsAsExceptions === undefined) {
|
||||
console.reportErrorsAsExceptions = true; // Individual apps can disable this
|
||||
}
|
||||
}
|
||||
|
||||
function stackToString(stack) {
|
||||
var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length));
|
||||
return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n');
|
||||
}
|
||||
|
||||
function stackFrameToString(stackFrame, maxLength) {
|
||||
var fileNameParts = stackFrame.file.split('/');
|
||||
var fileName = fileNameParts[fileNameParts.length - 1];
|
||||
|
||||
if (fileName.length > 18) {
|
||||
fileName = fileName.substr(0, 17) + '\u2026'; /* ... */
|
||||
}
|
||||
|
||||
var spaces = fillSpaces(maxLength - stackFrame.methodName.length);
|
||||
return ' ' + stackFrame.methodName + spaces + ' ' + fileName + ':' + stackFrame.lineNumber;
|
||||
}
|
||||
|
||||
function fillSpaces(n) {
|
||||
return new Array(n + 1).join(' ');
|
||||
}
|
||||
|
||||
module.exports = { handleException, installConsoleErrorReporter };
|
||||
|
|
|
@ -22,10 +22,6 @@
|
|||
/* eslint strict: 0 */
|
||||
/* globals GLOBAL: true, window: true */
|
||||
|
||||
// Just to make sure the JS gets packaged up.
|
||||
require('RCTDebugComponentOwnership');
|
||||
require('RCTDeviceEventEmitter');
|
||||
require('PerformanceLogger');
|
||||
require('regenerator/runtime');
|
||||
|
||||
if (typeof GLOBAL === 'undefined') {
|
||||
|
@ -36,12 +32,10 @@ if (typeof window === 'undefined') {
|
|||
window = GLOBAL;
|
||||
}
|
||||
|
||||
function handleError(e, isFatal) {
|
||||
try {
|
||||
require('ExceptionsManager').handleException(e, isFatal);
|
||||
} catch(ee) {
|
||||
console.log('Failed to print error: ', ee.message);
|
||||
}
|
||||
function setUpConsole() {
|
||||
// ExceptionsManager transitively requires Promise so we install it after
|
||||
var ExceptionsManager = require('ExceptionsManager');
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,21 +70,19 @@ function polyfillGlobal(name, newValue, scope=GLOBAL) {
|
|||
Object.defineProperty(scope, name, {...descriptor, value: newValue});
|
||||
}
|
||||
|
||||
function setUpRedBoxErrorHandler() {
|
||||
function setUpErrorHandler() {
|
||||
function handleError(e, isFatal) {
|
||||
try {
|
||||
require('ExceptionsManager').handleException(e, isFatal);
|
||||
} catch(ee) {
|
||||
console.log('Failed to print error: ', ee.message);
|
||||
}
|
||||
}
|
||||
|
||||
var ErrorUtils = require('ErrorUtils');
|
||||
ErrorUtils.setGlobalHandler(handleError);
|
||||
}
|
||||
|
||||
function setUpRedBoxConsoleErrorHandler() {
|
||||
// ExceptionsManager transitively requires Promise so we install it after
|
||||
var ExceptionsManager = require('ExceptionsManager');
|
||||
var Platform = require('Platform');
|
||||
// TODO (#6925182): Enable console.error redbox on Android
|
||||
if (__DEV__ && Platform.OS === 'ios') {
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
}
|
||||
}
|
||||
|
||||
function setUpFlowChecker() {
|
||||
if (__DEV__) {
|
||||
var checkFlowAtRuntime = require('checkFlowAtRuntime');
|
||||
|
@ -163,8 +155,6 @@ function setUpWebSockets() {
|
|||
}
|
||||
|
||||
function setUpProfile() {
|
||||
console.profile = console.profile || GLOBAL.nativeTraceBeginSection || function () {};
|
||||
console.profileEnd = console.profileEnd || GLOBAL.nativeTraceEndSection || function () {};
|
||||
if (__DEV__) {
|
||||
require('BridgeProfiling').swizzleReactPerf();
|
||||
}
|
||||
|
@ -184,15 +174,30 @@ function setUpNumber() {
|
|||
Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(Math.pow(2, 53) - 1);
|
||||
}
|
||||
|
||||
setUpRedBoxErrorHandler();
|
||||
function setUpDevTools() {
|
||||
// not when debugging in chrome
|
||||
if (__DEV__ && !window.document && require('Platform').OS === 'ios') {
|
||||
var setupDevtools = require('setupDevtools');
|
||||
setupDevtools();
|
||||
}
|
||||
}
|
||||
|
||||
setUpProcessEnv();
|
||||
setUpConsole();
|
||||
setUpTimers();
|
||||
setUpAlert();
|
||||
setUpPromise();
|
||||
setUpErrorHandler();
|
||||
setUpXHR();
|
||||
setUpRedBoxConsoleErrorHandler();
|
||||
setUpGeolocation();
|
||||
setUpWebSockets();
|
||||
setUpProfile();
|
||||
setUpProcessEnv();
|
||||
setUpFlowChecker();
|
||||
setUpNumber();
|
||||
setUpDevTools();
|
||||
|
||||
// Just to make sure the JS gets packaged up. Wait until the JS environment has
|
||||
// been initialized before requiring them.
|
||||
require('RCTDebugComponentOwnership');
|
||||
require('RCTDeviceEventEmitter');
|
||||
require('PerformanceLogger');
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
require('mock-modules').autoMockOff();
|
||||
jest.autoMockOff();
|
||||
|
||||
var parseErrorStack = require('parseErrorStack');
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ var RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
|
|||
*
|
||||
* In apps written with React Native from the root view down, you should use
|
||||
* Navigator instead of Modal. With a top-level Navigator, you have more control
|
||||
* over how to present the modal scene over the rest of your app.
|
||||
* over how to present the modal scene over the rest of your app by using the
|
||||
* configureScene property.
|
||||
*/
|
||||
class Modal extends React.Component {
|
||||
render(): ?ReactElement {
|
||||
|
@ -58,9 +59,14 @@ class Modal extends React.Component {
|
|||
Modal.propTypes = {
|
||||
animated: PropTypes.bool,
|
||||
transparent: PropTypes.bool,
|
||||
visible: PropTypes.bool,
|
||||
onDismiss: PropTypes.func,
|
||||
};
|
||||
|
||||
Modal.defaultProps = {
|
||||
visible: true,
|
||||
};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
modal: {
|
||||
position: 'absolute',
|
||||
|
|
|
@ -47,26 +47,18 @@ type FormDataPart = {
|
|||
*/
|
||||
class FormData {
|
||||
_parts: Array<FormDataNameValuePair>;
|
||||
_partsByKey: {[key: string]: FormDataNameValuePair};
|
||||
|
||||
constructor() {
|
||||
this._parts = [];
|
||||
this._partsByKey = {};
|
||||
}
|
||||
|
||||
append(key: string, value: FormDataValue) {
|
||||
var parts = this._partsByKey[key];
|
||||
if (parts) {
|
||||
// It's a bit unclear what the behaviour should be in this case.
|
||||
// The XMLHttpRequest spec doesn't specify it, while MDN says that
|
||||
// the any new values should appended to existing values. We're not
|
||||
// doing that for now -- it's tedious and doesn't seem worth the effort.
|
||||
parts[1] = value;
|
||||
return;
|
||||
}
|
||||
parts = [key, value];
|
||||
this._parts.push(parts);
|
||||
this._partsByKey[key] = parts;
|
||||
// The XMLHttpRequest spec doesn't specify if duplicate keys are allowed.
|
||||
// MDN says that any new values should be appended to existing values.
|
||||
// In any case, major browsers allow duplicate keys, so that's what we'll do
|
||||
// too. They'll simply get appended as additional form data parts in the
|
||||
// request body, leaving the server to deal with them.
|
||||
this._parts.push([key, value]);
|
||||
}
|
||||
|
||||
getParts(): Array<FormDataPart> {
|
||||
|
|
|
@ -36,6 +36,7 @@ RCT_EXPORT_MODULE()
|
|||
_queue.maxConcurrentOperationCount = 2;
|
||||
}
|
||||
|
||||
__weak __block NSBlockOperation *weakOp;
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
|
||||
// Get mime type
|
||||
|
@ -48,7 +49,7 @@ RCT_EXPORT_MODULE()
|
|||
expectedContentLength:-1
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:op didReceiveResponse:response];
|
||||
[delegate URLRequest:weakOp didReceiveResponse:response];
|
||||
|
||||
// Load data
|
||||
NSError *error;
|
||||
|
@ -56,11 +57,12 @@ RCT_EXPORT_MODULE()
|
|||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (data) {
|
||||
[delegate URLRequest:op didReceiveData:data];
|
||||
[delegate URLRequest:weakOp didReceiveData:data];
|
||||
}
|
||||
[delegate URLRequest:op didCompleteWithError:error];
|
||||
[delegate URLRequest:weakOp didCompleteWithError:error];
|
||||
}];
|
||||
|
||||
weakOp = op;
|
||||
[_queue addOperation:op];
|
||||
return op;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ RCT_EXPORT_MODULE()
|
|||
_fileQueue.maxConcurrentOperationCount = 4;
|
||||
}
|
||||
|
||||
__weak __block NSBlockOperation *weakOp;
|
||||
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
|
||||
|
||||
// Get content length
|
||||
|
@ -49,7 +50,7 @@ RCT_EXPORT_MODULE()
|
|||
NSFileManager *fileManager = [NSFileManager new];
|
||||
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
|
||||
if (error) {
|
||||
[delegate URLRequest:op didCompleteWithError:error];
|
||||
[delegate URLRequest:weakOp didCompleteWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -66,18 +67,19 @@ RCT_EXPORT_MODULE()
|
|||
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
|
||||
textEncodingName:nil];
|
||||
|
||||
[delegate URLRequest:op didReceiveResponse:response];
|
||||
[delegate URLRequest:weakOp didReceiveResponse:response];
|
||||
|
||||
// Load data
|
||||
NSData *data = [NSData dataWithContentsOfURL:request.URL
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (data) {
|
||||
[delegate URLRequest:op didReceiveData:data];
|
||||
[delegate URLRequest:weakOp didReceiveData:data];
|
||||
}
|
||||
[delegate URLRequest:op didCompleteWithError:error];
|
||||
[delegate URLRequest:weakOp didCompleteWithError:error];
|
||||
}];
|
||||
|
||||
weakOp = op;
|
||||
[_fileQueue addOperation:op];
|
||||
return op;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
static NSSet *schemes = nil;
|
||||
static NSSet<NSString *> *schemes = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
// technically, RCTHTTPRequestHandler can handle file:// as well,
|
||||
|
|
|
@ -22,7 +22,7 @@ typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
|
|||
|
||||
@property (nonatomic, readonly) NSURLRequest *request;
|
||||
@property (nonatomic, readonly) NSNumber *requestID;
|
||||
@property (nonatomic, readonly) id requestToken;
|
||||
@property (nonatomic, readonly, weak) id requestToken;
|
||||
@property (nonatomic, readonly) NSURLResponse *response;
|
||||
@property (nonatomic, readonly) RCTURLRequestCompletionBlock completionBlock;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSD
|
|||
|
||||
@implementation RCTHTTPFormDataHelper
|
||||
{
|
||||
NSMutableArray *_parts;
|
||||
NSMutableArray<NSDictionary *> *_parts;
|
||||
NSMutableData *_multipartBody;
|
||||
RCTHTTPQueryResult _callback;
|
||||
NSString *_boundary;
|
||||
|
@ -122,7 +122,7 @@ static NSString *RCTGenerateFormBoundary()
|
|||
@implementation RCTNetworking
|
||||
{
|
||||
NSMutableDictionary *_tasksByRequestID;
|
||||
NSArray *_handlers;
|
||||
NSArray<id<RCTURLRequestHandler>> *_handlers;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
@ -133,10 +133,10 @@ RCT_EXPORT_MODULE()
|
|||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
// get handlers
|
||||
NSMutableArray *handlers = [NSMutableArray array];
|
||||
NSMutableArray<id<RCTURLRequestHandler>> *handlers = [NSMutableArray array];
|
||||
for (id<RCTBridgeModule> module in bridge.modules.allValues) {
|
||||
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
|
||||
[handlers addObject:module];
|
||||
[handlers addObject:(id<RCTURLRequestHandler>)module];
|
||||
}
|
||||
}
|
||||
|
||||
|
|