Merge pull request #1 from facebook/master

Updating fork
This commit is contained in:
Edoardo Moreni 2015-11-09 10:41:06 +00:00
commit 49df1cbfb0
493 changed files with 10573 additions and 5820 deletions

View File

@ -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

View File

@ -82,3 +82,4 @@ env:
branches:
only:
- master
- /^.*-stable$/

View File

@ -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];

View File

@ -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];

View File

@ -69,7 +69,7 @@ public class MoviesActivity extends Activity implements DefaultHardwareBackBtnHa
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onResume(this);
mReactInstanceManager.onResume(this, this);
}
}

View File

@ -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];

View File

@ -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>
);

View File

@ -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>

View File

@ -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>
);
}
},
];

View File

@ -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',
},

View File

@ -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>
);
},

View File

@ -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} />;
}
}
];

View File

@ -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

View File

@ -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>

View File

@ -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 />;
}
},
];

View File

@ -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 />; },
},
];

View File

@ -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() {

View File

@ -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>
);
},

View File

@ -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 />; }
}
];

View File

@ -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;

View File

@ -73,5 +73,6 @@ var styles = StyleSheet.create({
});
AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp);
UIExplorerList.registerComponents();
module.exports = UIExplorerApp;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -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

View File

@ -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
{

View File

@ -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({

View File

@ -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

View File

@ -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];

View File

@ -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")

View File

@ -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

View File

@ -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)));

View File

@ -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,

View File

@ -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({

View File

@ -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({

View File

@ -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;

View File

@ -69,7 +69,7 @@ public class UIExplorerActivity extends Activity implements DefaultHardwareBackB
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onResume(this);
mReactInstanceManager.onResume(this, this);
}
}

View File

@ -19,7 +19,7 @@
CGPoint _endPoint;
}
- (instancetype)initWithArray:(NSArray *)array
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 5) {

View File

@ -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) {

View File

@ -21,7 +21,7 @@
CGFloat _radiusRatio;
}
- (instancetype)initWithArray:(NSArray *)array
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
{
if ((self = [super initWithArray:array])) {
if (array.count < 7) {

View File

@ -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]);

View File

@ -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;

View File

@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (ARTNode *)node
{
return [[ARTGroup alloc] init];
return [ARTGroup new];
}
@end

View File

@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (ARTNode *)node
{
return [[ARTNode alloc] init];
return [ARTNode new];
}
- (UIView *)view

View File

@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
return [[ARTRenderable alloc] init];
return [ARTRenderable new];
}
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)

View File

@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
return [[ARTShape alloc] init];
return [ARTShape new];
}
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)

View File

@ -17,7 +17,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[ARTSurfaceView alloc] init];
return [ARTSurfaceView new];
}
@end

View File

@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
- (ARTRenderable *)node
{
return [[ARTText alloc] init];
return [ARTText new];
}
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)

View File

@ -27,7 +27,6 @@ var ActionSheetIOS = {
);
RCTActionSheetManager.showActionSheetWithOptions(
options,
() => {}, // RKActionSheet compatibility hack
callback
);
},

View File

@ -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

View File

@ -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,

View File

@ -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"`.
*

View File

@ -15,11 +15,6 @@
@interface RCTBridge (RCTAssetsLibraryImageLoader)
/**
* The shared Assets Library image loader
*/
@property (nonatomic, readonly) RCTAssetsLibraryImageLoader *assetsLibraryImageLoader;
/**
* The shared asset library instance.
*/

View File

@ -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];

View File

@ -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>

View File

@ -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;

View File

@ -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(@[]);

View File

@ -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 {

View File

@ -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>
);
}

View File

@ -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`.

View File

@ -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;

View File

@ -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;

View File

@ -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,
});
}
}
});
},

View File

@ -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,

View File

@ -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`.

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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: {

View File

@ -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}
/>;

View File

@ -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);
},
});

View File

@ -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;
}
}

View File

@ -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');

View File

@ -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,
});
},

View File

@ -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({

View File

@ -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)

View File

@ -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;

View File

@ -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)];

View File

@ -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;

View File

@ -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);
};
}
}

View File

@ -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];

View File

@ -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;

View File

@ -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);
};
}

View File

@ -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,
});
});

View File

@ -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);
}
}

View File

@ -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 };

View File

@ -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');

View File

@ -9,7 +9,7 @@
'use strict';
require('mock-modules').autoMockOff();
jest.autoMockOff();
var parseErrorStack = require('parseErrorStack');

View File

@ -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',

View File

@ -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> {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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;

View File

@ -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];
}
}

Some files were not shown because too many files have changed in this diff Show More