mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
173
Commits
0.8-stable
...
0.9-stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff135f6a55 | ||
|
|
e2c1da70e5 | ||
|
|
b10a1549c6 | ||
|
|
c627600ef3 | ||
|
|
fc9150c674 | ||
|
|
de13b59dd2 | ||
|
|
0f32bea634 | ||
|
|
5164aa290b | ||
|
|
7fcd85cc53 | ||
|
|
7a806f6d89 | ||
|
|
379444515b | ||
|
|
f502e17cc7 | ||
|
|
757d6d204a | ||
|
|
62b7920b9d | ||
|
|
3cff9be3d1 | ||
|
|
3b83853713 | ||
|
|
842ce51099 | ||
|
|
9649e7cc8d | ||
|
|
58a403d3c8 | ||
|
|
18e6094cab | ||
|
|
33e62f71f4 | ||
|
|
b7253dc604 | ||
|
|
01911e5e06 | ||
|
|
09bb761b25 | ||
|
|
2eb401a496 | ||
|
|
7996c32211 | ||
|
|
2c5290946b | ||
|
|
d178e27939 | ||
|
|
f9abb5aae2 | ||
|
|
0e5422f36c | ||
|
|
c3d194d1f8 | ||
|
|
f69e33e6a8 | ||
|
|
aca76c75dd | ||
|
|
f1bd1cbc94 | ||
|
|
53222f0dda | ||
|
|
5f0317291b | ||
|
|
ea5276ed24 | ||
|
|
c8373d2ad6 | ||
|
|
bf7e2a85d0 | ||
|
|
d10e4dbf0f | ||
|
|
a1612a7dd2 | ||
|
|
000ab1139f | ||
|
|
81dd9c27ea | ||
|
|
4499f28c1d | ||
|
|
ef5cec4f08 | ||
|
|
7c87952e43 | ||
|
|
1b06e41c83 | ||
|
|
accf6f12e4 | ||
|
|
e01f90784f | ||
|
|
710d29efae | ||
|
|
9e4af68d91 | ||
|
|
10ffe170c2 | ||
|
|
3fa3963465 | ||
|
|
aefb3d3628 | ||
|
|
a803e3217e | ||
|
|
03dccfbf71 | ||
|
|
064dafa618 | ||
|
|
9c8ba9502c | ||
|
|
19a8eff668 | ||
|
|
9d19829742 | ||
|
|
6fea7c2846 | ||
|
|
fea2db42fd | ||
|
|
b34892eb80 | ||
|
|
e06af51cf9 | ||
|
|
a671c58189 | ||
|
|
93bbc6482d | ||
|
|
bb7aa500d7 | ||
|
|
cec5360f1b | ||
|
|
49b55804b1 | ||
|
|
8dd1256c25 | ||
|
|
280347d66a | ||
|
|
214c47ed76 | ||
|
|
75b220d6ce | ||
|
|
4e33b801ca | ||
|
|
1e5f22dc9c | ||
|
|
35aa9f3b97 | ||
|
|
4d3c3e625e | ||
|
|
961c1eb429 | ||
|
|
4f904b5d68 | ||
|
|
98ab7581a5 | ||
|
|
85cb35c514 | ||
|
|
9c73e2ff7a | ||
|
|
9c5fe3612d | ||
|
|
151ddd9e42 | ||
|
|
6e2f07fb81 | ||
|
|
9105b22925 | ||
|
|
e54e452026 | ||
|
|
bf7497a418 | ||
|
|
270e09d46e | ||
|
|
725053acfe | ||
|
|
6213950334 | ||
|
|
95dc38929c | ||
|
|
0d91b93aba | ||
|
|
72f7a1be6f | ||
|
|
5db42643cf | ||
|
|
e3afc4efd8 | ||
|
|
f2d65ea85b | ||
|
|
f3dd07531c | ||
|
|
4a262e0f2b | ||
|
|
c28d33f3a2 | ||
|
|
c4389c006f | ||
|
|
b56717ede9 | ||
|
|
3c78aa6d25 | ||
|
|
61c648d564 | ||
|
|
82a774e92b | ||
|
|
fc23d0c40d | ||
|
|
4a5f12aa29 | ||
|
|
c43e93d1b4 | ||
|
|
2771fc4c52 | ||
|
|
77a0cf27f2 | ||
|
|
5ec60effea | ||
|
|
326a66ba07 | ||
|
|
4b82673484 | ||
|
|
ab9a87c33a | ||
|
|
fca16fbe40 | ||
|
|
fa4c570d33 | ||
|
|
81ad713f5f | ||
|
|
36f76e5893 | ||
|
|
f74efc13ed | ||
|
|
b362072c83 | ||
|
|
2cb634bb0b | ||
|
|
9f94dd457a | ||
|
|
72694a722e | ||
|
|
09236ccbe7 | ||
|
|
28b9a6f69e | ||
|
|
902ffbbfb8 | ||
|
|
5006eca4a8 | ||
|
|
70feab9d50 | ||
|
|
915540d237 | ||
|
|
64c00dcd7a | ||
|
|
ba433be886 | ||
|
|
94e2e51876 | ||
|
|
548549ea9f | ||
|
|
19ddbac113 | ||
|
|
04fa09afef | ||
|
|
5d4140c513 | ||
|
|
7a23651ee1 | ||
|
|
d30ada61f0 | ||
|
|
9799c215cb | ||
|
|
3e8604d5d8 | ||
|
|
2e9d156fad | ||
|
|
c3e75393ab | ||
|
|
74f8055692 | ||
|
|
d1a82c4af1 | ||
|
|
a43987309d | ||
|
|
52f599a080 | ||
|
|
34c2157b66 | ||
|
|
ebd046ae21 | ||
|
|
a8cb47e011 | ||
|
|
9936a2406d | ||
|
|
691a1dafd1 | ||
|
|
0c61b49f0a | ||
|
|
b34a85f4da | ||
|
|
a65bbe14d3 | ||
|
|
be2cabc3f8 | ||
|
|
eb06659711 | ||
|
|
6357931b61 | ||
|
|
875ab073ea | ||
|
|
0d153562be | ||
|
|
31b63ade13 | ||
|
|
509e9c96e2 | ||
|
|
e1ce646275 | ||
|
|
e5f7200213 | ||
|
|
9f07b9a2b6 | ||
|
|
d5943b0e47 | ||
|
|
90dd7a13f0 | ||
|
|
d1b14ef062 | ||
|
|
47315af069 | ||
|
|
01151f8c7a | ||
|
|
4beef5bec3 | ||
|
|
9c4b9973f9 | ||
|
|
f732e46581 | ||
|
|
2cbcfcea88 |
+7
-5
@@ -9,11 +9,13 @@
|
||||
|
||||
# Ignore react-tools where there are overlaps, but don't ignore anything that
|
||||
# react-native relies on
|
||||
.*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js
|
||||
.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js
|
||||
.*/node_modules/react-tools/src/browser/ui/React.js
|
||||
.*/node_modules/react-tools/src/core/ReactInstanceHandles.js
|
||||
.*/node_modules/react-tools/src/event/EventPropagators.js
|
||||
.*/node_modules/react-tools/src/React.js
|
||||
.*/node_modules/react-tools/src/renderers/shared/reconciler/ReactInstanceHandles.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/renderers/shared/event/eventPlugins/ResponderSyntheticEvent.js
|
||||
.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderTouchHistoryStore.js
|
||||
.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/commoner/test/.*
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@ cache:
|
||||
before_install:
|
||||
- brew update
|
||||
install:
|
||||
- brew reinstall flow xctool nvm
|
||||
- brew reinstall xctool nvm
|
||||
- npm install -g flow-bin@`node -p "require('fs').readFileSync('.flowconfig', 'utf8').split('[version]')[1].trim()"`
|
||||
- mkdir -p .nvm
|
||||
- cp $(brew --prefix nvm)/nvm-exec .nvm/
|
||||
- export NVM_DIR=.nvm
|
||||
|
||||
+39
-41
@@ -24,7 +24,7 @@ var {
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var AnimationExperimental = require('AnimationExperimental');
|
||||
var Animated = require('Animated');
|
||||
var GameBoard = require('GameBoard');
|
||||
var TouchableBounce = require('TouchableBounce');
|
||||
|
||||
@@ -53,58 +53,58 @@ class Board extends React.Component {
|
||||
}
|
||||
|
||||
class Tile extends React.Component {
|
||||
static _getPosition(index): number {
|
||||
return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
|
||||
}
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
|
||||
var tile = this.props.tile;
|
||||
|
||||
this.state = {
|
||||
opacity: new Animated.Value(0),
|
||||
top: new Animated.Value(Tile._getPosition(tile.toRow())),
|
||||
left: new Animated.Value(Tile._getPosition(tile.toColumn())),
|
||||
};
|
||||
}
|
||||
|
||||
calculateOffset(): {top: number; left: number; opacity: number} {
|
||||
var tile = this.props.tile;
|
||||
|
||||
var pos = (i) => {
|
||||
return BOARD_PADDING + (i * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
|
||||
};
|
||||
|
||||
var animationPosition = (i) => {
|
||||
return pos(i) + (CELL_SIZE / 2);
|
||||
};
|
||||
|
||||
var offset = {
|
||||
top: pos(tile.toRow()),
|
||||
left: pos(tile.toColumn()),
|
||||
opacity: 1,
|
||||
top: this.state.top,
|
||||
left: this.state.left,
|
||||
opacity: this.state.opacity,
|
||||
};
|
||||
|
||||
if (tile.isNew()) {
|
||||
offset.opacity = 0;
|
||||
} else {
|
||||
var point = {
|
||||
x: animationPosition(tile.toColumn()),
|
||||
y: animationPosition(tile.toRow()),
|
||||
};
|
||||
AnimationExperimental.startAnimation({
|
||||
node: this.refs['this'],
|
||||
Animated.timing(this.state.opacity, {
|
||||
duration: 100,
|
||||
easing: 'easeInOutQuad',
|
||||
property: 'position',
|
||||
toValue: point,
|
||||
});
|
||||
toValue: 1,
|
||||
}).start();
|
||||
} else {
|
||||
Animated.parallel([
|
||||
Animated.timing(offset.top, {
|
||||
duration: 100,
|
||||
toValue: Tile._getPosition(tile.toRow()),
|
||||
}),
|
||||
Animated.timing(offset.left, {
|
||||
duration: 100,
|
||||
toValue: Tile._getPosition(tile.toColumn()),
|
||||
}),
|
||||
]).start();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AnimationExperimental.startAnimation({
|
||||
node: this.refs['this'],
|
||||
duration: 100,
|
||||
easing: 'easeInOutQuad',
|
||||
property: 'opacity',
|
||||
toValue: 1,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
var tile = this.props.tile;
|
||||
|
||||
var tileStyles = [
|
||||
styles.tile,
|
||||
styles['tile' + tile.value],
|
||||
this.calculateOffset()
|
||||
this.calculateOffset(),
|
||||
];
|
||||
|
||||
var textStyles = [
|
||||
@@ -115,9 +115,9 @@ class Tile extends React.Component {
|
||||
];
|
||||
|
||||
return (
|
||||
<View ref="this" style={tileStyles}>
|
||||
<Animated.View style={tileStyles}>
|
||||
<Text style={textStyles}>{tile.value}</Text>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -136,10 +136,8 @@ class GameEndOverlay extends React.Component {
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<Text style={styles.overlayMessage}>{message}</Text>
|
||||
<TouchableBounce onPress={this.props.onRestart}>
|
||||
<View style={styles.tryAgain}>
|
||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||
</View>
|
||||
<TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}>
|
||||
<Text style={styles.tryAgainText}>Try Again?</Text>
|
||||
</TouchableBounce>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -262,6 +262,7 @@ var SearchScreen = React.createClass({
|
||||
) {
|
||||
return (
|
||||
<MovieCell
|
||||
key={movie.id}
|
||||
onSelect={() => this.selectMovie(movie)}
|
||||
onHighlight={() => highlightRowFunc(sectionID, rowID)}
|
||||
onUnhighlight={() => highlightRowFunc(null, null)}
|
||||
@@ -307,7 +308,7 @@ var NoMovies = React.createClass({
|
||||
render: function() {
|
||||
var text = '';
|
||||
if (this.props.filter) {
|
||||
text = `No results for “${this.props.filter}”`;
|
||||
text = `No results for "${this.props.filter}"`;
|
||||
} else if (!this.props.isLoading) {
|
||||
// If we're looking at the latest movies, aren't currently loading, and
|
||||
// still have no results, show a message
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
|
||||
# Ignore react-tools where there are overlaps, but don't ignore anything that
|
||||
# react-native relies on
|
||||
.*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js
|
||||
.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js
|
||||
.*/node_modules/react-tools/src/browser/ui/React.js
|
||||
.*/node_modules/react-tools/src/core/ReactInstanceHandles.js
|
||||
.*/node_modules/react-tools/src/event/EventPropagators.js
|
||||
.*/node_modules/react-tools/src/React.js
|
||||
.*/node_modules/react-tools/src/renderers/shared/reconciler/ReactInstanceHandles.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/renderers/shared/event/eventPlugins/ResponderSyntheticEvent.js
|
||||
.*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderTouchHistoryStore.js
|
||||
.*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js
|
||||
|
||||
|
||||
# Ignore commoner tests
|
||||
.*/node_modules/commoner/test/.*
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Image,
|
||||
StyleSheet,
|
||||
View,
|
||||
ScrollView
|
||||
} = React;
|
||||
|
||||
var AssetScaledImageExample = React.createClass({
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
asset: this.props.asset
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
var image = this.state.asset.node.image;
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.row}>
|
||||
<Image source={image} style={styles.imageWide}/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Image source={image} style={styles.imageThumb}/>
|
||||
<Image source={image} style={styles.imageThumb}/>
|
||||
<Image source={image} style={styles.imageThumb}/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Image source={image} style={styles.imageT1}/>
|
||||
<Image source={image} style={styles.imageT2}/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
row: {
|
||||
padding: 5,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
textColumn: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
imageWide: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'black',
|
||||
width: 320,
|
||||
height: 240,
|
||||
margin: 5,
|
||||
},
|
||||
imageThumb: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'black',
|
||||
width: 100,
|
||||
height: 100,
|
||||
margin: 5,
|
||||
},
|
||||
imageT1: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'black',
|
||||
width: 212,
|
||||
height: 320,
|
||||
margin: 5,
|
||||
},
|
||||
imageT2: {
|
||||
borderWidth: 1,
|
||||
borderColor: 'black',
|
||||
width: 100,
|
||||
height: 320,
|
||||
margin: 5,
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<AssetScaledImageExample>';
|
||||
exports.description = 'Example component that displays the automatic scaling capabilities of the <Image /> tag';
|
||||
module.exports = AssetScaledImageExample;
|
||||
@@ -24,9 +24,11 @@ var {
|
||||
SwitchIOS,
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity
|
||||
} = React;
|
||||
|
||||
var CameraRollView = require('./CameraRollView.ios');
|
||||
var AssetScaledImageExampleView = require('./AssetScaledImageExample');
|
||||
|
||||
var CAMERA_ROLL_VIEW = 'camera_roll_view';
|
||||
|
||||
@@ -54,7 +56,7 @@ var CameraRollExample = React.createClass({
|
||||
<Text>{'Group Type: ' + this.state.groupTypes}</Text>
|
||||
<CameraRollView
|
||||
ref={CAMERA_ROLL_VIEW}
|
||||
batchSize={5}
|
||||
batchSize={20}
|
||||
groupTypes={this.state.groupTypes}
|
||||
renderImage={this._renderImage}
|
||||
/>
|
||||
@@ -62,24 +64,35 @@ var CameraRollExample = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
loadAsset(asset){
|
||||
this.props.navigator.push({
|
||||
title: 'Camera Roll Image',
|
||||
component: AssetScaledImageExampleView,
|
||||
backButtonTitle: 'Back',
|
||||
passProps: { asset: asset },
|
||||
});
|
||||
},
|
||||
|
||||
_renderImage(asset) {
|
||||
var imageSize = this.state.bigImages ? 150 : 75;
|
||||
var imageStyle = [styles.image, {width: imageSize, height: imageSize}];
|
||||
var location = asset.node.location.longitude ?
|
||||
JSON.stringify(asset.node.location) : 'Unknown location';
|
||||
return (
|
||||
<View key={asset} style={styles.row}>
|
||||
<Image
|
||||
source={asset.node.image}
|
||||
style={imageStyle}
|
||||
/>
|
||||
<View style={styles.info}>
|
||||
<Text style={styles.url}>{asset.node.image.uri}</Text>
|
||||
<Text>{location}</Text>
|
||||
<Text>{asset.node.group_name}</Text>
|
||||
<Text>{new Date(asset.node.timestamp).toString()}</Text>
|
||||
<TouchableOpacity onPress={ this.loadAsset.bind( this, asset ) }>
|
||||
<View key={asset} style={styles.row}>
|
||||
<Image
|
||||
source={asset.node.image}
|
||||
style={imageStyle}
|
||||
/>
|
||||
<View style={styles.info}>
|
||||
<Text style={styles.url}>{asset.node.image.uri}</Text>
|
||||
<Text>{location}</Text>
|
||||
<Text>{asset.node.group_name}</Text>
|
||||
<Text>{new Date(asset.node.timestamp).toString()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -115,7 +128,7 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<CameraRollView>';
|
||||
exports.title = 'Camera Roll';
|
||||
exports.description = 'Example component that uses CameraRoll to list user\'s photos';
|
||||
exports.examples = [
|
||||
{
|
||||
|
||||
@@ -21,10 +21,41 @@ var {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
ActivityIndicatorIOS
|
||||
} = React;
|
||||
|
||||
var ImageCapInsetsExample = require('./ImageCapInsetsExample');
|
||||
|
||||
var NetworkImageExample = React.createClass({
|
||||
watchID: (null: ?number),
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
error: false,
|
||||
loading: false,
|
||||
progress: 0
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
var loader = this.state.loading ?
|
||||
<View style={styles.progress}>
|
||||
<Text>{this.state.progress}%</Text>
|
||||
<ActivityIndicatorIOS style={{marginLeft:5}}/>
|
||||
</View> : null;
|
||||
return this.state.error ?
|
||||
<Text>{this.state.error}</Text> :
|
||||
<Image
|
||||
source={this.props.source}
|
||||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadStart={(e) => this.setState({loading: true})}
|
||||
onError={(e) => this.setState({error: e.nativeEvent.error, loading: false})}
|
||||
onProgress={(e) => this.setState({progress: Math.round(100 * e.nativeEvent.loaded / e.nativeEvent.total)})}
|
||||
onLoad={() => this.setState({loading: false, error: false})}>
|
||||
{loader}
|
||||
</Image>;
|
||||
}
|
||||
});
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
exports.framework = 'React';
|
||||
exports.title = '<Image>';
|
||||
@@ -59,6 +90,22 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Error Handler',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png'}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Image Download Progress',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Border Color',
|
||||
render: function() {
|
||||
@@ -194,23 +241,46 @@ exports.examples = [
|
||||
'pixels to the tint color.',
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, {tintColor: 'blue' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, styles.leftMargin, {tintColor: 'green' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, styles.leftMargin, {tintColor: 'red' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, styles.leftMargin, {tintColor: 'black' }]}
|
||||
/>
|
||||
<View>
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, {tintColor: '#5ac8fa' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, styles.leftMargin, {tintColor: '#4cd964' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, styles.leftMargin, {tintColor: '#ff2d55' }]}
|
||||
/>
|
||||
<Image
|
||||
source={require('image!uie_thumb_normal')}
|
||||
style={[styles.icon, styles.leftMargin, {tintColor: '#8e8e93' }]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.sectionText}>
|
||||
It also works with downloaded images:
|
||||
</Text>
|
||||
<View style={styles.horizontal}>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, {tintColor: '#5ac8fa' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {tintColor: '#4cd964' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {tintColor: '#ff2d55' }]}
|
||||
/>
|
||||
<Image
|
||||
source={smallImage}
|
||||
style={[styles.base, styles.leftMargin, {tintColor: '#8e8e93' }]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
@@ -277,12 +347,21 @@ var styles = StyleSheet.create({
|
||||
width: 38,
|
||||
height: 38,
|
||||
},
|
||||
progress: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
width: 100
|
||||
},
|
||||
leftMargin: {
|
||||
marginLeft: 10,
|
||||
},
|
||||
background: {
|
||||
backgroundColor: '#222222'
|
||||
},
|
||||
sectionText: {
|
||||
marginVertical: 6,
|
||||
},
|
||||
nestedText: {
|
||||
marginLeft: 12,
|
||||
marginTop: 20,
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Image,
|
||||
ListView,
|
||||
TouchableHighlight,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var THUMB_URLS = [
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851549_767334479959628_274486868_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851579_767334503292959_179092627_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851589_767334513292958_1747022277_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851563_767334559959620_1193692107_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851593_767334566626286_1953955109_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851591_767334523292957_797560749_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851567_767334529959623_843148472_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851548_767334489959627_794462220_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851575_767334539959622_441598241_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851573_767334549959621_534583464_n.png',
|
||||
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851583_767334573292952_1519550680_n.png',
|
||||
];
|
||||
|
||||
var ListViewGridLayoutExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: '<ListView> - Grid Layout',
|
||||
description: 'Flexbox grid layout.'
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
|
||||
return {
|
||||
dataSource: ds.cloneWithRows(this._genRows({})),
|
||||
};
|
||||
},
|
||||
|
||||
_pressData: ({}: {[key: number]: boolean}),
|
||||
|
||||
componentWillMount: function() {
|
||||
this._pressData = {};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
// ListView wraps ScrollView and so takes on its properties.
|
||||
// With that in mind you can use the ScrollView's contentContainerStyle prop to style the items.
|
||||
<ListView
|
||||
contentContainerStyle={styles.list}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this._renderRow}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
_renderRow: function(rowData: string, sectionID: number, rowID: number) {
|
||||
var rowHash = Math.abs(hashCode(rowData));
|
||||
var imgSource = {
|
||||
uri: THUMB_URLS[rowHash % THUMB_URLS.length],
|
||||
};
|
||||
return (
|
||||
<TouchableHighlight onPress={() => this._pressRow(rowID)} underlayColor="transparent">
|
||||
<View>
|
||||
<View style={styles.row}>
|
||||
<Image style={styles.thumb} source={imgSource} />
|
||||
<Text style={styles.text}>
|
||||
{rowData}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
},
|
||||
|
||||
_genRows: function(pressData: {[key: number]: boolean}): Array<string> {
|
||||
var dataBlob = [];
|
||||
for (var ii = 0; ii < 100; ii++) {
|
||||
var pressedText = pressData[ii] ? ' (X)' : '';
|
||||
dataBlob.push('Cell ' + ii + pressedText);
|
||||
}
|
||||
return dataBlob;
|
||||
},
|
||||
|
||||
_pressRow: function(rowID: number) {
|
||||
this._pressData[rowID] = !this._pressData[rowID];
|
||||
this.setState({dataSource: this.state.dataSource.cloneWithRows(
|
||||
this._genRows(this._pressData)
|
||||
)});
|
||||
},
|
||||
});
|
||||
|
||||
/* eslint no-bitwise: 0 */
|
||||
var hashCode = function(str) {
|
||||
var hash = 15;
|
||||
for (var ii = str.length - 1; ii >= 0; ii--) {
|
||||
hash = ((hash << 5) - hash) + str.charCodeAt(ii);
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
list: {
|
||||
justifyContent: 'space-around',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
},
|
||||
row: {
|
||||
justifyContent: 'center',
|
||||
padding: 5,
|
||||
margin: 3,
|
||||
width: 100,
|
||||
height: 100,
|
||||
backgroundColor: '#F6F6F6',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
borderColor: '#CCC'
|
||||
},
|
||||
thumb: {
|
||||
width: 64,
|
||||
height: 64
|
||||
},
|
||||
text: {
|
||||
flex: 1,
|
||||
marginTop: 5,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = ListViewGridLayoutExample;
|
||||
@@ -48,19 +48,19 @@ var Thumb = React.createClass({
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<TouchableOpacity onPress={this._onPressThumb}>
|
||||
<View style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
{this.state.dir === 'column' ?
|
||||
<Text>
|
||||
Oooo, look at this new text! So awesome it may just be crazy.
|
||||
Let me keep typing here so it wraps at least one line.
|
||||
</Text> :
|
||||
<Text />
|
||||
}
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={this._onPressThumb}
|
||||
style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
<Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
|
||||
{this.state.dir === 'column' ?
|
||||
<Text>
|
||||
Oooo, look at this new text! So awesome it may just be crazy.
|
||||
Let me keep typing here so it wraps at least one line.
|
||||
</Text> :
|
||||
<Text />
|
||||
}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
@@ -127,14 +127,12 @@ var ListViewPagingExample = React.createClass({
|
||||
<View><Text style={styles.text}>1 Like</Text></View> :
|
||||
null;
|
||||
return (
|
||||
<TouchableOpacity onPress={this._onPressHeader}>
|
||||
<View style={styles.header}>
|
||||
{headerLikeText}
|
||||
<View>
|
||||
<Text style={styles.text}>
|
||||
Table Header (click me)
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={this._onPressHeader} style={styles.header}>
|
||||
{headerLikeText}
|
||||
<View>
|
||||
<Text style={styles.text}>
|
||||
Table Header (click me)
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
@@ -55,26 +55,24 @@ var BreadcrumbNavSample = React.createClass({
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(_getRandomRoute())}>
|
||||
<View>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</View>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
iconForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity onPress={() => {
|
||||
navigator.popToRoute(route);
|
||||
}}>
|
||||
<View style={styles.crumbIconPlaceholder} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => { navigator.popToRoute(route); }}
|
||||
style={styles.crumbIconPlaceholder}
|
||||
/>
|
||||
);
|
||||
},
|
||||
separatorForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableOpacity onPress={navigator.pop}>
|
||||
<View style={styles.crumbSeparatorPlaceholder} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={navigator.pop}
|
||||
style={styles.crumbSeparatorPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,12 +51,11 @@ var NavigationBarRouteMapper = {
|
||||
var previousRoute = navState.routeStack[index - 1];
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.pop()}>
|
||||
<View style={styles.navBarLeftButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
{previousRoute.title}
|
||||
</Text>
|
||||
</View>
|
||||
onPress={() => navigator.pop()}
|
||||
style={styles.navBarLeftButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
{previousRoute.title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
@@ -64,12 +63,11 @@ var NavigationBarRouteMapper = {
|
||||
RightButton: function(route, navigator, index, navState) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(newRandomRoute())}>
|
||||
<View style={styles.navBarRightButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
Next
|
||||
</Text>
|
||||
</View>
|
||||
onPress={() => navigator.push(newRandomRoute())}
|
||||
style={styles.navBarRightButton}>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
Next
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -83,6 +83,26 @@ exports.examples = [{
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Status Bar Network Activity Indicator',
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => StatusBarIOS.setNetworkActivityIndicatorVisible(true)}>
|
||||
<View style={styles.button}>
|
||||
<Text>setNetworkActivityIndicatorVisible(true)</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.wrapper}
|
||||
onPress={() => StatusBarIOS.setNetworkActivityIndicatorVisible(false)}>
|
||||
<View style={styles.button}>
|
||||
<Text>setNetworkActivityIndicatorVisible(false)</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
|
||||
@@ -39,11 +39,11 @@ var TabBarExample = React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
_renderContent: function(color: string, pageText: string) {
|
||||
_renderContent: function(color: string, pageText: string, num?: number) {
|
||||
return (
|
||||
<View style={[styles.tabContent, {backgroundColor: color}]}>
|
||||
<Text style={styles.tabText}>{pageText}</Text>
|
||||
<Text style={styles.tabText}>{this.state.presses} re-renders of the More tab</Text>
|
||||
<Text style={styles.tabText}>{num} re-renders of the {pageText}</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
@@ -51,8 +51,8 @@ var TabBarExample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<TabBarIOS
|
||||
tintColor="black"
|
||||
barTintColor="#3abeff">
|
||||
tintColor="white"
|
||||
barTintColor="darkslateblue">
|
||||
<TabBarIOS.Item
|
||||
title="Blue Tab"
|
||||
selected={this.state.selectedTab === 'blueTab'}
|
||||
@@ -73,7 +73,7 @@ var TabBarExample = React.createClass({
|
||||
notifCount: this.state.notifCount + 1,
|
||||
});
|
||||
}}>
|
||||
{this._renderContent('#783E33', 'Red Tab')}
|
||||
{this._renderContent('#783E33', 'Red Tab', this.state.notifCount)}
|
||||
</TabBarIOS.Item>
|
||||
<TabBarIOS.Item
|
||||
systemIcon="more"
|
||||
@@ -84,7 +84,7 @@ var TabBarExample = React.createClass({
|
||||
presses: this.state.presses + 1
|
||||
});
|
||||
}}>
|
||||
{this._renderContent('#21551C', 'Green Tab')}
|
||||
{this._renderContent('#21551C', 'Green Tab', this.state.presses)}
|
||||
</TabBarIOS.Item>
|
||||
</TabBarIOS>
|
||||
);
|
||||
|
||||
@@ -269,7 +269,7 @@ exports.examples = [
|
||||
right right right right right right right right right right right right right
|
||||
</Text>
|
||||
<Text style={{textAlign: 'justify'}}>
|
||||
justify: this text component's contents are laid out with "textAlign: justify"
|
||||
justify: this text component{"'"}s contents are laid out with "textAlign: justify"
|
||||
and as you can see all of the lines except the last one span the
|
||||
available width of the parent container.
|
||||
</Text>
|
||||
@@ -369,6 +369,15 @@ exports.examples = [
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}, {
|
||||
title: 'Text highlighting (tap the link to see highlight)',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<Text>Lorem ipsum dolor sit amet, <Text suppressHighlighting={false} style={{backgroundColor: 'white', textDecorationLine: 'underline', color: 'blue'}} onPress={() => null}>consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud</Text> exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}];
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
|
||||
@@ -33,7 +33,7 @@ var WithLabel = React.createClass({
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var TextEventsExample = React.createClass({
|
||||
@@ -41,13 +41,17 @@ var TextEventsExample = React.createClass({
|
||||
return {
|
||||
curText: '<No Event>',
|
||||
prevText: '<No Event>',
|
||||
prev2Text: '<No Event>',
|
||||
};
|
||||
},
|
||||
|
||||
updateText: function(text) {
|
||||
this.setState({
|
||||
curText: text,
|
||||
prevText: this.state.curText,
|
||||
this.setState((state) => {
|
||||
return {
|
||||
curText: text,
|
||||
prevText: state.curText,
|
||||
prev2Text: state.prevText,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -73,13 +77,43 @@ var TextEventsExample = React.createClass({
|
||||
/>
|
||||
<Text style={styles.eventLabel}>
|
||||
{this.state.curText}{'\n'}
|
||||
(prev: {this.state.prevText})
|
||||
(prev: {this.state.prevText}){'\n'}
|
||||
(prev2: {this.state.prev2Text})
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
class RewriteExample extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {text: ''};
|
||||
}
|
||||
render() {
|
||||
var limit = 20;
|
||||
var remainder = limit - this.state.text.length;
|
||||
var remainderColor = remainder > 5 ? 'blue' : 'red';
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
page: {
|
||||
paddingBottom: 300,
|
||||
@@ -125,12 +159,19 @@ var styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
},
|
||||
label: {
|
||||
width: 120,
|
||||
justifyContent: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
width: 115,
|
||||
alignItems: 'flex-end',
|
||||
marginRight: 10,
|
||||
paddingTop: 2,
|
||||
},
|
||||
rewriteContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
remainder: {
|
||||
textAlign: 'right',
|
||||
width: 24,
|
||||
},
|
||||
});
|
||||
|
||||
exports.displayName = (undefined: ?string);
|
||||
@@ -143,6 +184,12 @@ exports.examples = [
|
||||
return <TextInput autoFocus={true} style={styles.default} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Live Re-Write (<sp> -> '_') + maxLength",
|
||||
render: function() {
|
||||
return <RewriteExample />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Auto-capitalize',
|
||||
render: function() {
|
||||
@@ -268,7 +315,7 @@ exports.examples = [
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="true">
|
||||
<TextInput password={true} style={styles.default} value="abc" />
|
||||
<TextInput password={true} style={styles.default} defaultValue="abc" />
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
@@ -276,7 +323,7 @@ exports.examples = [
|
||||
},
|
||||
{
|
||||
title: 'Event handling',
|
||||
render: function(): ReactElement { return <TextEventsExample /> },
|
||||
render: function(): ReactElement { return <TextEventsExample />; },
|
||||
},
|
||||
{
|
||||
title: 'Colored input text',
|
||||
@@ -285,11 +332,11 @@ exports.examples = [
|
||||
<View>
|
||||
<TextInput
|
||||
style={[styles.default, {color: 'blue'}]}
|
||||
value="Blue"
|
||||
defaultValue="Blue"
|
||||
/>
|
||||
<TextInput
|
||||
style={[styles.default, {color: 'green'}]}
|
||||
value="Green"
|
||||
defaultValue="Green"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -336,7 +383,7 @@ exports.examples = [
|
||||
<WithLabel label="clearTextOnFocus">
|
||||
<TextInput
|
||||
placeholder="text is cleared on focus"
|
||||
value="text is cleared on focus"
|
||||
defaultValue="text is cleared on focus"
|
||||
style={styles.default}
|
||||
clearTextOnFocus={true}
|
||||
/>
|
||||
@@ -344,7 +391,7 @@ exports.examples = [
|
||||
<WithLabel label="selectTextOnFocus">
|
||||
<TextInput
|
||||
placeholder="text is selected on focus"
|
||||
value="text is selected on focus"
|
||||
defaultValue="text is selected on focus"
|
||||
style={styles.default}
|
||||
selectTextOnFocus={true}
|
||||
/>
|
||||
@@ -364,7 +411,7 @@ exports.examples = [
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="mutliline text input with font styles and placeholder"
|
||||
placeholder="multiline text input with font styles and placeholder"
|
||||
multiline={true}
|
||||
clearTextOnFocus={true}
|
||||
autoCorrect={true}
|
||||
@@ -374,7 +421,7 @@ exports.examples = [
|
||||
style={[styles.multiline, styles.multilineWithFontStyles]}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="uneditable mutliline text input"
|
||||
placeholder="uneditable multiline text input"
|
||||
editable={false}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
|
||||
@@ -1,61 +1,85 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* @providesModule TransformExample
|
||||
* 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');
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var UIExplorerBlock = require('./UIExplorerBlock');
|
||||
var UIExplorerPage = require('./UIExplorerPage');
|
||||
|
||||
var TransformExample = React.createClass({
|
||||
|
||||
mixins: [TimerMixin],
|
||||
|
||||
var Flip = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
interval: this.setInterval(this._update, 800),
|
||||
pulse: false,
|
||||
theta: new Animated.Value(45),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this._animate();
|
||||
},
|
||||
|
||||
_animate() {
|
||||
this.state.theta.setValue(0);
|
||||
Animated.timing(this.state.theta, {
|
||||
toValue: 360,
|
||||
duration: 5000,
|
||||
}).start(this._animate);
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UIExplorerPage title="Transforms">
|
||||
<UIExplorerBlock title="foo bar">
|
||||
<View style={{height: 500}}>
|
||||
<View style={styles.box1} />
|
||||
<View style={styles.box2} />
|
||||
<View style={styles.box3step1} />
|
||||
<View style={styles.box3step2} />
|
||||
<View style={styles.box3step3} />
|
||||
<View style={styles.box4} />
|
||||
<View style={[
|
||||
styles.box5,
|
||||
this.state.pulse ? styles.box5Transform : null
|
||||
]} />
|
||||
</View>
|
||||
</UIExplorerBlock>
|
||||
</UIExplorerPage>
|
||||
<View style={styles.flipCardContainer}>
|
||||
<Animated.View style={[
|
||||
styles.flipCard,
|
||||
{transform: [
|
||||
{perspective: 850},
|
||||
{rotateX: this.state.theta.interpolate({
|
||||
inputRange: [0, 180],
|
||||
outputRange: ['0deg', '180deg']
|
||||
})},
|
||||
]}]}>
|
||||
<Text style={styles.flipText}>
|
||||
This text is flipping great.
|
||||
</Text>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.flipCard, {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
backgroundColor: 'red',
|
||||
transform: [
|
||||
{perspective: 850},
|
||||
{rotateX: this.state.theta.interpolate({
|
||||
inputRange: [0, 180],
|
||||
outputRange: ['180deg', '360deg']
|
||||
})},
|
||||
]}]}>
|
||||
<Text style={styles.flipText}>
|
||||
On the flip side...
|
||||
</Text>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_update() {
|
||||
this.setState({
|
||||
pulse: !this.state.pulse,
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
height: 500,
|
||||
},
|
||||
box1: {
|
||||
left: 0,
|
||||
backgroundColor: 'green',
|
||||
@@ -88,7 +112,7 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
box3step1: {
|
||||
left: 0,
|
||||
backgroundColor: '#ffb6c1', // lightpink
|
||||
backgroundColor: 'lightpink',
|
||||
height: 50,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@@ -99,7 +123,7 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
box3step2: {
|
||||
left: 0,
|
||||
backgroundColor: '#ff69b4', //hotpink
|
||||
backgroundColor: 'hotpink',
|
||||
height: 50,
|
||||
opacity: 0.5,
|
||||
position: 'absolute',
|
||||
@@ -113,7 +137,7 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
box3step3: {
|
||||
left: 0,
|
||||
backgroundColor: '#ff1493', // deeppink
|
||||
backgroundColor: 'deeppink',
|
||||
height: 50,
|
||||
opacity: 0.5,
|
||||
position: 'absolute',
|
||||
@@ -129,7 +153,7 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
box4: {
|
||||
left: 0,
|
||||
backgroundColor: '#ff8c00', // darkorange
|
||||
backgroundColor: 'darkorange',
|
||||
height: 50,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@@ -141,7 +165,7 @@ var styles = StyleSheet.create({
|
||||
width: 100,
|
||||
},
|
||||
box5: {
|
||||
backgroundColor: '#800000', // maroon
|
||||
backgroundColor: 'maroon',
|
||||
height: 50,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
@@ -155,7 +179,110 @@ var styles = StyleSheet.create({
|
||||
{scale: 2},
|
||||
],
|
||||
},
|
||||
flipCardContainer: {
|
||||
marginVertical: 40,
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
flipCard: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'blue',
|
||||
backfaceVisibility: 'hidden',
|
||||
},
|
||||
flipText: {
|
||||
width: 90,
|
||||
fontSize: 20,
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = TransformExample;
|
||||
exports.title = 'Transforms';
|
||||
exports.description = 'View transforms';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Perspective',
|
||||
description: 'perspective: 850, rotateX: Animated.timing(0 -> 360)',
|
||||
render(): ReactElement { return <Flip />; }
|
||||
},
|
||||
{
|
||||
title: 'Translate, Rotate, Scale',
|
||||
description: "translateX: 100, translateY: 50, rotate: '30deg', scaleX: 2, scaleY: 2",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.box1} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Scale, Translate, Rotate, ',
|
||||
description: "scaleX: 2, scaleY: 2, translateX: 100, translateY: 50, rotate: '30deg'",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.box2} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate',
|
||||
description: "rotate: '30deg'",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.box3step1} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate, Scale',
|
||||
description: "rotate: '30deg', scaleX: 2, scaleY: 2",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.box3step2} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rotate, Scale, Translate ',
|
||||
description: "rotate: '30deg', scaleX: 2, scaleY: 2, translateX: 100, translateY: 50",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.box3step3} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Translate, Scale, Rotate',
|
||||
description: "translate: [200, 350], scale: 2.5, rotate: '-0.2rad'",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.box4} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Translate, Rotate, Scale',
|
||||
description: "translate: [-50, 35], rotate: '50deg', scale: 2",
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.box5, styles.box5Transform]} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -7,19 +7,23 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; };
|
||||
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; };
|
||||
134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; };
|
||||
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
|
||||
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
|
||||
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
|
||||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; };
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
|
||||
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
|
||||
141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; };
|
||||
143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
|
||||
144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClippingTests.m */; };
|
||||
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClipRectTests.m */; };
|
||||
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
|
||||
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */; };
|
||||
1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */; };
|
||||
@@ -47,6 +51,7 @@
|
||||
14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
|
||||
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
|
||||
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
|
||||
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
|
||||
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -153,11 +158,14 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = "<group>"; };
|
||||
13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; };
|
||||
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; };
|
||||
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
|
||||
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
|
||||
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
|
||||
138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; };
|
||||
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
|
||||
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = "<group>"; };
|
||||
@@ -167,6 +175,7 @@
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = UIExplorer/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
|
||||
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
|
||||
141FC1201B222EBB004D5FFB /* IntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationTests.m; sourceTree = "<group>"; };
|
||||
143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
|
||||
@@ -178,7 +187,7 @@
|
||||
143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
143BC5981B21E3E100462512 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerSnapshotTests.m; sourceTree = "<group>"; };
|
||||
144D21231B2204C5006DB32B /* RCTClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClippingTests.m; sourceTree = "<group>"; };
|
||||
144D21231B2204C5006DB32B /* RCTClipRectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClipRectTests.m; sourceTree = "<group>"; };
|
||||
1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAllocationTests.m; sourceTree = "<group>"; };
|
||||
1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeTests.m; sourceTree = "<group>"; };
|
||||
1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutorTests.m; sourceTree = "<group>"; };
|
||||
@@ -202,6 +211,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -342,11 +352,15 @@
|
||||
children = (
|
||||
1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */,
|
||||
1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */,
|
||||
144D21231B2204C5006DB32B /* RCTClippingTests.m */,
|
||||
138D6A151B53CD440074A87E /* RCTCacheTests.m */,
|
||||
144D21231B2204C5006DB32B /* RCTClipRectTests.m */,
|
||||
1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */,
|
||||
1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
|
||||
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
|
||||
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
|
||||
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
|
||||
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
|
||||
1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
|
||||
1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
|
||||
143BC57E1B21E18100462512 /* Info.plist */,
|
||||
@@ -383,6 +397,7 @@
|
||||
children = (
|
||||
141FC1201B222EBB004D5FFB /* IntegrationTests.m */,
|
||||
143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */,
|
||||
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */,
|
||||
143BC5971B21E3E100462512 /* Supporting Files */,
|
||||
);
|
||||
path = UIExplorerIntegrationTests;
|
||||
@@ -772,14 +787,18 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */,
|
||||
144D21241B2204C5006DB32B /* RCTClippingTests.m in Sources */,
|
||||
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */,
|
||||
1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */,
|
||||
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
|
||||
1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
|
||||
1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */,
|
||||
1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */,
|
||||
1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */,
|
||||
1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */,
|
||||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
|
||||
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -797,6 +816,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */,
|
||||
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */,
|
||||
143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1014,9 +1034,11 @@
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_STRICT_SELECTOR_MATCH = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
@@ -1028,6 +1050,7 @@
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../React/**",
|
||||
"$(SRCROOT)/../../Libraries/**",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
@@ -1068,9 +1091,11 @@
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_STRICT_SELECTOR_MATCH = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
@@ -1082,6 +1107,7 @@
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../React/**",
|
||||
"$(SRCROOT)/../../Libraries/**",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
* Load from pre-bundled file on disk. To re-generate the static bundle, `cd`
|
||||
* to your Xcode project folder and run
|
||||
*
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
* $ curl 'http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.includeRequire.runModule.bundle' -o main.jsbundle
|
||||
*
|
||||
* then add the `main.jsbundle` file to your project and uncomment this line:
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var UIExplorerList = require('./UIExplorerList');
|
||||
var UIExplorerList = require('./UIExplorerList.ios');
|
||||
var {
|
||||
AppRegistry,
|
||||
NavigatorIOS,
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@interface RCTUIManager (Testing)
|
||||
|
||||
- (void)_manageChildren:(NSNumber *)containerReactTag
|
||||
moveFromIndices:(NSArray *)moveFromIndices
|
||||
moveToIndices:(NSArray *)moveToIndices
|
||||
addChildReactTags:(NSArray *)addChildReactTags
|
||||
addAtIndices:(NSArray *)addAtIndices
|
||||
removeAtIndices:(NSArray *)removeAtIndices
|
||||
registry:(RCTSparseArray *)registry;
|
||||
|
||||
@property (nonatomic, readonly) RCTSparseArray *viewRegistry;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTUIManagerScenarioTests : XCTestCase
|
||||
|
||||
@property (nonatomic, readwrite, strong) RCTUIManager *uiManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTUIManagerScenarioTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_uiManager = [[RCTUIManager alloc] init];
|
||||
|
||||
// Register 20 views to use in the tests
|
||||
for (NSInteger i = 1; i <= 20; i++) {
|
||||
UIView *registeredView = [[UIView alloc] init];
|
||||
[registeredView setReactTag:@(i)];
|
||||
_uiManager.viewRegistry[i] = registeredView;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testManagingChildrenToAddViews
|
||||
{
|
||||
UIView *containerView = _uiManager.viewRegistry[20];
|
||||
NSMutableArray *addedViews = [NSMutableArray array];
|
||||
|
||||
NSArray *tagsToAdd = @[@1, @2, @3, @4, @5];
|
||||
NSArray *addAtIndices = @[@0, @1, @2, @3, @4];
|
||||
for (NSNumber *tag in tagsToAdd) {
|
||||
[addedViews addObject:_uiManager.viewRegistry[tag]];
|
||||
}
|
||||
|
||||
// Add views 1-5 to view 20
|
||||
[_uiManager _manageChildren:@20
|
||||
moveFromIndices:nil
|
||||
moveToIndices:nil
|
||||
addChildReactTags:tagsToAdd
|
||||
addAtIndices:addAtIndices
|
||||
removeAtIndices:nil
|
||||
registry:_uiManager.viewRegistry];
|
||||
|
||||
XCTAssertTrue([[containerView reactSubviews] count] == 5,
|
||||
@"Expect to have 5 react subviews after calling manage children \
|
||||
with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]);
|
||||
for (UIView *view in addedViews) {
|
||||
XCTAssertTrue([view superview] == containerView,
|
||||
@"Expected to have manage children successfully add children");
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testManagingChildrenToRemoveViews
|
||||
{
|
||||
UIView *containerView = _uiManager.viewRegistry[20];
|
||||
NSMutableArray *removedViews = [NSMutableArray array];
|
||||
|
||||
NSArray *removeAtIndices = @[@0, @4, @8, @12, @16];
|
||||
for (NSNumber *index in removeAtIndices) {
|
||||
NSNumber *reactTag = @([index integerValue] + 2);
|
||||
[removedViews addObject:_uiManager.viewRegistry[reactTag]];
|
||||
}
|
||||
for (NSInteger i = 2; i < 20; i++) {
|
||||
UIView *view = _uiManager.viewRegistry[i];
|
||||
[containerView addSubview:view];
|
||||
}
|
||||
|
||||
// Remove views 1-5 from view 20
|
||||
[_uiManager _manageChildren:@20
|
||||
moveFromIndices:nil
|
||||
moveToIndices:nil
|
||||
addChildReactTags:nil
|
||||
addAtIndices:nil
|
||||
removeAtIndices:removeAtIndices
|
||||
registry:_uiManager.viewRegistry];
|
||||
|
||||
XCTAssertEqual(containerView.reactSubviews.count, (NSUInteger)13,
|
||||
@"Expect to have 13 react subviews after calling manage children\
|
||||
with 5 tags to remove and 18 prior children, instead have %zd",
|
||||
containerView.reactSubviews.count);
|
||||
for (UIView *view in removedViews) {
|
||||
XCTAssertTrue([view superview] == nil,
|
||||
@"Expected to have manage children successfully remove children");
|
||||
// After removing views are unregistered - we need to reregister
|
||||
_uiManager.viewRegistry[view.reactTag] = view;
|
||||
}
|
||||
for (NSInteger i = 2; i < 20; i++) {
|
||||
UIView *view = _uiManager.viewRegistry[i];
|
||||
if (![removedViews containsObject:view]) {
|
||||
XCTAssertTrue([view superview] == containerView,
|
||||
@"Should not have removed view with react tag %ld during delete but did", (long)i);
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We want to start with views 1-10 added at indices 0-9
|
||||
// Then we'll remove indices 2, 3, 5 and 8
|
||||
// Add views 11 and 12 to indices 0 and 6
|
||||
// And move indices 4 and 9 to 1 and 7
|
||||
// So in total it goes from:
|
||||
// [1,2,3,4,5,6,7,8,9,10]
|
||||
// to
|
||||
// [11,5,1,2,7,8,12,10]
|
||||
- (void)testManagingChildrenToAddRemoveAndMove
|
||||
{
|
||||
UIView *containerView = _uiManager.viewRegistry[20];
|
||||
|
||||
NSArray *removeAtIndices = @[@2, @3, @5, @8];
|
||||
NSArray *addAtIndices = @[@0, @6];
|
||||
NSArray *tagsToAdd = @[@11, @12];
|
||||
NSArray *moveFromIndices = @[@4, @9];
|
||||
NSArray *moveToIndices = @[@1, @7];
|
||||
|
||||
// We need to keep these in array to keep them around
|
||||
NSMutableArray *viewsToRemove = [NSMutableArray array];
|
||||
for (NSUInteger i = 0; i < removeAtIndices.count; i++) {
|
||||
NSNumber *reactTagToRemove = @([removeAtIndices[i] integerValue] + 1);
|
||||
UIView *viewToRemove = _uiManager.viewRegistry[reactTagToRemove];
|
||||
[viewsToRemove addObject:viewToRemove];
|
||||
}
|
||||
|
||||
for (NSInteger i = 1; i < 11; i++) {
|
||||
UIView *view = _uiManager.viewRegistry[i];
|
||||
[containerView addSubview:view];
|
||||
}
|
||||
|
||||
[_uiManager _manageChildren:@20
|
||||
moveFromIndices:moveFromIndices
|
||||
moveToIndices:moveToIndices
|
||||
addChildReactTags:tagsToAdd
|
||||
addAtIndices:addAtIndices
|
||||
removeAtIndices:removeAtIndices
|
||||
registry:_uiManager.viewRegistry];
|
||||
|
||||
XCTAssertTrue([[containerView reactSubviews] count] == 8,
|
||||
@"Expect to have 8 react subviews after calling manage children,\
|
||||
instead have the following subviews %@", [containerView reactSubviews]);
|
||||
|
||||
NSArray *expectedReactTags = @[@11, @5, @1, @2, @7, @8, @12, @10];
|
||||
for (NSUInteger i = 0; i < containerView.subviews.count; i++) {
|
||||
XCTAssertEqualObjects([[containerView reactSubviews][i] reactTag], expectedReactTags[i],
|
||||
@"Expected subview at index %ld to have react tag #%@ but has tag #%@",
|
||||
(long)i, expectedReactTags[i], [[containerView reactSubviews][i] reactTag]);
|
||||
}
|
||||
|
||||
// Clean up after ourselves
|
||||
for (NSInteger i = 1; i < 13; i++) {
|
||||
UIView *view = _uiManager.viewRegistry[i];
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
for (UIView *view in viewsToRemove) {
|
||||
_uiManager.viewRegistry[view.reactTag] = view;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
@@ -59,12 +59,12 @@ var IntegrationTestsApp = React.createClass({
|
||||
<View style={styles.separator} />
|
||||
<ScrollView>
|
||||
{TESTS.map((test) => [
|
||||
<TouchableOpacity onPress={() => this.setState({test})}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.testName}>
|
||||
{test.displayName}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={() => this.setState({test})}
|
||||
style={styles.row}>
|
||||
<Text style={styles.testName}>
|
||||
{test.displayName}
|
||||
</Text>
|
||||
</TouchableOpacity>,
|
||||
<View style={styles.separator} />
|
||||
])}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
AppRegistry,
|
||||
Settings,
|
||||
StyleSheet,
|
||||
} = React;
|
||||
|
||||
var { TestModule } = React.addons;
|
||||
|
||||
import type { NavigationContext } from 'NavigationContext';
|
||||
|
||||
var UIExplorerListBase = require('./UIExplorerListBase');
|
||||
|
||||
var COMPONENTS = [
|
||||
require('./ActivityIndicatorIOSExample'),
|
||||
require('./DatePickerIOSExample'),
|
||||
require('./ImageExample'),
|
||||
require('./LayoutEventsExample'),
|
||||
require('./ListViewExample'),
|
||||
require('./ListViewGridLayoutExample'),
|
||||
require('./ListViewPagingExample'),
|
||||
require('./MapViewExample'),
|
||||
require('./Navigator/NavigatorExample'),
|
||||
require('./NavigatorIOSColorsExample'),
|
||||
require('./NavigatorIOSExample'),
|
||||
require('./PickerIOSExample'),
|
||||
require('./ProgressViewIOSExample'),
|
||||
require('./ScrollViewExample'),
|
||||
require('./SegmentedControlIOSExample'),
|
||||
require('./SliderIOSExample'),
|
||||
require('./SwitchIOSExample'),
|
||||
require('./TabBarIOSExample'),
|
||||
require('./TextExample.ios'),
|
||||
require('./TextInputExample'),
|
||||
require('./TouchableExample'),
|
||||
require('./ViewExample'),
|
||||
require('./WebViewExample'),
|
||||
];
|
||||
|
||||
var APIS = [
|
||||
require('./AccessibilityIOSExample'),
|
||||
require('./ActionSheetIOSExample'),
|
||||
require('./AdSupportIOSExample'),
|
||||
require('./AlertIOSExample'),
|
||||
require('./AnimationExample/AnExApp'),
|
||||
require('./AppStateIOSExample'),
|
||||
require('./AsyncStorageExample'),
|
||||
require('./BorderExample'),
|
||||
require('./CameraRollExample.ios'),
|
||||
require('./GeolocationExample'),
|
||||
require('./LayoutExample'),
|
||||
require('./NetInfoExample'),
|
||||
require('./PanResponderExample'),
|
||||
require('./PointerEventsExample'),
|
||||
require('./PushNotificationIOSExample'),
|
||||
require('./StatusBarIOSExample'),
|
||||
require('./TimerExample'),
|
||||
require('./VibrationIOSExample'),
|
||||
require('./XHRExample'),
|
||||
];
|
||||
|
||||
// Register suitable examples for snapshot tests
|
||||
COMPONENTS.concat(APIS).forEach((Example) => {
|
||||
if (Example.displayName) {
|
||||
var Snapshotter = React.createClass({
|
||||
componentDidMount: function() {
|
||||
// View is still blank after first RAF :\
|
||||
global.requestAnimationFrame(() =>
|
||||
global.requestAnimationFrame(() => TestModule.verifySnapshot(
|
||||
TestModule.markTestPassed
|
||||
)
|
||||
));
|
||||
},
|
||||
render: function() {
|
||||
var Renderable = UIExplorerListBase.makeRenderable(Example);
|
||||
return <Renderable />;
|
||||
},
|
||||
});
|
||||
AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
|
||||
}
|
||||
});
|
||||
|
||||
type Props = {
|
||||
navigator: {
|
||||
navigationContext: NavigationContext,
|
||||
push: (route: {title: string, component: ReactClass<any,any,any>}) => void,
|
||||
},
|
||||
onExternalExampleRequested: Function,
|
||||
};
|
||||
|
||||
class UIExplorerList extends React.Component {
|
||||
props: Props;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UIExplorerListBase
|
||||
components={COMPONENTS}
|
||||
apis={APIS}
|
||||
searchText={Settings.get('searchText')}
|
||||
renderAdditionalView={this.renderAdditionalView.bind(this)}
|
||||
search={this.search.bind(this)}
|
||||
onPressRow={this.onPressRow.bind(this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
var wasUIExplorer = false;
|
||||
var didOpenExample = false;
|
||||
|
||||
this.props.navigator.navigationContext.addListener('didfocus', (event) => {
|
||||
var isUIExplorer = event.data.route.title === 'UIExplorer';
|
||||
|
||||
if (!didOpenExample && isUIExplorer) {
|
||||
didOpenExample = true;
|
||||
|
||||
var visibleExampleTitle = Settings.get('visibleExample');
|
||||
if (visibleExampleTitle) {
|
||||
var predicate = (example) => example.title === visibleExampleTitle;
|
||||
var foundExample = APIS.find(predicate) || COMPONENTS.find(predicate);
|
||||
if (foundExample) {
|
||||
setTimeout(() => this._openExample(foundExample), 100);
|
||||
}
|
||||
} else if (!wasUIExplorer && isUIExplorer) {
|
||||
Settings.set({visibleExample: null});
|
||||
}
|
||||
}
|
||||
|
||||
wasUIExplorer = isUIExplorer;
|
||||
});
|
||||
}
|
||||
|
||||
renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component {
|
||||
return renderTextInput(styles.searchTextInput);
|
||||
}
|
||||
|
||||
search(text: mixed) {
|
||||
Settings.set({searchText: text});
|
||||
}
|
||||
|
||||
_openExample(example: any) {
|
||||
if (example.external) {
|
||||
this.props.onExternalExampleRequested(example);
|
||||
return;
|
||||
}
|
||||
|
||||
var Component = UIExplorerListBase.makeRenderable(example);
|
||||
this.props.navigator.push({
|
||||
title: Component.title,
|
||||
component: Component,
|
||||
});
|
||||
}
|
||||
|
||||
onPressRow(example: any) {
|
||||
Settings.set({visibleExample: example.title});
|
||||
this._openExample(example);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
searchTextInput: {
|
||||
height: 30,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = UIExplorerList;
|
||||
@@ -40,6 +40,7 @@ var COMMON_COMPONENTS = [
|
||||
require('./ImageExample'),
|
||||
require('./LayoutEventsExample'),
|
||||
require('./ListViewExample'),
|
||||
require('./ListViewGridLayoutExample'),
|
||||
require('./ListViewPagingExample'),
|
||||
require('./MapViewExample'),
|
||||
require('./Navigator/NavigatorExample'),
|
||||
@@ -80,6 +81,7 @@ if (Platform.OS === 'ios') {
|
||||
require('./AlertIOSExample'),
|
||||
require('./AppStateIOSExample'),
|
||||
require('./AsyncStorageExample'),
|
||||
require('./TransformExample'),
|
||||
require('./BorderExample'),
|
||||
require('./CameraRollExample.ios'),
|
||||
require('./NetInfoExample'),
|
||||
@@ -164,15 +166,6 @@ class UIExplorerList extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
this._search(this.state.searchText);
|
||||
|
||||
var visibleExampleTitle = Settings.get('visibleExample');
|
||||
if (visibleExampleTitle) {
|
||||
var predicate = (example) => example.title === visibleExampleTitle;
|
||||
var foundExample = APIS.find(predicate) || COMPONENTS.find(predicate);
|
||||
if (foundExample) {
|
||||
setTimeout(() => this._openExample(foundExample), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
ListView,
|
||||
PixelRatio,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
var createExamplePage = require('./createExamplePage');
|
||||
|
||||
var ds = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) => r1 !== r2,
|
||||
sectionHeaderHasChanged: (h1, h2) => h1 !== h2,
|
||||
});
|
||||
|
||||
class UIExplorerListBase extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dataSource: ds.cloneWithRowsAndSections({
|
||||
components: [],
|
||||
apis: [],
|
||||
}),
|
||||
searchText: this.props.searchText,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.search(this.state.searchText);
|
||||
}
|
||||
|
||||
render() {
|
||||
var topView = this.props.renderAdditionalView &&
|
||||
this.props.renderAdditionalView(this.renderRow.bind(this), this.renderTextInput.bind(this));
|
||||
|
||||
return (
|
||||
<View style={styles.listContainer}>
|
||||
{topView}
|
||||
<ListView
|
||||
style={styles.list}
|
||||
dataSource={this.state.dataSource}
|
||||
renderRow={this.renderRow.bind(this)}
|
||||
renderSectionHeader={this._renderSectionHeader}
|
||||
keyboardShouldPersistTaps={true}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
keyboardDismissMode="on-drag"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderTextInput(searchTextInputStyle: any) {
|
||||
return (
|
||||
<View style={styles.searchRow}>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
clearButtonMode="always"
|
||||
onChangeText={this.search.bind(this)}
|
||||
placeholder="Search..."
|
||||
style={[styles.searchTextInput, searchTextInputStyle]}
|
||||
testID="explorer_search"
|
||||
value={this.state.searchText}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
_renderSectionHeader(data: any, section: string) {
|
||||
return (
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionHeaderTitle}>
|
||||
{section.toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderRow(example: any, i: number) {
|
||||
return (
|
||||
<View key={i}>
|
||||
<TouchableHighlight onPress={() => this.onPressRow(example)}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.rowTitleText}>
|
||||
{example.title}
|
||||
</Text>
|
||||
<Text style={styles.rowDetailText}>
|
||||
{example.description}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
<View style={styles.separator} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
search(text: mixed): void {
|
||||
this.props.search && this.props.search(text);
|
||||
|
||||
var regex = new RegExp(text, 'i');
|
||||
var filter = (component) => regex.test(component.title);
|
||||
|
||||
this.setState({
|
||||
dataSource: ds.cloneWithRowsAndSections({
|
||||
components: this.props.components.filter(filter),
|
||||
apis: this.props.apis.filter(filter),
|
||||
}),
|
||||
searchText: text,
|
||||
});
|
||||
}
|
||||
|
||||
onPressRow(example: any): void {
|
||||
this.props.onPressRow && this.props.onPressRow(example);
|
||||
}
|
||||
|
||||
static makeRenderable(example: any): ReactClass<any, any, any> {
|
||||
return example.examples ?
|
||||
createExamplePage(null, example) :
|
||||
example;
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
listContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
list: {
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
sectionHeader: {
|
||||
padding: 5,
|
||||
},
|
||||
group: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
sectionHeaderTitle: {
|
||||
fontWeight: '500',
|
||||
fontSize: 11,
|
||||
},
|
||||
row: {
|
||||
backgroundColor: 'white',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
separator: {
|
||||
height: 1 / PixelRatio.get(),
|
||||
backgroundColor: '#bbbbbb',
|
||||
marginLeft: 15,
|
||||
},
|
||||
rowTitleText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '500',
|
||||
},
|
||||
rowDetailText: {
|
||||
fontSize: 15,
|
||||
color: '#888888',
|
||||
lineHeight: 20,
|
||||
},
|
||||
searchRow: {
|
||||
backgroundColor: '#eeeeee',
|
||||
paddingTop: 75,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
searchTextInput: {
|
||||
backgroundColor: 'white',
|
||||
borderColor: '#cccccc',
|
||||
borderRadius: 3,
|
||||
borderWidth: 1,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = UIExplorerListBase;
|
||||
@@ -1,4 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
@@ -7,17 +19,15 @@
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTRootView.h"
|
||||
|
||||
#define RUN_RUNLOOP_WHILE(CONDITION, TIMEOUT) \
|
||||
#define RUN_RUNLOOP_WHILE(CONDITION) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:TIMEOUT]; \
|
||||
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:0.1]; \
|
||||
while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
|
||||
} \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define DEFAULT_TIMEOUT 2
|
||||
|
||||
@interface RCTBridge (RCTAllocationTests)
|
||||
|
||||
@property (nonatomic, weak) RCTBridge *batchedBridge;
|
||||
@@ -71,7 +81,6 @@ RCT_EXPORT_MODULE();
|
||||
(void)view;
|
||||
}
|
||||
|
||||
sleep(DEFAULT_TIMEOUT);
|
||||
XCTAssertNil(weakBridge, @"RCTBridge should have been deallocated");
|
||||
}
|
||||
|
||||
@@ -92,8 +101,7 @@ RCT_EXPORT_MODULE();
|
||||
* Sleep on the main thread to allow js thread deallocations then run the runloop
|
||||
* to allow the module to be deallocated on the main thread
|
||||
*/
|
||||
sleep(1);
|
||||
RUN_RUNLOOP_WHILE(module.isValid, 1)
|
||||
RUN_RUNLOOP_WHILE(module.isValid)
|
||||
XCTAssertFalse(module.isValid, @"AllocationTestModule should have been invalidated by the bridge");
|
||||
}
|
||||
|
||||
@@ -112,12 +120,7 @@ RCT_EXPORT_MODULE();
|
||||
(void)bridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep on the main thread to allow js thread deallocations then run the runloop
|
||||
* to allow the module to be deallocated on the main thread
|
||||
*/
|
||||
sleep(1);
|
||||
RUN_RUNLOOP_WHILE(weakModule, 1)
|
||||
RUN_RUNLOOP_WHILE(weakModule)
|
||||
XCTAssertNil(weakModule, @"AllocationTestModule should have been deallocated");
|
||||
}
|
||||
|
||||
@@ -133,8 +136,7 @@ RCT_EXPORT_MODULE();
|
||||
(void)bridge;
|
||||
}
|
||||
|
||||
RUN_RUNLOOP_WHILE(weakExecutor, 1);
|
||||
sleep(1);
|
||||
RUN_RUNLOOP_WHILE(weakExecutor);
|
||||
XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released");
|
||||
}
|
||||
|
||||
@@ -146,13 +148,12 @@ RCT_EXPORT_MODULE();
|
||||
moduleProvider:nil
|
||||
launchOptions:nil];
|
||||
id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
|
||||
RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]), DEFAULT_TIMEOUT);
|
||||
RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]));
|
||||
XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created");
|
||||
(void)bridge;
|
||||
}
|
||||
|
||||
RUN_RUNLOOP_WHILE(weakContext, 1);
|
||||
sleep(1);
|
||||
RUN_RUNLOOP_WHILE(weakContext);
|
||||
XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated");
|
||||
}
|
||||
|
||||
@@ -164,12 +165,11 @@ RCT_EXPORT_MODULE();
|
||||
__weak id rootContentView;
|
||||
@autoreleasepool {
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""];
|
||||
RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]), DEFAULT_TIMEOUT)
|
||||
RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]))
|
||||
XCTAssertTrue([rootContentView isValid], @"RCTContentView should be valid");
|
||||
(void)rootView;
|
||||
}
|
||||
|
||||
sleep(DEFAULT_TIMEOUT);
|
||||
XCTAssertFalse([rootContentView isValid], @"RCTContentView should have been invalidated");
|
||||
}
|
||||
|
||||
@@ -184,8 +184,7 @@ RCT_EXPORT_MODULE();
|
||||
[bridge reload];
|
||||
}
|
||||
|
||||
// Use RUN_RUNLOOP_WHILE because `batchedBridge` deallocates on the main thread.
|
||||
RUN_RUNLOOP_WHILE(batchedBridge != nil, DEFAULT_TIMEOUT)
|
||||
RUN_RUNLOOP_WHILE(batchedBridge != nil)
|
||||
|
||||
XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated");
|
||||
XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated");
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
@@ -12,7 +24,7 @@
|
||||
|
||||
@property (nonatomic, strong, readonly) RCTBridge *batchedBridge;
|
||||
|
||||
- (void)_handleBuffer:(id)buffer context:(NSNumber *)context;
|
||||
- (void)_handleBuffer:(id)buffer;
|
||||
- (void)setUp;
|
||||
|
||||
@end
|
||||
@@ -45,7 +57,6 @@ RCT_EXPORT_MODULE()
|
||||
- (void)executeJSCall:(__unused NSString *)name
|
||||
method:(__unused NSString *)method
|
||||
arguments:(__unused NSArray *)arguments
|
||||
context:(__unused NSNumber *)executorID
|
||||
callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
onComplete(nil, nil);
|
||||
@@ -143,7 +154,7 @@ RCT_EXPORT_MODULE(TestModule)
|
||||
NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
|
||||
NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567];
|
||||
|
||||
[_bridge.batchedBridge _handleBuffer:buffer context:RCTGetExecutorID(executor)];
|
||||
[_bridge.batchedBridge _handleBuffer:buffer];
|
||||
|
||||
dispatch_sync(_methodQueue, ^{
|
||||
// clear the queue
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "RCTCache.h"
|
||||
|
||||
// Silence silly sign warnings when using int literals
|
||||
#pragma clang diagnostic ignored "-Wsign-compare"
|
||||
|
||||
@interface RCTCache (Private)
|
||||
|
||||
- (void)cleanUpAllObjects;
|
||||
- (void)resequence;
|
||||
- (NSDictionary *)cache;
|
||||
- (void)setSequenceNumber:(NSInteger)number;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTCacheTests : XCTestCase
|
||||
|
||||
@property (nonatomic, strong) RCTCache *cache;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTCacheTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
self.cache = [[RCTCache alloc] init];
|
||||
self.cache.countLimit = 3;
|
||||
self.cache.totalCostLimit = 100;
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
self.cache = nil;
|
||||
}
|
||||
|
||||
- (void)testInsertion
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo" cost:1];
|
||||
[self.cache setObject:@2 forKey:@"bar" cost:2];
|
||||
[self.cache setObject:@3 forKey:@"baz" cost:3];
|
||||
|
||||
XCTAssertEqual([self.cache count], 3);
|
||||
XCTAssertEqual([self.cache totalCost], 6);
|
||||
}
|
||||
|
||||
- (void)testRemoval
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo" cost:1];
|
||||
[self.cache setObject:@2 forKey:@"bar" cost:2];
|
||||
[self.cache setObject:@3 forKey:@"baz" cost:3];
|
||||
|
||||
[self.cache removeObjectForKey:@"bar"];
|
||||
|
||||
XCTAssertEqual([self.cache count], 2);
|
||||
XCTAssertNil([self.cache objectForKey:@"bar"]);
|
||||
}
|
||||
|
||||
- (void)testCountEviction
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo"];
|
||||
[self.cache setObject:@2 forKey:@"bar"];
|
||||
[self.cache setObject:@3 forKey:@"baz"];
|
||||
[self.cache setObject:@4 forKey:@"bam"];
|
||||
|
||||
XCTAssertEqual([self.cache count], 3);
|
||||
XCTAssertNil([self.cache objectForKey:@"foo"]);
|
||||
|
||||
[self.cache setObject:@5 forKey:@"boo"];
|
||||
|
||||
XCTAssertEqual([self.cache count], 3);
|
||||
XCTAssertNil([self.cache objectForKey:@"bar"]);
|
||||
}
|
||||
|
||||
- (void)testCostEviction
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo" cost:99];
|
||||
[self.cache setObject:@2 forKey:@"bar" cost:2];
|
||||
|
||||
XCTAssertEqual([self.cache count], 1);
|
||||
XCTAssertEqual([self.cache totalCost], 2);
|
||||
XCTAssertNil([self.cache objectForKey:@"foo"]);
|
||||
|
||||
[self.cache setObject:@3 forKey:@"baz" cost:999];
|
||||
|
||||
XCTAssertEqual([self.cache count], 0);
|
||||
XCTAssertEqual([self.cache totalCost], 0);
|
||||
}
|
||||
|
||||
- (void)testCleanup
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo"];
|
||||
[self.cache setObject:@2 forKey:@"bar"];
|
||||
[self.cache setObject:@3 forKey:@"baz"];
|
||||
|
||||
//simulate memory warning
|
||||
[self.cache cleanUpAllObjects];
|
||||
|
||||
XCTAssertEqual([self.cache count], 0);
|
||||
XCTAssertEqual([self.cache totalCost], 0);
|
||||
}
|
||||
|
||||
- (void)testResequence
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo"];
|
||||
[self.cache setObject:@2 forKey:@"bar"];
|
||||
[self.cache setObject:@3 forKey:@"baz"];
|
||||
|
||||
[self.cache resequence];
|
||||
|
||||
NSDictionary *innerCache = [self.cache cache];
|
||||
XCTAssertEqualObjects([innerCache[@"foo"] valueForKey:@"sequenceNumber"], @0);
|
||||
XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @1);
|
||||
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @2);
|
||||
|
||||
[self.cache removeObjectForKey:@"foo"];
|
||||
[self.cache resequence];
|
||||
|
||||
XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @0);
|
||||
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @1);
|
||||
}
|
||||
|
||||
- (void)testResequenceTrigger
|
||||
{
|
||||
[self.cache setObject:@1 forKey:@"foo"];
|
||||
[self.cache setObject:@2 forKey:@"bar"];
|
||||
|
||||
//first object should now be bar with sequence number of 1
|
||||
[self.cache removeObjectForKey:@"foo"];
|
||||
|
||||
//should trigger resequence
|
||||
[self.cache setSequenceNumber:NSIntegerMax];
|
||||
[self.cache setObject:@3 forKey:@"baz"];
|
||||
|
||||
NSDictionary *innerCache = [self.cache cache];
|
||||
XCTAssertEqualObjects([innerCache[@"bar"] valueForKey:@"sequenceNumber"], @0);
|
||||
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @1);
|
||||
|
||||
//first object should now be baz with sequence number of 1
|
||||
[self.cache removeObjectForKey:@"bar"];
|
||||
|
||||
//should also trigger resequence
|
||||
[self.cache setSequenceNumber:NSIntegerMax];
|
||||
[self.cache objectForKey:@"baz"];
|
||||
|
||||
XCTAssertEqualObjects([innerCache[@"baz"] valueForKey:@"sequenceNumber"], @0);
|
||||
}
|
||||
|
||||
- (void)testName
|
||||
{
|
||||
self.cache.name = @"Hello";
|
||||
XCTAssertEqualObjects(self.cache.name, @"Hello");
|
||||
}
|
||||
|
||||
@end
|
||||
+15
-6
@@ -16,10 +16,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIView.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
extern CGRect RCTClipRect(CGSize contentSize, CGFloat contentScale,
|
||||
CGSize targetSize, CGFloat targetScale,
|
||||
UIViewContentMode resizeMode);
|
||||
#import "RCTImageUtils.h"
|
||||
|
||||
#define RCTAssertEqualPoints(a, b) { \
|
||||
XCTAssertEqual(a.x, b.x); \
|
||||
@@ -36,11 +33,11 @@ RCTAssertEqualPoints(a.origin, b.origin); \
|
||||
RCTAssertEqualSizes(a.size, b.size); \
|
||||
}
|
||||
|
||||
@interface ClippingTests : XCTestCase
|
||||
@interface RCTClipRectTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ClippingTests
|
||||
@implementation RCTClipRectTests
|
||||
|
||||
- (void)testLandscapeSourceLandscapeTarget
|
||||
{
|
||||
@@ -109,6 +106,18 @@ RCTAssertEqualSizes(a.size, b.size); \
|
||||
|
||||
{
|
||||
CGRect expected = {{0, -37.5}, {10, 100}};
|
||||
CGRect result = RCTClipRect(content, 2, target, 2, UIViewContentModeScaleAspectFill);
|
||||
RCTAssertEqualRects(expected, result);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRounding
|
||||
{
|
||||
CGSize content = {10, 100};
|
||||
CGSize target = {20, 50};
|
||||
|
||||
{
|
||||
CGRect expected = {{0, -38}, {10, 100}};
|
||||
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
|
||||
RCTAssertEqualRects(expected, result);
|
||||
}
|
||||
@@ -1,4 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <mach/mach_time.h>
|
||||
|
||||
@@ -7,6 +19,7 @@
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define RUN_PERF_TESTS 0
|
||||
|
||||
@interface RCTContextExecutorTests : XCTestCase
|
||||
|
||||
@@ -21,7 +34,6 @@
|
||||
{
|
||||
[super setUp];
|
||||
_executor = [[RCTContextExecutor alloc] init];
|
||||
RCTSetExecutorID(_executor);
|
||||
[_executor setUp];
|
||||
}
|
||||
|
||||
@@ -37,6 +49,8 @@
|
||||
[_executor invalidate];
|
||||
}
|
||||
|
||||
#if RUN_PERF_TESTS
|
||||
|
||||
static uint64_t _get_time_nanoseconds(void)
|
||||
{
|
||||
static struct mach_timebase_info tb_info = {0, 0};
|
||||
@@ -80,7 +94,7 @@ static uint64_t _get_time_nanoseconds(void)
|
||||
JSContextGroupRelease(group);
|
||||
}
|
||||
|
||||
- (void)MANUALLY_testJavaScriptCallSpeed
|
||||
- (void)testJavaScriptCallSpeed
|
||||
{
|
||||
/**
|
||||
* Since we almost don't change the RCTContextExecutor logic, and this test is
|
||||
@@ -127,7 +141,6 @@ static uint64_t _get_time_nanoseconds(void)
|
||||
[_executor executeJSCall:@"module"
|
||||
method:@"method"
|
||||
arguments:params
|
||||
context:RCTGetExecutorID(_executor)
|
||||
callback:^(id json, __unused NSError *unused) {
|
||||
XCTAssert([json isEqual:@YES], @"Invalid return");
|
||||
}];
|
||||
@@ -190,4 +203,6 @@ static uint64_t _get_time_nanoseconds(void)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTConvert_NSURLTests : XCTestCase
|
||||
|
||||
@@ -30,7 +43,7 @@ TEST_PATH(name, _input, [[[NSBundle mainBundle] bundlePath] stringByAppendingPat
|
||||
TEST_URL(basic, @"http://example.com", @"http://example.com")
|
||||
TEST_URL(null, (id)kCFNull, nil)
|
||||
|
||||
// Local files
|
||||
// Resource files
|
||||
TEST_PATH(fileURL, @"file:///blah/hello.jsbundle", @"/blah/hello.jsbundle")
|
||||
TEST_BUNDLE_PATH(filePath, @"blah/hello.jsbundle", @"blah/hello.jsbundle")
|
||||
TEST_BUNDLE_PATH(filePathWithSpaces, @"blah blah/hello.jsbundle", @"blah blah/hello.jsbundle")
|
||||
@@ -38,6 +51,9 @@ TEST_BUNDLE_PATH(filePathWithEncodedSpaces, @"blah%20blah/hello.jsbundle", @"bla
|
||||
TEST_BUNDLE_PATH(imageAt2XPath, @"images/foo@2x.jpg", @"images/foo@2x.jpg")
|
||||
TEST_BUNDLE_PATH(imageFile, @"foo.jpg", @"foo.jpg")
|
||||
|
||||
// User documents
|
||||
TEST_PATH(documentsFolder, @"~/Documents", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject])
|
||||
|
||||
// Remote files
|
||||
TEST_URL(fullURL, @"http://example.com/blah/hello.jsbundle", @"http://example.com/blah/hello.jsbundle")
|
||||
TEST_URL(urlWithSpaces, @"http://example.com/blah blah/foo", @"http://example.com/blah%20blah/foo")
|
||||
@@ -45,4 +61,12 @@ TEST_URL(urlWithEncodedSpaces, @"http://example.com/blah%20blah/foo", @"http://e
|
||||
TEST_URL(imageURL, @"http://example.com/foo@2x.jpg", @"http://example.com/foo@2x.jpg")
|
||||
TEST_URL(imageURLWithSpaces, @"http://example.com/blah foo@2x.jpg", @"http://example.com/blah%20foo@2x.jpg")
|
||||
|
||||
// Data URLs
|
||||
- (void)testDataURL
|
||||
{
|
||||
NSURL *expectedURL = RCTDataURL(@"text/plain", [@"abcde" dataUsingEncoding:NSUTF8StringEncoding]);
|
||||
NSURL *testURL = [NSURL URLWithString:@"data:text/plain;base64,YWJjZGU="];
|
||||
XCTAssertEqualObjects([testURL absoluteString], [expectedURL absoluteString]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@@ -174,7 +186,7 @@
|
||||
RCTAssertEqualFonts(expected, result);
|
||||
}
|
||||
{
|
||||
UIFont *expected = [UIFont boldSystemFontOfSize:14];
|
||||
UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightBold];
|
||||
UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar", @"fontWeight": @"bold"}];
|
||||
RCTAssertEqualFonts(expected, result);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTNetworking.h"
|
||||
|
||||
extern BOOL RCTIsGzippedData(NSData *data);
|
||||
|
||||
@interface RCTNetworking (Private)
|
||||
|
||||
- (void)buildRequest:(NSDictionary *)query
|
||||
completionBlock:(void (^)(NSURLRequest *request))block;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTGzipTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTGzipTests
|
||||
|
||||
- (void)testGzip
|
||||
{
|
||||
//set up data
|
||||
NSString *inputString = @"Hello World!";
|
||||
NSData *inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
//compress
|
||||
NSData *outputData = RCTGzipData(inputData, -1);
|
||||
XCTAssertTrue(RCTIsGzippedData(outputData));
|
||||
}
|
||||
|
||||
- (void)testDontRezipZippedData
|
||||
{
|
||||
//set up data
|
||||
NSString *inputString = @"Hello World!";
|
||||
NSData *inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
//compress
|
||||
NSData *compressedData = RCTGzipData(inputData, -1);
|
||||
inputString = [[NSString alloc] initWithData:compressedData encoding:NSUTF8StringEncoding];
|
||||
|
||||
//compress again
|
||||
NSData *outputData = RCTGzipData(inputData, -1);
|
||||
NSString *outputString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding];
|
||||
XCTAssertEqualObjects(outputString, inputString);
|
||||
}
|
||||
|
||||
- (void)testRequestBodyEncoding
|
||||
{
|
||||
NSDictionary *query = @{
|
||||
@"url": @"http://example.com",
|
||||
@"method": @"POST",
|
||||
@"data": @{@"string": @"Hello World"},
|
||||
@"headers": @{@"Content-Encoding": @"gzip"},
|
||||
};
|
||||
|
||||
RCTNetworking *networker = [[RCTNetworking alloc] init];
|
||||
__block NSURLRequest *request = nil;
|
||||
[networker buildRequest:query completionBlock:^(NSURLRequest *_request) {
|
||||
request = _request;
|
||||
}];
|
||||
|
||||
XCTAssertNotNil(request);
|
||||
XCTAssertNotNil(request.HTTPBody);
|
||||
XCTAssertTrue(RCTIsGzippedData(request.HTTPBody));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTJSONTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTJSONTests
|
||||
|
||||
- (void)testEncodingObject
|
||||
{
|
||||
NSDictionary *obj = @{@"foo": @"bar"};
|
||||
NSString *json = @"{\"foo\":\"bar\"}";
|
||||
XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
|
||||
}
|
||||
|
||||
- (void)testEncodingArray
|
||||
{
|
||||
NSArray *array = @[@"foo", @"bar"];
|
||||
NSString *json = @"[\"foo\",\"bar\"]";
|
||||
XCTAssertEqualObjects(json, RCTJSONStringify(array, NULL));
|
||||
}
|
||||
|
||||
- (void)testEncodingString
|
||||
{
|
||||
NSString *text = @"Hello\nWorld";
|
||||
NSString *json = @"\"Hello\\nWorld\"";
|
||||
XCTAssertEqualObjects(json, RCTJSONStringify(text, NULL));
|
||||
}
|
||||
|
||||
- (void)testDecodingObject
|
||||
{
|
||||
NSDictionary *obj = @{@"foo": @"bar"};
|
||||
NSString *json = @"{\"foo\":\"bar\"}";
|
||||
XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL));
|
||||
}
|
||||
|
||||
- (void)testDecodingArray
|
||||
{
|
||||
NSArray *array = @[@"foo", @"bar"];
|
||||
NSString *json = @"[\"foo\",\"bar\"]";
|
||||
XCTAssertEqualObjects(array, RCTJSONParse(json, NULL));
|
||||
}
|
||||
|
||||
- (void)testDecodingString
|
||||
{
|
||||
NSString *text = @"Hello\nWorld";
|
||||
NSString *json = @"\"Hello\\nWorld\"";
|
||||
XCTAssertEqualObjects(text, RCTJSONParse(json, NULL));
|
||||
}
|
||||
|
||||
- (void)testDecodingMutableArray
|
||||
{
|
||||
NSString *json = @"[1,2,3]";
|
||||
NSMutableArray *array = RCTJSONParseMutable(json, NULL);
|
||||
XCTAssertNoThrow([array addObject:@4]);
|
||||
XCTAssertEqualObjects(array, (@[@1, @2, @3, @4]));
|
||||
}
|
||||
|
||||
- (void)testLeadingWhitespace
|
||||
{
|
||||
NSDictionary *obj = @{@"foo": @"bar"};
|
||||
NSString *json = @" \r\n\t{\"foo\":\"bar\"}";
|
||||
XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,4 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTShadowView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "UIView+React.h"
|
||||
@@ -11,36 +21,14 @@
|
||||
@interface RCTUIManager (Testing)
|
||||
|
||||
- (void)_manageChildren:(NSNumber *)containerReactTag
|
||||
moveFromIndices:(NSArray *)moveFromIndices
|
||||
moveToIndices:(NSArray *)moveToIndices
|
||||
addChildReactTags:(NSArray *)addChildReactTags
|
||||
addAtIndices:(NSArray *)addAtIndices
|
||||
removeAtIndices:(NSArray *)removeAtIndices
|
||||
registry:(RCTSparseArray *)registry;
|
||||
|
||||
- (void)modifyManageChildren:(NSNumber *)containerReactTag
|
||||
addChildReactTags:(NSMutableArray *)mutableAddChildReactTags
|
||||
addAtIndices:(NSMutableArray *)mutableAddAtIndices
|
||||
removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices;
|
||||
|
||||
- (void)createView:(NSNumber *)reactTag
|
||||
viewName:(NSString *)viewName
|
||||
rootTag:(NSNumber *)rootTag
|
||||
props:(NSDictionary *)props;
|
||||
|
||||
- (void)updateView:(NSNumber *)reactTag
|
||||
viewName:(NSString *)viewName
|
||||
props:(NSDictionary *)props;
|
||||
|
||||
- (void)manageChildren:(NSNumber *)containerReactTag
|
||||
moveFromIndices:(NSArray *)moveFromIndices
|
||||
moveToIndices:(NSArray *)moveToIndices
|
||||
addChildReactTags:(NSArray *)addChildReactTags
|
||||
addAtIndices:(NSArray *)addAtIndices
|
||||
removeAtIndices:(NSArray *)removeAtIndices;
|
||||
|
||||
- (void)flushUIBlocks;
|
||||
|
||||
@property (nonatomic, readonly) RCTSparseArray *viewRegistry;
|
||||
@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only
|
||||
|
||||
@end
|
||||
|
||||
@@ -63,11 +51,6 @@
|
||||
UIView *registeredView = [[UIView alloc] init];
|
||||
[registeredView setReactTag:@(i)];
|
||||
_uiManager.viewRegistry[i] = registeredView;
|
||||
|
||||
RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init];
|
||||
registeredShadowView.viewName = @"RCTView";
|
||||
[registeredShadowView setReactTag:@(i)];
|
||||
_uiManager.shadowViewRegistry[i] = registeredShadowView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +67,8 @@
|
||||
|
||||
// Add views 1-5 to view 20
|
||||
[_uiManager _manageChildren:@20
|
||||
moveFromIndices:nil
|
||||
moveToIndices:nil
|
||||
addChildReactTags:tagsToAdd
|
||||
addAtIndices:addAtIndices
|
||||
removeAtIndices:nil
|
||||
@@ -116,6 +101,8 @@
|
||||
|
||||
// Remove views 1-5 from view 20
|
||||
[_uiManager _manageChildren:@20
|
||||
moveFromIndices:nil
|
||||
moveToIndices:nil
|
||||
addChildReactTags:nil
|
||||
addAtIndices:nil
|
||||
removeAtIndices:removeAtIndices
|
||||
@@ -153,9 +140,11 @@
|
||||
{
|
||||
UIView *containerView = _uiManager.viewRegistry[20];
|
||||
|
||||
NSArray *removeAtIndices = @[@2, @3, @5, @8, @4, @9];
|
||||
NSArray *addAtIndices = @[@0, @6, @1, @7];
|
||||
NSArray *tagsToAdd = @[@11, @12, @5, @10];
|
||||
NSArray *removeAtIndices = @[@2, @3, @5, @8];
|
||||
NSArray *addAtIndices = @[@0, @6];
|
||||
NSArray *tagsToAdd = @[@11, @12];
|
||||
NSArray *moveFromIndices = @[@4, @9];
|
||||
NSArray *moveToIndices = @[@1, @7];
|
||||
|
||||
// We need to keep these in array to keep them around
|
||||
NSMutableArray *viewsToRemove = [NSMutableArray array];
|
||||
@@ -171,6 +160,8 @@
|
||||
}
|
||||
|
||||
[_uiManager _manageChildren:@20
|
||||
moveFromIndices:moveFromIndices
|
||||
moveToIndices:moveToIndices
|
||||
addChildReactTags:tagsToAdd
|
||||
addAtIndices:addAtIndices
|
||||
removeAtIndices:removeAtIndices
|
||||
@@ -197,329 +188,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* +-----------------------------------------------------------+ +----------------------+
|
||||
* | Shadow Hierarchy | | Legend |
|
||||
* +-----------------------------------------------------------+ +----------------------+
|
||||
* | | | |
|
||||
* | +---+ ****** | | ******************** |
|
||||
* | | 1 | * 11 * | | * Layout-only View * |
|
||||
* | +---+ ****** | | ******************** |
|
||||
* | | | | | |
|
||||
* | +-------+---+---+----------+ +---+---+ | | +----+ |
|
||||
* | | | | | | | | | |View| Subview |
|
||||
* | v v v v v v | | +----+ -----------> |
|
||||
* | ***** +---+ ***** +---+ +----+ +----+ | | |
|
||||
* | * 2 * | 3 | * 4 * | 5 | | 12 | | 13 | | +----------------------+
|
||||
* | ***** +---+ ***** +---+ +----+ +----+ |
|
||||
* | | | | |
|
||||
* | +---+--+ | +---+---+ |
|
||||
* | | | | | | |
|
||||
* | v v v v v |
|
||||
* | +---+ +---+ +---+ +---+ ****** |
|
||||
* | | 6 | | 7 | | 8 | | 9 | * 10 * |
|
||||
* | +---+ +---+ +---+ +---+ ****** |
|
||||
* | |
|
||||
* +-----------------------------------------------------------+
|
||||
*
|
||||
* +-----------------------------------------------------------+
|
||||
* | View Hierarchy |
|
||||
* +-----------------------------------------------------------+
|
||||
* | |
|
||||
* | +---+ ****** |
|
||||
* | | 1 | * 11 * |
|
||||
* | +---+ ****** |
|
||||
* | | | |
|
||||
* | +------+------+------+------+ +---+---+ |
|
||||
* | | | | | | | | |
|
||||
* | v v v v v v v |
|
||||
* | +---+ +---+ +---+ +---+ +---+ +----+ +----+ |
|
||||
* | | 6 | | 7 | | 3 | | 8 | | 5 | | 12 | | 13 | |
|
||||
* | +---+ +---+ +---+ +---+ +---+ +----+ +----+ |
|
||||
* | | |
|
||||
* | v |
|
||||
* | +---+ |
|
||||
* | | 9 | |
|
||||
* | +---+ |
|
||||
* | |
|
||||
* +-----------------------------------------------------------+
|
||||
*/
|
||||
|
||||
- (void)updateShadowViewWithReactTag:(NSNumber *)reactTag layoutOnly:(BOOL)isLayoutOnly childTags:(NSArray *)childTags
|
||||
{
|
||||
RCTShadowView *shadowView = _uiManager.shadowViewRegistry[reactTag];
|
||||
shadowView.allProps = isLayoutOnly ? @{} : @{@"collapsible": @NO};
|
||||
[childTags enumerateObjectsUsingBlock:^(NSNumber *childTag, NSUInteger idx, __unused BOOL *stop) {
|
||||
[shadowView insertReactSubview:_uiManager.shadowViewRegistry[childTag] atIndex:idx];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setUpShadowViewHierarchy
|
||||
{
|
||||
[self updateShadowViewWithReactTag:@1 layoutOnly:NO childTags:@[@2, @3, @4, @5]];
|
||||
[self updateShadowViewWithReactTag:@2 layoutOnly:YES childTags:@[@6, @7]];
|
||||
[self updateShadowViewWithReactTag:@3 layoutOnly:NO childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@4 layoutOnly:YES childTags:@[@8]];
|
||||
[self updateShadowViewWithReactTag:@5 layoutOnly:NO childTags:@[@9, @10]];
|
||||
[self updateShadowViewWithReactTag:@6 layoutOnly:NO childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@7 layoutOnly:NO childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@8 layoutOnly:NO childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@9 layoutOnly:NO childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@10 layoutOnly:YES childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@11 layoutOnly:YES childTags:@[@12, @13]];
|
||||
[self updateShadowViewWithReactTag:@12 layoutOnly:NO childTags:nil];
|
||||
[self updateShadowViewWithReactTag:@13 layoutOnly:NO childTags:nil];
|
||||
}
|
||||
|
||||
- (void)testModifyIndices1
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[@2] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[@3] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[@0] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[@6, @7]));
|
||||
XCTAssertEqualObjects(addIndices, (@[@3, @4]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1]));
|
||||
}
|
||||
|
||||
- (void)testModifyIndices2
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[@4] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
||||
XCTAssertEqualObjects(addIndices, (@[@5, @6]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[]));
|
||||
}
|
||||
|
||||
- (void)testModifyIndices3
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[@2] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[]));
|
||||
XCTAssertEqualObjects(addIndices, (@[]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[@3]));
|
||||
}
|
||||
|
||||
- (void)testModifyIndices4
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[@3] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[@2] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
||||
XCTAssertEqualObjects(addIndices, (@[@4, @5]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[@3]));
|
||||
}
|
||||
|
||||
- (void)testModifyIndices5
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[]));
|
||||
XCTAssertEqualObjects(addIndices, (@[]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4]));
|
||||
}
|
||||
|
||||
- (void)testModifyIndices6
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[@0] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
||||
XCTAssertEqualObjects(addIndices, (@[@0, @1]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4]));
|
||||
}
|
||||
|
||||
- (void)testModifyIndices7
|
||||
{
|
||||
[self setUpShadowViewHierarchy];
|
||||
|
||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
||||
NSMutableArray *addIndices = [@[@1] mutableCopy];
|
||||
NSMutableArray *removeIndices = [@[@0, @2, @3] mutableCopy];
|
||||
[self.uiManager modifyManageChildren:@1
|
||||
addChildReactTags:addTags
|
||||
addAtIndices:addIndices
|
||||
removeAtIndices:removeIndices];
|
||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
||||
XCTAssertEqualObjects(addIndices, (@[@1, @2]));
|
||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4]));
|
||||
}
|
||||
|
||||
- (void)DISABLED_testScenario1 // t7660646
|
||||
{
|
||||
RCTUIManager *uiManager = [[RCTUIManager alloc] init];
|
||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];
|
||||
NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@""];
|
||||
|
||||
dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"];
|
||||
dispatch_async(shadowQueue, ^{
|
||||
// Make sure root view finishes loading.
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{});
|
||||
|
||||
/* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
||||
/* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
||||
/* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}];
|
||||
/* V */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"blue",@"height":@50,@"width":@50}];
|
||||
/* */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}];
|
||||
/* V */[uiManager createView:@7 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@10,@"margin":@50}];
|
||||
/* V */[uiManager createView:@8 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"yellow",@"height":@50}];
|
||||
/* V */[uiManager createView:@9 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}];
|
||||
/* */[uiManager createView:@10 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}];
|
||||
/* */[uiManager manageChildren:@9 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@10] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* V */[uiManager createView:@12 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"green",@"height":@50}];
|
||||
/* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8,@9,@12] addAtIndices:@[@0,@1,@2] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* V */[uiManager createView:@13 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"red",@"height":@50,@"width":@50}];
|
||||
/* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5,@6,@13] addAtIndices:@[@0,@1,@2] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
|
||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12);
|
||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[uiManager flushUIBlocks];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
|
||||
expectation = [self expectationWithDescription:@""];
|
||||
dispatch_async(shadowQueue, ^{
|
||||
[uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}];
|
||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12);
|
||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)7);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[uiManager flushUIBlocks];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
|
||||
expectation = [self expectationWithDescription:@""];
|
||||
dispatch_async(shadowQueue, ^{
|
||||
[uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":@10}];
|
||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12);
|
||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[uiManager flushUIBlocks];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
|
||||
- (void)DISABLED_testScenario2 // t7660646
|
||||
{
|
||||
RCTUIManager *uiManager = [[RCTUIManager alloc] init];
|
||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];
|
||||
NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@""];
|
||||
|
||||
dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"];
|
||||
dispatch_async(shadowQueue, ^{
|
||||
// Make sure root view finishes loading.
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{});
|
||||
|
||||
/* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
||||
/* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
||||
/* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}];
|
||||
/* */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}];
|
||||
/* V */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@1}];
|
||||
/* V */[uiManager createView:@7 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}];
|
||||
/* */[uiManager createView:@8 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}];
|
||||
/* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@5 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@6] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
/* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil];
|
||||
|
||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8);
|
||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[uiManager flushUIBlocks];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
|
||||
expectation = [self expectationWithDescription:@""];
|
||||
dispatch_async(shadowQueue, ^{
|
||||
[uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}];
|
||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8);
|
||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)3);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[uiManager flushUIBlocks];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
|
||||
expectation = [self expectationWithDescription:@""];
|
||||
dispatch_async(shadowQueue, ^{
|
||||
[uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":@1}];
|
||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8);
|
||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[uiManager flushUIBlocks];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -58,19 +58,19 @@ var WebViewExample = React.createClass({
|
||||
return (
|
||||
<View style={[styles.container]}>
|
||||
<View style={[styles.addressBarRow]}>
|
||||
<TouchableOpacity onPress={this.goBack}>
|
||||
<View style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'<'}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={this.goBack}
|
||||
style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'<'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={this.goForward}>
|
||||
<View style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'>'}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
onPress={this.goForward}
|
||||
style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
|
||||
<Text>
|
||||
{'>'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TextInput
|
||||
ref={TEXT_INPUT_REF}
|
||||
|
||||
@@ -128,6 +128,7 @@ class FormUploader extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
isUploading: false,
|
||||
uploadProgress: null,
|
||||
randomPhoto: null,
|
||||
textParams: [],
|
||||
};
|
||||
@@ -217,6 +218,14 @@ class FormUploader extends React.Component {
|
||||
this.state.textParams.forEach(
|
||||
(param) => formdata.append(param.name, param.value)
|
||||
);
|
||||
if (xhr.upload) {
|
||||
xhr.upload.onprogress = (event) => {
|
||||
console.log('upload onprogress', event);
|
||||
if (event.lengthComputable) {
|
||||
this.setState({uploadProgress: event.loaded / event.total});
|
||||
}
|
||||
};
|
||||
}
|
||||
xhr.send(formdata);
|
||||
this.setState({isUploading: true});
|
||||
}
|
||||
@@ -251,6 +260,10 @@ class FormUploader extends React.Component {
|
||||
</View>
|
||||
));
|
||||
var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload';
|
||||
var uploadProgress = this.state.uploadProgress;
|
||||
if (uploadProgress !== null) {
|
||||
uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%';
|
||||
}
|
||||
var uploadButton = (
|
||||
<View style={styles.uploadButtonBox}>
|
||||
<Text style={styles.uploadButtonLabel}>{uploadButtonLabel}</Text>
|
||||
|
||||
@@ -54,7 +54,9 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
|
||||
};
|
||||
var result = example.render(null);
|
||||
if (result) {
|
||||
renderedComponent = result;
|
||||
renderedComponent = React.cloneElement(result, {
|
||||
navigator: this.props.navigator,
|
||||
});
|
||||
}
|
||||
(React: Object).render = originalRender;
|
||||
(React: Object).renderComponent = originalRenderComponent;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#import "JSContextRef.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
JSValueRef nativeProfilerStart(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception);
|
||||
|
||||
JSValueRef nativeProfilerEnd(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//#include "config.h"
|
||||
|
||||
#include "JSCLegacyProfiler.h"
|
||||
|
||||
#include "APICast.h"
|
||||
#include "LegacyProfiler.h"
|
||||
#include "OpaqueJSString.h"
|
||||
#include "JSProfilerPrivate.h"
|
||||
#include "JSStringRef.h"
|
||||
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
#define GEN_AND_CHECK(expr) \
|
||||
do { \
|
||||
yajl_gen_status GEN_AND_CHECK_status = (expr); \
|
||||
if (GEN_AND_CHECK_status != yajl_gen_status_ok) { \
|
||||
return GEN_AND_CHECK_status; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
static inline yajl_gen_status yajl_gen_cstring(yajl_gen gen, const char *str) {
|
||||
return yajl_gen_string(gen, (const unsigned char*)str, strlen(str));
|
||||
}
|
||||
|
||||
static yajl_gen_status append_children_array_json(yajl_gen gen, const JSC::ProfileNode *node);
|
||||
static yajl_gen_status append_node_json(yajl_gen gen, const JSC::ProfileNode *node);
|
||||
|
||||
static yajl_gen_status append_root_json(yajl_gen gen, const JSC::Profile *profile) {
|
||||
GEN_AND_CHECK(yajl_gen_map_open(gen));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "rootNodes"));
|
||||
GEN_AND_CHECK(append_children_array_json(gen, profile->head()));
|
||||
GEN_AND_CHECK(yajl_gen_map_close(gen));
|
||||
|
||||
return yajl_gen_status_ok;
|
||||
}
|
||||
|
||||
static yajl_gen_status append_children_array_json(yajl_gen gen, const JSC::ProfileNode *node) {
|
||||
GEN_AND_CHECK(yajl_gen_array_open(gen));
|
||||
for (RefPtr<JSC::ProfileNode> child : node->children()) {
|
||||
GEN_AND_CHECK(append_node_json(gen, child.get()));
|
||||
}
|
||||
GEN_AND_CHECK(yajl_gen_array_close(gen));
|
||||
|
||||
return yajl_gen_status_ok;
|
||||
}
|
||||
|
||||
static yajl_gen_status append_node_json(yajl_gen gen, const JSC::ProfileNode *node) {
|
||||
GEN_AND_CHECK(yajl_gen_map_open(gen));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "id"));
|
||||
GEN_AND_CHECK(yajl_gen_integer(gen, node->id()));
|
||||
|
||||
if (!node->functionName().isEmpty()) {
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "functionName"));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, node->functionName().utf8().data()));
|
||||
}
|
||||
|
||||
if (!node->url().isEmpty()) {
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "url"));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, node->url().utf8().data()));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "lineNumber"));
|
||||
GEN_AND_CHECK(yajl_gen_integer(gen, node->lineNumber()));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "columnNumber"));
|
||||
GEN_AND_CHECK(yajl_gen_integer(gen, node->columnNumber()));
|
||||
}
|
||||
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "calls"));
|
||||
GEN_AND_CHECK(yajl_gen_array_open(gen));
|
||||
for (const JSC::ProfileNode::Call &call : node->calls()) {
|
||||
GEN_AND_CHECK(yajl_gen_map_open(gen));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "startTime"));
|
||||
GEN_AND_CHECK(yajl_gen_double(gen, call.startTime()));
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "totalTime"));
|
||||
GEN_AND_CHECK(yajl_gen_double(gen, call.totalTime()));
|
||||
GEN_AND_CHECK(yajl_gen_map_close(gen));
|
||||
}
|
||||
GEN_AND_CHECK(yajl_gen_array_close(gen));
|
||||
|
||||
if (!node->children().isEmpty()) {
|
||||
GEN_AND_CHECK(yajl_gen_cstring(gen, "children"));
|
||||
GEN_AND_CHECK(append_children_array_json(gen, node));
|
||||
}
|
||||
|
||||
GEN_AND_CHECK(yajl_gen_map_close(gen));
|
||||
|
||||
return yajl_gen_status_ok;
|
||||
}
|
||||
|
||||
static char *render_error_code(yajl_gen_status status) {
|
||||
char err[1024];
|
||||
snprintf(err, sizeof(err), "{\"error\": %d}", (int)status);
|
||||
return strdup(err);
|
||||
}
|
||||
|
||||
static char *convert_to_json(const JSC::Profile *profile) {
|
||||
yajl_gen_status status;
|
||||
yajl_gen gen = yajl_gen_alloc(NULL);
|
||||
|
||||
status = append_root_json(gen, profile);
|
||||
if (status != yajl_gen_status_ok) {
|
||||
yajl_gen_free(gen);
|
||||
return render_error_code(status);
|
||||
}
|
||||
|
||||
const unsigned char *buf;
|
||||
size_t buf_size;
|
||||
status = yajl_gen_get_buf(gen, &buf, &buf_size);
|
||||
if (status != yajl_gen_status_ok) {
|
||||
yajl_gen_free(gen);
|
||||
return render_error_code(status);
|
||||
}
|
||||
|
||||
char *json_copy = strdup((const char*)buf);
|
||||
yajl_gen_free(gen);
|
||||
return json_copy;
|
||||
}
|
||||
|
||||
static char *JSEndProfilingAndRender(JSContextRef ctx, JSStringRef title)
|
||||
{
|
||||
JSC::ExecState *exec = toJS(ctx);
|
||||
JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler();
|
||||
RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, title->string());
|
||||
return convert_to_json(rawProfile.get());
|
||||
}
|
||||
|
||||
JSValueRef nativeProfilerStart(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception) {
|
||||
if (argumentCount < 1) {
|
||||
// Could raise an exception here.
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
|
||||
JSStartProfiling(ctx, title);
|
||||
JSStringRelease(title);
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSValueRef nativeProfilerEnd(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception) {
|
||||
if (argumentCount < 1) {
|
||||
// Could raise an exception here.
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
|
||||
char *rendered = JSEndProfilingAndRender(ctx, title);
|
||||
JSStringRelease(title);
|
||||
JSStringRef profile = JSStringCreateWithUTF8CString(rendered);
|
||||
free(rendered);
|
||||
return JSValueMakeString(ctx, profile);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
HEADER_PATHS := `find ./tmp/JavaScriptCore -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"`
|
||||
CERT ?= "iPhone Developer"
|
||||
|
||||
ios8: prepare build generate
|
||||
|
||||
prepare: clean create download
|
||||
|
||||
build: x86_64 arm64 armv7
|
||||
|
||||
generate: lipo codesign
|
||||
|
||||
clean:
|
||||
@rm -rf tmp/ /tmp/RCTJSCProfiler
|
||||
|
||||
lipo:
|
||||
lipo -create -output /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib ./tmp/RCTJSCProfiler_x86_64 ./tmp/RCTJSCProfiler_arm64 ./tmp/RCTJSCProfiler_armv7
|
||||
|
||||
codesign:
|
||||
codesign -f -s ${CERT} /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib
|
||||
|
||||
create:
|
||||
mkdir -p ./tmp /tmp/RCTJSCProfiler/ ./tmp/CoreFoundation ./tmp/Foundation
|
||||
for file in ./tmp/CoreFoundation/CFUserNotification.h ./tmp/CoreFoundation/CFXMLNode.h ./tmp/CoreFoundation/CFXMLParser.h ./tmp/Foundation/Foundation.h; do echo '' > "$$file"; done
|
||||
|
||||
download: wtf jsc webcore yajl
|
||||
|
||||
wtf:
|
||||
curl -o tmp/WTF.tar.gz http://www.opensource.apple.com/tarballs/WTF/WTF-7600.1.24.tar.gz
|
||||
tar -zxvf tmp/WTF.tar.gz -C tmp
|
||||
|
||||
jsc:
|
||||
curl -o tmp/JSC.tar.gz http://www.opensource.apple.com/tarballs/JavaScriptCore/JavaScriptCore-7600.1.17.tar.gz
|
||||
tar -zxvf tmp/JSC.tar.gz -C tmp
|
||||
mv ./tmp/JavaScriptCore-7600.1.17 ./tmp/JavaScriptCore
|
||||
python ./tmp/JavaScriptCore/generate-bytecode-files --bytecodes_h ./tmp/JavaScriptCore/Bytecodes.h ./tmp/JavaScriptCore/bytecode/BytecodeList.json
|
||||
|
||||
webcore:
|
||||
curl -o tmp/WebCore.tar.gz http://www.opensource.apple.com/tarballs/WebCore/WebCore-7600.1.25.tar.gz
|
||||
tar -zxvf tmp/WebCore.tar.gz -C tmp
|
||||
|
||||
yajl:
|
||||
curl -o tmp/yajl.tar.gz https://codeload.github.com/lloyd/yajl/tar.gz/2.1.0
|
||||
tar -zxvf tmp/yajl.tar.gz -C tmp
|
||||
mkdir -p ./tmp/yajl-2.1.0/build && cd ./tmp/yajl-2.1.0/build && cmake .. && make
|
||||
echo `find . -name '*.c'`
|
||||
cd ./tmp/yajl-2.1.0/src && \
|
||||
clang -arch arm64 -arch armv7 -std=c99 \
|
||||
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/ \
|
||||
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/machine \
|
||||
-I ../build/yajl-2.1.0/include \
|
||||
-c `find . -name '*.c'`
|
||||
libtool -static -o ./tmp/yajl.a `find ./tmp/yajl-2.1.0/src/ -name '*.o'`
|
||||
|
||||
x86_64:
|
||||
clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_x86_64 -std=c++11 \
|
||||
-install_name RCTJSCProfiler.ios8.dylib \
|
||||
-include ./tmp/JavaScriptCore/config.h \
|
||||
-I ./tmp \
|
||||
-I ./tmp/WebCore-7600.1.25/icu \
|
||||
-I ./tmp/WTF-7600.1.24 \
|
||||
-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
|
||||
-DNDEBUG=1\
|
||||
-miphoneos-version-min=8.0 \
|
||||
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib \
|
||||
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/system \
|
||||
${HEADER_PATHS} \
|
||||
-undefined dynamic_lookup \
|
||||
./JSCLegacyProfiler.mm ./tmp/yajl-2.1.0/build/yajl-2.1.0/lib/libyajl_s.a
|
||||
|
||||
arm64:
|
||||
echo $(HEADER_PATHS)
|
||||
clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_arm64 -std=c++11 \
|
||||
-install_name RCTJSCProfiler.ios8.dylib \
|
||||
-arch arm64 \
|
||||
-include ./tmp/JavaScriptCore/config.h \
|
||||
-I ./tmp \
|
||||
-I ./tmp/WebCore-7600.1.25/icu \
|
||||
-I ./tmp/WTF-7600.1.24 \
|
||||
-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
|
||||
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include \
|
||||
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/machine \
|
||||
-DNDEBUG=1\
|
||||
-miphoneos-version-min=8.0 \
|
||||
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib \
|
||||
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/system \
|
||||
${HEADER_PATHS} \
|
||||
-undefined dynamic_lookup \
|
||||
./JSCLegacyProfiler.mm ./tmp/yajl.a
|
||||
|
||||
armv7:
|
||||
clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_armv7 -std=c++11 \
|
||||
-install_name RCTJSCProfiler.ios8.dylib \
|
||||
-arch armv7 \
|
||||
-include ./tmp/JavaScriptCore/config.h \
|
||||
-I ./tmp \
|
||||
-I ./tmp/WebCore-7600.1.25/icu \
|
||||
-I ./tmp/WTF-7600.1.24 \
|
||||
-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
|
||||
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include \
|
||||
-DNDEBUG=1\
|
||||
-miphoneos-version-min=8.0 \
|
||||
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib \
|
||||
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/system \
|
||||
${HEADER_PATHS} \
|
||||
-undefined dynamic_lookup \
|
||||
./JSCLegacyProfiler.mm ./tmp/yajl.a
|
||||
|
||||
.PHONY: ios8
|
||||
@@ -151,6 +151,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
@@ -190,6 +191,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
@@ -195,6 +196,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
|
||||
@@ -479,7 +479,13 @@ class SpringAnimation extends Animation {
|
||||
if (this._tension !== 0) {
|
||||
isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
|
||||
}
|
||||
|
||||
if (isOvershooting || (isVelocity && isDisplacement)) {
|
||||
if (this._tension !== 0) {
|
||||
// Ensure that we end up with a round value
|
||||
this._onUpdate(this._toValue);
|
||||
}
|
||||
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
@@ -1177,7 +1183,7 @@ var parallel = function(
|
||||
}
|
||||
|
||||
animations.forEach((animation, idx) => {
|
||||
animation.start(endResult => {
|
||||
var cb = function(endResult) {
|
||||
hasEnded[idx] = true;
|
||||
doneCount++;
|
||||
if (doneCount === animations.length) {
|
||||
@@ -1189,7 +1195,13 @@ var parallel = function(
|
||||
if (!endResult.finished && stopTogether) {
|
||||
result.stop();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!animation) {
|
||||
cb({finished: true});
|
||||
} else {
|
||||
animation.start(cb);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -127,6 +127,19 @@ describe('Animated', () => {
|
||||
Animated.spring(anim, {toValue: 0, velocity: 0}).start(callback);
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
it('send toValue when a spring stops', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
var listener = jest.genMockFunction();
|
||||
anim.addListener(listener);
|
||||
Animated.spring(anim, {toValue: 15}).start();
|
||||
jest.runAllTimers();
|
||||
var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value;
|
||||
expect(lastValue).not.toBe(15);
|
||||
expect(lastValue).toBeCloseTo(15);
|
||||
expect(anim.__getValue()).toBe(15);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -205,6 +218,16 @@ describe('Animated Parallel', () => {
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('works with an empty element in array', () => {
|
||||
var anim1 = {start: jest.genMockFunction()};
|
||||
var cb = jest.genMockFunction();
|
||||
Animated.parallel([null, anim1]).start(cb);
|
||||
|
||||
expect(anim1.start).toBeCalled();
|
||||
anim1.start.mock.calls[0][0]({finished: true});
|
||||
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('parellelizes well', () => {
|
||||
var anim1 = {start: jest.genMockFunction()};
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimationExperimental
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RCTAnimationManager = require('NativeModules').AnimationExperimentalManager;
|
||||
if (!RCTAnimationManager) {
|
||||
// AnimationExperimental isn't available internally - this is a temporary
|
||||
// workaround to enable its availability to be determined at runtime.
|
||||
// For Flow let's pretend like we always export AnimationExperimental
|
||||
// so all our users don't need to do null checks
|
||||
module.exports = null;
|
||||
} else {
|
||||
|
||||
var React = require('React');
|
||||
var AnimationUtils = require('AnimationUtils');
|
||||
|
||||
type EasingFunction = (t: number) => number;
|
||||
|
||||
var Properties = {
|
||||
opacity: true,
|
||||
position: true,
|
||||
positionX: true,
|
||||
positionY: true,
|
||||
rotation: true,
|
||||
scaleXY: true,
|
||||
};
|
||||
|
||||
type ValueType = number | Array<number> | {[key: string]: number};
|
||||
|
||||
/**
|
||||
* This is an experimental module that is under development, incomplete,
|
||||
* potentially buggy, not used in any production apps, and will probably change
|
||||
* in non-backward compatible ways.
|
||||
*
|
||||
* Use at your own risk.
|
||||
*/
|
||||
var AnimationExperimental = {
|
||||
startAnimation: function(
|
||||
anim: {
|
||||
node: any;
|
||||
duration: number;
|
||||
easing: ($Enum<typeof AnimationUtils.Defaults> | EasingFunction);
|
||||
property: $Enum<typeof Properties>;
|
||||
toValue: ValueType;
|
||||
fromValue?: ValueType;
|
||||
delay?: number;
|
||||
},
|
||||
callback?: ?(finished: bool) => void
|
||||
): number {
|
||||
var nodeHandle = React.findNodeHandle(anim.node);
|
||||
var easingSample = AnimationUtils.evaluateEasingFunction(
|
||||
anim.duration,
|
||||
anim.easing
|
||||
);
|
||||
var tag: number = AnimationUtils.allocateTag();
|
||||
var props = {};
|
||||
props[anim.property] = {to: anim.toValue};
|
||||
RCTAnimationManager.startAnimation(
|
||||
nodeHandle,
|
||||
tag,
|
||||
anim.duration,
|
||||
anim.delay,
|
||||
easingSample,
|
||||
props,
|
||||
callback
|
||||
);
|
||||
return tag;
|
||||
},
|
||||
|
||||
stopAnimation: function(tag: number) {
|
||||
RCTAnimationManager.stopAnimation(tag);
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
if (RCTAnimationManager && RCTAnimationManager.Properties) {
|
||||
var a = Object.keys(Properties);
|
||||
var b = RCTAnimationManager.Properties;
|
||||
var diff = a.filter((i) => b.indexOf(i) < 0).concat(
|
||||
b.filter((i) => a.indexOf(i) < 0)
|
||||
);
|
||||
if (diff.length > 0) {
|
||||
throw new Error('JS animation properties don\'t match native properties.' +
|
||||
JSON.stringify(diff, null, ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimationExperimental;
|
||||
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* The easing functions come from the the jQuery UI project.
|
||||
* See http://api.jqueryui.com/easings/
|
||||
* Copyright jQuery Foundation and other contributors, https://jquery.org/
|
||||
* Copyright (c) 2008 George McGinley Smith
|
||||
* Copyright (c) 2001 Robert Penner
|
||||
*
|
||||
* @providesModule AnimationUtils
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
type EasingFunction = (t: number) => number;
|
||||
|
||||
var defaults = {
|
||||
linear: function(t: number): number {
|
||||
return t;
|
||||
},
|
||||
easeInQuad: function(t: number): number {
|
||||
return t * t;
|
||||
},
|
||||
easeOutQuad: function(t: number): number {
|
||||
return -t * (t - 2);
|
||||
},
|
||||
easeInOutQuad: function(t: number): number {
|
||||
t = t * 2;
|
||||
if (t < 1) {
|
||||
return 0.5 * t * t;
|
||||
}
|
||||
return -((t - 1) * (t - 3) - 1) / 2;
|
||||
},
|
||||
easeInCubic: function(t: number): number {
|
||||
return t * t * t;
|
||||
},
|
||||
easeOutCubic: function(t: number): number {
|
||||
t -= 1;
|
||||
return t * t * t + 1;
|
||||
},
|
||||
easeInOutCubic: function(t: number): number {
|
||||
t *= 2;
|
||||
if (t < 1) {
|
||||
return 0.5 * t * t * t;
|
||||
}
|
||||
t -= 2;
|
||||
return (t * t * t + 2) / 2;
|
||||
},
|
||||
easeInQuart: function(t: number): number {
|
||||
return t * t * t * t;
|
||||
},
|
||||
easeOutQuart: function(t: number): number {
|
||||
t -= 1;
|
||||
return -(t * t * t * t - 1);
|
||||
},
|
||||
easeInOutQuart: function(t: number): number {
|
||||
t *= 2;
|
||||
if (t < 1) {
|
||||
return 0.5 * t * t * t * t;
|
||||
}
|
||||
t -= 2;
|
||||
return -(t * t * t * t - 2) / 2;
|
||||
},
|
||||
easeInQuint: function(t: number): number {
|
||||
return t * t * t * t * t;
|
||||
},
|
||||
easeOutQuint: function(t: number): number {
|
||||
t -= 1;
|
||||
return t * t * t * t * t + 1;
|
||||
},
|
||||
easeInOutQuint: function(t: number): number {
|
||||
t *= 2;
|
||||
if (t < 1) {
|
||||
return (t * t * t * t * t) / 2;
|
||||
}
|
||||
t -= 2;
|
||||
return (t * t * t * t * t + 2) / 2;
|
||||
},
|
||||
easeInSine: function(t: number): number {
|
||||
return -Math.cos(t * (Math.PI / 2)) + 1;
|
||||
},
|
||||
easeOutSine: function(t: number): number {
|
||||
return Math.sin(t * (Math.PI / 2));
|
||||
},
|
||||
easeInOutSine: function(t: number): number {
|
||||
return -(Math.cos(Math.PI * t) - 1) / 2;
|
||||
},
|
||||
easeInExpo: function(t: number): number {
|
||||
return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
|
||||
},
|
||||
easeOutExpo: function(t: number): number {
|
||||
return (t === 1) ? 1 : (-Math.pow(2, -10 * t) + 1);
|
||||
},
|
||||
easeInOutExpo: function(t: number): number {
|
||||
if (t === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (t === 1) {
|
||||
return 1;
|
||||
}
|
||||
t *= 2;
|
||||
if (t < 1) {
|
||||
return 0.5 * Math.pow(2, 10 * (t - 1));
|
||||
}
|
||||
return (-Math.pow(2, -10 * (t - 1)) + 2) / 2;
|
||||
},
|
||||
easeInCirc: function(t: number): number {
|
||||
return -(Math.sqrt(1 - t * t) - 1);
|
||||
},
|
||||
easeOutCirc: function(t: number): number {
|
||||
t -= 1;
|
||||
return Math.sqrt(1 - t * t);
|
||||
},
|
||||
easeInOutCirc: function(t: number): number {
|
||||
t *= 2;
|
||||
if (t < 1) {
|
||||
return -(Math.sqrt(1 - t * t) - 1) / 2;
|
||||
}
|
||||
t -= 2;
|
||||
return (Math.sqrt(1 - t * t) + 1) / 2;
|
||||
},
|
||||
easeInElastic: function(t: number): number {
|
||||
var s = 1.70158;
|
||||
var p = 0.3;
|
||||
if (t === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (t === 1) {
|
||||
return 1;
|
||||
}
|
||||
var s = p / (2 * Math.PI) * Math.asin(1);
|
||||
t -= 1;
|
||||
return -(Math.pow(2, 10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
|
||||
},
|
||||
easeOutElastic: function(t: number): number {
|
||||
var s = 1.70158;
|
||||
var p = 0.3;
|
||||
if (t === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (t === 1) {
|
||||
return 1;
|
||||
}
|
||||
var s = p / (2 * Math.PI) * Math.asin(1);
|
||||
return Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
|
||||
},
|
||||
easeInOutElastic: function(t: number): number {
|
||||
var s = 1.70158;
|
||||
var p = 0.3 * 1.5;
|
||||
if (t === 0) {
|
||||
return 0;
|
||||
}
|
||||
t *= 2;
|
||||
if (t === 2) {
|
||||
return 1;
|
||||
}
|
||||
var s = p / (2 * Math.PI) * Math.asin(1);
|
||||
if (t < 1) {
|
||||
t -= 1;
|
||||
return -(Math.pow(2, 10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)) / 2;
|
||||
}
|
||||
t -= 1;
|
||||
return Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) / 2 + 1;
|
||||
},
|
||||
easeInBack: function(t: number): number {
|
||||
var s = 1.70158;
|
||||
return t * t * ((s + 1) * t - s);
|
||||
},
|
||||
easeOutBack: function(t: number): number {
|
||||
var s = 1.70158;
|
||||
t -= 1;
|
||||
return (t * t * ((s + 1) * t + s) + 1);
|
||||
},
|
||||
easeInOutBack: function(t: number): number {
|
||||
var s = 1.70158 * 1.525;
|
||||
t *= 2;
|
||||
if (t < 1) {
|
||||
return (t * t * ((s + 1) * t - s)) / 2;
|
||||
}
|
||||
t -= 2;
|
||||
return (t * t * ((s + 1) * t + s) + 2) / 2;
|
||||
},
|
||||
easeInBounce: function(t: number): number {
|
||||
return 1 - this.easeOutBounce(1 - t);
|
||||
},
|
||||
easeOutBounce: function(t: number): number {
|
||||
if (t < (1 / 2.75)) {
|
||||
return 7.5625 * t * t;
|
||||
} else if (t < (2 / 2.75)) {
|
||||
t -= 1.5 / 2.75;
|
||||
return 7.5625 * t * t + 0.75;
|
||||
} else if (t < (2.5 / 2.75)) {
|
||||
t -= 2.25 / 2.75;
|
||||
return 7.5625 * t * t + 0.9375;
|
||||
} else {
|
||||
t -= 2.625 / 2.75;
|
||||
return 7.5625 * t * t + 0.984375;
|
||||
}
|
||||
},
|
||||
easeInOutBounce: function(t: number): number {
|
||||
if (t < 0.5) {
|
||||
return this.easeInBounce(t * 2) / 2;
|
||||
}
|
||||
return this.easeOutBounce(t * 2 - 1) / 2 + 0.5;
|
||||
},
|
||||
};
|
||||
|
||||
var ticksPerSecond = 60;
|
||||
var lastUsedTag = 0;
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns a new unique animation tag.
|
||||
*/
|
||||
allocateTag: function(): number {
|
||||
return ++lastUsedTag;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the values of the easing function at discrete ticks (60 per second).
|
||||
*/
|
||||
evaluateEasingFunction: function(duration: number, easing: string | EasingFunction): Array<number> {
|
||||
if (typeof easing === 'string') {
|
||||
easing = defaults[easing] || defaults.easeOutQuad;
|
||||
}
|
||||
|
||||
var tickCount = Math.round(duration * ticksPerSecond / 1000);
|
||||
var samples = [];
|
||||
if (tickCount > 0) {
|
||||
for (var i = 0; i <= tickCount; i++) {
|
||||
samples.push(easing.call(defaults, i / tickCount));
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
},
|
||||
|
||||
Defaults: defaults,
|
||||
};
|
||||
@@ -1,250 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
13BE3DEE1AC21097009241FE /* RCTAnimationExperimentalManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* RCTAnimationExperimentalManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
58B511D91A9E6C8500147676 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
134814201AA4EA6300B7C361 /* libRCTAnimationExperimental.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAnimationExperimental.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13BE3DEC1AC21097009241FE /* RCTAnimationExperimentalManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationExperimentalManager.h; sourceTree = "<group>"; };
|
||||
13BE3DED1AC21097009241FE /* RCTAnimationExperimentalManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationExperimentalManager.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
58B511D81A9E6C8500147676 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
134814211AA4EA7D00B7C361 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
134814201AA4EA6300B7C361 /* libRCTAnimationExperimental.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58B511D21A9E6C8500147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13BE3DEC1AC21097009241FE /* RCTAnimationExperimentalManager.h */,
|
||||
13BE3DED1AC21097009241FE /* RCTAnimationExperimentalManager.m */,
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
58B511DA1A9E6C8500147676 /* RCTAnimationExperimental */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimationExperimental" */;
|
||||
buildPhases = (
|
||||
58B511D71A9E6C8500147676 /* Sources */,
|
||||
58B511D81A9E6C8500147676 /* Frameworks */,
|
||||
58B511D91A9E6C8500147676 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTAnimationExperimental;
|
||||
productName = RCTDataManager;
|
||||
productReference = 134814201AA4EA6300B7C361 /* libRCTAnimationExperimental.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
58B511D31A9E6C8500147676 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0610;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
58B511DA1A9E6C8500147676 = {
|
||||
CreatedOnToolsVersion = 6.1.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimationExperimental" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 58B511D21A9E6C8500147676;
|
||||
productRefGroup = 58B511D21A9E6C8500147676;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
58B511DA1A9E6C8500147676 /* RCTAnimationExperimental */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
58B511D71A9E6C8500147676 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13BE3DEE1AC21097009241FE /* RCTAnimationExperimentalManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
58B511ED1A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
58B511EE1A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../React/**",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTAnimationExperimental;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../React/**",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTAnimationExperimental;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimationExperimental" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511ED1A9E6C8500147676 /* Debug */,
|
||||
58B511EE1A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimationExperimental" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511F01A9E6C8500147676 /* Debug */,
|
||||
58B511F11A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTAnimationExperimentalManager.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
#define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_D
|
||||
#else
|
||||
#define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_F
|
||||
#endif
|
||||
|
||||
@implementation RCTAnimationExperimentalManager
|
||||
{
|
||||
RCTSparseArray *_animationRegistry; // Main thread only; animation tag -> view tag
|
||||
RCTSparseArray *_callbackRegistry; // Main thread only; animation tag -> callback
|
||||
NSDictionary *_keypathMapping;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_animationRegistry = [[RCTSparseArray alloc] init];
|
||||
_callbackRegistry = [[RCTSparseArray alloc] init];
|
||||
_keypathMapping = @{
|
||||
@"opacity": @{
|
||||
@"keypath": @"opacity",
|
||||
@"type": @"NSNumber",
|
||||
},
|
||||
@"position": @{
|
||||
@"keypath": @"position",
|
||||
@"type": @"CGPoint",
|
||||
},
|
||||
@"positionX": @{
|
||||
@"keypath": @"position.x",
|
||||
@"type": @"NSNumber",
|
||||
},
|
||||
@"positionY": @{
|
||||
@"keypath": @"position.y",
|
||||
@"type": @"NSNumber",
|
||||
},
|
||||
@"rotation": @{
|
||||
@"keypath": @"transform.rotation.z",
|
||||
@"type": @"NSNumber",
|
||||
},
|
||||
@"scaleXY": @{
|
||||
@"keypath": @"transform.scale",
|
||||
@"type": @"CGPoint",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return _bridge.uiManager.methodQueue;
|
||||
}
|
||||
|
||||
- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName
|
||||
{
|
||||
if (count == 1) {
|
||||
CGFloat from = *fromArray, to = *toArray, delta = to - from;
|
||||
return ^(CGFloat t) {
|
||||
return @(from + t * delta);
|
||||
};
|
||||
}
|
||||
|
||||
CG_APPEND(vDSP_vsub,,D)(fromArray, 1, toArray, 1, toArray, 1, count);
|
||||
|
||||
const size_t size = count * sizeof(CGFloat);
|
||||
NSData *deltaData = [NSData dataWithBytes:toArray length:size];
|
||||
NSData *fromData = [NSData dataWithBytes:fromArray length:size];
|
||||
|
||||
return ^(CGFloat t) {
|
||||
const CGFloat *delta = deltaData.bytes;
|
||||
const CGFloat *_fromArray = fromData.bytes;
|
||||
|
||||
CGFloat value[count];
|
||||
CG_APPEND(vDSP_vma,,D)(delta, 1, &t, 0, _fromArray, 1, value, 1, count);
|
||||
return [NSValue valueWithBytes:value objCType:typeName];
|
||||
};
|
||||
}
|
||||
|
||||
static void RCTInvalidAnimationProp(RCTSparseArray *callbacks, NSNumber *tag, NSString *key, id value)
|
||||
{
|
||||
RCTResponseSenderBlock callback = callbacks[tag];
|
||||
RCTLogError(@"Invalid animation property `%@ = %@`", key, value);
|
||||
if (callback) {
|
||||
callback(@[@NO]);
|
||||
callbacks[tag] = nil;
|
||||
}
|
||||
[CATransaction commit];
|
||||
return;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag
|
||||
animationTag:(NSNumber *)animationTag
|
||||
duration:(NSTimeInterval)duration
|
||||
delay:(NSTimeInterval)delay
|
||||
easingSample:(NSNumberArray *)easingSample
|
||||
properties:(NSDictionary *)properties
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
__weak RCTAnimationExperimentalManager *weakSelf = self;
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTAnimationExperimentalManager *strongSelf = weakSelf;
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view) {
|
||||
RCTLogWarn(@"React tag #%@ is not registered with the view registry", reactTag);
|
||||
return;
|
||||
}
|
||||
__block BOOL completionBlockSet = NO;
|
||||
[CATransaction begin];
|
||||
for (NSString *prop in properties) {
|
||||
NSString *keypath = _keypathMapping[prop][@"keypath"];
|
||||
id obj = properties[prop][@"to"];
|
||||
if (!keypath) {
|
||||
return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj);
|
||||
}
|
||||
NSValue *toValue = nil;
|
||||
if ([keypath isEqualToString:@"transform.scale"]) {
|
||||
CGPoint point = [RCTConvert CGPoint:obj];
|
||||
if (point.x != point.y) {
|
||||
return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj);
|
||||
}
|
||||
toValue = @(point.x);
|
||||
} else if ([obj respondsToSelector:@selector(count)]) {
|
||||
switch ([obj count]) {
|
||||
case 2:
|
||||
if (![obj respondsToSelector:@selector(objectForKeyedSubscript:)] || obj[@"x"]) {
|
||||
toValue = [NSValue valueWithCGPoint:[RCTConvert CGPoint:obj]];
|
||||
} else {
|
||||
toValue = [NSValue valueWithCGSize:[RCTConvert CGSize:obj]];
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
toValue = [NSValue valueWithCGRect:[RCTConvert CGRect:obj]];
|
||||
break;
|
||||
case 16:
|
||||
toValue = [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:obj]];
|
||||
break;
|
||||
default:
|
||||
return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj);
|
||||
}
|
||||
} else if (![obj respondsToSelector:@selector(objCType)]) {
|
||||
return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, obj);
|
||||
}
|
||||
if (!toValue) {
|
||||
toValue = obj;
|
||||
}
|
||||
const char *typeName = toValue.objCType;
|
||||
|
||||
size_t count;
|
||||
switch (typeName[0]) {
|
||||
case 'i':
|
||||
case 'I':
|
||||
case 's':
|
||||
case 'S':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'q':
|
||||
case 'Q':
|
||||
count = 1;
|
||||
break;
|
||||
|
||||
default: {
|
||||
NSUInteger size;
|
||||
NSGetSizeAndAlignment(typeName, &size, NULL);
|
||||
count = size / sizeof(CGFloat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat toFields[count];
|
||||
|
||||
switch (typeName[0]) {
|
||||
#define CASE(encoding, type) \
|
||||
case encoding: { \
|
||||
type value; \
|
||||
[toValue getValue:&value]; \
|
||||
toFields[0] = value; \
|
||||
break; \
|
||||
}
|
||||
|
||||
CASE('i', int)
|
||||
CASE('I', unsigned int)
|
||||
CASE('s', short)
|
||||
CASE('S', unsigned short)
|
||||
CASE('l', long)
|
||||
CASE('L', unsigned long)
|
||||
CASE('q', long long)
|
||||
CASE('Q', unsigned long long)
|
||||
|
||||
#undef CASE
|
||||
|
||||
default:
|
||||
[toValue getValue:toFields];
|
||||
break;
|
||||
}
|
||||
|
||||
NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:keypath];
|
||||
#if !CGFLOAT_IS_DOUBLE
|
||||
if ([fromValue isKindOfClass:[NSNumber class]]) {
|
||||
fromValue = [NSNumber numberWithFloat:[(NSNumber *)fromValue doubleValue]];
|
||||
}
|
||||
#endif
|
||||
CGFloat fromFields[count];
|
||||
[fromValue getValue:fromFields];
|
||||
|
||||
id (^interpolationBlock)(CGFloat t) = [strongSelf interpolateFrom:fromFields to:toFields count:count typeName:typeName];
|
||||
|
||||
NSMutableArray *sampledValues = [NSMutableArray arrayWithCapacity:easingSample.count];
|
||||
for (NSNumber *sample in easingSample) {
|
||||
CGFloat t = sample.CG_APPEND(, floatValue, doubleValue);
|
||||
[sampledValues addObject:interpolationBlock(t)];
|
||||
}
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
animation.beginTime = CACurrentMediaTime() + delay;
|
||||
animation.duration = duration;
|
||||
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
animation.values = sampledValues;
|
||||
@try {
|
||||
[view.layer setValue:toValue forKey:keypath];
|
||||
NSString *animationKey = [@"RCT" stringByAppendingString:RCTJSONStringify(@{@"tag": animationTag, @"key": keypath}, nil)];
|
||||
if (!completionBlockSet) {
|
||||
strongSelf->_callbackRegistry[animationTag] = callback;
|
||||
[CATransaction setCompletionBlock:^{
|
||||
RCTResponseSenderBlock cb = strongSelf->_callbackRegistry[animationTag];
|
||||
if (cb) {
|
||||
cb(@[@YES]);
|
||||
strongSelf->_callbackRegistry[animationTag] = nil;
|
||||
}
|
||||
}];
|
||||
completionBlockSet = YES;
|
||||
}
|
||||
[view.layer addAnimation:animation forKey:animationKey];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, toValue);
|
||||
}
|
||||
}
|
||||
[CATransaction commit];
|
||||
strongSelf->_animationRegistry[animationTag] = reactTag;
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag)
|
||||
{
|
||||
__weak RCTAnimationExperimentalManager *weakSelf = self;
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTAnimationExperimentalManager *strongSelf = weakSelf;
|
||||
|
||||
NSNumber *reactTag = strongSelf->_animationRegistry[animationTag];
|
||||
if (!reactTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
for (NSString *animationKey in view.layer.animationKeys) {
|
||||
if ([animationKey hasPrefix:@"RCT{"]) {
|
||||
NSDictionary *data = RCTJSONParse([animationKey substringFromIndex:3], nil);
|
||||
if (animationTag.integerValue == [data[@"tag"] integerValue]) {
|
||||
[view.layer removeAnimationForKey:animationKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
RCTResponseSenderBlock cb = strongSelf->_callbackRegistry[animationTag];
|
||||
if (cb) {
|
||||
cb(@[@NO]);
|
||||
strongSelf->_callbackRegistry[animationTag] = nil;
|
||||
}
|
||||
strongSelf->_animationRegistry[animationTag] = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
return @{@"Properties": [_keypathMapping allKeys] };
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -109,17 +109,28 @@ var getPhotosReturnChecker = createStrictShapeTypeChecker({
|
||||
}).isRequired,
|
||||
});
|
||||
|
||||
/**
|
||||
* `CameraRoll` provides access to the local camera roll / gallery.
|
||||
*/
|
||||
class CameraRoll {
|
||||
|
||||
static GroupTypesOptions: Array<string>;
|
||||
static AssetTypeOptions: Array<string>;
|
||||
/**
|
||||
* Saves the image with tag `tag` to the camera roll.
|
||||
* Saves the image to the camera roll / gallery.
|
||||
*
|
||||
* @param {string} tag - Can be any of the three kinds of tags we accept:
|
||||
* 1. URL
|
||||
* 2. assets-library tag
|
||||
* 3. tag returned from storing an image in memory
|
||||
* @param {string} tag On Android, this is a local URI, such
|
||||
* as `"file:///sdcard/img.png"`.
|
||||
*
|
||||
* On iOS, the tag can be one of the following:
|
||||
*
|
||||
* - local URI
|
||||
* - assets-library tag
|
||||
* - a tag not maching any of the above, which means the image data will
|
||||
* be stored in memory (and consume memory as long as the process is alive)
|
||||
*
|
||||
* @param successCallback Invoked with the value of `tag` on success.
|
||||
* @param errorCallback Invoked with error message on error.
|
||||
*/
|
||||
static saveImageWithTag(tag, successCallback, errorCallback) {
|
||||
invariant(
|
||||
@@ -140,10 +151,10 @@ class CameraRoll {
|
||||
* Invokes `callback` with photo identifier objects from the local camera
|
||||
* roll of the device matching shape defined by `getPhotosReturnChecker`.
|
||||
*
|
||||
* @param {object} params - See `getPhotosParamChecker`.
|
||||
* @param {function} callback - Invoked with arg of shape defined by
|
||||
* `getPhotosReturnChecker` on success.
|
||||
* @param {function} errorCallback - Invoked with error message on error.
|
||||
* @param {object} params See `getPhotosParamChecker`.
|
||||
* @param {function} callback Invoked with arg of shape defined by
|
||||
* `getPhotosReturnChecker` on success.
|
||||
* @param {function} errorCallback Invoked with error message on error.
|
||||
*/
|
||||
static getPhotos(params, callback, errorCallback) {
|
||||
var metaCallback = callback;
|
||||
|
||||
@@ -18,7 +18,6 @@ var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
||||
var GRAY = '#999999';
|
||||
|
||||
@@ -99,15 +98,8 @@ var styles = StyleSheet.create({
|
||||
|
||||
var RCTActivityIndicatorView = requireNativeComponent(
|
||||
'RCTActivityIndicatorView',
|
||||
null
|
||||
ActivityIndicatorIOS,
|
||||
{nativeOnly: {activityIndicatorViewStyle: true}},
|
||||
);
|
||||
if (__DEV__) {
|
||||
var nativeOnlyProps = {activityIndicatorViewStyle: true};
|
||||
verifyPropTypes(
|
||||
ActivityIndicatorIOS,
|
||||
RCTActivityIndicatorView.viewConfig,
|
||||
nativeOnlyProps
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = ActivityIndicatorIOS;
|
||||
|
||||
@@ -15,17 +15,14 @@ var EventEmitter = require('EventEmitter');
|
||||
var Image = require('Image');
|
||||
var NavigationContext = require('NavigationContext');
|
||||
var React = require('React');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var RCTNavigatorManager = require('NativeModules').NavigatorManager;
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
var View = require('View');
|
||||
|
||||
var createReactNativeComponentClass =
|
||||
require('createReactNativeComponentClass');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var invariant = require('invariant');
|
||||
var logError = require('logError');
|
||||
var merge = require('merge');
|
||||
|
||||
var TRANSITIONER_REF = 'transitionerRef';
|
||||
|
||||
@@ -36,36 +33,6 @@ function getuid() {
|
||||
return __uid++;
|
||||
}
|
||||
|
||||
var RCTNavigator = createReactNativeComponentClass({
|
||||
validAttributes: merge(ReactNativeViewAttributes.UIView, {
|
||||
requestedTopOfStack: true
|
||||
}),
|
||||
uiViewClassName: 'RCTNavigator',
|
||||
});
|
||||
|
||||
var RCTNavigatorItem = createReactNativeComponentClass({
|
||||
validAttributes: {
|
||||
// TODO: Remove or fix the attributes that are not fully functional.
|
||||
// NavigatorIOS does not use them all, because some are problematic
|
||||
title: true,
|
||||
barTintColor: true,
|
||||
leftButtonIcon: true,
|
||||
leftButtonTitle: true,
|
||||
onNavLeftButtonTap: true,
|
||||
rightButtonIcon: true,
|
||||
rightButtonTitle: true,
|
||||
onNavRightButtonTap: true,
|
||||
backButtonIcon: true,
|
||||
backButtonTitle: true,
|
||||
tintColor: true,
|
||||
translucent: true,
|
||||
navigationBarHidden: true,
|
||||
titleTextColor: true,
|
||||
style: true,
|
||||
},
|
||||
uiViewClassName: 'RCTNavItem',
|
||||
});
|
||||
|
||||
var NavigatorTransitionerIOS = React.createClass({
|
||||
requestSchedulingNavigation: function(cb) {
|
||||
RCTNavigatorManager.requestSchedulingJavaScriptNavigation(
|
||||
@@ -281,6 +248,11 @@ var NavigatorIOS = React.createClass({
|
||||
*/
|
||||
navigationBarHidden: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* A Boolean value that indicates whether to hide the 1px hairline shadow
|
||||
*/
|
||||
shadowHidden: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The default wrapper style for components in the navigator.
|
||||
* A common use case is to set the backgroundColor for every page
|
||||
@@ -640,6 +612,7 @@ var NavigatorIOS = React.createClass({
|
||||
rightButtonTitle={route.rightButtonTitle}
|
||||
onNavRightButtonTap={route.onRightButtonPress}
|
||||
navigationBarHidden={this.props.navigationBarHidden}
|
||||
shadowHidden={this.props.shadowHidden}
|
||||
tintColor={this.props.tintColor}
|
||||
barTintColor={this.props.barTintColor}
|
||||
translucent={this.props.translucent !== false}
|
||||
@@ -704,4 +677,7 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTNavigator = requireNativeComponent('RCTNavigator');
|
||||
var RCTNavigatorItem = requireNativeComponent('RCTNavItem');
|
||||
|
||||
module.exports = NavigatorIOS;
|
||||
|
||||
@@ -19,7 +19,6 @@ var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
||||
/**
|
||||
* Use `ProgressViewIOS` to render a UIProgressView on iOS.
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
'use strict';
|
||||
|
||||
var NativeModules = require('NativeModules');
|
||||
var Platform = require('Platform');
|
||||
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
|
||||
var React = require('React');
|
||||
var Subscribable = require('Subscribable');
|
||||
var TextInputState = require('TextInputState');
|
||||
|
||||
var RCTUIManager = NativeModules.UIManager;
|
||||
var RCTUIManagerDeprecated = NativeModules.UIManager;
|
||||
var RCTScrollViewConsts = RCTUIManager.RCTScrollView.Constants;
|
||||
|
||||
var warning = require('warning');
|
||||
@@ -351,7 +351,19 @@ var ScrollResponderMixin = {
|
||||
* can also be used to quickly scroll to any element we want to focus
|
||||
*/
|
||||
scrollResponderScrollTo: function(offsetX: number, offsetY: number) {
|
||||
RCTUIManagerDeprecated.scrollTo(React.findNodeHandle(this), offsetX, offsetY);
|
||||
if (Platform.OS === 'android') {
|
||||
RCTUIManager.dispatchViewManagerCommand(
|
||||
React.findNodeHandle(this),
|
||||
RCTUIManager.RCTScrollView.Commands.scrollTo,
|
||||
[offsetX, offsetY],
|
||||
);
|
||||
} else {
|
||||
RCTUIManager.scrollTo(
|
||||
React.findNodeHandle(this),
|
||||
offsetX,
|
||||
offsetY
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -359,7 +371,7 @@ var ScrollResponderMixin = {
|
||||
* @param {object} rect Should have shape {x, y, width, height}
|
||||
*/
|
||||
scrollResponderZoomTo: function(rect: { x: number; y: number; width: number; height: number; }) {
|
||||
RCTUIManagerDeprecated.zoomToRect(React.findNodeHandle(this), rect);
|
||||
RCTUIManager.zoomToRect(React.findNodeHandle(this), rect);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -377,7 +389,7 @@ var ScrollResponderMixin = {
|
||||
this.preventNegativeScrollOffset = !!preventNegativeScrollOffset;
|
||||
RCTUIManager.measureLayout(
|
||||
nodeHandle,
|
||||
React.findNodeHandle(this),
|
||||
React.findNodeHandle(this.getInnerViewNode()),
|
||||
this.scrollResponderTextInputFocusError,
|
||||
this.scrollResponderInputMeasureAndScrollToKeyboard
|
||||
);
|
||||
@@ -429,6 +441,10 @@ var ScrollResponderMixin = {
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this.scrollResponderKeyboardWillHide);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidShow', this.scrollResponderKeyboardDidShow);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidHide', this.scrollResponderKeyboardDidHide);
|
||||
warning(this.getInnerViewNode, 'You need to implement getInnerViewNode in '
|
||||
+ this.constructor.displayName + ' to get full'
|
||||
+ 'functionality from ScrollResponder mixin. See example of ListView and'
|
||||
+ ' ScrollView.');
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -469,9 +485,13 @@ var ScrollResponderMixin = {
|
||||
this.props.onKeyboardWillHide && this.props.onKeyboardWillHide(e);
|
||||
},
|
||||
|
||||
scrollResponderKeyboardDidShow: function() {
|
||||
this.keyboardWillOpenTo = null;
|
||||
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow();
|
||||
scrollResponderKeyboardDidShow: function(e: Event) {
|
||||
// TODO(7693961): The event for DidShow is not available on iOS yet.
|
||||
// Use the one from WillShow and do not assign.
|
||||
if (e) {
|
||||
this.keyboardWillOpenTo = e;
|
||||
}
|
||||
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
|
||||
},
|
||||
|
||||
scrollResponderKeyboardDidHide: function() {
|
||||
|
||||
@@ -17,7 +17,6 @@ var PointPropType = require('PointPropType');
|
||||
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
|
||||
var RCTScrollViewConsts = RCTScrollView.Constants;
|
||||
var React = require('React');
|
||||
var ReactChildren = require('ReactChildren');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var RCTUIManager = require('NativeModules').UIManager;
|
||||
var ScrollResponder = require('ScrollResponder');
|
||||
@@ -54,44 +53,54 @@ var INNERVIEW = 'InnerScrollView';
|
||||
* Doesn't yet support other contained responders from blocking this scroll
|
||||
* view from becoming the responder.
|
||||
*/
|
||||
|
||||
var ScrollView = React.createClass({
|
||||
propTypes: {
|
||||
automaticallyAdjustContentInsets: PropTypes.bool, // true
|
||||
contentInset: EdgeInsetsPropType, // zeros
|
||||
contentOffset: PointPropType, // zeros
|
||||
onScroll: PropTypes.func,
|
||||
onScrollAnimationEnd: PropTypes.func,
|
||||
scrollEnabled: PropTypes.bool, // true
|
||||
scrollIndicatorInsets: EdgeInsetsPropType, // zeros
|
||||
showsHorizontalScrollIndicator: PropTypes.bool,
|
||||
showsVerticalScrollIndicator: PropTypes.bool,
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
scrollEventThrottle: PropTypes.number, // null
|
||||
|
||||
/**
|
||||
* Controls whether iOS should automatically adjust the content inset
|
||||
* for scroll views that are placed behind a navigation bar or
|
||||
* tab bar/ toolbar. The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||
/**
|
||||
* The amount by which the scroll view content is inset from the edges
|
||||
* of the scroll view. Defaults to `{0, 0, 0, 0}`.
|
||||
* @platform ios
|
||||
*/
|
||||
contentInset: EdgeInsetsPropType,
|
||||
/**
|
||||
* Used to manually set the starting scroll offset.
|
||||
* The default value is `{x: 0, y: 0}`.
|
||||
* @platform ios
|
||||
*/
|
||||
contentOffset: PointPropType,
|
||||
/**
|
||||
* When true, the scroll view bounces when it reaches the end of the
|
||||
* content if the content is larger then the scroll view along the axis of
|
||||
* the scroll direction. When false, it disables all bouncing even if
|
||||
* the `alwaysBounce*` props are true. The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
bounces: PropTypes.bool,
|
||||
/**
|
||||
* When true, gestures can drive zoom past min/max and the zoom will animate
|
||||
* to the min/max value at gesture end, otherwise the zoom will not exceed
|
||||
* the limits.
|
||||
* @platform ios
|
||||
*/
|
||||
bouncesZoom: PropTypes.bool,
|
||||
/**
|
||||
* When true, the scroll view bounces horizontally when it reaches the end
|
||||
* even if the content is smaller than the scroll view itself. The default
|
||||
* value is true when `horizontal={true}` and false otherwise.
|
||||
* @platform ios
|
||||
*/
|
||||
alwaysBounceHorizontal: PropTypes.bool,
|
||||
/**
|
||||
* When true, the scroll view bounces vertically when it reaches the end
|
||||
* even if the content is smaller than the scroll view itself. The default
|
||||
* value is false when `horizontal={true}` and true otherwise.
|
||||
* @platform ios
|
||||
*/
|
||||
alwaysBounceVertical: PropTypes.bool,
|
||||
/**
|
||||
@@ -99,6 +108,7 @@ var ScrollView = React.createClass({
|
||||
* content is smaller than the scroll view bounds; when the content is
|
||||
* larger than the scroll view, this property has no effect. The default
|
||||
* value is false.
|
||||
* @platform ios
|
||||
*/
|
||||
centerContent: PropTypes.bool,
|
||||
/**
|
||||
@@ -122,6 +132,7 @@ var ScrollView = React.createClass({
|
||||
* decelerates after the user lifts their finger. Reasonable choices include
|
||||
* - Normal: 0.998 (the default)
|
||||
* - Fast: 0.9
|
||||
* @platform ios
|
||||
*/
|
||||
decelerationRate: PropTypes.number,
|
||||
/**
|
||||
@@ -132,17 +143,19 @@ var ScrollView = React.createClass({
|
||||
/**
|
||||
* When true, the ScrollView will try to lock to only vertical or horizontal
|
||||
* scrolling while dragging. The default value is false.
|
||||
* @platform ios
|
||||
*/
|
||||
directionalLockEnabled: PropTypes.bool,
|
||||
/**
|
||||
* When false, once tracking starts, won't try to drag if the touch moves.
|
||||
* The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
canCancelContentTouches: PropTypes.bool,
|
||||
/**
|
||||
* Determines whether the keyboard gets dismissed in response to a drag.
|
||||
* - 'none' (the default), drags do not dismiss the keyboard.
|
||||
* - 'onDrag', the keyboard is dismissed when a drag begins.
|
||||
* - 'on-drag', the keyboard is dismissed when a drag begins.
|
||||
* - 'interactive', the keyboard is dismissed interactively with the drag
|
||||
* and moves in synchrony with the touch; dragging upwards cancels the
|
||||
* dismissal.
|
||||
@@ -157,35 +170,83 @@ var ScrollView = React.createClass({
|
||||
* is up dismisses the keyboard. When true, the scroll view will not catch
|
||||
* taps, and the keyboard will not dismiss automatically. The default value
|
||||
* is false.
|
||||
* @platform ios
|
||||
*/
|
||||
keyboardShouldPersistTaps: PropTypes.bool,
|
||||
/**
|
||||
* The maximum allowed zoom scale. The default value is 1.0.
|
||||
* @platform ios
|
||||
*/
|
||||
maximumZoomScale: PropTypes.number,
|
||||
/**
|
||||
* The minimum allowed zoom scale. The default value is 1.0.
|
||||
* @platform ios
|
||||
*/
|
||||
minimumZoomScale: PropTypes.number,
|
||||
/**
|
||||
* Fires at most once per frame during scrolling. The frequency of the
|
||||
* events can be contolled using the `scrollEventThrottle` prop.
|
||||
*/
|
||||
onScroll: PropTypes.func,
|
||||
/**
|
||||
* Called when a scrolling animation ends.
|
||||
* @platform ios
|
||||
*/
|
||||
onScrollAnimationEnd: PropTypes.func,
|
||||
/**
|
||||
* When true, the scroll view stops on multiples of the scroll view's size
|
||||
* when scrolling. This can be used for horizontal pagination. The default
|
||||
* value is false.
|
||||
* @platform ios
|
||||
*/
|
||||
pagingEnabled: PropTypes.bool,
|
||||
/**
|
||||
* When false, the content does not scroll.
|
||||
* The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
scrollEnabled: PropTypes.bool,
|
||||
/**
|
||||
* This controls how often the scroll event will be fired while scrolling
|
||||
* (in events per seconds). A higher number yields better accuracy for code
|
||||
* that is tracking the scroll position, but can lead to scroll performance
|
||||
* problems due to the volume of information being send over the bridge.
|
||||
* The default value is zero, which means the scroll event will be sent
|
||||
* only once each time the view is scrolled.
|
||||
* @platform ios
|
||||
*/
|
||||
scrollEventThrottle: PropTypes.number,
|
||||
/**
|
||||
* The amount by which the scroll view indicators are inset from the edges
|
||||
* of the scroll view. This should normally be set to the same value as
|
||||
* the `contentInset`. Defaults to `{0, 0, 0, 0}`.
|
||||
* @platform ios
|
||||
*/
|
||||
scrollIndicatorInsets: EdgeInsetsPropType,
|
||||
/**
|
||||
* When true, the scroll view scrolls to top when the status bar is tapped.
|
||||
* The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
scrollsToTop: PropTypes.bool,
|
||||
/**
|
||||
* When true, shows a horizontal scroll indicator.
|
||||
*/
|
||||
showsHorizontalScrollIndicator: PropTypes.bool,
|
||||
/**
|
||||
* When true, shows a vertical scroll indicator.
|
||||
*/
|
||||
showsVerticalScrollIndicator: PropTypes.bool,
|
||||
/**
|
||||
* An array of child indices determining which children get docked to the
|
||||
* top of the screen when scrolling. For example, passing
|
||||
* `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
|
||||
* top of the scroll view. This property is not supported in conjunction
|
||||
* with `horizontal={true}`.
|
||||
* @platform ios
|
||||
*/
|
||||
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
|
||||
style: StyleSheetPropType(ViewStylePropTypes),
|
||||
/**
|
||||
* Experimental: When true, offscreen child views (whose `overflow` value is
|
||||
* `hidden`) are removed from their native backing superview when offscreen.
|
||||
@@ -195,6 +256,7 @@ var ScrollView = React.createClass({
|
||||
removeClippedSubviews: PropTypes.bool,
|
||||
/**
|
||||
* The current scale of the scroll view content. The default value is 1.0.
|
||||
* @platform ios
|
||||
*/
|
||||
zoomScale: PropTypes.number,
|
||||
},
|
||||
@@ -224,19 +286,8 @@ var ScrollView = React.createClass({
|
||||
},
|
||||
|
||||
scrollTo: function(destY?: number, destX?: number) {
|
||||
if (Platform.OS === 'android') {
|
||||
RCTUIManager.dispatchViewManagerCommand(
|
||||
React.findNodeHandle(this),
|
||||
RCTUIManager.RCTScrollView.Commands.scrollTo,
|
||||
[destX || 0, destY || 0]
|
||||
);
|
||||
} else {
|
||||
RCTUIManager.scrollTo(
|
||||
React.findNodeHandle(this),
|
||||
destX || 0,
|
||||
destY || 0
|
||||
);
|
||||
}
|
||||
// $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now
|
||||
this.getScrollResponder().scrollResponderScrollTo(destX || 0, destY || 0);
|
||||
},
|
||||
|
||||
scrollWithoutAnimationTo: function(destY?: number, destX?: number) {
|
||||
@@ -247,6 +298,21 @@ var ScrollView = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
handleScroll: function(e: Event) {
|
||||
if (__DEV__) {
|
||||
if (this.props.onScroll && !this.props.scrollEventThrottle) {
|
||||
console.log(
|
||||
'You specified `onScroll` on a <ScrollView> but not ' +
|
||||
'`scrollEventThrottle`. You will only receive one event. ' +
|
||||
'Using `16` you get all the events but be aware that it may ' +
|
||||
'cause frame drops, use a bigger number if you don\'t need as ' +
|
||||
'much precision.'
|
||||
);
|
||||
}
|
||||
}
|
||||
this.scrollResponderHandleScroll(e);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var contentContainerStyle = [
|
||||
this.props.horizontal && styles.contentContainerHorizontal,
|
||||
@@ -262,40 +328,13 @@ var ScrollView = React.createClass({
|
||||
') must by applied through the contentContainerStyle prop.'
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (this.props.onScroll && !this.props.scrollEventThrottle) {
|
||||
var onScroll = this.props.onScroll;
|
||||
this.props.onScroll = function() {
|
||||
console.log(
|
||||
'You specified `onScroll` on a <ScrollView> but not ' +
|
||||
'`scrollEventThrottle`. You will only receive one event. ' +
|
||||
'Using `16` you get all the events but be aware that it may ' +
|
||||
'cause frame drops, use a bigger number if you don\'t need as ' +
|
||||
'much precision.'
|
||||
);
|
||||
onScroll.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var children = this.props.children;
|
||||
if (this.props.stickyHeaderIndices) {
|
||||
children = ReactChildren.map(children, (child) => {
|
||||
if (child) {
|
||||
return <View collapsible={false}>{child}</View>;
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var contentContainer =
|
||||
<View
|
||||
collapsible={false}
|
||||
ref={INNERVIEW}
|
||||
style={contentContainerStyle}
|
||||
removeClippedSubviews={this.props.removeClippedSubviews}>
|
||||
{children}
|
||||
{this.props.children}
|
||||
</View>;
|
||||
|
||||
var alwaysBounceHorizontal =
|
||||
@@ -323,7 +362,7 @@ var ScrollView = React.createClass({
|
||||
onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder,
|
||||
onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture,
|
||||
onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder,
|
||||
onScroll: this.scrollResponderHandleScroll,
|
||||
onScroll: this.handleScroll,
|
||||
onResponderGrant: this.scrollResponderHandleResponderGrant,
|
||||
onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest,
|
||||
onResponderTerminate: this.scrollResponderHandleTerminate,
|
||||
|
||||
@@ -18,7 +18,6 @@ var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
||||
type DefaultProps = {
|
||||
values: Array<string>;
|
||||
|
||||
@@ -35,6 +35,10 @@ var StatusBarIOS = {
|
||||
animation = animation || 'none';
|
||||
RCTStatusBarManager.setHidden(hidden, animation);
|
||||
},
|
||||
|
||||
setNetworkActivityIndicatorVisible(visible: boolean) {
|
||||
RCTStatusBarManager.setNetworkActivityIndicatorVisible(visible);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = StatusBarIOS;
|
||||
|
||||
@@ -32,7 +32,11 @@ var TabBarIOS = React.createClass({
|
||||
/**
|
||||
* Background color of the tab bar
|
||||
*/
|
||||
barTintColor: React.PropTypes.string
|
||||
barTintColor: React.PropTypes.string,
|
||||
/**
|
||||
* A Boolean value that indicates whether the tab bar is translucent
|
||||
*/
|
||||
translucent: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
@@ -40,7 +44,8 @@ var TabBarIOS = React.createClass({
|
||||
<RCTTabBar
|
||||
style={[styles.tabGroup, this.props.style]}
|
||||
tintColor={this.props.tintColor}
|
||||
barTintColor={this.props.barTintColor}>
|
||||
barTintColor={this.props.barTintColor}
|
||||
translucent={this.props.translucent !== false}>
|
||||
{this.props.children}
|
||||
</RCTTabBar>
|
||||
);
|
||||
|
||||
@@ -31,8 +31,8 @@ var invariant = require('invariant');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var onlyMultiline = {
|
||||
onSelectionChange: true,
|
||||
onTextInput: true,
|
||||
onSelectionChange: true, // not supported in Open Source yet
|
||||
onTextInput: true, // not supported in Open Source yet
|
||||
children: true,
|
||||
};
|
||||
|
||||
@@ -64,10 +64,6 @@ var viewConfigAndroid = {
|
||||
var RCTTextView = requireNativeComponent('RCTTextView', null);
|
||||
var RCTTextField = requireNativeComponent('RCTTextField', null);
|
||||
|
||||
type DefaultProps = {
|
||||
bufferDelay: number;
|
||||
};
|
||||
|
||||
type Event = Object;
|
||||
|
||||
/**
|
||||
@@ -77,32 +73,30 @@ type Event = Object;
|
||||
* types, such as a numeric keypad.
|
||||
*
|
||||
* The simplest use case is to plop down a `TextInput` and subscribe to the
|
||||
* `onChangeText` events to read the user input. There are also other events, such
|
||||
* as `onSubmitEditing` and `onFocus` that can be subscribed to. A simple
|
||||
* `onChangeText` events to read the user input. There are also other events,
|
||||
* such as `onSubmitEditing` and `onFocus` that can be subscribed to. A simple
|
||||
* example:
|
||||
*
|
||||
* ```
|
||||
* <View>
|
||||
* <TextInput
|
||||
* style={{height: 40, borderColor: 'gray', borderWidth: 1}}
|
||||
* onChangeText={(text) => this.setState({input: text})}
|
||||
* onChangeText={(text) => this.setState({text})}
|
||||
* value={this.state.text}
|
||||
* />
|
||||
* <Text>{'user input: ' + this.state.input}</Text>
|
||||
* </View>
|
||||
* ```
|
||||
*
|
||||
* The `value` prop can be used to set the value of the input in order to make
|
||||
* the state of the component clear, but <TextInput> does not behave as a true
|
||||
* controlled component by default because all operations are asynchronous.
|
||||
* Setting `value` once is like setting the default value, but you can change it
|
||||
* continuously based on `onChangeText` events as well. If you really want to
|
||||
* force the component to always revert to the value you are setting, you can
|
||||
* set `controlled={true}`.
|
||||
* Note that some props are only available with multiline={true/false}:
|
||||
*
|
||||
* The `multiline` prop is not supported in all releases, and some props are
|
||||
* multiline only.
|
||||
* var onlyMultiline = {
|
||||
* onSelectionChange: true, // not supported in Open Source yet
|
||||
* onTextInput: true, // not supported in Open Source yet
|
||||
* children: true,
|
||||
* };
|
||||
*
|
||||
* var notMultiline = {
|
||||
* onSubmitEditing: true,
|
||||
* };
|
||||
*/
|
||||
|
||||
var TextInput = React.createClass({
|
||||
propTypes: {
|
||||
/**
|
||||
@@ -120,32 +114,44 @@ var TextInput = React.createClass({
|
||||
'characters',
|
||||
]),
|
||||
/**
|
||||
* If false, disables auto-correct. Default value is true.
|
||||
* If false, disables auto-correct. The default value is true.
|
||||
*/
|
||||
autoCorrect: PropTypes.bool,
|
||||
/**
|
||||
* If true, focuses the input on componentDidMount. Default value is false.
|
||||
* If true, focuses the input on componentDidMount.
|
||||
* The default value is false.
|
||||
*/
|
||||
autoFocus: PropTypes.bool,
|
||||
/**
|
||||
* Set the position of the cursor from where editing will begin.
|
||||
* @platorm android
|
||||
*/
|
||||
textAlign: PropTypes.oneOf([
|
||||
'start',
|
||||
'center',
|
||||
'end',
|
||||
]),
|
||||
/**
|
||||
* Aligns text vertically within the TextInput.
|
||||
* @platform android
|
||||
*/
|
||||
textAlignVertical: PropTypes.oneOf([
|
||||
'top',
|
||||
'center',
|
||||
'bottom',
|
||||
]),
|
||||
/**
|
||||
* If false, text is not editable. Default value is true.
|
||||
* If false, text is not editable. The default value is true.
|
||||
* @platform ios
|
||||
*/
|
||||
editable: PropTypes.bool,
|
||||
/**
|
||||
* Determines which keyboard to open, e.g.`numeric`.
|
||||
*
|
||||
* The following values work across platforms:
|
||||
* - default
|
||||
* - numeric
|
||||
* - email-address
|
||||
*/
|
||||
keyboardType: PropTypes.oneOf([
|
||||
// Cross-platform
|
||||
@@ -165,6 +171,7 @@ var TextInput = React.createClass({
|
||||
]),
|
||||
/**
|
||||
* Determines how the return key should look.
|
||||
* @platform ios
|
||||
*/
|
||||
returnKeyType: PropTypes.oneOf([
|
||||
'default',
|
||||
@@ -179,13 +186,21 @@ var TextInput = React.createClass({
|
||||
'done',
|
||||
'emergency-call',
|
||||
]),
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* If true, the keyboard disables the return key when there is no text and
|
||||
* automatically enables it when there is text. Default value is false.
|
||||
* automatically enables it when there is text. The default value is false.
|
||||
* @platform ios
|
||||
*/
|
||||
enablesReturnKeyAutomatically: PropTypes.bool,
|
||||
/**
|
||||
* If true, the text input can be multiple lines. Default value is false.
|
||||
* If true, the text input can be multiple lines.
|
||||
* The default value is false.
|
||||
*/
|
||||
multiline: PropTypes.bool,
|
||||
/**
|
||||
@@ -214,14 +229,9 @@ var TextInput = React.createClass({
|
||||
*/
|
||||
onSubmitEditing: PropTypes.func,
|
||||
/**
|
||||
* Invoked on mount and layout changes with {x, y, width, height}.
|
||||
* Invoked on mount and layout changes with `{x, y, width, height}`.
|
||||
*/
|
||||
onLayout: PropTypes.func,
|
||||
/**
|
||||
* If true, the text input obscures the text entered so that sensitive text
|
||||
* like passwords stay secure. Default value is false.
|
||||
*/
|
||||
password: PropTypes.bool,
|
||||
/**
|
||||
* The string that will be rendered before text input has been entered
|
||||
*/
|
||||
@@ -230,30 +240,36 @@ var TextInput = React.createClass({
|
||||
* The text color of the placeholder string
|
||||
*/
|
||||
placeholderTextColor: PropTypes.string,
|
||||
/**
|
||||
* If true, the text input obscures the text entered so that sensitive text
|
||||
* like passwords stay secure. The default value is false.
|
||||
*/
|
||||
secureTextEntry: PropTypes.bool,
|
||||
/**
|
||||
* See DocumentSelectionState.js, some state that is responsible for
|
||||
* maintaining selection information for a document
|
||||
* @platform ios
|
||||
*/
|
||||
selectionState: PropTypes.instanceOf(DocumentSelectionState),
|
||||
/**
|
||||
* The default value for the text input
|
||||
* The value to show for the text input. TextInput is a controlled
|
||||
* component, which means the native value will be forced to match this
|
||||
* value prop if provided. For most uses this works great, but in some
|
||||
* cases this may cause flickering - one common cause is preventing edits
|
||||
* by keeping value the same. In addition to simply setting the same value,
|
||||
* either set `editable={false}`, or set/update `maxLength` to prevent
|
||||
* unwanted edits without flicker.
|
||||
*/
|
||||
value: PropTypes.string,
|
||||
/**
|
||||
* This helps avoid drops characters due to race conditions between JS and
|
||||
* the native text input. The default should be fine, but if you're
|
||||
* potentially doing very slow operations on every keystroke then you may
|
||||
* want to try increasing this.
|
||||
* Provides an initial value that will change when the user starts typing.
|
||||
* Useful for simple use-cases where you don't want to deal with listening
|
||||
* to events and updating the value prop to keep the controlled state in sync.
|
||||
*/
|
||||
bufferDelay: PropTypes.number,
|
||||
/**
|
||||
* If you really want this to behave as a controlled component, you can set
|
||||
* this true, but you will probably see flickering, dropped keystrokes,
|
||||
* and/or laggy typing, depending on how you process onChange events.
|
||||
*/
|
||||
controlled: PropTypes.bool,
|
||||
defaultValue: PropTypes.string,
|
||||
/**
|
||||
* When the clear button should appear on the right side of the text view
|
||||
* @platform ios
|
||||
*/
|
||||
clearButtonMode: PropTypes.oneOf([
|
||||
'never',
|
||||
@@ -263,10 +279,12 @@ var TextInput = React.createClass({
|
||||
]),
|
||||
/**
|
||||
* If true, clears the text field automatically when editing begins
|
||||
* @platform ios
|
||||
*/
|
||||
clearTextOnFocus: PropTypes.bool,
|
||||
/**
|
||||
* If true, selected the text automatically when editing begins
|
||||
* If true, all text will automatically be selected on focus
|
||||
* @platform ios
|
||||
*/
|
||||
selectTextOnFocus: PropTypes.bool,
|
||||
/**
|
||||
@@ -274,11 +292,12 @@ var TextInput = React.createClass({
|
||||
*/
|
||||
style: Text.propTypes.style,
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
* Used to locate this view in end-to-end tests
|
||||
*/
|
||||
testID: PropTypes.string,
|
||||
/**
|
||||
* The color of the textInput underline. Is only supported on Android.
|
||||
* The color of the textInput underline.
|
||||
* @platform android
|
||||
*/
|
||||
underlineColorAndroid: PropTypes.string,
|
||||
},
|
||||
@@ -297,16 +316,9 @@ var TextInput = React.createClass({
|
||||
React.findNodeHandle(this.refs.input);
|
||||
},
|
||||
|
||||
getDefaultProps: function(): DefaultProps {
|
||||
return {
|
||||
bufferDelay: 100,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
mostRecentEventCounter: 0,
|
||||
bufferedValue: this.props.value,
|
||||
mostRecentEventCount: 0,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -346,52 +358,6 @@ var TextInput = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_bufferTimeout: (undefined: ?number),
|
||||
|
||||
componentWillReceiveProps: function(newProps: {value: any}) {
|
||||
if (newProps.value !== this.props.value) {
|
||||
if (!this.isFocused()) {
|
||||
// Set the value immediately if the input is not focused since that
|
||||
// means there is no risk of the user typing immediately.
|
||||
this.setState({bufferedValue: newProps.value});
|
||||
} else {
|
||||
// The following clear and setTimeout buffers the value such that if more
|
||||
// characters are typed in quick succession, generating new values, the
|
||||
// out of date values will get cancelled before they are ever sent to
|
||||
// native.
|
||||
//
|
||||
// If we don't do this, it's likely the out of date values will blow
|
||||
// away recently typed characters in the native input that JS was not
|
||||
// yet aware of (since it is informed asynchronously), then the next
|
||||
// character will be appended to the older value, dropping the
|
||||
// characters in between. Here is a potential sequence of events
|
||||
// (recall we have multiple independently serial, interleaved queues):
|
||||
//
|
||||
// 1) User types 'R' => send 'R' to JS queue.
|
||||
// 2) User types 'e' => send 'Re' to JS queue.
|
||||
// 3) JS processes 'R' and sends 'R' back to native.
|
||||
// 4) Native recieves 'R' and changes input from 'Re' back to 'R'.
|
||||
// 5) User types 'a' => send 'Ra' to JS queue.
|
||||
// 6) JS processes 'Re' and sends 'Re' back to native.
|
||||
// 7) Native recieves 'Re' and changes input from 'R' back to 'Re'.
|
||||
// 8) JS processes 'Ra' and sends 'Ra' back to native.
|
||||
// 9) Native recieves final 'Ra' from JS - 'e' has been dropped!
|
||||
//
|
||||
// This isn't 100% foolproop (e.g. if it takes longer than
|
||||
// `props.bufferDelay` ms to process one keystroke), and there are of
|
||||
// course other potential algorithms to deal with this, but this is a
|
||||
// simple solution that seems to reduce the chance of dropped characters
|
||||
// drastically without compromising native input responsiveness (e.g. by
|
||||
// introducing delay from a synchronization protocol).
|
||||
this.clearTimeout(this._bufferTimeout);
|
||||
this._bufferTimeout = this.setTimeout(
|
||||
() => this.setState({bufferedValue: newProps.value}),
|
||||
this.props.bufferDelay
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChildContext: function(): Object {
|
||||
return {isInAParentText: true};
|
||||
},
|
||||
@@ -400,6 +366,10 @@ var TextInput = React.createClass({
|
||||
isInAParentText: React.PropTypes.bool
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
this.setNativeProps({text: ''});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (Platform.OS === 'ios') {
|
||||
return this._renderIOS();
|
||||
@@ -408,12 +378,17 @@ var TextInput = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_getText: function(): ?string {
|
||||
return typeof this.props.value === 'string' ?
|
||||
this.props.value :
|
||||
this.props.defaultValue;
|
||||
},
|
||||
|
||||
_renderIOS: function() {
|
||||
var textContainer;
|
||||
|
||||
var props = this.props;
|
||||
var props = Object.assign({}, this.props);
|
||||
props.style = [styles.input, this.props.style];
|
||||
|
||||
if (!props.multiline) {
|
||||
for (var propKey in onlyMultiline) {
|
||||
if (props[propKey]) {
|
||||
@@ -430,7 +405,8 @@ var TextInput = React.createClass({
|
||||
onBlur={this._onBlur}
|
||||
onChange={this._onChange}
|
||||
onSelectionChangeShouldSetResponder={() => true}
|
||||
text={this.state.bufferedValue}
|
||||
text={this._getText()}
|
||||
mostRecentEventCount={this.state.mostRecentEventCount}
|
||||
/>;
|
||||
} else {
|
||||
for (var propKey in notMultiline) {
|
||||
@@ -459,14 +435,14 @@ var TextInput = React.createClass({
|
||||
ref="input"
|
||||
{...props}
|
||||
children={children}
|
||||
mostRecentEventCounter={this.state.mostRecentEventCounter}
|
||||
mostRecentEventCount={this.state.mostRecentEventCount}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
onChange={this._onChange}
|
||||
onSelectionChange={this._onSelectionChange}
|
||||
onTextInput={this._onTextInput}
|
||||
onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
|
||||
text={this.state.bufferedValue}
|
||||
text={this._getText()}
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -516,7 +492,7 @@ var TextInput = React.createClass({
|
||||
password={this.props.password || this.props.secureTextEntry}
|
||||
placeholder={this.props.placeholder}
|
||||
placeholderTextColor={this.props.placeholderTextColor}
|
||||
text={this.state.bufferedValue}
|
||||
text={this._getText()}
|
||||
underlineColorAndroid={this.props.underlineColorAndroid}
|
||||
children={children}
|
||||
/>;
|
||||
@@ -537,15 +513,26 @@ var TextInput = React.createClass({
|
||||
},
|
||||
|
||||
_onPress: function(event: Event) {
|
||||
this.focus();
|
||||
if (this.props.editable || this.props.editable === undefined) {
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
|
||||
_onChange: function(event: Event) {
|
||||
if (this.props.controlled && event.nativeEvent.text !== this.props.value) {
|
||||
this.refs.input.setNativeProps({text: this.props.value});
|
||||
}
|
||||
var text = event.nativeEvent.text;
|
||||
var eventCount = event.nativeEvent.eventCount;
|
||||
this.props.onChange && this.props.onChange(event);
|
||||
this.props.onChangeText && this.props.onChangeText(event.nativeEvent.text);
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onBlur: function(event: Event) {
|
||||
@@ -565,10 +552,6 @@ var TextInput = React.createClass({
|
||||
|
||||
_onTextInput: function(event: Event) {
|
||||
this.props.onTextInput && this.props.onTextInput(event);
|
||||
var counter = event.nativeEvent.eventCounter;
|
||||
if (counter > this.state.mostRecentEventCounter) {
|
||||
this.setState({mostRecentEventCounter: counter});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -11,20 +11,12 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AnimationExperimental = require('AnimationExperimental');
|
||||
var Animated = require('Animated');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var POPAnimation = require('POPAnimation');
|
||||
var React = require('React');
|
||||
var Touchable = require('Touchable');
|
||||
|
||||
var merge = require('merge');
|
||||
var onlyChild = require('onlyChild');
|
||||
|
||||
var invariant = require('invariant');
|
||||
invariant(
|
||||
AnimationExperimental || POPAnimation,
|
||||
'Please add the RCTAnimationExperimental framework to your project, or add //Libraries/FBReactKit:RCTPOPAnimation to your BUCK file if running internally within Facebook.'
|
||||
);
|
||||
|
||||
type State = {
|
||||
animationID: ?number;
|
||||
@@ -58,40 +50,23 @@ var TouchableBounce = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function(): State {
|
||||
return merge(this.touchableGetInitialState(), {animationID: null});
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
scale: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
|
||||
bounceTo: function(
|
||||
value: number,
|
||||
velocity: number,
|
||||
bounciness: number,
|
||||
fromValue?: ?number,
|
||||
callback?: ?Function
|
||||
) {
|
||||
if (POPAnimation) {
|
||||
this.state.animationID && this.removeAnimation(this.state.animationID);
|
||||
var anim = {
|
||||
property: POPAnimation.Properties.scaleXY,
|
||||
dynamicsTension: 0,
|
||||
toValue: [value, value],
|
||||
velocity: [velocity, velocity],
|
||||
springBounciness: bounciness,
|
||||
fromValue: fromValue ? [fromValue, fromValue] : undefined,
|
||||
};
|
||||
this.state.animationID = POPAnimation.createSpringAnimation(anim);
|
||||
this.addAnimation(this.state.animationID, callback);
|
||||
} else {
|
||||
AnimationExperimental.startAnimation(
|
||||
{
|
||||
node: this,
|
||||
duration: 300,
|
||||
easing: 'easeOutBack',
|
||||
property: 'scaleXY',
|
||||
toValue: { x: value, y: value},
|
||||
},
|
||||
callback
|
||||
);
|
||||
}
|
||||
Animated.spring(this.state.scale, {
|
||||
toValue: value,
|
||||
velocity,
|
||||
bounciness,
|
||||
}).start(callback);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -109,13 +84,14 @@ var TouchableBounce = React.createClass({
|
||||
touchableHandlePress: function() {
|
||||
var onPressWithCompletion = this.props.onPressWithCompletion;
|
||||
if (onPressWithCompletion) {
|
||||
onPressWithCompletion(
|
||||
this.bounceTo.bind(this, 1, 10, 10, 0.93, this.props.onPressAnimationComplete)
|
||||
);
|
||||
onPressWithCompletion(() => {
|
||||
this.state.scale.setValue(0.93);
|
||||
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.bounceTo(1, 10, 10, undefined, this.props.onPressAnimationComplete);
|
||||
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
|
||||
this.props.onPress && this.props.onPress();
|
||||
},
|
||||
|
||||
@@ -127,18 +103,21 @@ var TouchableBounce = React.createClass({
|
||||
return 0;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var child = onlyChild(this.props.children);
|
||||
return React.cloneElement(child, {
|
||||
accessible: true,
|
||||
testID: this.props.testID,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
onResponderMove: this.touchableHandleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate
|
||||
});
|
||||
render: function(): ReactElement {
|
||||
return (
|
||||
<Animated.View
|
||||
style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
|
||||
accessible={true}
|
||||
testID={this.props.testID}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -181,6 +181,10 @@ var TouchableHighlight = React.createClass({
|
||||
},
|
||||
|
||||
_showUnderlay: function() {
|
||||
if (!this.isMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
|
||||
this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
|
||||
this.props.onShowUnderlay && this.props.onShowUnderlay();
|
||||
|
||||
@@ -12,19 +12,16 @@
|
||||
|
||||
// Note (avik): add @flow when Flow supports spread properties in propTypes
|
||||
|
||||
var Animated = require('Animated');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var POPAnimationMixin = require('POPAnimationMixin');
|
||||
var React = require('React');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('Touchable');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
|
||||
var cloneWithProps = require('cloneWithProps');
|
||||
var ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var keyOf = require('keyOf');
|
||||
var onlyChild = require('onlyChild');
|
||||
|
||||
/**
|
||||
* A wrapper for making views respond properly to touches.
|
||||
@@ -52,7 +49,7 @@ var onlyChild = require('onlyChild');
|
||||
*/
|
||||
|
||||
var TouchableOpacity = React.createClass({
|
||||
mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
|
||||
mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],
|
||||
|
||||
propTypes: {
|
||||
...TouchableWithoutFeedback.propTypes,
|
||||
@@ -70,16 +67,17 @@ var TouchableOpacity = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.touchableGetInitialState();
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
ensurePositiveDelayProps(this.props);
|
||||
ensureComponentIsNative(this.refs[CHILD_REF]);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
ensureComponentIsNative(this.refs[CHILD_REF]);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
@@ -87,22 +85,10 @@ var TouchableOpacity = React.createClass({
|
||||
},
|
||||
|
||||
setOpacityTo: function(value) {
|
||||
if (POPAnimationMixin) {
|
||||
// Reset with animation if POP is available
|
||||
this.stopAllAnimations();
|
||||
var anim = {
|
||||
type: this.AnimationTypes.linear,
|
||||
property: this.AnimationProperties.opacity,
|
||||
duration: 0.15,
|
||||
toValue: value,
|
||||
};
|
||||
this.startAnimation(CHILD_REF, anim);
|
||||
} else {
|
||||
// Reset immediately if POP is unavailable
|
||||
this.refs[CHILD_REF].setNativeProps({
|
||||
opacity: value
|
||||
});
|
||||
}
|
||||
Animated.timing(
|
||||
this.state.anim,
|
||||
{toValue: value, duration: 150}
|
||||
).start();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -161,25 +147,27 @@ var TouchableOpacity = React.createClass({
|
||||
_opacityInactive: function() {
|
||||
this.clearTimeout(this._hideTimeout);
|
||||
this._hideTimeout = null;
|
||||
var child = onlyChild(this.props.children);
|
||||
var childStyle = flattenStyle(child.props.style) || {};
|
||||
var childStyle = flattenStyle(this.props.style) || {};
|
||||
this.setOpacityTo(
|
||||
childStyle.opacity === undefined ? 1 : childStyle.opacity
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return cloneWithProps(onlyChild(this.props.children), {
|
||||
ref: CHILD_REF,
|
||||
accessible: true,
|
||||
testID: this.props.testID,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
onResponderMove: this.touchableHandleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
});
|
||||
return (
|
||||
<Animated.View
|
||||
accessible={true}
|
||||
style={[this.props.style, {opacity: this.state.anim}]}
|
||||
testID={this.props.testID}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}>
|
||||
{this.props.children}
|
||||
</Animated.View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -191,6 +179,5 @@ var TouchableOpacity = React.createClass({
|
||||
*/
|
||||
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
var CHILD_REF = keyOf({childRef: null});
|
||||
|
||||
module.exports = TouchableOpacity;
|
||||
|
||||
@@ -44,6 +44,12 @@ var AccessibilityTraits = [
|
||||
'pageTurn',
|
||||
];
|
||||
|
||||
// <<<<< WARNING >>>>>
|
||||
// If adding any properties to View that could change the way layout-only status
|
||||
// works on iOS, make sure to update ReactNativeViewAttributes.js and
|
||||
// RCTShadowView.m (in the -[RCTShadowView isLayoutOnly] method).
|
||||
// <<<<< WARNING >>>>>
|
||||
|
||||
/**
|
||||
* The most fundamental component for building UI, `View` is a
|
||||
* container that supports layout with flexbox, style, some touch handling, and
|
||||
@@ -77,12 +83,6 @@ var View = React.createClass({
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* When false, indicates that the view should not be collapsed, even if it is
|
||||
* layout-only. Defaults to true.
|
||||
*/
|
||||
collapsible: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* When true, indicates that the view is an accessibility element. By default,
|
||||
* all the touchable elements are accessible.
|
||||
|
||||
@@ -21,6 +21,7 @@ var TransformPropTypes = require('TransformPropTypes');
|
||||
var ViewStylePropTypes = {
|
||||
...LayoutPropTypes,
|
||||
...TransformPropTypes,
|
||||
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
|
||||
backgroundColor: ReactPropTypes.string,
|
||||
borderColor: ReactPropTypes.string,
|
||||
borderTopColor: ReactPropTypes.string,
|
||||
|
||||
@@ -36,7 +36,8 @@ var WebView = React.createClass({
|
||||
propTypes: {
|
||||
renderError: PropTypes.func, // view to show if there's an error
|
||||
renderLoading: PropTypes.func, // loading indicator to show
|
||||
url: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
html: PropTypes.string,
|
||||
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||
contentInset: EdgeInsetsPropType,
|
||||
onNavigationStateChange: PropTypes.func,
|
||||
@@ -102,6 +103,7 @@ var WebView = React.createClass({
|
||||
key="webViewKey"
|
||||
style={webViewStyles}
|
||||
url={this.props.url}
|
||||
html={this.props.html}
|
||||
injectedJavaScript={this.props.injectedJavaScript}
|
||||
userAgent={this.props.userAgent}
|
||||
javaScriptEnabledAndroid={this.props.javaScriptEnabledAndroid}
|
||||
@@ -183,6 +185,7 @@ var WebView = React.createClass({
|
||||
|
||||
var RCTWebView = createReactNativeComponentClass({
|
||||
validAttributes: merge(ReactNativeViewAttributes.UIView, {
|
||||
html: true,
|
||||
injectedJavaScript: true,
|
||||
javaScriptEnabledAndroid: true,
|
||||
url: true,
|
||||
|
||||
@@ -4,6 +4,6 @@ For React Native Custom Components software
|
||||
|
||||
Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
|
||||
Facebook, Inc. (“Facebook”) owns all right, title and interest, including all intellectual property and other proprietary rights, in and to the React Native Custom Components software (the “Software”). Subject to your compliance with these terms, you are hereby granted a non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the Software; and (2) reproduce and distribute the Software as part of your own software (“Your Software”). Facebook reserves all rights not expressly granted to you in this license agreement.
|
||||
Facebook, Inc. ("Facebook") owns all right, title and interest, including all intellectual property and other proprietary rights, in and to the React Native Custom Components software (the "Software"). Subject to your compliance with these terms, you are hereby granted a non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the Software; and (2) reproduce and distribute the Software as part of your own software ("Your Software"). Facebook reserves all rights not expressly granted to you in this license agreement.
|
||||
|
||||
THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -29,7 +29,7 @@
|
||||
var ListViewDataSource = require('ListViewDataSource');
|
||||
var React = require('React');
|
||||
var RCTUIManager = require('NativeModules').UIManager;
|
||||
var RKScrollViewManager = require('NativeModules').ScrollViewManager;
|
||||
var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
|
||||
var ScrollView = require('ScrollView');
|
||||
var ScrollResponder = require('ScrollResponder');
|
||||
var StaticRenderer = require('StaticRenderer');
|
||||
@@ -209,7 +209,7 @@ var ListView = React.createClass({
|
||||
*/
|
||||
getMetrics: function() {
|
||||
return {
|
||||
contentHeight: this.scrollProperties.contentHeight,
|
||||
contentLength: this.scrollProperties.contentLength,
|
||||
totalRows: this.props.dataSource.getRowCount(),
|
||||
renderedRows: this.state.curRenderedRowsCount,
|
||||
visibleRows: Object.keys(this._visibleRows).length,
|
||||
@@ -221,7 +221,9 @@ var ListView = React.createClass({
|
||||
* such as scrollTo.
|
||||
*/
|
||||
getScrollResponder: function() {
|
||||
return this.refs[SCROLLVIEW_REF];
|
||||
return this.refs[SCROLLVIEW_REF] &&
|
||||
this.refs[SCROLLVIEW_REF].getScrollResponder &&
|
||||
this.refs[SCROLLVIEW_REF].getScrollResponder();
|
||||
},
|
||||
|
||||
setNativeProps: function(props) {
|
||||
@@ -250,12 +252,16 @@ var ListView = React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
getInnerViewNode: function() {
|
||||
return this.refs[SCROLLVIEW_REF].getInnerViewNode();
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// this data should never trigger a render pass, so don't put in state
|
||||
this.scrollProperties = {
|
||||
visibleHeight: null,
|
||||
contentHeight: null,
|
||||
offsetY: 0
|
||||
visibleLength: null,
|
||||
contentLength: null,
|
||||
offset: 0
|
||||
};
|
||||
this._childFrames = [];
|
||||
this._visibleRows = {};
|
||||
@@ -384,14 +390,20 @@ var ListView = React.createClass({
|
||||
Object.assign(props, {
|
||||
onScroll: this._onScroll,
|
||||
stickyHeaderIndices: sectionHeaderIndices,
|
||||
children: [header, bodyComponents, footer],
|
||||
|
||||
// Do not pass these events downstream to ScrollView since they will be
|
||||
// registered in ListView's own ScrollResponder.Mixin
|
||||
onKeyboardWillShow: undefined,
|
||||
onKeyboardWillHide: undefined,
|
||||
onKeyboardDidShow: undefined,
|
||||
onKeyboardDidHide: undefined,
|
||||
});
|
||||
|
||||
// TODO(ide): Use function refs so we can compose with the scroll
|
||||
// component's original ref instead of clobbering it
|
||||
return React.cloneElement(renderScrollComponent(props), {
|
||||
ref: SCROLLVIEW_REF,
|
||||
});
|
||||
}, header, bodyComponents, footer);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -399,32 +411,39 @@ var ListView = React.createClass({
|
||||
*/
|
||||
|
||||
_measureAndUpdateScrollProps: function() {
|
||||
var scrollComponent = this.getScrollResponder();
|
||||
if (!scrollComponent || !scrollComponent.getInnerViewNode) {
|
||||
return;
|
||||
}
|
||||
RCTUIManager.measureLayout(
|
||||
this.refs[SCROLLVIEW_REF].getInnerViewNode(),
|
||||
React.findNodeHandle(this.refs[SCROLLVIEW_REF]),
|
||||
scrollComponent.getInnerViewNode(),
|
||||
React.findNodeHandle(scrollComponent),
|
||||
logError,
|
||||
this._setScrollContentHeight
|
||||
this._setScrollContentLength
|
||||
);
|
||||
RCTUIManager.measureLayoutRelativeToParent(
|
||||
React.findNodeHandle(this.refs[SCROLLVIEW_REF]),
|
||||
React.findNodeHandle(scrollComponent),
|
||||
logError,
|
||||
this._setScrollVisibleHeight
|
||||
this._setScrollVisibleLength
|
||||
);
|
||||
|
||||
// RKScrollViewManager.calculateChildFrames not available on every platform
|
||||
RKScrollViewManager && RKScrollViewManager.calculateChildFrames &&
|
||||
RKScrollViewManager.calculateChildFrames(
|
||||
React.findNodeHandle(this.refs[SCROLLVIEW_REF]),
|
||||
// RCTScrollViewManager.calculateChildFrames is not available on
|
||||
// every platform
|
||||
RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames &&
|
||||
RCTScrollViewManager.calculateChildFrames(
|
||||
React.findNodeHandle(scrollComponent),
|
||||
this._updateChildFrames,
|
||||
);
|
||||
},
|
||||
|
||||
_setScrollContentHeight: function(left, top, width, height) {
|
||||
this.scrollProperties.contentHeight = height;
|
||||
_setScrollContentLength: function(left, top, width, height) {
|
||||
this.scrollProperties.contentLength = !this.props.horizontal ?
|
||||
height : width;
|
||||
},
|
||||
|
||||
_setScrollVisibleHeight: function(left, top, width, height) {
|
||||
this.scrollProperties.visibleHeight = height;
|
||||
_setScrollVisibleLength: function(left, top, width, height) {
|
||||
this.scrollProperties.visibleLength = !this.props.horizontal ?
|
||||
height : width;
|
||||
this._updateVisibleRows();
|
||||
this._renderMoreRowsIfNeeded();
|
||||
},
|
||||
@@ -434,8 +453,8 @@ var ListView = React.createClass({
|
||||
},
|
||||
|
||||
_renderMoreRowsIfNeeded: function() {
|
||||
if (this.scrollProperties.contentHeight === null ||
|
||||
this.scrollProperties.visibleHeight === null ||
|
||||
if (this.scrollProperties.contentLength === null ||
|
||||
this.scrollProperties.visibleLength === null ||
|
||||
this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) {
|
||||
return;
|
||||
}
|
||||
@@ -465,9 +484,9 @@ var ListView = React.createClass({
|
||||
},
|
||||
|
||||
_getDistanceFromEnd: function(scrollProperties) {
|
||||
return scrollProperties.contentHeight -
|
||||
scrollProperties.visibleHeight -
|
||||
scrollProperties.offsetY;
|
||||
return scrollProperties.contentLength -
|
||||
scrollProperties.visibleLength -
|
||||
scrollProperties.offset;
|
||||
},
|
||||
|
||||
_updateVisibleRows: function(updatedFrames) {
|
||||
@@ -479,9 +498,10 @@ var ListView = React.createClass({
|
||||
this._childFrames[newFrame.index] = merge(newFrame);
|
||||
});
|
||||
}
|
||||
var isVertical = !this.props.horizontal;
|
||||
var dataSource = this.props.dataSource;
|
||||
var visibleTop = this.scrollProperties.offsetY;
|
||||
var visibleBottom = visibleTop + this.scrollProperties.visibleHeight;
|
||||
var visibleMin = this.scrollProperties.offset;
|
||||
var visibleMax = visibleMin + this.scrollProperties.visibleLength;
|
||||
var allRowIDs = dataSource.rowIdentities;
|
||||
|
||||
var header = this.props.renderHeader && this.props.renderHeader();
|
||||
@@ -509,9 +529,9 @@ var ListView = React.createClass({
|
||||
break;
|
||||
}
|
||||
var rowVisible = visibleSection[rowID];
|
||||
var top = frame.y;
|
||||
var bottom = top + frame.height;
|
||||
if (top > visibleBottom || bottom < visibleTop) {
|
||||
var min = isVertical ? frame.y : frame.x;
|
||||
var max = min + (isVertical ? frame.height : frame.width);
|
||||
if (min > visibleMax || max < visibleMin) {
|
||||
if (rowVisible) {
|
||||
visibilityChanged = true;
|
||||
delete visibleSection[rowID];
|
||||
@@ -539,16 +559,23 @@ var ListView = React.createClass({
|
||||
},
|
||||
|
||||
_onScroll: function(e) {
|
||||
this.scrollProperties.visibleHeight = e.nativeEvent.layoutMeasurement.height;
|
||||
this.scrollProperties.contentHeight = e.nativeEvent.contentSize.height;
|
||||
this.scrollProperties.offsetY = e.nativeEvent.contentOffset.y;
|
||||
var isVertical = !this.props.horizontal;
|
||||
this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[
|
||||
isVertical ? 'height' : 'width'
|
||||
];
|
||||
this.scrollProperties.contentLength = e.nativeEvent.contentSize[
|
||||
isVertical ? 'height' : 'width'
|
||||
];
|
||||
this.scrollProperties.offset = e.nativeEvent.contentOffset[
|
||||
isVertical ? 'y' : 'x'
|
||||
];
|
||||
this._updateVisibleRows(e.nativeEvent.updatedChildFrames);
|
||||
var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold;
|
||||
if (nearEnd &&
|
||||
this.props.onEndReached &&
|
||||
this.scrollProperties.contentHeight !== this._sentEndForContentHeight &&
|
||||
this.scrollProperties.contentLength !== this._sentEndForContentLength &&
|
||||
this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) {
|
||||
this._sentEndForContentHeight = this.scrollProperties.contentHeight;
|
||||
this._sentEndForContentLength = this.scrollProperties.contentLength;
|
||||
this.props.onEndReached(e);
|
||||
} else {
|
||||
this._renderMoreRowsIfNeeded();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -67,10 +67,10 @@ class NavigationContext {
|
||||
}
|
||||
}
|
||||
|
||||
emit(eventType: String, data: any): void {
|
||||
emit(eventType: String, data: any, didEmitCallback: ?Function): void {
|
||||
var emitter = this._eventEmitter;
|
||||
if (emitter) {
|
||||
emitter.emit(eventType, data);
|
||||
emitter.emit(eventType, data, didEmitCallback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,119 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @providesModule NavigationEvent
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
class NavigationEvent {
|
||||
type: String;
|
||||
target: Object;
|
||||
data: any;
|
||||
var invariant = require('invariant');
|
||||
|
||||
constructor(type: String, target: Object, data: any) {
|
||||
this.type = type;
|
||||
this.target = target;
|
||||
this.data = data;
|
||||
class NavigationEventPool {
|
||||
_list: Array<any>;
|
||||
|
||||
constructor() {
|
||||
this._list = [];
|
||||
}
|
||||
|
||||
get(type: string, target: Object, data: any): NavigationEvent {
|
||||
var event;
|
||||
if (this._list.length > 0) {
|
||||
event = this._list.pop();
|
||||
event.constructor.call(event, type, target, data);
|
||||
} else {
|
||||
event = new NavigationEvent(type, target, data);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
put(event: NavigationEvent) {
|
||||
this._list.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
var _navigationEventPool = new NavigationEventPool();
|
||||
|
||||
class NavigationEvent {
|
||||
_data: any;
|
||||
_defaultPrevented: boolean;
|
||||
_disposed: boolean;
|
||||
_target: ?Object;
|
||||
_type: ?string;
|
||||
|
||||
static pool(type: string, target: Object, data: any): NavigationEvent {
|
||||
return _navigationEventPool.get(type, target, data);
|
||||
}
|
||||
|
||||
constructor(type: string, target: Object, data: any) {
|
||||
this._type = type;
|
||||
this._target = target;
|
||||
this._data = data;
|
||||
this._defaultPrevented = false;
|
||||
this._disposed = false;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get type(): string {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get target(): Object {
|
||||
return this._target;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get data(): any {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/* $FlowFixMe - get/set properties not yet supported */
|
||||
get defaultPrevented(): boolean {
|
||||
return this._defaultPrevented;
|
||||
}
|
||||
|
||||
preventDefault(): void {
|
||||
this._defaultPrevented = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the event.
|
||||
* NavigationEvent shall be disposed after being emitted by
|
||||
* `NavigationEventEmitter`.
|
||||
*/
|
||||
dispose(): void {
|
||||
invariant(!this._disposed, 'NavigationEvent is already disposed');
|
||||
this._disposed = true;
|
||||
|
||||
// Clean up.
|
||||
this._type = null;
|
||||
this._target = null;
|
||||
this._data = null;
|
||||
this._defaultPrevented = false;
|
||||
|
||||
// Put this back to the pool to reuse the instance.
|
||||
_navigationEventPool.put(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -31,8 +31,9 @@ var EventEmitter = require('EventEmitter');
|
||||
var NavigationEvent = require('NavigationEvent');
|
||||
|
||||
type EventParams = {
|
||||
eventType: String;
|
||||
data: any;
|
||||
didEmitCallback: ?Function;
|
||||
eventType: string;
|
||||
};
|
||||
|
||||
class NavigationEventEmitter extends EventEmitter {
|
||||
@@ -47,22 +48,36 @@ class NavigationEventEmitter extends EventEmitter {
|
||||
this._target = target;
|
||||
}
|
||||
|
||||
emit(eventType: String, data: any): void {
|
||||
emit(
|
||||
eventType: string,
|
||||
data: any,
|
||||
didEmitCallback: ?Function
|
||||
): void {
|
||||
if (this._emitting) {
|
||||
// An event cycle that was previously created hasn't finished yet.
|
||||
// Put this event cycle into the queue and will finish them later.
|
||||
this._emitQueue.push({eventType, data});
|
||||
this._emitQueue.push({eventType, data, didEmitCallback});
|
||||
return;
|
||||
}
|
||||
|
||||
this._emitting = true;
|
||||
|
||||
var event = new NavigationEvent(eventType, this._target, data);
|
||||
super.emit(eventType, event);
|
||||
|
||||
// EventEmitter#emit only takes `eventType` as `String`. Casting `eventType`
|
||||
// to `String` to make @flow happy.
|
||||
super.emit(String(eventType), event);
|
||||
|
||||
if (typeof didEmitCallback === 'function') {
|
||||
didEmitCallback.call(this._target, event);
|
||||
}
|
||||
event.dispose();
|
||||
|
||||
this._emitting = false;
|
||||
|
||||
while (this._emitQueue.length) {
|
||||
var arg = this._emitQueue.shift();
|
||||
this.emit(arg.eventType, arg.data);
|
||||
this.emit(arg.eventType, arg.data, arg.didEmitCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,16 @@ class RouteStack {
|
||||
return this._routes.get(index);
|
||||
}
|
||||
|
||||
indexOf(route: any): number {
|
||||
return this._routes.indexOf(route);
|
||||
}
|
||||
|
||||
slice(begin: ?number, end: ?number): RouteStack {
|
||||
var routes = this._routes.slice(begin, end);
|
||||
var index = Math.min(this._index, routes.size - 1);
|
||||
return this._update(index, routes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new stack with the provided route appended,
|
||||
* starting at this stack size.
|
||||
@@ -71,7 +81,7 @@ class RouteStack {
|
||||
list.slice(0, this._index + 1).push(route);
|
||||
});
|
||||
|
||||
return new RouteStack(routes.size - 1, routes);
|
||||
return this._update(routes.size - 1, routes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +93,7 @@ class RouteStack {
|
||||
|
||||
// When popping, removes the rest of the routes past the current index.
|
||||
var routes = this._routes.slice(0, this._index);
|
||||
return new RouteStack(routes.size - 1, routes);
|
||||
return this._update(routes.size - 1, routes);
|
||||
}
|
||||
|
||||
jumpToIndex(index: number): RouteStack {
|
||||
@@ -92,11 +102,7 @@ class RouteStack {
|
||||
'index out of bound'
|
||||
);
|
||||
|
||||
if (index === this._index) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return new RouteStack(index, this._routes);
|
||||
return this._update(index, this._routes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +135,14 @@ class RouteStack {
|
||||
);
|
||||
|
||||
var routes = this._routes.set(index, route);
|
||||
return new RouteStack(this._index, routes);
|
||||
return this._update(index, routes);
|
||||
}
|
||||
|
||||
_update(index: number, routes: List): RouteStack {
|
||||
if (this._index === index && this._routes === routes) {
|
||||
return this;
|
||||
}
|
||||
return new RouteStack(index, routes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.dontMock('NavigationEvent')
|
||||
.dontMock('invariant');
|
||||
|
||||
var NavigationEvent = require('NavigationEvent');
|
||||
|
||||
describe('NavigationEvent', () => {
|
||||
it('constructs', () => {
|
||||
var target = {};
|
||||
var event = new NavigationEvent('foo', target, 123);
|
||||
expect(event.type).toBe('foo');
|
||||
expect(event.target).toBe(target);
|
||||
expect(event.data).toBe(123);
|
||||
});
|
||||
|
||||
it('constructs from pool', () => {
|
||||
var target = {};
|
||||
var event = NavigationEvent.pool('foo', target, 123);
|
||||
expect(event.type).toBe('foo');
|
||||
expect(event.target).toBe(target);
|
||||
expect(event.data).toBe(123);
|
||||
});
|
||||
|
||||
it('prevents default', () => {
|
||||
var event = new NavigationEvent('foo', {}, 123);
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
event.preventDefault();
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
});
|
||||
|
||||
it('recycles', () => {
|
||||
var event1 = NavigationEvent.pool('foo', {}, 123);
|
||||
event1.dispose();
|
||||
expect(event1.type).toBe(null);
|
||||
expect(event1.data).toBe(null);
|
||||
expect(event1.target).toBe(null);
|
||||
|
||||
var event2 = NavigationEvent.pool('bar', {}, 456);
|
||||
expect(event2.type).toBe('bar');
|
||||
expect(event2).toBe(event1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+98
-18
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -34,27 +34,48 @@ jest
|
||||
var NavigationEventEmitter = require('NavigationEventEmitter');
|
||||
|
||||
describe('NavigationEventEmitter', () => {
|
||||
it('emit event', () => {
|
||||
var target = {};
|
||||
var emitter = new NavigationEventEmitter(target);
|
||||
var focusCounter = 0;
|
||||
var focusTarget;
|
||||
it('emits event', () => {
|
||||
var context = {};
|
||||
var emitter = new NavigationEventEmitter(context);
|
||||
var logs = [];
|
||||
|
||||
emitter.addListener('ping', (event) => {
|
||||
var {type, data, target, defaultPrevented} = event;
|
||||
|
||||
logs.push({
|
||||
data,
|
||||
defaultPrevented,
|
||||
target,
|
||||
type,
|
||||
});
|
||||
|
||||
emitter.addListener('focus', (event) => {
|
||||
focusCounter++;
|
||||
focusTarget = event.target;
|
||||
});
|
||||
|
||||
emitter.emit('focus');
|
||||
emitter.emit('blur');
|
||||
emitter.emit('ping', 'hello');
|
||||
|
||||
expect(focusCounter).toBe(1);
|
||||
expect(focusTarget).toBe(target);
|
||||
expect(logs.length).toBe(1);
|
||||
expect(logs[0].target).toBe(context);
|
||||
expect(logs[0].type).toBe('ping');
|
||||
expect(logs[0].data).toBe('hello');
|
||||
expect(logs[0].defaultPrevented).toBe(false);
|
||||
});
|
||||
|
||||
it('put nested emit call in queue', () => {
|
||||
var target = {};
|
||||
var emitter = new NavigationEventEmitter(target);
|
||||
it('does not emit event that has no listeners', () => {
|
||||
var context = {};
|
||||
var emitter = new NavigationEventEmitter(context);
|
||||
var pinged = false;
|
||||
|
||||
emitter.addListener('ping', () => {
|
||||
pinged = true;
|
||||
});
|
||||
|
||||
emitter.emit('yo', 'bo');
|
||||
expect(pinged).toBe(false);
|
||||
});
|
||||
|
||||
it('puts nested emit call in a queue', () => {
|
||||
var context = {};
|
||||
var emitter = new NavigationEventEmitter(context);
|
||||
var logs = [];
|
||||
|
||||
emitter.addListener('one', () => {
|
||||
@@ -77,4 +98,63 @@ describe('NavigationEventEmitter', () => {
|
||||
|
||||
expect(logs).toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('calls callback after emitting', () => {
|
||||
var context = {};
|
||||
var emitter = new NavigationEventEmitter(context);
|
||||
var logs = [];
|
||||
|
||||
emitter.addListener('ping', (event) => {
|
||||
var {type, data, target, defaultPrevented} = event;
|
||||
logs.push({
|
||||
data,
|
||||
defaultPrevented,
|
||||
target,
|
||||
type,
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
emitter.emit('ping', 'hello', (event) => {
|
||||
var {type, data, target, defaultPrevented} = event;
|
||||
logs.push({
|
||||
data,
|
||||
defaultPrevented,
|
||||
target,
|
||||
type,
|
||||
});
|
||||
});
|
||||
|
||||
expect(logs.length).toBe(2);
|
||||
expect(logs[1].target).toBe(context);
|
||||
expect(logs[1].type).toBe('ping');
|
||||
expect(logs[1].data).toBe('hello');
|
||||
expect(logs[1].defaultPrevented).toBe(true);
|
||||
});
|
||||
|
||||
it('calls callback after emitting the current event and before ' +
|
||||
'emitting the next event', () => {
|
||||
var context = {};
|
||||
var emitter = new NavigationEventEmitter(context);
|
||||
var logs = [];
|
||||
|
||||
emitter.addListener('ping', (event) => {
|
||||
logs.push('ping');
|
||||
emitter.emit('pong');
|
||||
});
|
||||
|
||||
emitter.addListener('pong', (event) => {
|
||||
logs.push('pong');
|
||||
});
|
||||
|
||||
emitter.emit('ping', null, () => {
|
||||
logs.push('did-ping');
|
||||
});
|
||||
|
||||
expect(logs).toEqual([
|
||||
'ping',
|
||||
'did-ping',
|
||||
'pong',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+65
-24
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -36,17 +36,17 @@ describe('NavigationRouteStack:', () => {
|
||||
// Basic
|
||||
it('gets index', () => {
|
||||
var stack = new NavigationRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.index).toEqual(1);
|
||||
expect(stack.index).toBe(1);
|
||||
});
|
||||
|
||||
it('gets size', () => {
|
||||
var stack = new NavigationRouteStack(1, ['a', 'b', 'c']);
|
||||
expect(stack.size).toEqual(3);
|
||||
expect(stack.size).toBe(3);
|
||||
});
|
||||
|
||||
it('gets route', () => {
|
||||
var stack = new NavigationRouteStack(0, ['a', 'b', 'c']);
|
||||
expect(stack.get(2)).toEqual('c');
|
||||
expect(stack.get(2)).toBe('c');
|
||||
});
|
||||
|
||||
it('converts to an array', () => {
|
||||
@@ -57,7 +57,7 @@ describe('NavigationRouteStack:', () => {
|
||||
it('creates a new stack after mutation', () => {
|
||||
var stack1 = new NavigationRouteStack(0, ['a', 'b']);
|
||||
var stack2 = stack1.push('c');
|
||||
expect(stack1).not.toEqual(stack2);
|
||||
expect(stack1).not.toBe(stack2);
|
||||
});
|
||||
|
||||
it('throws at index out of bound', () => {
|
||||
@@ -70,15 +70,57 @@ describe('NavigationRouteStack:', () => {
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('finds index', () => {
|
||||
var stack = new NavigationRouteStack(0, ['a', 'b']);
|
||||
expect(stack.indexOf('b')).toBe(1);
|
||||
expect(stack.indexOf('c')).toBe(-1);
|
||||
});
|
||||
|
||||
it('slices', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c', 'd']);
|
||||
var stack2 = stack1.slice(1, 3);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['b', 'c']);
|
||||
});
|
||||
|
||||
it('may update index after slicing', () => {
|
||||
var stack = new NavigationRouteStack(2, ['a', 'b', 'c']);
|
||||
expect(stack.slice().index).toBe(2);
|
||||
expect(stack.slice(0, 1).index).toBe(0);
|
||||
expect(stack.slice(0, 2).index).toBe(1);
|
||||
expect(stack.slice(0, 3).index).toBe(2);
|
||||
expect(stack.slice(0, 100).index).toBe(2);
|
||||
expect(stack.slice(-2).index).toBe(1);
|
||||
});
|
||||
|
||||
it('slices without specifying params', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']);
|
||||
var stack2 = stack1.slice();
|
||||
expect(stack2).toBe(stack1);
|
||||
});
|
||||
|
||||
it('slices to from the end', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c', 'd']);
|
||||
var stack2 = stack1.slice(-2);
|
||||
expect(stack2.toArray()).toEqual(['c', 'd']);
|
||||
});
|
||||
|
||||
it('throws when slicing to empty', () => {
|
||||
expect(() => {
|
||||
var stack = new NavigationRouteStack(1, ['a', 'b']);
|
||||
stack.slice(100);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
// Push
|
||||
it('pushes route', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b']);
|
||||
var stack2 = stack1.push('c');
|
||||
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'b', 'c']);
|
||||
expect(stack2.index).toEqual(2);
|
||||
expect(stack2.size).toEqual(3);
|
||||
expect(stack2.index).toBe(2);
|
||||
expect(stack2.size).toBe(3);
|
||||
});
|
||||
|
||||
it('throws when pushing empty route', () => {
|
||||
@@ -101,27 +143,27 @@ describe('NavigationRouteStack:', () => {
|
||||
it('replaces routes on push', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']);
|
||||
var stack2 = stack1.push('d');
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'b', 'd']);
|
||||
expect(stack2.index).toEqual(2);
|
||||
expect(stack2.index).toBe(2);
|
||||
});
|
||||
|
||||
// Pop
|
||||
it('pops route', () => {
|
||||
var stack1 = new NavigationRouteStack(2, ['a', 'b', 'c']);
|
||||
var stack2 = stack1.pop();
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'b']);
|
||||
expect(stack2.index).toEqual(1);
|
||||
expect(stack2.size).toEqual(2);
|
||||
expect(stack2.index).toBe(1);
|
||||
expect(stack2.size).toBe(2);
|
||||
});
|
||||
|
||||
it('replaces routes on pop', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b', 'c']);
|
||||
var stack2 = stack1.pop();
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a']);
|
||||
expect(stack2.index).toEqual(0);
|
||||
expect(stack2.index).toBe(0);
|
||||
});
|
||||
|
||||
it('throws when popping to empty stack', () => {
|
||||
@@ -136,8 +178,8 @@ describe('NavigationRouteStack:', () => {
|
||||
var stack1 = new NavigationRouteStack(0, ['a', 'b', 'c']);
|
||||
var stack2 = stack1.jumpToIndex(2);
|
||||
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2.index).toEqual(2);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.index).toBe(2);
|
||||
});
|
||||
|
||||
it('throws then jumping to index out of bound', () => {
|
||||
@@ -157,21 +199,20 @@ describe('NavigationRouteStack:', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b']);
|
||||
var stack2 = stack1.replaceAtIndex(0, 'x');
|
||||
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['x', 'b']);
|
||||
expect(stack2.index).toEqual(1);
|
||||
expect(stack2.index).toBe(0);
|
||||
});
|
||||
|
||||
it('replaces route at negative index', () => {
|
||||
var stack1 = new NavigationRouteStack(1, ['a', 'b']);
|
||||
var stack2 = stack1.replaceAtIndex(-1, 'x');
|
||||
|
||||
expect(stack2).not.toEqual(stack1);
|
||||
expect(stack2).not.toBe(stack1);
|
||||
expect(stack2.toArray()).toEqual(['a', 'x']);
|
||||
expect(stack2.index).toEqual(1);
|
||||
expect(stack2.index).toBe(1);
|
||||
});
|
||||
|
||||
|
||||
it('throws when replacing empty route', () => {
|
||||
expect(() => {
|
||||
var stack = new NavigationRouteStack(1, ['a', 'b']);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -56,8 +56,9 @@ var PropTypes = React.PropTypes;
|
||||
var SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
var SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||
var SCENE_DISABLED_NATIVE_PROPS = {
|
||||
pointerEvents: 'none',
|
||||
style: {
|
||||
left: SCREEN_WIDTH,
|
||||
top: SCREEN_HEIGHT,
|
||||
opacity: 0,
|
||||
},
|
||||
};
|
||||
@@ -67,6 +68,24 @@ function getuid() {
|
||||
return __uid++;
|
||||
}
|
||||
|
||||
function getRouteID(route) {
|
||||
if (route === null || typeof route !== 'object') {
|
||||
return String(route);
|
||||
}
|
||||
|
||||
var key = '__navigatorRouteID';
|
||||
|
||||
if (!route.hasOwnProperty(key)) {
|
||||
Object.defineProperty(route, key, {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: getuid(),
|
||||
});
|
||||
}
|
||||
return route[key];
|
||||
}
|
||||
|
||||
// styles moved to the top of the file so getDefaultProps can refer to it
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
@@ -89,7 +108,7 @@ var styles = StyleSheet.create({
|
||||
top: 0,
|
||||
},
|
||||
disabledScene: {
|
||||
left: SCREEN_WIDTH,
|
||||
top: SCREEN_HEIGHT,
|
||||
},
|
||||
transitioner: {
|
||||
flex: 1,
|
||||
@@ -220,11 +239,6 @@ var Navigator = React.createClass({
|
||||
*/
|
||||
onDidFocus: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Will be called with (ref, indexInStack, route) when the scene ref changes
|
||||
*/
|
||||
onItemRef: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Optionally provide a navigation bar that persists across scene
|
||||
* transitions
|
||||
@@ -277,7 +291,6 @@ var Navigator = React.createClass({
|
||||
sceneConfigStack: routeStack.map(
|
||||
(route) => this.props.configureScene(route)
|
||||
),
|
||||
idStack: routeStack.map(() => getuid()),
|
||||
routeStack,
|
||||
presentedIndex: initialRouteIndex,
|
||||
transitionFromIndex: null,
|
||||
@@ -318,7 +331,6 @@ var Navigator = React.createClass({
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderTerminate: this._handlePanResponderTerminate,
|
||||
});
|
||||
this._itemRefs = {};
|
||||
this._interactionHandle = null;
|
||||
this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]);
|
||||
},
|
||||
@@ -345,7 +357,6 @@ var Navigator = React.createClass({
|
||||
immediatelyResetRouteStack: function(nextRouteStack) {
|
||||
var destIndex = nextRouteStack.length - 1;
|
||||
this.setState({
|
||||
idStack: nextRouteStack.map(getuid),
|
||||
routeStack: nextRouteStack,
|
||||
sceneConfigStack: nextRouteStack.map(
|
||||
this.props.configureScene
|
||||
@@ -519,15 +530,18 @@ var Navigator = React.createClass({
|
||||
_enableScene: function(sceneIndex) {
|
||||
// First, determine what the defined styles are for scenes in this navigator
|
||||
var sceneStyle = flattenStyle([styles.baseScene, this.props.sceneStyle]);
|
||||
// Then restore the left value for this scene
|
||||
// Then restore the pointer events and top value for this scene
|
||||
var enabledSceneNativeProps = {
|
||||
left: sceneStyle.left,
|
||||
pointerEvents: 'auto',
|
||||
style: {
|
||||
top: sceneStyle.top,
|
||||
},
|
||||
};
|
||||
if (sceneIndex !== this.state.transitionFromIndex &&
|
||||
sceneIndex !== this.state.presentedIndex) {
|
||||
// If we are not in a transition from this index, make sure opacity is 0
|
||||
// to prevent the enabled scene from flashing over the presented scene
|
||||
enabledSceneNativeProps.opacity = 0;
|
||||
enabledSceneNativeProps.style.opacity = 0;
|
||||
}
|
||||
this.refs['scene_' + sceneIndex] &&
|
||||
this.refs['scene_' + sceneIndex].setNativeProps(enabledSceneNativeProps);
|
||||
@@ -870,17 +884,14 @@ var Navigator = React.createClass({
|
||||
invariant(!!route, 'Must supply route to push');
|
||||
var activeLength = this.state.presentedIndex + 1;
|
||||
var activeStack = this.state.routeStack.slice(0, activeLength);
|
||||
var activeIDStack = this.state.idStack.slice(0, activeLength);
|
||||
var activeAnimationConfigStack = this.state.sceneConfigStack.slice(0, activeLength);
|
||||
var nextStack = activeStack.concat([route]);
|
||||
var destIndex = nextStack.length - 1;
|
||||
var nextIDStack = activeIDStack.concat([getuid()]);
|
||||
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
|
||||
this.props.configureScene(route),
|
||||
]);
|
||||
this._emitWillFocus(nextStack[destIndex]);
|
||||
this.setState({
|
||||
idStack: nextIDStack,
|
||||
routeStack: nextStack,
|
||||
sceneConfigStack: nextAnimationConfigStack,
|
||||
}, () => {
|
||||
@@ -930,12 +941,8 @@ var Navigator = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
// I don't believe we need to lock for a replace since there's no
|
||||
// navigation actually happening
|
||||
var nextIDStack = this.state.idStack.slice();
|
||||
var nextRouteStack = this.state.routeStack.slice();
|
||||
var nextAnimationModeStack = this.state.sceneConfigStack.slice();
|
||||
nextIDStack[index] = getuid();
|
||||
nextRouteStack[index] = route;
|
||||
nextAnimationModeStack[index] = this.props.configureScene(route);
|
||||
|
||||
@@ -943,7 +950,6 @@ var Navigator = React.createClass({
|
||||
this._emitWillFocus(route);
|
||||
}
|
||||
this.setState({
|
||||
idStack: nextIDStack,
|
||||
routeStack: nextRouteStack,
|
||||
sceneConfigStack: nextAnimationModeStack,
|
||||
}, () => {
|
||||
@@ -1006,63 +1012,37 @@ var Navigator = React.createClass({
|
||||
return this.state.routeStack.slice();
|
||||
},
|
||||
|
||||
_handleItemRef: function(itemId, route, ref) {
|
||||
this._itemRefs[itemId] = ref;
|
||||
var itemIndex = this.state.idStack.indexOf(itemId);
|
||||
if (itemIndex === -1) {
|
||||
return;
|
||||
}
|
||||
this.props.onItemRef && this.props.onItemRef(ref, itemIndex, route);
|
||||
},
|
||||
|
||||
_cleanScenesPastIndex: function(index) {
|
||||
var newStackLength = index + 1;
|
||||
// Remove any unneeded rendered routes.
|
||||
if (newStackLength < this.state.routeStack.length) {
|
||||
this.state.idStack.slice(newStackLength).map((removingId) => {
|
||||
this._itemRefs[removingId] = null;
|
||||
});
|
||||
this.setState({
|
||||
sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength),
|
||||
idStack: this.state.idStack.slice(0, newStackLength),
|
||||
routeStack: this.state.routeStack.slice(0, newStackLength),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_renderScene: function(route, i) {
|
||||
var child = this.props.renderScene(
|
||||
route,
|
||||
this
|
||||
);
|
||||
var disabledSceneStyle = null;
|
||||
var disabledScenePointerEvents = 'auto';
|
||||
if (i !== this.state.presentedIndex) {
|
||||
disabledSceneStyle = styles.disabledScene;
|
||||
}
|
||||
var originalRef = child.ref;
|
||||
if (originalRef != null && typeof originalRef !== 'function') {
|
||||
console.warn(
|
||||
'String refs are not supported for navigator scenes. Use a callback ' +
|
||||
'ref instead. Ignoring ref: ' + originalRef
|
||||
);
|
||||
originalRef = null;
|
||||
disabledScenePointerEvents = 'none';
|
||||
}
|
||||
return (
|
||||
<View
|
||||
key={this.state.idStack[i]}
|
||||
key={'scene_' + getRouteID(route)}
|
||||
ref={'scene_' + i}
|
||||
onStartShouldSetResponderCapture={() => {
|
||||
return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null);
|
||||
}}
|
||||
pointerEvents={disabledScenePointerEvents}
|
||||
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
|
||||
{React.cloneElement(child, {
|
||||
ref: component => {
|
||||
this._handleItemRef(this.state.idStack[i], route, component);
|
||||
if (originalRef) {
|
||||
originalRef(component);
|
||||
}
|
||||
}
|
||||
})}
|
||||
{this.props.renderScene(
|
||||
route,
|
||||
this
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -33,6 +33,8 @@ var StaticContainer = require('StaticContainer.react');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var { Map } = require('immutable');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
|
||||
@@ -86,7 +88,6 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
|
||||
}),
|
||||
navState: React.PropTypes.shape({
|
||||
routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
idStack: React.PropTypes.arrayOf(React.PropTypes.number),
|
||||
presentedIndex: React.PropTypes.number,
|
||||
}),
|
||||
style: View.propTypes.style,
|
||||
@@ -173,11 +174,19 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._descriptors = {
|
||||
crumb: new Map(),
|
||||
title: new Map(),
|
||||
right: new Map(),
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var navState = this.props.navState;
|
||||
var icons = navState && navState.routeStack.map(this._renderOrReturnBreadcrumb);
|
||||
var titles = navState.routeStack.map(this._renderOrReturnTitle);
|
||||
var buttons = navState.routeStack.map(this._renderOrReturnRightButton);
|
||||
var icons = navState && navState.routeStack.map(this._getBreadcrumb);
|
||||
var titles = navState.routeStack.map(this._getTitle);
|
||||
var buttons = navState.routeStack.map(this._getRightButton);
|
||||
return (
|
||||
<View style={[styles.breadCrumbContainer, this.props.style]}>
|
||||
{titles}
|
||||
@@ -187,104 +196,69 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
_renderOrReturnBreadcrumb: function(route, index) {
|
||||
var uid = this.props.navState.idStack[index];
|
||||
var navBarRouteMapper = this.props.routeMapper;
|
||||
var navOps = this.props.navigator;
|
||||
var alreadyRendered = this.refs['crumbContainer' + uid];
|
||||
if (alreadyRendered) {
|
||||
// Don't bother re-calculating the children
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={'crumbContainer' + uid}
|
||||
key={'crumbContainer' + uid}
|
||||
shouldUpdate={false}
|
||||
/>
|
||||
);
|
||||
_getBreadcrumb: function(route, index) {
|
||||
if (this._descriptors.crumb.has(route)) {
|
||||
return this._descriptors.crumb.get(route);
|
||||
}
|
||||
|
||||
var navBarRouteMapper = this.props.routeMapper;
|
||||
var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={'crumbContainer' + uid}
|
||||
key={'crumbContainer' + uid}
|
||||
shouldUpdate={false}>
|
||||
<View ref={'crumb_' + index} style={firstStyles.Crumb}>
|
||||
<View ref={'icon_' + index} style={firstStyles.Icon}>
|
||||
{navBarRouteMapper.iconForRoute(route, navOps)}
|
||||
</View>
|
||||
<View ref={'separator_' + index} style={firstStyles.Separator}>
|
||||
{navBarRouteMapper.separatorForRoute(route, navOps)}
|
||||
</View>
|
||||
|
||||
var breadcrumbDescriptor = (
|
||||
<View ref={'crumb_' + index} style={firstStyles.Crumb}>
|
||||
<View ref={'icon_' + index} style={firstStyles.Icon}>
|
||||
{navBarRouteMapper.iconForRoute(route, this.props.navigator)}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
<View ref={'separator_' + index} style={firstStyles.Separator}>
|
||||
{navBarRouteMapper.separatorForRoute(route, this.props.navigator)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
this._descriptors.crumb = this._descriptors.crumb.set(route, breadcrumbDescriptor);
|
||||
return breadcrumbDescriptor;
|
||||
},
|
||||
|
||||
_renderOrReturnTitle: function(route, index) {
|
||||
var navState = this.props.navState;
|
||||
var uid = navState.idStack[index];
|
||||
var alreadyRendered = this.refs['titleContainer' + uid];
|
||||
if (alreadyRendered) {
|
||||
// Don't bother re-calculating the children
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={'titleContainer' + uid}
|
||||
key={'titleContainer' + uid}
|
||||
shouldUpdate={false}
|
||||
/>
|
||||
);
|
||||
_getTitle: function(route, index) {
|
||||
if (this._descriptors.title.has(route)) {
|
||||
return this._descriptors.title.get(route);
|
||||
}
|
||||
var navBarRouteMapper = this.props.routeMapper;
|
||||
var titleContent = navBarRouteMapper.titleContentForRoute(
|
||||
navState.routeStack[index],
|
||||
|
||||
var titleContent = this.props.routeMapper.titleContentForRoute(
|
||||
this.props.navState.routeStack[index],
|
||||
this.props.navigator
|
||||
);
|
||||
var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={'titleContainer' + uid}
|
||||
key={'titleContainer' + uid}
|
||||
shouldUpdate={false}>
|
||||
<View ref={'title_' + index} style={firstStyles.Title}>
|
||||
{titleContent}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
|
||||
var titleDescriptor = (
|
||||
<View ref={'title_' + index} style={firstStyles.Title}>
|
||||
{titleContent}
|
||||
</View>
|
||||
);
|
||||
this._descriptors.title = this._descriptors.title.set(route, titleDescriptor);
|
||||
return titleDescriptor;
|
||||
},
|
||||
|
||||
_renderOrReturnRightButton: function(route, index) {
|
||||
var navState = this.props.navState;
|
||||
var navBarRouteMapper = this.props.routeMapper;
|
||||
var uid = navState.idStack[index];
|
||||
var alreadyRendered = this.refs['rightContainer' + uid];
|
||||
if (alreadyRendered) {
|
||||
// Don't bother re-calculating the children
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={'rightContainer' + uid}
|
||||
key={'rightContainer' + uid}
|
||||
shouldUpdate={false}
|
||||
/>
|
||||
);
|
||||
_getRightButton: function(route, index) {
|
||||
if (this._descriptors.right.has(route)) {
|
||||
return this._descriptors.right.get(route);
|
||||
}
|
||||
var rightContent = navBarRouteMapper.rightContentForRoute(
|
||||
navState.routeStack[index],
|
||||
var rightContent = this.props.routeMapper.rightContentForRoute(
|
||||
this.props.navState.routeStack[index],
|
||||
this.props.navigator
|
||||
);
|
||||
if (!rightContent) {
|
||||
this._descriptors.right = this._descriptors.right.set(route, null);
|
||||
return null;
|
||||
}
|
||||
var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={'rightContainer' + uid}
|
||||
key={'rightContainer' + uid}
|
||||
shouldUpdate={false}>
|
||||
<View ref={'right_' + index} style={firstStyles.RightItem}>
|
||||
{rightContent}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
var rightButtonDescriptor = (
|
||||
<View ref={'right_' + index} style={firstStyles.RightItem}>
|
||||
{rightContent}
|
||||
</View>
|
||||
);
|
||||
this._descriptors.right = this._descriptors.right.set(route, rightButtonDescriptor);
|
||||
return rightButtonDescriptor;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -32,6 +32,8 @@ var StaticContainer = require('StaticContainer.react');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var { Map } = require('immutable');
|
||||
|
||||
var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton'];
|
||||
|
||||
var navStatePresentedIndex = function(navState) {
|
||||
@@ -53,7 +55,6 @@ var NavigatorNavigationBar = React.createClass({
|
||||
}),
|
||||
navState: React.PropTypes.shape({
|
||||
routeStack: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
idStack: React.PropTypes.arrayOf(React.PropTypes.number),
|
||||
presentedIndex: React.PropTypes.number,
|
||||
}),
|
||||
style: View.propTypes.style,
|
||||
@@ -63,6 +64,16 @@ var NavigatorNavigationBar = React.createClass({
|
||||
Styles: NavigatorNavigationBarStyles,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._components = {};
|
||||
this._descriptors = {};
|
||||
|
||||
COMPONENT_NAMES.forEach(componentName => {
|
||||
this._components[componentName] = new Map();
|
||||
this._descriptors[componentName] = new Map();
|
||||
});
|
||||
},
|
||||
|
||||
_getReusableProps: function(
|
||||
/*string*/componentName,
|
||||
/*number*/index
|
||||
@@ -104,7 +115,7 @@ var NavigatorNavigationBar = React.createClass({
|
||||
}
|
||||
|
||||
COMPONENT_NAMES.forEach(function (componentName) {
|
||||
var component = this.refs[componentName + index];
|
||||
var component = this._components[componentName].get(this.props.navState.routeStack[index]);
|
||||
var props = this._getReusableProps(componentName, index);
|
||||
if (component && interpolate[componentName](props.style, amount)) {
|
||||
component.setNativeProps(props);
|
||||
@@ -128,7 +139,7 @@ var NavigatorNavigationBar = React.createClass({
|
||||
var navState = this.props.navState;
|
||||
var components = COMPONENT_NAMES.map(function (componentName) {
|
||||
return navState.routeStack.map(
|
||||
this._renderOrReturnComponent.bind(this, componentName)
|
||||
this._getComponent.bind(this, componentName)
|
||||
);
|
||||
}, this);
|
||||
|
||||
@@ -139,28 +150,19 @@ var NavigatorNavigationBar = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
_renderOrReturnComponent: function(
|
||||
_getComponent: function(
|
||||
/*string*/componentName,
|
||||
/*object*/route,
|
||||
/*number*/index
|
||||
) /*object*/ {
|
||||
var navState = this.props.navState;
|
||||
var uid = navState.idStack[index];
|
||||
var containerRef = componentName + 'Container' + uid;
|
||||
var alreadyRendered = this.refs[containerRef];
|
||||
if (alreadyRendered) {
|
||||
// Don't bother re-calculating the children
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={containerRef}
|
||||
key={containerRef}
|
||||
shouldUpdate={false}
|
||||
/>
|
||||
);
|
||||
) /*?Object*/ {
|
||||
if (this._descriptors[componentName].includes(route)) {
|
||||
return this._descriptors[componentName].get(route);
|
||||
}
|
||||
|
||||
var rendered = null;
|
||||
|
||||
var content = this.props.routeMapper[componentName](
|
||||
navState.routeStack[index],
|
||||
this.props.navState.routeStack[index],
|
||||
this.props.navigator,
|
||||
index,
|
||||
this.props.navState
|
||||
@@ -171,16 +173,18 @@ var NavigatorNavigationBar = React.createClass({
|
||||
|
||||
var initialStage = index === navStatePresentedIndex(this.props.navState) ?
|
||||
NavigatorNavigationBarStyles.Stages.Center : NavigatorNavigationBarStyles.Stages.Left;
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={containerRef}
|
||||
key={containerRef}
|
||||
shouldUpdate={false}>
|
||||
<View ref={componentName + index} style={initialStage[componentName]}>
|
||||
{content}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
rendered = (
|
||||
<View
|
||||
ref={(ref) => {
|
||||
this._components[componentName] = this._components[componentName].set(route, ref);
|
||||
}}
|
||||
style={initialStage[componentName]}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
|
||||
this._descriptors[componentName] = this._descriptors[componentName].set(route, rendered);
|
||||
return rendered;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
*
|
||||
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||
* Facebook, Inc. ("Facebook") owns all right, title and interest, including
|
||||
* all intellectual property and other proprietary rights, in and to the React
|
||||
* Native CustomComponents software (the “Software”). Subject to your
|
||||
* Native CustomComponents software (the "Software"). Subject to your
|
||||
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||
* and (2) reproduce and distribute the Software as part of your own software
|
||||
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||
* ("Your Software"). Facebook reserves all rights not expressly granted to
|
||||
* you in this license agreement.
|
||||
*
|
||||
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||
@@ -163,6 +163,56 @@ var ToTheLeft = {
|
||||
},
|
||||
};
|
||||
|
||||
var ToTheUp = {
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: 0, z: 0},
|
||||
to: {x: 0, y: -Dimensions.get('window').height, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
opacity: {
|
||||
value: 1.0,
|
||||
type: 'constant',
|
||||
},
|
||||
translateY: {
|
||||
from: 0,
|
||||
to: -Dimensions.get('window').height,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
};
|
||||
|
||||
var ToTheDown = {
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: 0, z: 0},
|
||||
to: {x: 0, y: Dimensions.get('window').height, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
opacity: {
|
||||
value: 1.0,
|
||||
type: 'constant',
|
||||
},
|
||||
translateY: {
|
||||
from: 0,
|
||||
to: Dimensions.get('window').height,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheRight = {
|
||||
opacity: {
|
||||
value: 1.0,
|
||||
@@ -221,6 +271,50 @@ var FromTheLeft = {
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheDown = {
|
||||
...FromTheRight,
|
||||
transformTranslate: {
|
||||
from: {y: SCREEN_HEIGHT, x: 0, z: 0},
|
||||
to: {x: 0, y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
translateY: {
|
||||
from: SCREEN_HEIGHT,
|
||||
to: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheTop = {
|
||||
...FromTheRight,
|
||||
transformTranslate: {
|
||||
from: {y: -SCREEN_HEIGHT, x: 0, z: 0},
|
||||
to: {x: 0, y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
translateY: {
|
||||
from: -SCREEN_HEIGHT,
|
||||
to: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
};
|
||||
|
||||
var ToTheBack = {
|
||||
// Rotate *requires* you to break out each individual component of
|
||||
// rotation (x, y, z, w)
|
||||
@@ -378,6 +472,18 @@ var BaseRightToLeftGesture = {
|
||||
direction: 'right-to-left',
|
||||
};
|
||||
|
||||
var BaseDownUpGesture = {
|
||||
...BaseLeftToRightGesture,
|
||||
fullDistance: SCREEN_HEIGHT,
|
||||
direction: 'down-to-up',
|
||||
};
|
||||
|
||||
var BaseUpDownGesture = {
|
||||
...BaseLeftToRightGesture,
|
||||
fullDistance: SCREEN_HEIGHT,
|
||||
direction: 'up-to-down',
|
||||
};
|
||||
|
||||
var BaseConfig = {
|
||||
// A list of all gestures that are enabled on this scene
|
||||
gestures: {
|
||||
@@ -468,6 +574,48 @@ var NavigatorSceneConfigs = {
|
||||
out: buildStyleInterpolator(ToTheLeft),
|
||||
},
|
||||
},
|
||||
VerticalUpSwipeJump: {
|
||||
...BaseConfig,
|
||||
gestures: {
|
||||
jumpBack: {
|
||||
...BaseDownUpGesture,
|
||||
overswipe: BaseOverswipeConfig,
|
||||
edgeHitWidth: null,
|
||||
isDetachable: true,
|
||||
},
|
||||
jumpForward: {
|
||||
...BaseDownUpGesture,
|
||||
overswipe: BaseOverswipeConfig,
|
||||
edgeHitWidth: null,
|
||||
isDetachable: true,
|
||||
},
|
||||
},
|
||||
animationInterpolators: {
|
||||
into: buildStyleInterpolator(FromTheDown),
|
||||
out: buildStyleInterpolator(ToTheUp),
|
||||
},
|
||||
},
|
||||
VerticalDownSwipeJump: {
|
||||
...BaseConfig,
|
||||
gestures: {
|
||||
jumpBack: {
|
||||
...BaseUpDownGesture,
|
||||
overswipe: BaseOverswipeConfig,
|
||||
edgeHitWidth: null,
|
||||
isDetachable: true,
|
||||
},
|
||||
jumpForward: {
|
||||
...BaseUpDownGesture,
|
||||
overswipe: BaseOverswipeConfig,
|
||||
edgeHitWidth: null,
|
||||
isDetachable: true,
|
||||
},
|
||||
},
|
||||
animationInterpolators: {
|
||||
into: buildStyleInterpolator(FromTheTop),
|
||||
out: buildStyleInterpolator(ToTheDown),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = NavigatorSceneConfigs;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule setupDevtools
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function setupDevtools() {
|
||||
var messageListeners = [];
|
||||
var closeListeners = [];
|
||||
var ws = new window.WebSocket('ws://localhost:8081/devtools');
|
||||
// this is accessed by the eval'd backend code
|
||||
var FOR_BACKEND = { // eslint-disable-line no-unused-vars
|
||||
resolveRNStyle: require('flattenStyle'),
|
||||
wall: {
|
||||
listen(fn) {
|
||||
messageListeners.push(fn);
|
||||
},
|
||||
onClose(fn) {
|
||||
closeListeners.push(fn);
|
||||
},
|
||||
send(data) {
|
||||
ws.send(JSON.stringify(data));
|
||||
},
|
||||
},
|
||||
};
|
||||
ws.onclose = () => {
|
||||
console.warn('devtools socket closed');
|
||||
closeListeners.forEach(fn => fn());
|
||||
};
|
||||
ws.onerror = error => {
|
||||
console.warn('devtools socket errored', error);
|
||||
closeListeners.forEach(fn => fn());
|
||||
};
|
||||
ws.onopen = function () {
|
||||
tryToConnect();
|
||||
};
|
||||
|
||||
function tryToConnect() {
|
||||
ws.send('attach:agent');
|
||||
var _interval = setInterval(() => ws.send('attach:agent'), 500);
|
||||
ws.onmessage = evt => {
|
||||
if (evt.data.indexOf('eval:') === 0) {
|
||||
clearInterval(_interval);
|
||||
initialize(evt.data.slice('eval:'.length));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function initialize(text) {
|
||||
try {
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
|
||||
CurrentOwner: require('ReactCurrentOwner'),
|
||||
InstanceHandles: require('ReactInstanceHandles'),
|
||||
Mount: require('ReactNativeMount'),
|
||||
Reconciler: require('ReactReconciler'),
|
||||
TextComponent: require('ReactNativeTextComponent'),
|
||||
});
|
||||
ws.onmessage = handleMessage;
|
||||
}
|
||||
|
||||
function handleMessage(evt) {
|
||||
var data;
|
||||
try {
|
||||
data = JSON.parse(evt.data);
|
||||
} catch (e) {
|
||||
return console.error('failed to parse json: ' + evt.data);
|
||||
}
|
||||
// the devtools closed
|
||||
if (data.$close || data.$error) {
|
||||
closeListeners.forEach(fn => fn());
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.emit('shutdown');
|
||||
tryToConnect();
|
||||
return;
|
||||
}
|
||||
if (data.$open) {
|
||||
return; // ignore
|
||||
}
|
||||
messageListeners.forEach(fn => {
|
||||
try {
|
||||
fn(data);
|
||||
} catch (e) {
|
||||
// jsc doesn't play so well with tracebacks that go into eval'd code,
|
||||
// so the stack trace here will stop at the `eval()` call. Getting the
|
||||
// message that caused the error is the best we can do for now.
|
||||
console.log(data);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = setupDevtools;
|
||||
@@ -151,6 +151,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
@@ -190,6 +191,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
|
||||
@@ -24,10 +24,8 @@ var StyleSheetPropType = require('StyleSheetPropType');
|
||||
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
var warning = require('warning');
|
||||
|
||||
/**
|
||||
@@ -54,9 +52,9 @@ var warning = require('warning');
|
||||
* },
|
||||
* ```
|
||||
*/
|
||||
|
||||
var Image = React.createClass({
|
||||
propTypes: {
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
/**
|
||||
* `uri` is a string representing the resource identifier for the image, which
|
||||
* could be an http address, a local file path, or the name of a static image
|
||||
@@ -68,16 +66,20 @@ var Image = React.createClass({
|
||||
/**
|
||||
* A static image to display while downloading the final image off the
|
||||
* network.
|
||||
* @platform ios
|
||||
*/
|
||||
defaultSource: PropTypes.shape({
|
||||
uri: PropTypes.string,
|
||||
}),
|
||||
/**
|
||||
* Whether this element should be revealed as an accessible element.
|
||||
* When true, indicates the image is an accessibility element.
|
||||
* @platform ios
|
||||
*/
|
||||
accessible: PropTypes.bool,
|
||||
/**
|
||||
* Custom string to display for accessibility.
|
||||
* The text that's read by the screen reader when the user interacts with
|
||||
* the image.
|
||||
* @platform ios
|
||||
*/
|
||||
accessibilityLabel: PropTypes.string,
|
||||
/**
|
||||
@@ -86,6 +88,7 @@ var Image = React.createClass({
|
||||
* of the image will be stretched. This is useful for creating resizable
|
||||
* rounded buttons, shadows, and other resizable assets. More info on
|
||||
* [Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets)
|
||||
* @platform ios
|
||||
*/
|
||||
capInsets: EdgeInsetsPropType,
|
||||
/**
|
||||
@@ -93,7 +96,6 @@ var Image = React.createClass({
|
||||
* image dimensions.
|
||||
*/
|
||||
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
|
||||
style: StyleSheetPropType(ImageStylePropTypes),
|
||||
/**
|
||||
* A unique identifier for this element to be used in UI Automation
|
||||
* testing scripts.
|
||||
@@ -101,36 +103,34 @@ var Image = React.createClass({
|
||||
testID: PropTypes.string,
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
* {nativeEvent: { layout: {x, y, width, height}}}.
|
||||
* `{nativeEvent: {layout: {x, y, width, height}}}`.
|
||||
*/
|
||||
onLayout: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load start
|
||||
* @platform ios
|
||||
*/
|
||||
onLoadStart: PropTypes.func,
|
||||
/**
|
||||
* Invoked on download progress with
|
||||
*
|
||||
* {nativeEvent: { written, total}}.
|
||||
* Invoked on download progress with `{nativeEvent: {loaded, total}}`
|
||||
* @platform ios
|
||||
*/
|
||||
onLoadProgress: PropTypes.func,
|
||||
onProgress: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load abort
|
||||
* Invoked on load error with `{nativeEvent: {error}}`
|
||||
* @platform ios
|
||||
*/
|
||||
onLoadAbort: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load error
|
||||
*
|
||||
* {nativeEvent: { error}}.
|
||||
* Invoked when load completes successfully
|
||||
* @platform ios
|
||||
*/
|
||||
onLoadError: PropTypes.func,
|
||||
onLoad: PropTypes.func,
|
||||
/**
|
||||
* Invoked on load end
|
||||
*
|
||||
* Invoked when load either succeeds or fails
|
||||
* @platform ios
|
||||
*/
|
||||
onLoaded: PropTypes.func
|
||||
|
||||
onLoadEnd: PropTypes.func,
|
||||
},
|
||||
|
||||
statics: {
|
||||
@@ -149,46 +149,33 @@ var Image = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
for (var prop in nativeOnlyProps) {
|
||||
for (var prop in cfg.nativeOnly) {
|
||||
if (this.props[prop] !== undefined) {
|
||||
console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
|
||||
'not be set directly on Image.');
|
||||
}
|
||||
}
|
||||
var source = resolveAssetSource(this.props.source) || {};
|
||||
var defaultSource = (this.props.defaultSource && resolveAssetSource(this.props.defaultSource)) || {};
|
||||
|
||||
var {width, height} = source;
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]);
|
||||
invariant(style, 'style must be initialized');
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
|
||||
|
||||
var isNetwork = source.uri && source.uri.match(/^https?:/);
|
||||
invariant(
|
||||
!(isNetwork && source.isStatic),
|
||||
'static image uris cannot start with "http": "' + source.uri + '"'
|
||||
var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView;
|
||||
var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
|
||||
var tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
|
||||
|
||||
return (
|
||||
<RawImage
|
||||
{...this.props}
|
||||
style={style}
|
||||
resizeMode={resizeMode}
|
||||
tintColor={tintColor}
|
||||
src={source.uri}
|
||||
defaultSrc={defaultSource.uri}
|
||||
/>
|
||||
);
|
||||
var isStored = !source.isStatic && !isNetwork;
|
||||
var RawImage = isNetwork ? RCTNetworkImage : RCTStaticImage;
|
||||
|
||||
if (this.props.style && this.props.style.tintColor) {
|
||||
warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.');
|
||||
}
|
||||
var resizeMode = this.props.resizeMode || style.resizeMode || 'cover';
|
||||
|
||||
var nativeProps = merge(this.props, {
|
||||
style,
|
||||
resizeMode,
|
||||
tintColor: style.tintColor,
|
||||
});
|
||||
if (isStored) {
|
||||
nativeProps.imageTag = source.uri;
|
||||
} else {
|
||||
nativeProps.src = source.uri;
|
||||
}
|
||||
if (this.props.defaultSource) {
|
||||
nativeProps.defaultImageSrc = this.props.defaultSource.uri;
|
||||
}
|
||||
nativeProps.progressHandlerRegistered = isNetwork && this.props.onLoadProgress;
|
||||
return <RawImage {...nativeProps} />;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -198,18 +185,15 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTNetworkImage = requireNativeComponent('RCTNetworkImageView', null);
|
||||
var RCTStaticImage = requireNativeComponent('RCTStaticImage', null);
|
||||
|
||||
var nativeOnlyProps = {
|
||||
src: true,
|
||||
defaultImageSrc: true,
|
||||
imageTag: true,
|
||||
progressHandlerRegistered: true
|
||||
var cfg = {
|
||||
nativeOnly: {
|
||||
src: true,
|
||||
defaultImageSrc: true,
|
||||
imageTag: true,
|
||||
progressHandlerRegistered: true,
|
||||
},
|
||||
};
|
||||
if (__DEV__) {
|
||||
verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps);
|
||||
verifyPropTypes(Image, RCTNetworkImage.viewConfig, nativeOnlyProps);
|
||||
}
|
||||
var RCTImageView = requireNativeComponent('RCTImageView', Image, cfg);
|
||||
var RCTNetworkImageView = (NativeModules.NetworkImageViewManager) ? requireNativeComponent('RCTNetworkImageView', Image, cfg) : RCTImageView;
|
||||
|
||||
module.exports = Image;
|
||||
|
||||
@@ -24,6 +24,7 @@ var ImageStylePropTypes = {
|
||||
borderColor: ReactPropTypes.string,
|
||||
borderWidth: ReactPropTypes.number,
|
||||
borderRadius: ReactPropTypes.number,
|
||||
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
|
||||
|
||||
// iOS-Specific style to "tint" an image.
|
||||
// It changes the color of all the non-transparent pixels to the tintColor
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
@@ -22,16 +23,18 @@
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag
|
||||
successCallback:(RCTResponseSenderBlock)successCallback
|
||||
errorCallback:(RCTResponseErrorBlock)errorCallback)
|
||||
{
|
||||
[RCTImageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) {
|
||||
[_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) {
|
||||
if (loadError) {
|
||||
errorCallback(loadError);
|
||||
return;
|
||||
}
|
||||
[[RCTImageLoader assetsLibrary] writeImageToSavedPhotosAlbum:[loadedImage CGImage] metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
[_bridge.assetsLibrary writeImageToSavedPhotosAlbum:[loadedImage CGImage] metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
|
||||
if (saveError) {
|
||||
RCTLogWarn(@"Error saving cropped image: %@", saveError);
|
||||
errorCallback(saveError);
|
||||
@@ -93,7 +96,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
|
||||
BOOL __block calledCallback = NO;
|
||||
NSMutableArray *assets = [[NSMutableArray alloc] init];
|
||||
|
||||
[[RCTImageLoader assetsLibrary] enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
|
||||
[_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
|
||||
if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) {
|
||||
|
||||
if (assetType == nil || [assetType isEqualToString:@"Photos"]) {
|
||||
|
||||
@@ -12,36 +12,33 @@
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static void *const RCTDownloadTaskWrapperCompletionBlockKey = (void *)&RCTDownloadTaskWrapperCompletionBlockKey;
|
||||
static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownloadTaskWrapperProgressBlockKey;
|
||||
@interface NSObject (RCTDownloadTaskWrapper)
|
||||
|
||||
@interface NSURLSessionTask (RCTDownloadTaskWrapper)
|
||||
|
||||
@property (nonatomic, copy, setter=rct_setCompletionBlock:) RCTDataCompletionBlock rct_completionBlock;
|
||||
@property (nonatomic, copy, setter=rct_setProgressBlock:) RCTDataProgressBlock rct_progressBlock;
|
||||
@property (nonatomic, copy) RCTDataCompletionBlock reactCompletionBlock;
|
||||
@property (nonatomic, copy) RCTDataProgressBlock reactProgressBlock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSessionTask (RCTDownloadTaskWrapper)
|
||||
@implementation NSObject (RCTDownloadTaskWrapper)
|
||||
|
||||
- (RCTDataCompletionBlock)rct_completionBlock
|
||||
- (RCTDataCompletionBlock)reactCompletionBlock
|
||||
{
|
||||
return objc_getAssociatedObject(self, RCTDownloadTaskWrapperCompletionBlockKey);
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)rct_setCompletionBlock:(RCTDataCompletionBlock)completionBlock
|
||||
- (void)setReactCompletionBlock:(RCTDataCompletionBlock)completionBlock
|
||||
{
|
||||
objc_setAssociatedObject(self, RCTDownloadTaskWrapperCompletionBlockKey, completionBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
objc_setAssociatedObject(self, @selector(reactCompletionBlock), completionBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
- (RCTDataProgressBlock)rct_progressBlock
|
||||
- (RCTDataProgressBlock)reactProgressBlock
|
||||
{
|
||||
return objc_getAssociatedObject(self, RCTDownloadTaskWrapperProgressBlockKey);
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)rct_setProgressBlock:(RCTDataProgressBlock)progressBlock
|
||||
- (void)setReactProgressBlock:(RCTDataProgressBlock)progressBlock
|
||||
{
|
||||
objc_setAssociatedObject(self, RCTDownloadTaskWrapperProgressBlockKey, progressBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
objc_setAssociatedObject(self, @selector(reactProgressBlock), progressBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -62,11 +59,9 @@ static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownload
|
||||
|
||||
- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock
|
||||
{
|
||||
NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url completionHandler:nil];
|
||||
task.rct_completionBlock = completionBlock;
|
||||
task.rct_progressBlock = progressBlock;
|
||||
|
||||
[task resume];
|
||||
NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url];
|
||||
task.reactCompletionBlock = completionBlock;
|
||||
task.reactProgressBlock = progressBlock;
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -74,28 +69,28 @@ static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownload
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
|
||||
{
|
||||
if (downloadTask.rct_completionBlock) {
|
||||
if (downloadTask.reactCompletionBlock) {
|
||||
NSData *data = [NSData dataWithContentsOfURL:location];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
downloadTask.rct_completionBlock(downloadTask.response, data, nil);
|
||||
downloadTask.reactCompletionBlock(downloadTask.response, data, nil);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)didWriteData totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
|
||||
{
|
||||
if (downloadTask.rct_progressBlock) {
|
||||
if (downloadTask.reactProgressBlock) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
downloadTask.rct_progressBlock(totalBytesWritten, totalBytesExpectedToWrite);
|
||||
downloadTask.reactProgressBlock(totalBytesWritten, totalBytesExpectedToWrite);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
||||
{
|
||||
if (error && task.rct_completionBlock) {
|
||||
if (error && task.reactCompletionBlock) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
task.rct_completionBlock(nil, nil, error);
|
||||
task.reactCompletionBlock(nil, nil, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */; };
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTImageView.m */; };
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; };
|
||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
|
||||
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
|
||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; };
|
||||
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -35,27 +35,27 @@
|
||||
/* Begin PBXFileReference section */
|
||||
03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTaskWrapper.h; sourceTree = "<group>"; };
|
||||
03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTaskWrapper.m; sourceTree = "<group>"; };
|
||||
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = "<group>"; };
|
||||
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = "<group>"; };
|
||||
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = "<group>"; };
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
|
||||
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageView.h; sourceTree = "<group>"; };
|
||||
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageView.m; sourceTree = "<group>"; };
|
||||
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageViewManager.h; sourceTree = "<group>"; };
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageViewManager.m; sourceTree = "<group>"; };
|
||||
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
|
||||
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
|
||||
1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; };
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; };
|
||||
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
|
||||
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
|
||||
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
|
||||
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
|
||||
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
|
||||
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
|
||||
143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
|
||||
35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; };
|
||||
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageStoreManager.m; sourceTree = "<group>"; };
|
||||
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
|
||||
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
|
||||
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageView.h; sourceTree = "<group>"; };
|
||||
58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageView.m; sourceTree = "<group>"; };
|
||||
58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageViewManager.h; sourceTree = "<group>"; };
|
||||
58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkImageViewManager.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -86,14 +86,14 @@
|
||||
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
|
||||
1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
|
||||
1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
|
||||
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */,
|
||||
58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */,
|
||||
58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */,
|
||||
58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */,
|
||||
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */,
|
||||
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */,
|
||||
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */,
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */,
|
||||
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
|
||||
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
|
||||
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
|
||||
1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */,
|
||||
35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */,
|
||||
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
|
||||
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
|
||||
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
|
||||
58B5115E1A9E6B3D00147676 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
@@ -164,17 +164,17 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
|
||||
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
|
||||
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
|
||||
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
|
||||
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
|
||||
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,
|
||||
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
|
||||
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
|
||||
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
|
||||
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
|
||||
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
|
||||
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
|
||||
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -211,6 +211,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
@@ -250,6 +251,7 @@
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
|
||||
GCC_WARN_SHADOW = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
|
||||
@@ -38,15 +38,9 @@ typedef void (^RCTImageDownloadCancellationBlock)(void);
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
tintColor:(UIColor *)tintColor
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
progressBlock:(RCTDataProgressBlock)progressBlock
|
||||
block:(RCTImageDownloadBlock)block;
|
||||
|
||||
/**
|
||||
* Cancel an in-flight download. If multiple requets have been made for the
|
||||
* same image, only the request that relates to the token passed will be
|
||||
* cancelled.
|
||||
*/
|
||||
- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken;
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
#import "RCTImageDownloader.h"
|
||||
|
||||
#import "RCTDownloadTaskWrapper.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error);
|
||||
typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSURLResponse *response,
|
||||
NSData *data, NSError *error);
|
||||
|
||||
CGSize RCTTargetSizeForClipRect(CGRect);
|
||||
CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
@@ -50,7 +52,9 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url progressBlock:progressBlock block:(RCTCachedDataDownloadBlock)block
|
||||
- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url
|
||||
progressBlock:progressBlock
|
||||
block:(RCTCachedDataDownloadBlock)block
|
||||
{
|
||||
NSString *const cacheKey = url.absoluteString;
|
||||
|
||||
@@ -79,24 +83,36 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
_pendingBlocks[cacheKey] = [NSMutableArray arrayWithObject:block];
|
||||
|
||||
__weak RCTImageDownloader *weakSelf = self;
|
||||
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
|
||||
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSURLResponse *response, NSData *data, NSError *error) {
|
||||
|
||||
if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (httpResponse.statusCode != 200) {
|
||||
data = nil;
|
||||
error = [[NSError alloc] initWithDomain:NSURLErrorDomain
|
||||
code:httpResponse.statusCode
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(_processingQueue, ^{
|
||||
RCTImageDownloader *strongSelf = weakSelf;
|
||||
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
||||
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
||||
for (RCTCachedDataDownloadBlock downloadBlock in blocks) {
|
||||
downloadBlock(cached, data, error);
|
||||
downloadBlock(cached, response, data, error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:url];
|
||||
task = [_downloadTaskWrapper downloadData:url progressBlock:progressBlock completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
|
||||
if (!cancelled) {
|
||||
runBlocks(NO, data, error);
|
||||
runBlocks(NO, response, data, error);
|
||||
}
|
||||
|
||||
if (response) {
|
||||
if (response && !error) {
|
||||
RCTImageDownloader *strongSelf = weakSelf;
|
||||
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
|
||||
[strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
|
||||
@@ -105,25 +121,26 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
}];
|
||||
|
||||
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedResponse) {
|
||||
runBlocks(YES, cachedResponse.data, nil);
|
||||
} else {
|
||||
[task resume];
|
||||
}
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedResponse) {
|
||||
runBlocks(YES, cachedResponse.response, cachedResponse.data, nil);
|
||||
} else {
|
||||
[task resume];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return [cancel copy];
|
||||
}
|
||||
|
||||
- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block
|
||||
- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url
|
||||
progressBlock:(RCTDataProgressBlock)progressBlock
|
||||
block:(RCTDataDownloadBlock)block
|
||||
{
|
||||
return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSData *data, NSError *error) {
|
||||
return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSURLResponse *response, NSData *data, NSError *error) {
|
||||
block(data, error);
|
||||
}];
|
||||
}
|
||||
@@ -132,28 +149,24 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
tintColor:(UIColor *)tintColor
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
progressBlock:(RCTDataProgressBlock)progressBlock
|
||||
block:(RCTImageDownloadBlock)block
|
||||
{
|
||||
scale = scale ?: RCTScreenScale();
|
||||
|
||||
return [self downloadDataForURL:url progressBlock:progressBlock block:^(NSData *data, NSError *error) {
|
||||
if (!data || error) {
|
||||
block(nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
// Target size wasn't available yet, so abort image drawing
|
||||
block(nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
if (image) {
|
||||
if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
|
||||
|
||||
// Get scale and size
|
||||
CGFloat destScale = scale ?: RCTScreenScale();
|
||||
CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
|
||||
CGRect imageRect = RCTClipRect(image.size, scale, size, scale, resizeMode);
|
||||
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
|
||||
|
||||
// Opacity optimizations
|
||||
@@ -169,11 +182,15 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
}
|
||||
|
||||
// Decompress image at required size
|
||||
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
|
||||
UIGraphicsBeginImageContextWithOptions(destSize, opaque, scale);
|
||||
if (blendColor) {
|
||||
[blendColor setFill];
|
||||
UIRectFill((CGRect){CGPointZero, destSize});
|
||||
}
|
||||
if (tintColor) {
|
||||
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
[tintColor setFill];
|
||||
}
|
||||
[image drawInRect:imageRect];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
@@ -183,90 +200,4 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken
|
||||
{
|
||||
if (downloadToken) {
|
||||
downloadToken();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Returns the optimal context size for an image drawn using the clip rect
|
||||
* returned by RCTClipRect.
|
||||
*/
|
||||
CGSize RCTTargetSizeForClipRect(CGRect clipRect)
|
||||
{
|
||||
return (CGSize){
|
||||
clipRect.size.width + clipRect.origin.x * 2,
|
||||
clipRect.size.height + clipRect.origin.y * 2
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes an input content size & scale (typically from an image),
|
||||
* a target size & scale that it will be drawn into (typically a CGContext) and
|
||||
* then calculates the optimal rectangle to draw the image into so that it will
|
||||
* be sized and positioned correctly if drawn using the specified content mode.
|
||||
*/
|
||||
CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
|
||||
CGSize destSize, CGFloat destScale,
|
||||
UIViewContentMode resizeMode)
|
||||
{
|
||||
// Precompensate for scale
|
||||
CGFloat scale = sourceScale / destScale;
|
||||
sourceSize.width *= scale;
|
||||
sourceSize.height *= scale;
|
||||
|
||||
// Calculate aspect ratios if needed (don't bother is resizeMode == stretch)
|
||||
CGFloat aspect = 0.0, targetAspect = 0.0;
|
||||
if (resizeMode != UIViewContentModeScaleToFill) {
|
||||
aspect = sourceSize.width / sourceSize.height;
|
||||
targetAspect = destSize.width / destSize.height;
|
||||
if (aspect == targetAspect) {
|
||||
resizeMode = UIViewContentModeScaleToFill;
|
||||
}
|
||||
}
|
||||
|
||||
switch (resizeMode) {
|
||||
case UIViewContentModeScaleToFill: // stretch
|
||||
|
||||
sourceSize.width = MIN(destSize.width, sourceSize.width);
|
||||
sourceSize.height = MIN(destSize.height, sourceSize.height);
|
||||
return (CGRect){CGPointZero, sourceSize};
|
||||
|
||||
case UIViewContentModeScaleAspectFit: // contain
|
||||
|
||||
if (targetAspect <= aspect) { // target is taller than content
|
||||
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
|
||||
sourceSize.height = sourceSize.width / aspect;
|
||||
} else { // target is wider than content
|
||||
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
|
||||
sourceSize.width = sourceSize.height * aspect;
|
||||
}
|
||||
return (CGRect){CGPointZero, sourceSize};
|
||||
|
||||
case UIViewContentModeScaleAspectFill: // cover
|
||||
|
||||
if (targetAspect <= aspect) { // target is taller than content
|
||||
|
||||
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
|
||||
sourceSize.width = sourceSize.height * aspect;
|
||||
destSize.width = destSize.height * targetAspect;
|
||||
return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize};
|
||||
|
||||
} else { // target is wider than content
|
||||
|
||||
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
|
||||
sourceSize.height = sourceSize.width / aspect;
|
||||
destSize.height = destSize.width / targetAspect;
|
||||
return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize};
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
|
||||
return (CGRect){CGPointZero, destSize};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,58 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
@class ALAssetsLibrary;
|
||||
@class UIImage;
|
||||
|
||||
@interface RCTImageLoader : NSObject
|
||||
typedef void (^RCTImageLoaderProgressBlock)(int64_t written, int64_t total);
|
||||
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* UIImage or CAAnimation */);
|
||||
typedef void (^RCTImageLoaderCancellationBlock)(void);
|
||||
|
||||
+ (ALAssetsLibrary *)assetsLibrary;
|
||||
@interface RCTImageLoader : NSObject <RCTBridgeModule>
|
||||
|
||||
/**
|
||||
* Can be called from any thread.
|
||||
* Will always call callback on main thread.
|
||||
* Loads the specified image at the highest available resolution.
|
||||
* Can be called from any thread, will always call callback on main thread.
|
||||
*/
|
||||
+ (void)loadImageWithTag:(NSString *)tag
|
||||
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback;
|
||||
|
||||
/**
|
||||
* As above, but includes target size, scale and resizeMode, which are used to
|
||||
* select the optimal dimensions for the loaded image.
|
||||
*/
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progress
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completion;
|
||||
|
||||
/**
|
||||
* Is the specified image tag an asset library image?
|
||||
*/
|
||||
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag;
|
||||
|
||||
/**
|
||||
* Is the specified image tag a remote image?
|
||||
*/
|
||||
+ (BOOL)isRemoteImage:(NSString *)imageTag;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTImageLoader)
|
||||
|
||||
/**
|
||||
* The shared image loader instance
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTImageLoader *imageLoader;
|
||||
|
||||
/**
|
||||
* The shared asset library instance.
|
||||
*/
|
||||
@property (nonatomic, readonly) ALAssetsLibrary *assetsLibrary;
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
#import <Photos/PHImageManager.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTDefines.h"
|
||||
#import "RCTGIFImage.h"
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTImageStoreManager.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@@ -45,25 +48,86 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
||||
}
|
||||
|
||||
@implementation RCTImageLoader
|
||||
|
||||
+ (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
static ALAssetsLibrary *assetsLibrary = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
assetsLibrary = [[ALAssetsLibrary alloc] init];
|
||||
});
|
||||
return assetsLibrary;
|
||||
ALAssetsLibrary *_assetsLibrary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called from any thread.
|
||||
* Will always call callback on main thread.
|
||||
*/
|
||||
+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id image))callback
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
callback:(RCTImageLoaderCompletionBlock)callback
|
||||
{
|
||||
if ([imageTag hasPrefix:@"assets-library"]) {
|
||||
[[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
|
||||
return [self loadImageWithTag:imageTag
|
||||
size:CGSizeZero
|
||||
scale:0
|
||||
resizeMode:UIViewContentModeScaleToFill
|
||||
progressBlock:nil
|
||||
completionBlock:callback];
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
NSUInteger length = (NSUInteger)representation.size;
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
if (![representation getBytes:data.mutableBytes
|
||||
fromOffset:0
|
||||
length:length
|
||||
error:error]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGSize sourceSize = representation.dimensions;
|
||||
CGRect targetRect = RCTClipRect(sourceSize, representation.scale, size, scale, resizeMode);
|
||||
CGSize targetSize = targetRect.size;
|
||||
|
||||
NSDictionary *options = @{
|
||||
(id)kCGImageSourceShouldAllowFloat: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
|
||||
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
|
||||
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
|
||||
};
|
||||
|
||||
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
|
||||
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
|
||||
if (sourceRef) {
|
||||
CFRelease(sourceRef);
|
||||
}
|
||||
|
||||
if (imageRef) {
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
|
||||
orientation:(UIImageOrientation)representation.orientation];
|
||||
CGImageRelease(imageRef);
|
||||
return image;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
if (!_assetsLibrary) {
|
||||
_assetsLibrary = [[ALAssetsLibrary alloc] init];
|
||||
}
|
||||
return _assetsLibrary;
|
||||
}
|
||||
|
||||
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
resizeMode:(UIViewContentMode)resizeMode
|
||||
progressBlock:(RCTImageLoaderProgressBlock)progress
|
||||
completionBlock:(RCTImageLoaderCompletionBlock)completion
|
||||
{
|
||||
if ([imageTag hasPrefix:@"assets-library://"]) {
|
||||
[[self assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
|
||||
if (asset) {
|
||||
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
|
||||
// resolution images at once will spike the memory up to store the image data,
|
||||
@@ -73,80 +137,155 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
|
||||
// Also make sure the image is released immediately after it's used so it
|
||||
// doesn't spike the memory up during the process.
|
||||
@autoreleasepool {
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
ALAssetOrientation orientation = [representation orientation];
|
||||
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation];
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, image);
|
||||
|
||||
UIImage *image;
|
||||
NSError *error = nil;
|
||||
if (useMaximumSize) {
|
||||
image = [UIImage imageWithCGImage:representation.fullResolutionImage
|
||||
scale:scale
|
||||
orientation:(UIImageOrientation)representation.orientation];
|
||||
} else {
|
||||
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
|
||||
}
|
||||
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, image);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
} failureBlock:^(NSError *loadError) {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}];
|
||||
return ^{};
|
||||
} else if ([imageTag hasPrefix:@"ph://"]) {
|
||||
// Using PhotoKit for iOS 8+
|
||||
// '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 NSURL which is what assets-library is based on.
|
||||
// This means if we use any FB standard photo picker, we will get this prefix =(
|
||||
// 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 = [imageTag 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);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
return;
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
return ^{};
|
||||
}
|
||||
|
||||
PHAsset *asset = [results firstObject];
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
|
||||
PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init];
|
||||
|
||||
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
||||
CGSize targetSize;
|
||||
|
||||
if ( useMaximumSize ){
|
||||
targetSize = PHImageManagerMaximumSize;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
} else {
|
||||
targetSize = size;
|
||||
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
|
||||
}
|
||||
|
||||
PHImageContentMode contentMode = PHImageContentModeAspectFill;
|
||||
if (resizeMode == UIViewContentModeScaleAspectFit) {
|
||||
contentMode = PHImageContentModeAspectFit;
|
||||
}
|
||||
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||
if (result) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, result);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, result);
|
||||
} else {
|
||||
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
|
||||
NSError *error = RCTErrorWithMessage(errorText);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
return ^{};
|
||||
} else if ([imageTag hasPrefix:@"http"]) {
|
||||
NSURL *url = [NSURL URLWithString:imageTag];
|
||||
if (!url) {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
|
||||
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
|
||||
return;
|
||||
RCTDispatchCallbackOnMainQueue(completion, RCTErrorWithMessage(errorMessage), nil);
|
||||
return ^{};
|
||||
}
|
||||
[[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
|
||||
if (error) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
|
||||
return [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:progress block:^(NSData *data, NSError *error) {
|
||||
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
|
||||
if (!image && !error) {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
|
||||
error = RCTErrorWithMessage(errorMessage);
|
||||
}
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, image);
|
||||
}];
|
||||
} else {
|
||||
return [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:progress block:^(UIImage *image, NSError *error) {
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, image);
|
||||
}];
|
||||
}
|
||||
} else if ([imageTag hasPrefix:@"rct-image-store://"]) {
|
||||
[_bridge.imageStoreManager getImageForTag:imageTag withBlock:^(UIImage *image) {
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, image);
|
||||
} else {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, [UIImage imageWithData:data]);
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
}];
|
||||
} else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
|
||||
return ^{};
|
||||
} else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
|
||||
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, image);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
return ^{};
|
||||
} else {
|
||||
UIImage *image = [RCTConvert UIImage:imageTag];
|
||||
if (image) {
|
||||
RCTDispatchCallbackOnMainQueue(callback, nil, image);
|
||||
RCTDispatchCallbackOnMainQueue(completion, nil, image);
|
||||
} else {
|
||||
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
|
||||
NSError *error = RCTErrorWithMessage(errorMessage);
|
||||
RCTDispatchCallbackOnMainQueue(callback, error, nil);
|
||||
RCTDispatchCallbackOnMainQueue(completion, error, nil);
|
||||
}
|
||||
return ^{};
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag
|
||||
{
|
||||
return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph://"];
|
||||
}
|
||||
|
||||
+ (BOOL)isRemoteImage:(NSString *)imageTag
|
||||
{
|
||||
return [imageTag hasPrefix:@"http://"] || [imageTag hasPrefix:@"https://"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTImageLoader)
|
||||
|
||||
- (RCTImageLoader *)imageLoader
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])];
|
||||
}
|
||||
|
||||
- (ALAssetsLibrary *)assetsLibrary
|
||||
{
|
||||
return [self.imageLoader assetsLibrary];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTImageRequestHandler
|
||||
{
|
||||
NSInteger _currentToken;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
{
|
||||
return [@[@"assets-library", @"ph"] containsObject:[request.URL.scheme lowercaseString]];
|
||||
@@ -28,9 +28,10 @@ RCT_EXPORT_MODULE()
|
||||
- (id)sendRequest:(NSURLRequest *)request
|
||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
||||
{
|
||||
NSNumber *requestToken = @(++_currentToken);
|
||||
NSString *URLString = [request.URL absoluteString];
|
||||
[RCTImageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) {
|
||||
|
||||
__block RCTImageLoaderCancellationBlock requestToken = nil;
|
||||
requestToken = [_bridge.imageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
[delegate URLRequest:requestToken didCompleteWithError:error];
|
||||
return;
|
||||
@@ -59,4 +60,11 @@ RCT_EXPORT_MODULE()
|
||||
return requestToken;
|
||||
}
|
||||
|
||||
- (void)cancelRequest:(id /* RCTImageLoaderCancellationBlock */)requestToken
|
||||
{
|
||||
if (requestToken) {
|
||||
((RCTImageLoaderCancellationBlock)requestToken)();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTURLRequestHandler.h"
|
||||
|
||||
@interface RCTImageStoreManager : NSObject<RCTURLRequestHandler>
|
||||
|
||||
/**
|
||||
* Set and get cached images. These must be called from the main thread.
|
||||
*/
|
||||
- (NSString *)storeImage:(UIImage *)image;
|
||||
- (UIImage *)imageForTag:(NSString *)imageTag;
|
||||
|
||||
/**
|
||||
* Set and get cached images asynchronously. It is safe to call these from any
|
||||
* thread. The callbacks will be called on the main thread.
|
||||
*/
|
||||
- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block;
|
||||
- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTImageStoreManager)
|
||||
|
||||
@property (nonatomic, readonly) RCTImageStoreManager *imageStoreManager;
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user