Second Update from Tue 24 Mar

This commit is contained in:
Christopher Chedeau 2015-03-24 19:34:12 -07:00
parent 8068c65f12
commit ead3a740ca
121 changed files with 6808 additions and 667 deletions

View File

@ -21,6 +21,7 @@
[libs]
Libraries/react-native/react-native-interface.js
Examples/UIExplorer/ImageMocks.js
[options]
module.system=haste

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ImageCapInsetsExample
* @flow
*/
'use strict';

View File

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

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

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

View File

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

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

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

View File

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

View File

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

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

View File

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

View File

@ -15,10 +15,10 @@ var {
AlertIOS,
StyleSheet,
Text,
TimerMixin,
TouchableHighlight,
View,
} = React;
var TimerMixin = require('react-timer-mixin');
var Button = React.createClass({
render: function() {

View File

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

View File

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

View File

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

View File

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

View File

@ -25,6 +25,7 @@ var TESTS = [
require('./IntegrationTestHarnessTest'),
require('./TimersTest'),
require('./AsyncStorageTest'),
require('./SimpleSnapshotTest'),
];
TESTS.forEach(

View File

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

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

View File

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

View File

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

View File

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

View File

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

View 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 */;
}

View File

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

View File

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

View File

@ -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) + '. ' +

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule AppStateIOS
* @flow
*/
'use strict';

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule AppStateIOS
* @flow
*/
'use strict';

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

View File

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactIOSStyleAttributes
* @flow
*/
"use strict";

View File

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

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactIOSViewAttributes
* @flow
*/
"use strict";

View File

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

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule EdgeInsetsPropType
* @flow
*/
'use strict'

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule LayoutPropTypes
* @flow
*/
'use strict';

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule PointPropType
* @flow
*/
'use strict'

View File

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

View File

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

View File

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

View File

@ -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>>') + ': ' +

View File

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

View File

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

View File

@ -15,6 +15,4 @@
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
@property (nonatomic, assign) NSUInteger numberOfLines;
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
@end

View File

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

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

View 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',
};

View File

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

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

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@
#import "RCTBridge.h"
@interface RCTRootView : UIView
@interface RCTRootView : UIView<RCTInvalidating>
/**
* The URL of the bundled application script (required).

View File

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

View File

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

View File

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

View File

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

View File

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