diff --git a/__tests__/__mocks__/react-native.mock.js b/__tests__/__mocks__/react-native.mock.js new file mode 100644 index 0000000..49cf25c --- /dev/null +++ b/__tests__/__mocks__/react-native.mock.js @@ -0,0 +1,3 @@ +jest.mock('react-native/Libraries/Image/resolveAssetSource', () => { + return () => ({ uri: `asset://test.png` }); +}); diff --git a/__tests__/utils/MapboxStyleSheet.test.js b/__tests__/utils/MapboxStyleSheet.test.js index 1fa3df0..0cf6cf5 100644 --- a/__tests__/utils/MapboxStyleSheet.test.js +++ b/__tests__/utils/MapboxStyleSheet.test.js @@ -24,6 +24,16 @@ describe('MapboxStyleSheet', () => { }); }); + it('should create asset image item for when we require images directly in JS', () => { + verifyStyleSheetsMatch({ fillPattern: 123 }, { + __MAPBOX_STYLESHEET__: true, + fillPattern: { + type: 'constant', + payload: { value: 'asset://test.png', image: true }, + }, + }); + }); + it('should create translate item', () => { verifyStyleSheetsMatch({ fillTranslate: { x: 1, y: 2 } }, { __MAPBOX_STYLESHEET__: true, diff --git a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/RCTMGLStyle.java b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/RCTMGLStyle.java index 01bb41b..0abcf5d 100644 --- a/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/RCTMGLStyle.java +++ b/android/rctmgl/src/main/java/com/mapbox/rctmgl/components/styles/RCTMGLStyle.java @@ -1,8 +1,10 @@ package com.mapbox.rctmgl.components.styles; +import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.StringDef; +import com.facebook.common.util.UriUtil; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -57,16 +59,20 @@ public class RCTMGLStyle { } public void addImage(String uriStr) { - if (uriStr == null || isTokenString(uriStr)) { + if (!shouldAddImage(uriStr)) { return; } - Map.Entry[] images = new Map.Entry[]{ new AbstractMap.SimpleEntry(uriStr, uriStr) }; DownloadMapImageTask task = new DownloadMapImageTask(mMap, null); task.execute(images); } - private boolean isTokenString(String str) { - return str.charAt(0) == '{' && str.charAt(str.length() - 1) == '}'; + private boolean shouldAddImage(String uriStr) { + return uriStr != null && isValidURI(uriStr); + } + + private boolean isValidURI(String str) { + Uri uri = Uri.parse(str); + return UriUtil.isLocalAssetUri(uri) || UriUtil.isNetworkUri(uri); } } diff --git a/ios/RCTMGL/RCTMGLStyle.m b/ios/RCTMGL/RCTMGLStyle.m index 6bffe47..c68f938 100644 --- a/ios/RCTMGL/RCTMGLStyle.m +++ b/ios/RCTMGL/RCTMGLStyle.m @@ -53,7 +53,7 @@ } else if ([prop isEqualToString:@"fillTranslateAnchor"]) { [self setFillTranslateAnchor:layer withReactStyleValue:styleValue]; } else if ([prop isEqualToString:@"fillPattern"]) { - if ([self _isTokenString:styleValue.payload[@"value"]]) { + if (![self _shouldAddImage:styleValue.payload[@"value"]]) { [self setFillPattern:layer withReactStyleValue:styleValue]; } else { [RCTMGLUtils fetchImage:_bridge url:styleValue.payload[@"value"] callback:^(NSError *error, UIImage *image) { @@ -131,7 +131,7 @@ } else if ([prop isEqualToString:@"lineDasharrayTransition"]) { [self setLineDasharrayTransition:layer withReactStyleValue:styleValue]; } else if ([prop isEqualToString:@"linePattern"]) { - if ([self _isTokenString:styleValue.payload[@"value"]]) { + if (![self _shouldAddImage:styleValue.payload[@"value"]]) { [self setLinePattern:layer withReactStyleValue:styleValue]; } else { [RCTMGLUtils fetchImage:_bridge url:styleValue.payload[@"value"] callback:^(NSError *error, UIImage *image) { @@ -185,7 +185,7 @@ } else if ([prop isEqualToString:@"iconTextFitPadding"]) { [self setIconTextFitPadding:layer withReactStyleValue:styleValue]; } else if ([prop isEqualToString:@"iconImage"]) { - if ([self _isTokenString:styleValue.payload[@"value"]]) { + if (![self _shouldAddImage:styleValue.payload[@"value"]]) { [self setIconImage:layer withReactStyleValue:styleValue]; } else { [RCTMGLUtils fetchImage:_bridge url:styleValue.payload[@"value"] callback:^(NSError *error, UIImage *image) { @@ -392,7 +392,7 @@ } else if ([prop isEqualToString:@"fillExtrusionTranslateAnchor"]) { [self setFillExtrusionTranslateAnchor:layer withReactStyleValue:styleValue]; } else if ([prop isEqualToString:@"fillExtrusionPattern"]) { - if ([self _isTokenString:styleValue.payload[@"value"]]) { + if (![self _shouldAddImage:styleValue.payload[@"value"]]) { [self setFillExtrusionPattern:layer withReactStyleValue:styleValue]; } else { [RCTMGLUtils fetchImage:_bridge url:styleValue.payload[@"value"] callback:^(NSError *error, UIImage *image) { @@ -491,7 +491,7 @@ } else if ([prop isEqualToString:@"backgroundColorTransition"]) { [self setBackgroundColorTransition:layer withReactStyleValue:styleValue]; } else if ([prop isEqualToString:@"backgroundPattern"]) { - if ([self _isTokenString:styleValue.payload[@"value"]]) { + if (![self _shouldAddImage:styleValue.payload[@"value"]]) { [self setBackgroundPattern:layer withReactStyleValue:styleValue]; } else { [RCTMGLUtils fetchImage:_bridge url:styleValue.payload[@"value"] callback:^(NSError *error, UIImage *image) { @@ -1847,12 +1847,15 @@ -- (BOOL)_isTokenString:(NSString *)str +- (BOOL)_shouldAddImage:(NSString *)str { - if (str == nil) { - return false; - } - return [str hasPrefix:@"{"] && [str hasSuffix:@"}"]; + return str != nil && [self _isValidURL:str]; +} + +- (BOOL)_isValidURL:(NSString *)str +{ + NSURL *url = [NSURL URLWithString:str]; + return [UIApplication.sharedApplication canOpenURL:url]; } - (BOOL)_hasReactStyle:(NSDictionary *)reactStyle diff --git a/javascript/utils/MapboxStyleSheet.js b/javascript/utils/MapboxStyleSheet.js index 557f264..26c1dd2 100644 --- a/javascript/utils/MapboxStyleSheet.js +++ b/javascript/utils/MapboxStyleSheet.js @@ -77,6 +77,21 @@ class MapStyleFunctionItem extends MapStyleItem { } } +function resolveImage (imageURL) { + let resolved = imageURL; + + if (typeof imageURL === 'number') { // required from JS, local file resolve it's asset filepath + const res = resolveAssetSource(imageURL); + + // we found a local uri + if (res.uri) { + resolved = res.uri; + } + } + + return resolved; +} + function makeStyleValue (prop, value, extras = {}) { let item; @@ -93,8 +108,7 @@ function makeStyleValue (prop, value, extras = {}) { } else if (styleMap[prop] === StyleTypes.Translation) { item = new MapStyleTranslationItem(value.x, value.y, extraData); } else if (styleMap[prop] === StyleTypes.Image) { - const res = resolveAssetSource(value) || {}; - item = new MapStyleConstantItem(res.uri || value, { image: true, ...extraData }); + item = new MapStyleConstantItem(resolveImage(value), { image: true, ...extraData }); } else { item = new MapStyleConstantItem(value, extraData); } diff --git a/package.json b/package.json index 4eedca3..adedbcd 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,8 @@ "javascript/**/*.js" ], "setupFiles": [ - "./__tests__/__mocks__/react-native-mapbox-gl.mock.js" + "./__tests__/__mocks__/react-native-mapbox-gl.mock.js", + "./__tests__/__mocks__/react-native.mock.js" ], "modulePathIgnorePatterns": [ "example", diff --git a/scripts/templates/RCTMGLStyle.m.ejs b/scripts/templates/RCTMGLStyle.m.ejs index dd69f38..6606ab4 100644 --- a/scripts/templates/RCTMGLStyle.m.ejs +++ b/scripts/templates/RCTMGLStyle.m.ejs @@ -36,7 +36,7 @@ <% for (let i = 0; i < layer.properties.length; i++) { -%> <%- ifOrElseIf(i) -%> ([prop isEqualToString:@"<%= layer.properties[i].name %>"]) { <%_ if (layer.properties[i].image) { _%> - if ([self _isTokenString:styleValue.payload[@"value"]]) { + if (![self _shouldAddImage:styleValue.payload[@"value"]]) { [self set<%- iosPropMethodName(layer, pascelCase(layer.properties[i].name)) -%>:layer withReactStyleValue:styleValue]; } else { [RCTMGLUtils fetchImage:_bridge url:styleValue.payload[@"value"] callback:^(NSError *error, UIImage *image) { @@ -88,12 +88,15 @@ <% } %> <% } %> -- (BOOL)_isTokenString:(NSString *)str +- (BOOL)_shouldAddImage:(NSString *)str { - if (str == nil) { - return false; - } - return [str hasPrefix:@"{"] && [str hasSuffix:@"}"]; + return str != nil && [self _isValidURL:str]; +} + +- (BOOL)_isValidURL:(NSString *)str +{ + NSURL *url = [NSURL URLWithString:str]; + return [UIApplication.sharedApplication canOpenURL:url]; } - (BOOL)_hasReactStyle:(NSDictionary *)reactStyle