mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-06 17:34:07 +08:00
Second Update from Tue 24 Mar
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
[libs]
|
||||
Libraries/react-native/react-native-interface.js
|
||||
Examples/UIExplorer/ImageMocks.js
|
||||
|
||||
[options]
|
||||
module.system=haste
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
13ACB6741AC2117000FF4204 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13ACB6711AC2113600FF4204 /* libRCTAnimation.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 */; };
|
||||
@@ -16,6 +17,13 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
13ACB6701AC2113600FF4204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 13ACB66C1AC2113500FF4204 /* RCTAnimation.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = RCTAnimation;
|
||||
};
|
||||
832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
|
||||
@@ -33,6 +41,7 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
13ACB66C1AC2113500FF4204 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = ../../Libraries/Animation/RCTAnimation.xcodeproj; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* 2048.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 2048.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = 2048/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = 2048/AppDelegate.m; sourceTree = "<group>"; };
|
||||
@@ -49,6 +58,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13ACB6741AC2117000FF4204 /* libRCTAnimation.a in Frameworks */,
|
||||
8323482C1A77B59500B55238 /* libReactKit.a in Frameworks */,
|
||||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */,
|
||||
);
|
||||
@@ -57,6 +67,14 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
13ACB66D1AC2113500FF4204 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13ACB6711AC2113600FF4204 /* libRCTAnimation.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FAE1A68108700A75B9A /* 2048 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -75,6 +93,7 @@
|
||||
children = (
|
||||
834D32361A76971A00F38302 /* ReactKit.xcodeproj */,
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
|
||||
13ACB66C1AC2113500FF4204 /* RCTAnimation.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -153,6 +172,10 @@
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 13ACB66D1AC2113500FF4204 /* Products */;
|
||||
ProjectRef = 13ACB66C1AC2113500FF4204 /* RCTAnimation.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
|
||||
ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
|
||||
@@ -170,6 +193,13 @@
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
13ACB6711AC2113600FF4204 /* libRCTAnimation.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libRCTAnimation.a;
|
||||
remoteRef = 13ACB6701AC2113600FF4204 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
||||
@@ -18,9 +18,9 @@ var {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TimerMixin,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var MovieCell = require('./MovieCell');
|
||||
var MovieScreen = require('./MovieScreen');
|
||||
|
||||
@@ -14,9 +14,9 @@ var React = require('react-native');
|
||||
var {
|
||||
ActivityIndicatorIOS,
|
||||
StyleSheet,
|
||||
TimerMixin,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var ToggleAnimatingActivityIndicator = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ImageCapInsetsExample
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
35
Examples/UIExplorer/ImageMocks.js
Normal file
35
Examples/UIExplorer/ImageMocks.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
declare module 'image!story-background' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
declare module 'image!uie_comment_highlighted' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
declare module 'image!uie_comment_normal' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
declare module 'image!uie_thumb_normal' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
|
||||
declare module 'image!uie_thumb_selected' {
|
||||
declare var uri: string;
|
||||
declare var isStatic: boolean;
|
||||
}
|
||||
276
Examples/UIExplorer/JSNavigationStack/BreadcrumbNavSample.js
Normal file
276
Examples/UIExplorer/JSNavigationStack/BreadcrumbNavSample.js
Normal file
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule BreadcrumbNavSample
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BreadcrumbNavigationBar = require('BreadcrumbNavigationBar');
|
||||
var JSNavigationStack = require('JSNavigationStack');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var ScrollView = require('ScrollView');
|
||||
var TabBarItemIOS = require('TabBarItemIOS');
|
||||
var TabBarIOS = require('TabBarIOS');
|
||||
var Text = require('Text');
|
||||
var TouchableBounce = require('TouchableBounce');
|
||||
var View = require('View');
|
||||
|
||||
|
||||
|
||||
var SAMPLE_TEXT = 'Top Pushes. Middle Replaces. Bottom Pops.';
|
||||
|
||||
var _getRandomRoute = function() {
|
||||
return {
|
||||
backButtonTitle: 'Back' + ('' + 10 * Math.random()).substr(0, 1),
|
||||
content:
|
||||
SAMPLE_TEXT + '\nHere\'s a random number ' + Math.random(),
|
||||
title: Math.random() > 0.5 ? 'Hello' : 'There',
|
||||
rightButtonTitle: Math.random() > 0.5 ? 'Right' : 'Button',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var SampleNavigationBarRouteMapper = {
|
||||
rightContentForRoute: function(route, navigator) {
|
||||
if (route.rightButtonTitle) {
|
||||
return (
|
||||
<Text style={[styles.titleText, styles.filterText]}>
|
||||
{route.rightButtonTitle}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
titleContentForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableBounce
|
||||
onPress={() => navigator.push(_getRandomRoute())}>
|
||||
<View>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
);
|
||||
},
|
||||
iconForRoute: function(route, navigator) {
|
||||
var onPress =
|
||||
navigator.popToRoute.bind(navigator, route);
|
||||
return (
|
||||
<TouchableBounce onPress={onPress}>
|
||||
<View style={styles.crumbIconPlaceholder} />
|
||||
</TouchableBounce>
|
||||
);
|
||||
},
|
||||
separatorForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableBounce onPress={navigator.pop}>
|
||||
<View style={styles.crumbSeparatorPlaceholder} />
|
||||
</TouchableBounce>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var _delay = 400; // Just to test for race conditions with native nav.
|
||||
|
||||
var renderScene = function(route, navigator) {
|
||||
var content = route.content;
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.scene}>
|
||||
<TouchableBounce
|
||||
onPress={_pushRouteLater(navigator.push)}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>request push soon</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_pushRouteLater(navigator.replace)}>
|
||||
<View style={styles.button}>
|
||||
<Text>{content}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_pushRouteLater(navigator.replace)}>
|
||||
<View style={styles.button}>
|
||||
<Text>{content}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_pushRouteLater(navigator.replace)}>
|
||||
<View style={styles.button}>
|
||||
<Text>{content}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_pushRouteLater(navigator.replace)}>
|
||||
<View style={styles.button}>
|
||||
<Text>{content}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_pushRouteLater(navigator.replace)}>
|
||||
<View style={styles.button}>
|
||||
<Text>{content}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_popRouteLater(navigator.pop)}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>request pop soon</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={
|
||||
_immediatelySetTwoItemsLater(
|
||||
navigator.immediatelyResetRouteStack
|
||||
)
|
||||
}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>Immediate set two routes</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={_popToTopLater(navigator.popToTop)}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>pop to top soon</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
var _popToTopLater = function(popToTop) {
|
||||
return () => setTimeout(popToTop, _delay);
|
||||
};
|
||||
|
||||
var _pushRouteLater = function(push) {
|
||||
return () => setTimeout(
|
||||
() => push(_getRandomRoute()),
|
||||
_delay
|
||||
);
|
||||
};
|
||||
|
||||
var _immediatelySetTwoItemsLater = function(immediatelyResetRouteStack) {
|
||||
return () => setTimeout(
|
||||
() => immediatelyResetRouteStack([
|
||||
_getRandomRoute(),
|
||||
_getRandomRoute(),
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
var _popRouteLater = function(pop) {
|
||||
return () => setTimeout(pop, _delay);
|
||||
};
|
||||
|
||||
var BreadcrumbNavSample = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
selectedTab: 0,
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var initialRoute = {
|
||||
backButtonTitle: 'Start', // no back button for initial scene
|
||||
content: SAMPLE_TEXT,
|
||||
title: 'Campaigns',
|
||||
rightButtonTitle: 'Filter',
|
||||
};
|
||||
return (
|
||||
<TabBarIOS>
|
||||
<TabBarItemIOS
|
||||
selected={this.state.selectedTab === 0}
|
||||
onPress={this.onTabSelect.bind(this, 0)}
|
||||
icon={require('image!madman_tabnav_list')}
|
||||
title="One">
|
||||
<JSNavigationStack
|
||||
debugOverlay={false}
|
||||
style={[styles.appContainer]}
|
||||
initialRoute={initialRoute}
|
||||
renderScene={renderScene}
|
||||
navigationBar={
|
||||
<BreadcrumbNavigationBar
|
||||
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TabBarItemIOS>
|
||||
<TabBarItemIOS
|
||||
selected={this.state.selectedTab === 1}
|
||||
onPress={this.onTabSelect.bind(this, 1)}
|
||||
icon={require('image!madman_tabnav_create')}
|
||||
title="Two">
|
||||
<JSNavigationStack
|
||||
animationConfigRouteMapper={() => JSNavigationStack.AnimationConfigs.FloatFromBottom}
|
||||
debugOverlay={false}
|
||||
style={[styles.appContainer]}
|
||||
initialRoute={initialRoute}
|
||||
renderScene={renderScene}
|
||||
navigationBar={
|
||||
<BreadcrumbNavigationBar
|
||||
navigationBarRouteMapper={SampleNavigationBarRouteMapper}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TabBarItemIOS>
|
||||
</TabBarIOS>
|
||||
);
|
||||
},
|
||||
|
||||
onTabSelect: function(tab, event) {
|
||||
if (this.state.selectedTab !== tab) {
|
||||
this.setState({selectedTab: tab});
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
navigationItem: {
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
scene: {
|
||||
paddingTop: 50,
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#cccccc',
|
||||
margin: 50,
|
||||
marginTop: 26,
|
||||
padding: 10,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
appContainer: {
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#dddddd',
|
||||
flex: 1,
|
||||
},
|
||||
titleText: {
|
||||
fontSize: 18,
|
||||
color: '#666666',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 32,
|
||||
},
|
||||
filterText: {
|
||||
color: '#5577ff',
|
||||
},
|
||||
// TODO: Accept icons from route.
|
||||
crumbIconPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#666666',
|
||||
},
|
||||
crumbSeparatorPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#aaaaaa',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = BreadcrumbNavSample;
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
var JSNavigationStack = require('JSNavigationStack');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var ScrollView = require('ScrollView');
|
||||
var TouchableHighlight = require('TouchableHighlight');
|
||||
var BreadcrumbNavSample = require('./BreadcrumbNavSample');
|
||||
var NavigationBarSample = require('./NavigationBarSample');
|
||||
var JumpingNavSample = require('./JumpingNavSample');
|
||||
|
||||
class NavMenu extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ScrollView style={styles.scene}>
|
||||
<TouchableHighlight style={styles.button} onPress={() => {
|
||||
this.props.navigator.push({ id: 'breadcrumbs' });
|
||||
}}>
|
||||
<Text style={styles.buttonText}>Breadcrumbs Example</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.button} onPress={() => {
|
||||
this.props.navigator.push({ id: 'navbar' });
|
||||
}}>
|
||||
<Text style={styles.buttonText}>Navbar Example</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.button} onPress={() => {
|
||||
this.props.navigator.push({ id: 'jumping' });
|
||||
}}>
|
||||
<Text style={styles.buttonText}>Jumping Example</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight style={styles.button} onPress={() => {
|
||||
this.props.onExampleExit();
|
||||
}}>
|
||||
<Text style={styles.buttonText}>Exit JSNavigationStack Example</Text>
|
||||
</TouchableHighlight>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var TabBarExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: '<JSNavigationStack>',
|
||||
description: 'JS-implemented navigation',
|
||||
},
|
||||
|
||||
renderScene: function(route, nav) {
|
||||
switch (route.id) {
|
||||
case 'menu':
|
||||
return (
|
||||
<NavMenu
|
||||
navigator={nav}
|
||||
onExampleExit={this.props.onExampleExit}
|
||||
/>
|
||||
);
|
||||
case 'navbar':
|
||||
return <NavigationBarSample />;
|
||||
case 'breadcrumbs':
|
||||
return <BreadcrumbNavSample />;
|
||||
case 'jumping':
|
||||
return <JumpingNavSample />;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<JSNavigationStack
|
||||
style={styles.container}
|
||||
initialRoute={{ id: 'menu', }}
|
||||
renderScene={this.renderScene}
|
||||
animationConfigRouteMapper={(route) => JSNavigationStack.AnimationConfigs.FloatFromBottom}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: 'white',
|
||||
padding: 15,
|
||||
},
|
||||
buttonText: {
|
||||
},
|
||||
scene: {
|
||||
flex: 1,
|
||||
paddingTop: 64,
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = TabBarExample;
|
||||
192
Examples/UIExplorer/JSNavigationStack/JumpingNavSample.js
Normal file
192
Examples/UIExplorer/JSNavigationStack/JumpingNavSample.js
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule JumpingNavSample
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var JSNavigationStack = require('JSNavigationStack');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var ScrollView = require('ScrollView');
|
||||
var Text = require('Text');
|
||||
var TouchableBounce = require('TouchableBounce');
|
||||
var View = require('View');
|
||||
|
||||
var _getRandomRoute = function() {
|
||||
return {
|
||||
randNumber: Math.random(),
|
||||
};
|
||||
};
|
||||
|
||||
var INIT_ROUTE = _getRandomRoute();
|
||||
var ROUTE_STACK = [
|
||||
_getRandomRoute(),
|
||||
_getRandomRoute(),
|
||||
INIT_ROUTE,
|
||||
_getRandomRoute(),
|
||||
_getRandomRoute(),
|
||||
];
|
||||
var renderScene = function(route, navigator) {
|
||||
return (
|
||||
<ScrollView style={styles.scene}>
|
||||
<View style={styles.scroll}>
|
||||
<Text>{route.randNumber}</Text>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.jumpBack();
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>jumpBack</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.jumpForward();
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>jumpForward</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.jumpTo(INIT_ROUTE);
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>jumpTo initial route</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.push(_getRandomRoute());
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>destructive: push</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.replace(_getRandomRoute());
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>destructive: replace</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.pop();
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>destructive: pop</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.immediatelyResetRouteStack([
|
||||
_getRandomRoute(),
|
||||
_getRandomRoute(),
|
||||
]);
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>destructive: Immediate set two routes</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<TouchableBounce
|
||||
onPress={() => {
|
||||
navigator.popToTop();
|
||||
}}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>destructive: pop to top</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
class JumpingNavBar extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.navBar}>
|
||||
{this.props.routeStack.map((route, index) => (
|
||||
<TouchableBounce onPress={() => {
|
||||
this.props.navigator.jumpTo(route);
|
||||
}}>
|
||||
<View style={styles.navButton}>
|
||||
<Text
|
||||
style={[
|
||||
styles.navButtonText,
|
||||
this.props.navState.toIndex === index && styles.navButtonActive
|
||||
]}>
|
||||
{index}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var JumpingNavSample = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<JSNavigationStack
|
||||
debugOverlay={false}
|
||||
style={[styles.appContainer]}
|
||||
initialRoute={INIT_ROUTE}
|
||||
initialRouteStack={ROUTE_STACK}
|
||||
renderScene={renderScene}
|
||||
navigationBar={<JumpingNavBar routeStack={ROUTE_STACK} />}
|
||||
shouldJumpOnBackstackPop={true}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
scene: {
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
scroll: {
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#cccccc',
|
||||
margin: 50,
|
||||
marginTop: 26,
|
||||
padding: 10,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
},
|
||||
appContainer: {
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#dddddd',
|
||||
flex: 1,
|
||||
},
|
||||
navBar: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 90,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
navButton: {
|
||||
flex: 1,
|
||||
},
|
||||
navButtonText: {
|
||||
textAlign: 'center',
|
||||
fontSize: 32,
|
||||
marginTop: 25,
|
||||
},
|
||||
navButtonActive: {
|
||||
color: 'green',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = JumpingNavSample;
|
||||
118
Examples/UIExplorer/JSNavigationStack/NavigationBarSample.js
Normal file
118
Examples/UIExplorer/JSNavigationStack/NavigationBarSample.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule NavigationBarSample
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var JSNavigationStack = require('JSNavigationStack');
|
||||
var NavigationBar = require('NavigationBar');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var TouchableOpacity = require('TouchableOpacity');
|
||||
var View = require('View');
|
||||
|
||||
var cssVar = require('cssVar');
|
||||
|
||||
|
||||
var NavigationBarRouteMapper = {
|
||||
|
||||
LeftButton: function(route, navigator, index, navState) {
|
||||
if (index === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var previousRoute = navState.routeStack[index - 1];
|
||||
return (
|
||||
<TouchableOpacity onPress={() => navigator.pop()}>
|
||||
<View>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
{previousRoute.title}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
RightButton: function(route, navigator, index, navState) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => navigator.push(newRandomRoute())}>
|
||||
<View>
|
||||
<Text style={[styles.navBarText, styles.navBarButtonText]}>
|
||||
Next
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
|
||||
Title: function(route, navigator, index, navState) {
|
||||
return (
|
||||
<Text style={[styles.navBarText, styles.navBarTitleText]}>
|
||||
{route.title} [{index}]
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
function newRandomRoute() {
|
||||
return {
|
||||
content: 'Hello World!',
|
||||
title: 'Random ' + Math.round(Math.random() * 100),
|
||||
};
|
||||
}
|
||||
|
||||
var NavigationBarSample = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View style={styles.appContainer}>
|
||||
<JSNavigationStack
|
||||
debugOverlay={false}
|
||||
style={styles.appContainer}
|
||||
initialRoute={newRandomRoute()}
|
||||
renderScene={(route, navigator) => (
|
||||
<View style={styles.scene}>
|
||||
<Text>{route.content}</Text>
|
||||
</View>
|
||||
)}
|
||||
navigationBar={
|
||||
<NavigationBar
|
||||
navigationBarRouteMapper={NavigationBarRouteMapper}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
appContainer: {
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#ffffff',
|
||||
flex: 1,
|
||||
},
|
||||
scene: {
|
||||
paddingTop: 50,
|
||||
flex: 1,
|
||||
},
|
||||
navBarText: {
|
||||
fontSize: 16,
|
||||
marginVertical: 10,
|
||||
},
|
||||
navBarTitleText: {
|
||||
color: cssVar('fbui-bluegray-60'),
|
||||
fontWeight: 'bold',
|
||||
marginVertical: 9,
|
||||
},
|
||||
navBarButtonText: {
|
||||
color: cssVar('fbui-accent-blue'),
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationBarSample;
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule NestedBreadcrumbNavSample
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BreadcrumbNavigationBar = require('BreadcrumbNavigationBar');
|
||||
var JSNavigationStack = require('JSNavigationStack');
|
||||
var React = require('React');
|
||||
var ScrollView = require('ScrollView');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var TouchableBounce = require('TouchableBounce');
|
||||
var View = require('View');
|
||||
|
||||
var SAMPLE_TEXT = 'Top Pushes. Middle Replaces. Bottom Pops.';
|
||||
|
||||
var _getRandomRoute = function() {
|
||||
return {
|
||||
backButtonTitle: 'Back' + ('' + 10 * Math.random()).substr(0, 1),
|
||||
content:
|
||||
SAMPLE_TEXT + '\nHere\'s a random number ' + Math.random(),
|
||||
title: 'Pushed!',
|
||||
rightButtonTitle: Math.random() > 0.5 ? 'Right' : 'Button',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var HorizontalNavigationBarRouteMapper = {
|
||||
rightContentForRoute: function(route, navigator) {
|
||||
if (route.rightButtonTitle) {
|
||||
return (
|
||||
<Text style={[styles.titleText, styles.filterText]}>
|
||||
{route.rightButtonTitle}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
titleContentForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableBounce
|
||||
onPress={() => () => { navigator.push(_getRandomRoute()); }}>
|
||||
<View>
|
||||
<Text style={styles.titleText}>{route.title}</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
);
|
||||
},
|
||||
iconForRoute: function(route, navigator) {
|
||||
var onPress =
|
||||
navigator.popToRoute.bind(navigator, route);
|
||||
return (
|
||||
<TouchableBounce onPress={onPress}>
|
||||
<View style={styles.crumbIconPlaceholder} />
|
||||
</TouchableBounce>
|
||||
);
|
||||
},
|
||||
separatorForRoute: function(route, navigator) {
|
||||
return (
|
||||
<TouchableBounce onPress={navigator.pop}>
|
||||
<View style={styles.crumbSeparatorPlaceholder} />
|
||||
</TouchableBounce>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var ThirdDeepRouteMapper = (route, navigator) => (
|
||||
<View style={styles.navigationItem}>
|
||||
<ScrollView>
|
||||
<View style={styles.thirdDeepScrollContent}>
|
||||
<TouchableBounce
|
||||
onPress={() => { navigator.push(_getRandomRoute()); }}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>request push soon</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
||||
var SecondDeepRouteMapper = (route, navigator) => (
|
||||
<View style={styles.navigationItem}>
|
||||
<TouchableBounce
|
||||
onPress={() => { navigator.push(_getRandomRoute()); }}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>Push Horizontal</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<JSNavigationStack
|
||||
style={styles.thirdDeepNavigator}
|
||||
initialRoute={{title: '3x Nested Horizontal'}}
|
||||
renderScene={ThirdDeepRouteMapper}
|
||||
navigationBar={
|
||||
<BreadcrumbNavigationBar
|
||||
navigationBarRouteMapper={HorizontalNavigationBarRouteMapper}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
var FirstDeepRouteMapper = (route, navigator) => (
|
||||
<View style={styles.navigationItem}>
|
||||
<TouchableBounce
|
||||
onPress={() => { navigator.push(_getRandomRoute()); }}>
|
||||
<View style={styles.button}>
|
||||
<Text style={styles.buttonText}>Push Outer Vertical Stack</Text>
|
||||
</View>
|
||||
</TouchableBounce>
|
||||
<JSNavigationStack
|
||||
style={styles.secondDeepNavigator}
|
||||
initialRoute={{title: '2x Nested Horizontal Nav'}}
|
||||
renderScene={SecondDeepRouteMapper}
|
||||
navigationBar={
|
||||
<BreadcrumbNavigationBar
|
||||
navigationBarRouteMapper={HorizontalNavigationBarRouteMapper}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* The outer component.
|
||||
*/
|
||||
var NestedBreadcrumbNavSample = React.createClass({
|
||||
render: function() {
|
||||
var initialRoute = {title: 'Vertical'};
|
||||
// No navigation bar.
|
||||
return (
|
||||
<JSNavigationStack
|
||||
style={[styles.appContainer]}
|
||||
animationConfigRouteMapper={() => JSNavigationStack.AnimationConfigs.FloatFromBottom}
|
||||
initialRoute={initialRoute}
|
||||
renderScene={FirstDeepRouteMapper}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
navigationItem: {
|
||||
backgroundColor: '#eeeeee',
|
||||
shadowColor: 'black',
|
||||
shadowRadius: 20,
|
||||
shadowOffset: {w: 0, h: -10},
|
||||
},
|
||||
paddingForNavBar: {
|
||||
paddingTop: 60,
|
||||
},
|
||||
paddingForMenuBar: {
|
||||
paddingTop: 10,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#888888',
|
||||
margin: 10,
|
||||
marginTop: 10,
|
||||
padding: 10,
|
||||
marginRight: 20,
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 12,
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
},
|
||||
appContainer: {
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#dddddd',
|
||||
flex: 1,
|
||||
},
|
||||
titleText: {
|
||||
fontSize: 18,
|
||||
color: '#666666',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 32,
|
||||
},
|
||||
filterText: {
|
||||
color: '#5577ff',
|
||||
},
|
||||
// TODO: Accept icons from route.
|
||||
crumbIconPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#666666',
|
||||
},
|
||||
crumbSeparatorPlaceholder: {
|
||||
flex: 1,
|
||||
backgroundColor: '#aaaaaa',
|
||||
},
|
||||
secondDeepNavigator: {
|
||||
margin: 0,
|
||||
borderColor: '#666666',
|
||||
borderWidth: 0.5,
|
||||
height: 400,
|
||||
},
|
||||
thirdDeepNavigator: {
|
||||
margin: 0,
|
||||
borderColor: '#aaaaaa',
|
||||
borderWidth: 0.5,
|
||||
height: 400,
|
||||
},
|
||||
thirdDeepScrollContent: {
|
||||
height: 1000,
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NestedBreadcrumbNavSample;
|
||||
@@ -5,6 +5,8 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -188,7 +190,7 @@ exports.description = 'Base component to display maps';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Map',
|
||||
render() { return <MapViewExample />; }
|
||||
render(): ReactElement { return <MapViewExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Map shows user location',
|
||||
|
||||
125
Examples/UIExplorer/ResponderExample.js
Normal file
125
Examples/UIExplorer/ResponderExample.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
StyleSheet,
|
||||
PanResponder,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var CIRCLE_SIZE = 80;
|
||||
var CIRCLE_COLOR = 'blue';
|
||||
var CIRCLE_HIGHLIGHT_COLOR = 'green';
|
||||
|
||||
|
||||
var NavigatorIOSExample = React.createClass({
|
||||
|
||||
statics: {
|
||||
title: 'PanResponder Sample',
|
||||
description: 'Basic gesture handling example',
|
||||
},
|
||||
|
||||
_panResponder: {},
|
||||
_previousLeft: 0,
|
||||
_previousTop: 0,
|
||||
_circleStyles: {},
|
||||
circle: (null : ?React.Element),
|
||||
|
||||
componentWillMount: function() {
|
||||
this._panResponder = PanResponder.create({
|
||||
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
|
||||
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderRelease: this._handlePanResponderEnd,
|
||||
onPanResponderTerminate: this._handlePanResponderEnd,
|
||||
});
|
||||
this._previousLeft = 20;
|
||||
this._previousTop = 84;
|
||||
this._circleStyles = {
|
||||
left: this._previousLeft,
|
||||
top: this._previousTop,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._updatePosition();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View
|
||||
style={styles.container}>
|
||||
<View
|
||||
ref={(circle) => {
|
||||
this.circle = circle;
|
||||
}}
|
||||
style={styles.circle}
|
||||
{...this._panResponder.panHandlers}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_highlight: function() {
|
||||
this.circle && this.circle.setNativeProps({
|
||||
backgroundColor: CIRCLE_HIGHLIGHT_COLOR
|
||||
});
|
||||
},
|
||||
|
||||
_unHighlight: function() {
|
||||
this.circle && this.circle.setNativeProps({
|
||||
backgroundColor: CIRCLE_COLOR
|
||||
});
|
||||
},
|
||||
|
||||
_updatePosition: function() {
|
||||
this.circle && this.circle.setNativeProps(this._circleStyles);
|
||||
},
|
||||
|
||||
_handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
|
||||
// Should we become active when the user presses down on the circle?
|
||||
return true;
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
|
||||
// Should we become active when the user moves a touch over the circle?
|
||||
return true;
|
||||
},
|
||||
|
||||
_handlePanResponderGrant: function(e: Object, gestureState: Object) {
|
||||
this._highlight();
|
||||
},
|
||||
_handlePanResponderMove: function(e: Object, gestureState: Object) {
|
||||
this._circleStyles.left = this._previousLeft + gestureState.dx;
|
||||
this._circleStyles.top = this._previousTop + gestureState.dy;
|
||||
this._updatePosition();
|
||||
},
|
||||
_handlePanResponderEnd: function(e: Object, gestureState: Object) {
|
||||
this._unHighlight();
|
||||
this._previousLeft += gestureState.dx;
|
||||
this._previousTop += gestureState.dy;
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
circle: {
|
||||
width: CIRCLE_SIZE,
|
||||
height: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
backgroundColor: CIRCLE_COLOR,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 64,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigatorIOSExample;
|
||||
@@ -5,6 +5,8 @@
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -31,7 +33,7 @@ var TabBarExample = React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
_renderContent: function(color, pageText) {
|
||||
_renderContent: function(color: string, pageText: string) {
|
||||
return (
|
||||
<View style={[styles.tabContent, {backgroundColor: color}]}>
|
||||
<Text style={styles.tabText}>{pageText}</Text>
|
||||
|
||||
@@ -15,10 +15,10 @@ var {
|
||||
AlertIOS,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TimerMixin,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var Button = React.createClass({
|
||||
render: function() {
|
||||
|
||||
@@ -13,23 +13,42 @@
|
||||
|
||||
var React = require('react-native');
|
||||
var UIExplorerList = require('./UIExplorerList');
|
||||
|
||||
var {
|
||||
AppRegistry,
|
||||
NavigatorIOS,
|
||||
StyleSheet,
|
||||
} = React;
|
||||
|
||||
|
||||
var UIExplorerApp = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
openExternalExample: (null: ?React.Component),
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.openExternalExample) {
|
||||
var Example = this.state.openExternalExample;
|
||||
return (
|
||||
<Example
|
||||
onExampleExit={() => {
|
||||
this.setState({ openExternalExample: null, });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<NavigatorIOS
|
||||
style={styles.container}
|
||||
initialRoute={{
|
||||
title: 'UIExplorer',
|
||||
component: UIExplorerList,
|
||||
passProps: {
|
||||
onExternalExampleRequested: (example) => {
|
||||
this.setState({ openExternalExample: example, });
|
||||
},
|
||||
}
|
||||
}}
|
||||
itemWrapperStyle={styles.itemWrapper}
|
||||
tintColor='#008888'
|
||||
|
||||
@@ -21,6 +21,7 @@ var {
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = React;
|
||||
var JSNavigationStackExample = require('./JSNavigationStack/JSNavigationStackExample');
|
||||
|
||||
var createExamplePage = require('./createExamplePage');
|
||||
|
||||
@@ -32,6 +33,7 @@ var COMPONENTS = [
|
||||
require('./ListViewSimpleExample'),
|
||||
require('./MapViewExample'),
|
||||
require('./NavigatorIOSExample'),
|
||||
JSNavigationStackExample,
|
||||
require('./PickerExample'),
|
||||
require('./ScrollViewExample'),
|
||||
require('./SliderIOSExample'),
|
||||
@@ -57,6 +59,7 @@ var APIS = [
|
||||
require('./PointerEventsExample'),
|
||||
require('./PushNotificationIOSExample'),
|
||||
require('./StatusBarIOSExample'),
|
||||
require('./ResponderExample'),
|
||||
require('./TimerExample'),
|
||||
require('./VibrationIOSExample'),
|
||||
];
|
||||
@@ -143,6 +146,12 @@ class UIExplorerList extends React.Component {
|
||||
}
|
||||
|
||||
_onPressRow(example) {
|
||||
if (example === JSNavigationStackExample) {
|
||||
this.props.onExternalExampleRequested(
|
||||
JSNavigationStackExample
|
||||
);
|
||||
return;
|
||||
}
|
||||
var Component = example.examples ?
|
||||
createExamplePage(null, example) :
|
||||
example;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule createExamplePage
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -16,7 +17,19 @@ var UIExplorerPage = require('./UIExplorerPage');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var createExamplePage = function(title, exampleModule) {
|
||||
class Example extends React.Component {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
type ExampleModule = {
|
||||
title: string;
|
||||
description: string;
|
||||
examples: Array<Example>;
|
||||
};
|
||||
|
||||
var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
|
||||
: ReactClass<any, any, any> {
|
||||
invariant(!!exampleModule.examples, 'The module must have examples');
|
||||
|
||||
var ExamplePage = React.createClass({
|
||||
@@ -31,15 +44,17 @@ var createExamplePage = function(title, exampleModule) {
|
||||
var originalRenderComponent = React.renderComponent;
|
||||
var originalRender = React.render;
|
||||
var renderedComponent;
|
||||
React.render = React.renderComponent = function(element, container) {
|
||||
// TODO remove typecasts when Flow bug #6560135 is fixed
|
||||
// and workaround is removed from react-native.js
|
||||
(React: Object).render = (React: Object).renderComponent = function(element, container) {
|
||||
renderedComponent = element;
|
||||
};
|
||||
var result = example.render(null);
|
||||
if (result) {
|
||||
renderedComponent = result;
|
||||
}
|
||||
React.renderComponent = originalRenderComponent;
|
||||
React.render = originalRender;
|
||||
(React: Object).renderComponent = originalRenderComponent;
|
||||
(React: Object).render = originalRender;
|
||||
return (
|
||||
<UIExplorerBlock
|
||||
key={i}
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
580C37631AB0F62C0015E709 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C37551AB0F56E0015E709 /* libRCTImage.a */; };
|
||||
580C37641AB0F6350015E709 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C375A1AB0F5970015E709 /* libRCTNetwork.a */; };
|
||||
580C37651AB0F63E0015E709 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C375F1AB0F5D10015E709 /* libRCTText.a */; };
|
||||
580C37921AB1090B0015E709 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C378F1AB104B00015E709 /* libRCTTest.a */; };
|
||||
58B80D5F1ABA4147004008FB /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C378F1AB104B00015E709 /* libRCTTest.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
004D28A41AAF61C70097A701 /* PBXContainerItemProxy */ = {
|
||||
58005BCB1ABA44F10062E044 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
@@ -117,8 +117,8 @@
|
||||
580C37621AB0F6260015E709 /* libRCTGeolocation.a in Frameworks */,
|
||||
580C37631AB0F62C0015E709 /* libRCTImage.a in Frameworks */,
|
||||
580C37641AB0F6350015E709 /* libRCTNetwork.a in Frameworks */,
|
||||
58B80D5F1ABA4147004008FB /* libRCTTest.a in Frameworks */,
|
||||
580C37651AB0F63E0015E709 /* libRCTText.a in Frameworks */,
|
||||
580C37921AB1090B0015E709 /* libRCTTest.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -258,7 +258,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
004D28A51AAF61C70097A701 /* PBXTargetDependency */,
|
||||
58005BCC1ABA44F10062E044 /* PBXTargetDependency */,
|
||||
);
|
||||
name = IntegrationTestsTests;
|
||||
productName = IntegrationTestsTests;
|
||||
@@ -438,10 +438,10 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
004D28A51AAF61C70097A701 /* PBXTargetDependency */ = {
|
||||
58005BCC1ABA44F10062E044 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 13B07F861A680F5B00A75B9A /* IntegrationTests */;
|
||||
targetProxy = 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */;
|
||||
targetProxy = 58005BCB1ABA44F10062E044 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
@@ -468,6 +468,7 @@
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"FB_REFERENCE_IMAGE_DIR=\"\\\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\\\"\"",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = IntegrationTestsTests/Info.plist;
|
||||
|
||||
@@ -25,6 +25,7 @@ var TESTS = [
|
||||
require('./IntegrationTestHarnessTest'),
|
||||
require('./TimersTest'),
|
||||
require('./AsyncStorageTest'),
|
||||
require('./SimpleSnapshotTest'),
|
||||
];
|
||||
|
||||
TESTS.forEach(
|
||||
|
||||
@@ -24,34 +24,57 @@
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
_runner = [[RCTTestRunner alloc] initWithApp:@"IntegrationTests/IntegrationTestsApp"];
|
||||
#ifdef __LP64__
|
||||
RCTAssert(!__LP64__, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
|
||||
#endif
|
||||
NSString *version = [[UIDevice currentDevice] systemVersion];
|
||||
RCTAssert([version isEqualToString:@"8.1"], @"Tests should be run on iOS 8.1, found %@", version);
|
||||
_runner = initRunnerForApp(@"IntegrationTests/IntegrationTestsApp");
|
||||
|
||||
// If tests have changes, set recordMode = YES below and run the affected tests on an iPhone5, iOS 8.1 simulator.
|
||||
_runner.recordMode = NO;
|
||||
}
|
||||
|
||||
#pragma mark Logic Tests
|
||||
|
||||
- (void)testTheTester
|
||||
{
|
||||
[_runner runTest:@"IntegrationTestHarnessTest"];
|
||||
[_runner runTest:_cmd module:@"IntegrationTestHarnessTest"];
|
||||
}
|
||||
|
||||
- (void)testTheTester_waitOneFrame
|
||||
{
|
||||
[_runner runTest:@"IntegrationTestHarnessTest" initialProps:@{@"waitOneFrame": @YES} expectErrorBlock:nil];
|
||||
[_runner runTest:_cmd module:@"IntegrationTestHarnessTest" initialProps:@{@"waitOneFrame": @YES} expectErrorBlock:nil];
|
||||
}
|
||||
|
||||
- (void)testTheTester_ExpectError
|
||||
{
|
||||
[_runner runTest:@"IntegrationTestHarnessTest"
|
||||
[_runner runTest:_cmd
|
||||
module:@"IntegrationTestHarnessTest"
|
||||
initialProps:@{@"shouldThrow": @YES}
|
||||
expectErrorRegex:[NSRegularExpression regularExpressionWithPattern:@"because shouldThrow" options:0 error:nil]];
|
||||
}
|
||||
|
||||
- (void)testTimers
|
||||
{
|
||||
[_runner runTest:@"TimersTest"];
|
||||
[_runner runTest:_cmd module:@"TimersTest"];
|
||||
}
|
||||
|
||||
- (void)testAsyncStorage
|
||||
{
|
||||
[_runner runTest:@"AsyncStorageTest"];
|
||||
[_runner runTest:_cmd module:@"AsyncStorageTest"];
|
||||
}
|
||||
|
||||
#pragma mark Snapshot Tests
|
||||
|
||||
- (void)testSimpleSnapshot
|
||||
{
|
||||
[_runner runTest:_cmd module:@"SimpleSnapshotTest"];
|
||||
}
|
||||
|
||||
- (void)testZZZ_NotInRecordMode
|
||||
{
|
||||
RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
56
IntegrationTests/SimpleSnapshotTest.js
Normal file
56
IntegrationTests/SimpleSnapshotTest.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
StyleSheet,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var { TestModule } = React.addons;
|
||||
|
||||
var SimpleSnapshotTest = React.createClass({
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
}
|
||||
requestAnimationFrame(() => TestModule.verifySnapshot(this.done));
|
||||
},
|
||||
|
||||
done() {
|
||||
TestModule.markTestCompleted();
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{backgroundColor: 'white', padding: 100}}>
|
||||
<View style={styles.box1} />
|
||||
<View style={styles.box2} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
box1: {
|
||||
width: 80,
|
||||
height: 50,
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
box2: {
|
||||
top: -10,
|
||||
left: 20,
|
||||
width: 70,
|
||||
height: 90,
|
||||
backgroundColor: 'blue',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SimpleSnapshotTest;
|
||||
@@ -13,9 +13,9 @@ var React = require('react-native');
|
||||
var {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TimerMixin,
|
||||
View,
|
||||
} = React;
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var TimersTest = React.createClass({
|
||||
mixins: [TimerMixin],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ActionSheetIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -15,7 +16,7 @@ var RCTActionSheetManager = require('NativeModules').ActionSheetManager;
|
||||
var invariant = require('invariant');
|
||||
|
||||
var ActionSheetIOS = {
|
||||
showActionSheetWithOptions(options, callback) {
|
||||
showActionSheetWithOptions(options: Object, callback: Function) {
|
||||
invariant(
|
||||
typeof options === 'object' && options !== null,
|
||||
'Options must a valid object'
|
||||
@@ -31,7 +32,11 @@ var ActionSheetIOS = {
|
||||
);
|
||||
},
|
||||
|
||||
showShareActionSheetWithOptions(options, failureCallback, successCallback) {
|
||||
showShareActionSheetWithOptions(
|
||||
options: Object,
|
||||
failureCallback: Function,
|
||||
successCallback: Function
|
||||
) {
|
||||
invariant(
|
||||
typeof options === 'object' && options !== null,
|
||||
'Options must a valid object'
|
||||
|
||||
@@ -7,17 +7,18 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AdSupportIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AdSupport = require('NativeModules').AdSupport;
|
||||
|
||||
module.exports = {
|
||||
getAdvertisingId: function(onSuccess, onFailure) {
|
||||
getAdvertisingId: function(onSuccess: Function, onFailure: Function) {
|
||||
AdSupport.getAdvertisingId(onSuccess, onFailure);
|
||||
},
|
||||
|
||||
getAdvertisingTrackingEnabled: function(onSuccess, onFailure) {
|
||||
getAdvertisingTrackingEnabled: function(onSuccess: Function, onFailure: Function) {
|
||||
AdSupport.getAdvertisingTrackingEnabled(onSuccess, onFailure);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule LayoutAnimation
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -42,6 +43,15 @@ var animChecker = createStrictShapeTypeChecker({
|
||||
),
|
||||
});
|
||||
|
||||
type Anim = {
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
springDamping?: number;
|
||||
initialVelocity?: number;
|
||||
type?: $Enum<typeof Types>;
|
||||
property?: $Enum<typeof Properties>;
|
||||
}
|
||||
|
||||
var configChecker = createStrictShapeTypeChecker({
|
||||
duration: PropTypes.number.isRequired,
|
||||
create: animChecker,
|
||||
@@ -49,46 +59,56 @@ var configChecker = createStrictShapeTypeChecker({
|
||||
delete: animChecker,
|
||||
});
|
||||
|
||||
var LayoutAnimation = {
|
||||
configureNext(config, onAnimationDidEnd, onError) {
|
||||
configChecker({config}, 'config', 'LayoutAnimation.configureNext');
|
||||
RCTUIManager.configureNextLayoutAnimation(config, onAnimationDidEnd, onError);
|
||||
},
|
||||
create(duration, type, creationProp) {
|
||||
return {
|
||||
duration,
|
||||
create: {
|
||||
type,
|
||||
property: creationProp,
|
||||
},
|
||||
update: {
|
||||
type,
|
||||
},
|
||||
};
|
||||
},
|
||||
Types: Types,
|
||||
Properties: Properties,
|
||||
configChecker: configChecker,
|
||||
};
|
||||
type Config = {
|
||||
duration: number;
|
||||
create?: Anim;
|
||||
update?: Anim;
|
||||
delete?: Anim;
|
||||
}
|
||||
|
||||
LayoutAnimation.Presets = {
|
||||
easeInEaseOut: LayoutAnimation.create(
|
||||
0.3, Types.easeInEaseOut, Properties.opacity
|
||||
),
|
||||
linear: LayoutAnimation.create(
|
||||
0.5, Types.linear, Properties.opacity
|
||||
),
|
||||
spring: {
|
||||
duration: 0.7,
|
||||
function configureNext(config: Config, onAnimationDidEnd?: Function, onError?: Function) {
|
||||
configChecker({config}, 'config', 'LayoutAnimation.configureNext');
|
||||
RCTUIManager.configureNextLayoutAnimation(config, onAnimationDidEnd, onError);
|
||||
}
|
||||
|
||||
function create(duration: number, type, creationProp): Config {
|
||||
return {
|
||||
duration,
|
||||
create: {
|
||||
type: Types.linear,
|
||||
property: Properties.opacity,
|
||||
type,
|
||||
property: creationProp,
|
||||
},
|
||||
update: {
|
||||
type: Types.spring,
|
||||
springDamping: 0.4,
|
||||
type,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var LayoutAnimation = {
|
||||
configureNext,
|
||||
create,
|
||||
Types,
|
||||
Properties,
|
||||
configChecker: configChecker,
|
||||
Presets: {
|
||||
easeInEaseOut: create(
|
||||
0.3, Types.easeInEaseOut, Properties.opacity
|
||||
),
|
||||
linear: create(
|
||||
0.5, Types.linear, Properties.opacity
|
||||
),
|
||||
spring: {
|
||||
duration: 0.7,
|
||||
create: {
|
||||
type: Types.linear,
|
||||
property: Properties.opacity,
|
||||
},
|
||||
update: {
|
||||
type: Types.spring,
|
||||
springDamping: 0.4,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LayoutAnimation;
|
||||
|
||||
256
Libraries/Animation/RCTAnimation.xcodeproj/project.pbxproj
Normal file
256
Libraries/Animation/RCTAnimation.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,256 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
13BE3DEE1AC21097009241FE /* RCTAnimationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* RCTAnimationManager.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 /* libRCTAnimation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAnimation.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13BE3DEC1AC21097009241FE /* RCTAnimationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationManager.h; sourceTree = "<group>"; };
|
||||
13BE3DED1AC21097009241FE /* RCTAnimationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationManager.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 /* libRCTAnimation.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58B511D21A9E6C8500147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13BE3DEC1AC21097009241FE /* RCTAnimationManager.h */,
|
||||
13BE3DED1AC21097009241FE /* RCTAnimationManager.m */,
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
58B511DA1A9E6C8500147676 /* RCTAnimation */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimation" */;
|
||||
buildPhases = (
|
||||
58B511D71A9E6C8500147676 /* Sources */,
|
||||
58B511D81A9E6C8500147676 /* Frameworks */,
|
||||
58B511D91A9E6C8500147676 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTAnimation;
|
||||
productName = RCTDataManager;
|
||||
productReference = 134814201AA4EA6300B7C361 /* libRCTAnimation.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 "RCTAnimation" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 58B511D21A9E6C8500147676;
|
||||
productRefGroup = 58B511D21A9E6C8500147676;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
58B511DA1A9E6C8500147676 /* RCTAnimation */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
58B511D71A9E6C8500147676 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13BE3DEE1AC21097009241FE /* RCTAnimationManager.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)/../../ReactKit/**",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTAnimation;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../ReactKit/**",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos",
|
||||
);
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTAnimation;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimation" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511ED1A9E6C8500147676 /* Debug */,
|
||||
58B511EE1A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimation" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511F01A9E6C8500147676 /* Debug */,
|
||||
58B511F11A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#import "RCTAnimationManager.h"
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTSparseArray.h"
|
||||
@@ -54,10 +53,10 @@
|
||||
|
||||
return ^(CGFloat t) {
|
||||
const CGFloat *delta = deltaData.bytes;
|
||||
const CGFloat *fromArray = fromData.bytes;
|
||||
const CGFloat *_fromArray = fromData.bytes;
|
||||
|
||||
CGFloat value[count];
|
||||
CG_APPEND(vDSP_vma,,D)(delta, 1, &t, 0, fromArray, 1, value, 1, count);
|
||||
CG_APPEND(vDSP_vma,,D)(delta, 1, &t, 0, _fromArray, 1, value, 1, count);
|
||||
return [NSValue valueWithBytes:value objCType:typeName];
|
||||
};
|
||||
}
|
||||
@@ -84,10 +83,10 @@
|
||||
} else if ([obj respondsToSelector:@selector(count)]) {
|
||||
switch ([obj count]) {
|
||||
case 2:
|
||||
if ([obj respondsToSelector:@selector(objectForKey:)] && [obj objectForKey:@"w"]) {
|
||||
toValue = [NSValue valueWithCGSize:[RCTConvert CGSize:obj]];
|
||||
} else {
|
||||
if ([obj respondsToSelector:@selector(objectForKeyedSubscript:)] && obj[@"x"]) {
|
||||
toValue = [NSValue valueWithCGPoint:[RCTConvert CGPoint:obj]];
|
||||
} else {
|
||||
toValue = [NSValue valueWithCGSize:[RCTConvert CGSize:obj]];
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AppRegistry
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -21,6 +22,12 @@ if (__DEV__) {
|
||||
|
||||
var runnables = {};
|
||||
|
||||
type AppConfig = {
|
||||
appKey: string;
|
||||
component: ReactClass<any, any, any>;
|
||||
run?: Function;
|
||||
};
|
||||
|
||||
/**
|
||||
* `AppRegistry` is the JS entry point to running all React Native apps. App
|
||||
* root components should register themselves with
|
||||
@@ -33,17 +40,18 @@ var runnables = {};
|
||||
* `require`d.
|
||||
*/
|
||||
var AppRegistry = {
|
||||
registerConfig: function(config) {
|
||||
registerConfig: function(config: Array<AppConfig>) {
|
||||
for (var i = 0; i < config.length; ++i) {
|
||||
if (config[i].run) {
|
||||
AppRegistry.registerRunnable(config[i].appKey, config[i].run);
|
||||
var appConfig = config[i];
|
||||
if (appConfig.run) {
|
||||
AppRegistry.registerRunnable(appConfig.appKey, appConfig.run);
|
||||
} else {
|
||||
AppRegistry.registerComponent(config[i].appKey, config[i].component);
|
||||
AppRegistry.registerComponent(appConfig.appKey, appConfig.component);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
registerComponent: function(appKey, getComponentFunc) {
|
||||
registerComponent: function(appKey: string, getComponentFunc: Function): string {
|
||||
runnables[appKey] = {
|
||||
run: (appParameters) =>
|
||||
renderApplication(getComponentFunc(), appParameters.initialProps, appParameters.rootTag)
|
||||
@@ -51,12 +59,12 @@ var AppRegistry = {
|
||||
return appKey;
|
||||
},
|
||||
|
||||
registerRunnable: function(appKey, func) {
|
||||
registerRunnable: function(appKey: string, func: Function): string {
|
||||
runnables[appKey] = {run: func};
|
||||
return appKey;
|
||||
},
|
||||
|
||||
runApplication: function(appKey, appParameters) {
|
||||
runApplication: function(appKey: string, appParameters: any): void {
|
||||
console.log(
|
||||
'Running application "' + appKey + '" with appParams: ' +
|
||||
JSON.stringify(appParameters) + '. ' +
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AppStateIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule AppStateIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var TextInputState = require('TextInputState');
|
||||
var TimerMixin = require('TimerMixin');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
|
||||
@@ -14,7 +14,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var TimerMixin = require('TimerMixin');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var Touchable = require('Touchable');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
var View = require('View');
|
||||
|
||||
860
Libraries/CustomComponents/JSNavigationStack.js
Normal file
860
Libraries/CustomComponents/JSNavigationStack.js
Normal file
@@ -0,0 +1,860 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule JSNavigationStack
|
||||
*/
|
||||
|
||||
"use strict"
|
||||
|
||||
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
|
||||
var Backstack = require('Backstack');
|
||||
var Dimensions = require('Dimensions');
|
||||
var InteractionMixin = require('InteractionMixin');
|
||||
var JSNavigationStackAnimationConfigs = require('JSNavigationStackAnimationConfigs');
|
||||
var PanResponder = require('PanResponder');
|
||||
var React = require('React');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Subscribable = require('Subscribable');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
var View = require('View');
|
||||
|
||||
var clamp = require('clamp');
|
||||
var invariant = require('invariant');
|
||||
var keyMirror = require('keyMirror');
|
||||
var merge = require('merge');
|
||||
var rebound = require('rebound');
|
||||
|
||||
var PropTypes = React.PropTypes;
|
||||
|
||||
var SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
var SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||
|
||||
var OFF_SCREEN = {style: {opacity: 0}};
|
||||
|
||||
var NAVIGATION_BAR_REF = 'navigationBar_ref';
|
||||
|
||||
var __uid = 0;
|
||||
function getuid() {
|
||||
return __uid++;
|
||||
}
|
||||
|
||||
var nextComponentUid = 0;
|
||||
|
||||
// styles moved to the top of the file so getDefaultProps can refer to it
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
defaultSceneStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
height: SCREEN_HEIGHT,
|
||||
},
|
||||
presentNavItem: {
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
},
|
||||
futureNavItem: {
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
opacity: 0,
|
||||
},
|
||||
transitioner: {
|
||||
flex: 1,
|
||||
backgroundColor: '#555555',
|
||||
overflow: 'hidden',
|
||||
}
|
||||
});
|
||||
|
||||
var JSNavigationStack = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
animationConfigRouteMapper: PropTypes.func,
|
||||
// Returns the rendered scene to display when invoked with (route, navigator)
|
||||
renderScene: PropTypes.func.isRequired,
|
||||
initialRoute: PropTypes.object,
|
||||
initialRouteStack: PropTypes.arrayOf(PropTypes.object),
|
||||
// Will emit the target route on mounting and before each nav transition,
|
||||
// overriding the handler in this.props.navigator
|
||||
onWillFocus: PropTypes.func,
|
||||
// Will emit the new route after mounting and after each nav transition,
|
||||
// overriding the handler in this.props.navigator
|
||||
onDidFocus: PropTypes.func,
|
||||
// Will be called with (ref, indexInStack) when an item ref resolves
|
||||
onItemRef: PropTypes.func,
|
||||
// Define the component to use for the nav bar, which will get navState and navigator props
|
||||
navigationBar: PropTypes.node,
|
||||
// The navigator object from a parent JSNavigationStack
|
||||
navigator: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Should the backstack back button "jump" back instead of pop? Set to true
|
||||
* if a jump forward might happen after the android back button is pressed,
|
||||
* so the scenes will remain mounted
|
||||
*/
|
||||
shouldJumpOnBackstackPop: PropTypes.bool,
|
||||
},
|
||||
|
||||
statics: {
|
||||
AnimationConfigs: JSNavigationStackAnimationConfigs,
|
||||
},
|
||||
|
||||
mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin],
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
animationConfigRouteMapper: () => JSNavigationStackAnimationConfigs.PushFromRight,
|
||||
sceneStyle: styles.defaultSceneStyle,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var routeStack = this.props.initialRouteStack || [];
|
||||
var initialRouteIndex = 0;
|
||||
if (this.props.initialRoute && routeStack.length) {
|
||||
initialRouteIndex = routeStack.indexOf(this.props.initialRoute);
|
||||
invariant(
|
||||
initialRouteIndex !== -1,
|
||||
'initialRoute is not in initialRouteStack.'
|
||||
);
|
||||
} else if (this.props.initialRoute) {
|
||||
routeStack = [this.props.initialRoute];
|
||||
} else {
|
||||
invariant(
|
||||
routeStack.length >= 1,
|
||||
'JSNavigationStack requires props.initialRoute or props.initialRouteStack.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
animationConfigStack: routeStack.map(
|
||||
(route) => this.props.animationConfigRouteMapper(route)
|
||||
),
|
||||
idStack: routeStack.map(() => getuid()),
|
||||
routeStack,
|
||||
// These are tracked to avoid rendering everything all the time.
|
||||
updatingRangeStart: initialRouteIndex,
|
||||
updatingRangeLength: initialRouteIndex + 1,
|
||||
// Either animating or gesturing.
|
||||
isAnimating: false,
|
||||
jumpToIndex: routeStack.length - 1,
|
||||
presentedIndex: initialRouteIndex,
|
||||
isResponderOnlyToBlockTouches: false,
|
||||
fromIndex: initialRouteIndex,
|
||||
toIndex: initialRouteIndex,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.memoizedNavigationOperations = {
|
||||
jumpBack: this.jumpBack,
|
||||
jumpForward: this.jumpForward,
|
||||
jumpTo: this.jumpTo,
|
||||
push: this.push,
|
||||
pop: this.pop,
|
||||
replace: this.replace,
|
||||
replaceAtIndex: this.replaceAtIndex,
|
||||
replacePrevious: this.replacePrevious,
|
||||
replacePreviousAndPop: this.replacePreviousAndPop,
|
||||
immediatelyResetRouteStack: this.immediatelyResetRouteStack,
|
||||
resetTo: this.resetTo,
|
||||
popToRoute: this.popToRoute,
|
||||
popToTop: this.popToTop,
|
||||
parentNavigator: this.props.navigator,
|
||||
// We want to bubble focused routes to the top navigation stack. If we are
|
||||
// a child, this will allow us to call this.props.navigator.on*Focus
|
||||
onWillFocus: this.props.onWillFocus,
|
||||
onDidFocus: this.props.onDidFocus,
|
||||
};
|
||||
|
||||
this.panGesture = PanResponder.create({
|
||||
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
|
||||
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderRelease: this._handlePanResponderRelease,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderTerminate: this._handlePanResponderTerminate,
|
||||
});
|
||||
this._itemRefs = {};
|
||||
this._interactionHandle = null;
|
||||
this._backstackComponentKey = 'jsnavstack' + nextComponentUid;
|
||||
nextComponentUid++;
|
||||
|
||||
Backstack.eventEmitter && this.addListenerOn(
|
||||
Backstack.eventEmitter,
|
||||
'popNavigation',
|
||||
this._onBackstackPopState);
|
||||
|
||||
this._emitWillFocus(this.state.presentedIndex);
|
||||
},
|
||||
|
||||
_configureSpring: function(animationConfig) {
|
||||
var config = this.spring.getSpringConfig();
|
||||
config.friction = animationConfig.springFriction;
|
||||
config.tension = animationConfig.springTension;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.springSystem = new rebound.SpringSystem();
|
||||
this.spring = this.springSystem.createSpring();
|
||||
this.spring.setRestSpeedThreshold(0.05);
|
||||
var animationConfig = this.state.animationConfigStack[this.state.presentedIndex];
|
||||
animationConfig && this._configureSpring(animationConfig);
|
||||
this.spring.addListener(this);
|
||||
this.onSpringUpdate();
|
||||
|
||||
// Fill up the Backstack with routes that have been provided in
|
||||
// initialRouteStack
|
||||
this._fillBackstackRange(0, this.state.routeStack.indexOf(this.props.initialRoute));
|
||||
this._emitDidFocus(this.state.presentedIndex);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
Backstack.removeComponentHistory(this._backstackComponentKey);
|
||||
},
|
||||
|
||||
_onBackstackPopState: function(componentKey, stateKey, state) {
|
||||
if (componentKey !== this._backstackComponentKey) {
|
||||
return;
|
||||
}
|
||||
if (!this._canNavigate()) {
|
||||
// A bit hacky: if we can't actually handle the pop, just push it back on the stack
|
||||
Backstack.pushNavigation(componentKey, stateKey, state);
|
||||
} else {
|
||||
if (this.props.shouldJumpOnBackstackPop) {
|
||||
this._jumpToWithoutBackstack(state.fromRoute);
|
||||
} else {
|
||||
this._popToRouteWithoutBackstack(state.fromRoute);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {RouteStack} nextRouteStack Next route stack to reinitialize. This
|
||||
* doesn't accept stack item `id`s, which implies that all existing items are
|
||||
* destroyed, and then potentially recreated according to `routeStack`. Does
|
||||
* not animate, immediately replaces and rerenders navigation bar and stack
|
||||
* items.
|
||||
*/
|
||||
immediatelyResetRouteStack: function(nextRouteStack) {
|
||||
var destIndex = nextRouteStack.length - 1;
|
||||
this.setState({
|
||||
idStack: nextRouteStack.map(getuid),
|
||||
routeStack: nextRouteStack,
|
||||
animationConfigStack: nextRouteStack.map(
|
||||
this.props.animationConfigRouteMapper
|
||||
),
|
||||
updatingRangeStart: 0,
|
||||
updatingRangeLength: nextRouteStack.length,
|
||||
presentedIndex: destIndex,
|
||||
jumpToIndex: destIndex,
|
||||
toIndex: destIndex,
|
||||
fromIndex: destIndex,
|
||||
}, () => {
|
||||
this.onSpringUpdate();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: Accept callback for spring completion.
|
||||
*/
|
||||
_requestTransitionTo: function(topOfStack) {
|
||||
if (topOfStack !== this.state.presentedIndex) {
|
||||
invariant(!this.state.isAnimating, 'Cannot navigate while transitioning');
|
||||
this.state.fromIndex = this.state.presentedIndex;
|
||||
this.state.toIndex = topOfStack;
|
||||
this.spring.setOvershootClampingEnabled(false);
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.startRecordingFps();
|
||||
}
|
||||
this._transitionToToIndexWithVelocity(
|
||||
this.state.animationConfigStack[this.state.fromIndex].defaultTransitionVelocity
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* `onSpring*` spring delegate. Wired up via `spring.addListener(this)`
|
||||
*/
|
||||
onSpringEndStateChange: function() {
|
||||
if (!this._interactionHandle) {
|
||||
this._interactionHandle = this.createInteractionHandle();
|
||||
}
|
||||
},
|
||||
|
||||
onSpringUpdate: function() {
|
||||
this._transitionBetween(
|
||||
this.state.fromIndex,
|
||||
this.state.toIndex,
|
||||
this.spring.getCurrentValue()
|
||||
);
|
||||
},
|
||||
|
||||
onSpringAtRest: function() {
|
||||
this.state.isAnimating = false;
|
||||
this._completeTransition();
|
||||
this.spring.setCurrentValue(0).setAtRest();
|
||||
if (this._interactionHandle) {
|
||||
this.clearInteractionHandle(this._interactionHandle);
|
||||
this._interactionHandle = null;
|
||||
}
|
||||
},
|
||||
|
||||
_completeTransition: function() {
|
||||
if (this.spring.getCurrentValue() === 1) {
|
||||
var presentedIndex = this.state.toIndex;
|
||||
this.state.fromIndex = presentedIndex;
|
||||
this.state.presentedIndex = presentedIndex;
|
||||
this._emitDidFocus(presentedIndex);
|
||||
this._removePoppedRoutes();
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.stopRecordingFps();
|
||||
}
|
||||
this._hideOtherScenes(presentedIndex);
|
||||
}
|
||||
},
|
||||
|
||||
_transitionToToIndexWithVelocity: function(v) {
|
||||
this._configureSpring(
|
||||
// For visual consistency, the from index is always used to configure the spring
|
||||
this.state.animationConfigStack[this.state.fromIndex]
|
||||
);
|
||||
this.state.isAnimating = true;
|
||||
this.spring.setVelocity(v);
|
||||
this.spring.setEndValue(1);
|
||||
this._emitWillFocus(this.state.toIndex);
|
||||
},
|
||||
|
||||
_transitionToFromIndexWithVelocity: function(v) {
|
||||
this._configureSpring(
|
||||
this.state.animationConfigStack[this.state.fromIndex]
|
||||
);
|
||||
this.state.isAnimating = true;
|
||||
this.spring.setVelocity(v);
|
||||
this.spring.setEndValue(0);
|
||||
},
|
||||
|
||||
_emitDidFocus: function(index) {
|
||||
var route = this.state.routeStack[index];
|
||||
if (this.props.onDidFocus) {
|
||||
this.props.onDidFocus(route);
|
||||
} else if (this.props.navigator && this.props.navigator.onDidFocus) {
|
||||
this.props.navigator.onDidFocus(route);
|
||||
}
|
||||
},
|
||||
|
||||
_emitWillFocus: function(index) {
|
||||
var route = this.state.routeStack[index];
|
||||
if (this.props.onWillFocus) {
|
||||
this.props.onWillFocus(route);
|
||||
} else if (this.props.navigator && this.props.navigator.onWillFocus) {
|
||||
this.props.navigator.onWillFocus(route);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Does not delete the scenes - merely hides them.
|
||||
*/
|
||||
_hideOtherScenes: function(activeIndex) {
|
||||
for (var i = 0; i < this.state.routeStack.length; i++) {
|
||||
if (i === activeIndex) {
|
||||
continue;
|
||||
}
|
||||
var sceneRef = 'scene_' + i;
|
||||
this.refs[sceneRef] &&
|
||||
this.refs['scene_' + i].setNativeProps(OFF_SCREEN);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Becomes the responder on touch start (capture) while animating so that it
|
||||
* blocks all touch interactions inside of it. However, this responder lock
|
||||
* means nothing more than that. We record if the sole reason for being
|
||||
* responder is to block interactions (`isResponderOnlyToBlockTouches`).
|
||||
*/
|
||||
_handleStartShouldSetPanResponderCapture: function(e, gestureState) {
|
||||
return this.state.isAnimating;
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponder: function(e, gestureState) {
|
||||
var currentRoute = this.state.routeStack[this.state.presentedIndex];
|
||||
var animationConfig = this.state.animationConfigStack[this.state.presentedIndex];
|
||||
if (!animationConfig.enableGestures) {
|
||||
return false;
|
||||
}
|
||||
var currentLoc = animationConfig.isVertical ? gestureState.moveY : gestureState.moveX;
|
||||
var travelDist = animationConfig.isVertical ? gestureState.dy : gestureState.dx;
|
||||
var oppositeAxisTravelDist =
|
||||
animationConfig.isVertical ? gestureState.dx : gestureState.dy;
|
||||
var moveStartedInRegion = currentLoc < animationConfig.edgeHitWidth;
|
||||
var moveTravelledFarEnough =
|
||||
travelDist >= animationConfig.gestureDetectMovement &&
|
||||
travelDist > oppositeAxisTravelDist * animationConfig.directionRatio;
|
||||
return (
|
||||
!this.state.isResponderOnlyToBlockTouches &&
|
||||
moveStartedInRegion &&
|
||||
!this.state.isAnimating &&
|
||||
this.state.presentedIndex > 0 &&
|
||||
moveTravelledFarEnough
|
||||
);
|
||||
},
|
||||
|
||||
_handlePanResponderGrant: function(e, gestureState) {
|
||||
this.state.isResponderOnlyToBlockTouches = this.state.isAnimating;
|
||||
if (!this.state.isAnimating) {
|
||||
this.state.fromIndex = this.state.presentedIndex;
|
||||
this.state.toIndex = this.state.presentedIndex - 1;
|
||||
}
|
||||
},
|
||||
|
||||
_handlePanResponderRelease: function(e, gestureState) {
|
||||
if (this.state.isResponderOnlyToBlockTouches) {
|
||||
this.state.isResponderOnlyToBlockTouches = false;
|
||||
return;
|
||||
}
|
||||
var animationConfig = this.state.animationConfigStack[this.state.presentedIndex];
|
||||
var velocity = animationConfig.isVertical ? gestureState.vy : gestureState.vx;
|
||||
// It's not the real location. There is no *real* location - that's the
|
||||
// point of the pan gesture.
|
||||
var pseudoLocation = animationConfig.isVertical ?
|
||||
gestureState.y0 + gestureState.dy :
|
||||
gestureState.x0 + gestureState.dx;
|
||||
var still = Math.abs(velocity) < animationConfig.notMoving;
|
||||
if (this.spring.getCurrentValue() === 0) {
|
||||
this.spring.setCurrentValue(0).setAtRest();
|
||||
this._completeTransition();
|
||||
return;
|
||||
}
|
||||
var transitionVelocity =
|
||||
still && animationConfig.pastPointOfNoReturn(pseudoLocation) ? animationConfig.snapVelocity :
|
||||
still && !animationConfig.pastPointOfNoReturn(pseudoLocation) ? -animationConfig.snapVelocity :
|
||||
clamp(-10, velocity, 10); // What are Rebound UoM?
|
||||
|
||||
this.spring.setOvershootClampingEnabled(true);
|
||||
if (transitionVelocity < 0) {
|
||||
this._transitionToFromIndexWithVelocity(transitionVelocity);
|
||||
} else {
|
||||
this._manuallyPopBackstack(1);
|
||||
this._transitionToToIndexWithVelocity(transitionVelocity);
|
||||
}
|
||||
},
|
||||
|
||||
_handlePanResponderTerminate: function(e, gestureState) {
|
||||
this.state.isResponderOnlyToBlockTouches = false;
|
||||
this._transitionToFromIndexWithVelocity(0);
|
||||
},
|
||||
|
||||
_handlePanResponderMove: function(e, gestureState) {
|
||||
if (!this.state.isResponderOnlyToBlockTouches) {
|
||||
var animationConfig = this.state.animationConfigStack[this.state.presentedIndex];
|
||||
var distance = animationConfig.isVertical ? gestureState.dy : gestureState.dx;
|
||||
var gestureDetectMovement = animationConfig.gestureDetectMovement;
|
||||
var nextProgress = (distance - gestureDetectMovement) /
|
||||
(animationConfig.screenDimension - gestureDetectMovement);
|
||||
this.spring.setCurrentValue(clamp(0, nextProgress, 1));
|
||||
}
|
||||
},
|
||||
|
||||
_transitionSceneStyle: function(fromIndex, toIndex, progress, index) {
|
||||
var viewAtIndex = this.refs['scene_' + index];
|
||||
if (viewAtIndex === null || viewAtIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
// Use toIndex animation when we move forwards. Use fromIndex when we move back
|
||||
var animationIndex = this.state.presentedIndex < toIndex ? toIndex : fromIndex;
|
||||
var animationConfig = this.state.animationConfigStack[animationIndex];
|
||||
var styleToUse = {};
|
||||
var useFn = index < fromIndex || index < toIndex ?
|
||||
animationConfig.interpolators.out :
|
||||
animationConfig.interpolators.into;
|
||||
var directionAdjustedProgress = fromIndex < toIndex ? progress : 1 - progress;
|
||||
var didChange = useFn(styleToUse, directionAdjustedProgress);
|
||||
if (didChange) {
|
||||
viewAtIndex.setNativeProps({style: styleToUse});
|
||||
}
|
||||
},
|
||||
|
||||
_transitionBetween: function(fromIndex, toIndex, progress) {
|
||||
this._transitionSceneStyle(fromIndex, toIndex, progress, fromIndex);
|
||||
this._transitionSceneStyle(fromIndex, toIndex, progress, toIndex);
|
||||
var navBar = this.refs[NAVIGATION_BAR_REF];
|
||||
if (navBar && navBar.updateProgress) {
|
||||
navBar.updateProgress(progress, fromIndex, toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
_handleResponderTerminationRequest: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
_resetUpdatingRange: function() {
|
||||
this.state.updatingRangeStart = 0;
|
||||
this.state.updatingRangeLength = this.state.routeStack.length;
|
||||
},
|
||||
|
||||
_canNavigate: function() {
|
||||
return !this.state.isAnimating;
|
||||
},
|
||||
|
||||
_jumpNWithoutBackstack: function(n) {
|
||||
var destIndex = this._getDestIndexWithinBounds(n);
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
var requestTransitionAndResetUpdatingRange = () => {
|
||||
this._requestTransitionTo(destIndex);
|
||||
this._resetUpdatingRange();
|
||||
};
|
||||
this.setState({
|
||||
updatingRangeStart: destIndex,
|
||||
updatingRangeLength: 1,
|
||||
toIndex: destIndex,
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
_fillBackstackRange: function(start, end) {
|
||||
invariant(
|
||||
start <= end,
|
||||
'Can only fill the backstack forward. Provide end index greater than start'
|
||||
);
|
||||
for (var i = 0; i < (end - start); i++) {
|
||||
var fromIndex = start + i;
|
||||
var toIndex = start + i + 1;
|
||||
Backstack.pushNavigation(
|
||||
this._backstackComponentKey,
|
||||
toIndex,
|
||||
{
|
||||
fromRoute: this.state.routeStack[fromIndex],
|
||||
toRoute: this.state.routeStack[toIndex],
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_getDestIndexWithinBounds: function(n) {
|
||||
var currentIndex = this.state.presentedIndex;
|
||||
var destIndex = currentIndex + n;
|
||||
invariant(
|
||||
destIndex >= 0,
|
||||
'Cannot jump before the first route.'
|
||||
);
|
||||
var maxIndex = this.state.routeStack.length - 1;
|
||||
invariant(
|
||||
maxIndex >= destIndex,
|
||||
'Cannot jump past the last route.'
|
||||
);
|
||||
return destIndex;
|
||||
},
|
||||
|
||||
_jumpN: function(n) {
|
||||
var currentIndex = this.state.presentedIndex;
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
if (n > 0) {
|
||||
this._fillBackstackRange(currentIndex, currentIndex + n);
|
||||
} else {
|
||||
var landingBeforeIndex = currentIndex + n + 1;
|
||||
Backstack.resetToBefore(
|
||||
this._backstackComponentKey,
|
||||
landingBeforeIndex
|
||||
);
|
||||
}
|
||||
this._jumpNWithoutBackstack(n);
|
||||
},
|
||||
|
||||
jumpTo: function(route) {
|
||||
var destIndex = this.state.routeStack.indexOf(route);
|
||||
invariant(
|
||||
destIndex !== -1,
|
||||
'Cannot jump to route that is not in the route stack'
|
||||
);
|
||||
this._jumpN(destIndex - this.state.presentedIndex);
|
||||
},
|
||||
|
||||
_jumpToWithoutBackstack: function(route) {
|
||||
var destIndex = this.state.routeStack.indexOf(route);
|
||||
invariant(
|
||||
destIndex !== -1,
|
||||
'Cannot jump to route that is not in the route stack'
|
||||
);
|
||||
this._jumpNWithoutBackstack(destIndex - this.state.presentedIndex);
|
||||
},
|
||||
|
||||
jumpForward: function() {
|
||||
this._jumpN(1);
|
||||
},
|
||||
|
||||
jumpBack: function() {
|
||||
this._jumpN(-1);
|
||||
},
|
||||
|
||||
push: function(route) {
|
||||
invariant(!!route, 'Must supply route to push');
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
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.animationConfigStack.slice(0, activeLength);
|
||||
var nextStack = activeStack.concat([route]);
|
||||
var nextIDStack = activeIDStack.concat([getuid()]);
|
||||
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
|
||||
this.props.animationConfigRouteMapper(route),
|
||||
]);
|
||||
var requestTransitionAndResetUpdatingRange = () => {
|
||||
this._requestTransitionTo(nextStack.length - 1);
|
||||
this._resetUpdatingRange();
|
||||
};
|
||||
var navigationState = {
|
||||
toRoute: route,
|
||||
fromRoute: this.state.routeStack[this.state.routeStack.length - 1],
|
||||
};
|
||||
Backstack.pushNavigation(
|
||||
this._backstackComponentKey,
|
||||
this.state.routeStack.length,
|
||||
navigationState);
|
||||
|
||||
this.setState({
|
||||
idStack: nextIDStack,
|
||||
routeStack: nextStack,
|
||||
animationConfigStack: nextAnimationConfigStack,
|
||||
jumpToIndex: nextStack.length - 1,
|
||||
updatingRangeStart: nextStack.length - 1,
|
||||
updatingRangeLength: 1,
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
_manuallyPopBackstack: function(n) {
|
||||
Backstack.resetToBefore(this._backstackComponentKey, this.state.routeStack.length - n);
|
||||
},
|
||||
|
||||
/**
|
||||
* Like popN, but doesn't also update the Backstack.
|
||||
*/
|
||||
_popNWithoutBackstack: function(n) {
|
||||
if (n === 0 || !this._canNavigate()) {
|
||||
return;
|
||||
}
|
||||
invariant(
|
||||
this.state.presentedIndex - n >= 0,
|
||||
'Cannot pop below zero'
|
||||
);
|
||||
this.state.jumpToIndex = this.state.presentedIndex - n;
|
||||
this._requestTransitionTo(
|
||||
this.state.presentedIndex - n
|
||||
);
|
||||
},
|
||||
|
||||
popN: function(n) {
|
||||
if (n === 0 || !this._canNavigate()) {
|
||||
return;
|
||||
}
|
||||
this._popNWithoutBackstack(n);
|
||||
this._manuallyPopBackstack(n);
|
||||
},
|
||||
|
||||
pop: function() {
|
||||
if (this.props.navigator && this.state.routeStack.length === 1) {
|
||||
return this.props.navigator.pop();
|
||||
}
|
||||
this.popN(1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace a route in the navigation stack.
|
||||
*
|
||||
* `index` specifies the route in the stack that should be replaced.
|
||||
* If it's negative, it counts from the back.
|
||||
*/
|
||||
replaceAtIndex: function(route, index) {
|
||||
invariant(!!route, 'Must supply route to replace');
|
||||
if (index < 0) {
|
||||
index += this.state.routeStack.length;
|
||||
}
|
||||
|
||||
if (this.state.routeStack.length <= index) {
|
||||
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.animationConfigStack.slice();
|
||||
nextIDStack[index] = getuid();
|
||||
nextRouteStack[index] = route;
|
||||
nextAnimationModeStack[index] = this.props.animationConfigRouteMapper(route);
|
||||
|
||||
this.setState({
|
||||
idStack: nextIDStack,
|
||||
routeStack: nextRouteStack,
|
||||
animationConfigStack: nextAnimationModeStack,
|
||||
updatingRangeStart: index,
|
||||
updatingRangeLength: 1,
|
||||
}, () => {
|
||||
this._resetUpdatingRange();
|
||||
if (index === this.state.presentedIndex) {
|
||||
this._emitWillFocus(this.state.presentedIndex);
|
||||
this._emitDidFocus(this.state.presentedIndex);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces the current scene in the stack.
|
||||
*/
|
||||
replace: function(route) {
|
||||
this.replaceAtIndex(route, this.state.presentedIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace the current route's parent.
|
||||
*/
|
||||
replacePrevious: function(route) {
|
||||
this.replaceAtIndex(route, this.state.presentedIndex - 1);
|
||||
},
|
||||
|
||||
popToTop: function() {
|
||||
this.popToRoute(this.state.routeStack[0]);
|
||||
},
|
||||
|
||||
_getNumToPopForRoute: function(route) {
|
||||
var indexOfRoute = this.state.routeStack.indexOf(route);
|
||||
invariant(
|
||||
indexOfRoute !== -1,
|
||||
'Calling pop to route for a route that doesn\'t exist!'
|
||||
);
|
||||
return this.state.routeStack.length - indexOfRoute - 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Like popToRoute, but doesn't update the Backstack, presumably because it's already up to date.
|
||||
*/
|
||||
_popToRouteWithoutBackstack: function(route) {
|
||||
var numToPop = this._getNumToPopForRoute(route);
|
||||
this._popNWithoutBackstack(numToPop);
|
||||
},
|
||||
|
||||
popToRoute: function(route) {
|
||||
var numToPop = this._getNumToPopForRoute(route);
|
||||
this.popN(numToPop);
|
||||
},
|
||||
|
||||
replacePreviousAndPop: function(route) {
|
||||
if (this.state.routeStack.length < 2 || !this._canNavigate()) {
|
||||
return;
|
||||
}
|
||||
this.replacePrevious(route);
|
||||
this.pop();
|
||||
},
|
||||
|
||||
resetTo: function(route) {
|
||||
invariant(!!route, 'Must supply route to push');
|
||||
if (this._canNavigate()) {
|
||||
this.replaceAtIndex(route, 0);
|
||||
this.popToRoute(route);
|
||||
}
|
||||
},
|
||||
|
||||
_onItemRef: function(itemId, ref) {
|
||||
this._itemRefs[itemId] = ref;
|
||||
var itemIndex = this.state.idStack.indexOf(itemId);
|
||||
if (itemIndex === -1) {
|
||||
return;
|
||||
}
|
||||
this.props.onItemRef && this.props.onItemRef(ref, itemIndex);
|
||||
},
|
||||
|
||||
_removePoppedRoutes: function() {
|
||||
var newStackLength = this.state.jumpToIndex + 1;
|
||||
// Remove any unneeded rendered routes.
|
||||
if (newStackLength < this.state.routeStack.length) {
|
||||
var updatingRangeStart = newStackLength; // One past the top
|
||||
var updatingRangeLength = this.state.routeStack.length - newStackLength + 1;
|
||||
this.state.idStack.slice(newStackLength).map((removingId) => {
|
||||
this._itemRefs[removingId] = null;
|
||||
});
|
||||
this.setState({
|
||||
updatingRangeStart: updatingRangeStart,
|
||||
updatingRangeLength: updatingRangeLength,
|
||||
animationConfigStack: this.state.animationConfigStack.slice(0, newStackLength),
|
||||
idStack: this.state.idStack.slice(0, newStackLength),
|
||||
routeStack: this.state.routeStack.slice(0, newStackLength),
|
||||
}, this._resetUpdatingRange);
|
||||
}
|
||||
},
|
||||
|
||||
_routeToOptimizedStackItem: function(route, i) {
|
||||
var shouldUpdateChild =
|
||||
this.state.updatingRangeLength !== 0 &&
|
||||
i >= this.state.updatingRangeStart &&
|
||||
i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
|
||||
var child = this.props.renderScene(
|
||||
route,
|
||||
this.memoizedNavigationOperations,
|
||||
this._onItemRef.bind(null, this.state.idStack[i])
|
||||
);
|
||||
|
||||
var initialSceneStyle =
|
||||
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
|
||||
return (
|
||||
<StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}>
|
||||
<View
|
||||
key={this.state.idStack[i]}
|
||||
ref={'scene_' + i}
|
||||
style={[initialSceneStyle, this.props.sceneStyle]}>
|
||||
{child}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
);
|
||||
},
|
||||
|
||||
renderNavigationStackItems: function() {
|
||||
var shouldRecurseToNavigator = this.state.updatingRangeLength !== 0;
|
||||
// If not recursing update to navigator at all, may as well avoid
|
||||
// computation of navigator children.
|
||||
var items = shouldRecurseToNavigator ?
|
||||
this.state.routeStack.map(this._routeToOptimizedStackItem) : null;
|
||||
|
||||
return (
|
||||
<StaticContainer shouldUpdate={shouldRecurseToNavigator}>
|
||||
<View
|
||||
style={styles.transitioner}
|
||||
{...this.panGesture.panHandlers}
|
||||
onResponderTerminationRequest={this._handleResponderTerminationRequest}>
|
||||
{items}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
);
|
||||
},
|
||||
|
||||
renderNavigationStackBar: function() {
|
||||
var NavigationBarClass = this.props.NavigationBarClass;
|
||||
if (!this.props.navigationBar) {
|
||||
return null;
|
||||
}
|
||||
return React.cloneElement(this.props.navigationBar, {
|
||||
ref: NAVIGATION_BAR_REF,
|
||||
navigator: this.memoizedNavigationOperations,
|
||||
navState: this.state,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<View style={[styles.container, this.props.style]}>
|
||||
{this.renderNavigationStackItems()}
|
||||
{this.renderNavigationStackBar()}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = JSNavigationStack;
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule BreadcrumbNavigationBar
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BreadcrumbNavigationBarStyles = require('BreadcrumbNavigationBarStyles');
|
||||
var PixelRatio = require('PixelRatio');
|
||||
var React = require('React');
|
||||
var NavigationBarStyles = require('NavigationBarStyles');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var Interpolators = BreadcrumbNavigationBarStyles.Interpolators;
|
||||
var PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* Reusable props objects.
|
||||
*/
|
||||
var CRUMB_PROPS = Interpolators.map(() => {return {style: {}};});
|
||||
var ICON_PROPS = Interpolators.map(() => {return {style: {}};});
|
||||
var SEPARATOR_PROPS = Interpolators.map(() => {return {style: {}};});
|
||||
var TITLE_PROPS = Interpolators.map(() => {return {style: {}};});
|
||||
var RIGHT_BUTTON_PROPS = Interpolators.map(() => {return {style: {}};});
|
||||
|
||||
|
||||
/**
|
||||
* TODO: Rename `observedTopOfStack` to `presentedIndex` in `NavigationStack`.
|
||||
*/
|
||||
var navStatePresentedIndex = function(navState) {
|
||||
if (navState.presentedIndex !== undefined) {
|
||||
return navState.presentedIndex;
|
||||
} else {
|
||||
return navState.observedTopOfStack;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The first route is initially rendered using a different style than all
|
||||
* future routes.
|
||||
*
|
||||
* @param {number} index Index of breadcrumb.
|
||||
* @return {object} Style config for initial rendering of index.
|
||||
*/
|
||||
var initStyle = function(index, presentedIndex) {
|
||||
return index === presentedIndex ? BreadcrumbNavigationBarStyles.Center[index] :
|
||||
index < presentedIndex ? BreadcrumbNavigationBarStyles.Left[index] :
|
||||
BreadcrumbNavigationBarStyles.Right[index];
|
||||
};
|
||||
|
||||
var BreadcrumbNavigationBar = React.createClass({
|
||||
propTypes: {
|
||||
navigator: PropTypes.shape({
|
||||
push: PropTypes.func,
|
||||
pop: PropTypes.func,
|
||||
replace: PropTypes.func,
|
||||
popToRoute: PropTypes.func,
|
||||
popToTop: PropTypes.func,
|
||||
}),
|
||||
navigationBarRouteMapper: PropTypes.shape({
|
||||
rightContentForRoute: PropTypes.func,
|
||||
titleContentForRoute: PropTypes.func,
|
||||
iconForRoute: PropTypes.func,
|
||||
}),
|
||||
navigationBarStyles: PropTypes.number,
|
||||
},
|
||||
|
||||
_updateIndexProgress: function(progress, index, fromIndex, toIndex) {
|
||||
var amount = toIndex > fromIndex ? progress : (1 - progress);
|
||||
var oldDistToCenter = index - fromIndex;
|
||||
var newDistToCenter = index - toIndex;
|
||||
var interpolate;
|
||||
if (oldDistToCenter > 0 && newDistToCenter === 0 ||
|
||||
newDistToCenter > 0 && oldDistToCenter === 0) {
|
||||
interpolate = Interpolators[index].RightToCenter;
|
||||
} else if (oldDistToCenter < 0 && newDistToCenter === 0 ||
|
||||
newDistToCenter < 0 && oldDistToCenter === 0) {
|
||||
interpolate = Interpolators[index].CenterToLeft;
|
||||
} else if (oldDistToCenter === newDistToCenter) {
|
||||
interpolate = Interpolators[index].RightToCenter;
|
||||
} else {
|
||||
interpolate = Interpolators[index].RightToLeft;
|
||||
}
|
||||
|
||||
if (interpolate.Crumb(CRUMB_PROPS[index].style, amount)) {
|
||||
this.refs['crumb_' + index].setNativeProps(CRUMB_PROPS[index]);
|
||||
}
|
||||
if (interpolate.Icon(ICON_PROPS[index].style, amount)) {
|
||||
this.refs['icon_' + index].setNativeProps(ICON_PROPS[index]);
|
||||
}
|
||||
if (interpolate.Separator(SEPARATOR_PROPS[index].style, amount)) {
|
||||
this.refs['separator_' + index].setNativeProps(SEPARATOR_PROPS[index]);
|
||||
}
|
||||
if (interpolate.Title(TITLE_PROPS[index].style, amount)) {
|
||||
this.refs['title_' + index].setNativeProps(TITLE_PROPS[index]);
|
||||
}
|
||||
var right = this.refs['right_' + index];
|
||||
if (right &&
|
||||
interpolate.RightItem(RIGHT_BUTTON_PROPS[index].style, amount)) {
|
||||
right.setNativeProps(RIGHT_BUTTON_PROPS[index]);
|
||||
}
|
||||
},
|
||||
|
||||
updateProgress: function(progress, fromIndex, toIndex) {
|
||||
var max = Math.max(fromIndex, toIndex);
|
||||
var min = Math.min(fromIndex, toIndex);
|
||||
for (var index = min; index <= max; index++) {
|
||||
this._updateIndexProgress(progress, index, fromIndex, toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
return (
|
||||
<View style={[styles.breadCrumbContainer, this.props.navigationBarStyles]}>
|
||||
{titles}
|
||||
{icons}
|
||||
{buttons}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_renderOrReturnBreadcrumb: function(route, index) {
|
||||
var uid = this.props.navState.idStack[index];
|
||||
var navBarRouteMapper = this.props.navigationBarRouteMapper;
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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>
|
||||
</View>
|
||||
</StaticContainer>
|
||||
);
|
||||
},
|
||||
|
||||
_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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
var navBarRouteMapper = this.props.navigationBarRouteMapper;
|
||||
var titleContent = navBarRouteMapper.titleContentForRoute(
|
||||
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>
|
||||
);
|
||||
},
|
||||
|
||||
_renderOrReturnRightButton: function(route, index) {
|
||||
var navState = this.props.navState;
|
||||
var navBarRouteMapper = this.props.navigationBarRouteMapper;
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
var rightContent = navBarRouteMapper.rightContentForRoute(
|
||||
navState.routeStack[index],
|
||||
this.props.navigator
|
||||
);
|
||||
if (!rightContent) {
|
||||
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 styles = StyleSheet.create({
|
||||
breadCrumbContainer: {
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
height: NavigationBarStyles.General.TotalNavHeight,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: NavigationBarStyles.General.ScreenWidth,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = BreadcrumbNavigationBar;
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule BreadcrumbNavigationBarStyles
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NavigationBarStyles = require('NavigationBarStyles');
|
||||
|
||||
var buildStyleInterpolator = require('buildStyleInterpolator');
|
||||
var merge = require('merge');
|
||||
|
||||
var SCREEN_WIDTH = NavigationBarStyles.General.ScreenWidth;
|
||||
var STATUS_BAR_HEIGHT = NavigationBarStyles.General.StatusBarHeight;
|
||||
var NAV_BAR_HEIGHT = NavigationBarStyles.General.NavBarHeight;
|
||||
|
||||
var SPACING = 4;
|
||||
var ICON_WIDTH = 40;
|
||||
var SEPARATOR_WIDTH = 9;
|
||||
var CRUMB_WIDTH = ICON_WIDTH + SEPARATOR_WIDTH;
|
||||
var RIGHT_BUTTON_WIDTH = 58;
|
||||
|
||||
var OPACITY_RATIO = 100;
|
||||
var ICON_INACTIVE_OPACITY = 0.6;
|
||||
var MAX_BREADCRUMBS = 10;
|
||||
|
||||
var CRUMB_BASE = {
|
||||
position: 'absolute',
|
||||
flexDirection: 'row',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
width: CRUMB_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
};
|
||||
|
||||
var ICON_BASE = {
|
||||
width: ICON_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
};
|
||||
|
||||
var SEPARATOR_BASE = {
|
||||
width: SEPARATOR_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
};
|
||||
|
||||
var TITLE_BASE = {
|
||||
position: 'absolute',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
};
|
||||
|
||||
// For first title styles, make sure first title is centered
|
||||
var FIRST_TITLE_BASE = merge(TITLE_BASE, {
|
||||
left: 0,
|
||||
alignItems: 'center',
|
||||
width: SCREEN_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
});
|
||||
|
||||
var RIGHT_BUTTON_BASE = {
|
||||
position: 'absolute',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
left: SCREEN_WIDTH - SPACING - RIGHT_BUTTON_WIDTH,
|
||||
overflow: 'hidden',
|
||||
opacity: 1,
|
||||
width: RIGHT_BUTTON_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
};
|
||||
|
||||
/**
|
||||
* Precompute crumb styles so that they don't need to be recomputed on every
|
||||
* interaction.
|
||||
*/
|
||||
var LEFT = [];
|
||||
var CENTER = [];
|
||||
var RIGHT = [];
|
||||
for (var i = 0; i < MAX_BREADCRUMBS; i++) {
|
||||
var crumbLeft = CRUMB_WIDTH * i + SPACING;
|
||||
LEFT[i] = {
|
||||
Crumb: merge(CRUMB_BASE, { left: crumbLeft }),
|
||||
Icon: merge(ICON_BASE, { opacity: ICON_INACTIVE_OPACITY }),
|
||||
Separator: merge(SEPARATOR_BASE, { opacity: 1 }),
|
||||
Title: merge(TITLE_BASE, { left: crumbLeft, opacity: 0 }),
|
||||
RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 0 }),
|
||||
};
|
||||
CENTER[i] = {
|
||||
Crumb: merge(CRUMB_BASE, { left: crumbLeft }),
|
||||
Icon: merge(ICON_BASE, { opacity: 1 }),
|
||||
Separator: merge(SEPARATOR_BASE, { opacity: 0 }),
|
||||
Title: merge(TITLE_BASE, {
|
||||
left: crumbLeft + ICON_WIDTH,
|
||||
opacity: 1,
|
||||
}),
|
||||
RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 1 }),
|
||||
};
|
||||
var crumbRight = SCREEN_WIDTH - 100;
|
||||
RIGHT[i] = {
|
||||
Crumb: merge(CRUMB_BASE, { left: crumbRight}),
|
||||
Icon: merge(ICON_BASE, { opacity: 0 }),
|
||||
Separator: merge(SEPARATOR_BASE, { opacity: 0 }),
|
||||
Title: merge(TITLE_BASE, {
|
||||
left: crumbRight + ICON_WIDTH,
|
||||
opacity: 0,
|
||||
}),
|
||||
RightItem: merge(RIGHT_BUTTON_BASE, { opacity: 0 }),
|
||||
};
|
||||
}
|
||||
|
||||
// Special case the CENTER state of the first scene.
|
||||
CENTER[0] = {
|
||||
Crumb: merge(CRUMB_BASE, {left: SCREEN_WIDTH / 4}),
|
||||
Icon: merge(ICON_BASE, {opacity: 0}),
|
||||
Separator: merge(SEPARATOR_BASE, {opacity: 0}),
|
||||
Title: merge(FIRST_TITLE_BASE, {opacity: 1}),
|
||||
RightItem: CENTER[0].RightItem,
|
||||
};
|
||||
LEFT[0].Title = merge(FIRST_TITLE_BASE, {left: - SCREEN_WIDTH / 4, opacity: 0});
|
||||
RIGHT[0].Title = merge(FIRST_TITLE_BASE, {opacity: 0});
|
||||
|
||||
|
||||
var buildIndexSceneInterpolator = function(startStyles, endStyles) {
|
||||
return {
|
||||
Crumb: buildStyleInterpolator({
|
||||
left: {
|
||||
type: 'linear',
|
||||
from: startStyles.Crumb.left,
|
||||
to: endStyles.Crumb.left,
|
||||
min: 0,
|
||||
max: 1,
|
||||
extrapolate: true,
|
||||
},
|
||||
}),
|
||||
Icon: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.Icon.opacity,
|
||||
to: endStyles.Icon.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
}),
|
||||
Separator: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.Separator.opacity,
|
||||
to: endStyles.Separator.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
}),
|
||||
Title: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.Title.opacity,
|
||||
to: endStyles.Title.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
left: {
|
||||
type: 'linear',
|
||||
from: startStyles.Title.left,
|
||||
to: endStyles.Title.left,
|
||||
min: 0,
|
||||
max: 1,
|
||||
extrapolate: true,
|
||||
},
|
||||
}),
|
||||
RightItem: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.RightItem.opacity,
|
||||
to: endStyles.RightItem.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
round: OPACITY_RATIO,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
var Interpolators = CENTER.map(function(_, ii) {
|
||||
return {
|
||||
// Animating *into* the center stage from the right
|
||||
RightToCenter: buildIndexSceneInterpolator(RIGHT[ii], CENTER[ii]),
|
||||
// Animating out of the center stage, to the left
|
||||
CenterToLeft: buildIndexSceneInterpolator(CENTER[ii], LEFT[ii]),
|
||||
// Both stages (animating *past* the center stage)
|
||||
RightToLeft: buildIndexSceneInterpolator(RIGHT[ii], LEFT[ii]),
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Contains constants that are used in constructing both `StyleSheet`s and
|
||||
* inline styles during transitions.
|
||||
*/
|
||||
module.exports = {
|
||||
Interpolators,
|
||||
Left: LEFT,
|
||||
Center: CENTER,
|
||||
Right: RIGHT,
|
||||
IconWidth: ICON_WIDTH,
|
||||
IconHeight: NAV_BAR_HEIGHT,
|
||||
SeparatorWidth: SEPARATOR_WIDTH,
|
||||
SeparatorHeight: NAV_BAR_HEIGHT,
|
||||
};
|
||||
164
Libraries/CustomComponents/JSNavigationStack/NavigationBar.js
Normal file
164
Libraries/CustomComponents/JSNavigationStack/NavigationBar.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @providesModule NavigationBar
|
||||
* @typechecks
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
var NavigationBarStyles = require('NavigationBarStyles');
|
||||
var StaticContainer = require('StaticContainer.react');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
var Text = require('Text');
|
||||
|
||||
var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton'];
|
||||
|
||||
/**
|
||||
* TODO (janzer): Rename `observedTopOfStack` to `presentedIndex` in `NavigationStack`.
|
||||
*/
|
||||
var navStatePresentedIndex = function(navState) {
|
||||
if (navState.presentedIndex !== undefined) {
|
||||
return navState.presentedIndex;
|
||||
} else {
|
||||
return navState.observedTopOfStack;
|
||||
}
|
||||
};
|
||||
|
||||
var NavigationBar = React.createClass({
|
||||
|
||||
_getReusableProps: function(
|
||||
/*string*/componentName,
|
||||
/*number*/index
|
||||
) /*object*/ {
|
||||
if (!this._reusableProps) {
|
||||
this._reusableProps = {};
|
||||
};
|
||||
var propStack = this._reusableProps[componentName];
|
||||
if (!propStack) {
|
||||
propStack = this._reusableProps[componentName] = [];
|
||||
}
|
||||
var props = propStack[index];
|
||||
if (!props) {
|
||||
props = propStack[index] = {style:{}};
|
||||
}
|
||||
return props;
|
||||
},
|
||||
|
||||
_updateIndexProgress: function(
|
||||
/*number*/progress,
|
||||
/*number*/index,
|
||||
/*number*/fromIndex,
|
||||
/*number*/toIndex
|
||||
) {
|
||||
var amount = toIndex > fromIndex ? progress : (1 - progress);
|
||||
var oldDistToCenter = index - fromIndex;
|
||||
var newDistToCenter = index - toIndex;
|
||||
var interpolate;
|
||||
if (oldDistToCenter > 0 && newDistToCenter === 0 ||
|
||||
newDistToCenter > 0 && oldDistToCenter === 0) {
|
||||
interpolate = NavigationBarStyles.Interpolators.RightToCenter;
|
||||
} else if (oldDistToCenter < 0 && newDistToCenter === 0 ||
|
||||
newDistToCenter < 0 && oldDistToCenter === 0) {
|
||||
interpolate = NavigationBarStyles.Interpolators.CenterToLeft;
|
||||
} else if (oldDistToCenter === newDistToCenter) {
|
||||
interpolate = NavigationBarStyles.Interpolators.RightToCenter;
|
||||
} else {
|
||||
interpolate = NavigationBarStyles.Interpolators.RightToLeft;
|
||||
}
|
||||
|
||||
COMPONENT_NAMES.forEach(function (componentName) {
|
||||
var component = this.refs[componentName + index];
|
||||
var props = this._getReusableProps(componentName, index);
|
||||
if (component && interpolate[componentName](props.style, amount)) {
|
||||
component.setNativeProps(props);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
updateProgress: function(
|
||||
/*number*/progress,
|
||||
/*number*/fromIndex,
|
||||
/*number*/toIndex
|
||||
) {
|
||||
var max = Math.max(fromIndex, toIndex);
|
||||
var min = Math.min(fromIndex, toIndex);
|
||||
for (var index = min; index <= max; index++) {
|
||||
this._updateIndexProgress(progress, index, fromIndex, toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var navState = this.props.navState;
|
||||
var components = COMPONENT_NAMES.map(function (componentName) {
|
||||
return navState.routeStack.map(
|
||||
this._renderOrReturnComponent.bind(this, componentName)
|
||||
);
|
||||
}, this);
|
||||
|
||||
return (
|
||||
<View style={[styles.navBarContainer, this.props.navigationBarStyles]}>
|
||||
{components}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_renderOrReturnComponent: function(
|
||||
/*string*/componentName,
|
||||
/*object*/route,
|
||||
/*number*/index
|
||||
) /*object*/ {
|
||||
var navState = this.props.navState;
|
||||
var navBarRouteMapper = this.props.navigationBarRouteMapper;
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
var content = navBarRouteMapper[componentName](
|
||||
navState.routeStack[index],
|
||||
this.props.navigator,
|
||||
index,
|
||||
this.props.navState
|
||||
);
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var initialStage = index === navStatePresentedIndex(this.props.navState) ?
|
||||
NavigationBarStyles.Stages.Center : NavigationBarStyles.Stages.Left;
|
||||
return (
|
||||
<StaticContainer
|
||||
ref={containerRef}
|
||||
key={containerRef}
|
||||
shouldUpdate={false}>
|
||||
<View ref={componentName + index} style={initialStage[componentName]}>
|
||||
{content}
|
||||
</View>
|
||||
</StaticContainer>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
navBarContainer: {
|
||||
position: 'absolute',
|
||||
height: NavigationBarStyles.General.TotalNavHeight,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: NavigationBarStyles.General.ScreenWidth,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NavigationBar;
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule NavigationBarStyles
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Dimensions = require('Dimensions');
|
||||
|
||||
var buildStyleInterpolator = require('buildStyleInterpolator');
|
||||
var merge = require('merge');
|
||||
|
||||
var SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
var NAV_BAR_HEIGHT = 44;
|
||||
var STATUS_BAR_HEIGHT = 20;
|
||||
var NAV_HEIGHT = NAV_BAR_HEIGHT + STATUS_BAR_HEIGHT;
|
||||
|
||||
var BASE_STYLES = {
|
||||
Title: {
|
||||
position: 'absolute',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
left: 0,
|
||||
alignItems: 'center',
|
||||
width: SCREEN_WIDTH,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
LeftButton: {
|
||||
position: 'absolute',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
left: 0,
|
||||
overflow: 'hidden',
|
||||
opacity: 1,
|
||||
width: SCREEN_WIDTH / 3,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
RightButton: {
|
||||
position: 'absolute',
|
||||
top: STATUS_BAR_HEIGHT,
|
||||
left: 2 * SCREEN_WIDTH / 3,
|
||||
overflow: 'hidden',
|
||||
opacity: 1,
|
||||
alignItems: 'flex-end',
|
||||
width: SCREEN_WIDTH / 3,
|
||||
height: NAV_BAR_HEIGHT,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
};
|
||||
|
||||
// There are 3 stages: left, center, right. All previous navigation
|
||||
// items are in the left stage. The current navigation item is in the
|
||||
// center stage. All upcoming navigation items are in the right stage.
|
||||
// Another way to think of the stages is in terms of transitions. When
|
||||
// we move forward in the navigation stack, we perform a
|
||||
// right-to-center transition on the new navigation item and a
|
||||
// center-to-left transition on the current navigation item.
|
||||
var Stages = {
|
||||
Left: {
|
||||
Title: merge(BASE_STYLES.Title, { left: - SCREEN_WIDTH / 2, opacity: 0 }),
|
||||
LeftButton: merge(BASE_STYLES.LeftButton, { left: - SCREEN_WIDTH / 3, opacity: 1 }),
|
||||
RightButton: merge(BASE_STYLES.RightButton, { left: SCREEN_WIDTH / 3, opacity: 0 }),
|
||||
},
|
||||
Center: {
|
||||
Title: merge(BASE_STYLES.Title, { left: 0, opacity: 1 }),
|
||||
LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 1 }),
|
||||
RightButton: merge(BASE_STYLES.RightButton, { left: 2 * SCREEN_WIDTH / 3 - 0, opacity: 1 }),
|
||||
},
|
||||
Right: {
|
||||
Title: merge(BASE_STYLES.Title, { left: SCREEN_WIDTH / 2, opacity: 0 }),
|
||||
LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 0 }),
|
||||
RightButton: merge(BASE_STYLES.RightButton, { left: SCREEN_WIDTH, opacity: 0 }),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var opacityRatio = 100;
|
||||
|
||||
function buildSceneInterpolators(startStyles, endStyles) {
|
||||
return {
|
||||
Title: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.Title.opacity,
|
||||
to: endStyles.Title.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
left: {
|
||||
type: 'linear',
|
||||
from: startStyles.Title.left,
|
||||
to: endStyles.Title.left,
|
||||
min: 0,
|
||||
max: 1,
|
||||
extrapolate: true,
|
||||
},
|
||||
}),
|
||||
LeftButton: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.LeftButton.opacity,
|
||||
to: endStyles.LeftButton.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
round: opacityRatio,
|
||||
},
|
||||
left: {
|
||||
type: 'linear',
|
||||
from: startStyles.LeftButton.left,
|
||||
to: endStyles.LeftButton.left,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
}),
|
||||
RightButton: buildStyleInterpolator({
|
||||
opacity: {
|
||||
type: 'linear',
|
||||
from: startStyles.RightButton.opacity,
|
||||
to: endStyles.RightButton.opacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
round: opacityRatio,
|
||||
},
|
||||
left: {
|
||||
type: 'linear',
|
||||
from: startStyles.RightButton.left,
|
||||
to: endStyles.RightButton.left,
|
||||
min: 0,
|
||||
max: 1,
|
||||
extrapolate: true,
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
var Interpolators = {
|
||||
// Animating *into* the center stage from the right
|
||||
RightToCenter: buildSceneInterpolators(Stages.Right, Stages.Center),
|
||||
// Animating out of the center stage, to the left
|
||||
CenterToLeft: buildSceneInterpolators(Stages.Center, Stages.Left),
|
||||
// Both stages (animating *past* the center stage)
|
||||
RightToLeft: buildSceneInterpolators(Stages.Right, Stages.Left),
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
General: {
|
||||
NavBarHeight: NAV_BAR_HEIGHT,
|
||||
StatusBarHeight: STATUS_BAR_HEIGHT,
|
||||
TotalNavHeight: NAV_HEIGHT,
|
||||
ScreenWidth: SCREEN_WIDTH,
|
||||
},
|
||||
Interpolators,
|
||||
Stages,
|
||||
};
|
||||
279
Libraries/CustomComponents/JSNavigationStackAnimationConfigs.js
Normal file
279
Libraries/CustomComponents/JSNavigationStackAnimationConfigs.js
Normal file
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule JSNavigationStackAnimationConfigs
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Dimensions = require('Dimensions');
|
||||
var PixelRatio = require('PixelRatio');
|
||||
|
||||
var buildStyleInterpolator = require('buildStyleInterpolator');
|
||||
var merge = require('merge');
|
||||
|
||||
var SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
var SCREEN_HEIGHT = Dimensions.get('window').height;
|
||||
|
||||
var ToTheLeft = {
|
||||
// Rotate *requires* you to break out each individual component of
|
||||
// rotation (x, y, z, w)
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: 0, z: 0},
|
||||
to: {x: -Math.round(Dimensions.get('window').width * 0.3), y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
// Uncomment to try rotation:
|
||||
// Quick guide to reasoning about rotations:
|
||||
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#Quaternions
|
||||
// transformRotateRadians: {
|
||||
// from: {x: 0, y: 0, z: 0, w: 1},
|
||||
// to: {x: 0, y: 0, z: -0.47, w: 0.87},
|
||||
// min: 0,
|
||||
// max: 1,
|
||||
// type: 'linear',
|
||||
// extrapolate: true
|
||||
// },
|
||||
transformScale: {
|
||||
from: {x: 1, y: 1, z: 1},
|
||||
to: {x: 0.95, y: 0.95, z: 1},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true
|
||||
},
|
||||
opacity: {
|
||||
from: 1,
|
||||
to: 0.3,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: false,
|
||||
round: 100,
|
||||
},
|
||||
translateX: {
|
||||
from: 0,
|
||||
to: -Math.round(Dimensions.get('window').width * 0.3),
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
scaleX: {
|
||||
from: 1,
|
||||
to: 0.95,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true
|
||||
},
|
||||
scaleY: {
|
||||
from: 1,
|
||||
to: 0.95,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheRight = {
|
||||
opacity: {
|
||||
value: 1.0,
|
||||
type: 'constant',
|
||||
},
|
||||
|
||||
transformTranslate: {
|
||||
from: {x: Dimensions.get('window').width, y: 0, z: 0},
|
||||
to: {x: 0, y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
|
||||
translateX: {
|
||||
from: Dimensions.get('window').width,
|
||||
to: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
|
||||
scaleX: {
|
||||
value: 1,
|
||||
type: 'constant',
|
||||
},
|
||||
scaleY: {
|
||||
value: 1,
|
||||
type: 'constant',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var ToTheBack = {
|
||||
// Rotate *requires* you to break out each individual component of
|
||||
// rotation (x, y, z, w)
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: 0, z: 0},
|
||||
to: {x: 0, y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
transformScale: {
|
||||
from: {x: 1, y: 1, z: 1},
|
||||
to: {x: 0.95, y: 0.95, z: 1},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true
|
||||
},
|
||||
opacity: {
|
||||
from: 1,
|
||||
to: 0.3,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: false,
|
||||
round: 100,
|
||||
},
|
||||
scaleX: {
|
||||
from: 1,
|
||||
to: 0.95,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true
|
||||
},
|
||||
scaleY: {
|
||||
from: 1,
|
||||
to: 0.95,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheFront = {
|
||||
opacity: {
|
||||
value: 1.0,
|
||||
type: 'constant',
|
||||
},
|
||||
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: Dimensions.get('window').height, z: 0},
|
||||
to: {x: 0, y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
translateY: {
|
||||
from: Dimensions.get('window').height,
|
||||
to: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
scaleX: {
|
||||
value: 1,
|
||||
type: 'constant',
|
||||
},
|
||||
scaleY: {
|
||||
value: 1,
|
||||
type: 'constant',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var Interpolators = {
|
||||
Vertical: {
|
||||
into: buildStyleInterpolator(FromTheFront),
|
||||
out: buildStyleInterpolator(ToTheBack),
|
||||
},
|
||||
Horizontal: {
|
||||
into: buildStyleInterpolator(FromTheRight),
|
||||
out: buildStyleInterpolator(ToTheLeft),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// These are meant to mimic iOS default behavior
|
||||
var PastPointOfNoReturn = {
|
||||
horizontal: function(location) {
|
||||
return location > SCREEN_WIDTH * 3 / 5;
|
||||
},
|
||||
vertical: function(location) {
|
||||
return location > SCREEN_HEIGHT * 3 / 5;
|
||||
},
|
||||
};
|
||||
|
||||
var BaseConfig = {
|
||||
// When false, all gestures are ignored for this scene
|
||||
enableGestures: true,
|
||||
|
||||
// How far the swipe must drag to start transitioning
|
||||
gestureDetectMovement: 2,
|
||||
|
||||
// Amplitude of release velocity that is considered still
|
||||
notMoving: 0.3,
|
||||
|
||||
// Velocity to start at when transitioning without gesture
|
||||
defaultTransitionVelocity: 1.5,
|
||||
|
||||
// Fraction of directional move required.
|
||||
directionRatio: 0.66,
|
||||
|
||||
// Velocity to transition with when the gesture release was "not moving"
|
||||
snapVelocity: 2,
|
||||
|
||||
// Rebound spring parameters when transitioning FROM this scene
|
||||
springFriction: 26,
|
||||
springTension: 200,
|
||||
|
||||
// Defaults for horizontal transitioning:
|
||||
|
||||
isVertical: false,
|
||||
screenDimension: SCREEN_WIDTH,
|
||||
|
||||
// Region that can trigger swipe. iOS default is 30px from the left edge
|
||||
edgeHitWidth: 30,
|
||||
|
||||
// Point at which a non-velocity release will cause nav pop
|
||||
pastPointOfNoReturn: PastPointOfNoReturn.horizontal,
|
||||
|
||||
// Animation interpolators for this transition
|
||||
interpolators: Interpolators.Horizontal,
|
||||
};
|
||||
|
||||
var JSNavigationStackAnimationConfigs = {
|
||||
PushFromRight: merge(BaseConfig, {
|
||||
// We will want to customize this soon
|
||||
}),
|
||||
FloatFromRight: merge(BaseConfig, {
|
||||
// We will want to customize this soon
|
||||
}),
|
||||
FloatFromBottom: merge(BaseConfig, {
|
||||
edgeHitWidth: 150,
|
||||
interpolators: Interpolators.Vertical,
|
||||
isVertical: true,
|
||||
pastPointOfNoReturn: PastPointOfNoReturn.vertical,
|
||||
screenDimension: SCREEN_HEIGHT,
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = JSNavigationStackAnimationConfigs;
|
||||
@@ -16,7 +16,7 @@ var RCTUIManager = require('NativeModules').UIManager;
|
||||
var ScrollView = require('ScrollView');
|
||||
var ScrollResponder = require('ScrollResponder');
|
||||
var StaticRenderer = require('StaticRenderer');
|
||||
var TimerMixin = require('TimerMixin');
|
||||
var TimerMixin = require('react-timer-mixin');
|
||||
|
||||
var logError = require('logError');
|
||||
var merge = require('merge');
|
||||
|
||||
49
Libraries/Interaction/InteractionMixin.js
Normal file
49
Libraries/Interaction/InteractionMixin.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule InteractionMixin
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var InteractionManager = require('InteractionManager');
|
||||
|
||||
/**
|
||||
* This mixin provides safe versions of InteractionManager start/end methods
|
||||
* that ensures `clearInteractionHandle` is always called
|
||||
* once per start, even if the component is unmounted.
|
||||
*/
|
||||
var InteractionMixin = {
|
||||
componentWillUnmount: function() {
|
||||
while (this._interactionMixinHandles.length) {
|
||||
InteractionManager.clearInteractionHandle(
|
||||
this._interactionMixinHandles.pop()
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_interactionMixinHandles: [],
|
||||
|
||||
createInteractionHandle: function() {
|
||||
var handle = InteractionManager.createInteractionHandle();
|
||||
this._interactionMixinHandles.push(handle);
|
||||
return handle;
|
||||
},
|
||||
|
||||
clearInteractionHandle: function(clearHandle) {
|
||||
InteractionManager.clearInteractionHandle(clearHandle);
|
||||
this._interactionMixinHandles = this._interactionMixinHandles.filter(
|
||||
handle => handle !== clearHandle
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule work for after all interactions have completed.
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
runAfterInteractions: function(callback) {
|
||||
InteractionManager.runAfterInteractions(callback);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = InteractionMixin;
|
||||
@@ -15,153 +15,32 @@ var RCTDataManager = require('NativeModules').DataManager;
|
||||
|
||||
var crc32 = require('crc32');
|
||||
|
||||
class XMLHttpRequest {
|
||||
var XMLHttpRequestBase = require('XMLHttpRequestBase');
|
||||
|
||||
UNSENT: number;
|
||||
OPENED: number;
|
||||
HEADERS_RECEIVED: number;
|
||||
LOADING: number;
|
||||
DONE: number;
|
||||
|
||||
onreadystatechange: ?Function;
|
||||
onload: ?Function;
|
||||
upload: any;
|
||||
readyState: number;
|
||||
responseHeaders: ?Object;
|
||||
responseText: ?string;
|
||||
status: ?string;
|
||||
|
||||
_method: ?string;
|
||||
_url: ?string;
|
||||
_headers: Object;
|
||||
_sent: boolean;
|
||||
_aborted: boolean;
|
||||
|
||||
constructor() {
|
||||
this.UNSENT = 0;
|
||||
this.OPENED = 1;
|
||||
this.HEADERS_RECEIVED = 2;
|
||||
this.LOADING = 3;
|
||||
this.DONE = 4;
|
||||
|
||||
this.onreadystatechange = undefined;
|
||||
this.upload = undefined; /* Upload not supported */
|
||||
this.readyState = this.UNSENT;
|
||||
this.responseHeaders = undefined;
|
||||
this.responseText = undefined;
|
||||
this.status = undefined;
|
||||
|
||||
this._method = null;
|
||||
this._url = null;
|
||||
this._headers = {};
|
||||
this._sent = false;
|
||||
this._aborted = false;
|
||||
}
|
||||
|
||||
getAllResponseHeaders(): ?string {
|
||||
/* Stub */
|
||||
return '';
|
||||
}
|
||||
|
||||
getResponseHeader(header: string): ?string {
|
||||
/* Stub */
|
||||
return '';
|
||||
}
|
||||
|
||||
setRequestHeader(header: string, value: any): void {
|
||||
this._headers[header] = value;
|
||||
}
|
||||
|
||||
open(method: string, url: string, async: ?boolean): void {
|
||||
/* Other optional arguments are not supported */
|
||||
if (this.readyState !== this.UNSENT) {
|
||||
throw new Error('Cannot open, already sending');
|
||||
}
|
||||
if (async !== undefined && !async) {
|
||||
// async is default
|
||||
throw new Error('Synchronous http requests are not supported');
|
||||
}
|
||||
this._method = method;
|
||||
this._url = url;
|
||||
this._aborted = false;
|
||||
this._setReadyState(this.OPENED);
|
||||
}
|
||||
|
||||
send(data: any): void {
|
||||
if (this.readyState !== this.OPENED) {
|
||||
throw new Error('Request has not been opened');
|
||||
}
|
||||
if (this._sent) {
|
||||
throw new Error('Request has already been sent');
|
||||
}
|
||||
this._sent = true;
|
||||
class XMLHttpRequest extends XMLHttpRequestBase {
|
||||
|
||||
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
|
||||
RCTDataManager.queryData(
|
||||
'http',
|
||||
JSON.stringify({
|
||||
method: this._method,
|
||||
url: this._url,
|
||||
method: method,
|
||||
url: url,
|
||||
data: data,
|
||||
headers: this._headers,
|
||||
headers: headers,
|
||||
}),
|
||||
'h' + crc32(this._method + '|' + this._url + '|' + data),
|
||||
'h' + crc32(method + '|' + url + '|' + data),
|
||||
(result) => {
|
||||
result = JSON.parse(result);
|
||||
this._callback(result.status, result.responseHeaders, result.responseText);
|
||||
this.callback(result.status, result.responseHeaders, result.responseText);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
abort(): void {
|
||||
abortImpl(): void {
|
||||
console.warn(
|
||||
'XMLHttpRequest: abort() cancels JS callbacks ' +
|
||||
'but not native HTTP request.'
|
||||
);
|
||||
// only call onreadystatechange if there is something to abort,
|
||||
// below logic is per spec
|
||||
if (!(this.readyState === this.UNSENT ||
|
||||
(this.readyState === this.OPENED && !this._sent) ||
|
||||
this.readyState === this.DONE)) {
|
||||
this._sent = false;
|
||||
this._setReadyState(this.DONE);
|
||||
}
|
||||
if (this.readyState === this.DONE) {
|
||||
this._sendLoad();
|
||||
}
|
||||
this.readyState = this.UNSENT;
|
||||
this._aborted = true;
|
||||
}
|
||||
|
||||
_setReadyState(newState: number): void {
|
||||
this.readyState = newState;
|
||||
// TODO: workaround flow bug with nullable function checks
|
||||
var onreadystatechange = this.onreadystatechange;
|
||||
if (onreadystatechange) {
|
||||
// We should send an event to handler, but since we don't process that
|
||||
// event anywhere, let's leave it empty
|
||||
onreadystatechange(null);
|
||||
}
|
||||
}
|
||||
|
||||
_sendLoad(): void {
|
||||
// TODO: workaround flow bug with nullable function checks
|
||||
var onload = this.onload;
|
||||
if (onload) {
|
||||
// We should send an event to handler, but since we don't process that
|
||||
// event anywhere, let's leave it empty
|
||||
onload(null);
|
||||
}
|
||||
}
|
||||
|
||||
_callback(status: string, responseHeaders: ?Object, responseText: string): void {
|
||||
if (this._aborted) {
|
||||
return;
|
||||
}
|
||||
this.status = status;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.responseText = responseText;
|
||||
this._setReadyState(this.DONE);
|
||||
this._sendLoad();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
153
Libraries/Network/XMLHttpRequestBase.js
Normal file
153
Libraries/Network/XMLHttpRequestBase.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule XMLHttpRequestBase
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Shared base for platform-specific XMLHttpRequest implementations.
|
||||
*/
|
||||
class XMLHttpRequestBase {
|
||||
|
||||
UNSENT: number;
|
||||
OPENED: number;
|
||||
HEADERS_RECEIVED: number;
|
||||
LOADING: number;
|
||||
DONE: number;
|
||||
|
||||
onreadystatechange: ?Function;
|
||||
onload: ?Function;
|
||||
upload: any;
|
||||
readyState: number;
|
||||
responseHeaders: ?Object;
|
||||
responseText: ?string;
|
||||
status: ?string;
|
||||
|
||||
_method: ?string;
|
||||
_url: ?string;
|
||||
_headers: Object;
|
||||
_sent: boolean;
|
||||
_aborted: boolean;
|
||||
|
||||
constructor() {
|
||||
this.UNSENT = 0;
|
||||
this.OPENED = 1;
|
||||
this.HEADERS_RECEIVED = 2;
|
||||
this.LOADING = 3;
|
||||
this.DONE = 4;
|
||||
|
||||
this.onreadystatechange = undefined;
|
||||
this.upload = undefined; /* Upload not supported */
|
||||
this.readyState = this.UNSENT;
|
||||
this.responseHeaders = undefined;
|
||||
this.responseText = undefined;
|
||||
this.status = undefined;
|
||||
|
||||
this._method = null;
|
||||
this._url = null;
|
||||
this._headers = {};
|
||||
this._sent = false;
|
||||
this._aborted = false;
|
||||
}
|
||||
|
||||
getAllResponseHeaders(): ?string {
|
||||
/* Stub */
|
||||
return '';
|
||||
}
|
||||
|
||||
getResponseHeader(header: string): ?string {
|
||||
/* Stub */
|
||||
return '';
|
||||
}
|
||||
|
||||
setRequestHeader(header: string, value: any): void {
|
||||
this._headers[header] = value;
|
||||
}
|
||||
|
||||
open(method: string, url: string, async: ?boolean): void {
|
||||
/* Other optional arguments are not supported */
|
||||
if (this.readyState !== this.UNSENT) {
|
||||
throw new Error('Cannot open, already sending');
|
||||
}
|
||||
if (async !== undefined && !async) {
|
||||
// async is default
|
||||
throw new Error('Synchronous http requests are not supported');
|
||||
}
|
||||
this._method = method;
|
||||
this._url = url;
|
||||
this._aborted = false;
|
||||
this._setReadyState(this.OPENED);
|
||||
}
|
||||
|
||||
sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
|
||||
throw new Error('Subclass must define sendImpl method');
|
||||
}
|
||||
|
||||
abortImpl(): void {
|
||||
throw new Error('Subclass must define abortImpl method');
|
||||
}
|
||||
|
||||
send(data: any): void {
|
||||
if (this.readyState !== this.OPENED) {
|
||||
throw new Error('Request has not been opened');
|
||||
}
|
||||
if (this._sent) {
|
||||
throw new Error('Request has already been sent');
|
||||
}
|
||||
this._sent = true;
|
||||
this.sendImpl(this._method, this._url, this._headers, data);
|
||||
}
|
||||
|
||||
abort(): void {
|
||||
this.abortImpl();
|
||||
// only call onreadystatechange if there is something to abort,
|
||||
// below logic is per spec
|
||||
if (!(this.readyState === this.UNSENT ||
|
||||
(this.readyState === this.OPENED && !this._sent) ||
|
||||
this.readyState === this.DONE)) {
|
||||
this._sent = false;
|
||||
this._setReadyState(this.DONE);
|
||||
}
|
||||
if (this.readyState === this.DONE) {
|
||||
this._sendLoad();
|
||||
}
|
||||
this.readyState = this.UNSENT;
|
||||
this._aborted = true;
|
||||
}
|
||||
|
||||
callback(status: string, responseHeaders: ?Object, responseText: string): void {
|
||||
if (this._aborted) {
|
||||
return;
|
||||
}
|
||||
this.status = status;
|
||||
this.responseHeaders = responseHeaders;
|
||||
this.responseText = responseText;
|
||||
this._setReadyState(this.DONE);
|
||||
this._sendLoad();
|
||||
}
|
||||
|
||||
_setReadyState(newState: number): void {
|
||||
this.readyState = newState;
|
||||
// TODO: workaround flow bug with nullable function checks
|
||||
var onreadystatechange = this.onreadystatechange;
|
||||
if (onreadystatechange) {
|
||||
// We should send an event to handler, but since we don't process that
|
||||
// event anywhere, let's leave it empty
|
||||
onreadystatechange(null);
|
||||
}
|
||||
}
|
||||
|
||||
_sendLoad(): void {
|
||||
// TODO: workaround flow bug with nullable function checks
|
||||
var onload = this.onload;
|
||||
if (onload) {
|
||||
// We should send an event to handler, but since we don't process that
|
||||
// event anywhere, let's leave it empty
|
||||
onload(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = XMLHttpRequestBase;
|
||||
@@ -50,9 +50,9 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification";
|
||||
|
||||
+ (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
|
||||
{
|
||||
#ifdef __IPHONE_8_0
|
||||
[application registerForRemoteNotifications];
|
||||
#endif
|
||||
if ([application respondsToSelector:@selector(registerForRemoteNotifications)]) {
|
||||
[application registerForRemoteNotifications];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
|
||||
@@ -93,11 +93,6 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification";
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[self requestPermissions];
|
||||
});
|
||||
|
||||
[UIApplication sharedApplication].applicationIconBadgeNumber = number;
|
||||
}
|
||||
|
||||
@@ -117,13 +112,22 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification";
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
#ifdef __IPHONE_8_0
|
||||
UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert;
|
||||
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
|
||||
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
|
||||
#else
|
||||
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
|
||||
|
||||
// if we are targeting iOS 7, *and* the new UIUserNotificationSettings
|
||||
// class is not available, then register using the old mechanism
|
||||
if (![UIUserNotificationSettings class]) {
|
||||
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
|
||||
UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert];
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert;
|
||||
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
|
||||
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
|
||||
|
||||
}
|
||||
|
||||
+ (void)checkPermissions:(RCTResponseSenderBlock)callback
|
||||
@@ -131,17 +135,11 @@ NSString *const RCTOpenURLNotification = @"RCTOpenURLNotification";
|
||||
RCT_EXPORT();
|
||||
|
||||
NSMutableDictionary *permissions = [[NSMutableDictionary alloc] init];
|
||||
#ifdef __IPHONE_8_0
|
||||
|
||||
UIUserNotificationType types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
|
||||
permissions[@"alert"] = @((BOOL)(types & UIUserNotificationTypeAlert));
|
||||
permissions[@"badge"] = @((BOOL)(types & UIUserNotificationTypeBadge));
|
||||
permissions[@"sound"] = @((BOOL)(types & UIUserNotificationTypeSound));
|
||||
#else
|
||||
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
|
||||
permissions[@"alert"] = @((BOOL)(types & UIRemoteNotificationTypeAlert));
|
||||
permissions[@"badge"] = @((BOOL)(types & UIRemoteNotificationTypeBadge));
|
||||
permissions[@"sound"] = @((BOOL)(types & UIRemoteNotificationTypeSound));
|
||||
#endif
|
||||
|
||||
callback(@[permissions]);
|
||||
}
|
||||
|
||||
99
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h
Normal file
99
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 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 <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#ifndef FB_REFERENCE_IMAGE_DIR
|
||||
#define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
|
||||
#endif
|
||||
|
||||
/**
|
||||
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
|
||||
@param view The view to snapshot
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param referenceImageDirectorySuffix An optional suffix, appended to the reference image directory path, such as "_iOS8"
|
||||
*/
|
||||
#define FBSnapshotVerifyViewWithReferenceDirectorySuffix(view__, identifier__, referenceImagesDirectorySuffix__) \
|
||||
{ \
|
||||
NSError *error__ = nil; \
|
||||
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, referenceImagesDirectorySuffix__]; \
|
||||
BOOL comparisonSuccess__ = [self compareSnapshotOfView:(view__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
|
||||
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
|
||||
}
|
||||
|
||||
#define FBSnapshotVerifyView(view__, identifier__) \
|
||||
{ \
|
||||
FBSnapshotVerifyViewWithReferenceDirectorySuffix(view__, identifier__, @""); \
|
||||
}
|
||||
|
||||
/**
|
||||
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
|
||||
@param layer The layer to snapshot
|
||||
@param identifier An optional identifier, used is there are multiple snapshot tests in a given -test method.
|
||||
@param referenceImageDirectorySuffix An optional suffix, appended to the reference image directory path, such as "_iOS8"
|
||||
*/
|
||||
#define FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, referenceImagesDirectorySuffix__) \
|
||||
{ \
|
||||
NSError *error__ = nil; \
|
||||
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, referenceImagesDirectorySuffix__]; \
|
||||
BOOL comparisonSuccess__ = [self compareSnapshotOfLayer:(layer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
|
||||
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
|
||||
}
|
||||
|
||||
#define FBSnapshotVerifyLayer(layer__, identifier__) \
|
||||
{ \
|
||||
FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
|
||||
}
|
||||
|
||||
/**
|
||||
The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test
|
||||
and compare an image of the view to a reference image that write lots of complex layout-code tests.
|
||||
|
||||
In order to flip the tests in your subclass to record the reference images set `recordMode` to YES before calling
|
||||
-[super setUp].
|
||||
*/
|
||||
@interface FBSnapshotTestCase : XCTestCase
|
||||
|
||||
/**
|
||||
When YES, the test macros will save reference images, rather than performing an actual test.
|
||||
*/
|
||||
@property (readwrite, nonatomic, assign) BOOL recordMode;
|
||||
|
||||
/**
|
||||
Performs the comparisong or records a snapshot of the layer if recordMode is YES.
|
||||
@param layer The Layer to snapshot
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparisong or records a snapshot of the view if recordMode is YES.
|
||||
@param view The view to snapshot
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
@end
|
||||
82
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.m
Normal file
82
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.m
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 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 "FBSnapshotTestCase.h"
|
||||
|
||||
#import "FBSnapshotTestController.h"
|
||||
|
||||
@interface FBSnapshotTestCase ()
|
||||
|
||||
@property (readwrite, nonatomic, retain) FBSnapshotTestController *snapshotController;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FBSnapshotTestCase
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
self.snapshotController = [[FBSnapshotTestController alloc] initWithTestName:NSStringFromClass([self class])];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
self.snapshotController = nil;
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (BOOL)recordMode
|
||||
{
|
||||
return self.snapshotController.recordMode;
|
||||
}
|
||||
|
||||
- (void)setRecordMode:(BOOL)recordMode
|
||||
{
|
||||
self.snapshotController.recordMode = recordMode;
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self _compareSnapshotOfViewOrLayer:layer
|
||||
referenceImagesDirectory:referenceImagesDirectory
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self _compareSnapshotOfViewOrLayer:view
|
||||
referenceImagesDirectory:referenceImagesDirectory
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private API
|
||||
|
||||
- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
|
||||
return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
|
||||
selector:self.invocation.selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
@end
|
||||
152
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h
Normal file
152
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, FBSnapshotTestControllerErrorCode) {
|
||||
FBSnapshotTestControllerErrorCodeUnknown,
|
||||
FBSnapshotTestControllerErrorCodeNeedsRecord,
|
||||
FBSnapshotTestControllerErrorCodePNGCreationFailed,
|
||||
FBSnapshotTestControllerErrorCodeImagesDifferentSizes,
|
||||
FBSnapshotTestControllerErrorCodeImagesDifferent,
|
||||
};
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController use this domain.
|
||||
*/
|
||||
extern NSString *const FBSnapshotTestControllerErrorDomain;
|
||||
|
||||
/**
|
||||
Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary.
|
||||
*/
|
||||
extern NSString *const FBReferenceImageFilePathKey;
|
||||
|
||||
/**
|
||||
Provides the heavy-lifting for FBSnapshotTestCase. It loads and saves images, along with performing the actual pixel-
|
||||
by-pixel comparison of images.
|
||||
Instances are initialized with the test class, and directories to read and write to.
|
||||
*/
|
||||
@interface FBSnapshotTestController : NSObject
|
||||
|
||||
/**
|
||||
Record snapshots.
|
||||
**/
|
||||
@property(readwrite, nonatomic, assign) BOOL recordMode;
|
||||
|
||||
/**
|
||||
@param testClass The subclass of FBSnapshotTestCase that is using this controller.
|
||||
@param referenceImagesDirectory The directory where the reference images are stored.
|
||||
@returns An instance of FBSnapshotTestController.
|
||||
*/
|
||||
- (id)initWithTestClass:(Class)testClass;
|
||||
|
||||
/**
|
||||
Designated initializer.
|
||||
@param testName The name of the tests.
|
||||
@param referenceImagesDirectory The directory where the reference images are stored.
|
||||
@returns An instance of FBSnapshotTestController.
|
||||
*/
|
||||
- (id)initWithTestName:(NSString *)testName;
|
||||
|
||||
|
||||
/**
|
||||
Performs the comparison of the layer.
|
||||
@param layer The Layer to snapshot.
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison of the view.
|
||||
@param view The view to snapshot.
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison of a view or layer.
|
||||
@param view The view or layer to snapshot.
|
||||
@param referenceImagesDirectory The directory in which reference images are stored.
|
||||
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
|
||||
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
|
||||
/**
|
||||
The directory in which referfence images are stored.
|
||||
*/
|
||||
@property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory;
|
||||
|
||||
/**
|
||||
Loads a reference image.
|
||||
@param selector The test method being run.
|
||||
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
|
||||
@param error An error, if this methods returns nil, the error will be something useful.
|
||||
@returns An image.
|
||||
*/
|
||||
- (UIImage *)referenceImageForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)error;
|
||||
|
||||
/**
|
||||
Saves a reference image.
|
||||
@param selector The test method being run.
|
||||
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
|
||||
@param error An error, if this methods returns NO, the error will be something useful.
|
||||
@returns An image.
|
||||
*/
|
||||
- (BOOL)saveReferenceImage:(UIImage *)image
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs a pixel-by-pixel comparison of the two images.
|
||||
@param referenceImage The reference (correct) image.
|
||||
@param image The image to test against the reference.
|
||||
@param error An error that indicates why the comparison failed if it does.
|
||||
@param YES if the comparison succeeded and the images are the same.
|
||||
*/
|
||||
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
|
||||
toImage:(UIImage *)image
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Saves the reference image and the test image to `failedOutputDirectory`.
|
||||
@param referenceImage The reference (correct) image.
|
||||
@param testImage The image to test against the reference.
|
||||
@param selector The test method being run.
|
||||
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
|
||||
@param error An error that indicates why the comparison failed if it does.
|
||||
@param YES if the save succeeded.
|
||||
*/
|
||||
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
|
||||
testImage:(UIImage *)testImage
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
@end
|
||||
392
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m
Normal file
392
Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 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 "FBSnapshotTestController.h"
|
||||
|
||||
#import "UIImage+Compare.h"
|
||||
#import "UIImage+Diff.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain";
|
||||
|
||||
NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
|
||||
|
||||
typedef struct RGBAPixel {
|
||||
char r;
|
||||
char g;
|
||||
char b;
|
||||
char a;
|
||||
} RGBAPixel;
|
||||
|
||||
@interface FBSnapshotTestController ()
|
||||
|
||||
@property (readonly, nonatomic, copy) NSString *testName;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FBSnapshotTestController
|
||||
{
|
||||
NSFileManager *_fileManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Lifecycle
|
||||
|
||||
- (id)initWithTestClass:(Class)testClass;
|
||||
{
|
||||
return [self initWithTestName:NSStringFromClass(testClass)];
|
||||
}
|
||||
|
||||
- (id)initWithTestName:(NSString *)testName
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_testName = [testName copy];
|
||||
_fileManager = [[NSFileManager alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Properties
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Public API
|
||||
|
||||
- (UIImage *)referenceImageForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
|
||||
if (nil == image && NULL != errorPtr) {
|
||||
BOOL exists = [_fileManager fileExistsAtPath:filePath];
|
||||
if (!exists) {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodeNeedsRecord
|
||||
userInfo:@{
|
||||
FBReferenceImageFilePathKey: filePath,
|
||||
NSLocalizedDescriptionKey: @"Unable to load reference image.",
|
||||
NSLocalizedFailureReasonErrorKey: @"Reference image not found. You need to run the test in record mode",
|
||||
}];
|
||||
} else {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodeUnknown
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
- (BOOL)saveReferenceImage:(UIImage *)image
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
BOOL didWrite = NO;
|
||||
if (nil != image) {
|
||||
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
|
||||
NSData *pngData = UIImagePNGRepresentation(image);
|
||||
if (nil != pngData) {
|
||||
NSError *creationError = nil;
|
||||
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:&creationError];
|
||||
if (!didCreateDir) {
|
||||
if (NULL != errorPtr) {
|
||||
*errorPtr = creationError;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr];
|
||||
if (didWrite) {
|
||||
NSLog(@"Reference image save at: %@", filePath);
|
||||
}
|
||||
} else {
|
||||
if (nil != errorPtr) {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodePNGCreationFailed
|
||||
userInfo:@{
|
||||
FBReferenceImageFilePathKey: filePath,
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
return didWrite;
|
||||
}
|
||||
|
||||
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
|
||||
testImage:(UIImage *)testImage
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
NSData *referencePNGData = UIImagePNGRepresentation(referenceImage);
|
||||
NSData *testPNGData = UIImagePNGRepresentation(testImage);
|
||||
|
||||
NSString *referencePath = [self _failedFilePathForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeFailedReference];
|
||||
|
||||
NSError *creationError = nil;
|
||||
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[referencePath stringByDeletingLastPathComponent]
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:&creationError];
|
||||
if (!didCreateDir) {
|
||||
if (NULL != errorPtr) {
|
||||
*errorPtr = creationError;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *testPath = [self _failedFilePathForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeFailedTest];
|
||||
|
||||
if (![testPNGData writeToFile:testPath options:NSDataWritingAtomic error:errorPtr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *diffPath = [self _failedFilePathForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff];
|
||||
|
||||
UIImage *diffImage = [referenceImage diffWithImage:testImage];
|
||||
NSData *diffImageData = UIImagePNGRepresentation(diffImage);
|
||||
|
||||
if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSLog(@"If you have Kaleidoscope installed you can run this command to see an image diff:\n"
|
||||
@"ksdiff \"%@\" \"%@\"", referencePath, testPath);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)compareReferenceImage:(UIImage *)referenceImage toImage:(UIImage *)image error:(NSError **)errorPtr
|
||||
{
|
||||
if (CGSizeEqualToSize(referenceImage.size, image.size)) {
|
||||
|
||||
BOOL imagesEqual = [referenceImage compareWithImage:image];
|
||||
if (NULL != errorPtr) {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodeImagesDifferent
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"Images different",
|
||||
}];
|
||||
}
|
||||
return imagesEqual;
|
||||
}
|
||||
if (NULL != errorPtr) {
|
||||
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
|
||||
code:FBSnapshotTestControllerErrorCodeImagesDifferentSizes
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"Images different sizes",
|
||||
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"referenceImage:%@, image:%@",
|
||||
NSStringFromCGSize(referenceImage.size),
|
||||
NSStringFromCGSize(image.size)],
|
||||
}];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private API
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
|
||||
FBTestSnapshotFileNameTypeReference,
|
||||
FBTestSnapshotFileNameTypeFailedReference,
|
||||
FBTestSnapshotFileNameTypeFailedTest,
|
||||
FBTestSnapshotFileNameTypeFailedTestDiff,
|
||||
};
|
||||
|
||||
- (NSString *)_fileNameForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
fileNameType:(FBTestSnapshotFileNameType)fileNameType
|
||||
{
|
||||
NSString *fileName = nil;
|
||||
switch (fileNameType) {
|
||||
case FBTestSnapshotFileNameTypeFailedReference:
|
||||
fileName = @"reference_";
|
||||
break;
|
||||
case FBTestSnapshotFileNameTypeFailedTest:
|
||||
fileName = @"failed_";
|
||||
break;
|
||||
case FBTestSnapshotFileNameTypeFailedTestDiff:
|
||||
fileName = @"diff_";
|
||||
break;
|
||||
default:
|
||||
fileName = @"";
|
||||
break;
|
||||
}
|
||||
fileName = [fileName stringByAppendingString:NSStringFromSelector(selector)];
|
||||
if (0 < identifier.length) {
|
||||
fileName = [fileName stringByAppendingFormat:@"_%@", identifier];
|
||||
}
|
||||
if ([[UIScreen mainScreen] scale] > 1.0) {
|
||||
fileName = [fileName stringByAppendingFormat:@"@%.fx", [[UIScreen mainScreen] scale]];
|
||||
}
|
||||
fileName = [fileName stringByAppendingPathExtension:@"png"];
|
||||
return fileName;
|
||||
}
|
||||
|
||||
- (NSString *)_referenceFilePathForSelector:(SEL)selector identifier:(NSString *)identifier
|
||||
{
|
||||
NSString *fileName = [self _fileNameForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:FBTestSnapshotFileNameTypeReference];
|
||||
NSString *filePath = [_referenceImagesDirectory stringByAppendingPathComponent:_testName];
|
||||
filePath = [filePath stringByAppendingPathComponent:fileName];
|
||||
return filePath;
|
||||
}
|
||||
|
||||
- (NSString *)_failedFilePathForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
fileNameType:(FBTestSnapshotFileNameType)fileNameType
|
||||
{
|
||||
NSString *fileName = [self _fileNameForSelector:selector
|
||||
identifier:identifier
|
||||
fileNameType:fileNameType];
|
||||
NSString *folderPath = NSTemporaryDirectory();
|
||||
if (getenv("IMAGE_DIFF_DIR")) {
|
||||
folderPath = @(getenv("IMAGE_DIFF_DIR"));
|
||||
}
|
||||
NSString *filePath = [folderPath stringByAppendingPathComponent:_testName];
|
||||
filePath = [filePath stringByAppendingPathComponent:fileName];
|
||||
return filePath;
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self compareSnapshotOfViewOrLayer:layer
|
||||
selector:selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self compareSnapshotOfViewOrLayer:view
|
||||
selector:selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
if (self.recordMode) {
|
||||
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
|
||||
} else {
|
||||
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private API
|
||||
|
||||
- (BOOL)_performPixelComparisonWithViewOrLayer:(UIView *)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
|
||||
if (nil != referenceImage) {
|
||||
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
|
||||
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot error:errorPtr];
|
||||
if (!imagesSame) {
|
||||
[self saveFailedReferenceImage:referenceImage
|
||||
testImage:snapshot
|
||||
selector:selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
return imagesSame;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
|
||||
return [self saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
|
||||
}
|
||||
|
||||
- (UIImage *)_snapshotViewOrLayer:(id)viewOrLayer
|
||||
{
|
||||
CALayer *layer = nil;
|
||||
|
||||
if ([viewOrLayer isKindOfClass:[UIView class]]) {
|
||||
return [self _renderView:viewOrLayer];
|
||||
} else if ([viewOrLayer isKindOfClass:[CALayer class]]) {
|
||||
layer = (CALayer *)viewOrLayer;
|
||||
[layer layoutIfNeeded];
|
||||
return [self _renderLayer:layer];
|
||||
} else {
|
||||
[NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIImage *)_renderLayer:(CALayer *)layer
|
||||
{
|
||||
CGRect bounds = layer.bounds;
|
||||
|
||||
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
|
||||
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
NSAssert1(context, @"Could not generate context for layer %@", layer);
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
[layer renderInContext:context];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
- (UIImage *)_renderView:(UIView *)view
|
||||
{
|
||||
[view layoutIfNeeded];
|
||||
return [self _renderLayer:view.layer];
|
||||
}
|
||||
|
||||
@end
|
||||
37
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Compare.h
Normal file
37
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Compare.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS 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>
|
||||
|
||||
@interface UIImage (Compare)
|
||||
|
||||
- (BOOL)compareWithImage:(UIImage *)image;
|
||||
|
||||
@end
|
||||
91
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Compare.m
Normal file
91
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Compare.m
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS 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 "UIImage+Compare.h"
|
||||
|
||||
@implementation UIImage (Compare)
|
||||
|
||||
- (BOOL)compareWithImage:(UIImage *)image
|
||||
{
|
||||
NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");
|
||||
|
||||
// The images have the equal size, so we could use the smallest amount of bytes because of byte padding
|
||||
size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage));
|
||||
size_t referenceImageSizeBytes = CGImageGetHeight(self.CGImage) * minBytesPerRow;
|
||||
void *referenceImagePixels = calloc(1, referenceImageSizeBytes);
|
||||
void *imagePixels = calloc(1, referenceImageSizeBytes);
|
||||
|
||||
if (!referenceImagePixels || !imagePixels) {
|
||||
free(referenceImagePixels);
|
||||
free(imagePixels);
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels,
|
||||
CGImageGetWidth(self.CGImage),
|
||||
CGImageGetHeight(self.CGImage),
|
||||
CGImageGetBitsPerComponent(self.CGImage),
|
||||
minBytesPerRow,
|
||||
CGImageGetColorSpace(self.CGImage),
|
||||
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
|
||||
);
|
||||
CGContextRef imageContext = CGBitmapContextCreate(imagePixels,
|
||||
CGImageGetWidth(image.CGImage),
|
||||
CGImageGetHeight(image.CGImage),
|
||||
CGImageGetBitsPerComponent(image.CGImage),
|
||||
minBytesPerRow,
|
||||
CGImageGetColorSpace(image.CGImage),
|
||||
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
|
||||
);
|
||||
|
||||
CGFloat scaleFactor = [[UIScreen mainScreen] scale];
|
||||
CGContextScaleCTM(referenceImageContext, scaleFactor, scaleFactor);
|
||||
CGContextScaleCTM(imageContext, scaleFactor, scaleFactor);
|
||||
|
||||
if (!referenceImageContext || !imageContext) {
|
||||
CGContextRelease(referenceImageContext);
|
||||
CGContextRelease(imageContext);
|
||||
free(referenceImagePixels);
|
||||
free(imagePixels);
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGContextDrawImage(referenceImageContext, CGRectMake(0.0f, 0.0f, self.size.width, self.size.height), self.CGImage);
|
||||
CGContextDrawImage(imageContext, CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), image.CGImage);
|
||||
CGContextRelease(referenceImageContext);
|
||||
CGContextRelease(imageContext);
|
||||
|
||||
BOOL imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
|
||||
free(referenceImagePixels);
|
||||
free(imagePixels);
|
||||
return imageEqual;
|
||||
}
|
||||
|
||||
@end
|
||||
37
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Diff.h
Normal file
37
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Diff.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS 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>
|
||||
|
||||
@interface UIImage (Diff)
|
||||
|
||||
- (UIImage *)diffWithImage:(UIImage *)image;
|
||||
|
||||
@end
|
||||
56
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Diff.m
Normal file
56
Libraries/RCTTest/FBSnapshotTestCase/UIImage+Diff.m
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Created by Gabriel Handford on 3/1/09.
|
||||
// Copyright 2009-2013. All rights reserved.
|
||||
// Created by John Boiles on 10/20/11.
|
||||
// Copyright (c) 2011. All rights reserved
|
||||
// Modified by Felix Schulze on 2/11/13.
|
||||
// Copyright 2013. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// 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
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS 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 "UIImage+Diff.h"
|
||||
|
||||
@implementation UIImage (Diff)
|
||||
|
||||
- (UIImage *)diffWithImage:(UIImage *)image
|
||||
{
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height));
|
||||
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0.0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
|
||||
CGContextSetAlpha(context, 0.5f);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
|
||||
CGContextSetBlendMode(context, kCGBlendModeDifference);
|
||||
CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height));
|
||||
CGContextEndTransparencyLayer(context);
|
||||
UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return returnImage;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -10,6 +10,9 @@
|
||||
585135371AB3C56F00882537 /* RCTTestModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 585135341AB3C56F00882537 /* RCTTestModule.m */; };
|
||||
585135381AB3C57000882537 /* RCTTestRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 585135361AB3C56F00882537 /* RCTTestRunner.m */; };
|
||||
585135391AB3C59A00882537 /* RCTTestRunner.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 585135351AB3C56F00882537 /* RCTTestRunner.h */; };
|
||||
58E64FED1AB964CD007446E2 /* FBSnapshotTestController.m in Sources */ = {isa = PBXBuildFile; fileRef = 58E64FE71AB964CD007446E2 /* FBSnapshotTestController.m */; };
|
||||
58E64FEE1AB964CD007446E2 /* UIImage+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = 58E64FE91AB964CD007446E2 /* UIImage+Compare.m */; };
|
||||
58E64FEF1AB964CD007446E2 /* UIImage+Diff.m in Sources */ = {isa = PBXBuildFile; fileRef = 58E64FEB1AB964CD007446E2 /* UIImage+Diff.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -31,6 +34,14 @@
|
||||
585135341AB3C56F00882537 /* RCTTestModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTestModule.m; sourceTree = "<group>"; };
|
||||
585135351AB3C56F00882537 /* RCTTestRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTestRunner.h; sourceTree = "<group>"; };
|
||||
585135361AB3C56F00882537 /* RCTTestRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTestRunner.m; sourceTree = "<group>"; };
|
||||
58E64FE41AB964CD007446E2 /* FBSnapshotTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSnapshotTestCase.h; sourceTree = "<group>"; };
|
||||
58E64FE51AB964CD007446E2 /* FBSnapshotTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSnapshotTestCase.m; sourceTree = "<group>"; };
|
||||
58E64FE61AB964CD007446E2 /* FBSnapshotTestController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSnapshotTestController.h; sourceTree = "<group>"; };
|
||||
58E64FE71AB964CD007446E2 /* FBSnapshotTestController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSnapshotTestController.m; sourceTree = "<group>"; };
|
||||
58E64FE81AB964CD007446E2 /* UIImage+Compare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Compare.h"; sourceTree = "<group>"; };
|
||||
58E64FE91AB964CD007446E2 /* UIImage+Compare.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Compare.m"; sourceTree = "<group>"; };
|
||||
58E64FEA1AB964CD007446E2 /* UIImage+Diff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Diff.h"; sourceTree = "<group>"; };
|
||||
58E64FEB1AB964CD007446E2 /* UIImage+Diff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Diff.m"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -51,6 +62,7 @@
|
||||
585135341AB3C56F00882537 /* RCTTestModule.m */,
|
||||
585135351AB3C56F00882537 /* RCTTestRunner.h */,
|
||||
585135361AB3C56F00882537 /* RCTTestRunner.m */,
|
||||
58E64FE31AB964CD007446E2 /* FBSnapshotTestCase */,
|
||||
580C37701AB104AF0015E709 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -63,6 +75,21 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58E64FE31AB964CD007446E2 /* FBSnapshotTestCase */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
58E64FE41AB964CD007446E2 /* FBSnapshotTestCase.h */,
|
||||
58E64FE51AB964CD007446E2 /* FBSnapshotTestCase.m */,
|
||||
58E64FE61AB964CD007446E2 /* FBSnapshotTestController.h */,
|
||||
58E64FE71AB964CD007446E2 /* FBSnapshotTestController.m */,
|
||||
58E64FE81AB964CD007446E2 /* UIImage+Compare.h */,
|
||||
58E64FE91AB964CD007446E2 /* UIImage+Compare.m */,
|
||||
58E64FEA1AB964CD007446E2 /* UIImage+Diff.h */,
|
||||
58E64FEB1AB964CD007446E2 /* UIImage+Diff.m */,
|
||||
);
|
||||
path = FBSnapshotTestCase;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -119,7 +146,10 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
58E64FEE1AB964CD007446E2 /* UIImage+Compare.m in Sources */,
|
||||
585135371AB3C56F00882537 /* RCTTestModule.m in Sources */,
|
||||
58E64FEF1AB964CD007446E2 /* UIImage+Diff.m in Sources */,
|
||||
58E64FED1AB964CD007446E2 /* FBSnapshotTestController.m in Sources */,
|
||||
585135381AB3C57000882537 /* RCTTestRunner.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -7,10 +7,20 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@class FBSnapshotTestController;
|
||||
|
||||
@interface RCTTestModule : NSObject <RCTBridgeModule>
|
||||
|
||||
// This is typically polled while running the runloop until true
|
||||
@property (nonatomic, readonly, getter=isDone) BOOL done;
|
||||
|
||||
// This is used to give meaningful names to snapshot image files.
|
||||
@property (nonatomic, assign) SEL testSelector;
|
||||
|
||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,7 +9,46 @@
|
||||
|
||||
#import "RCTTestModule.h"
|
||||
|
||||
@implementation RCTTestModule
|
||||
#import "FBSnapshotTestController.h"
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation RCTTestModule {
|
||||
__weak FBSnapshotTestController *_snapshotController;
|
||||
__weak UIView *_view;
|
||||
NSMutableDictionary *_snapshotCounter;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSnapshotController:(FBSnapshotTestController *)controller view:(UIView *)view
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_snapshotController = controller;
|
||||
_view = view;
|
||||
_snapshotCounter = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)verifySnapshot:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
if (!_snapshotController) {
|
||||
RCTLogWarn(@"No snapshot controller configured.");
|
||||
callback(@[]);
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
NSString *testName = NSStringFromSelector(_testSelector);
|
||||
_snapshotCounter[testName] = @([_snapshotCounter[testName] integerValue] + 1);
|
||||
BOOL success = [_snapshotController compareSnapshotOfView:_view
|
||||
selector:_testSelector
|
||||
identifier:[_snapshotCounter[testName] stringValue]
|
||||
error:&error];
|
||||
RCTAssert(success, @"Snapshot comparison failed: %@", error);
|
||||
callback(@[]);
|
||||
}
|
||||
|
||||
- (void)markTestCompleted
|
||||
{
|
||||
|
||||
@@ -9,13 +9,63 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Use the initRunnerForApp macro for typical usage.
|
||||
*
|
||||
* Add this to your test target's gcc preprocessor macros:
|
||||
*
|
||||
* FB_REFERENCE_IMAGE_DIR="\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
|
||||
*/
|
||||
#define initRunnerForApp(app__) [[RCTTestRunner alloc] initWithApp:(app__) referenceDir:@FB_REFERENCE_IMAGE_DIR]
|
||||
|
||||
@interface RCTTestRunner : NSObject
|
||||
|
||||
@property (nonatomic, assign) BOOL recordMode;
|
||||
@property (nonatomic, copy) NSString *script;
|
||||
|
||||
- (instancetype)initWithApp:(NSString *)app;
|
||||
- (void)runTest:(NSString *)moduleName;
|
||||
- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)expectErrorRegex;
|
||||
- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock;
|
||||
/**
|
||||
* Initialize a runner. It's recommended that you use the initRunnerForApp macro instead of calling this directly.
|
||||
*
|
||||
* @param app The path to the app bundle without suffixes, e.g. IntegrationTests/IntegrationTestsApp
|
||||
* @param referencesDir The path for snapshot references images. The initRunnerForApp macro uses
|
||||
* FB_REFERENCE_IMAGE_DIR for this automatically.
|
||||
*/
|
||||
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir;
|
||||
|
||||
/**
|
||||
* Simplest runTest function simply mounts the specified JS module with no initialProps and waits for it to call
|
||||
*
|
||||
* RCTTestModule.markTestCompleted()
|
||||
*
|
||||
* JS errors/exceptions and timeouts will fail the test. Snapshot tests call RCTTestModule.verifySnapshot whenever they
|
||||
* want to verify what has been rendered (typically via requestAnimationFrame to make sure the latest state has been
|
||||
* rendered in native.
|
||||
*
|
||||
* @param test Selector of the test, usually just `_cmd`.
|
||||
* @param moduleName Name of the JS component as registered by `AppRegistry.registerComponent` in JS.
|
||||
*/
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName;
|
||||
|
||||
/**
|
||||
* Same as runTest:, but allows for passing initialProps for providing mock data or requesting different behaviors, and
|
||||
* expectErrorRegex verifies that the error you expected was thrown.
|
||||
*
|
||||
* @param test Selector of the test, usually just `_cmd`.
|
||||
* @param moduleName Name of the JS component as registered by `AppRegistry.registerComponent` in JS.
|
||||
* @param initialProps props that are passed into the component when rendered.
|
||||
* @param expectErrorRegex A regex that must match the error thrown. If no error is thrown, the test fails.
|
||||
*/
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)expectErrorRegex;
|
||||
|
||||
/**
|
||||
* Same as runTest:, but allows for passing initialProps for providing mock data or requesting different behaviors, and
|
||||
* expectErrorBlock provides arbitrary logic for processing errors (nil will cause any error to fail the test).
|
||||
*
|
||||
* @param test Selector of the test, usually just `_cmd`.
|
||||
* @param moduleName Name of the JS component as registered by `AppRegistry.registerComponent` in JS.
|
||||
* @param initialProps props that are passed into the component when rendered.
|
||||
* @param expectErrorBlock A block that takes the error message and returns NO to fail the test.
|
||||
*/
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#import "RCTTestRunner.h"
|
||||
|
||||
#import "FBSnapshotTestController.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTTestModule.h"
|
||||
@@ -17,33 +18,55 @@
|
||||
#define TIMEOUT_SECONDS 240
|
||||
|
||||
@implementation RCTTestRunner
|
||||
|
||||
- (instancetype)initWithApp:(NSString *)app
|
||||
{
|
||||
if (self = [super init]) {
|
||||
FBSnapshotTestController *_snapshotController;
|
||||
}
|
||||
|
||||
- (instancetype)initWithApp:(NSString *)app referenceDir:(NSString *)referenceDir
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
NSString *sanitizedAppName = [app stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
|
||||
sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"];
|
||||
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName];
|
||||
_snapshotController.referenceImagesDirectory = referenceDir;
|
||||
_script = [NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)runTest:(NSString *)moduleName
|
||||
- (void)setRecordMode:(BOOL)recordMode
|
||||
{
|
||||
[self runTest:moduleName initialProps:nil expectErrorBlock:nil];
|
||||
_snapshotController.recordMode = recordMode;
|
||||
}
|
||||
|
||||
- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)errorRegex
|
||||
- (BOOL)recordMode
|
||||
{
|
||||
[self runTest:moduleName initialProps:initialProps expectErrorBlock:^BOOL(NSString *error){
|
||||
return _snapshotController.recordMode;
|
||||
}
|
||||
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName
|
||||
{
|
||||
[self runTest:test module:moduleName initialProps:nil expectErrorBlock:nil];
|
||||
}
|
||||
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)errorRegex
|
||||
{
|
||||
[self runTest:test module:moduleName initialProps:initialProps expectErrorBlock:^BOOL(NSString *error){
|
||||
return [errorRegex numberOfMatchesInString:error options:0 range:NSMakeRange(0, [error length])] > 0;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||
- (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||
{
|
||||
RCTTestModule *testModule = [[RCTTestModule alloc] init];
|
||||
RCTRootView *rootView = [[RCTRootView alloc] init];
|
||||
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
|
||||
vc.view = rootView;
|
||||
if ([vc.view isKindOfClass:[RCTRootView class]]) {
|
||||
[(RCTRootView *)vc.view invalidate]; // Make sure the normal app view doesn't interfere
|
||||
}
|
||||
vc.view = [[UIView alloc] init];
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithFrame:CGRectMake(0, 0, 320, 2000)]; // Constant size for testing on multiple devices
|
||||
RCTTestModule *testModule = [[RCTTestModule alloc] initWithSnapshotController:_snapshotController view:rootView];
|
||||
testModule.testSelector = test;
|
||||
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
|
||||
rootView.moduleProvider = ^(void){
|
||||
return @[testModule];
|
||||
};
|
||||
@@ -58,9 +81,13 @@
|
||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
|
||||
error = [[RCTRedBox sharedInstance] currentErrorMessage];
|
||||
}
|
||||
[rootView invalidate];
|
||||
[rootView removeFromSuperview];
|
||||
RCTAssert(vc.view.subviews.count == 0, @"There shouldn't be any other views: %@", vc.view);
|
||||
vc.view = nil;
|
||||
[[RCTRedBox sharedInstance] dismiss];
|
||||
if (expectErrorBlock) {
|
||||
RCTAssert(expectErrorBlock(error), @"Expected an error but got none.");
|
||||
RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched.");
|
||||
} else if (error) {
|
||||
RCTAssert(error == nil, @"RedBox error: %@", error);
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#import "SRWebSocket.h"
|
||||
|
||||
#import <Availability.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#define HAS_ICU
|
||||
#endif
|
||||
@@ -110,14 +112,19 @@ static NSString *newSHA1String(const char *bytes, size_t length) {
|
||||
assert(length >= 0);
|
||||
assert(length <= UINT32_MAX);
|
||||
CC_SHA1(bytes, (CC_LONG)length, md);
|
||||
|
||||
|
||||
NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
|
||||
|
||||
if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
||||
return [data base64EncodedStringWithOptions:0];
|
||||
|
||||
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0) \
|
||||
|| (__MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9)
|
||||
|
||||
if (![NSData instancesRespondToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
||||
return [data base64Encoding];
|
||||
}
|
||||
|
||||
return [data base64Encoding];
|
||||
#endif
|
||||
|
||||
return [data base64EncodedStringWithOptions:0];
|
||||
}
|
||||
|
||||
@implementation NSData (SRWebSocket)
|
||||
@@ -212,19 +219,19 @@ typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data);
|
||||
|
||||
@implementation SRWebSocket {
|
||||
NSInteger _webSocketVersion;
|
||||
|
||||
|
||||
NSOperationQueue *_delegateOperationQueue;
|
||||
dispatch_queue_t _delegateDispatchQueue;
|
||||
|
||||
|
||||
dispatch_queue_t _workQueue;
|
||||
NSMutableArray *_consumers;
|
||||
|
||||
NSInputStream *_inputStream;
|
||||
NSOutputStream *_outputStream;
|
||||
|
||||
|
||||
NSMutableData *_readBuffer;
|
||||
NSUInteger _readBufferOffset;
|
||||
|
||||
|
||||
NSMutableData *_outputBuffer;
|
||||
NSUInteger _outputBufferOffset;
|
||||
|
||||
@@ -233,18 +240,18 @@ typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data);
|
||||
size_t _readOpCount;
|
||||
uint32_t _currentStringScanPosition;
|
||||
NSMutableData *_currentFrameData;
|
||||
|
||||
|
||||
NSString *_closeReason;
|
||||
|
||||
|
||||
NSString *_secKey;
|
||||
|
||||
|
||||
BOOL _pinnedCertFound;
|
||||
|
||||
|
||||
uint8_t _currentReadMaskKey[4];
|
||||
size_t _currentReadMaskOffset;
|
||||
|
||||
BOOL _consumerStopped;
|
||||
|
||||
|
||||
BOOL _closeWhenFinishedWriting;
|
||||
BOOL _failed;
|
||||
|
||||
@@ -252,18 +259,18 @@ typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data);
|
||||
NSURLRequest *_urlRequest;
|
||||
|
||||
CFHTTPMessageRef _receivedHTTPHeaders;
|
||||
|
||||
|
||||
BOOL _sentClose;
|
||||
BOOL _didFail;
|
||||
int _closeCode;
|
||||
|
||||
|
||||
BOOL _isPumping;
|
||||
|
||||
|
||||
NSMutableSet *_scheduledRunloops;
|
||||
|
||||
|
||||
// We use this to retain ourselves.
|
||||
__strong SRWebSocket *_selfRetain;
|
||||
|
||||
|
||||
NSArray *_requestedProtocols;
|
||||
SRIOConsumerPool *_consumerPool;
|
||||
}
|
||||
@@ -287,12 +294,12 @@ static __strong NSData *CRLFCRLF;
|
||||
assert(request.URL);
|
||||
_url = request.URL;
|
||||
_urlRequest = request;
|
||||
|
||||
|
||||
_requestedProtocols = [protocols copy];
|
||||
|
||||
|
||||
[self _SR_commonInit];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -314,39 +321,38 @@ static __strong NSData *CRLFCRLF;
|
||||
|
||||
- (void)_SR_commonInit;
|
||||
{
|
||||
|
||||
NSString *scheme = _url.scheme.lowercaseString;
|
||||
assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);
|
||||
|
||||
|
||||
if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) {
|
||||
_secure = YES;
|
||||
}
|
||||
|
||||
|
||||
_readyState = SR_CONNECTING;
|
||||
_consumerStopped = YES;
|
||||
_webSocketVersion = 13;
|
||||
|
||||
|
||||
_workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
|
||||
// Going to set a specific on the queue so we can validate we're on the work queue
|
||||
dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
|
||||
|
||||
|
||||
_delegateDispatchQueue = dispatch_get_main_queue();
|
||||
sr_dispatch_retain(_delegateDispatchQueue);
|
||||
|
||||
|
||||
_readBuffer = [[NSMutableData alloc] init];
|
||||
_outputBuffer = [[NSMutableData alloc] init];
|
||||
|
||||
|
||||
_currentFrameData = [[NSMutableData alloc] init];
|
||||
|
||||
_consumers = [[NSMutableArray alloc] init];
|
||||
|
||||
|
||||
_consumerPool = [[SRIOConsumerPool alloc] init];
|
||||
|
||||
|
||||
_scheduledRunloops = [[NSMutableSet alloc] init];
|
||||
|
||||
|
||||
[self _initializeStreams];
|
||||
|
||||
|
||||
// default handlers
|
||||
}
|
||||
|
||||
@@ -362,15 +368,15 @@ static __strong NSData *CRLFCRLF;
|
||||
|
||||
[_inputStream close];
|
||||
[_outputStream close];
|
||||
|
||||
|
||||
sr_dispatch_release(_workQueue);
|
||||
_workQueue = NULL;
|
||||
|
||||
|
||||
if (_receivedHTTPHeaders) {
|
||||
CFRelease(_receivedHTTPHeaders);
|
||||
_receivedHTTPHeaders = NULL;
|
||||
}
|
||||
|
||||
|
||||
if (_delegateDispatchQueue) {
|
||||
sr_dispatch_release(_delegateDispatchQueue);
|
||||
_delegateDispatchQueue = NULL;
|
||||
@@ -499,17 +505,24 @@ static __strong NSData *CRLFCRLF;
|
||||
{
|
||||
SRFastLog(@"Connected");
|
||||
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1);
|
||||
|
||||
|
||||
// Set host first so it defaults
|
||||
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host));
|
||||
|
||||
|
||||
NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16];
|
||||
SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes);
|
||||
|
||||
if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
||||
_secKey = [keyBytes base64EncodedStringWithOptions:0];
|
||||
} else {
|
||||
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0) \
|
||||
|| (__MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9)
|
||||
|
||||
if (![NSData instancesRespondToSelector:@selector(base64EncodedStringWithOptions:)]) {
|
||||
_secKey = [keyBytes base64Encoding];
|
||||
} else
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
_secKey = [keyBytes base64EncodedStringWithOptions:0];
|
||||
}
|
||||
|
||||
assert([_secKey length] == 24);
|
||||
|
||||
@@ -7,12 +7,25 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule queryLayoutByID
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactIOSTagHandles = require('ReactIOSTagHandles');
|
||||
var RCTUIManager = require('NativeModules').UIManager;
|
||||
|
||||
type OnSuccessCallback = (
|
||||
left: number,
|
||||
top: number,
|
||||
width: number,
|
||||
height: number,
|
||||
pageX: number,
|
||||
pageY: number
|
||||
) => void
|
||||
|
||||
// I don't know what type error is...
|
||||
type OnErrorCallback = (error: any) => void
|
||||
|
||||
/**
|
||||
* Queries the layout of a view. The layout does not reflect the element as
|
||||
* seen by the user, rather it reflects the position within the layout system,
|
||||
@@ -32,7 +45,11 @@ var RCTUIManager = require('NativeModules').UIManager;
|
||||
* @param {function} onError `func(error)`
|
||||
* @param {function} onSuccess `func(left, top, width, height, pageX, pageY)`
|
||||
*/
|
||||
var queryLayoutByID = function(rootNodeID, onError, onSuccess) {
|
||||
var queryLayoutByID = function(
|
||||
rootNodeID: string,
|
||||
onError: OnErrorCallback,
|
||||
onSuccess: OnSuccessCallback
|
||||
): void {
|
||||
// Native bridge doesn't *yet* surface errors.
|
||||
RCTUIManager.measure(
|
||||
ReactIOSTagHandles.rootNodeIDToTag[rootNodeID],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSGlobalInteractionHandler
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -17,7 +18,7 @@ var InteractionManager = require('InteractionManager');
|
||||
var interactionHandle = null;
|
||||
|
||||
var ReactIOSGlobalInteractionHandler = {
|
||||
onChange: function(numberActiveTouches) {
|
||||
onChange: function(numberActiveTouches: number) {
|
||||
if (numberActiveTouches === 0) {
|
||||
if (interactionHandle) {
|
||||
InteractionManager.clearInteractionHandle(interactionHandle);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSGlobalResponderHandler
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -14,7 +15,7 @@ var RCTUIManager = require('NativeModules').UIManager;
|
||||
var ReactIOSTagHandles = require('ReactIOSTagHandles');
|
||||
|
||||
var ReactIOSGlobalResponderHandler = {
|
||||
onChange: function(from, to) {
|
||||
onChange: function(from: string, to: string) {
|
||||
if (to !== null) {
|
||||
RCTUIManager.setJSResponder(
|
||||
ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(to)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSMount
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -83,7 +84,10 @@ var ReactIOSMount = {
|
||||
* @param {ReactComponent} instance Instance to render.
|
||||
* @param {containerTag} containerView Handle to native view tag
|
||||
*/
|
||||
renderComponent: function(descriptor, containerTag) {
|
||||
renderComponent: function(
|
||||
descriptor: ReactComponent,
|
||||
containerTag: number
|
||||
) {
|
||||
var instance = instantiateReactComponent(descriptor);
|
||||
|
||||
if (!ReactIOSTagHandles.reactTagIsNativeTopRootID(containerTag)) {
|
||||
@@ -152,7 +156,9 @@ var ReactIOSMount = {
|
||||
* asynchronously, it's easier to just have this method be the one that calls
|
||||
* for removal of the view.
|
||||
*/
|
||||
unmountComponentAtNodeAndRemoveContainer: function(containerTag) {
|
||||
unmountComponentAtNodeAndRemoveContainer: function(
|
||||
containerTag: number
|
||||
) {
|
||||
ReactIOSMount.unmountComponentAtNode(containerTag);
|
||||
// call back into native to remove all of the subviews from this container
|
||||
RCTUIManager.removeRootView(containerTag);
|
||||
@@ -163,7 +169,7 @@ var ReactIOSMount = {
|
||||
* that has been rendered and unmounting it. There should just be one child
|
||||
* component at this time.
|
||||
*/
|
||||
unmountComponentAtNode: function(containerTag) {
|
||||
unmountComponentAtNode: function(containerTag: number): bool {
|
||||
var containerID = ReactIOSTagHandles.tagToRootNodeID[containerTag];
|
||||
|
||||
invariant(
|
||||
@@ -185,20 +191,25 @@ var ReactIOSMount = {
|
||||
* Unmounts a component and sends messages back to iOS to remove its subviews.
|
||||
*
|
||||
* @param {ReactComponent} instance React component instance.
|
||||
* @param {int} containerID ID of container we're removing from.
|
||||
* @param {string} containerID ID of container we're removing from.
|
||||
* @final
|
||||
* @internal
|
||||
* @see {ReactIOSMount.unmountComponentAtNode}
|
||||
*/
|
||||
unmountComponentFromNode: function(instance, containerID) {
|
||||
unmountComponentFromNode: function(
|
||||
instance: ReactComponent,
|
||||
containerID: string
|
||||
) {
|
||||
// call back into native to remove all of the subviews from this container
|
||||
instance.unmountComponent();
|
||||
// TODO: ReactComponent.prototype.unmountComponent is missing from Flow's
|
||||
// react lib.
|
||||
(instance: any).unmountComponent();
|
||||
var containerTag =
|
||||
ReactIOSTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID);
|
||||
RCTUIManager.removeSubviewsFromContainerWithID(containerTag);
|
||||
},
|
||||
|
||||
getNode: function(id) {
|
||||
getNode: function<T>(id: T): T {
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSNativeComponent
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -28,13 +29,20 @@ var registrationNames = ReactIOSEventEmitter.registrationNames;
|
||||
var putListener = ReactIOSEventEmitter.putListener;
|
||||
var deleteAllListeners = ReactIOSEventEmitter.deleteAllListeners;
|
||||
|
||||
type ReactIOSNativeComponentViewConfig = {
|
||||
validAttributes: Object;
|
||||
uiViewClassName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor ReactIOSNativeComponent
|
||||
* @extends ReactComponent
|
||||
* @extends ReactMultiChild
|
||||
* @param {!object} UIKit View Configuration.
|
||||
*/
|
||||
var ReactIOSNativeComponent = function(viewConfig) {
|
||||
var ReactIOSNativeComponent = function(
|
||||
viewConfig: ReactIOSNativeComponentViewConfig
|
||||
) {
|
||||
this.viewConfig = viewConfig;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSReconcileTransaction
|
||||
* @typechecks static-only
|
||||
* @flow
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSStyleAttributes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSTagHandles
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -31,7 +32,7 @@ var ReactIOSTagHandles = {
|
||||
tagsStartAt: INITIAL_TAG_COUNT,
|
||||
tagCount: INITIAL_TAG_COUNT,
|
||||
|
||||
allocateTag: function() {
|
||||
allocateTag: function(): number {
|
||||
// Skip over root IDs as those are reserved for native
|
||||
while (this.reactTagIsNativeTopRootID(ReactIOSTagHandles.tagCount)) {
|
||||
ReactIOSTagHandles.tagCount++;
|
||||
@@ -50,13 +51,18 @@ var ReactIOSTagHandles = {
|
||||
* `unmountComponent` isn't the correct time because that doesn't imply that
|
||||
* the native node has been natively unmounted.
|
||||
*/
|
||||
associateRootNodeIDWithMountedNodeHandle: function(rootNodeID, tag) {
|
||||
associateRootNodeIDWithMountedNodeHandle: function(
|
||||
rootNodeID: ?string,
|
||||
tag: ?number
|
||||
) {
|
||||
warning(rootNodeID && tag, 'Root node or tag is null when associating');
|
||||
ReactIOSTagHandles.tagToRootNodeID[tag] = rootNodeID;
|
||||
ReactIOSTagHandles.rootNodeIDToTag[rootNodeID] = tag;
|
||||
if (rootNodeID && tag) {
|
||||
ReactIOSTagHandles.tagToRootNodeID[tag] = rootNodeID;
|
||||
ReactIOSTagHandles.rootNodeIDToTag[rootNodeID] = tag;
|
||||
}
|
||||
},
|
||||
|
||||
allocateRootNodeIDForTag: function(tag) {
|
||||
allocateRootNodeIDForTag: function(tag: number): string {
|
||||
invariant(
|
||||
this.reactTagIsNativeTopRootID(tag),
|
||||
'Expect a native root tag, instead got ', tag
|
||||
@@ -64,7 +70,7 @@ var ReactIOSTagHandles = {
|
||||
return '.r[' + tag + ']{TOP_LEVEL}';
|
||||
},
|
||||
|
||||
reactTagIsNativeTopRootID: function(reactTag) {
|
||||
reactTagIsNativeTopRootID: function(reactTag: number): bool {
|
||||
// We reserve all tags that are 1 mod 10 for native root views
|
||||
return reactTag % 10 === 1;
|
||||
},
|
||||
@@ -81,13 +87,15 @@ var ReactIOSTagHandles = {
|
||||
* @return {number} Tag ID of native view for most recent mounting of
|
||||
* `rootNodeID`.
|
||||
*/
|
||||
mostRecentMountedNodeHandleForRootNodeID: function(rootNodeID) {
|
||||
mostRecentMountedNodeHandleForRootNodeID: function(
|
||||
rootNodeID: string
|
||||
): number {
|
||||
return ReactIOSTagHandles.rootNodeIDToTag[rootNodeID];
|
||||
},
|
||||
|
||||
tagToRootNodeID: [],
|
||||
tagToRootNodeID: ([] : Array<string>),
|
||||
|
||||
rootNodeIDToTag: {}
|
||||
rootNodeIDToTag: ({} : {[key: string]: number})
|
||||
};
|
||||
|
||||
module.exports = ReactIOSTagHandles;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactIOSViewAttributes
|
||||
* @flow
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule renderApplication
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -14,7 +15,11 @@ var React = require('React');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
function renderApplication(RootComponent, initialProps, rootTag) {
|
||||
function renderApplication<D, P, S>(
|
||||
RootComponent: ReactClass<D, P, S>,
|
||||
initialProps: P,
|
||||
rootTag: any
|
||||
) {
|
||||
invariant(
|
||||
rootTag,
|
||||
'Expect to have a valid rootTag, instead got ', rootTag
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule EdgeInsetsPropType
|
||||
* @flow
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule LayoutPropTypes
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule PointPropType
|
||||
* @flow
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule StyleSheet
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -58,7 +59,7 @@ var StyleSheetValidation = require('StyleSheetValidation');
|
||||
* subsequent uses are going to refer an id (not implemented yet).
|
||||
*/
|
||||
class StyleSheet {
|
||||
static create(obj) {
|
||||
static create(obj: {[key: string]: any}): {[key: string]: number} {
|
||||
var result = {};
|
||||
for (var key in obj) {
|
||||
StyleSheetValidation.validateStyle(key, obj);
|
||||
|
||||
@@ -7,15 +7,18 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule StyleSheetPropType
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
|
||||
function StyleSheetPropType(shape) {
|
||||
function StyleSheetPropType(
|
||||
shape: {[key: string]: ReactPropsCheckType}
|
||||
): ReactPropsCheckType {
|
||||
var shapePropType = createStrictShapeTypeChecker(shape);
|
||||
return function(props, propName, componentName, location) {
|
||||
return function(props, propName, componentName, location?) {
|
||||
var newProps = props;
|
||||
if (props[propName]) {
|
||||
// Just make a dummy prop object with only the flattened style
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule StyleSheetRegistry
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -15,7 +16,7 @@ var uniqueID = 1;
|
||||
var emptyStyle = {};
|
||||
|
||||
class StyleSheetRegistry {
|
||||
static registerStyle(style) {
|
||||
static registerStyle(style: Object): number {
|
||||
var id = ++uniqueID;
|
||||
if (__DEV__) {
|
||||
Object.freeze(style);
|
||||
@@ -24,7 +25,7 @@ class StyleSheetRegistry {
|
||||
return id;
|
||||
}
|
||||
|
||||
static getStyleByID(id) {
|
||||
static getStyleByID(id: number): Object {
|
||||
if (!id) {
|
||||
// Used in the style={[condition && id]} pattern,
|
||||
// we want it to be a no-op when the value is false or null
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule StyleSheetValidation
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -60,7 +61,7 @@ class StyleSheetValidation {
|
||||
}
|
||||
}
|
||||
|
||||
var styleError = function(message1, style, caller, message2) {
|
||||
var styleError = function(message1, style, caller?, message2?) {
|
||||
invariant(
|
||||
false,
|
||||
message1 + '\n' + (caller || '<<unknown>>') + ': ' +
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule flattenStyle
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var StyleSheetRegistry = require('StyleSheetRegistry');
|
||||
var invariant = require('invariant');
|
||||
var mergeIntoFast = require('mergeIntoFast');
|
||||
|
||||
type Atom = number | bool | Object | Array<?Atom>
|
||||
type StyleObj = Atom | Array<?StyleObj>
|
||||
|
||||
function getStyle(style) {
|
||||
if (typeof style === 'number') {
|
||||
return StyleSheetRegistry.getStyleByID(style);
|
||||
@@ -20,10 +25,14 @@ function getStyle(style) {
|
||||
return style;
|
||||
}
|
||||
|
||||
function flattenStyle(style) {
|
||||
// TODO: Flow 0.7.0 doesn't refine bools properly so we have to use `any` to
|
||||
// tell it that this can't be a bool anymore. Should be fixed in 0.8.0,
|
||||
// after which this can take a ?StyleObj.
|
||||
function flattenStyle(style: any): ?Object {
|
||||
if (!style) {
|
||||
return undefined;
|
||||
}
|
||||
invariant(style !== true, 'style may be false but not true');
|
||||
|
||||
if (!Array.isArray(style)) {
|
||||
return getStyle(style);
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule styleDiffer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
|
||||
function styleDiffer(a, b) {
|
||||
function styleDiffer(a: any, b: any): bool {
|
||||
return !styleEqual(a, b);
|
||||
}
|
||||
|
||||
function styleEqual(a, b) {
|
||||
function styleEqual(a: any, b: any): bool {
|
||||
if (!a) {
|
||||
return !b;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,4 @@
|
||||
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
|
||||
@property (nonatomic, assign) NSUInteger numberOfLines;
|
||||
|
||||
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule Text
|
||||
* @typechecks static-only
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
@@ -102,7 +102,7 @@ var Text = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
onStartShouldSetResponder: function() {
|
||||
onStartShouldSetResponder: function(): bool {
|
||||
var shouldSetFromProps = this.props.onStartShouldSetResponder &&
|
||||
this.props.onStartShouldSetResponder();
|
||||
return shouldSetFromProps || !!this.props.onPress;
|
||||
@@ -111,7 +111,7 @@ var Text = React.createClass({
|
||||
/*
|
||||
* Returns true to allow responder termination
|
||||
*/
|
||||
handleResponderTerminationRequest: function() {
|
||||
handleResponderTerminationRequest: function(): bool {
|
||||
// Allow touchable or props.onResponderTerminationRequest to deny
|
||||
// the request
|
||||
var allowTermination = this.touchableHandleResponderTerminationRequest();
|
||||
@@ -121,25 +121,25 @@ var Text = React.createClass({
|
||||
return allowTermination;
|
||||
},
|
||||
|
||||
handleResponderGrant: function(e, dispatchID) {
|
||||
handleResponderGrant: function(e: SyntheticEvent, dispatchID: string) {
|
||||
this.touchableHandleResponderGrant(e, dispatchID);
|
||||
this.props.onResponderGrant &&
|
||||
this.props.onResponderGrant.apply(this, arguments);
|
||||
},
|
||||
|
||||
handleResponderMove: function(e) {
|
||||
handleResponderMove: function(e: SyntheticEvent) {
|
||||
this.touchableHandleResponderMove(e);
|
||||
this.props.onResponderMove &&
|
||||
this.props.onResponderMove.apply(this, arguments);
|
||||
},
|
||||
|
||||
handleResponderRelease: function(e) {
|
||||
handleResponderRelease: function(e: SyntheticEvent) {
|
||||
this.touchableHandleResponderRelease(e);
|
||||
this.props.onResponderRelease &&
|
||||
this.props.onResponderRelease.apply(this, arguments);
|
||||
},
|
||||
|
||||
handleResponderTerminate: function(e) {
|
||||
handleResponderTerminate: function(e: SyntheticEvent) {
|
||||
this.touchableHandleResponderTerminate(e);
|
||||
this.props.onResponderTerminate &&
|
||||
this.props.onResponderTerminate.apply(this, arguments);
|
||||
@@ -167,7 +167,7 @@ var Text = React.createClass({
|
||||
this.props.onPress && this.props.onPress();
|
||||
},
|
||||
|
||||
touchableGetPressRectOffset: function() {
|
||||
touchableGetPressRectOffset: function(): RectOffset {
|
||||
return PRESS_RECT_OFFSET;
|
||||
},
|
||||
|
||||
@@ -193,6 +193,13 @@ var Text = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
type RectOffset = {
|
||||
top: number;
|
||||
left: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
var RCTText = createReactIOSNativeComponentClass(viewConfig);
|
||||
|
||||
16
Libraries/Utilities/Backstack.ios.js
Normal file
16
Libraries/Utilities/Backstack.ios.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* To lower the risk of breaking things on iOS, we are stubbing out the
|
||||
* BackStack for now. See Backstack.android.js
|
||||
*
|
||||
* @providesModule Backstack
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var Backstack = {
|
||||
pushNavigation: () => {},
|
||||
resetToBefore: () => {},
|
||||
removeComponentHistory: () => {},
|
||||
};
|
||||
|
||||
module.exports = Backstack;
|
||||
62
Libraries/Utilities/CSSVarConfig.js
Normal file
62
Libraries/Utilities/CSSVarConfig.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule CSSVarConfig
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// this a partial list of the contants in CSSConstants:: from PHP that are applicable to mobile
|
||||
|
||||
module.exports = {
|
||||
'fbui-accent-blue': '#5890ff',
|
||||
'fbui-blue-90': '#4e69a2',
|
||||
'fbui-blue-80': '#627aad',
|
||||
'fbui-blue-70': '#758ab7',
|
||||
'fbui-blue-60': '#899bc1',
|
||||
'fbui-blue-50': '#9daccb',
|
||||
'fbui-blue-40': '#b1bdd6',
|
||||
'fbui-blue-30': '#c4cde0',
|
||||
'fbui-blue-20': '#d8deea',
|
||||
'fbui-blue-10': '#ebeef4',
|
||||
'fbui-blue-5': '#f5f7fa',
|
||||
'fbui-blue-2': '#fbfcfd',
|
||||
'fbui-blueblack-90': '#06090f',
|
||||
'fbui-blueblack-80': '#0c121e',
|
||||
'fbui-blueblack-70': '#121b2e',
|
||||
'fbui-blueblack-60': '#18243d',
|
||||
'fbui-blueblack-50': '#1e2d4c',
|
||||
'fbui-blueblack-40': '#23355b',
|
||||
'fbui-blueblack-30': '#293e6b',
|
||||
'fbui-blueblack-20': '#2f477a',
|
||||
'fbui-blueblack-10': '#355089',
|
||||
'fbui-blueblack-5': '#385490',
|
||||
'fbui-blueblack-2': '#3a5795',
|
||||
'fbui-bluegray-90': '#080a10',
|
||||
'fbui-bluegray-80': '#141823',
|
||||
'fbui-bluegray-70': '#232937',
|
||||
'fbui-bluegray-60': '#373e4d',
|
||||
'fbui-bluegray-50': '#4e5665',
|
||||
'fbui-bluegray-40': '#6a7180',
|
||||
'fbui-bluegray-30': '#9197a3',
|
||||
'fbui-bluegray-20': '#bdc1c9',
|
||||
'fbui-bluegray-10': '#dcdee3',
|
||||
'fbui-bluegray-5': '#e9eaed',
|
||||
'fbui-bluegray-2': '#f6f7f8',
|
||||
'fbui-gray-90': '#191919',
|
||||
'fbui-gray-80': '#333333',
|
||||
'fbui-gray-70': '#4c4c4c',
|
||||
'fbui-gray-60': '#666666',
|
||||
'fbui-gray-50': '#7f7f7f',
|
||||
'fbui-gray-40': '#999999',
|
||||
'fbui-gray-30': '#b2b2b2',
|
||||
'fbui-gray-20': '#cccccc',
|
||||
'fbui-gray-10': '#e5e5e5',
|
||||
'fbui-gray-5': '#f2f2f2',
|
||||
'fbui-gray-2': '#fafafa',
|
||||
'fbui-red': '#da2929',
|
||||
'fbui-error': '#ce0d24',
|
||||
'x-mobile-dark-text': '#4e5665',
|
||||
'x-mobile-medium-text': '#6a7180',
|
||||
'x-mobile-light-text': '#9197a3',
|
||||
'x-mobile-base-wash': '#dcdee3',
|
||||
};
|
||||
@@ -1,109 +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 TimerMixin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var setImmediate = require('setImmediate');
|
||||
var clearImmediate = require('clearImmediate');
|
||||
|
||||
/**
|
||||
* Using bare setTimeout, setInterval, setImmediate and
|
||||
* requestAnimationFrame calls is very dangerous because if you forget to cancel
|
||||
* the request before the component is unmounted, you risk the callback throwing
|
||||
* an exception.
|
||||
*
|
||||
* If you include TimerMixin, then you can replace your calls
|
||||
* to `setTimeout(fn, 500)`
|
||||
* with `this.setTimeout(fn, 500)` (just prepend `this.`)
|
||||
* and everything will be properly cleaned up for you.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var Component = React.createClass({
|
||||
* mixins: [TimerMixin],
|
||||
* componentDidMount: function() {
|
||||
* this.setTimeout(
|
||||
* () => { console.log('I do not leak!'); },
|
||||
* 500
|
||||
* );
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
|
||||
var setter = function(setter, clearer, array) {
|
||||
return function(
|
||||
callback: () => void,
|
||||
delta: number
|
||||
): number {
|
||||
var id = setter(() => {
|
||||
clearer.call(this, id);
|
||||
callback.apply(this, arguments);
|
||||
}, delta);
|
||||
|
||||
if (!this[array]) {
|
||||
this[array] = [id];
|
||||
} else {
|
||||
this[array].push(id);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
};
|
||||
|
||||
var clearer = function(clearer, array) {
|
||||
return function(id: number) {
|
||||
if (this[array]) {
|
||||
var index = this[array].indexOf(id);
|
||||
if (index !== -1) {
|
||||
this[array].splice(index, 1);
|
||||
}
|
||||
}
|
||||
clearer(id);
|
||||
};
|
||||
};
|
||||
|
||||
var _timeouts = 'TimerMixin_timeouts';
|
||||
var _clearTimeout = clearer(clearTimeout, _timeouts);
|
||||
var _setTimeout = setter(setTimeout, _clearTimeout, _timeouts);
|
||||
|
||||
var _intervals = 'TimerMixin_intervals';
|
||||
var _clearInterval = clearer(clearInterval, _intervals);
|
||||
var _setInterval = setter(setInterval, () => {/* noop */}, _intervals);
|
||||
|
||||
var _immediates = 'TimerMixin_immediates';
|
||||
var _clearImmediate = clearer(clearImmediate, _immediates);
|
||||
var _setImmediate = setter(setImmediate, _clearImmediate, _immediates);
|
||||
|
||||
var _rafs = 'TimerMixin_rafs';
|
||||
var _cancelAnimationFrame = clearer(window.cancelAnimationFrame, _rafs);
|
||||
var _requestAnimationFrame = setter(window.requestAnimationFrame, _cancelAnimationFrame, _rafs);
|
||||
|
||||
var TimerMixin = {
|
||||
componentWillUnmount: function() {
|
||||
this[_timeouts] && this[_timeouts].forEach(this.clearTimeout);
|
||||
this[_intervals] && this[_intervals].forEach(this.clearInterval);
|
||||
this[_immediates] && this[_immediates].forEach(this.clearImmediate);
|
||||
this[_rafs] && this[_rafs].forEach(this.cancelAnimationFrame);
|
||||
},
|
||||
|
||||
setTimeout: _setTimeout,
|
||||
clearTimeout: _clearTimeout,
|
||||
|
||||
setInterval: _setInterval,
|
||||
clearInterval: _clearInterval,
|
||||
|
||||
setImmediate: _setImmediate,
|
||||
clearImmediate: _clearImmediate,
|
||||
|
||||
requestAnimationFrame: _requestAnimationFrame,
|
||||
cancelAnimationFrame: _cancelAnimationFrame,
|
||||
};
|
||||
|
||||
module.exports = TimerMixin;
|
||||
559
Libraries/Utilities/buildStyleInterpolator.js
Normal file
559
Libraries/Utilities/buildStyleInterpolator.js
Normal file
@@ -0,0 +1,559 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule buildStyleInterpolator
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cannot "use strict" because we must use eval in this file.
|
||||
*/
|
||||
|
||||
var keyOf = require('keyOf');
|
||||
|
||||
var X_DIM = keyOf({x: null});
|
||||
var Y_DIM = keyOf({y: null});
|
||||
var Z_DIM = keyOf({z: null});
|
||||
var W_DIM = keyOf({w: null});
|
||||
|
||||
var TRANSFORM_ROTATE_NAME = keyOf({transformRotateRadians: null});
|
||||
|
||||
var ShouldAllocateReusableOperationVars = {
|
||||
transformRotateRadians: true,
|
||||
transformScale: true,
|
||||
transformTranslate: true,
|
||||
};
|
||||
|
||||
var InitialOperationField = {
|
||||
transformRotateRadians: [0, 0, 0, 1],
|
||||
transformTranslate: [0, 0, 0],
|
||||
transformScale: [1, 1, 1],
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a highly specialized animation function that may be evaluated every
|
||||
* frame. For example:
|
||||
*
|
||||
* var ToTheLeft = {
|
||||
* opacity: {
|
||||
* from: 1,
|
||||
* to: 0.7,
|
||||
* min: 0,
|
||||
* max: 1,
|
||||
* type: 'linear',
|
||||
* extrapolate: false,
|
||||
* round: 100,
|
||||
* },
|
||||
* left: {
|
||||
* from: 0,
|
||||
* to: -SCREEN_WIDTH * 0.3,
|
||||
* min: 0,
|
||||
* max: 1,
|
||||
* type: 'linear',
|
||||
* extrapolate: true,
|
||||
* round: PixelRatio.get(),
|
||||
* },
|
||||
* };
|
||||
*
|
||||
* var toTheLeft = buildStyleInterpolator(ToTheLeft);
|
||||
*
|
||||
* Would returns a specialized function of the form:
|
||||
*
|
||||
* function(result, value) {
|
||||
* var didChange = false;
|
||||
* var nextScalarVal;
|
||||
* var ratio;
|
||||
* ratio = (value - 0) / 1;
|
||||
* ratio = ratio > 1 ? 1 : (ratio < 0 ? 0 : ratio);
|
||||
* nextScalarVal = Math.round(100 * (1 * (1 - ratio) + 0.7 * ratio)) / 100;
|
||||
* if (!didChange) {
|
||||
* var prevVal = result.opacity;
|
||||
* result.opacity = nextScalarVal;
|
||||
* didChange = didChange || (nextScalarVal !== prevVal);
|
||||
* } else {
|
||||
* result.opacity = nextScalarVal;
|
||||
* }
|
||||
* ratio = (value - 0) / 1;
|
||||
* nextScalarVal = Math.round(2 * (0 * (1 - ratio) + -30 * ratio)) / 2;
|
||||
* if (!didChange) {
|
||||
* var prevVal = result.left;
|
||||
* result.left = nextScalarVal;
|
||||
* didChange = didChange || (nextScalarVal !== prevVal);
|
||||
* } else {
|
||||
* result.left = nextScalarVal;
|
||||
* }
|
||||
* return didChange;
|
||||
* }
|
||||
*/
|
||||
|
||||
var ARGUMENT_NAMES_RE = /([^\s,]+)/g;
|
||||
/**
|
||||
* This is obviously a huge hack. Proper tooling would allow actual inlining.
|
||||
* This only works in a few limited cases (where there is no function return
|
||||
* value, and the function operates mutatively on parameters).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
*
|
||||
* var inlineMe(a, b) {
|
||||
* a = b + b;
|
||||
* };
|
||||
*
|
||||
* inline(inlineMe, ['hi', 'bye']); // "hi = bye + bye;"
|
||||
*
|
||||
* @param {function} func Any simple function whos arguments can be replaced via a regex.
|
||||
* @param {array<string>} replaceWithArgs Corresponding names of variables
|
||||
* within an environment, to replace `func` args with.
|
||||
* @return {string} Resulting function body string.
|
||||
*/
|
||||
var inline = function(func, replaceWithArgs) {
|
||||
var fnStr = func.toString();
|
||||
var parameterNames = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'))
|
||||
.match(ARGUMENT_NAMES_RE) ||
|
||||
[];
|
||||
var replaceRegexStr = parameterNames.map(function(paramName) {
|
||||
return '\\b' + paramName + '\\b';
|
||||
}).join('|');
|
||||
var replaceRegex = new RegExp(replaceRegexStr, 'g');
|
||||
var fnBody = fnStr.substring(fnStr.indexOf('{') + 1, fnStr.lastIndexOf('}') - 1);
|
||||
var newFnBody = fnBody.replace(replaceRegex, function(parameterName) {
|
||||
var indexInParameterNames = parameterNames.indexOf(parameterName);
|
||||
var replacementName = replaceWithArgs[indexInParameterNames];
|
||||
return replacementName;
|
||||
});
|
||||
return newFnBody.split('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Simply a convenient way to inline functions using the function's toString
|
||||
* method.
|
||||
*/
|
||||
var MatrixOps = {
|
||||
unroll: function(matVar, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15) {
|
||||
m0 = matVar[0];
|
||||
m1 = matVar[1];
|
||||
m2 = matVar[2];
|
||||
m3 = matVar[3];
|
||||
m4 = matVar[4];
|
||||
m5 = matVar[5];
|
||||
m6 = matVar[6];
|
||||
m7 = matVar[7];
|
||||
m8 = matVar[8];
|
||||
m9 = matVar[9];
|
||||
m10 = matVar[10];
|
||||
m11 = matVar[11];
|
||||
m12 = matVar[12];
|
||||
m13 = matVar[13];
|
||||
m14 = matVar[14];
|
||||
m15 = matVar[15];
|
||||
},
|
||||
|
||||
matrixDiffers: function(retVar, matVar, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15) {
|
||||
retVar = retVar ||
|
||||
m0 !== matVar[0] ||
|
||||
m1 !== matVar[1] ||
|
||||
m2 !== matVar[2] ||
|
||||
m3 !== matVar[3] ||
|
||||
m4 !== matVar[4] ||
|
||||
m5 !== matVar[5] ||
|
||||
m6 !== matVar[6] ||
|
||||
m7 !== matVar[7] ||
|
||||
m8 !== matVar[8] ||
|
||||
m9 !== matVar[9] ||
|
||||
m10 !== matVar[10] ||
|
||||
m11 !== matVar[11] ||
|
||||
m12 !== matVar[12] ||
|
||||
m13 !== matVar[13] ||
|
||||
m14 !== matVar[14] ||
|
||||
m15 !== matVar[15];
|
||||
},
|
||||
|
||||
transformScale: function(matVar, opVar) {
|
||||
// Scaling matVar by opVar
|
||||
var x = opVar[0];
|
||||
var y = opVar[1];
|
||||
var z = opVar[2];
|
||||
matVar[0] = matVar[0] * x;
|
||||
matVar[1] = matVar[1] * x;
|
||||
matVar[2] = matVar[2] * x;
|
||||
matVar[3] = matVar[3] * x;
|
||||
matVar[4] = matVar[4] * y;
|
||||
matVar[5] = matVar[5] * y;
|
||||
matVar[6] = matVar[6] * y;
|
||||
matVar[7] = matVar[7] * y;
|
||||
matVar[8] = matVar[8] * z;
|
||||
matVar[9] = matVar[9] * z;
|
||||
matVar[10] = matVar[10] * z;
|
||||
matVar[11] = matVar[11] * z;
|
||||
matVar[12] = matVar[12];
|
||||
matVar[13] = matVar[13];
|
||||
matVar[14] = matVar[14];
|
||||
matVar[15] = matVar[15];
|
||||
},
|
||||
|
||||
/**
|
||||
* All of these matrix transforms are not general purpose utilities, and are
|
||||
* only suitable for being inlined for the use of building up interpolators.
|
||||
*/
|
||||
transformTranslate: function(matVar, opVar) {
|
||||
// Translating matVar by opVar
|
||||
var x = opVar[0];
|
||||
var y = opVar[1];
|
||||
var z = opVar[2];
|
||||
matVar[12] = matVar[0] * x + matVar[4] * y + matVar[8] * z + matVar[12];
|
||||
matVar[13] = matVar[1] * x + matVar[5] * y + matVar[9] * z + matVar[13];
|
||||
matVar[14] = matVar[2] * x + matVar[6] * y + matVar[10] * z + matVar[14];
|
||||
matVar[15] = matVar[3] * x + matVar[7] * y + matVar[11] * z + matVar[15];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {array} matVar Both the input, and the output matrix.
|
||||
* @param {quaternion specification} q Four element array describing rotation.
|
||||
*/
|
||||
transformRotateRadians: function(matVar, q) {
|
||||
// Rotating matVar by q
|
||||
var xQuat = q[0], yQuat = q[1], zQuat = q[2], wQuat = q[3];
|
||||
var x2Quat = xQuat + xQuat;
|
||||
var y2Quat = yQuat + yQuat;
|
||||
var z2Quat = zQuat + zQuat;
|
||||
var xxQuat = xQuat * x2Quat;
|
||||
var xyQuat = xQuat * y2Quat;
|
||||
var xzQuat = xQuat * z2Quat;
|
||||
var yyQuat = yQuat * y2Quat;
|
||||
var yzQuat = yQuat * z2Quat;
|
||||
var zzQuat = zQuat * z2Quat;
|
||||
var wxQuat = wQuat * x2Quat;
|
||||
var wyQuat = wQuat * y2Quat;
|
||||
var wzQuat = wQuat * z2Quat;
|
||||
// Step 1: Inlines the construction of a quaternion matrix (`quatMat`)
|
||||
var quatMat0 = 1 - (yyQuat + zzQuat);
|
||||
var quatMat1 = xyQuat + wzQuat;
|
||||
var quatMat2 = xzQuat - wyQuat;
|
||||
var quatMat4 = xyQuat - wzQuat;
|
||||
var quatMat5 = 1 - (xxQuat + zzQuat);
|
||||
var quatMat6 = yzQuat + wxQuat;
|
||||
var quatMat8 = xzQuat + wyQuat;
|
||||
var quatMat9 = yzQuat - wxQuat;
|
||||
var quatMat10 = 1 - (xxQuat + yyQuat);
|
||||
// quatMat3/7/11/12/13/14 = 0, quatMat15 = 1
|
||||
|
||||
// Step 2: Inlines multiplication, takes advantage of constant quatMat cells
|
||||
var a00 = matVar[0];
|
||||
var a01 = matVar[1];
|
||||
var a02 = matVar[2];
|
||||
var a03 = matVar[3];
|
||||
var a10 = matVar[4];
|
||||
var a11 = matVar[5];
|
||||
var a12 = matVar[6];
|
||||
var a13 = matVar[7];
|
||||
var a20 = matVar[8];
|
||||
var a21 = matVar[9];
|
||||
var a22 = matVar[10];
|
||||
var a23 = matVar[11];
|
||||
|
||||
var b0 = quatMat0, b1 = quatMat1, b2 = quatMat2;
|
||||
matVar[0] = b0 * a00 + b1 * a10 + b2 * a20;
|
||||
matVar[1] = b0 * a01 + b1 * a11 + b2 * a21;
|
||||
matVar[2] = b0 * a02 + b1 * a12 + b2 * a22;
|
||||
matVar[3] = b0 * a03 + b1 * a13 + b2 * a23;
|
||||
b0 = quatMat4; b1 = quatMat5; b2 = quatMat6;
|
||||
matVar[4] = b0 * a00 + b1 * a10 + b2 * a20;
|
||||
matVar[5] = b0 * a01 + b1 * a11 + b2 * a21;
|
||||
matVar[6] = b0 * a02 + b1 * a12 + b2 * a22;
|
||||
matVar[7] = b0 * a03 + b1 * a13 + b2 * a23;
|
||||
b0 = quatMat8; b1 = quatMat9; b2 = quatMat10;
|
||||
matVar[8] = b0 * a00 + b1 * a10 + b2 * a20;
|
||||
matVar[9] = b0 * a01 + b1 * a11 + b2 * a21;
|
||||
matVar[10] = b0 * a02 + b1 * a12 + b2 * a22;
|
||||
matVar[11] = b0 * a03 + b1 * a13 + b2 * a23;
|
||||
}
|
||||
};
|
||||
|
||||
// Optimized version of general operation applications that can be used when
|
||||
// the target matrix is known to be the identity matrix.
|
||||
var MatrixOpsInitial = {
|
||||
transformScale: function(matVar, opVar) {
|
||||
// Scaling matVar known to be identity by opVar
|
||||
matVar[0] = opVar[0];
|
||||
matVar[1] = 0;
|
||||
matVar[2] = 0;
|
||||
matVar[3] = 0;
|
||||
matVar[4] = 0;
|
||||
matVar[5] = opVar[1];
|
||||
matVar[6] = 0;
|
||||
matVar[7] = 0;
|
||||
matVar[8] = 0;
|
||||
matVar[9] = 0;
|
||||
matVar[10] = opVar[2];
|
||||
matVar[11] = 0;
|
||||
matVar[12] = 0;
|
||||
matVar[13] = 0;
|
||||
matVar[14] = 0;
|
||||
matVar[15] = 1;
|
||||
},
|
||||
|
||||
transformTranslate: function(matVar, opVar) {
|
||||
// Translating matVar known to be identity by opVar';
|
||||
matVar[0] = 1;
|
||||
matVar[1] = 0;
|
||||
matVar[2] = 0;
|
||||
matVar[3] = 0;
|
||||
matVar[4] = 0;
|
||||
matVar[5] = 1;
|
||||
matVar[6] = 0;
|
||||
matVar[7] = 0;
|
||||
matVar[8] = 0;
|
||||
matVar[9] = 0;
|
||||
matVar[10] = 1;
|
||||
matVar[11] = 0;
|
||||
matVar[12] = opVar[0];
|
||||
matVar[13] = opVar[1];
|
||||
matVar[14] = opVar[2];
|
||||
matVar[15] = 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {array} matVar Both the input, and the output matrix - assumed to be
|
||||
* identity.
|
||||
* @param {quaternion specification} q Four element array describing rotation.
|
||||
*/
|
||||
transformRotateRadians: function(matVar, q) {
|
||||
|
||||
// Rotating matVar which is known to be identity by q
|
||||
var xQuat = q[0], yQuat = q[1], zQuat = q[2], wQuat = q[3];
|
||||
var x2Quat = xQuat + xQuat;
|
||||
var y2Quat = yQuat + yQuat;
|
||||
var z2Quat = zQuat + zQuat;
|
||||
var xxQuat = xQuat * x2Quat;
|
||||
var xyQuat = xQuat * y2Quat;
|
||||
var xzQuat = xQuat * z2Quat;
|
||||
var yyQuat = yQuat * y2Quat;
|
||||
var yzQuat = yQuat * z2Quat;
|
||||
var zzQuat = zQuat * z2Quat;
|
||||
var wxQuat = wQuat * x2Quat;
|
||||
var wyQuat = wQuat * y2Quat;
|
||||
var wzQuat = wQuat * z2Quat;
|
||||
// Step 1: Inlines the construction of a quaternion matrix (`quatMat`)
|
||||
var quatMat0 = 1 - (yyQuat + zzQuat);
|
||||
var quatMat1 = xyQuat + wzQuat;
|
||||
var quatMat2 = xzQuat - wyQuat;
|
||||
var quatMat4 = xyQuat - wzQuat;
|
||||
var quatMat5 = 1 - (xxQuat + zzQuat);
|
||||
var quatMat6 = yzQuat + wxQuat;
|
||||
var quatMat8 = xzQuat + wyQuat;
|
||||
var quatMat9 = yzQuat - wxQuat;
|
||||
var quatMat10 = 1 - (xxQuat + yyQuat);
|
||||
// quatMat3/7/11/12/13/14 = 0, quatMat15 = 1
|
||||
|
||||
// Step 2: Inlines the multiplication with identity matrix.
|
||||
var b0 = quatMat0, b1 = quatMat1, b2 = quatMat2;
|
||||
matVar[0] = b0;
|
||||
matVar[1] = b1;
|
||||
matVar[2] = b2;
|
||||
matVar[3] = 0;
|
||||
b0 = quatMat4; b1 = quatMat5; b2 = quatMat6;
|
||||
matVar[4] = b0;
|
||||
matVar[5] = b1;
|
||||
matVar[6] = b2;
|
||||
matVar[7] = 0;
|
||||
b0 = quatMat8; b1 = quatMat9; b2 = quatMat10;
|
||||
matVar[8] = b0;
|
||||
matVar[9] = b1;
|
||||
matVar[10] = b2;
|
||||
matVar[11] = 0;
|
||||
matVar[12] = 0;
|
||||
matVar[13] = 0;
|
||||
matVar[14] = 0;
|
||||
matVar[15] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var setNextValAndDetectChange = function(name, tmpVarName) {
|
||||
return (
|
||||
' if (!didChange) {\n' +
|
||||
' var prevVal = result.' + name +';\n' +
|
||||
' result.' + name + ' = ' + tmpVarName + ';\n' +
|
||||
' didChange = didChange || (' + tmpVarName + ' !== prevVal);\n' +
|
||||
' } else {\n' +
|
||||
' result.' + name + ' = ' + tmpVarName + ';\n' +
|
||||
' }\n'
|
||||
);
|
||||
};
|
||||
|
||||
var computeNextValLinear = function(anim, from, to, tmpVarName) {
|
||||
var hasRoundRatio = 'round' in anim;
|
||||
var roundRatio = anim.round;
|
||||
var fn = ' ratio = (value - ' + anim.min + ') / ' + (anim.max - anim.min) + ';\n';
|
||||
if (!anim.extrapolate) {
|
||||
fn += ' ratio = ratio > 1 ? 1 : (ratio < 0 ? 0 : ratio);\n';
|
||||
}
|
||||
|
||||
var roundOpen = (hasRoundRatio ? 'Math.round(' + roundRatio + ' * ' : '' );
|
||||
var roundClose = (hasRoundRatio ? ') / ' + roundRatio : '' );
|
||||
fn +=
|
||||
' ' + tmpVarName + ' = ' +
|
||||
roundOpen +
|
||||
'(' + from + ' * (1 - ratio) + ' + to + ' * ratio)' +
|
||||
roundClose + ';\n';
|
||||
return fn;
|
||||
};
|
||||
|
||||
var computeNextValLinearScalar = function(anim) {
|
||||
return computeNextValLinear(anim, anim.from, anim.to, 'nextScalarVal');
|
||||
};
|
||||
|
||||
var computeNextValConstant = function(anim) {
|
||||
var constantExpression = JSON.stringify(anim.value);
|
||||
return ' nextScalarVal = ' + constantExpression + ';\n';
|
||||
};
|
||||
|
||||
var computeNextValStep = function(anim) {
|
||||
return (
|
||||
' nextScalarVal = value >= ' +
|
||||
(anim.threshold + ' ? ' + anim.to + ' : ' + anim.from) + ';\n'
|
||||
);
|
||||
};
|
||||
|
||||
var computeNextValIdentity = function(anim) {
|
||||
return ' nextScalarVal = value;\n';
|
||||
};
|
||||
|
||||
var operationVar = function(name) {
|
||||
return name + 'ReuseOp';
|
||||
};
|
||||
|
||||
var createReusableOperationVars = function(anims) {
|
||||
var ret = '';
|
||||
for (var name in anims) {
|
||||
if (ShouldAllocateReusableOperationVars[name]) {
|
||||
ret += 'var ' + operationVar(name) + ' = [];\n';
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
var newlines = function(statements) {
|
||||
return '\n' + statements.join('\n') + '\n';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Animation} anim Configuration entry.
|
||||
* @param {key} dimension Key to examine in `from`/`to`.
|
||||
* @param {number} index Field in operationVar to set.
|
||||
* @return {string} Code that sets the operation variable's field.
|
||||
*/
|
||||
var computeNextMatrixOperationField = function(anim, name, dimension, index) {
|
||||
var fieldAccess = operationVar(name) + '[' + index + ']';
|
||||
if (anim.from[dimension] !== undefined && anim.to[dimension] !== undefined) {
|
||||
return ' ' + anim.from[dimension] !== anim.to[dimension] ?
|
||||
computeNextValLinear(anim, anim.from[dimension], anim.to[dimension], fieldAccess) :
|
||||
fieldAccess + ' = ' + anim.from[dimension] + ';';
|
||||
} else {
|
||||
return ' ' + fieldAccess + ' = ' + InitialOperationField[name][index] + ';';
|
||||
}
|
||||
};
|
||||
|
||||
var unrolledVars = [];
|
||||
for (var varIndex = 0; varIndex < 16; varIndex++) {
|
||||
unrolledVars.push('m' + varIndex);
|
||||
}
|
||||
var setNextMatrixAndDetectChange = function(orderedMatrixOperations) {
|
||||
var fn = [
|
||||
' var transformMatrix = result.transformMatrix !== undefined ? ' +
|
||||
'result.transformMatrix : (result.transformMatrix = []);'
|
||||
];
|
||||
fn.push.apply(
|
||||
fn,
|
||||
inline(MatrixOps.unroll, ['transformMatrix'].concat(unrolledVars))
|
||||
);
|
||||
for (var i = 0; i < orderedMatrixOperations.length; i++) {
|
||||
var opName = orderedMatrixOperations[i];
|
||||
if (i === 0) {
|
||||
fn.push.apply(
|
||||
fn,
|
||||
inline(MatrixOpsInitial[opName], ['transformMatrix', operationVar(opName)])
|
||||
);
|
||||
} else {
|
||||
fn.push.apply(
|
||||
fn,
|
||||
inline(MatrixOps[opName], ['transformMatrix', operationVar(opName)])
|
||||
);
|
||||
}
|
||||
}
|
||||
fn.push.apply(
|
||||
fn,
|
||||
inline(MatrixOps.matrixDiffers, ['didChange', 'transformMatrix'].concat(unrolledVars))
|
||||
);
|
||||
return fn;
|
||||
};
|
||||
|
||||
var InterpolateMatrix = {
|
||||
transformTranslate: true,
|
||||
transformRotateRadians: true,
|
||||
transformScale: true,
|
||||
};
|
||||
|
||||
var createFunctionString = function(anims) {
|
||||
// We must track the order they appear in so transforms are applied in the
|
||||
// correct order.
|
||||
var orderedMatrixOperations = [];
|
||||
|
||||
// Wrapping function allows the final function to contain state (for
|
||||
// caching).
|
||||
var fn = 'return (function() {\n';
|
||||
fn += createReusableOperationVars(anims);
|
||||
fn += 'return function(result, value) {\n';
|
||||
fn += ' var didChange = false;\n';
|
||||
fn += ' var nextScalarVal;\n';
|
||||
fn += ' var ratio;\n';
|
||||
|
||||
for (var name in anims) {
|
||||
var anim = anims[name];
|
||||
if (anim.type === 'linear') {
|
||||
if (InterpolateMatrix[name]) {
|
||||
orderedMatrixOperations.push(name);
|
||||
var setOperations = [
|
||||
computeNextMatrixOperationField(anim, name, X_DIM, 0),
|
||||
computeNextMatrixOperationField(anim, name, Y_DIM, 1),
|
||||
computeNextMatrixOperationField(anim, name, Z_DIM, 2)
|
||||
];
|
||||
if (name === TRANSFORM_ROTATE_NAME) {
|
||||
setOperations.push(computeNextMatrixOperationField(anim, name, W_DIM, 3));
|
||||
}
|
||||
fn += newlines(setOperations);
|
||||
} else {
|
||||
fn += computeNextValLinearScalar(anim, 'nextScalarVal');
|
||||
fn += setNextValAndDetectChange(name, 'nextScalarVal');
|
||||
}
|
||||
} else if (anim.type === 'constant') {
|
||||
fn += computeNextValConstant(anim);
|
||||
fn += setNextValAndDetectChange(name, 'nextScalarVal');
|
||||
} else if (anim.type === 'step') {
|
||||
fn += computeNextValStep(anim);
|
||||
fn += setNextValAndDetectChange(name, 'nextScalarVal');
|
||||
} else if (anim.type === 'identity') {
|
||||
fn += computeNextValIdentity(anim);
|
||||
fn += setNextValAndDetectChange(name, 'nextScalarVal');
|
||||
}
|
||||
}
|
||||
if (orderedMatrixOperations.length) {
|
||||
fn += newlines(setNextMatrixAndDetectChange(orderedMatrixOperations));
|
||||
}
|
||||
fn += ' return didChange;\n';
|
||||
fn += '};\n';
|
||||
fn += '})()';
|
||||
return fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} anims Animation configuration by style property name.
|
||||
* @return {function} Function accepting style object, that mutates that style
|
||||
* object and returns a boolean describing if any update was actually applied.
|
||||
*/
|
||||
var buildStyleInterpolator = function(anims) {
|
||||
return Function(createFunctionString(anims))();
|
||||
};
|
||||
|
||||
|
||||
module.exports = buildStyleInterpolator;
|
||||
18
Libraries/Utilities/cssVar.js
Normal file
18
Libraries/Utilities/cssVar.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
||||
*
|
||||
* @providesModule cssVar
|
||||
* @typechecks
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var invariant = require('invariant');
|
||||
var CSSVarConfig = require('CSSVarConfig');
|
||||
|
||||
var cssVar = function(/*string*/ key) /*string*/ {
|
||||
invariant(CSSVarConfig[key], 'invalid css variable ' + key);
|
||||
|
||||
return CSSVarConfig[key];
|
||||
};
|
||||
|
||||
module.exports = cssVar;
|
||||
6
Libraries/react-native/react-native.js
vendored
6
Libraries/react-native/react-native.js
vendored
@@ -25,6 +25,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
||||
ListView: require('ListView'),
|
||||
MapView: require('MapView'),
|
||||
NavigatorIOS: require('NavigatorIOS'),
|
||||
JSNavigationStack: require('JSNavigationStack'),
|
||||
PickerIOS: require('PickerIOS'),
|
||||
ScrollView: require('ScrollView'),
|
||||
SliderIOS: require('SliderIOS'),
|
||||
@@ -50,9 +51,9 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
||||
NetInfo: require('NetInfo'),
|
||||
PixelRatio: require('PixelRatio'),
|
||||
PushNotificationIOS: require('PushNotificationIOS'),
|
||||
PanResponder: require('PanResponder'),
|
||||
StatusBarIOS: require('StatusBarIOS'),
|
||||
StyleSheet: require('StyleSheet'),
|
||||
TimerMixin: require('TimerMixin'),
|
||||
VibrationIOS: require('VibrationIOS'),
|
||||
|
||||
// Plugins
|
||||
@@ -60,11 +61,12 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
||||
NativeModules: require('NativeModules'),
|
||||
|
||||
addons: {
|
||||
batchedUpdates: require('ReactUpdates').batchedUpdates,
|
||||
LinkedStateMixin: require('LinkedStateMixin'),
|
||||
Perf: undefined,
|
||||
PureRenderMixin: require('ReactComponentWithPureRenderMixin'),
|
||||
TestModule: require('NativeModules').TestModule,
|
||||
TestUtils: undefined,
|
||||
batchedUpdates: require('ReactUpdates').batchedUpdates,
|
||||
cloneWithProps: require('cloneWithProps'),
|
||||
update: require('update'),
|
||||
},
|
||||
|
||||
275
Libraries/vendor/react/browser/eventPlugins/PanResponder.js
vendored
Normal file
275
Libraries/vendor/react/browser/eventPlugins/PanResponder.js
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* @providesModule PanResponder
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = require('TouchHistoryMath');
|
||||
|
||||
var currentCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
var currentCentroidYOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
|
||||
var previousCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
|
||||
var previousCentroidYOfTouchesChangedAfter =
|
||||
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
|
||||
var currentCentroidX = TouchHistoryMath.currentCentroidX;
|
||||
var currentCentroidY = TouchHistoryMath.currentCentroidY;
|
||||
|
||||
/**
|
||||
*
|
||||
* +----------------------------+ +--------------------------------+
|
||||
* | ResponderTouchHistoryStore | |TouchHistoryMath |
|
||||
* +----------------------------+ +----------+---------------------+
|
||||
* |Global store of touchHistory| |Allocation-less math util |
|
||||
* |including activeness, start | |on touch history (centroids |
|
||||
* |position, prev/cur position.| |and multitouch movement etc) |
|
||||
* | | | |
|
||||
* +----^-----------------------+ +----^---------------------------+
|
||||
* | |
|
||||
* | (records relevant history |
|
||||
* | of touches relevant for |
|
||||
* | implementing higher level |
|
||||
* | gestures) |
|
||||
* | |
|
||||
* +----+-----------------------+ +----|---------------------------+
|
||||
* | ResponderEventPlugin | | | Your App/Component |
|
||||
* +----------------------------+ +----|---------------------------+
|
||||
* |Negotiates which view gets | Low level | | High level |
|
||||
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
|
||||
* |Also records history into | touchHistory| | Pan | multitouch + |
|
||||
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
|
||||
* +----------------------------+ attached to | | | distance and |
|
||||
* each event | +---------+ velocity. |
|
||||
* | |
|
||||
* | |
|
||||
* +--------------------------------+
|
||||
*
|
||||
*
|
||||
*
|
||||
* Gesture that calculates cumulative movement over time in a way that just
|
||||
* "does the right thing" for multiple touches. The "right thing" is very
|
||||
* nuanced. When moving two touches in opposite directions, the cumulative
|
||||
* distance is zero in each dimension. When two touches move in parallel five
|
||||
* pixels in the same direction, the cumulative distance is five, not ten. If
|
||||
* two touches start, one moves five in a direction, then stops and the other
|
||||
* touch moves fives in the same direction, the cumulative distance is ten.
|
||||
*
|
||||
* This logic requires a kind of processing of time "clusters" of touch events
|
||||
* so that two touch moves that essentially occur in parallel but move every
|
||||
* other frame respectively, are considered part of the same movement.
|
||||
*
|
||||
* Explanation of some of the non-obvious fields:
|
||||
*
|
||||
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
|
||||
* invalid. If a move event has been observed, `(moveX, moveY)` is the
|
||||
* centroid of the most recently moved "cluster" of active touches.
|
||||
* (Currently all move have the same timeStamp, but later we should add some
|
||||
* threshold for what is considered to be "moving"). If a palm is
|
||||
* accidentally counted as a touch, but a finger is moving greatly, the palm
|
||||
* will move slightly, but we only want to count the single moving touch.
|
||||
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
|
||||
* responder.
|
||||
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
|
||||
* distance. Accounts for touch moves that are clustered together in time,
|
||||
* moving the same direction. Only valid when currently responder (otherwise,
|
||||
* it only represents the drag distance below the threshold).
|
||||
* - vx/vy: Velocity.
|
||||
*/
|
||||
var PanResponder = {
|
||||
_initializeGestureState: function(gestureState) {
|
||||
gestureState.moveX = 0;
|
||||
gestureState.moveY = 0;
|
||||
gestureState.x0 = 0;
|
||||
gestureState.y0 = 0;
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
gestureState.vx = 0;
|
||||
gestureState.vy = 0;
|
||||
gestureState.numberActiveTouches = 0;
|
||||
// All `gestureState` accounts for timeStamps up until:
|
||||
gestureState._accountsForMovesUpTo = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is nuanced and is necessary. It is incorrect to continuously take all
|
||||
* active *and* recently moved touches, find the centroid, and track how that
|
||||
* result changes over time. Instead, we must take all recently moved
|
||||
* touches, and calculate how the centroid has changed just for those
|
||||
* recently moved touches, and append that change to an accumulator. This is
|
||||
* to (at least) handle the case where the user is moving three fingers, and
|
||||
* then one of the fingers stops but the other two continue.
|
||||
*
|
||||
* This is very different than taking all of the recently moved touches and
|
||||
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
|
||||
* changes* in the centroid of recently moved touches.
|
||||
*
|
||||
* There is also some nuance with how we handle multiple moved touches in a
|
||||
* single event. With the way `ReactIOSEventEmitter` dispatches touches as
|
||||
* individual events, multiple touches generate two 'move' events, each of
|
||||
* them triggering `onResponderMove`. But with the way `PanResponder` works,
|
||||
* all of the gesture inference is performed on the first dispatch, since it
|
||||
* looks at all of the touches (even the ones for which there hasn't been a
|
||||
* native dispatch yet). Therefore, `PanResponder` does not call
|
||||
* `onResponderMove` passed the first dispatch. This diverges from the
|
||||
* typical responder callback pattern (without using `PanResponder`), but
|
||||
* avoids more dispatches than necessary.
|
||||
*/
|
||||
_updateGestureStateOnMove: function(gestureState, touchHistory) {
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
|
||||
touchHistory,
|
||||
gestureState._accountsForMovesUpTo
|
||||
);
|
||||
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
|
||||
touchHistory,
|
||||
gestureState._accountsForMovesUpTo
|
||||
);
|
||||
var movedAfter = gestureState._accountsForMovesUpTo;
|
||||
var prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var nextDX = gestureState.dx + (x - prevX);
|
||||
var nextDY = gestureState.dy + (y - prevY);
|
||||
|
||||
// TODO: This must be filtered intelligently.
|
||||
var dt =
|
||||
(touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo);
|
||||
gestureState.vx = (nextDX - gestureState.dx) / dt;
|
||||
gestureState.vy = (nextDY - gestureState.dy) / dt;
|
||||
|
||||
gestureState.dx = nextDX;
|
||||
gestureState.dy = nextDY;
|
||||
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} config Enhanced versions of all of the responder callbacks
|
||||
* that accept not only the typical `ResponderSyntheticEvent`, but also the
|
||||
* `PanResponder` gesture state. Simply replace the word `Responder` with
|
||||
* `PanResponder` in each of the typical `onResponder*` callbacks. For
|
||||
* example, the `config` object would look like:
|
||||
*
|
||||
* - onMoveShouldSetPanResponder: (e, gestureState) => {...}
|
||||
* - onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}
|
||||
* - onStartShouldSetPanResponder: (e, gestureState) => {...}
|
||||
* - onStartShouldSetPanResponderCapture: (e, gestureState) => {...}
|
||||
* - onPanResponderReject: (e, gestureState) => {...}
|
||||
* - onPanResponderGrant: (e, gestureState) => {...}
|
||||
* - onPanResponderStart: (e, gestureState) => {...}
|
||||
* - onPanResponderEnd: (e, gestureState) => {...}
|
||||
* - onPanResponderRelease: (e, gestureState) => {...}
|
||||
* - onPanResponderMove: (e, gestureState) => {...}
|
||||
* - onPanResponderTerminate: (e, gestureState) => {...}
|
||||
* - onPanResponderTerminationRequest: (e, gestureState) => {...}
|
||||
*
|
||||
* - In general, for events that have capture equivalents, we update the
|
||||
* gestureState once in the capture phase and can use it in the bubble phase
|
||||
* as well.
|
||||
*
|
||||
* - Be careful with onStartShould* callbacks. They only reflect updated
|
||||
* `gestureState` for start/end events that bubble/capture to the Node.
|
||||
* Once the node is the responder, you can rely on every start/end event
|
||||
* being processed by the gesture and `gestureState` being updated
|
||||
* accordingly. (numberActiveTouches) may not be totally accurate unless you
|
||||
* are the responder.
|
||||
*/
|
||||
create: function(config) {
|
||||
var gestureState = {
|
||||
// Useful for debugging
|
||||
stateID: Math.random(),
|
||||
};
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
var panHandlers = {
|
||||
onStartShouldSetResponder: function(e) {
|
||||
return config.onStartShouldSetPanResponder === undefined ? false :
|
||||
config.onStartShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onMoveShouldSetResponder: function(e) {
|
||||
return config.onMoveShouldSetPanResponder === undefined ? false :
|
||||
config.onMoveShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onStartShouldSetResponderCapture: function(e) {
|
||||
// TODO: Actually, we should reinitialize the state any time
|
||||
// touches.length increases from 0 active to > 0 active.
|
||||
if (e.nativeEvent.touches.length === 1) {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
||||
return config.onStartShouldSetPanResponderCapture !== undefined ?
|
||||
config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onMoveShouldSetResponderCapture: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
// Responder system incorrectly dispatches should* to current responder
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||
return false;
|
||||
}
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
return config.onMoveShouldSetResponderCapture ?
|
||||
config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onResponderGrant: function(e) {
|
||||
gestureState.x0 = currentCentroidX(e.touchHistory);
|
||||
gestureState.y0 = currentCentroidY(e.touchHistory);
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderReject: function(e) {
|
||||
config.onPanResponderReject && config.onPanResponderReject(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderRelease: function(e) {
|
||||
config.onPanResponderRelease && config.onPanResponderRelease(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderStart: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderStart && config.onPanResponderStart(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderMove: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
// Guard against the dispatch of two touch moves when there are two
|
||||
// simultaneously changed touches.
|
||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||
return;
|
||||
}
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
config.onPanResponderMove && config.onPanResponderMove(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderEnd: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
config.onPanResponderEnd && config.onPanResponderEnd(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminate: function(e) {
|
||||
config.onPanResponderTerminate &&
|
||||
config.onPanResponderTerminate(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminationRequest: function(e) {
|
||||
return config.onPanResponderTerminationRequest === undefined ? true :
|
||||
config.onPanResponderTerminationRequest(e, gestureState);
|
||||
},
|
||||
};
|
||||
return {panHandlers: panHandlers};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = PanResponder;
|
||||
@@ -39,6 +39,13 @@ var touchHistory = {
|
||||
mostRecentTimeStamp: 0,
|
||||
};
|
||||
|
||||
var timestampForTouch = function(touch) {
|
||||
// The legacy internal implementation provides "timeStamp", which has been
|
||||
// renamed to "timestamp". Let both work for now while we iron it out
|
||||
// TODO (evv): rename timeStamp to timestamp in internal code
|
||||
return touch.timeStamp || touch.timestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Instead of making gestures recompute filtered velocity, we could
|
||||
* include a built in velocity computation that can be reused globally.
|
||||
@@ -47,29 +54,29 @@ var touchHistory = {
|
||||
var initializeTouchData = function(touch) {
|
||||
return {
|
||||
touchActive: true,
|
||||
startTimeStamp: touch.timeStamp,
|
||||
startTimeStamp: timestampForTouch(touch),
|
||||
startPageX: touch.pageX,
|
||||
startPageY: touch.pageY,
|
||||
currentPageX: touch.pageX,
|
||||
currentPageY: touch.pageY,
|
||||
currentTimeStamp: touch.timeStamp,
|
||||
currentTimeStamp: timestampForTouch(touch),
|
||||
previousPageX: touch.pageX,
|
||||
previousPageY: touch.pageY,
|
||||
previousTimeStamp: touch.timeStamp,
|
||||
previousTimeStamp: timestampForTouch(touch),
|
||||
};
|
||||
};
|
||||
|
||||
var reinitializeTouchTrack = function(touchTrack, touch) {
|
||||
touchTrack.touchActive = true;
|
||||
touchTrack.startTimeStamp = touch.timeStamp;
|
||||
touchTrack.startTimeStamp = timestampForTouch(touch);
|
||||
touchTrack.startPageX = touch.pageX;
|
||||
touchTrack.startPageY = touch.pageY;
|
||||
touchTrack.currentPageX = touch.pageX;
|
||||
touchTrack.currentPageY = touch.pageY;
|
||||
touchTrack.currentTimeStamp = touch.timeStamp;
|
||||
touchTrack.currentTimeStamp = timestampForTouch(touch);
|
||||
touchTrack.previousPageX = touch.pageX;
|
||||
touchTrack.previousPageY = touch.pageY;
|
||||
touchTrack.previousTimeStamp = touch.timeStamp;
|
||||
touchTrack.previousTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
|
||||
var validateTouch = function(touch) {
|
||||
@@ -96,7 +103,7 @@ var recordStartTouchData = function(touch) {
|
||||
} else {
|
||||
reinitializeTouchTrack(touchTrack, touch);
|
||||
}
|
||||
touchHistory.mostRecentTimeStamp = touch.timeStamp;
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
|
||||
var recordMoveTouchData = function(touch) {
|
||||
@@ -112,8 +119,8 @@ var recordMoveTouchData = function(touch) {
|
||||
touchTrack.previousTimeStamp = touchTrack.currentTimeStamp;
|
||||
touchTrack.currentPageX = touch.pageX;
|
||||
touchTrack.currentPageY = touch.pageY;
|
||||
touchTrack.currentTimeStamp = touch.timeStamp;
|
||||
touchHistory.mostRecentTimeStamp = touch.timeStamp;
|
||||
touchTrack.currentTimeStamp = timestampForTouch(touch);
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
|
||||
var recordEndTouchData = function(touch) {
|
||||
@@ -128,9 +135,9 @@ var recordEndTouchData = function(touch) {
|
||||
touchTrack.previousTimeStamp = touchTrack.currentTimeStamp;
|
||||
touchTrack.currentPageX = touch.pageX;
|
||||
touchTrack.currentPageY = touch.pageY;
|
||||
touchTrack.currentTimeStamp = touch.timeStamp;
|
||||
touchTrack.currentTimeStamp = timestampForTouch(touch);
|
||||
touchTrack.touchActive = false;
|
||||
touchHistory.mostRecentTimeStamp = touch.timeStamp;
|
||||
touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
|
||||
};
|
||||
|
||||
var ResponderTouchHistoryStore = {
|
||||
|
||||
122
Libraries/vendor/react/browser/eventPlugins/TouchHistoryMath.js
vendored
Normal file
122
Libraries/vendor/react/browser/eventPlugins/TouchHistoryMath.js
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @providesModule TouchHistoryMath
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var TouchHistoryMath = {
|
||||
/**
|
||||
* This code is optimized and not intended to look beautiful. This allows
|
||||
* computing of touch centroids that have moved after `touchesChangedAfter`
|
||||
* timeStamp. You can compute the current centroid involving all touches
|
||||
* moves after `touchesChangedAfter`, or you can compute the previous
|
||||
* centroid of all touches that were moved after `touchesChangedAfter`.
|
||||
*
|
||||
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
|
||||
* data.
|
||||
* @param {number} touchesChangedAfter timeStamp after which moved touches
|
||||
* are considered "actively moving" - not just "active".
|
||||
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
|
||||
* @param {boolean} ofCurrent Compute current centroid for actively moving
|
||||
* touches vs. previous centroid of now actively moving touches.
|
||||
* @return {number} value of centroid in specified dimension.
|
||||
*/
|
||||
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var total = 0;
|
||||
var count = 0;
|
||||
|
||||
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
|
||||
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
|
||||
|
||||
if (oneTouchData !== null) {
|
||||
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
|
||||
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
|
||||
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
|
||||
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
|
||||
oneTouchData.previousPageY;
|
||||
count = 1;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < touchBank.length; i++) {
|
||||
var touchTrack = touchBank[i];
|
||||
if (touchTrack !== null &&
|
||||
touchTrack !== undefined &&
|
||||
touchTrack.touchActive &&
|
||||
touchTrack.currentTimeStamp >= touchesChangedAfter) {
|
||||
var toAdd; // Yuck, program temporarily in invalid state.
|
||||
if (ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.currentPageX;
|
||||
} else if (ofCurrent && !isXAxis) {
|
||||
toAdd = touchTrack.currentPageY;
|
||||
} else if (!ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.previousPageX;
|
||||
} else {
|
||||
toAdd = touchTrack.previousPageY;
|
||||
}
|
||||
total += toAdd;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
|
||||
},
|
||||
|
||||
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidX: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidY: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
noCentroid: -1,
|
||||
};
|
||||
|
||||
module.exports = TouchHistoryMath;
|
||||
35
Libraries/vendor/react/core/clamp.js
vendored
Normal file
35
Libraries/vendor/react/core/clamp.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @generated SignedSource<<ec51291ea6059cf23faa74f8644d17b1>>
|
||||
*
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
* !! This file is a check-in of a static_upstream project! !!
|
||||
* !! !!
|
||||
* !! You should not modify this file directly. Instead: !!
|
||||
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
|
||||
* !! the latest version from upstream. !!
|
||||
* !! 2) Make your changes, test them, etc. !!
|
||||
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
|
||||
* !! static_upstream. !!
|
||||
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
*
|
||||
* @providesModule clamp
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @return {number}
|
||||
*/
|
||||
function clamp(min, value, max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
module.exports = clamp;
|
||||
@@ -26,6 +26,9 @@
|
||||
+ (float)float:(id)json;
|
||||
+ (int)int:(id)json;
|
||||
|
||||
+ (int64_t)int64_t:(id)json;
|
||||
+ (uint64_t)uint64_t:(id)json;
|
||||
|
||||
+ (NSInteger)NSInteger:(id)json;
|
||||
+ (NSUInteger)NSUInteger:(id)json;
|
||||
|
||||
@@ -70,6 +73,7 @@
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json size:(id)json weight:(id)json;
|
||||
|
||||
+ (NSArray *)NSStringArray:(id)json;
|
||||
+ (NSArray *)NSURLArray:(id)json;
|
||||
+ (NSArray *)NSNumberArray:(id)json;
|
||||
+ (NSArray *)UIColorArray:(id)json;
|
||||
+ (NSArray *)CGColorArray:(id)json;
|
||||
@@ -142,6 +146,14 @@ id RCTConvertValue(id target, NSString *keypath, id json);
|
||||
#define RCT_CONVERTER(type, name, getter) \
|
||||
RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
||||
|
||||
/**
|
||||
* This macro is similar to RCT_CONVERTER, but specifically geared towards
|
||||
* numeric types. It will handle string input correctly, and provides more
|
||||
* detailed error reporting if a wrong value is passed in.
|
||||
*/
|
||||
#define RCT_NUMBER_CONVERTER(type, getter) \
|
||||
RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
|
||||
|
||||
/**
|
||||
* This macro is used for creating converters for enum types.
|
||||
*/
|
||||
@@ -177,7 +189,7 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
||||
* This macro is used for creating converter functions for structs that consist
|
||||
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
|
||||
*/
|
||||
#define RCT_CGSTRUCT_CONVERTER(type, values) \
|
||||
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
@try { \
|
||||
@@ -194,12 +206,23 @@ RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
||||
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [json[i] doubleValue]; \
|
||||
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
|
||||
} \
|
||||
} \
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) { \
|
||||
NSDictionary *aliases = _aliases; \
|
||||
if (aliases.count) { \
|
||||
json = [json mutableCopy]; \
|
||||
for (NSString *alias in aliases) { \
|
||||
NSString *key = aliases[alias]; \
|
||||
NSNumber *number = json[key]; \
|
||||
if (number) { \
|
||||
((NSMutableDictionary *)json)[key] = number; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [json[fields[i]] doubleValue]; \
|
||||
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
|
||||
} \
|
||||
} else if (json && json != [NSNull null]) { \
|
||||
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
|
||||
|
||||
@@ -13,25 +13,39 @@
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
CGFloat const RCTDefaultFontSize = 14;
|
||||
NSString *const RCTDefaultFontName = @"HelveticaNeue";
|
||||
NSString *const RCTDefaultFontWeight = @"normal";
|
||||
NSString *const RCTBoldFontWeight = @"bold";
|
||||
|
||||
@implementation RCTConvert
|
||||
|
||||
RCT_CONVERTER(BOOL, BOOL, boolValue)
|
||||
RCT_CONVERTER(double, double, doubleValue)
|
||||
RCT_CONVERTER(float, float, floatValue)
|
||||
RCT_CONVERTER(int, int, intValue)
|
||||
RCT_NUMBER_CONVERTER(double, doubleValue)
|
||||
RCT_NUMBER_CONVERTER(float, floatValue)
|
||||
RCT_NUMBER_CONVERTER(int, intValue)
|
||||
|
||||
RCT_CONVERTER(NSInteger, NSInteger, integerValue)
|
||||
RCT_CONVERTER_CUSTOM(NSUInteger, NSUInteger, [json unsignedIntegerValue])
|
||||
RCT_NUMBER_CONVERTER(int64_t, longLongValue);
|
||||
RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue);
|
||||
|
||||
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
|
||||
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
|
||||
|
||||
RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
|
||||
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
|
||||
RCT_CONVERTER(NSString *, NSString, description)
|
||||
RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
|
||||
|
||||
+ (NSNumber *)NSNumber:(id)json
|
||||
{
|
||||
if ([json isKindOfClass:[NSNumber class]]) {
|
||||
return json;
|
||||
} else if ([json isKindOfClass:[NSString class]]) {
|
||||
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
||||
NSNumber *number = [formatter numberFromString:json];
|
||||
if (!number) {
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
|
||||
}
|
||||
return number;
|
||||
} else if (json && json != [NSNull null]) {
|
||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json class]);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
@@ -47,7 +61,12 @@ RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
|
||||
}
|
||||
else if ([path length])
|
||||
{
|
||||
return [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
|
||||
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
|
||||
if ([URL isFileURL] &&![[NSFileManager defaultManager] fileExistsAtPath:[URL absoluteString]]) {
|
||||
RCTLogWarn(@"The file '%@' does not exist", URL);
|
||||
return nil;
|
||||
}
|
||||
return URL;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@@ -58,11 +77,11 @@ RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
|
||||
}
|
||||
|
||||
// JS Standard for time is milliseconds
|
||||
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[json doubleValue] / 1000.0])
|
||||
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [json doubleValue] / 1000.0)
|
||||
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[self double:json] / 1000.0])
|
||||
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
|
||||
|
||||
// JS standard for time zones is minutes.
|
||||
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[json doubleValue] * 60.0])
|
||||
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
|
||||
|
||||
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
|
||||
@"auto": @(NSTextAlignmentNatural),
|
||||
@@ -90,11 +109,12 @@ RCT_ENUM_CONVERTER(UIKeyboardType, (@{
|
||||
@"default": @(UIKeyboardTypeDefault),
|
||||
}), UIKeyboardTypeDefault, integerValue)
|
||||
|
||||
RCT_CONVERTER(CGFloat, CGFloat, doubleValue)
|
||||
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]))
|
||||
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"w", @"h"]))
|
||||
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"w", @"h"]))
|
||||
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]))
|
||||
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
|
||||
RCT_CONVERTER_CUSTOM(CGFloat, CGFloat, [self double:json])
|
||||
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
|
||||
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
|
||||
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]), nil)
|
||||
|
||||
RCT_ENUM_CONVERTER(CGLineJoin, (@{
|
||||
@"miter": @(kCGLineJoinMiter),
|
||||
@@ -113,9 +133,9 @@ RCT_CGSTRUCT_CONVERTER(CATransform3D, (@[
|
||||
@"m21", @"m22", @"m23", @"m24",
|
||||
@"m31", @"m32", @"m33", @"m34",
|
||||
@"m41", @"m42", @"m43", @"m44"
|
||||
]))
|
||||
]), nil)
|
||||
|
||||
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]))
|
||||
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]), nil)
|
||||
|
||||
+ (UIColor *)UIColor:(id)json
|
||||
{
|
||||
@@ -328,19 +348,19 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
|
||||
} else {
|
||||
|
||||
// Color array
|
||||
color = [UIColor colorWithRed:[json[0] doubleValue]
|
||||
green:[json[1] doubleValue]
|
||||
blue:[json[2] doubleValue]
|
||||
alpha:[json count] > 3 ? [json[3] doubleValue] : 1];
|
||||
color = [UIColor colorWithRed:[self double:json[0]]
|
||||
green:[self double:json[1]]
|
||||
blue:[self double:json[2]]
|
||||
alpha:[json count] > 3 ? [self double:json[3]] : 1];
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
// Color dictionary
|
||||
color = [UIColor colorWithRed:[json[@"r"] doubleValue]
|
||||
green:[json[@"g"] doubleValue]
|
||||
blue:[json[@"b"] doubleValue]
|
||||
alpha:[json[@"a"] ?: @1 doubleValue]];
|
||||
color = [UIColor colorWithRed:[self double:json[@"r"]]
|
||||
green:[self double:json[@"g"]]
|
||||
blue:[self double:json[@"b"]]
|
||||
alpha:[self double:json[@"a"] ?: @1]];
|
||||
|
||||
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
||||
|
||||
@@ -415,6 +435,11 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight
|
||||
{
|
||||
CGFloat const RCTDefaultFontSize = 14;
|
||||
NSString *const RCTDefaultFontName = @"HelveticaNeue";
|
||||
NSString *const RCTDefaultFontWeight = @"normal";
|
||||
NSString *const RCTBoldFontWeight = @"bold";
|
||||
|
||||
// Create descriptor
|
||||
UIFontDescriptor *fontDescriptor = font.fontDescriptor ?: [UIFontDescriptor fontDescriptorWithName:RCTDefaultFontName size:RCTDefaultFontSize];
|
||||
|
||||
@@ -427,7 +452,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
|
||||
// Get font family
|
||||
NSString *familyName = [self NSString:family];
|
||||
if (familyName) {
|
||||
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
|
||||
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
|
||||
font = [UIFont fontWithName:familyName size:fontDescriptor.pointSize];
|
||||
if (font) {
|
||||
// It's actually a font name, not a font family name,
|
||||
@@ -437,11 +462,14 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
|
||||
} else {
|
||||
// Not a valid font or family
|
||||
RCTLogError(@"Unrecognized font family '%@'", familyName);
|
||||
familyName = [UIFont fontWithDescriptor:fontDescriptor size:0].familyName;
|
||||
}
|
||||
} else {
|
||||
// Set font family
|
||||
fontDescriptor = [fontDescriptor fontDescriptorWithFamily:familyName];
|
||||
}
|
||||
} else {
|
||||
familyName = [UIFont fontWithDescriptor:fontDescriptor size:0].familyName;
|
||||
}
|
||||
|
||||
// Get font weight
|
||||
@@ -451,29 +479,43 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty
|
||||
static NSSet *values;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
values = [NSSet setWithObjects:@"bold", @"normal", nil];
|
||||
values = [NSSet setWithObjects:RCTDefaultFontWeight, RCTBoldFontWeight, nil];
|
||||
});
|
||||
|
||||
if (fontWeight && ![values containsObject:fontWeight]) {
|
||||
RCTLogError(@"Unrecognized font weight '%@', must be one of %@", fontWeight, values);
|
||||
fontWeight = RCTDefaultFontWeight;
|
||||
}
|
||||
|
||||
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
|
||||
// this is hacky. we are appending the string -Medium because most fonts we currently use
|
||||
// just need to have -Medium appended to get the bold we want. we're going to revamp this
|
||||
// to make it easier to know which options are available in JS. t4996115
|
||||
if ([fontWeight isEqualToString:RCTBoldFontWeight]) {
|
||||
symbolicTraits |= UIFontDescriptorTraitBold;
|
||||
} else {
|
||||
symbolicTraits &= ~UIFontDescriptorTraitBold;
|
||||
font = nil;
|
||||
for (NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) {
|
||||
if ([fontName hasSuffix:@"-Medium"]) {
|
||||
font = [UIFont fontWithName:fontName size:fontDescriptor.pointSize];
|
||||
break;
|
||||
}
|
||||
if ([fontName hasSuffix:@"-Bold"]) {
|
||||
font = [UIFont fontWithName:fontName size:fontDescriptor.pointSize];
|
||||
// But keep searching in case there's a medium option
|
||||
}
|
||||
}
|
||||
if (font) {
|
||||
fontDescriptor = font.fontDescriptor;
|
||||
}
|
||||
}
|
||||
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
|
||||
}
|
||||
|
||||
// TODO: font style
|
||||
|
||||
// Create font
|
||||
return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize];
|
||||
return [UIFont fontWithDescriptor:fontDescriptor size:0];
|
||||
}
|
||||
|
||||
RCT_ARRAY_CONVERTER(NSString)
|
||||
RCT_ARRAY_CONVERTER(NSURL)
|
||||
RCT_ARRAY_CONVERTER(NSNumber)
|
||||
RCT_ARRAY_CONVERTER(UIColor)
|
||||
|
||||
@@ -729,6 +771,9 @@ static id RCTConvertValueWithExplicitEncoding(id target, NSString *key, id json,
|
||||
@"extAlignment": ^(id val) {
|
||||
return [RCTConvert NSTextAlignment:val];
|
||||
},
|
||||
@"ritingDirection": ^(id val) {
|
||||
return [RCTConvert NSWritingDirection:val];
|
||||
},
|
||||
@"Cap": ^(id val) {
|
||||
return [RCTConvert CGLineCap:val];
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
@interface RCTRootView : UIView
|
||||
@interface RCTRootView : UIView<RCTInvalidating>
|
||||
|
||||
/**
|
||||
* The URL of the bundled application script (required).
|
||||
|
||||
@@ -29,6 +29,7 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
||||
RCTBridge *_bridge;
|
||||
RCTTouchHandler *_touchHandler;
|
||||
id<RCTJavaScriptExecutor> _executor;
|
||||
BOOL _registered;
|
||||
}
|
||||
|
||||
static Class _globalExecutorClass;
|
||||
@@ -36,7 +37,7 @@ static Class _globalExecutorClass;
|
||||
+ (void)initialize
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
// Register Cmd-R as a global refresh key
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
|
||||
@@ -106,12 +107,32 @@ static Class _globalExecutorClass;
|
||||
|
||||
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
|
||||
args:@[self.reactTag]];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return [_bridge isValid];
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
// Clear view
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
[self removeGestureRecognizer:_touchHandler];
|
||||
[_touchHandler invalidate];
|
||||
[_executor invalidate];
|
||||
|
||||
// TODO: eventually we'll want to be able to share the bridge between
|
||||
// multiple rootviews, in which case we'll need to move this elsewhere
|
||||
[_bridge invalidate];
|
||||
}
|
||||
|
||||
#pragma mark Bundle loading
|
||||
|
||||
- (void)bundleFinishedLoading:(NSError *)error
|
||||
{
|
||||
if (error != nil) {
|
||||
@@ -124,6 +145,7 @@ static Class _globalExecutorClass;
|
||||
} else {
|
||||
|
||||
[_bridge.uiManager registerRootView:self];
|
||||
_registered = YES;
|
||||
|
||||
NSString *moduleName = _moduleName ?: @"";
|
||||
NSDictionary *appParameters = @{
|
||||
@@ -137,8 +159,7 @@ static Class _globalExecutorClass;
|
||||
|
||||
- (void)loadBundle
|
||||
{
|
||||
// Clear view
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
[self invalidate];
|
||||
|
||||
if (!_scriptURL) {
|
||||
return;
|
||||
@@ -150,6 +171,8 @@ static Class _globalExecutorClass;
|
||||
[_executor invalidate];
|
||||
[_bridge invalidate];
|
||||
|
||||
_registered = NO;
|
||||
|
||||
// Choose local executor if specified, followed by global, followed by default
|
||||
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
|
||||
_bridge = [[RCTBridge alloc] initWithExecutor:_executor moduleProvider:_moduleProvider];
|
||||
@@ -209,15 +232,20 @@ static Class _globalExecutorClass;
|
||||
[self bundleFinishedLoading:error];
|
||||
return;
|
||||
}
|
||||
if (!_bridge.isValid) {
|
||||
return; // Bridge was invalidated in the meanwhile
|
||||
}
|
||||
|
||||
// Success!
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = _scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *error) {
|
||||
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *_error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self bundleFinishedLoading:error];
|
||||
if (_bridge.isValid) {
|
||||
[self bundleFinishedLoading:_error];
|
||||
}
|
||||
});
|
||||
}];
|
||||
|
||||
@@ -236,9 +264,12 @@ static Class _globalExecutorClass;
|
||||
[self loadBundle];
|
||||
}
|
||||
|
||||
- (BOOL)isReactRootView
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
return YES;
|
||||
[super layoutSubviews];
|
||||
if (_registered) {
|
||||
[_bridge.uiManager setFrame:self.frame forRootView:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
|
||||
@@ -129,13 +129,15 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
// Find closest React-managed touchable view
|
||||
UIView *targetView = touch.view;
|
||||
while (targetView) {
|
||||
if (targetView.reactTag && targetView.userInteractionEnabled) { // TODO: implement respondsToTouch: mechanism
|
||||
if (targetView.reactTag && targetView.userInteractionEnabled &&
|
||||
[targetView reactRespondsToTouch:touch]) {
|
||||
break;
|
||||
}
|
||||
targetView = targetView.superview;
|
||||
}
|
||||
|
||||
if (!targetView.reactTag || !targetView.userInteractionEnabled) {
|
||||
NSNumber *reactTag = [targetView reactTagAtPoint:[touch locationInView:targetView]];
|
||||
if (!reactTag || !targetView.userInteractionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
|
||||
// Create touch
|
||||
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:9];
|
||||
reactTouch[@"target"] = [targetView reactTagAtPoint:[touch locationInView:targetView]];
|
||||
reactTouch[@"target"] = reactTag;
|
||||
reactTouch[@"identifier"] = @(touchID);
|
||||
reactTouch[@"touches"] = [NSNull null]; // We hijack this touchObj to serve both as an event
|
||||
reactTouch[@"changedTouches"] = [NSNull null]; // and as a Touch object, so making this JIT friendly.
|
||||
@@ -248,15 +250,15 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
|
||||
if (_recordingInteractionTiming) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(touch.originatingTime),
|
||||
@"operation": @"taskOriginated",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
@"timeSeconds": @(touch.originatingTime),
|
||||
@"operation": @"taskOriginated",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(enqueueTime),
|
||||
@"operation": @"taskEnqueuedPending",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
@"timeSeconds": @(enqueueTime),
|
||||
@"operation": @"taskEnqueuedPending",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,18 +275,18 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
if (_recordingInteractionTiming) {
|
||||
for (RCTTouchEvent *touch in _pendingTouches) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"frameAlignedDispatch",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"frameAlignedDispatch",
|
||||
@"taskID": @(touch.id),
|
||||
}];
|
||||
}
|
||||
|
||||
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
|
||||
[_bridgeInteractionTiming addObject:@{
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"mainThreadDisplayLink",
|
||||
@"taskID": @([RCTTouchEvent newID]),
|
||||
}];
|
||||
@"timeSeconds": @(sender.timestamp),
|
||||
@"operation": @"mainThreadDisplayLink",
|
||||
@"taskID": @([RCTTouchEvent newID]),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,14 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
|
||||
}
|
||||
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
|
||||
if (!jsonData) {
|
||||
RCTLog(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
|
||||
jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
|
||||
if (jsonData) {
|
||||
RCTLogWarn(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
|
||||
} else {
|
||||
// If our backup conversion fails, log the issue so we can see what strings are causing this (t6452813)
|
||||
RCTLogError(@"RCTJSONParse received the following string, which could not be converted to UTF8 data: '%@'", jsonString);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
return [self initWithJavaScriptThread:javaScriptThread globalContextRef:NULL];
|
||||
}
|
||||
|
||||
- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread globalContextRef:(JSGlobalContextRef)context
|
||||
- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
|
||||
globalContextRef:(JSGlobalContextRef)context
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_javaScriptThread = javaScriptThread;
|
||||
|
||||
@@ -184,7 +184,8 @@ static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
|
||||
asGlobalObjectNamed:(NSString *)objectName
|
||||
callback:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(!_objectsToInject[objectName], @"already injected object named %@", _objectsToInject[objectName]);
|
||||
RCTAssert(!_objectsToInject[objectName],
|
||||
@"already injected object named %@", _objectsToInject[objectName]);
|
||||
_objectsToInject[objectName] = script;
|
||||
onComplete(nil);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user