Updates from Tue 8 Sep

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

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,60 +27,85 @@ 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);
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);
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
NSDictionary *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];
if (imageCount > 1) {
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime];
if (delayTime == nil) {
if (i == 0) {
delayTime = @(kDelayTimeIntervalDefault);
} else {
delayTime = delays[i - 1];
NSTimeInterval duration = 0;
NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {
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];
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime];
if (delayTime == nil) {
if (i == 0) {
delayTime = @(kDelayTimeIntervalDefault);
} else {
delayTime = delays[i - 1];
}
}
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
delayTime = @(kDelayTimeIntervalDefault);
}
duration += delayTime.doubleValue;
delays[i] = delayTime;
images[i] = (__bridge_transfer id)imageRef;
}
CFRelease(imageSource);
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
runningDuration += delayNumber.doubleValue;
}
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
delayTime = @(kDelayTimeIntervalDefault);
[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;
image.reactKeyframeAnimation = animation;
} else {
// Don't bother creating an animation
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (imageRef) {
image = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
}
duration += delayTime.doubleValue;
delays[i] = delayTime;
images[i] = (__bridge_transfer id)image;
}
CFRelease(imageSource);
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
runningDuration += delayNumber.doubleValue;
CFRelease(imageSource);
}
[keyTimes addObject:@1.0];
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);
return nil;
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,60 +192,41 @@ var NetInfo = {
});
},
isConnected: {},
isConnected: {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
var listener = (connection) => {
handler(_isConnected(connection));
};
_isConnectedSubscriptions.set(handler, listener);
NetInfo.addEventListener(
eventName,
listener
);
},
isConnectionMetered: {},
};
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
var listener = _isConnectedSubscriptions.get(handler);
NetInfo.removeEventListener(
eventName,
listener
);
_isConnectedSubscriptions.delete(handler);
},
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 = {
addEventListener: function (
eventName: ChangeEventName,
handler: Function
): void {
var listener = (connection) => {
handler(_isConnected(connection));
};
_isConnectedSubscriptions.set(handler, listener);
NetInfo.addEventListener(
eventName,
listener
);
fetch: function(): Promise {
return NetInfo.fetch().then(
(connection) => _isConnected(connection)
);
},
},
removeEventListener: function(
eventName: ChangeEventName,
handler: Function
): void {
var listener = _isConnectedSubscriptions.get(handler);
NetInfo.removeEventListener(
eventName,
listener
);
_isConnectedSubscriptions.delete(handler);
},
fetch: function(): Promise {
return NetInfo.fetch().then(
(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,52 +78,69 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)runTest:(SEL)test module:(NSString *)moduleName
initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
{
__block NSString *error = nil;
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
error = message;
__weak id weakJSContext;
@autoreleasepool {
__block NSString *error = nil;
RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
error = message;
}
});
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:_moduleProvider
launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps];
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
RCTAssert(_testController != nil, @"_testController should not be nil");
testModule.controller = _testController;
testModule.testSelector = test;
testModule.view = rootView;
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
vc.view = [UIView new];
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
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]];
}
});
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:_moduleProvider
launchOptions:nil];
// 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];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps];
rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
RCTSetLogFunction(RCTDefaultLogFunction);
NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
RCTTestModule *testModule = rootView.bridge.batchedBridge.modules[testModuleName];
RCTAssert(_testController != nil, @"_testController should not be nil");
testModule.controller = _testController;
testModule.testSelector = test;
testModule.view = rootView;
NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
}]];
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
vc.view = [UIView new];
[vc.view addSubview:rootView]; // Add as subview so it doesn't get resized
if (expectErrorBlock) {
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 %0.f seconds", kTestTimeoutSeconds);
RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed");
}
[bridge invalidate];
}
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
while (date.timeIntervalSinceNow > 0 && testModule.status == RCTTestStatusPending && error == nil) {
// 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]];
}
[rootView removeFromSuperview];
RCTSetLogFunction(RCTDefaultLogFunction);
NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"];
}]];
RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews);
if (expectErrorBlock) {
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 == RCTTestStatusPassed, @"Test failed");
}
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') {