diff --git a/.travis.yml b/.travis.yml index 6efd4fbb2deb52..ea84c8bc8ab5c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -82,3 +82,4 @@ env: branches: only: - master + - /^.*-stable$/ diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 5720175747593c..a36d6b0425858b 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -179,6 +179,18 @@ var MapViewExample = React.createClass({ longitude: region.longitude, latitude: region.latitude, title: 'You Are Here', + rightCallout: { + type: "button", + onPress: function() { + console.info("You clicked me"); + } + }, + leftCallout: { + type: "image", + config: { + image: "http://facebook.github.io/react-native/img/opengraph.png?2" + } + } }]; }, diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index ac715e89679057..906f0d0954dc59 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -23,6 +23,7 @@ var deepDiffer = require('deepDiffer'); var insetsDiffer = require('insetsDiffer'); var merge = require('merge'); var requireNativeComponent = require('requireNativeComponent'); +var resolveAssetSource = require('resolveAssetSource'); type Event = Object; type MapRegion = { @@ -35,8 +36,7 @@ type MapRegion = { var MapView = React.createClass({ mixins: [NativeMethodsMixin], - checkAnnotationIds: function (annotations: Array) { - + checkAnnotationData: function(annotations: Array) { var newAnnotations = annotations.map(function (annotation) { if (!annotation.id) { // TODO: add a base64 (or similar) encoder here @@ -50,16 +50,15 @@ var MapView = React.createClass({ annotations: newAnnotations }); }, - componentWillMount: function() { if (this.props.annotations) { - this.checkAnnotationIds(this.props.annotations); + this.checkAnnotationData(this.props.annotations); } }, componentWillReceiveProps: function(nextProps: Object) { if (nextProps.annotations) { - this.checkAnnotationIds(nextProps.annotations); + this.checkAnnotationData(nextProps.annotations); } }, @@ -141,7 +140,7 @@ var MapView = React.createClass({ * to be displayed. */ latitudeDelta: React.PropTypes.number.isRequired, - longitudeDelta: React.PropTypes.number.isRequired, + longitudeDelta: React.PropTypes.number.isRequired }), /** @@ -166,16 +165,106 @@ var MapView = React.createClass({ subtitle: React.PropTypes.string, /** - * Whether the Annotation has callout buttons. + * Right callout */ - hasLeftCallout: React.PropTypes.bool, - hasRightCallout: React.PropTypes.bool, + rightCallout: React.PropTypes.shape({ + + /** + * Type of the callout. If image, set src in config + */ + type: React.PropTypes.oneOf([ + 'button', + 'image' + ]), + + /** + * Callback for when the accessory is clicked + * Currently only works on button and not image + */ + onPress: React.PropTypes.func, + + /** + * Additional config parameters to pass to the callout. + */ + config: React.PropTypes.shape({ + + /** + * Is being used when type == image. use the same input as for Image + */ + imageSize: React.PropTypes.shape({ + /** + * Width of the image to be initialized + */ + width: React.PropTypes.number, + + /** + * Height of the image to be initialized + */ + height: React.PropTypes.number + }), + + /** + * Is being used when type == image. use the same input as for Image + */ + image: React.PropTypes.string, + + /** + * Default Image is being used when the image has to get loaded + */ + defaultImage: React.PropTypes.shape + }) + }), /** - * Event handlers for callout buttons. + * Left callout */ - onLeftCalloutPress: React.PropTypes.func, - onRightCalloutPress: React.PropTypes.func, + leftCallout: React.PropTypes.shape({ + + /** + * Type of the callout. If image, set src in config + */ + type: React.PropTypes.oneOf([ + 'button', + 'image' + ]), + + /** + * Callback for when the accessory is clicked + * Currently only works on button and not image + */ + onPress: React.PropTypes.func, + + /** + * Additional config parameters to pass to the callout. + */ + config: React.PropTypes.shape({ + + /** + * Is being used when type == image. use the same input as for Image + */ + imageSize: React.PropTypes.shape({ + /** + * Width of the image to be initialized + */ + width: React.PropTypes.number, + + /** + * Height of the image to be initialized + */ + height: React.PropTypes.number + }), + + /** + * Is being used when type == image. use the same input as for Image + */ + image: React.PropTypes.string, + + /** + * Default Image is being used when the image has to get loaded + */ + defaultImage: React.PropTypes.shape + }) + }), /** * annotation id @@ -242,9 +331,9 @@ var MapView = React.createClass({ if (annotation.id === event.nativeEvent.annotationId) { // Pass the right function if (event.nativeEvent.side === 'left') { - annotation.onLeftCalloutPress && annotation.onLeftCalloutPress(event.nativeEvent); + annotation.leftCallout.onPress && annotation.leftCallout.onPress(event.nativeEvent); } else if (event.nativeEvent.side === 'right') { - annotation.onRightCalloutPress && annotation.onRightCalloutPress(event.nativeEvent); + annotation.rightCallout.onPress && annotation.rightCallout.onPress(event.nativeEvent); } } } diff --git a/React.podspec b/React.podspec index 7c20b2730032cf..a36146b254d9dc 100644 --- a/React.podspec +++ b/React.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "React" - s.version = "0.8.0" + s.version = "0.14.0" s.summary = "Build high quality mobile apps using React." s.description = <<-DESC React Native apps are built using the React JS diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index a85b09338dea8b..b4aa4ed561f010 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -197,13 +197,20 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad // Force JS __DEV__ value to match RCT_DEBUG if (shouldOverrideDev) { NSString *sourceString = [[NSString alloc] initWithData:source encoding:NSUTF8StringEncoding]; - NSRange range = [sourceString rangeOfString:@"__DEV__="]; + NSRange range = [sourceString rangeOfString:@"\\b__DEV__\\s*?=\\s*?(!1|!0|false|true)" + options:NSRegularExpressionSearch]; + RCTAssert(range.location != NSNotFound, @"It looks like the implementation" "of __DEV__ has changed. Update -[RCTBatchedBridge loadSource:]."); - NSRange valueRange = {range.location + range.length, 2}; - if ([[sourceString substringWithRange:valueRange] isEqualToString:@"!1"]) { - source = [[sourceString stringByReplacingCharactersInRange:valueRange withString:@" 1"] dataUsingEncoding:NSUTF8StringEncoding]; + + NSString *valueString = [sourceString substringWithRange:range]; + if ([valueString rangeOfString:@"!1"].length) { + valueString = [valueString stringByReplacingOccurrencesOfString:@"!1" withString:@"!0"]; + } else if ([valueString rangeOfString:@"false"].length) { + valueString = [valueString stringByReplacingOccurrencesOfString:@"false" withString:@"true"]; } + source = [[sourceString stringByReplacingCharactersInRange:range withString:valueString] + dataUsingEncoding:NSUTF8StringEncoding]; } _onSourceLoad(error, source); diff --git a/React/Modules/RCTPointAnnotation.h b/React/Modules/RCTPointAnnotation.h index 0646608d4805bc..99959520abc563 100644 --- a/React/Modules/RCTPointAnnotation.h +++ b/React/Modules/RCTPointAnnotation.h @@ -9,11 +9,24 @@ #import +typedef enum{ + RCTPointAnnotationTypeButton, + RCTPointAnnotationTypeImage +} RCTPointAnnotationCalloutType; + @interface RCTPointAnnotation : MKPointAnnotation @property (nonatomic, copy) NSString *identifier; + @property (nonatomic, assign) BOOL hasLeftCallout; @property (nonatomic, assign) BOOL hasRightCallout; + +@property (nonatomic, assign) RCTPointAnnotationCalloutType leftCalloutType; +@property (nonatomic, assign) RCTPointAnnotationCalloutType rightCalloutType; + +@property (nonatomic, copy) NSDictionary *rightCalloutConfig; +@property (nonatomic, copy) NSDictionary *leftCalloutConfig; + @property (nonatomic, assign) BOOL animateDrop; @end diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index 9206c3a4050ed0..fc0bf1c35597e0 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -58,9 +58,31 @@ + (RCTPointAnnotation *)RCTPointAnnotation:(id)json shape.title = [RCTConvert NSString:json[@"title"]]; shape.subtitle = [RCTConvert NSString:json[@"subtitle"]]; shape.identifier = [RCTConvert NSString:json[@"id"]]; - shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]]; - shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]]; shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; + + shape.leftCalloutConfig = [RCTConvert NSDictionary:json[@"leftCallout"][@"config"]]; + shape.rightCalloutConfig = [RCTConvert NSDictionary:json[@"rightCallout"][@"config"]]; + + shape.hasLeftCallout = false; + if ([[RCTConvert NSDictionary:json[@"leftCallout"]] count] > 0) { + shape.hasLeftCallout = true; + } + + shape.hasRightCallout = false; + if ([[RCTConvert NSDictionary:json[@"rightCallout"]] count] > 0) { + shape.hasRightCallout = true; + } + + shape.leftCalloutType = RCTPointAnnotationTypeButton; + if ([[RCTConvert NSString:json[@"leftCallout"][@"type"]] isEqual: @"image"]) { + shape.leftCalloutType = RCTPointAnnotationTypeImage; + } + + shape.rightCalloutType = RCTPointAnnotationTypeButton; + if ([[RCTConvert NSString:json[@"rightCallout"][@"type"]] isEqual: @"image"]) { + shape.rightCalloutType = RCTPointAnnotationTypeImage; + } + return shape; } diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index f4ee06f98f3a8c..d9b0df8341fb67 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -73,6 +73,66 @@ - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)vi } } +- (bool)isLocalImage: (NSDictionary *)config +{ + if (config[@"image"] != nil) { + if ([config[@"image"] isKindOfClass:[NSDictionary class]]) { + if ([config[@"image"] objectForKey:@"__packager_asset"] != nil) { + return true; + } + } + } + + return false; +} + +- (UIImageView *)generateCalloutImageAccessory:(NSDictionary *)config +{ + // Default width / height is 54 + int imageWidth = 54; + int imageHeight = 54; + + if (config[@"imageSize"] != nil) { + if ([config[@"imageSize"] objectForKey:@"height"] != nil) { + imageHeight = [RCTConvert int:config[@"imageSize"][@"height"]]; + } + + if ([config[@"imageSize"] objectForKey:@"width"] != nil) { + imageWidth = [RCTConvert int:config[@"imageSize"][@"width"]]; + } + } + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageWidth, imageHeight), NO, 0.0); + UIImage *blank = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIImageView *calloutImageView = [[UIImageView alloc] initWithImage:blank]; + + if ([self isLocalImage:config]) { + // We have a local ressource, no web loading necessary + NSString *ressourceName = [RCTConvert NSString:config[@"image"][@"uri"]]; + calloutImageView.image = [UIImage imageNamed:ressourceName]; + } else { + NSString *uri = [RCTConvert NSString:config[@"image"]]; + + if (config[@"defaultImage"] != nil) { + NSString *defaultImagePath = [RCTConvert NSString:config[@"defaultImage"][@"uri"]]; + calloutImageView.image = [UIImage imageNamed:defaultImagePath]; + } + + // Load the real image async and replace the image with it + [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:uri]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + if (!error) { + if ([@[@200, @301, @302, @304] containsObject:@([(NSHTTPURLResponse *)response statusCode])]) { + calloutImageView.image = [UIImage imageWithData:data]; + } + } + }]; + } + + return calloutImageView; +} + - (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTPointAnnotation *)annotation { if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { @@ -84,14 +144,20 @@ - (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(R annotationView.canShowCallout = true; annotationView.animatesDrop = annotation.animateDrop; - annotationView.leftCalloutAccessoryView = nil; - if (annotation.hasLeftCallout) { - annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; - } + for (NSString *side in @[@"left", @"right"]) { + NSString *accessoryViewSelector = [NSString stringWithFormat:@"%@CalloutAccessoryView", side]; + NSString *typeSelector = [NSString stringWithFormat:@"%@CalloutType", side]; + NSString *hasCheckSelector = [NSString stringWithFormat:@"has%@Callout", [side capitalizedString]]; + NSString *configSelector = [NSString stringWithFormat:@"%@CalloutConfig", side]; + + [annotationView setValue:nil forKey:accessoryViewSelector]; + if ([annotation valueForKey:hasCheckSelector]) { + [annotationView setValue:[UIButton buttonWithType:UIButtonTypeDetailDisclosure] forKey:accessoryViewSelector]; - annotationView.rightCalloutAccessoryView = nil; - if (annotation.hasRightCallout) { - annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + if ([[annotation valueForKey:typeSelector] integerValue] == RCTPointAnnotationTypeImage) { + [annotationView setValue:[self generateCalloutImageAccessory:[annotation valueForKey:configSelector]] forKey:accessoryViewSelector]; + } + } } return annotationView; @@ -158,6 +224,7 @@ - (void)mapViewWillStartRenderingMap:(RCTMap *)mapView [self _emitRegionChangeEvent:mapView continuous:NO]; } + #pragma mark Private - (void)_onTick:(NSTimer *)timer diff --git a/ReactAndroid/gradle.properties b/ReactAndroid/gradle.properties index 96433c14b48eed..a1c88c99757c0f 100644 --- a/ReactAndroid/gradle.properties +++ b/ReactAndroid/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=0.12.0-SNAPSHOT +VERSION_NAME=0.14.0 GROUP=com.facebook.react POM_NAME=ReactNative diff --git a/local-cli/generator-android/templates/src/app/build.gradle b/local-cli/generator-android/templates/src/app/build.gradle index 1298bb3f474c0c..05905f308991e8 100644 --- a/local-cli/generator-android/templates/src/app/build.gradle +++ b/local-cli/generator-android/templates/src/app/build.gradle @@ -74,5 +74,5 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.13.0" + compile "com.facebook.react:react-native:0.14.+" } diff --git a/package.json b/package.json index abf8fd6143a5f0..fcfa71d079a257 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native", - "version": "0.12.0", + "version": "0.14.0", "description": "A framework for building native apps using React", "license": "BSD-3-Clause", "repository": {