Updates from Tue 8 Sep

This commit is contained in:
Christopher Chedeau 2015-09-08 16:54:44 -07:00
commit a9607901e2
93 changed files with 4018 additions and 772 deletions

View File

@ -36,7 +36,7 @@
* on the same Wi-Fi network.
*/
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios"];
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/2048/Game2048.bundle?platform=ios&dev=true"];
/**
* OPTION 2

View File

@ -216,7 +216,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = Facebook;
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Movies" */;
@ -358,6 +358,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = Movies;
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**";
};
@ -376,6 +377,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = Movies;
USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../Libraries/**";
};
@ -401,6 +403,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -38,6 +38,8 @@
ReferencedContainer = "container:Movies.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
@ -47,8 +49,10 @@
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
@ -66,7 +70,8 @@
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"

View File

@ -37,7 +37,7 @@
* on the same Wi-Fi network.
*/
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.includeRequire.runModule.bundle"];
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/Movies/MoviesApp.ios.bundle?platform=ios&dev=true"];
/**
* OPTION 2

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -33,6 +33,11 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
@ -47,11 +52,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View File

@ -22,7 +22,9 @@ var {
var MAX_VALUE = 200;
function getStyleFromScore(score: number): {color: string} {
import type { StyleObj } from 'StyleSheetTypes';
function getStyleFromScore(score: number): StyleObj {
if (score < 0) {
return styles.noScore;
}

View File

@ -31,7 +31,7 @@
* on the same Wi-Fi network.
*/
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle"];
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/SampleApp/index.ios.bundle?platform=ios&dev=true"];
/**
* OPTION 2

View File

@ -36,7 +36,7 @@
* on the same Wi-Fi network.
*/
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios"];
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/Examples/TicTacToe/TicTacToeApp.bundle?platform=ios&dev=true"];
/**
* OPTION 2

View File

@ -0,0 +1,230 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
Animated,
Easing,
StyleSheet,
Text,
View,
} = React;
var UIExplorerButton = require('./UIExplorerButton');
exports.framework = 'React';
exports.title = 'Animated - Examples';
exports.description = 'Animated provides a powerful ' +
'and easy-to-use API for building modern, ' +
'interactive user experiences.';
exports.examples = [
{
title: 'FadeInView',
description: 'Uses a simple timing animation to ' +
'bring opacity from 0 to 1 when the component ' +
'mounts.',
render: function() {
class FadeInView extends React.Component {
constructor(props) {
super(props);
this.state = {
fadeAnim: new Animated.Value(0), // opacity 0
};
}
componentDidMount() {
Animated.timing( // Uses easing functions
this.state.fadeAnim, // The value to drive
{
toValue: 1, // Target
duration: 2000, // Configuration
},
).start(); // Don't forget start!
}
render() {
return (
<Animated.View // Special animatable View
style={{
opacity: this.state.fadeAnim, // Binds
}}>
{this.props.children}
</Animated.View>
);
}
}
class FadeInExample extends React.Component {
constructor(props) {
super(props);
this.state = {
show: true,
};
}
render() {
return (
<View>
<UIExplorerButton onPress={() => {
this.setState((state) => (
{show: !state.show}
));
}}>
Press to {this.state.show ?
'Hide' : 'Show'}
</UIExplorerButton>
{this.state.show && <FadeInView>
<View style={styles.content}>
<Text>FadeInView</Text>
</View>
</FadeInView>}
</View>
);
}
}
return <FadeInExample />;
},
},
{
title: 'Transform Bounce',
description: 'One `Animated.Value` is driven by a ' +
'spring with custom constants and mapped to an ' +
'ordered set of transforms. Each transform has ' +
'an interpolation to convert the value into the ' +
'right range and units.',
render: function() {
this.anim = this.anim || new Animated.Value(0);
return (
<View>
<UIExplorerButton onPress={() => {
Animated.spring(this.anim, {
toValue: 0, // Returns to the start
velocity: 3, // Velocity makes it move
tension: -10, // Slow
friction: 1, // Oscillate a lot
}).start(); }}>
Press to Fling it!
</UIExplorerButton>
<Animated.View
style={[styles.content, {
transform: [ // Array order matters
{scale: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 4],
})},
{translateX: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 500],
})},
{rotate: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [
'0deg', '360deg' // 'deg' or 'rad'
],
})},
]}
]}>
<Text>Transforms!</Text>
</Animated.View>
</View>
);
},
},
{
title: 'Composite Animations with Easing',
description: 'Sequence, parallel, delay, and ' +
'stagger with different easing functions.',
render: function() {
this.anims = this.anims || [1,2,3].map(
() => new Animated.Value(0)
);
return (
<View>
<UIExplorerButton onPress={() => {
var timing = Animated.timing;
Animated.sequence([ // One after the other
timing(this.anims[0], {
toValue: 200,
easing: Easing.linear,
}),
Animated.delay(400), // Use with sequence
timing(this.anims[0], {
toValue: 0,
easing: Easing.elastic(2), // Springy
}),
Animated.delay(400),
Animated.stagger(200,
this.anims.map((anim) => timing(
anim, {toValue: 200}
)).concat(
this.anims.map((anim) => timing(
anim, {toValue: 0}
))),
),
Animated.delay(400),
Animated.parallel([
Easing.inOut(Easing.quad), // Symmetric
Easing.back(1.5), // Goes backwards first
Easing.ease // Default bezier
].map((easing, ii) => (
timing(this.anims[ii], {
toValue: 320, easing, duration: 3000,
})
))),
Animated.delay(400),
Animated.stagger(200,
this.anims.map((anim) => timing(anim, {
toValue: 0,
easing: Easing.bounce, // Like a ball
duration: 2000,
})),
),
]).start(); }}>
Press to Animate
</UIExplorerButton>
{['Composite', 'Easing', 'Animations!'].map(
(text, ii) => (
<Animated.View
style={[styles.content, {
left: this.anims[ii]
}]}>
<Text>{text}</Text>
</Animated.View>
)
)}
</View>
);
},
},
{
title: 'Continuous Interactions',
description: 'Gesture events, chaining, 2D ' +
'values, interrupting and transitioning ' +
'animations, etc.',
render: () => (
<Text>Checkout the Gratuitous Animation App!</Text>
),
}
];
var styles = StyleSheet.create({
content: {
backgroundColor: 'deepskyblue',
borderWidth: 1,
borderColor: 'dodgerblue',
padding: 20,
margin: 20,
borderRadius: 10,
alignItems: 'center',
},
});

View File

@ -325,6 +325,18 @@ exports.examples = [
);
},
},
{
title: 'Animated GIF',
render: function() {
return (
<Image
style={styles.gif}
source={{uri: 'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'}}
/>
);
},
platform: 'ios',
},
{
title: 'Cap Insets',
description:
@ -384,5 +396,9 @@ var styles = StyleSheet.create({
},
horizontal: {
flexDirection: 'row',
}
},
gif: {
flex: 1,
height: 200,
},
});

View File

@ -18,27 +18,12 @@
var React = require('react-native');
var {
AlertIOS,
StyleSheet,
Text,
TouchableHighlight,
View,
} = React;
var TimerMixin = require('react-timer-mixin');
var Button = React.createClass({
render: function() {
return (
<TouchableHighlight
onPress={this.props.onPress}
style={styles.button}
underlayColor="#eeeeee">
<Text>
{this.props.children}
</Text>
</TouchableHighlight>
);
},
});
var UIExplorerButton = require('./UIExplorerButton');
var TimerTester = React.createClass({
mixins: [TimerMixin],
@ -52,9 +37,9 @@ var TimerTester = React.createClass({
render: function() {
var args = 'fn' + (this.props.dt !== undefined ? ', ' + this.props.dt : '');
return (
<Button onPress={this._run}>
<UIExplorerButton onPress={this._run}>
Measure: {this.props.type}({args}) - {this._ii || 0}
</Button>
</UIExplorerButton>
);
},
@ -112,18 +97,6 @@ var TimerTester = React.createClass({
},
});
var styles = StyleSheet.create({
button: {
borderColor: 'gray',
borderRadius: 8,
borderWidth: 1,
padding: 10,
margin: 5,
alignItems: 'center',
justifyContent: 'center',
},
});
exports.framework = 'React';
exports.title = 'Timers, TimerMixin';
exports.description = 'The TimerMixin provides timer functions for executing ' +
@ -183,9 +156,9 @@ exports.examples = [
if (this.state.showTimer) {
var timer = [
<TimerTester ref="interval" dt={25} type="setInterval" />,
<Button onPress={() => this.refs.interval.clear() }>
<UIExplorerButton onPress={() => this.refs.interval.clear() }>
Clear interval
</Button>
</UIExplorerButton>
];
var toggleText = 'Unmount timer';
} else {
@ -195,9 +168,9 @@ exports.examples = [
return (
<View>
{timer}
<Button onPress={this._toggleTimer}>
<UIExplorerButton onPress={this._toggleTimer}>
{toggleText}
</Button>
</UIExplorerButton>
</View>
);
},

View File

@ -15,6 +15,7 @@
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; };
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; };
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; };
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
@ -96,6 +97,13 @@
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTGeolocation;
};
138DEE081B9EDDDB007F4EA5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
remoteInfo = RCTImage;
};
139FDED81B0651EA00C62182 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */;
@ -171,6 +179,7 @@
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; };
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = "<group>"; };
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -254,6 +263,7 @@
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */,
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */,
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */,
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */,
@ -281,13 +291,14 @@
isa = PBXGroup;
children = (
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */,
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */,
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */,
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */,
138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */,
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */,
134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
14DC67E71AB71876001358AB /* RCTPushNotification.xcodeproj */,
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */,
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */,
13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
@ -337,6 +348,14 @@
name = Products;
sourceTree = "<group>";
};
138DEE031B9EDDDB007F4EA5 /* Products */ = {
isa = PBXGroup;
children = (
138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */,
);
name = Products;
sourceTree = "<group>";
};
139FDECB1B0651EA00C62182 /* Products */ = {
isa = PBXGroup;
children = (
@ -624,6 +643,10 @@
ProductGroup = 134454561AAFCAAE003F0779 /* Products */;
ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */;
},
{
ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */;
ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
},
{
ProductGroup = 134A8A211AACED6A00945AAE /* Products */;
ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
@ -714,6 +737,13 @@
remoteRef = 134A8A241AACED6A00945AAE /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTCameraRoll.a;
remoteRef = 138DEE081B9EDDDB007F4EA5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
139FDED91B0651EA00C62182 /* libRCTWebSocket.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@ -59,7 +59,7 @@
* on the same Wi-Fi network.
*/
sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?dev=true"];
sourceURL = [NSURL URLWithString:@"http://localhost:8081/Examples/UIExplorer/UIExplorerApp.ios.bundle?platform=ios&dev=true"];
/**
* OPTION 2

View File

@ -0,0 +1,56 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
TouchableHighlight,
} = React;
var UIExplorerButton = React.createClass({
propTypes: {
onPress: React.PropTypes.func,
},
render: function() {
return (
<TouchableHighlight
onPress={this.props.onPress}
style={styles.button}
underlayColor="grey">
<Text>
{this.props.children}
</Text>
</TouchableHighlight>
);
},
});
var styles = StyleSheet.create({
button: {
borderColor: 'dimgray',
borderRadius: 8,
borderWidth: 1,
padding: 10,
margin: 5,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'lightgrey',
},
});
module.exports = UIExplorerButton;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -14,11 +14,17 @@
#import "RCTAssert.h"
@interface IntegrationTests : XCTestCase
#define RCT_TEST(name) \
- (void)test##name \
{ \
[_runner runTest:_cmd module:@#name]; \
}
@interface UIExplorerIntegrationTests : XCTestCase
@end
@implementation IntegrationTests
@implementation UIExplorerIntegrationTests
{
RCTTestRunner *_runner;
}
@ -36,11 +42,6 @@
#pragma mark Logic Tests
- (void)testTheTester
{
[_runner runTest:_cmd module:@"IntegrationTestHarnessTest"];
}
- (void)testTheTester_waitOneFrame
{
[_runner runTest:_cmd
@ -57,38 +58,12 @@
expectErrorRegex:@"because shouldThrow"];
}
- (void)testTimers
{
[_runner runTest:_cmd module:@"TimersTest"];
}
- (void)testAsyncStorage
{
[_runner runTest:_cmd module:@"AsyncStorageTest"];
}
- (void)DISABLED_testLayoutEvents // #7149037
{
[_runner runTest:_cmd module:@"LayoutEventsTest"];
}
- (void)testAppEvents
{
[_runner runTest:_cmd module:@"AppEventsTest"];
}
- (void)testPromises
{
[_runner runTest:_cmd module:@"PromiseTest"];
}
#pragma mark Snapshot Tests
- (void)testSimpleSnapshot
{
// If tests have changes, set recordMode = YES below and re-run
_runner.recordMode = NO;
[_runner runTest:_cmd module:@"SimpleSnapshotTest"];
}
RCT_TEST(TimersTest)
RCT_TEST(IntegrationTestHarnessTest)
RCT_TEST(AsyncStorageTest)
// RCT_TEST(LayoutEventsTest) -- Disabled: #8153468
RCT_TEST(AppEventsTest)
RCT_TEST(PromiseTest)
// RCT_TEST(SimpleSnapshotTest) -- Disabled: #8153475
@end

View File

@ -19,7 +19,7 @@ var {
Text,
View,
} = React;
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager;
var TestModule = NativeModules.TestModule;
var deepDiffer = require('deepDiffer');

View File

@ -20,7 +20,7 @@ var {
Text,
View,
} = React;
var TestModule = NativeModules.TestModule || NativeModules.SnapshotTestManager;
var TestModule = NativeModules.TestModule;
var deepDiffer = require('deepDiffer');

View File

@ -60,7 +60,8 @@ var APIS = [
require('./ActionSheetIOSExample'),
require('./AdSupportIOSExample'),
require('./AlertIOSExample'),
require('./AnimationExample/AnExApp'),
require('./AnimatedExample'),
require('./AnimatedGratuitousApp/AnExApp'),
require('./AppStateIOSExample'),
require('./AsyncStorageExample'),
require('./BorderExample'),

View File

@ -4,7 +4,7 @@ SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/$1.platform/Deve
SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk)
CERT ?= "iPhone Developer"
CERT ?= iPhone Developer
ARCHS = x86_64 arm64 armv7 i386
@ -26,13 +26,12 @@ else
cp $^
endif
/tmp/JSCProfiler:
/tmp/RCTJSCProfiler:
mkdir -p $@
.PRECIOUS: RCTJSCProfiler.ios8.dylib
RCTJSCProfiler.ios8.dylib: RCTJSCProfiler_unsigned.ios8.dylib
cp $< $@
codesign -f -s ${CERT} $@ || rm $@
codesign -f -s "${CERT}" $@
.PRECIOUS: RCTJSCProfiler_unsigned.ios8.dylib
RCTJSCProfiler_unsigned.ios8.dylib: $(patsubst %,RCTJSCProfiler_%.ios8.dylib,$(ARCHS))
@ -77,7 +76,7 @@ libyajl_%.a: download/yajl-2.1.0
.PRECIOUS: download/yajl-2.1.0
download/yajl-2.1.0: download/yajl-2.1.0.tar.gz
tar -zxvf $< -C download
tar -zxvf $< -C download > /dev/null
mkdir -p download/yajl-2.1.0/build && cd download/yajl-2.1.0/build && cmake ..
.PRECIOUS: download/yajl-2.1.0.tar.gz
@ -87,7 +86,7 @@ download/yajl-2.1.0.tar.gz:
.PRECIOUS: download/%
download/%: download/%.tar.gz
tar -zxvf $< -C `dirname $@`
tar -zxvf $< -C `dirname $@` > /dev/null
.PRECIOUS: %.tar.gz
%.tar.gz:

View File

@ -503,6 +503,12 @@ type ValueListenerCallback = (state: {value: number}) => void;
var _uniqueId = 1;
/**
* Standard value for driving animations. One `Animated.Value` can drive
* multiple properties in a synchronized fashion, but can only be driven by one
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
* or calling `setValue`) will stop any previous ones.
*/
class AnimatedValue extends AnimatedWithChildren {
_value: number;
_offset: number;
@ -526,6 +532,10 @@ class AnimatedValue extends AnimatedWithChildren {
return this._value + this._offset;
}
/**
* Directly set the value. This will stop any animations running on the value
* and udpate all the bound properties.
*/
setValue(value: number): void {
if (this._animation) {
this._animation.stop();
@ -534,15 +544,29 @@ class AnimatedValue extends AnimatedWithChildren {
this._updateValue(value);
}
/**
* Sets an offset that is applied on top of whatever value is set, whether via
* `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*/
setOffset(offset: number): void {
this._offset = offset;
}
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*/
flattenOffset(): void {
this._value += this._offset;
this._offset = 0;
}
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations or whathaveyou. This is useful because there is no way to
* syncronously read the value because it might be driven natively.
*/
addListener(callback: ValueListenerCallback): string {
var id = String(_uniqueId++);
this._listeners[id] = callback;
@ -557,6 +581,30 @@ class AnimatedValue extends AnimatedWithChildren {
this._listeners = {};
}
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*/
stopAnimation(callback?: ?(value: number) => void): void {
this.stopTracking();
this._animation && this._animation.stop();
this._animation = null;
callback && callback(this.__getValue());
}
/**
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10.
*/
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, Interpolation.create(config));
}
/**
* Typically only used internally, but could be used by a custom Animation
* class.
*/
animate(animation: Animation, callback: ?EndCallback): void {
var handle = InteractionManager.createInteractionHandle();
var previousAnimation = this._animation;
@ -576,27 +624,22 @@ class AnimatedValue extends AnimatedWithChildren {
);
}
stopAnimation(callback?: ?(value: number) => void): void {
this.stopTracking();
this._animation && this._animation.stop();
this._animation = null;
callback && callback(this.__getValue());
}
/**
* Typically only used internally.
*/
stopTracking(): void {
this._tracking && this._tracking.__detach();
this._tracking = null;
}
/**
* Typically only used internally.
*/
track(tracking: Animated): void {
this.stopTracking();
this._tracking = tracking;
}
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, Interpolation.create(config));
}
_updateValue(value: number): void {
this._value = value;
_flush(this);
@ -607,6 +650,45 @@ class AnimatedValue extends AnimatedWithChildren {
}
type ValueXYListenerCallback = (value: {x: number; y: number}) => void;
/**
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* API to normal `Animated.Value`, but multiplexed. Contains two regular
* `Animated.Value`s under the hood. Example:
*
*```javascript
* class DraggableView extends React.Component {
* constructor(props) {
* super(props);
* this.state = {
* pan: new Animated.ValueXY(), // inits to zero
* };
* this.state.panResponder = PanResponder.create({
* onStartShouldSetPanResponder: () => true,
* onPanResponderMove: Animated.event([null, {
* dx: this.state.pan.x, // x,y are Animated.Value
* dy: this.state.pan.y,
* }]),
* onPanResponderRelease: () => {
* Animated.spring(
* this.state.pan, // Auto-multiplexed
* {toValue: {x: 0, y: 0}} // Back to zero
* ).start();
* },
* });
* }
* render() {
* return (
* <Animated.View
* {...this.state.panResponder.panHandlers}
* style={this.state.pan.getLayout()}>
* {this.props.children}
* </Animated.View>
* );
* }
* }
*```
*/
class AnimatedValueXY extends AnimatedWithChildren {
x: AnimatedValue;
y: AnimatedValue;
@ -677,6 +759,13 @@ class AnimatedValueXY extends AnimatedWithChildren {
delete this._listeners[id];
}
/**
* Converts `{x, y}` into `{left, top}` for use in style, e.g.
*
*```javascript
* style={this.state.anim.getLayout()}
*```
*/
getLayout(): {[key: string]: AnimatedValue} {
return {
left: this.x,
@ -684,6 +773,15 @@ class AnimatedValueXY extends AnimatedWithChildren {
};
}
/**
* Converts `{x, y}` into a useable translation transform, e.g.
*
*```javascript
* style={{
* transform: this.state.anim.getTranslateTransform()
* }}
*```
*/
getTranslateTransform(): Array<{[key: string]: AnimatedValue}> {
return [
{translateX: this.x},
@ -1235,21 +1333,6 @@ var stagger = function(
type Mapping = {[key: string]: Mapping} | AnimatedValue;
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls setValue on the mapped outputs. e.g.
*
* onScroll={this.AnimatedEvent(
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
* {listener} // optional listener invoked asynchronously
* )
* ...
* onPanResponderMove: this.AnimatedEvent([
* null, // raw event arg
* {dx: this._panX}, // gestureState arg
* ]),
*
*/
type EventConfig = {listener?: ?Function};
var event = function(
argMapping: Array<?Mapping>,
@ -1287,23 +1370,179 @@ var event = function(
};
};
/**
* Animations are an important part of modern UX, and the `Animated`
* library is designed to make them fluid, powerful, and easy to build and
* maintain.
*
* The simplest workflow is to create an `Animated.Value`, hook it up to one or
* more style attributes of an animated component, and then drive updates either
* via animations, such as `Animated.timing`, or by hooking into gestures like
* panning or scolling via `Animated.event`. `Animated.Value` can also bind to
* props other than style, and can be interpolated as well. Here is a basic
* example of a container view that will fade in when it's mounted:
*
*```javascript
* class FadeInView extends React.Component {
* constructor(props) {
* super(props);
* this.state = {
* fadeAnim: new Animated.Value(0), // init opacity 0
* };
* }
* componentDidMount() {
* Animated.timing( // Uses easing functions
* this.state.fadeAnim, // The value to drive
* {toValue: 1}, // Configuration
* ).start(); // Don't forget start!
* }
* render() {
* return (
* <Animated.View // Special animatable View
* style={{opacity: this.state.fadeAnim}}> // Binds
* {this.props.children}
* </Animated.View>
* );
* }
* }
*```
*
* Note that only animatable components can be animated. `View`, `Text`, and
* `Image` are already provided, and you can create custom ones with
* `createAnimatedComponent`. These special components do the magic of binding
* the animated values to the properties, and do targetted native updates to
* avoid the cost of the react render and reconciliation process on every frame.
* They also handle cleanup on unmount so they are safe by default.
*
* Animations are heavily configurable. Custom and pre-defined easing
* functions, delays, durations, decay factors, spring constants, and more can
* all be tweaked depending on the type of animation.
*
* A single `Animated.Value` can drive any number of properties, and each
* property can be run through an interpolation first. An interpolation maps
* input ranges to output ranges, typically using a linear interpolation but
* also supports easing functions. By default, it will extrapolate the curve
* beyond the ranges given, but you can also have it clamp the output value.
*
* For example, you may want to think about your `Animated.Value` as going from
* 0 to 1, but animate the position from 150px to 0px and the opacity from 0 to
* 1. This can easily be done by modifying `style` in the example above like so:
*
*```javascript
* style={{
* opacity: this.state.fadeAnim, // Binds directly
* transform: [{
* translateY: this.state.fadeAnim.interpolate({
* inputRange: [0, 1],
* outputRange: [150, 0] // 0 : 150, 0.5 : 75, 1 : 0
* }),
* }],
* }}>
*```
*
* Animations can also be combined in complex ways using composition functions
* such as `sequence` and `parallel`, and can also be chained together simply
* by setting the `toValue` of one animation to be another `Animated.Value`.
*
* `Animated.ValueXY` is handy for 2D animations, like panning, and there are
* other helpful additions like `setOffset` and `getLayout` to aid with typical
* interaction patterns, like drag-and-drop.
*
* You can see more example usage in `AnimationExample.js`, the Gratuitous
* Animation App, and [Animations documentation guide](http://facebook.github.io/react-native/docs/animations.html).
*
* Note that `Animated` is designed to be fully serializable so that animations
* can be run in a high performace way, independent of the normal JavaScript
* event loop. This does influence the API, so keep that in mind when it seems a
* little trickier to do something compared to a fully synchronous system.
* Checkout `Animated.Value.addListener` as a way to work around some of these
* limitations, but use it sparingly since it might have performance
* implications in the future.
*/
module.exports = {
delay,
sequence,
parallel,
stagger,
/**
* Standard value class for driving animations. Typically initialized with
* `new Animated.Value(0);`
*/
Value: AnimatedValue,
/**
* 2D value class for driving 2D animations, such as pan gestures.
*/
ValueXY: AnimatedValueXY,
/**
* An animatable View component.
*/
View: createAnimatedComponent(View),
/**
* An animatable Text component.
*/
Text: createAnimatedComponent(Text),
/**
* An animatable Image component.
*/
Image: createAnimatedComponent(Image),
/**
* Animates a value from an initial velocity to zero based on a decay
* coefficient.
*/
decay,
/**
* Animates a value along a timed easing curve. The `Easing` module has tons
* of pre-defined curves, or you can use your own function.
*/
timing,
/**
* Spring animation based on Rebound and Origami. Tracks velocity state to
* create fluid motions as the `toValue` updates, and can be chained together.
*/
spring,
/**
* Starts an animation after the given delay.
*/
delay,
/**
* Starts an array of animations in order, waiting for each to complete
* before starting the next. If the current running animation is stopped, no
* following animations will be started.
*/
sequence,
/**
* Starts an array of animations all at the same time. By default, if one
* of the animations is stopped, they will all be stopped. You can override
* this with the `stopTogether` flag.
*/
parallel,
/**
* Array of animations may run in parallel (overlap), but are started in
* sequence with successive delays. Nice for doing trailing effects.
*/
stagger,
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls `setValue` on the mapped outputs. e.g.
*
*```javascript
* onScroll={this.AnimatedEvent(
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
* {listener}, // Optional async listener
* )
* ...
* onPanResponderMove: this.AnimatedEvent([
* null, // raw event arg ignored
* {dx: this._panX}, // gestureState arg
* ]),
*```
*/
event,
Value: AnimatedValue,
ValueXY: AnimatedValueXY,
__PropsOnlyForTests: AnimatedProps,
View: createAnimatedComponent(View),
Text: createAnimatedComponent(Text),
Image: createAnimatedComponent(Image),
/**
* Make any React component Animatable. Used to create `Animated.View`, etc.
*/
createAnimatedComponent,
__PropsOnlyForTests: AnimatedProps,
};

View File

@ -123,12 +123,18 @@ class Easing {
return easing;
}
/**
* Runs an easing function backwards.
*/
static out(
easing: (t: number) => number,
): (t: number) => number {
return (t) => 1 - easing(1 - t);
}
/**
* Makes any easing function symmetrical.
*/
static inOut(
easing: (t: number) => number,
): (t: number) => number {

View File

@ -0,0 +1,296 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B5115B1A9E6B3D00147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTCameraRoll.a; sourceTree = BUILT_PRODUCTS_DIR; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B5115A1A9E6B3D00147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
58B5115E1A9E6B3D00147676 /* Products */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
};
58B5115E1A9E6B3D00147676 /* Products */ = {
isa = PBXGroup;
children = (
58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B5115C1A9E6B3D00147676 /* RCTCameraRoll */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTCameraRoll" */;
buildPhases = (
58B511591A9E6B3D00147676 /* Sources */,
58B5115A1A9E6B3D00147676 /* Frameworks */,
58B5115B1A9E6B3D00147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RCTCameraRoll;
productName = RCTNetworkImage;
productReference = 58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511551A9E6B3D00147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B5115C1A9E6B3D00147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTCameraRoll" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 58B511541A9E6B3D00147676;
productRefGroup = 58B5115E1A9E6B3D00147676 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
58B5115C1A9E6B3D00147676 /* RCTCameraRoll */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511591A9E6B3D00147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B5116F1A9E6B3D00147676 /* 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_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
WARNING_CFLAGS = (
"-Werror",
"-Wall",
);
};
name = Debug;
};
58B511701A9E6B3D00147676 /* 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_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
WARNING_CFLAGS = (
"-Werror",
"-Wall",
);
};
name = Release;
};
58B511721A9E6B3D00147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
"$(SRCROOT)/../Image/**",
"$(SRCROOT)/../Network/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/UIExplorer-gjaibsjtheitasdxdtcvxxqavkvy/Build/Products/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTCameraRoll;
RUN_CLANG_STATIC_ANALYZER = YES;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511731A9E6B3D00147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_STATIC_ANALYZER_MODE = deep;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../React/**",
"$(SRCROOT)/../Image/**",
"$(SRCROOT)/../Network/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/UIExplorer-gjaibsjtheitasdxdtcvxxqavkvy/Build/Products/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RCTCameraRoll;
RUN_CLANG_STATIC_ANALYZER = NO;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTCameraRoll" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B5116F1A9E6B3D00147676 /* Debug */,
58B511701A9E6B3D00147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTCameraRoll" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511721A9E6B3D00147676 /* Debug */,
58B511731A9E6B3D00147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511551A9E6B3D00147676 /* Project object */;
}

View File

@ -49,6 +49,7 @@ var AndroidTextInputAttributes = {
keyboardType: true,
mostRecentEventCount: true,
multiline: true,
numberOfLines: true,
password: true,
placeholder: true,
placeholderTextColor: true,
@ -193,6 +194,12 @@ var TextInput = React.createClass({
* @platform ios
*/
maxLength: PropTypes.number,
/**
* Sets the number of lines for a TextInput. Use it with multiline set to
* true to be able to fill the lines.
* @platform android
*/
numberOfLines: PropTypes.number,
/**
* If true, the keyboard disables the return key when there is no text and
* automatically enables it when there is text. The default value is false.
@ -484,6 +491,7 @@ var TextInput = React.createClass({
keyboardType={this.props.keyboardType}
mostRecentEventCount={this.state.mostRecentEventCount}
multiline={this.props.multiline}
numberOfLines={this.props.numberOfLines}
onFocus={this._onFocus}
onBlur={this._onBlur}
onChange={this._onChange}

View File

@ -209,6 +209,8 @@ var TouchableHighlight = React.createClass({
return (
<View
accessible={true}
accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits}
ref={UNDERLAY_REF}
style={this.state.underlayStyle}
onLayout={this.props.onLayout}

View File

@ -156,6 +156,8 @@ var TouchableOpacity = React.createClass({
return (
<Animated.View
accessible={true}
accessibilityComponentType={this.props.accessibilityComponentType}
accessibilityTraits={this.props.accessibilityTraits}
style={[this.props.style, {opacity: this.state.anim}]}
testID={this.props.testID}
onLayout={this.props.onLayout}

View File

@ -14,6 +14,7 @@
var React = require('React');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('Touchable');
var View = require('View');
var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
var onlyChild = require('onlyChild');
@ -36,11 +37,16 @@ var TouchableWithoutFeedback = React.createClass({
mixins: [TimerMixin, Touchable.Mixin],
propTypes: {
accessible: React.PropTypes.bool,
accessibilityComponentType: React.PropTypes.oneOf(View.AccessibilityComponentType),
accessibilityTraits: React.PropTypes.oneOfType([
React.PropTypes.oneOf(View.AccessibilityTraits),
React.PropTypes.arrayOf(React.PropTypes.oneOf(View.AccessibilityTraits)),
]),
/**
* Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock).
*/
accessible: React.PropTypes.bool,
onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func,
@ -120,6 +126,8 @@ var TouchableWithoutFeedback = React.createClass({
// Note(avik): remove dynamic typecast once Flow has been upgraded
return (React: any).cloneElement(onlyChild(this.props.children), {
accessible: this.props.accessible !== false,
accessibilityComponentType: this.props.accessibilityComponentType,
accessibilityTraits: this.props.accessibilityTraits,
testID: this.props.testID,
onLayout: this.props.onLayout,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,

View File

@ -44,6 +44,13 @@ var AccessibilityTraits = [
'pageTurn',
];
var AccessibilityComponentType = [
'none',
'button',
'radiobutton_checked',
'radiobutton_unchecked',
];
/**
* The most fundamental component for building UI, `View` is a
* container that supports layout with flexbox, style, some touch handling, and
@ -76,6 +83,11 @@ var View = React.createClass({
validAttributes: ReactNativeViewAttributes.RCTView
},
statics: {
AccessibilityTraits,
AccessibilityComponentType,
},
propTypes: {
/**
* When true, indicates that the view is an accessibility element. By default,
@ -95,12 +107,7 @@ var View = React.createClass({
* native one. Works for Android only.
* @platform android
*/
accessibilityComponentType: PropTypes.oneOf([
'none',
'button',
'radiobutton_checked',
'radiobutton_unchecked',
]),
accessibilityComponentType: PropTypes.oneOf(AccessibilityComponentType),
/**
* Indicates to accessibility services whether the user should be notified
@ -152,7 +159,7 @@ var View = React.createClass({
* When `accessible` is true, the system will try to invoke this function
* when the user performs accessibility tap gesture.
*/
onAcccessibilityTap: PropTypes.func,
onAccessibilityTap: PropTypes.func,
/**
* When `accessible` is true, the system will invoke this function when the

View File

@ -72,7 +72,9 @@ function setupDevtools() {
}
function handleMessage(evt) {
var data;
// It's hard to handle JSON in a safe manner without inspecting it at
// runtime, hence the any
var data: any;
try {
data = JSON.parse(evt.data);
} catch (e) {

View File

@ -27,18 +27,30 @@ RCT_EXPORT_MODULE()
return !strcmp(header, "GIF87a") || !strcmp(header, "GIF89a");
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
UIImage *image = nil;
size_t imageCount = CGImageSourceGetCount(imageSource);
if (imageCount > 1) {
NSTimeInterval duration = 0;
NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
if (!image) {
image = [UIImage imageWithCGImage:imageRef];
}
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
NSDictionary *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];
@ -59,7 +71,7 @@ RCT_EXPORT_MODULE()
duration += delayTime.doubleValue;
delays[i] = delayTime;
images[i] = (__bridge_transfer id)image;
images[i] = (__bridge_transfer id)imageRef;
}
CFRelease(imageSource);
@ -72,15 +84,28 @@ RCT_EXPORT_MODULE()
[keyTimes addObject:@1.0];
// Create animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.calculationMode = kCAAnimationDiscrete;
animation.repeatCount = loopCount == 0 ? HUGE_VALF : loopCount;
animation.keyTimes = keyTimes;
animation.values = images;
animation.duration = duration;
completionHandler(nil, animation);
image.reactKeyframeAnimation = animation;
return nil;
} else {
// Don't bother creating an animation
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (imageRef) {
image = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
}
CFRelease(imageSource);
}
completionHandler(nil, image);
return ^{};
}
@end

View File

@ -11,14 +11,10 @@
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; };
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */; };
/* End PBXBuildFile section */
@ -43,10 +39,6 @@
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.m; sourceTree = "<group>"; };
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = "<group>"; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = "<group>"; };
143879371AAD32A300F088A5 /* RCTImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoader.m; sourceTree = "<group>"; };
35123E691B59C99D00EBAD80 /* RCTImageStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageStoreManager.h; sourceTree = "<group>"; };
@ -56,10 +48,6 @@
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetBundleImageLoader.h; sourceTree = "<group>"; };
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetBundleImageLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -80,10 +68,6 @@
children = (
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */,
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */,
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
@ -92,8 +76,6 @@
354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
@ -102,8 +84,6 @@
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
58B5115E1A9E6B3D00147676 /* Products */,
);
indentWidth = 2;
@ -174,14 +154,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,

View File

@ -50,7 +50,7 @@ RCT_EXPORT_MODULE()
*/
- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
progressHandler:(RCTImageLoaderProgressBlock)progressBlock
completionHandler:(RCTImageLoaderCompletionBlock)completionBlock
completionHandler:(void (^)(NSError *error, NSData *data))completionBlock
{
if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");

View File

@ -15,10 +15,15 @@
@class ALAssetsLibrary;
typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* UIImage or CAAnimation */);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* NSData, UIImage, CAAnimation */);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, UIImage *image);
typedef void (^RCTImageLoaderCancellationBlock)(void);
@interface UIImage (React)
@property (nonatomic, copy) CAKeyframeAnimation *reactKeyframeAnimation;
@end
@interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler>
/**

View File

@ -28,6 +28,20 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
}
}
@implementation UIImage (React)
- (CAKeyframeAnimation *)reactKeyframeAnimation
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setReactKeyframeAnimation:(CAKeyframeAnimation *)reactKeyframeAnimation
{
objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
@implementation RCTImageLoader
@synthesize bridge = _bridge;
@ -99,7 +113,7 @@ RCT_EXPORT_MODULE()
progressBlock(progress, total);
});
}
} completionHandler:^(NSError *error, id image) {
} completionHandler:^(NSError *error, UIImage *image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}] ?: ^{};
}
@ -142,7 +156,7 @@ RCT_EXPORT_MODULE()
{
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data];
if (imageDecoder) {
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, id image) {
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, UIImage *image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}];
} else {

View File

@ -31,6 +31,7 @@
@implementation RCTImageView
{
RCTBridge *_bridge;
CGSize _targetSize;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@ -142,10 +143,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
scale:RCTScreenScale()
resizeMode:self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, id image) {
completionBlock:^(NSError *error, UIImage *image) {
if ([image isKindOfClass:[CAAnimation class]]) {
[self.layer addAnimation:image forKey:@"contents"];
if (image.reactKeyframeAnimation) {
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else {
[self.layer removeAnimationForKey:@"contents"];
self.image = image;
@ -173,19 +174,17 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
{
[super reactSetFrame:frame];
if (self.image == nil) {
_targetSize = frame.size;
[self reloadImage];
} else if ([RCTImageView srcNeedsReload:_src]) {
// Get optimal image size
CGSize currentSize = self.image.size;
CGSize idealSize = RCTTargetSize(self.image.size, self.image.scale, frame.size,
RCTScreenScale(), self.contentMode, YES);
CGFloat widthChangeFraction = ABS(currentSize.width - idealSize.width) / currentSize.width;
CGFloat heightChangeFraction = ABS(currentSize.height - idealSize.height) / currentSize.height;
CGFloat widthChangeFraction = ABS(_targetSize.width - idealSize.width) / _targetSize.width;
CGFloat heightChangeFraction = ABS(_targetSize.height - idealSize.height) / _targetSize.height;
// If the combined change is more than 20%, reload the asset in case there is a better size.
if (widthChangeFraction + heightChangeFraction > 0.2) {
_targetSize = idealSize;
[self reloadImage];
}
}

View File

@ -33,6 +33,7 @@ class Modal extends React.Component {
<RCTModalHostView
animated={this.props.animated}
transparent={this.props.transparent}
onDismiss={this.props.onDismiss}
style={styles.modal}>
<View style={[styles.container, containerBackgroundColor]}>
{this.props.children}
@ -45,6 +46,7 @@ class Modal extends React.Component {
Modal.propTypes = {
animated: PropTypes.bool,
transparent: PropTypes.bool,
onDismiss: PropTypes.func,
};
var styles = StyleSheet.create({

View File

@ -138,6 +138,23 @@ type ConnectivityStateAndroid = $Enum<{
var _subscriptions = new Map();
if (Platform.OS === 'ios') {
var _isConnected = function(
reachability: ReachabilityStateIOS
): bool {
return reachability !== 'none' &&
reachability !== 'unknown';
};
} else if (Platform.OS === 'android') {
var _isConnected = function(
connectionType: ConnectivityStateAndroid
): bool {
return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
};
}
var _isConnectedSubscriptions = new Map();
var NetInfo = {
addEventListener: function (
eventName: ChangeEventName,
@ -175,29 +192,7 @@ var NetInfo = {
});
},
isConnected: {},
isConnectionMetered: {},
};
if (Platform.OS === 'ios') {
var _isConnected = function(
reachability: ReachabilityStateIOS
): bool {
return reachability !== 'none' &&
reachability !== 'unknown';
};
} else if (Platform.OS === 'android') {
var _isConnected = function(
connectionType: ConnectivityStateAndroid
): bool {
return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
};
}
var _isConnectedSubscriptions = new Map();
NetInfo.isConnected = {
isConnected: {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
@ -229,6 +224,9 @@ NetInfo.isConnected = {
(connection) => _isConnected(connection)
);
},
},
isConnectionMetered: ({}: {} | (callback:Function) => void),
};
if (Platform.OS === 'android') {

View File

@ -56,6 +56,7 @@ RCT_EXPORT_MODULE()
// Lazy setup
if (!_session && [self isValid]) {
NSOperationQueue *callbackQueue = [NSOperationQueue new];
callbackQueue.maxConcurrentOperationCount = 1;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:configuration
delegate:self

View File

@ -111,6 +111,12 @@ var styles = StyleSheet.create({
},
});
var RCTPickerIOS = requireNativeComponent('RCTPicker', null);
var RCTPickerIOS = requireNativeComponent('RCTPicker', PickerIOS, {
nativeOnly: {
items: true,
onChange: true,
selectedIndex: true,
},
});
module.exports = PickerIOS;

View File

@ -16,13 +16,8 @@
#import "RCTTestModule.h"
#import "RCTUtils.h"
#define TIMEOUT_SECONDS 60
@interface RCTBridge (RCTTestRunner)
@property (nonatomic, weak) RCTBridge *batchedBridge;
@end
static const NSTimeInterval kTestTimeoutSeconds = 60;
static const NSTimeInterval kTestTeardownTimeoutSeconds = 30;
@implementation RCTTestRunner
{
@ -49,7 +44,7 @@
_scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
RCTAssert(_scriptURL != nil, @"Could not locate main.jsBundle");
#else
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?dev=true&platform=ios", app]];
_scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]];
#endif
}
return self;
@ -83,6 +78,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)runTest:(SEL)test module:(NSString *)moduleName
initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
{
__weak id weakJSContext;
@autoreleasepool {
__block NSString *error = nil;
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
@ -98,7 +96,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.batchedBridge.modules[testModuleName];
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
RCTAssert(_testController != nil, @"_testController should not be nil");
testModule.controller = _testController;
testModule.testSelector = test;
@ -108,11 +106,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
vc.view = [UIView new];
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:kTestTimeoutSeconds];
while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && error == nil) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
// Take a weak reference to the JS context, so we track its deallocation later
// (we can only do this now, since it's been lazily initialized)
weakJSContext = [[[bridge valueForKey:@"batchedBridge"] valueForKey:@"javaScriptExecutor"] valueForKey:@"context"];
[rootView removeFromSuperview];
RCTSetLogFunction(RCTDefaultLogFunction);
@ -126,9 +128,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched.");
} else {
RCTAssert(error == nil, @"RedBox error: %@", error);
RCTAssert(testModule.status != RCTTestStatusPending, @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
RCTAssert(testModule.status != RCTTestStatusPending, @"Test didn't finish within %0.f seconds", kTestTimeoutSeconds);
RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed");
}
[bridge invalidate];
}
// Wait for the executor to have shut down completely before returning
NSDate *teardownTimeout = [NSDate dateWithTimeIntervalSinceNow:kTestTeardownTimeoutSeconds];
while (teardownTimeout.timeIntervalSinceNow > 0 && weakJSContext) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
RCTAssert(!weakJSContext, @"JS context was not deallocated after being invalidated");
}
@end

View File

@ -0,0 +1,15 @@
/**
* 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 StyleSheetTypes
* @flow
*/
'use strict';
type Atom = number | bool | Object | Array<?Atom>;
export type StyleObj = Atom | Array<?StyleObj>;

View File

@ -14,8 +14,7 @@
var StyleSheetRegistry = require('StyleSheetRegistry');
var invariant = require('invariant');
type Atom = number | bool | Object | Array<?Atom>
type StyleObj = Atom | Array<?StyleObj>
import type { StyleObj } from 'StyleSheetTypes';
function getStyle(style) {
if (typeof style === 'number') {

View File

@ -117,8 +117,15 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSString *sourceCode;
[self loadSource:^(__unused NSError *error, NSString *source) {
[self loadSource:^(NSError *error, NSString *source) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
}];
@ -131,7 +138,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
RCTProfileHookModules(self);
}
__weak RCTBatchedBridge *weakSelf = self;
__block NSString *config;
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
@ -150,16 +156,24 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
// We're not waiting for this complete to leave the dispatch group, since
// injectJSONConfiguration and executeSourceCode will schedule operations on the
// same queue anyway.
[weakSelf injectJSONConfiguration:config onComplete:^(__unused NSError *error) {
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
});
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
if (sourceCode) {
dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
RCTBatchedBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
dispatch_async(bridgeQueue, ^{
[weakSelf executeSourceCode:sourceCode];
});
}
});
}
@ -172,23 +186,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) {
RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil);
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
if (error) {
NSArray *stack = error.userInfo[@"stack"];
if (stack) {
[self.redBox showErrorMessage:error.localizedDescription
withStack:stack];
} else {
[self.redBox showErrorMessage:error.localizedDescription
withDetails:error.localizedFailureReason];
}
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo];
}
_onSourceLoad(error, source);
};
@ -283,7 +280,6 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
object:self];
}
- (void)setupExecutor
{
[_javaScriptExecutor setUp];
@ -313,12 +309,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:^(NSError *error) {
if (error) {
[self.redBox showError:error];
}
onComplete(error);
}];
callback:onComplete];
}
- (void)executeSourceCode:(NSString *)sourceCode
@ -333,7 +324,9 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
if (loadError) {
[self.redBox showError:loadError];
dispatch_async(dispatch_get_main_queue(), ^{
[self stopLoadingWithError:loadError];
});
return;
}
@ -352,6 +345,28 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void);
}];
}
- (void)stopLoadingWithError:(NSError *)error
{
RCTAssertMainThread();
if (!self.isValid || !self.loading) {
return;
}
_loading = NO;
NSArray *stack = error.userInfo[@"stack"];
if (stack) {
[self.redBox showErrorMessage:error.localizedDescription withStack:stack];
} else {
[self.redBox showError:error];
}
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo];
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleURL
moduleProvider:(__unused RCTBridgeModuleProviderBlock)block

View File

@ -13,7 +13,6 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
@ -246,6 +245,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder)
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex

View File

@ -23,7 +23,7 @@
#import "RCTUtils.h"
#ifndef RCT_JSC_PROFILER
#if RCT_DEV && RCT_DEBUG
#if RCT_DEV
#define RCT_JSC_PROFILER 1
#else
#define RCT_JSC_PROFILER 0
@ -34,7 +34,7 @@
#include <dlfcn.h>
#ifndef RCT_JSC_PROFILER_DYLIB
#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"Frameworks"] UTF8String]
#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String]
#endif
#endif

View File

@ -11,4 +11,6 @@
@interface RCTDevLoadingView : NSObject <RCTBridgeModule>
+ (void)setEnabled:(BOOL)enabled;
@end

View File

@ -16,6 +16,8 @@
#if RCT_DEV
static BOOL isEnabled = YES;
@implementation RCTDevLoadingView
{
UIWindow *_window;
@ -27,6 +29,11 @@
RCT_EXPORT_MODULE()
+ (void)setEnabled:(BOOL)enabled
{
isEnabled = enabled;
}
- (instancetype)init
{
if ((self = [super init])) {
@ -57,6 +64,10 @@ RCT_EXPORT_MODULE()
- (void)showWithURL:(NSURL *)URL
{
if (!isEnabled) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
_showDate = [NSDate date];
@ -90,6 +101,10 @@ RCT_EXPORT_MODULE()
- (void)hide
{
if (!isEnabled) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
const NSTimeInterval MIN_PRESENTED_TIME = 0.6;
@ -117,6 +132,7 @@ RCT_EXPORT_MODULE()
@implementation RCTDevLoadingView
+ (NSString *)moduleName { return nil; }
+ (void)setEnabled:(BOOL)enabled { }
@end

View File

@ -488,6 +488,11 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
// Perform layout (possibly animated)
return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTResponseSenderBlock callback = self->_layoutAnimation.callback;
// It's unsafe to call this callback more than once, so we nil it out here
// to make sure that doesn't happen.
_layoutAnimation.callback = nil;
__block NSUInteger completionsCalled = 0;
for (NSUInteger ii = 0; ii < frames.count; ii++) {
NSNumber *reactTag = frameReactTags[ii];

View File

@ -589,7 +589,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'mkdir -p \"$1/Frameworks\" && cp -r /tmp/RCTJSCProfiler/* \"$1/Frameworks\"' -- {}\nfi";
shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'cp -r /tmp/RCTJSCProfiler \"$1\"' -- {}\nfi";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

View File

@ -17,4 +17,13 @@
@property (nonatomic, assign, readwrite) UIEdgeInsets contentInset;
@property (nonatomic, assign, readwrite) BOOL automaticallyAdjustContentInsets;
/**
* Automatically adjusted content inset depends on view controller's top and bottom
* layout guides so if you've changed one of them (e.g. after rotation or manually) you should call this method
* to recalculate and refresh content inset.
* To handle case with changing navigation bar height call this method from viewDidLayoutSubviews:
* of your view controller.
*/
- (void)refreshContentInset;
@end

View File

@ -24,6 +24,7 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
- (NSDictionary *)constantsToExport
{

View File

@ -460,10 +460,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
_scrollView.frame = self.bounds;
_scrollView.contentOffset = originalOffset;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:YES];
[self updateClippedSubviews];
}
@ -523,6 +519,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_scrollView zoomToRect:rect animated:animated];
}
- (void)refreshContentInset
{
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:YES];
}
#pragma mark - ScrollView delegate
#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \

View File

@ -95,9 +95,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
{
[super layoutSubviews];
_webView.frame = self.bounds;
[RCTView autoAdjustInsetsForView:self
withScrollView:_webView.scrollView
updateOffset:YES];
}
- (void)setContentInset:(UIEdgeInsets)contentInset
@ -133,6 +130,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return event;
}
- (void)refreshContentInset
{
[RCTView autoAdjustInsetsForView:self
withScrollView:_webView.scrollView
updateOffset:YES];
}
#pragma mark - UIWebViewDelegate methods
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request

View File

@ -16,13 +16,15 @@
#import "RCTUtils.h"
#import "RCTViewControllerProtocol.h"
#import "UIView+React.h"
#import "RCTAutoInsetsProtocol.h"
@implementation RCTWrapperViewController
{
UIView *_wrapperView;
UIView *_contentView;
CGFloat _previousTopLayout;
CGFloat _previousBottomLayout;
RCTEventDispatcher *_eventDispatcher;
CGFloat _previousTopLayoutLength;
CGFloat _previousBottomLayoutLength;
}
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
@ -58,6 +60,32 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
_currentBottomLayoutGuide = self.bottomLayoutGuide;
}
static BOOL RCTFindScrollViewAndRefreshContentInsetInView(UIView *view)
{
if ([view conformsToProtocol:@protocol(RCTAutoInsetsProtocol)]) {
[(id <RCTAutoInsetsProtocol>) view refreshContentInset];
return YES;
}
for (UIView *subview in view.subviews) {
if (RCTFindScrollViewAndRefreshContentInsetInView(subview)) {
return YES;
}
}
return NO;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (_previousTopLayoutLength != _currentTopLayoutGuide.length ||
_previousBottomLayoutLength != _currentBottomLayoutGuide.length) {
RCTFindScrollViewAndRefreshContentInsetInView(_contentView);
_previousTopLayoutLength = _currentTopLayoutGuide.length;
_previousBottomLayoutLength = _currentBottomLayoutGuide.length;
}
}
static UIView *RCTFindNavBarShadowViewInView(UIView *view)
{
if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) {

2428
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -47,42 +47,42 @@
},
"dependencies": {
"absolute-path": "0.0.0",
"babel": "5.8.21",
"babel-core": "5.8.21",
"bser": "1.0.0",
"chalk": "1.0.0",
"babel": "5.8.23",
"babel-core": "5.8.23",
"bser": "1.0.2",
"chalk": "1.1.1",
"connect": "2.8.3",
"debug": "2.1.0",
"debug": "2.2.0",
"graceful-fs": "4.1.2",
"image-size": "0.3.5",
"immutable": "^3.7.4",
"joi": "5.1.0",
"jstransform": "11.0.1",
"module-deps": "3.5.6",
"immutable": "3.7.5",
"joi": "6.6.1",
"jstransform": "11.0.3",
"module-deps": "3.9.1",
"optimist": "0.6.1",
"progress": "^1.1.8",
"promise": "^7.0.3",
"react-timer-mixin": "^0.13.1",
"progress": "1.1.8",
"promise": "7.0.4",
"react-timer-mixin": "0.13.2",
"react-tools": "git://github.com/facebook/react#b4e74e38e43ac53af8acd62c78c9213be0194245",
"rebound": "^0.0.12",
"rebound": "0.0.13",
"regenerator": "0.8.36",
"sane": "^1.1.2",
"semver": "^4.3.6",
"source-map": "0.1.31",
"sane": "^1.2.0",
"semver": "5.0.1",
"source-map": "0.4.4",
"stacktrace-parser": "0.1.3",
"uglify-js": "2.4.16",
"underscore": "1.7.0",
"wordwrap": "^1.0.0",
"worker-farm": "^1.3.1",
"uglify-js": "2.4.24",
"underscore": "1.8.3",
"wordwrap": "1.0.0",
"worker-farm": "1.3.1",
"ws": "0.8.0",
"yargs": "1.3.2",
"yeoman-environment": "^1.2.7",
"yeoman-generator": "^0.20.2"
"yargs": "3.24.0",
"yeoman-environment": "1.2.7",
"yeoman-generator": "0.20.3"
},
"devDependencies": {
"jest-cli": "0.5.0",
"babel-eslint": "3.1.5",
"eslint": "0.21.2",
"eslint-plugin-react": "2.3.0"
"jest-cli": "0.5.1",
"babel-eslint": "4.1.1",
"eslint": "1.3.1",
"eslint-plugin-react": "3.3.1"
}
}

View File

@ -59,6 +59,14 @@ var options = parseCommandLine([{
type: 'string',
default: require.resolve('./transformer.js'),
description: 'Specify a custom transformer to be used (absolute path)'
}, {
command: 'resetCache',
description: 'Removes cached files',
default: false,
}, {
command: 'reset-cache',
description: 'Removes cached files',
default: false,
}]);
if (options.projectRoots) {
@ -229,6 +237,7 @@ function getAppMiddleware(options) {
transformModulePath: transformerPath,
assetRoots: options.assetRoots,
assetExts: ['png', 'jpeg', 'jpg'],
resetCache: options.resetCache || options['reset-cache'],
polyfillModuleNames: [
require.resolve(
'../Libraries/JavaScriptAppEngine/polyfills/document.js'

View File

@ -1,6 +1,7 @@
'use strict';
jest
.dontMock('../../lib/getPlatformExtension')
.dontMock('../../lib/getAssetDataFromName')
.dontMock('../');
@ -47,6 +48,43 @@ describe('AssetServer', () => {
);
});
pit('should work for the simple case with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.ios.png': 'b ios image',
'b.android.png': 'b android image',
'c.png': 'c general image',
'c.android.png': 'c android image',
}
}
});
return Promise.all([
server.get('imgs/b.png', 'ios').then(
data => expect(data).toBe('b ios image')
),
server.get('imgs/b.png', 'android').then(
data => expect(data).toBe('b android image')
),
server.get('imgs/c.png', 'android').then(
data => expect(data).toBe('c android image')
),
server.get('imgs/c.png', 'ios').then(
data => expect(data).toBe('c general image')
),
server.get('imgs/c.png').then(
data => expect(data).toBe('c general image')
),
]);
});
pit('should work for the simple case with jpg', () => {
const server = new AssetServer({
projectRoots: ['/root'],
@ -95,6 +133,37 @@ describe('AssetServer', () => {
);
});
pit('should pick the bigger one with platform ext', () => {
const server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@4x.png': 'b4 image',
'b@4.5x.png': 'b4.5 image',
'b@1x.ios.png': 'b1 ios image',
'b@2x.ios.png': 'b2 ios image',
'b@4x.ios.png': 'b4 ios image',
'b@4.5x.ios.png': 'b4.5 ios image',
}
}
});
return Promise.all([
server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
),
server.get('imgs/b@3x.png', 'ios').then(data =>
expect(data).toBe('b4 ios image')
),
]);
});
pit('should support multiple project roots', () => {
const server = new AssetServer({
projectRoots: ['/root', '/root2'],

View File

@ -20,7 +20,6 @@ const stat = Promise.denodeify(fs.stat);
const readDir = Promise.denodeify(fs.readdir);
const readFile = Promise.denodeify(fs.readFile);
const validateOpts = declareOpts({
projectRoots: {
type: 'array',
@ -39,9 +38,9 @@ class AssetServer {
this._assetExts = opts.assetExts;
}
get(assetPath) {
get(assetPath, platform = null) {
const assetData = getAssetDataFromName(assetPath);
return this._getAssetRecord(assetPath).then(record => {
return this._getAssetRecord(assetPath, platform).then(record => {
for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) {
return readFile(record.files[i]);
@ -52,14 +51,14 @@ class AssetServer {
});
}
getAssetData(assetPath) {
getAssetData(assetPath, platform = null) {
const nameData = getAssetDataFromName(assetPath);
const data = {
name: nameData.name,
type: nameData.type,
};
return this._getAssetRecord(assetPath).then(record => {
return this._getAssetRecord(assetPath, platform).then(record => {
data.scales = record.scales;
return Promise.all(
@ -85,9 +84,10 @@ class AssetServer {
* 1. We first parse the directory of the asset
* 2. We check to find a matching directory in one of the project roots
* 3. We then build a map of all assets and their scales in this directory
* 4. Then pick the closest resolution (rounding up) to the requested one
* 4. Then try to pick platform-specific asset records
* 5. Then pick the closest resolution (rounding up) to the requested one
*/
_getAssetRecord(assetPath) {
_getAssetRecord(assetPath, platform = null) {
const filename = path.basename(assetPath);
return (
@ -104,11 +104,20 @@ class AssetServer {
const files = res[1];
const assetData = getAssetDataFromName(filename);
const map = this._buildAssetMap(dir, files);
const record = map[assetData.assetName];
const map = this._buildAssetMap(dir, files, platform);
let record;
if (platform != null){
record = map[getAssetKey(assetData.assetName, platform)] ||
map[assetData.assetName];
} else {
record = map[assetData.assetName];
}
if (!record) {
throw new Error('Asset not found');
throw new Error(
`Asset not found: ${assetPath} for platform: ${platform}`
);
}
return record;
@ -141,9 +150,10 @@ class AssetServer {
const map = Object.create(null);
assets.forEach(function(asset, i) {
const file = files[i];
let record = map[asset.assetName];
const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map[assetKey];
if (!record) {
record = map[asset.assetName] = {
record = map[assetKey] = {
scales: [],
files: [],
};
@ -151,6 +161,7 @@ class AssetServer {
let insertIndex;
const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) {
break;
@ -164,4 +175,12 @@ class AssetServer {
}
}
function getAssetKey(assetName, platform) {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
module.exports = AssetServer;

View File

@ -115,13 +115,18 @@ class Bundle {
getMinifiedSourceAndMap() {
this._assertFinalized();
if (this._minifiedSourceAndMap) {
return this._minifiedSourceAndMap;
}
const source = this._getSource();
try {
return UglifyJS.minify(source, {
this._minifiedSourceAndMap = UglifyJS.minify(source, {
fromString: true,
outSourceMap: 'bundle.js',
inSourceMap: this.getSourceMap(),
});
return this._minifiedSourceAndMap;
} catch(e) {
// Sometimes, when somebody is using a new syntax feature that we
// don't yet have transform for, the untransformed line is sent to
@ -186,6 +191,10 @@ class Bundle {
options = options || {};
if (options.minify) {
return this.getMinifiedSourceAndMap().map;
}
if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options);
}

View File

@ -154,7 +154,7 @@ class Bundler {
bundle.setMainModuleId(result.mainModuleId);
return Promise.all(
result.dependencies.map(
module => this._transformModule(bundle, module).then(transformed => {
module => this._transformModule(bundle, module, platform).then(transformed => {
if (bar) {
bar.tick();
}
@ -182,13 +182,13 @@ class Bundler {
return this._resolver.getDependencies(main, { dev: isDev, platform });
}
_transformModule(bundle, module) {
_transformModule(bundle, module, platform = null) {
let transform;
if (module.isAsset_DEPRECATED()) {
transform = this.generateAssetModule_DEPRECATED(bundle, module);
} else if (module.isAsset()) {
transform = this.generateAssetModule(bundle, module);
transform = this.generateAssetModule(bundle, module, platform);
} else if (module.isJSON()) {
transform = generateJSONModule(module);
} else {
@ -243,12 +243,12 @@ class Bundler {
});
}
generateAssetModule(bundle, module) {
generateAssetModule(bundle, module, platform = null) {
const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
return Promise.all([
sizeOf(module.path),
this._assetServer.getAssetData(relPath),
this._assetServer.getAssetData(relPath, platform),
]).then(function(res) {
const dimensions = res[0];
const assetData = res[1];

View File

@ -16,6 +16,7 @@ jest
.dontMock('../../crawlers')
.dontMock('../../crawlers/node')
.dontMock('../../replacePatterns')
.dontMock('../../../lib/getPlatformExtension')
.dontMock('../../../lib/getAssetDataFromName')
.dontMock('../../fastfs')
.dontMock('../../AssetModule_DEPRECATED')
@ -424,6 +425,90 @@ describe('DependencyGraph', function() {
});
});
pit('should respect platform extension in assets', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("./imgs/a.png");',
'require("./imgs/b.png");',
'require("./imgs/c.png");',
].join('\n'),
'imgs': {
'a@1.5x.ios.png': '',
'b@.7x.ios.png': '',
'c.ios.png': '',
'c@2x.ios.png': '',
},
'package.json': JSON.stringify({
name: 'rootPackage'
}),
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
cache: cache,
});
dgraph.setup({ platform: 'ios' });
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
expect(deps)
.toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: [
'./imgs/a.png',
'./imgs/b.png',
'./imgs/c.png',
],
isAsset: false,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
resolution: undefined,
},
{
id: 'rootPackage/imgs/a.png',
path: '/root/imgs/a@1.5x.ios.png',
resolution: 1.5,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
{
id: 'rootPackage/imgs/b.png',
path: '/root/imgs/b@.7x.ios.png',
resolution: 0.7,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
{
id: 'rootPackage/imgs/c.png',
path: '/root/imgs/c.ios.png',
resolution: 1,
dependencies: [],
isAsset: true,
isAsset_DEPRECATED: false,
isJSON: false,
isPolyfill: false,
},
]);
});
});
pit('Deprecated and relative assets can live together', function() {
var root = '/root';
fs.__setMockFilesystem({

View File

@ -17,6 +17,7 @@ const crawl = require('../crawlers');
const debug = require('debug')('DependencyGraph');
const declareOpts = require('../../lib/declareOpts');
const getAssetDataFromName = require('../../lib/getAssetDataFromName');
const getPontentialPlatformExt = require('../../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const util = require('util');
@ -274,7 +275,7 @@ class DependencyGraph {
// `platformExt` could be set in the `setup` method.
if (!this._platformExt) {
const platformExt = getPlatformExt(entryPath);
const platformExt = getPontentialPlatformExt(entryPath);
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
this._platformExt = platformExt;
} else {
@ -390,12 +391,18 @@ class DependencyGraph {
return Promise.resolve().then(() => {
if (this._isAssetFile(potentialModulePath)) {
const {name, type} = getAssetDataFromName(potentialModulePath);
const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type);
let pattern = '^' + name + '(@[\\d\\.]+x)?';
if (this._platformExt != null) {
pattern += '(\\.' + this._platformExt + ')?';
}
pattern += '\\.' + type;
// We arbitrarly grab the first one, because scale selection
// will happen somewhere
const [assetFile] = this._fastfs.matches(
path.dirname(potentialModulePath),
pattern
new RegExp(pattern)
);
if (assetFile) {
@ -496,7 +503,7 @@ class DependencyGraph {
const modules = this._hasteMap[name];
if (this._platformExt != null) {
for (let i = 0; i < modules.length; i++) {
if (getPlatformExt(modules[i].path) === this._platformExt) {
if (getPontentialPlatformExt(modules[i].path) === this._platformExt) {
return modules[i];
}
}
@ -662,15 +669,6 @@ function normalizePath(modulePath) {
return modulePath.replace(/\/$/, '');
}
// Extract platform extension: index.ios.js -> ios
function getPlatformExt(file) {
const parts = path.basename(file).split('.');
if (parts.length < 3) {
return null;
}
return parts[parts.length - 2];
}
util.inherits(NotFoundError, Error);
module.exports = DependencyGraph;

View File

@ -245,8 +245,16 @@ describe('processRequest', () => {
expect(res.end).toBeCalledWith('i am image');
});
it('should return 404', () => {
it('should parse the platform option', () => {
const req = {url: '/assets/imgs/a.png?platform=ios'};
const res = {end: jest.genMockFn()};
AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image'));
server.processRequest(req, res);
jest.runAllTimers();
expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios');
expect(res.end).toBeCalledWith('i am image');
});
});

View File

@ -278,7 +278,8 @@ class Server {
_processAssetsRequest(req, res) {
const urlObj = url.parse(req.url, true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
this._assetServer.get(assetPath[1])
const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`);
this._assetServer.get(assetPath[1], urlObj.query.platform)
.then(
data => res.end(data),
error => {
@ -286,7 +287,7 @@ class Server {
res.writeHead('404');
res.end('Asset not found');
}
).done();
).done(() => Activity.endEvent(assetEvent));
}
_processProfile(req, res) {
@ -370,7 +371,9 @@ class Server {
res.end(bundleSource);
Activity.endEvent(startReqEventId);
} else if (requestType === 'map') {
var sourceMap = JSON.stringify(p.getSourceMap());
var sourceMap = JSON.stringify(p.getSourceMap({
minify: options.minify,
}));
res.setHeader('Content-Type', 'application/json');
res.end(sourceMap);
Activity.endEvent(startReqEventId);

View File

@ -12,6 +12,7 @@ const Bundle = require('../Bundler/Bundle');
const Promise = require('promise');
const bser = require('bser');
const debug = require('debug')('ReactPackager:SocketClient');
const fs = require('fs');
const net = require('net');
const path = require('path');
const tmpdir = require('os').tmpdir();
@ -29,7 +30,16 @@ class SocketClient {
this._sock = net.connect(sockPath);
this._ready = new Promise((resolve, reject) => {
this._sock.on('connect', () => resolve(this));
this._sock.on('error', (e) => reject(e));
this._sock.on('error', (e) => {
e.message = `Error connecting to server on ${sockPath} ` +
`with error: ${e.message}`;
if (fs.existsSync(LOG_PATH)) {
e.message += '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8');
}
reject(e);
});
});
this._resolvers = Object.create(null);

View File

@ -16,6 +16,7 @@ const fs = require('fs');
const net = require('net');
const MAX_IDLE_TIME = 30 * 1000;
const MAX_STARTUP_TIME = 5 * 60 * 1000;
class SocketServer {
constructor(sockPath, options) {
@ -35,13 +36,15 @@ class SocketServer {
process.on('exit', () => fs.unlinkSync(sockPath));
});
});
this._numConnections = 0;
this._server.on('connection', (sock) => this._handleConnection(sock));
// Disable the file watcher.
options.nonPersistent = true;
this._packagerServer = new Server(options);
this._jobs = 0;
this._dieEventually();
this._dieEventually(MAX_STARTUP_TIME);
}
onReady() {
@ -50,10 +53,13 @@ class SocketServer {
_handleConnection(sock) {
debug('connection to server', process.pid);
this._numConnections++;
sock.on('close', () => this._numConnections--);
const bunser = new bser.BunserBuf();
sock.on('data', (buf) => bunser.append(buf));
bunser.on('value', (m) => this._handleMessage(sock, m));
bunser.on('error', (e) => console.error(e));
}
_handleMessage(sock, m) {
@ -113,15 +119,15 @@ class SocketServer {
}));
}
_dieEventually() {
_dieEventually(delay = MAX_IDLE_TIME) {
clearTimeout(this._deathTimer);
this._deathTimer = setTimeout(() => {
if (this._jobs <= 0) {
if (this._jobs <= 0 && this._numConnections <= 0) {
debug('server dying', process.pid);
process.exit();
}
this._dieEventually();
}, MAX_IDLE_TIME);
}, delay);
}
static listenOnServerIPCMessages() {

View File

@ -20,7 +20,7 @@ const path = require('path');
const tmpdir = require('os').tmpdir();
const {spawn} = require('child_process');
const CREATE_SERVER_TIMEOUT = 60000;
const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000;
const SocketInterface = {
getOrCreateSocketFor(options) {
@ -42,8 +42,16 @@ const SocketInterface = {
if (fs.existsSync(sockPath)) {
var sock = net.connect(sockPath);
sock.on('connect', () => {
SocketClient.create(sockPath).then(
client => {
sock.end();
resolve(SocketClient.create(sockPath));
resolve(client);
},
error => {
sock.end();
reject(error);
}
);
});
sock.on('error', (e) => {
try {

View File

@ -1,52 +0,0 @@
'use strict';
jest.autoMockOff();
var getAssetDataFromName = require('../getAssetDataFromName');
describe('getAssetDataFromName', function() {
it('should extract resolution simple case', function() {
var data = getAssetDataFromName('test@2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 2,
type: 'png',
name: 'test',
});
});
it('should default resolution to 1', function() {
var data = getAssetDataFromName('test.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1,
type: 'png',
name: 'test',
});
});
it('should support float', function() {
var data = getAssetDataFromName('test@1.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1.1,
type: 'png',
name: 'test',
});
data = getAssetDataFromName('test@.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.1,
type: 'png',
name: 'test',
});
data = getAssetDataFromName('test@0.2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.2,
type: 'png',
name: 'test',
});
});
});

View File

@ -0,0 +1,123 @@
/**
* 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';
jest.dontMock('../getPlatformExtension')
.dontMock('../getAssetDataFromName');
describe('getAssetDataFromName', () => {
let getAssetDataFromName;
beforeEach(() => {
getAssetDataFromName = require('../getAssetDataFromName');
});
it('should get data from name', () => {
expect(getAssetDataFromName('a/b/c.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({
resolution: 2.5,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: null,
});
expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({
resolution: 1,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({
resolution: 2.5,
assetName: 'a/b/c.png',
type: 'png',
name: 'c',
platform: 'ios',
});
});
describe('resolution extraction', () => {
it('should extract resolution simple case', () => {
var data = getAssetDataFromName('test@2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 2,
type: 'png',
name: 'test',
platform: null,
});
});
it('should default resolution to 1', () => {
var data = getAssetDataFromName('test.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1,
type: 'png',
name: 'test',
platform: null,
});
});
it('should support float', () => {
var data = getAssetDataFromName('test@1.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 1.1,
type: 'png',
name: 'test',
platform: null,
});
data = getAssetDataFromName('test@.1x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.1,
type: 'png',
name: 'test',
platform: null,
});
data = getAssetDataFromName('test@0.2x.png');
expect(data).toEqual({
assetName: 'test.png',
resolution: 0.2,
type: 'png',
name: 'test',
platform: null,
});
});
});
});

View File

@ -0,0 +1,24 @@
/**
* 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';
jest.dontMock('../getPlatformExtension');
describe('getPlatformExtension', function() {
it('should get platform ext', function() {
var getPlatformExtension = require('../getPlatformExtension');
expect(getPlatformExtension('a.ios.js')).toBe('ios');
expect(getPlatformExtension('a.android.js')).toBe('android');
expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios');
expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios');
expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios');
expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null);
expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null);
});
});

View File

@ -1,14 +1,29 @@
/**
* 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 path = require('path');
const path = require('path');
const getPlatformExtension = require('./getPlatformExtension');
function getAssetDataFromName(filename) {
var ext = path.extname(filename);
const ext = path.extname(filename);
const platformExt = getPlatformExtension(filename);
var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$');
let pattern = '@([\\d\\.]+)x';
if (platformExt != null) {
pattern += '(\\.' + platformExt + ')?';
}
pattern += '\\' + ext + '$';
const re = new RegExp(pattern);
var match = filename.match(re);
var resolution;
const match = filename.match(re);
let resolution;
if (!(match && match[1])) {
resolution = 1;
@ -19,12 +34,21 @@ function getAssetDataFromName(filename) {
}
}
var assetName = match ? filename.replace(re, ext) : filename;
let assetName;
if (match) {
assetName = filename.replace(re, ext);
} else if (platformExt != null) {
assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext);
} else {
assetName = filename;
}
return {
resolution: resolution,
assetName: assetName,
type: ext.slice(1),
name: path.basename(assetName, ext)
name: path.basename(assetName, ext),
platform: platformExt,
};
}

View File

@ -0,0 +1,28 @@
/**
* 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';
const path = require('path');
const SUPPORTED_PLATFORM_EXTS = ['android', 'ios'];
const re = new RegExp(
'[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$'
);
// Extract platform extension: index.ios.js -> ios
function getPlatformExtension(file) {
const match = file.match(re);
if (match && match[1]) {
return match[1];
}
return null;
}
module.exports = getPlatformExtension;

View File

@ -39,6 +39,7 @@ function transform(srcTxt, filename, options) {
'es7.objectRestSpread',
'flow',
'react',
'react.displayName',
'regenerator',
],
plugins: plugins,