feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#if __has_include(<React-RCTAppDelegate/RCTDependencyProvider.h>)
#import <React-RCTAppDelegate/RCTDependencyProvider.h>
#elif __has_include(<React_RCTAppDelegate/RCTDependencyProvider.h>)
#import <React_RCTAppDelegate/RCTDependencyProvider.h>
#else
#import "RCTDependencyProvider.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface RCTAppDependencyProvider : NSObject <RCTDependencyProvider>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTAppDependencyProvider.h"
#import <ReactCodegen/RCTModulesConformingToProtocolsProvider.h>
#import <ReactCodegen/RCTThirdPartyComponentsProvider.h>
@implementation RCTAppDependencyProvider {
NSArray<NSString *> * _URLRequestHandlerClassNames;
NSArray<NSString *> * _imageDataDecoderClassNames;
NSArray<NSString *> * _imageURLLoaderClassNames;
NSDictionary<NSString *,Class<RCTComponentViewProtocol>> * _thirdPartyFabricComponents;
}
- (nonnull NSArray<NSString *> *)URLRequestHandlerClassNames {
static dispatch_once_t requestUrlToken;
dispatch_once(&requestUrlToken, ^{
self->_URLRequestHandlerClassNames = RCTModulesConformingToProtocolsProvider.URLRequestHandlerClassNames;
});
return _URLRequestHandlerClassNames;
}
- (nonnull NSArray<NSString *> *)imageDataDecoderClassNames {
static dispatch_once_t dataDecoderToken;
dispatch_once(&dataDecoderToken, ^{
_imageDataDecoderClassNames = RCTModulesConformingToProtocolsProvider.imageDataDecoderClassNames;
});
return _imageDataDecoderClassNames;
}
- (nonnull NSArray<NSString *> *)imageURLLoaderClassNames {
static dispatch_once_t urlLoaderToken;
dispatch_once(&urlLoaderToken, ^{
_imageURLLoaderClassNames = RCTModulesConformingToProtocolsProvider.imageURLLoaderClassNames;
});
return _imageURLLoaderClassNames;
}
- (nonnull NSDictionary<NSString *,Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents {
static dispatch_once_t nativeComponentsToken;
dispatch_once(&nativeComponentsToken, ^{
_thirdPartyFabricComponents = RCTThirdPartyComponentsProvider.thirdPartyFabricComponents;
});
return _thirdPartyFabricComponents;
}
@end

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface RCTModulesConformingToProtocolsProvider: NSObject
+(NSArray<NSString *> *)imageURLLoaderClassNames;
+(NSArray<NSString *> *)imageDataDecoderClassNames;
+(NSArray<NSString *> *)URLRequestHandlerClassNames;
@end

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTModulesConformingToProtocolsProvider.h"
@implementation RCTModulesConformingToProtocolsProvider
+(NSArray<NSString *> *)imageURLLoaderClassNames
{
return @[
];
}
+(NSArray<NSString *> *)imageDataDecoderClassNames
{
return @[
];
}
+(NSArray<NSString *> *)URLRequestHandlerClassNames
{
return @[
];
}
@end

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol RCTComponentViewProtocol;
@interface RCTThirdPartyComponentsProvider: NSObject
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
@end

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RCTThirdPartyComponentsProvider.h"
#import <React/RCTComponentViewProtocol.h>
@implementation RCTThirdPartyComponentsProvider
+ (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
return @{
@"RNMapsGoogleMapView": NSClassFromString(@"RNMapsGoogleMapView"), // react-native-maps
@"RNMapsGooglePolygon": NSClassFromString(@"RNMapsGooglePolygonView"), // react-native-maps
@"RNMapsMapView": NSClassFromString(@"RNMapsMapView"), // react-native-maps
@"RNMapsMarker": NSClassFromString(@"RNMapsMarkerView"), // react-native-maps
};
}
@end

View File

@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
version = "0.77.2"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "ReactAppDependencyProvider"
s.version = version
s.summary = "The third party dependency provider for the app"
s.homepage = "https://reactnative.dev/"
s.documentation_url = "https://reactnative.dev/"
s.license = "MIT"
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "**/RCTAppDependencyProvider.{h,mm}"
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES"
}
s.dependency "ReactCodegen"
end

View File

@@ -0,0 +1,63 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJavaSpec.js
*
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import javax.annotation.Nonnull;
public abstract class NativeAirMapsModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "RNMapsAirModule";
public NativeAirMapsModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
@ReactMethod
@DoNotStrip
public abstract void getCamera(double tag, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getMarkersFrames(double tag, boolean onlyVisible, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getMapBoundaries(double tag, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void takeSnapshot(double tag, String config, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getAddressFromCoordinates(double tag, ReadableMap coordinate, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getPointForCoordinate(double tag, ReadableMap coordinate, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void getCoordinateForPoint(double tag, ReadableMap point, Promise promise);
}

View File

@@ -0,0 +1,35 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsCalloutManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsCalloutManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsCalloutManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "alphaHitTest":
mViewManager.setAlphaHitTest(view, value == null ? false : (boolean) value);
break;
case "tooltip":
mViewManager.setTooltip(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,18 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsCalloutManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setAlphaHitTest(T view, boolean value);
void setTooltip(T view, boolean value);
}

View File

@@ -0,0 +1,49 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsCircleManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsCircleManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsCircleManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "center":
mViewManager.setCenter(view, (ReadableMap) value);
break;
case "fillColor":
mViewManager.setFillColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "radius":
mViewManager.setRadius(view, value == null ? Double.NaN : ((Double) value).doubleValue());
break;
case "strokeColor":
mViewManager.setStrokeColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeWidth":
mViewManager.setStrokeWidth(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsCircleManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCenter(T view, @Nullable ReadableMap value);
void setFillColor(T view, @Nullable Integer value);
void setRadius(T view, double value);
void setStrokeColor(T view, @Nullable Integer value);
void setStrokeWidth(T view, float value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,52 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsGooglePolygonManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsGooglePolygonManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsGooglePolygonManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "coordinates":
mViewManager.setCoordinates(view, (ReadableArray) value);
break;
case "fillColor":
mViewManager.setFillColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeColor":
mViewManager.setStrokeColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeWidth":
mViewManager.setStrokeWidth(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "geodesic":
mViewManager.setGeodesic(view, value == null ? false : (boolean) value);
break;
case "holes":
mViewManager.setHoles(view, (ReadableArray) value);
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,25 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsGooglePolygonManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCoordinates(T view, @Nullable ReadableArray value);
void setFillColor(T view, @Nullable Integer value);
void setStrokeColor(T view, @Nullable Integer value);
void setStrokeWidth(T view, float value);
void setGeodesic(T view, boolean value);
void setHoles(T view, @Nullable ReadableArray value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,215 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsMapViewManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsMapViewManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsMapViewManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "cacheEnabled":
mViewManager.setCacheEnabled(view, value == null ? false : (boolean) value);
break;
case "camera":
mViewManager.setCamera(view, (ReadableMap) value);
break;
case "compassOffset":
mViewManager.setCompassOffset(view, (ReadableMap) value);
break;
case "followsUserLocation":
mViewManager.setFollowsUserLocation(view, value == null ? false : (boolean) value);
break;
case "poiClickEnabled":
mViewManager.setPoiClickEnabled(view, value == null ? false : (boolean) value);
break;
case "initialCamera":
mViewManager.setInitialCamera(view, (ReadableMap) value);
break;
case "initialRegion":
mViewManager.setInitialRegion(view, (ReadableMap) value);
break;
case "kmlSrc":
mViewManager.setKmlSrc(view, value == null ? null : (String) value);
break;
case "legalLabelInsets":
mViewManager.setLegalLabelInsets(view, (ReadableMap) value);
break;
case "liteMode":
mViewManager.setLiteMode(view, value == null ? false : (boolean) value);
break;
case "googleMapId":
mViewManager.setGoogleMapId(view, value == null ? null : (String) value);
break;
case "googleRenderer":
mViewManager.setGoogleRenderer(view, (String) value);
break;
case "loadingBackgroundColor":
mViewManager.setLoadingBackgroundColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "loadingEnabled":
mViewManager.setLoadingEnabled(view, value == null ? false : (boolean) value);
break;
case "loadingIndicatorColor":
mViewManager.setLoadingIndicatorColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "mapPadding":
mViewManager.setMapPadding(view, (ReadableMap) value);
break;
case "mapType":
mViewManager.setMapType(view, (String) value);
break;
case "maxDelta":
mViewManager.setMaxDelta(view, value == null ? 0f : ((Double) value).doubleValue());
break;
case "maxZoom":
mViewManager.setMaxZoom(view, value == null ? 0f : ((Double) value).floatValue());
break;
case "minDelta":
mViewManager.setMinDelta(view, value == null ? 0f : ((Double) value).doubleValue());
break;
case "minZoom":
mViewManager.setMinZoom(view, value == null ? 0f : ((Double) value).floatValue());
break;
case "moveOnMarkerPress":
mViewManager.setMoveOnMarkerPress(view, value == null ? true : (boolean) value);
break;
case "handlePanDrag":
mViewManager.setHandlePanDrag(view, value == null ? false : (boolean) value);
break;
case "paddingAdjustmentBehavior":
mViewManager.setPaddingAdjustmentBehavior(view, (String) value);
break;
case "pitchEnabled":
mViewManager.setPitchEnabled(view, value == null ? true : (boolean) value);
break;
case "region":
mViewManager.setRegion(view, (ReadableMap) value);
break;
case "rotateEnabled":
mViewManager.setRotateEnabled(view, value == null ? true : (boolean) value);
break;
case "scrollDuringRotateOrZoomEnabled":
mViewManager.setScrollDuringRotateOrZoomEnabled(view, value == null ? true : (boolean) value);
break;
case "scrollEnabled":
mViewManager.setScrollEnabled(view, value == null ? true : (boolean) value);
break;
case "showsBuildings":
mViewManager.setShowsBuildings(view, value == null ? true : (boolean) value);
break;
case "showsCompass":
mViewManager.setShowsCompass(view, value == null ? true : (boolean) value);
break;
case "showsIndoorLevelPicker":
mViewManager.setShowsIndoorLevelPicker(view, value == null ? false : (boolean) value);
break;
case "showsIndoors":
mViewManager.setShowsIndoors(view, value == null ? true : (boolean) value);
break;
case "showsPointsOfInterests":
mViewManager.setShowsPointsOfInterests(view, value == null ? true : (boolean) value);
break;
case "pointsOfInterestFilter":
mViewManager.setPointsOfInterestFilter(view, (ReadableArray) value);
break;
case "showsMyLocationButton":
mViewManager.setShowsMyLocationButton(view, value == null ? false : (boolean) value);
break;
case "showsScale":
mViewManager.setShowsScale(view, value == null ? false : (boolean) value);
break;
case "showsUserLocation":
mViewManager.setShowsUserLocation(view, value == null ? false : (boolean) value);
break;
case "tintColor":
mViewManager.setTintColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "toolbarEnabled":
mViewManager.setToolbarEnabled(view, value == null ? true : (boolean) value);
break;
case "userInterfaceStyle":
mViewManager.setUserInterfaceStyle(view, (String) value);
break;
case "customMapStyleString":
mViewManager.setCustomMapStyleString(view, value == null ? null : (String) value);
break;
case "userLocationAnnotationTitle":
mViewManager.setUserLocationAnnotationTitle(view, value == null ? null : (String) value);
break;
case "userLocationCalloutEnabled":
mViewManager.setUserLocationCalloutEnabled(view, value == null ? false : (boolean) value);
break;
case "userLocationFastestInterval":
mViewManager.setUserLocationFastestInterval(view, value == null ? 5000 : ((Double) value).intValue());
break;
case "userLocationPriority":
mViewManager.setUserLocationPriority(view, (String) value);
break;
case "userLocationUpdateInterval":
mViewManager.setUserLocationUpdateInterval(view, value == null ? 5000 : ((Double) value).intValue());
break;
case "zoomControlEnabled":
mViewManager.setZoomControlEnabled(view, value == null ? true : (boolean) value);
break;
case "zoomEnabled":
mViewManager.setZoomEnabled(view, value == null ? true : (boolean) value);
break;
case "showsTraffic":
mViewManager.setShowsTraffic(view, value == null ? false : (boolean) value);
break;
case "zoomTapEnabled":
mViewManager.setZoomTapEnabled(view, value == null ? true : (boolean) value);
break;
case "cameraZoomRange":
mViewManager.setCameraZoomRange(view, (ReadableMap) value);
break;
default:
super.setProperty(view, propName, value);
}
}
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
case "animateToRegion":
mViewManager.animateToRegion(view, args.getString(0), args.getInt(1));
break;
case "setCamera":
mViewManager.setCamera(view, args.getString(0));
break;
case "animateCamera":
mViewManager.animateCamera(view, args.getString(0), args.getInt(1));
break;
case "fitToElements":
mViewManager.fitToElements(view, args.getString(0), args.getBoolean(1));
break;
case "fitToSuppliedMarkers":
mViewManager.fitToSuppliedMarkers(view, args.getString(0), args.getString(1), args.getBoolean(2));
break;
case "fitToCoordinates":
mViewManager.fitToCoordinates(view, args.getString(0), args.getString(1), args.getBoolean(2));
break;
case "setIndoorActiveLevelIndex":
mViewManager.setIndoorActiveLevelIndex(view, args.getInt(0));
break;
}
}
}

View File

@@ -0,0 +1,78 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsMapViewManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCacheEnabled(T view, boolean value);
void setCamera(T view, @Nullable ReadableMap value);
void setCompassOffset(T view, @Nullable ReadableMap value);
void setFollowsUserLocation(T view, boolean value);
void setPoiClickEnabled(T view, boolean value);
void setInitialCamera(T view, @Nullable ReadableMap value);
void setInitialRegion(T view, @Nullable ReadableMap value);
void setKmlSrc(T view, @Nullable String value);
void setLegalLabelInsets(T view, @Nullable ReadableMap value);
void setLiteMode(T view, boolean value);
void setGoogleMapId(T view, @Nullable String value);
void setGoogleRenderer(T view, @Nullable String value);
void setLoadingBackgroundColor(T view, @Nullable Integer value);
void setLoadingEnabled(T view, boolean value);
void setLoadingIndicatorColor(T view, @Nullable Integer value);
void setMapPadding(T view, @Nullable ReadableMap value);
void setMapType(T view, @Nullable String value);
void setMaxDelta(T view, double value);
void setMaxZoom(T view, float value);
void setMinDelta(T view, double value);
void setMinZoom(T view, float value);
void setMoveOnMarkerPress(T view, boolean value);
void setHandlePanDrag(T view, boolean value);
void setPaddingAdjustmentBehavior(T view, @Nullable String value);
void setPitchEnabled(T view, boolean value);
void setRegion(T view, @Nullable ReadableMap value);
void setRotateEnabled(T view, boolean value);
void setScrollDuringRotateOrZoomEnabled(T view, boolean value);
void setScrollEnabled(T view, boolean value);
void setShowsBuildings(T view, boolean value);
void setShowsCompass(T view, boolean value);
void setShowsIndoorLevelPicker(T view, boolean value);
void setShowsIndoors(T view, boolean value);
void setShowsPointsOfInterests(T view, boolean value);
void setPointsOfInterestFilter(T view, @Nullable ReadableArray value);
void setShowsMyLocationButton(T view, boolean value);
void setShowsScale(T view, boolean value);
void setShowsUserLocation(T view, boolean value);
void setTintColor(T view, @Nullable Integer value);
void setToolbarEnabled(T view, boolean value);
void setUserInterfaceStyle(T view, @Nullable String value);
void setCustomMapStyleString(T view, @Nullable String value);
void setUserLocationAnnotationTitle(T view, @Nullable String value);
void setUserLocationCalloutEnabled(T view, boolean value);
void setUserLocationFastestInterval(T view, int value);
void setUserLocationPriority(T view, @Nullable String value);
void setUserLocationUpdateInterval(T view, int value);
void setZoomControlEnabled(T view, boolean value);
void setZoomEnabled(T view, boolean value);
void setShowsTraffic(T view, boolean value);
void setZoomTapEnabled(T view, boolean value);
void setCameraZoomRange(T view, @Nullable ReadableMap value);
void animateToRegion(T view, String regionJSON, int duration);
void setCamera(T view, String cameraJSON);
void animateCamera(T view, String cameraJSON, int duration);
void fitToElements(T view, String edgePaddingJSON, boolean animated);
void fitToSuppliedMarkers(T view, String markersJSON, String edgePaddingJSON, boolean animated);
void fitToCoordinates(T view, String coordinatesJSON, String edgePaddingJSON, boolean animated);
void setIndoorActiveLevelIndex(T view, int activeLevelIndex);
}

View File

@@ -0,0 +1,110 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsMarkerManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsMarkerManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsMarkerManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "anchor":
mViewManager.setAnchor(view, (ReadableMap) value);
break;
case "calloutAnchor":
mViewManager.setCalloutAnchor(view, (ReadableMap) value);
break;
case "image":
mViewManager.setImage(view, (ReadableMap) value);
break;
case "calloutOffset":
mViewManager.setCalloutOffset(view, (ReadableMap) value);
break;
case "displayPriority":
mViewManager.setDisplayPriority(view, (String) value);
break;
case "centerOffset":
mViewManager.setCenterOffset(view, (ReadableMap) value);
break;
case "coordinate":
mViewManager.setCoordinate(view, (ReadableMap) value);
break;
case "description":
mViewManager.setDescription(view, value == null ? null : (String) value);
break;
case "draggable":
mViewManager.setDraggable(view, value == null ? false : (boolean) value);
break;
case "title":
mViewManager.setTitle(view, value == null ? null : (String) value);
break;
case "tracksViewChanges":
mViewManager.setTracksViewChanges(view, value == null ? true : (boolean) value);
break;
case "identifier":
mViewManager.setIdentifier(view, value == null ? null : (String) value);
break;
case "isPreselected":
mViewManager.setIsPreselected(view, value == null ? false : (boolean) value);
break;
case "opacity":
mViewManager.setOpacity(view, value == null ? 1f : ((Double) value).doubleValue());
break;
case "pinColor":
mViewManager.setPinColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "titleVisibility":
mViewManager.setTitleVisibility(view, (String) value);
break;
case "subtitleVisibility":
mViewManager.setSubtitleVisibility(view, (String) value);
break;
case "useLegacyPinView":
mViewManager.setUseLegacyPinView(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
case "animateToCoordinates":
mViewManager.animateToCoordinates(view, args.getDouble(0), args.getDouble(1), args.getInt(2));
break;
case "setCoordinates":
mViewManager.setCoordinates(view, args.getDouble(0), args.getDouble(1));
break;
case "showCallout":
mViewManager.showCallout(view);
break;
case "hideCallout":
mViewManager.hideCallout(view);
break;
case "redrawCallout":
mViewManager.redrawCallout(view);
break;
case "redraw":
mViewManager.redraw(view);
break;
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsMarkerManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setAnchor(T view, @Nullable ReadableMap value);
void setCalloutAnchor(T view, @Nullable ReadableMap value);
void setImage(T view, @Nullable ReadableMap value);
void setCalloutOffset(T view, @Nullable ReadableMap value);
void setDisplayPriority(T view, @Nullable String value);
void setCenterOffset(T view, @Nullable ReadableMap value);
void setCoordinate(T view, @Nullable ReadableMap value);
void setDescription(T view, @Nullable String value);
void setDraggable(T view, boolean value);
void setTitle(T view, @Nullable String value);
void setTracksViewChanges(T view, boolean value);
void setIdentifier(T view, @Nullable String value);
void setIsPreselected(T view, boolean value);
void setOpacity(T view, double value);
void setPinColor(T view, @Nullable Integer value);
void setTitleVisibility(T view, @Nullable String value);
void setSubtitleVisibility(T view, @Nullable String value);
void setUseLegacyPinView(T view, boolean value);
void animateToCoordinates(T view, double latitude, double longitude, int duration);
void setCoordinates(T view, double latitude, double longitude);
void showCallout(T view);
void hideCallout(T view);
void redrawCallout(T view);
void redraw(T view);
}

View File

@@ -0,0 +1,45 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsOverlayManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsOverlayManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsOverlayManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "bearing":
mViewManager.setBearing(view, value == null ? 0f : ((Double) value).floatValue());
break;
case "bounds":
mViewManager.setBounds(view, (ReadableMap) value);
break;
case "image":
mViewManager.setImage(view, (ReadableMap) value);
break;
case "opacity":
mViewManager.setOpacity(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,23 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsOverlayManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setBearing(T view, float value);
void setBounds(T view, @Nullable ReadableMap value);
void setImage(T view, @Nullable ReadableMap value);
void setOpacity(T view, float value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,58 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ColorPropConverter;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsPolylineManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsPolylineManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsPolylineManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "coordinates":
mViewManager.setCoordinates(view, (ReadableArray) value);
break;
case "geodesic":
mViewManager.setGeodesic(view, value == null ? false : (boolean) value);
break;
case "lineCap":
mViewManager.setLineCap(view, (String) value);
break;
case "lineDashPattern":
mViewManager.setLineDashPattern(view, (ReadableArray) value);
break;
case "lineJoin":
mViewManager.setLineJoin(view, (String) value);
break;
case "strokeColor":
mViewManager.setStrokeColor(view, ColorPropConverter.getColor(value, view.getContext()));
break;
case "strokeColors":
mViewManager.setStrokeColors(view, (ReadableArray) value);
break;
case "strokeWidth":
mViewManager.setStrokeWidth(view, value == null ? 1f : ((Double) value).floatValue());
break;
case "tappable":
mViewManager.setTappable(view, value == null ? false : (boolean) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsPolylineManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setCoordinates(T view, @Nullable ReadableArray value);
void setGeodesic(T view, boolean value);
void setLineCap(T view, @Nullable String value);
void setLineDashPattern(T view, @Nullable ReadableArray value);
void setLineJoin(T view, @Nullable String value);
void setStrokeColor(T view, @Nullable Integer value);
void setStrokeColors(T view, @Nullable ReadableArray value);
void setStrokeWidth(T view, float value);
void setTappable(T view, boolean value);
}

View File

@@ -0,0 +1,62 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsUrlTileManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsUrlTileManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsUrlTileManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "doubleTileSize":
mViewManager.setDoubleTileSize(view, value == null ? false : (boolean) value);
break;
case "flipY":
mViewManager.setFlipY(view, value == null ? false : (boolean) value);
break;
case "maximumNativeZ":
mViewManager.setMaximumNativeZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "maximumZ":
mViewManager.setMaximumZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "minimumZ":
mViewManager.setMinimumZ(view, value == null ? 0 : ((Double) value).intValue());
break;
case "offlineMode":
mViewManager.setOfflineMode(view, value == null ? false : (boolean) value);
break;
case "shouldReplaceMapContent":
mViewManager.setShouldReplaceMapContent(view, value == null ? false : (boolean) value);
break;
case "tileCacheMaxAge":
mViewManager.setTileCacheMaxAge(view, value == null ? 0 : ((Double) value).intValue());
break;
case "tileCachePath":
mViewManager.setTileCachePath(view, value == null ? null : (String) value);
break;
case "tileSize":
mViewManager.setTileSize(view, value == null ? 256 : ((Double) value).intValue());
break;
case "urlTemplate":
mViewManager.setUrlTemplate(view, value == null ? null : (String) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsUrlTileManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setDoubleTileSize(T view, boolean value);
void setFlipY(T view, boolean value);
void setMaximumNativeZ(T view, int value);
void setMaximumZ(T view, int value);
void setMinimumZ(T view, int value);
void setOfflineMode(T view, boolean value);
void setShouldReplaceMapContent(T view, boolean value);
void setTileCacheMaxAge(T view, int value);
void setTileCachePath(T view, @Nullable String value);
void setTileSize(T view, int value);
void setUrlTemplate(T view, @Nullable String value);
}

View File

@@ -0,0 +1,56 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaDelegate.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;
public class RNMapsWMSTileManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNMapsWMSTileManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNMapsWMSTileManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "maximumNativeZ":
mViewManager.setMaximumNativeZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "maximumZ":
mViewManager.setMaximumZ(view, value == null ? 100 : ((Double) value).intValue());
break;
case "minimumZ":
mViewManager.setMinimumZ(view, value == null ? 0 : ((Double) value).intValue());
break;
case "offlineMode":
mViewManager.setOfflineMode(view, value == null ? false : (boolean) value);
break;
case "shouldReplaceMapContent":
mViewManager.setShouldReplaceMapContent(view, value == null ? false : (boolean) value);
break;
case "tileCacheMaxAge":
mViewManager.setTileCacheMaxAge(view, value == null ? 0 : ((Double) value).intValue());
break;
case "tileCachePath":
mViewManager.setTileCachePath(view, value == null ? null : (String) value);
break;
case "tileSize":
mViewManager.setTileSize(view, value == null ? 256 : ((Double) value).intValue());
break;
case "urlTemplate":
mViewManager.setUrlTemplate(view, value == null ? null : (String) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GeneratePropsJavaInterface.js
*/
package com.facebook.react.viewmanagers;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface;
public interface RNMapsWMSTileManagerInterface<T extends View> extends ViewManagerWithGeneratedInterface {
void setMaximumNativeZ(T view, int value);
void setMaximumZ(T view, int value);
void setMinimumZ(T view, int value);
void setOfflineMode(T view, boolean value);
void setShouldReplaceMapContent(T view, boolean value);
void setTileCacheMaxAge(T view, int value);
void setTileCachePath(T view, @Nullable String value);
void setTileSize(T view, int value);
void setUrlTemplate(T view, @Nullable String value);
}

View File

@@ -0,0 +1,77 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCalloutManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCalloutManagerInterface;
import com.rnmaps.maps.MapCallout;
import com.rnmaps.maps.SizeReportingShadowNode;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = CalloutManager.REACT_CLASS)
public class CalloutManager extends ViewGroupManager<MapCallout> implements RNMapsCalloutManagerInterface<MapCallout> {
public CalloutManager(ReactApplicationContext context){
super(context);
}
private final RNMapsCalloutManagerDelegate<MapCallout, CalloutManager> delegate =
new RNMapsCalloutManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapCallout> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapCallout createViewInstance(ThemedReactContext context) {
return new MapCallout(context);
}
public static final String REACT_CLASS = "RNMapsCallout";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapCallout.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setAlphaHitTest(MapCallout view, boolean value) {
/// do nothing
}
@Override
public void setTooltip(MapCallout view, boolean value) {
view.setTooltip(value);
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// we use a custom shadow node that emits the width/height of the view
// after layout with the updateExtraData method. Without this, we can't generate
// a bitmap of the appropriate width/height of the rendered view.
return new SizeReportingShadowNode();
}
}

View File

@@ -0,0 +1,94 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCircleManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsCircleManagerInterface;
import com.rnmaps.maps.MapCircle;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = CircleManager.REACT_CLASS)
public class CircleManager extends ViewGroupManager<MapCircle> implements RNMapsCircleManagerInterface<MapCircle> {
public CircleManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsCircleManagerDelegate<MapCircle, CircleManager> delegate =
new RNMapsCircleManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapCircle> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapCircle createViewInstance(ThemedReactContext context) {
return new MapCircle(context);
}
public static final String REACT_CLASS = "RNMapsCircle";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapCircle.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setZIndex(MapCircle view, float value) {
view.setZIndex(value);
}
@Override
public void setCenter(MapCircle view, @Nullable ReadableMap value) {
view.setCenter(value);
}
@Override
public void setFillColor(MapCircle view, @Nullable Integer value) {
view.setFillColor(value);
}
@Override
public void setRadius(MapCircle view, double value) {
view.setRadius(value);
}
@Override
public void setStrokeColor(MapCircle view, @Nullable Integer value) {
view.setStrokeColor(value);
}
@Override
public void setStrokeWidth(MapCircle view, float value) {
view.setStrokeWidth(value);
}
@Override
public void setTappable(MapCircle view, boolean value) {
view.setTappable(value);
}
}

View File

@@ -0,0 +1,86 @@
package com.rnmaps.fabric;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
public class JSONUtil {
public static WritableMap convertJsonToWritable(JSONObject jsonObject) throws JSONException {
WritableMap map = Arguments.createMap();
Iterator<String> iterator = jsonObject.keys();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = jsonObject.get(key);
if (value == JSONObject.NULL) {
map.putNull(key);
}
// Check primitive types first
else if (value instanceof Integer) {
map.putInt(key, jsonObject.getInt(key));
}
else if (value instanceof Long) {
map.putDouble(key, ((Long) value).doubleValue());
}
else if (value instanceof Double) {
map.putDouble(key, jsonObject.getDouble(key));
}
else if (value instanceof Boolean) {
map.putBoolean(key, jsonObject.getBoolean(key));
}
else if (value instanceof String) {
map.putString(key, jsonObject.getString(key));
}
// Check complex types
else if (value instanceof JSONObject) {
map.putMap(key, convertJsonToWritable(jsonObject.getJSONObject(key)));
}
else if (value instanceof JSONArray) {
map.putArray(key, convertJsonArrayToWritable(jsonObject.getJSONArray(key)));
}
}
return map;
}
public static WritableArray convertJsonArrayToWritable(JSONArray jsonArray) throws JSONException {
WritableArray array = Arguments.createArray();
for (int i = 0; i < jsonArray.length(); i++) {
Object value = jsonArray.get(i);
if (value == JSONObject.NULL) {
array.pushNull();
}
else if (value instanceof Integer) {
array.pushInt((Integer) value);
}
else if (value instanceof Long) {
array.pushDouble(((Long) value).doubleValue());
}
else if (value instanceof Double) {
array.pushDouble((Double) value);
}
else if (value instanceof Boolean) {
array.pushBoolean((Boolean) value);
}
else if (value instanceof String) {
array.pushString((String) value);
}
else if (value instanceof JSONObject) {
array.pushMap(convertJsonToWritable((JSONObject) value));
}
else if (value instanceof JSONArray) {
array.pushArray(convertJsonArrayToWritable((JSONArray) value));
}
}
return array;
}
}

View File

@@ -0,0 +1,642 @@
package com.rnmaps.fabric;
import static com.rnmaps.maps.MapManager.MY_LOCATION_PRIORITY;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsMapViewManagerInterface;
import com.facebook.react.viewmanagers.RNMapsMapViewManagerDelegate;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapColorScheme;
import com.rnmaps.maps.MapMarker;
import com.rnmaps.maps.MapView;
import com.rnmaps.maps.SizeReportingShadowNode;
import java.util.Map;
@ReactModule(name = MapViewManager.REACT_CLASS)
public class MapViewManager extends ViewGroupManager<MapView> implements RNMapsMapViewManagerInterface<MapView> {
private static boolean rendererInitialized = false;
private final RNMapsMapViewManagerDelegate<MapView, MapViewManager> delegate =
new RNMapsMapViewManagerDelegate<>(this);
public MapViewManager(ReactApplicationContext context) {
super(context);
}
@Override
public ViewManagerDelegate<MapView> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapView createViewInstance(ThemedReactContext context) {
return new MapView(context, new GoogleMapOptions());
}
@Override
public int getChildCount(MapView view) {
return view.getFeatureCount();
}
@Override
public View getChildAt(MapView view, int index) {
return view.getFeatureAt(index);
}
@Override
public void removeViewAt(MapView parent, int index) {
parent.removeFeatureAt(index);
}
@Override
protected void setupViewRecycling() {
// override parent to block recycling / allow reliable GoogleMapsOptions passing
}
private GoogleMapOptions optionsForInitialProps(ReactStylesDiffMap initialProps){
GoogleMapOptions options = new GoogleMapOptions();
if (initialProps != null) {
setGoogleRenderer(null, initialProps.getString("googleRenderer"));
if (initialProps.hasKey("liteMode")) {
options.liteMode(initialProps.getBoolean("liteMode", false));
}
if (initialProps.hasKey("googleMapId")) {
String googleMapId = initialProps.getString("googleMapId");
options.mapId(googleMapId);
}
if (initialProps.getMap("initialCamera") != null) {
ReadableMap initialCamera = initialProps.getMap("initialCamera");
CameraPosition camera = MapView.cameraPositionFromMap(initialCamera);
if (camera != null) {
options.camera(camera);
}
}
if (initialProps.hasKey("mapType")) {
if (initialProps.getString("mapType") != null) {
options.mapType(mapTypeFromStrValue(initialProps.getString("mapType")));
}
}
if (initialProps.hasKey("minZoom")) {
if (initialProps.getInt("minZoom", 0) != 0) {
options.minZoomPreference(initialProps.getInt("minZoom", 0));
}
}
if (initialProps.hasKey("maxZoom")) {
if (initialProps.getInt("maxZoom", 0) != 0) {
options.maxZoomPreference(initialProps.getInt("maxZoom", 0));
}
}
if (initialProps.hasKey("userInterfaceStyle") && !initialProps.hasKey("liteMode")){
String style = initialProps.getString("userInterfaceStyle");
if ("system".equals(style)) {
options.mapColorScheme(MapColorScheme.FOLLOW_SYSTEM);
} else if ("light".equals(style)){
options.mapColorScheme(MapColorScheme.LIGHT);
} else if ("dark".equals(style)){
options.mapColorScheme(MapColorScheme.DARK);
}
}
if (initialProps.hasKey("pitchEnabled")) {
options.tiltGesturesEnabled(initialProps.getBoolean("pitchEnabled", true));
}
if (initialProps.hasKey("rotateEnabled")) {
options.rotateGesturesEnabled(initialProps.getBoolean("rotateEnabled", true));
}
if (initialProps.hasKey("scrollDuringRotateOrZoomEnabled")) {
options.scrollGesturesEnabledDuringRotateOrZoom(initialProps.getBoolean("scrollDuringRotateOrZoomEnabled", true));
}
if (initialProps.hasKey("scrollEnabled")) {
options.scrollGesturesEnabled(initialProps.getBoolean("scrollEnabled", true));
}
if (initialProps.hasKey("showsCompass")) {
options.compassEnabled(initialProps.getBoolean("showsCompass", true));
}
if (initialProps.hasKey("toolbarEnabled")) {
options.mapToolbarEnabled(initialProps.getBoolean("toolbarEnabled", true));
}
if (initialProps.hasKey("zoomControlEnabled")) {
options.zoomControlsEnabled(initialProps.getBoolean("zoomControlEnabled", true));
}
if (initialProps.hasKey("zoomEnabled")) {
options.zoomGesturesEnabled(initialProps.getBoolean("zoomEnabled", true));
}
}
return options;
}
@Override
protected MapView createViewInstance(int reactTag, @NonNull ThemedReactContext reactContext, @Nullable ReactStylesDiffMap initialProps, @Nullable StateWrapper stateWrapper) {
MapView view = null;
view = new MapView(reactContext, optionsForInitialProps(initialProps));
view.setId(reactTag);
this.addEventEmitters(reactContext, view);
if (initialProps != null) {
this.updateProperties(view, initialProps);
}
if (stateWrapper != null) {
Object extraData = this.updateState(view, initialProps, stateWrapper);
if (extraData != null) {
this.updateExtraData(view, extraData);
}
}
return view;
}
public static final String REACT_CLASS = "RNMapsMapView";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapView.getExportedCustomBubblingEventTypeConstants();
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// A custom shadow node is needed in order to pass back the width/height of the map to the
// view manager so that it can start applying camera moves with bounds.
return new SizeReportingShadowNode();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapView.getExportedCustomDirectEventTypeConstants();
}
@Override
public void setCacheEnabled(MapView view, boolean value) {
view.setCacheEnabled(value);
}
@Override
public void setCamera(MapView view, @Nullable ReadableMap value) {
view.setCamera(value);
}
@Override
public void setCompassOffset(MapView view, @Nullable ReadableMap value) {
// not supported
}
@Override
public void setFollowsUserLocation(MapView view, boolean value) {
// not supported
}
@Override
public void setPoiClickEnabled(MapView view, boolean value) {
view.setPoiClickEnabled(value);
}
@Override
public void setInitialCamera(MapView view, @Nullable ReadableMap value) {
CameraPosition camera = MapView.cameraPositionFromMap(value);
if (camera != null) {
view.setInitialCameraSet(true);
}
}
@Override
public void setInitialRegion(MapView view, @Nullable ReadableMap value) {
view.setInitialRegion(value);
}
@Override
public void setKmlSrc(MapView view, @Nullable String value) {
view.setKmlSrc(value);
}
@Override
public void setLegalLabelInsets(MapView view, @Nullable ReadableMap value) {
// not supported
}
@Override
public void setLiteMode(MapView view, boolean value) {
// do nothing (initialProp)
}
@Override
public void setGoogleMapId(MapView view, @Nullable String value) {
// do nothing (initialProp)
}
@Override
public void setGoogleRenderer(MapView view, @Nullable String value) {
if (!rendererInitialized) {
MapsInitializer.Renderer renderer = MapsInitializer.Renderer.LATEST;
if ("LEGACY".equals(value)) {
renderer = MapsInitializer.Renderer.LEGACY;
}
MapsInitializer.initialize(getReactApplicationContext(), renderer, r -> Log.d("AirMapRenderer", "Init with renderer: " + r));
rendererInitialized = true;
}
}
@Override
public void setLoadingBackgroundColor(MapView view, @Nullable Integer value) {
view.setLoadingBackgroundColor(value);
}
@Override
public void setLoadingEnabled(MapView view, boolean value) {
view.setLoadingEnabled(value);
}
@Override
public void setLoadingIndicatorColor(MapView view, @Nullable Integer value) {
view.setLoadingIndicatorColor(value);
}
@Override
public void setMapPadding(MapView view, @Nullable ReadableMap padding) {
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
double density = (double) view.getResources().getDisplayMetrics().density;
if (padding != null) {
if (padding.hasKey("left")) {
left = (int) (padding.getDouble("left") * density);
}
if (padding.hasKey("top")) {
top = (int) (padding.getDouble("top") * density);
}
if (padding.hasKey("right")) {
right = (int) (padding.getDouble("right") * density);
}
if (padding.hasKey("bottom")) {
bottom = (int) (padding.getDouble("bottom") * density);
}
}
view.applyBaseMapPadding(left, top, right, bottom);
}
@Override
public void addView(MapView parent, View child, int index) {
parent.addFeature(child, index);
if (child instanceof MapMarker && ((MapMarker) child).isLoadingImage()){
MapMarker markerView = (MapMarker) child;
// Marker is already added as invisible, restore visibility when image loads
markerView.setImageLoadedListener((uri, drawable, b) -> {
com.google.android.gms.maps.model.Marker googleMarker =
(com.google.android.gms.maps.model.Marker) markerView.getFeature();
if (googleMarker != null) {
// Restore original visibility (marker was hidden temporarily during image load)
if (View.VISIBLE == markerView.getVisibility()) {
googleMarker.setVisible(true);
}
}
markerView.update(true);
});
}
}
@Override
public void setMapType(MapView view, @Nullable String value) {
view.setMapType(mapTypeFromStrValue(value));
}
private static int mapTypeFromStrValue(String value) {
int mapType;
//hybrid | none | satellite | standard | terrain
if ("hybrid".equals(value)) {
mapType = GoogleMap.MAP_TYPE_HYBRID;
} else if ("none".equals(value)) {
mapType = GoogleMap.MAP_TYPE_NONE;
} else if ("satellite".equals(value)) {
mapType = GoogleMap.MAP_TYPE_SATELLITE;
} else if ("standard".equals(value)) {
mapType = GoogleMap.MAP_TYPE_NORMAL;
} else if ("terrain".equals(value)) {
mapType = GoogleMap.MAP_TYPE_TERRAIN;
} else {
mapType = GoogleMap.MAP_TYPE_NORMAL;
}
return mapType;
}
@Override
public void setMaxDelta(MapView view, double value) {
// not supported
}
@Override
public void setMaxZoom(MapView view, float value) {
view.setMaxZoomLevel(value);
}
@Override
public void setMinDelta(MapView view, double value) {
// not supported
}
@Override
public void setMinZoom(MapView view, float value) {
view.setMinZoomLevel(value);
}
@Override
public void setMoveOnMarkerPress(MapView view, boolean value) {
view.setMoveOnMarkerPress(value);
}
@Override
public void setHandlePanDrag(MapView view, boolean value) {
view.setHandlePanDrag(value);
}
@Override
public void setPaddingAdjustmentBehavior(MapView view, @Nullable String value) {
// not supported
}
@Override
public void setPitchEnabled(MapView view, boolean value) {
view.setPitchEnabled(value);
}
@Override
public void setRegion(MapView view, @Nullable ReadableMap value) {
view.setRegion(value);
}
@Override
public void setRotateEnabled(MapView view, boolean value) {
view.setRotateEnabled(value);
}
@Override
public void setScrollDuringRotateOrZoomEnabled(MapView view, boolean value) {
view.setScrollDuringRotateOrZoomEnabled(value);
}
@Override
public void setScrollEnabled(MapView view, boolean value) {
view.setScrollEnabled(value);
}
@Override
public void setShowsBuildings(MapView view, boolean value) {
view.setShowBuildings(value);
}
@Override
public void setShowsCompass(MapView view, boolean value) {
view.setShowsCompass(value);
}
@Override
public void setShowsIndoorLevelPicker(MapView view, boolean value) {
view.setShowsIndoorLevelPicker(value);
}
@Override
public void setShowsIndoors(MapView view, boolean value) {
view.setShowIndoors(value);
}
@Override
public void setShowsPointsOfInterests(MapView view, boolean value) {
// not supported
}
@Override
public void setPointsOfInterestFilter(MapView view, @Nullable ReadableArray value) {
// not supported
}
@Override
public void setShowsMyLocationButton(MapView view, boolean value) {
view.setShowsMyLocationButton(value);
}
@Override
public void setShowsScale(MapView view, boolean value) {
// not supported
}
@Override
public void setShowsUserLocation(MapView view, boolean value) {
view.setShowsUserLocation(value);
}
@Override
public void setTintColor(MapView view, @Nullable Integer value) {
// not supported
}
@Override
public void setToolbarEnabled(MapView view, boolean value) {
view.setToolbarEnabled(value);
}
@Override
public void setUserInterfaceStyle(MapView view, @Nullable String value) {
// do nothing (initialProp)
}
@Override
public void setCustomMapStyleString(MapView view, @Nullable String value) {
view.setMapStyle(value);
}
@Override
public void setUserLocationAnnotationTitle(MapView view, @Nullable String value) {
// not supported
}
@Override
public void setUserLocationCalloutEnabled(MapView view, boolean value) {
// not supported
}
@Override
public void setUserLocationFastestInterval(MapView view, int value) {
view.setUserLocationFastestInterval(value);
}
@Override
public void setUserLocationPriority(MapView view, @Nullable String value) {
view.setUserLocationPriority(MY_LOCATION_PRIORITY.get(value));
}
@Override
public void setUserLocationUpdateInterval(MapView view, int value) {
view.setUserLocationUpdateInterval(value);
}
@Override
public void setZoomControlEnabled(MapView view, boolean value) {
view.setZoomControlEnabled(value);
}
@Override
public void setZoomEnabled(MapView view, boolean value) {
view.setZoomEnabled(value);
}
@Override
public void setShowsTraffic(MapView view, boolean value) {
view.setShowsTraffic(value);
}
@Override
public void setZoomTapEnabled(MapView view, boolean value) {
// not supported
}
@Override
public void setCameraZoomRange(MapView view, @Nullable ReadableMap value) {
// not supported
}
@Override
public void animateToRegion(MapView view, String regionJSON, int duration) {
try {
JSONObject region = new JSONObject(regionJSON);
double lng = region.getDouble("longitude");
double lat = region.getDouble("latitude");
double lngDelta = region.getDouble("longitudeDelta");
double latDelta = region.getDouble("latitudeDelta");
LatLngBounds bounds = new LatLngBounds(
new LatLng(lat - latDelta / 2, lng - lngDelta / 2), // southwest
new LatLng(lat + latDelta / 2, lng + lngDelta / 2) // northeast
);
view.animateToRegion(bounds, duration);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void setCamera(MapView view, String cameraJSON) {
try {
JSONObject camera = new JSONObject(cameraJSON);
CameraPosition position = view.cameraPositionFromJSON(camera);
view.moveToCamera(position);
} catch (JSONException e) {
Log.e("MapViewManager", "parse camera exception " + e);
}
}
@Override
public void animateCamera(MapView view, String cameraJSON, int duration) {
try {
JSONObject camera = new JSONObject(cameraJSON);
CameraPosition position = view.cameraPositionFromJSON(camera);
view.animateToCamera(position, duration);
} catch (JSONException e) {
Log.e("MapViewManager", "parse camera exception " + e);
}
}
@Override
public void fitToElements(MapView view, String edgePaddingJSON, boolean animated) {
try {
WritableMap map = null;
if (edgePaddingJSON != null) {
JSONObject jsonObject = new JSONObject(edgePaddingJSON);
map = JSONUtil.convertJsonToWritable(jsonObject);
}
view.fitToElements(map, animated);
} catch (JSONException e){
Log.e("MapViewManager", "parse edgePaddingJSON exception " + e);
}
}
@Override
public void fitToSuppliedMarkers(MapView view, String markersJSON, String edgePaddingJSON, boolean animated) {
try {
WritableArray markers = null;
if (markersJSON != null) {
JSONArray array = new JSONArray(markersJSON);
markers = JSONUtil.convertJsonArrayToWritable(array);
}
WritableMap edgePadding = null;
if (edgePaddingJSON != null) {
JSONObject jsonObject = new JSONObject(edgePaddingJSON);
edgePadding = JSONUtil.convertJsonToWritable(jsonObject);
}
view.fitToSuppliedMarkers(markers, edgePadding, animated);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void fitToCoordinates(MapView view, String coordinatesJSON, String edgePaddingJSON, boolean animated) {
try {
WritableArray coordinates = null;
if (coordinatesJSON != null) {
JSONArray array = new JSONArray(coordinatesJSON);
coordinates = JSONUtil.convertJsonArrayToWritable(array);
}
WritableMap edgePadding = null;
if (edgePaddingJSON != null) {
JSONObject jsonObject = new JSONObject(edgePaddingJSON);
edgePadding = JSONUtil.convertJsonToWritable(jsonObject);
}
view.fitToCoordinates(coordinates, edgePadding, animated);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public void setIndoorActiveLevelIndex(MapView view, int activeLevelIndex) {
view.setIndoorActiveLevelIndex(activeLevelIndex);
}
@Override
public void onDropViewInstance(MapView view) {
view.doDestroy();
super.onDropViewInstance(view);
}
}

View File

@@ -0,0 +1,302 @@
package com.rnmaps.fabric;
import android.graphics.Color;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsMarkerManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsMarkerManagerInterface;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.rnmaps.maps.MapCallout;
import com.rnmaps.maps.MapMarker;
import java.util.Map;
@ReactModule(name = MarkerManager.REACT_CLASS)
public class MarkerManager extends ViewGroupManager<MapMarker> implements RNMapsMarkerManagerInterface<MapMarker> {
public MarkerManager(ReactApplicationContext context){
super(context);
}
private final RNMapsMarkerManagerDelegate<MapMarker, MarkerManager> delegate =
new RNMapsMarkerManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapMarker> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapMarker createViewInstance(ThemedReactContext context) {
return new MapMarker(context, null);
}
private MarkerOptions optionsForInitialProps(ReactStylesDiffMap initialProps){
MarkerOptions options = new MarkerOptions();
if (initialProps != null) {
if (initialProps.hasKey("opacity")) {
options.alpha(initialProps.getFloat("opacity", 1));
}
if (initialProps.hasKey("anchor")) {
ReadableMap map = initialProps.getMap("anchor");
float x = (float) (map != null && map.hasKey("x") ? map.getDouble("x") : 0.5);
float y = (float) (map != null && map.hasKey("y") ? map.getDouble("y") : 1.0);
options.anchor(x, y);
}
if (initialProps.hasKey("coordinate")) {
ReadableMap coordinate = initialProps.getMap("coordinate");
LatLng position = new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude"));
options.position(position);
}
if (initialProps.hasKey("title")) {
options.title(initialProps.getString("title"));
}
if (initialProps.hasKey("description")) {
options.snippet(initialProps.getString("description"));
}
if (initialProps.hasKey("draggable")){
options.draggable(initialProps.getBoolean("draggable", false));
}
if (initialProps.hasKey("rotation")){
options.rotation(initialProps.getFloat("rotation", 0));
}
if (initialProps.hasKey("flat")) {
options.flat(initialProps.getBoolean("flat", false));
}
if (initialProps.hasKey("calloutAnchor")){
ReadableMap map = initialProps.getMap("calloutAnchor");
float x = (float) (map != null && map.hasKey("x") ? map.getDouble("x") : 0.5);
float y = (float) (map != null && map.hasKey("y") ? map.getDouble("y") : 1.0);
options.infoWindowAnchor(x, y);
}
if (initialProps.hasKey("zIndex")){
options.zIndex(initialProps.getFloat("zIndex", 0));
}
}
return options;
}
@Override
protected MapMarker createViewInstance(int reactTag, @NonNull ThemedReactContext reactContext, @Nullable ReactStylesDiffMap initialProps, @Nullable StateWrapper stateWrapper) {
MapMarker view = null;
view = new MapMarker(reactContext, optionsForInitialProps(initialProps), null);
view.setId(reactTag);
this.addEventEmitters(reactContext, view);
if (initialProps != null) {
this.updateProperties(view, initialProps);
}
if (stateWrapper != null) {
Object extraData = this.updateState(view, initialProps, stateWrapper);
if (extraData != null) {
this.updateExtraData(view, extraData);
}
}
return view;
}
public static final String REACT_CLASS = "RNMapsMarker";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapMarker.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapMarker.getExportedCustomDirectEventTypeConstants();
}
@Override
public void setAnchor(MapMarker view, @Nullable ReadableMap map) {
double x = map != null && map.hasKey("x") ? map.getDouble("x") : 0.5;
double y = map != null && map.hasKey("y") ? map.getDouble("y") : 1.0;
view.setAnchor(x, y);
view.setUpdated(true);
}
@Override
public void setCalloutAnchor(MapMarker view, @Nullable ReadableMap value) {
}
@Override
public void setImage(MapMarker view, @Nullable ReadableMap value) {
if (value != null && value.hasKey("uri")) {
view.setImage(value.getString("uri"));
} else {
view.setImage(null);
}
view.setUpdated(true);
}
@Override
public void onDropViewInstance(MapMarker view) {
super.onDropViewInstance(view);
view.doDestroy();
}
@Override
public void setCalloutOffset(MapMarker view, @Nullable ReadableMap value) {
}
@Override
public void setDisplayPriority(MapMarker view, @Nullable String value) {
}
@Override
public void setCenterOffset(MapMarker view, @Nullable ReadableMap value) {
}
@Override
public void setCoordinate(MapMarker view, @Nullable ReadableMap value) {
view.setCoordinate(value);
view.setUpdated(true);
}
@Override
public void setDescription(MapMarker view, @Nullable String value) {
view.setSnippet(value);
view.setUpdated(true);
}
@Override
public void setDraggable(MapMarker view, boolean value) {
view.setDraggable(value);
view.setUpdated(true);
}
@Override
public void setTitle(MapMarker view, @Nullable String value) {
view.setTitle(value);
view.setUpdated(true);
}
@Override
public void setTracksViewChanges(MapMarker view, boolean value) {
view.setTracksViewChanges(value);
}
@Override
public void setIdentifier(MapMarker view, @Nullable String value) {
view.setIdentifier(value);
view.setUpdated(true);
}
@Override
public void setIsPreselected(MapMarker view, boolean value) {
}
@Override
public void setOpacity(MapMarker view, double value) {
view.setOpacity((float) value);
view.setUpdated(true);
}
@Override
public void setPinColor(MapMarker view, @Nullable Integer value) {
float[] hsv = new float[3];
Color.colorToHSV(value, hsv);
// NOTE: android only supports a hue
view.setMarkerHue(hsv[0]);
view.setUpdated(true);
}
@Override
public void setTitleVisibility(MapMarker view, @Nullable String value) {
}
@Override
public void setSubtitleVisibility(MapMarker view, @Nullable String value) {
}
@Override
public void setUseLegacyPinView(MapMarker view, boolean value) {
}
@Override
public void animateToCoordinates(MapMarker view, double latitude, double longitude, int duration) {
view.animateToCoodinate(new LatLng(latitude, longitude), duration);
}
@Override
public void setCoordinates(MapMarker view, double latitude, double longitude) {
view.setCoordinate(new LatLng(latitude, longitude));
view.setUpdated(true);
}
@Override
public void showCallout(MapMarker view) {
((Marker) view.getFeature()).showInfoWindow();
view.setUpdated(true);
}
@Override
public void hideCallout(MapMarker view) {
((Marker) view.getFeature()).hideInfoWindow();
view.setUpdated(true);
}
@Override
public void redrawCallout(MapMarker view) {
}
@Override
public void redraw(MapMarker view) {
view.redraw();
}
@Override
public void addView(MapMarker parent, View child, int index) {
// if an <Callout /> component is a child, then it is a callout view, NOT part of the
// marker.
if (child instanceof MapCallout) {
parent.setCalloutView((MapCallout) child);
} else {
super.addView(parent, child, index);
if (index == 0) {
child.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int newWidth = right - left;
int newHeight = bottom - top;
MapMarker marker = (MapMarker) v.getParent();
if(marker != null){
marker.update(newWidth, newHeight);
}
}
});
}
parent.update(true);
}
}
}

View File

@@ -0,0 +1,304 @@
package com.rnmaps.fabric;
import static com.rnmaps.maps.MapModule.SNAPSHOT_FORMAT_JPG;
import static com.rnmaps.maps.MapModule.SNAPSHOT_FORMAT_PNG;
import static com.rnmaps.maps.MapModule.SNAPSHOT_RESULT_BASE64;
import static com.rnmaps.maps.MapModule.SNAPSHOT_RESULT_FILE;
import static com.rnmaps.maps.MapModule.closeQuietly;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.location.Address;
import android.location.Geocoder;
import android.net.Uri;
import android.util.Base64;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
import com.facebook.fbreact.specs.NativeAirMapsModuleSpec;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.UIManagerHelper;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.rnmaps.maps.MapUIBlock;
import com.rnmaps.maps.MapView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class NativeAirMapsModule extends NativeAirMapsModuleSpec {
public NativeAirMapsModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public void getCamera(double tag, Promise promise) {
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
@Override
public void run() {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null || view.map == null) {
promise.reject("E_MAP_CAMERA", "Cannot get camera position because map view is null");
return;
}
CameraPosition position = view.map.getCameraPosition();
WritableMap map = Arguments.createMap();
WritableMap center = Arguments.createMap();
center.putDouble("latitude", position.target.latitude);
center.putDouble("longitude", position.target.longitude);
map.putMap("center", center);
map.putDouble("heading", position.bearing);
map.putDouble("pitch", position.tilt);
map.putDouble("zoom", position.zoom);
promise.resolve(map);
}
});
}
@Override
public void getMarkersFrames(double tag, boolean onlyVisible, Promise promise) {
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(() -> {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null) {
promise.reject("E_MAP_MARKERS", "Cannot get markers frames because map view is null");
return;
}
double[][] boundaries = view.getMarkersFrames(onlyVisible);
if (boundaries != null) {
WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();
northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);
coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);
promise.resolve(coordinates);
} else {
promise.resolve(null);
}
});
}
@Override
public void getMapBoundaries(double tag, Promise promise) {
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(() -> {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null) {
promise.reject("E_MAP_BOUNDARIES", "Cannot get map boundaries because map view is null");
return;
}
double[][] boundaries = view.getMapBoundaries();
if (boundaries == null) {
promise.reject("E_MAP_BOUNDARIES", "Map boundaries are null");
return;
}
WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();
northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);
coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);
promise.resolve(coordinates);
});
}
@Override
public void takeSnapshot(double tag, String config, Promise promise) {
WritableMap options = null;
try {
if (config != null) {
JSONObject jsonObject = new JSONObject(config);
options = JSONUtil.convertJsonToWritable(jsonObject);
WritableMap finalOptions = options;
final ReactApplicationContext context = getReactApplicationContext();
final String format = finalOptions.hasKey("format") ? finalOptions.getString("format") : "png";
final Bitmap.CompressFormat compressFormat =
format.equals(SNAPSHOT_FORMAT_PNG) ? Bitmap.CompressFormat.PNG :
format.equals(SNAPSHOT_FORMAT_JPG) ? Bitmap.CompressFormat.JPEG : null;
final double quality = finalOptions.hasKey("quality") ? finalOptions.getDouble("quality") : 1.0;
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
final Integer width =
finalOptions.hasKey("width") ? (int) (displayMetrics.density * finalOptions.getDouble("width")) : 0;
final Integer height =
finalOptions.hasKey("height") ? (int) (displayMetrics.density * finalOptions.getDouble("height")) : 0;
final String result = finalOptions.hasKey("result") ? finalOptions.getString("result") : "file";
UIManager uiManager = UIManagerHelper.getUIManagerForReactTag(getReactApplicationContext(), (int) tag);
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
@Override
public void run() {
MapView view = (MapView) uiManager.resolveView((int) tag);
if (view == null || view.map == null) {
promise.reject("E_SNAPSHOT", "Cannot take snapshot because map view or map is null");
return;
}
view.map.snapshot(snapshot -> {
// Convert image to requested width/height if necessary
if (snapshot == null) {
promise.reject("Failed to generate bitmap, snapshot = null");
return;
}
if ((width != 0) && (height != 0) &&
(width != snapshot.getWidth() || height != snapshot.getHeight())) {
snapshot = Bitmap.createScaledBitmap(snapshot, width, height, true);
}
// Save the snapshot to disk
if (result.equals(SNAPSHOT_RESULT_FILE)) {
File tempFile;
FileOutputStream outputStream;
try {
tempFile =
File.createTempFile("AirMapSnapshot", "." + format, context.getCacheDir());
outputStream = new FileOutputStream(tempFile);
} catch (Exception e) {
promise.reject(e);
return;
}
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
String uri = Uri.fromFile(tempFile).toString();
promise.resolve(uri);
} else if (result.equals(SNAPSHOT_RESULT_BASE64)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
byte[] bytes = outputStream.toByteArray();
String data = Base64.encodeToString(bytes, Base64.NO_WRAP);
promise.resolve(data);
}
});
}
});
}
} catch (JSONException e) {
promise.reject("Failed to parse config ", config);
}
}
@Override
public void getAddressFromCoordinates(double tag, ReadableMap coordinate, Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
if (coordinate == null ||
!coordinate.hasKey("latitude") ||
!coordinate.hasKey("longitude")) {
promise.reject("Invalid coordinate format");
return;
}
Geocoder geocoder = new Geocoder(context);
try {
List<Address> list =
geocoder.getFromLocation(coordinate.getDouble("latitude"), coordinate.getDouble("longitude"), 1);
if (list.isEmpty()) {
promise.reject("Can not get address location");
return;
}
Address address = list.get(0);
WritableMap addressJson = new WritableNativeMap();
addressJson.putString("name", address.getFeatureName());
addressJson.putString("locality", address.getLocality());
addressJson.putString("thoroughfare", address.getThoroughfare());
addressJson.putString("subThoroughfare", address.getSubThoroughfare());
addressJson.putString("subLocality", address.getSubLocality());
addressJson.putString("administrativeArea", address.getAdminArea());
addressJson.putString("subAdministrativeArea", address.getSubAdminArea());
addressJson.putString("postalCode", address.getPostalCode());
addressJson.putString("countryCode", address.getCountryCode());
addressJson.putString("country", address.getCountryName());
promise.resolve(addressJson);
} catch (IOException e) {
promise.reject("Can not get address location");
}
}
@Override
public void getPointForCoordinate(double tag, ReadableMap coordinate, Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final LatLng coord = new LatLng(
coordinate.hasKey("latitude") ? coordinate.getDouble("latitude") : 0.0,
coordinate.hasKey("longitude") ? coordinate.getDouble("longitude") : 0.0
);
MapUIBlock uiBlock = new MapUIBlock((int) tag, promise, context, view -> {
Point pt = view.map.getProjection().toScreenLocation(coord);
WritableMap ptJson = new WritableNativeMap();
ptJson.putDouble("x", (double) pt.x / density);
ptJson.putDouble("y", (double) pt.y / density);
promise.resolve(ptJson);
return null;
});
uiBlock.addToUIManager();
}
@Override
public void getCoordinateForPoint(double tag, ReadableMap point, Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final Point pt = new Point(
point.hasKey("x") ? (int) (point.getDouble("x") * density) : 0,
point.hasKey("y") ? (int) (point.getDouble("y") * density) : 0
);
MapUIBlock uiBlock = new MapUIBlock((int)tag, promise, context, view -> {
LatLng coord = view.map.getProjection().fromScreenLocation(pt);
WritableMap coordJson = new WritableNativeMap();
coordJson.putDouble("latitude", coord.latitude);
coordJson.putDouble("longitude", coord.longitude);
promise.resolve(coordJson);
return null;
});
uiBlock.addToUIManager();
}
}

View File

@@ -0,0 +1,101 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsOverlayManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsOverlayManagerInterface;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.rnmaps.maps.MapOverlay;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = OverlayManager.REACT_CLASS)
public class OverlayManager extends ViewGroupManager<MapOverlay> implements RNMapsOverlayManagerInterface<MapOverlay> {
public OverlayManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsOverlayManagerDelegate<MapOverlay, OverlayManager> delegate =
new RNMapsOverlayManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapOverlay> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapOverlay createViewInstance(ThemedReactContext context) {
return new MapOverlay(context);
}
public static final String REACT_CLASS = "RNMapsOverlay";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapOverlay.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setZIndex(MapOverlay view, float value) {
view.setZIndex(value);
}
@Override
public void setBearing(MapOverlay view, float value) {
view.setBearing(value);
}
@Override
public void setBounds(MapOverlay view, @Nullable ReadableMap value) {
LatLng sw = new LatLng(value.getMap("southWest").getDouble("latitude"),
value.getMap("southWest").getDouble("longitude"));
LatLng ne = new LatLng(value.getMap("northEast").getDouble("latitude"),
value.getMap("northEast").getDouble("longitude"));
LatLngBounds bounds = new LatLngBounds(sw, ne);
view.setBounds(bounds);
}
@Override
public void setImage(MapOverlay view, @Nullable ReadableMap value) {
if (value != null) {
view.setImage(value.getString("uri"));
}
}
@Override
public void setOpacity(MapOverlay view, float value) {
// todo report to google that it's not working / fix it
// view.setTransparency(value);
}
@Override
public void setTappable(MapOverlay view, boolean value) {
view.setTappable(value);
}
}

View File

@@ -0,0 +1,96 @@
package com.rnmaps.fabric;
import androidx.annotation.Nullable;
import android.util.DisplayMetrics;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsGooglePolygonManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsGooglePolygonManagerInterface;
import com.rnmaps.maps.MapPolygon;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = PolygonManager.REACT_CLASS)
public class PolygonManager extends ViewGroupManager<MapPolygon> implements RNMapsGooglePolygonManagerInterface<MapPolygon> {
public PolygonManager(ReactApplicationContext context){
super(context);
}//
private final RNMapsGooglePolygonManagerDelegate<MapPolygon, PolygonManager> delegate =
new RNMapsGooglePolygonManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapPolygon> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapPolygon createViewInstance(ThemedReactContext context) {
return new MapPolygon(context);
}
public static final String REACT_CLASS = "RNMapsGooglePolygon";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapPolygon.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setCoordinates(MapPolygon view, @Nullable ReadableArray value) {
view.setCoordinates(value);
}
@Override
public void setFillColor(MapPolygon view, @Nullable Integer value) {
view.setFillColor(value);
}
@Override
public void setStrokeColor(MapPolygon view, @Nullable Integer value) {
view.setStrokeColor(value);
}
@Override
public void setStrokeWidth(MapPolygon view, float value) {
DisplayMetrics metrics = view.getContext().getResources().getDisplayMetrics();
float widthInScreenPx = metrics.density * value; // done for parity with iOS
view.setStrokeWidth(widthInScreenPx);
}
@Override
public void setGeodesic(MapPolygon view, boolean value) {
view.setGeodesic(value);
}
@Override
public void setHoles(MapPolygon view, @Nullable ReadableArray value) {
view.setHoles(value);
}
@Override
public void setTappable(MapPolygon view, boolean value) {
view.setTappable(value);
}
}

View File

@@ -0,0 +1,121 @@
package com.rnmaps.fabric;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsPolylineManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsPolylineManagerInterface;
import com.rnmaps.maps.MapPolyline;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = PolylineManager.REACT_CLASS)
public class PolylineManager extends ViewGroupManager<MapPolyline> implements RNMapsPolylineManagerInterface<MapPolyline> {
private DisplayMetrics metrics;
public PolylineManager(ReactApplicationContext context) {
super(context);
metrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
private final RNMapsPolylineManagerDelegate<MapPolyline, PolylineManager> delegate =
new RNMapsPolylineManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapPolyline> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapPolyline createViewInstance(ThemedReactContext context) {
return new MapPolyline(context);
}
public static final String REACT_CLASS = "RNMapsPolyline";
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapPolyline.getExportedCustomBubblingEventTypeConstants();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setCoordinates(MapPolyline view, @Nullable ReadableArray value) {
view.setCoordinates(value);
}
@Override
public void setStrokeColor(MapPolyline view, @Nullable Integer value) {
view.setColor(value);
}
@Override
public void setStrokeColors(MapPolyline view, @Nullable ReadableArray value) {
view.setStrokeColors(value);
}
@Override
public void setStrokeWidth(MapPolyline view, float value) {
float widthInScreenPx = metrics.density * value; // done for parity with iOS
view.setWidth(widthInScreenPx);
}
@Override
public void setGeodesic(MapPolyline view, boolean value) {
view.setGeodesic(value);
}
@Override
public void setLineCap(MapPolyline view, @Nullable String value) {
view.setLineCap(value);
}
@Override
public void setLineDashPattern(MapPolyline view, @Nullable ReadableArray value) {
view.setLineDashPattern(value);
}
@Override
public void setLineJoin(MapPolyline view, @Nullable String value) {
view.setLineJoin(value);
}
@Override
public void setTappable(MapPolyline view, boolean value) {
view.setTappable(value);
}
@Override
public void setZIndex(MapPolyline view, float value) {
view.setZIndex(value);
}
}

View File

@@ -0,0 +1,124 @@
package com.rnmaps.fabric;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsUrlTileManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsUrlTileManagerInterface;
import com.rnmaps.maps.MapPolyline;
import com.rnmaps.maps.MapUrlTile;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = UrlTileManager.REACT_CLASS)
public class UrlTileManager extends ViewGroupManager<MapUrlTile> implements RNMapsUrlTileManagerInterface<MapUrlTile> {
public static final String REACT_CLASS = "RNMapsUrlTile";
public UrlTileManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsUrlTileManagerDelegate<MapUrlTile, UrlTileManager> delegate =
new RNMapsUrlTileManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapUrlTile> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapUrlTile createViewInstance(ThemedReactContext context) {
return new MapUrlTile(context);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return new HashMap<>();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setDoubleTileSize(MapUrlTile view, boolean value) {
view.setDoubleTileSize(value);
}
@Override
public void setFlipY(MapUrlTile view, boolean value) {
view.setFlipY(value);
}
@Override
public void setMaximumNativeZ(MapUrlTile view, int value) {
view.setMaximumNativeZ(value);
}
@Override
public void setMaximumZ(MapUrlTile view, int value) {
view.setMaximumZ(value);
}
@Override
public void setMinimumZ(MapUrlTile view, int value) {
view.setMinimumZ(value);
}
@Override
public void setOfflineMode(MapUrlTile view, boolean value) {
view.setOfflineMode(value);
}
@Override
public void setShouldReplaceMapContent(MapUrlTile view, boolean value) {
// not supported
}
@Override
public void setZIndex(@NonNull MapUrlTile view, float zIndex) {
super.setZIndex(view, zIndex);
view.setZIndex(zIndex);
}
@Override
public void setOpacity(@NonNull MapUrlTile view, float opacity) {
super.setOpacity(view, opacity);
view.setOpacity(opacity);
}
@Override
public void setTileCacheMaxAge(MapUrlTile view, int value) {
view.setTileCacheMaxAge(value);
}
@Override
public void setTileCachePath(MapUrlTile view, @Nullable String value) {
view.setTileCachePath(value);
}
@Override
public void setTileSize(MapUrlTile view, int value) {
view.setTileSize(value);
}
@Override
public void setUrlTemplate(MapUrlTile view, @Nullable String value) {
view.setUrlTemplate(value);
}
}

View File

@@ -0,0 +1,114 @@
package com.rnmaps.fabric;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsWMSTileManagerDelegate;
import com.facebook.react.viewmanagers.RNMapsWMSTileManagerInterface;
import com.rnmaps.maps.MapWMSTile;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = WMSTileManager.REACT_CLASS)
public class WMSTileManager extends ViewGroupManager<MapWMSTile> implements RNMapsWMSTileManagerInterface<MapWMSTile> {
public static final String REACT_CLASS = "RNMapsWMSTile";
public WMSTileManager(ReactApplicationContext context) {
super(context);
}
private final RNMapsWMSTileManagerDelegate<MapWMSTile, WMSTileManager> delegate =
new RNMapsWMSTileManagerDelegate<>(this);
@Override
public ViewManagerDelegate<MapWMSTile> getDelegate() {
return delegate;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public MapWMSTile createViewInstance(ThemedReactContext context) {
return new MapWMSTile(context);
}
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return new HashMap<>();
}
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return new HashMap<>();
}
@Override
public void setMaximumNativeZ(MapWMSTile view, int value) {
view.setMaximumNativeZ(value);
}
@Override
public void setMaximumZ(MapWMSTile view, int value) {
view.setMaximumZ(value);
}
@Override
public void setMinimumZ(MapWMSTile view, int value) {
view.setMinimumZ(value);
}
@Override
public void setOfflineMode(MapWMSTile view, boolean value) {
view.setOfflineMode(value);
}
@Override
public void setShouldReplaceMapContent(MapWMSTile view, boolean value) {
// not supported
}
@Override
public void setZIndex(@NonNull MapWMSTile view, float zIndex) {
super.setZIndex(view, zIndex);
view.setZIndex(zIndex);
}
@Override
public void setOpacity(@NonNull MapWMSTile view, float opacity) {
super.setOpacity(view, opacity);
view.setOpacity(opacity);
}
@Override
public void setTileCacheMaxAge(MapWMSTile view, int value) {
view.setTileCacheMaxAge(value);
}
@Override
public void setTileCachePath(MapWMSTile view, @Nullable String value) {
view.setTileCachePath(value);
}
@Override
public void setTileSize(MapWMSTile view, int value) {
view.setTileSize(value);
}
@Override
public void setUrlTemplate(MapWMSTile view, @Nullable String value) {
view.setUrlTemplate(value);
}
}

View File

@@ -0,0 +1,25 @@
package com.rnmaps.fabric.event;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnCalloutPressEvent extends Event<OnCalloutPressEvent> {
public static final String EVENT_NAME = "topCalloutPress";
private final WritableMap payload;
public OnCalloutPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDeselectEvent extends Event<OnDeselectEvent> {
public static final String EVENT_NAME = "topDeselect";
private final WritableMap payload;
public OnDeselectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,27 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDoublePressEvent extends Event<OnDoublePressEvent> {
public static final String EVENT_NAME = "topDoublePress";
private final WritableMap payload;
public OnDoublePressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDragEndEvent extends Event<OnDragEndEvent> {
public static final String EVENT_NAME = "topDragEnd";
private final WritableMap payload;
public OnDragEndEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDragEvent extends Event<OnDragEvent> {
public static final String EVENT_NAME = "topDrag";
private final WritableMap payload;
public OnDragEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnDragStartEvent extends Event<OnDragStartEvent> {
public static final String EVENT_NAME = "topDragStart";
private final WritableMap payload;
public OnDragStartEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnIndoorBuildingFocusedEvent extends Event<OnIndoorBuildingFocusedEvent> {
public static final String EVENT_NAME = "topIndoorBuildingFocused";
private final WritableMap payload;
public OnIndoorBuildingFocusedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnIndoorLevelActivatedEvent extends Event<OnIndoorLevelActivatedEvent> {
public static final String EVENT_NAME = "topIndoorLevelActivated";
private final WritableMap payload;
public OnIndoorLevelActivatedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnKmlReadyEvent extends Event<OnKmlReadyEvent> {
public static final String EVENT_NAME = "topKmlReady";
private final WritableMap payload;
public OnKmlReadyEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnLongPressEvent extends Event<OnLongPressEvent> {
public static final String EVENT_NAME = "topLongPress";
private final WritableMap payload;
public OnLongPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,29 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMapLoadedEvent extends Event<OnMapLoadedEvent> {
public static final String EVENT_NAME = "topMapLoaded";
private final WritableMap payload;
public OnMapLoadedEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,29 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMapReadyEvent extends Event<OnMapReadyEvent> {
public static final String EVENT_NAME = "topMapReady";
private final WritableMap payload;
public OnMapReadyEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDeselectEvent extends Event<OnMarkerDeselectEvent> {
public static final String EVENT_NAME = "topMarkerDeselect";
private final WritableMap payload;
public OnMarkerDeselectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDragEndEvent extends Event<OnMarkerDragEndEvent> {
public static final String EVENT_NAME = "topMarkerDragEnd";
private final WritableMap payload;
public OnMarkerDragEndEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDragEvent extends Event<OnMarkerDragEvent> {
public static final String EVENT_NAME = "topMarkerDrag";
private final WritableMap payload;
public OnMarkerDragEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerDragStartEvent extends Event<OnMarkerDragStartEvent> {
public static final String EVENT_NAME = "topMarkerDragStart";
private final WritableMap payload;
public OnMarkerDragStartEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerPressEvent extends Event<OnMarkerPressEvent> {
public static final String EVENT_NAME = "topMarkerPress";
private final WritableMap payload;
public OnMarkerPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnMarkerSelectEvent extends Event<OnMarkerSelectEvent> {
public static final String EVENT_NAME = "topMarkerSelect";
private final WritableMap payload;
public OnMarkerSelectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnPanDragEvent extends Event<OnPanDragEvent> {
public static final String EVENT_NAME = "topPanDrag";
private final WritableMap payload;
public OnPanDragEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,27 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnPoiClickEvent extends Event<OnPoiClickEvent> {
public static final String EVENT_NAME = "topPoiClick";
private final WritableMap payload;
public OnPoiClickEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,27 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnPressEvent extends Event<OnPressEvent> {
public static final String EVENT_NAME = "topPress";
private final WritableMap payload;
public OnPressEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,30 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnRegionChangeCompleteEvent extends Event<OnRegionChangeCompleteEvent> {
public static final String EVENT_NAME = "topRegionChangeComplete";
private final WritableMap payload;
public OnRegionChangeCompleteEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
protected WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,47 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.events.Event;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
public class OnRegionChangeEvent extends Event<OnRegionChangeEvent> {
public static final String EVENT_NAME = "topRegionChange";
private final WritableMap payload;
public OnRegionChangeEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
public static WritableMap payLoadFor(LatLngBounds bounds, boolean isGesture) {
WritableMap event = new WritableNativeMap();
WritableMap region = new WritableNativeMap();
LatLng center = bounds.getCenter();
region.putDouble("latitude", center.latitude);
region.putDouble("longitude", center.longitude);
region.putDouble("latitudeDelta", bounds.northeast.latitude - bounds.southwest.latitude);
region.putDouble("longitudeDelta", bounds.northeast.longitude - bounds.southwest.longitude);
event.putMap("region", region);
event.putBoolean("isGesture", isGesture);
return event;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
protected WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,33 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.uimanager.events.Event;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
public class OnRegionChangeStartEvent extends Event<OnRegionChangeStartEvent> {
public static final String EVENT_NAME = "topRegionChangeStart";
private final WritableMap payload;
public OnRegionChangeStartEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
protected WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnSelectEvent extends Event<OnSelectEvent> {
public static final String EVENT_NAME = "topSelect";
private final WritableMap payload;
public OnSelectEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,28 @@
package com.rnmaps.fabric.event;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
public class OnUserLocationChangeEvent extends Event<OnUserLocationChangeEvent> {
public static final String EVENT_NAME = "topUserLocationChange";
private final WritableMap payload;
public OnUserLocationChangeEvent(int surfaceId, int viewId, WritableMap payload) {
super(surfaceId, viewId);
this.payload = payload;
}
@NonNull
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public WritableMap getEventData() {
return payload;
}
}

View File

@@ -0,0 +1,74 @@
package com.rnmaps.maps;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import com.facebook.common.logging.FLog;
import com.facebook.react.common.ReactConstants;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
public class FileUtil extends AsyncTask<String, Void, InputStream> {
private Context context;
public FileUtil(Context context) {
super();
this.context = context;
}
protected InputStream doInBackground(String... urls) {
try {
Uri fileContentUri = Uri.parse(urls[0]);
if (fileContentUri.getScheme().startsWith("http")) {
return getDownloadFileInputStream(context, fileContentUri);
}
return context.getContentResolver().openInputStream(fileContentUri);
} catch (Exception e) {
FLog.e(
ReactConstants.TAG,
"Could not retrieve file for contentUri " + urls[0],
e);
return null;
}
}
private InputStream getDownloadFileInputStream(Context context, Uri uri)
throws IOException {
final File outputDir = context.getApplicationContext().getCacheDir();
String NAME = "FileUtil";
String TEMP_FILE_SUFFIX = "temp";
final File file = File.createTempFile(NAME, TEMP_FILE_SUFFIX, outputDir);
file.deleteOnExit();
final URL url = new URL(uri.toString());
final InputStream is = url.openStream();
try {
final ReadableByteChannel channel = Channels.newChannel(is);
try {
final FileOutputStream stream = new FileOutputStream(file);
try {
stream.getChannel().transferFrom(channel, 0, Long.MAX_VALUE);
return new FileInputStream(file);
} finally {
stream.close();
}
} finally {
channel.close();
}
} finally {
is.close();
}
}
}

View File

@@ -0,0 +1,75 @@
package com.rnmaps.maps;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.os.Looper;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.Priority;
import com.google.android.gms.maps.LocationSource;
import com.google.android.gms.tasks.OnSuccessListener;
import java.lang.SecurityException;
public class FusedLocationSource implements LocationSource {
private final FusedLocationProviderClient fusedLocationClientProviderClient;
private final LocationRequest locationRequest;
private LocationCallback locationCallback;
public FusedLocationSource(Context context){
fusedLocationClientProviderClient =
LocationServices.getFusedLocationProviderClient(context);
locationRequest = LocationRequest.create();
locationRequest.setPriority(Priority.PRIORITY_HIGH_ACCURACY);
locationRequest.setInterval(5000);
}
public void setPriority(int priority){
locationRequest.setPriority(priority);
}
public void setInterval(int interval){
locationRequest.setInterval(interval);
}
public void setFastestInterval(int fastestInterval){
locationRequest.setFastestInterval(fastestInterval);
}
@SuppressLint("MissingPermission")
@Override
public void activate(final OnLocationChangedListener onLocationChangedListener) {
try {
fusedLocationClientProviderClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
onLocationChangedListener.onLocationChanged(location);
}
}
});
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
for (Location location : locationResult.getLocations()) {
onLocationChangedListener.onLocationChanged(location);
}
}
};
fusedLocationClientProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void deactivate() {
fusedLocationClientProviderClient.removeLocationUpdates(locationCallback);
}
}

View File

@@ -0,0 +1,15 @@
package com.rnmaps.maps;
import android.graphics.Bitmap;
import com.google.android.gms.maps.model.BitmapDescriptor;
public interface ImageReadable {
public void setIconBitmap(Bitmap bitmap);
public void setIconBitmapDescriptor(BitmapDescriptor bitmapDescriptor);
public void update();
}

View File

@@ -0,0 +1,127 @@
package com.rnmaps.maps;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeHolder;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
public class ImageReader {
private final ImageReadable imp;
private final Context context;
private final Resources resources;
private final DraweeHolder<?> logoHolder;
private DataSource<CloseableReference<CloseableImage>> dataSource;
private final ControllerListener<ImageInfo> mLogoControllerListener =
new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable final ImageInfo imageInfo,
@Nullable Animatable animatable) {
CloseableReference<CloseableImage> imageReference = null;
try {
imageReference = dataSource.getResult();
if (imageReference != null) {
CloseableImage image = imageReference.get();
if (image instanceof CloseableStaticBitmap) {
CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) image;
Bitmap bitmap = closeableStaticBitmap.getUnderlyingBitmap();
if (bitmap != null) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
imp.setIconBitmap(bitmap);
imp.setIconBitmapDescriptor(BitmapDescriptorFactory.fromBitmap(bitmap));
}
}
}
} finally {
dataSource.close();
if (imageReference != null) {
CloseableReference.closeSafely(imageReference);
}
}
imp.update();
}
};
public ImageReader(Context context, Resources resources, ImageReadable imp) {
this.context = context;
this.resources = resources;
this.imp = imp;
logoHolder = DraweeHolder.create(createDraweeHeirarchy(resources), context);
logoHolder.onAttach();
}
private GenericDraweeHierarchy createDraweeHeirarchy(Resources resources){
return new GenericDraweeHierarchyBuilder(resources)
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build();
}
public void setImage(String uri) {
if (uri == null) {
imp.setIconBitmapDescriptor(null);
imp.update();
} else if (uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(uri))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setControllerListener(mLogoControllerListener)
.setOldController(logoHolder.getController())
.build();
logoHolder.setController(controller);
} else {
BitmapDescriptor iconBitmapDescriptor = getBitmapDescriptorByName(uri);
imp.setIconBitmapDescriptor(iconBitmapDescriptor);
imp.setIconBitmap(BitmapFactory.decodeResource(this.resources, getDrawableResourceByName
(uri)));
imp.update();
}
}
private int getDrawableResourceByName(String name) {
return this.resources.getIdentifier(
name,
"drawable",
this.context.getPackageName());
}
private BitmapDescriptor getBitmapDescriptorByName(String name) {
return BitmapDescriptorFactory.fromResource(getDrawableResourceByName(name));
}
}

View File

@@ -0,0 +1,26 @@
package com.rnmaps.maps;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
public class ImageUtil {
public static Bitmap convert(String base64Str) throws IllegalArgumentException {
byte[] decodedBytes = Base64.decode(
base64Str.substring(base64Str.indexOf(",") + 1),
Base64.DEFAULT
);
return BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
}
public static String convert(Bitmap bitmap) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
}
}

View File

@@ -0,0 +1,47 @@
package com.rnmaps.maps;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
public class LatLngBoundsUtils {
public static boolean BoundsAreDifferent(LatLngBounds a, LatLngBounds b) {
LatLng centerA = a.getCenter();
double latA = centerA.latitude;
double lngA = centerA.longitude;
double latDeltaA = a.northeast.latitude - a.southwest.latitude;
double lngDeltaA = a.northeast.longitude - a.southwest.longitude;
LatLng centerB = b.getCenter();
double latB = centerB.latitude;
double lngB = centerB.longitude;
double latDeltaB = b.northeast.latitude - b.southwest.latitude;
double lngDeltaB = b.northeast.longitude - b.southwest.longitude;
double latEps = LatitudeEpsilon(a, b);
double lngEps = LongitudeEpsilon(a, b);
return
different(latA, latB, latEps) ||
different(lngA, lngB, lngEps) ||
different(latDeltaA, latDeltaB, latEps) ||
different(lngDeltaA, lngDeltaB, lngEps);
}
private static boolean different(double a, double b, double epsilon) {
return Math.abs(a - b) > epsilon;
}
private static double LatitudeEpsilon(LatLngBounds a, LatLngBounds b) {
double sizeA = a.northeast.latitude - a.southwest.latitude; // something mod 180?
double sizeB = b.northeast.latitude - b.southwest.latitude; // something mod 180?
double size = Math.min(Math.abs(sizeA), Math.abs(sizeB));
return size / 2560;
}
private static double LongitudeEpsilon(LatLngBounds a, LatLngBounds b) {
double sizeA = a.northeast.longitude - a.southwest.longitude;
double sizeB = b.northeast.longitude - b.southwest.longitude;
double size = Math.min(Math.abs(sizeA), Math.abs(sizeB));
return size / 2560;
}
}

View File

@@ -0,0 +1,41 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.views.view.ReactViewGroup;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.Map;
public class MapCallout extends ReactViewGroup {
private boolean tooltip = false;
public int width;
public int height;
public MapCallout(Context context) {
super(context);
}
public void setTooltip(boolean tooltip) {
this.tooltip = tooltip;
}
public boolean getTooltip() {
return this.tooltip;
}
@Override
protected void onLayout(boolean changed,
int left, int top, int right, int bottom){
width = right - left;
height = bottom - top;
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
}

View File

@@ -0,0 +1,56 @@
package com.rnmaps.maps;
import androidx.annotation.Nullable;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
public class MapCalloutManager extends ViewGroupManager<MapCallout> {
@Override
public String getName() {
return "AIRMapCallout";
}
@Override
public MapCallout createViewInstance(ThemedReactContext context) {
return new MapCallout(context);
}
@ReactProp(name = "tooltip", defaultBoolean = false)
public void setTooltip(MapCallout view, boolean tooltip) {
view.setTooltip(tooltip);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of("onPress", MapBuilder.of("registrationName", "onPress"));
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// we use a custom shadow node that emits the width/height of the view
// after layout with the updateExtraData method. Without this, we can't generate
// a bitmap of the appropriate width/height of the rendered view.
return new SizeReportingShadowNode();
}
@Override
public void updateExtraData(MapCallout view, Object extraData) {
// This method is called from the shadow node with the width/height of the rendered
// marker view.
//noinspection unchecked
Map<String, Float> data = (Map<String, Float>) extraData;
float width = data.get("width");
float height = data.get("height");
view.width = (int) width;
view.height = (int) height;
}
}

View File

@@ -0,0 +1,126 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.collections.CircleManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.Map;
public class MapCircle extends MapFeature {
private CircleOptions circleOptions;
private Circle circle;
private LatLng center;
private double radius;
private int strokeColor;
private int fillColor;
private float strokeWidth;
private float zIndex;
private boolean tappable;
public MapCircle(Context context) {
super(context);
}
public void setCenter(LatLng center) {
this.center = center;
if (circle != null) {
circle.setCenter(this.center);
}
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public void setRadius(double radius) {
this.radius = radius;
if (circle != null) {
circle.setRadius(this.radius);
}
}
public void setFillColor(int color) {
this.fillColor = color;
if (circle != null) {
circle.setFillColor(color);
}
}
public void setStrokeColor(int color) {
this.strokeColor = color;
if (circle != null) {
circle.setStrokeColor(color);
}
}
public void setStrokeWidth(float width) {
this.strokeWidth = width;
if (circle != null) {
circle.setStrokeWidth(width);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (circle != null) {
circle.setZIndex(zIndex);
}
}
public void setTappable(boolean tappable) {
this.tappable = tappable;
if (circle != null) {
circle.setClickable(tappable);
}
}
public CircleOptions getCircleOptions() {
if (circleOptions == null) {
circleOptions = createCircleOptions();
}
return circleOptions;
}
private CircleOptions createCircleOptions() {
CircleOptions options = new CircleOptions();
options.center(center);
options.radius(radius);
options.fillColor(fillColor);
options.strokeColor(strokeColor);
options.strokeWidth(strokeWidth);
options.zIndex(zIndex);
return options;
}
@Override
public Object getFeature() {
return circle;
}
@Override
public void addToMap(Object collection) {
CircleManager.Collection circleCollection = (CircleManager.Collection) collection;
circle = circleCollection.addCircle(getCircleOptions());
}
@Override
public void removeFromMap(Object collection) {
CircleManager.Collection circleCollection = (CircleManager.Collection) collection;
circleCollection.remove(circle);
}
public void setCenter(ReadableMap center) {
setCenter(new LatLng(center.getDouble("latitude"), center.getDouble("longitude")));
}
}

View File

@@ -0,0 +1,67 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.LatLng;
public class MapCircleManager extends ViewGroupManager<MapCircle> {
private final DisplayMetrics metrics;
public MapCircleManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapCircle";
}
@Override
public MapCircle createViewInstance(ThemedReactContext context) {
return new MapCircle(context);
}
@ReactProp(name = "center")
public void setCenter(MapCircle view, ReadableMap center) {
view.setCenter(new LatLng(center.getDouble("latitude"), center.getDouble("longitude")));
}
@ReactProp(name = "radius", defaultDouble = 0)
public void setRadius(MapCircle view, double radius) {
view.setRadius(radius);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapCircle view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setStrokeWidth(widthInScreenPx);
}
@ReactProp(name = "fillColor", defaultInt = Color.RED, customType = "Color")
public void setFillColor(MapCircle view, int color) {
view.setFillColor(color);
}
@ReactProp(name = "strokeColor", defaultInt = Color.RED, customType = "Color")
public void setStrokeColor(MapCircle view, int color) {
view.setStrokeColor(color);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapCircle view, float zIndex) {
view.setZIndex(zIndex);
}
}

View File

@@ -0,0 +1,17 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.views.view.ReactViewGroup;
public abstract class MapFeature extends ReactViewGroup {
public MapFeature(Context context) {
super(context);
}
public abstract void addToMap(Object mapOrCollection);
public abstract void removeFromMap(Object mapOrCollection);
public abstract Object getFeature();
}

View File

@@ -0,0 +1,343 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
import com.google.maps.android.SphericalUtil;
import com.google.maps.android.geometry.Point;
import com.google.maps.android.projection.SphericalMercatorProjection;
import java.io.ByteArrayOutputStream;
import java.util.List;
/**
* Tile overlay used to display a colored polyline as a replacement for the
* non-existence of gradient polylines for google maps. Implementation borrowed
* from Dagothig/ColoredPolylineOverlay
* (https://gist.github.com/Dagothig/5f9cf0a4a7a42901a7b2)
*/
public class MapGradientPolyline extends MapFeature {
private List<LatLng> points;
private int[] colors;
private float zIndex;
private float width;
private GoogleMap map;
private TileOverlay tileOverlay;
protected final Context context;
public MapGradientPolyline(Context context) {
super(context);
this.context = context;
}
public void setCoordinates(List<LatLng> coordinates) {
this.points = coordinates;
if (tileOverlay != null) {
tileOverlay.remove();
}
if (map != null) {
tileOverlay = map.addTileOverlay(createTileOverlayOptions());
}
}
public void setStrokeColors(int[] colors) {
this.colors = colors;
if (tileOverlay != null) {
tileOverlay.remove();
}
if (map != null) {
tileOverlay = map.addTileOverlay(createTileOverlayOptions());
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (tileOverlay != null) {
tileOverlay.setZIndex(zIndex);
}
}
public void setWidth(float width) {
this.width = width;
if (tileOverlay != null) {
tileOverlay.remove();
}
if (map != null) {
tileOverlay = map.addTileOverlay(createTileOverlayOptions());
}
}
private TileOverlayOptions createTileOverlayOptions() {
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
AirMapGradientPolylineProvider tileProvider = new AirMapGradientPolylineProvider(context, points, colors, width);
options.tileProvider(tileProvider);
return options;
}
public static int interpolateColor(int[] colors, float proportion) {
int rTotal = 0, gTotal = 0, bTotal = 0;
// We correct the ratio to colors.length - 1 so that
// for i == colors.length - 1 and p == 1, then the final ratio is 1 (see below)
float p = proportion * (colors.length - 1);
for (int i = 0; i < colors.length; i++) {
// The ratio mostly resides on the 1 - Math.abs(p - i) calculation :
// Since for p == i, then the ratio is 1 and for p == i + 1 or p == i -1, then the ratio is 0
// This calculation works BECAUSE p lies within [0, length - 1] and i lies within [0, length - 1] as well
float iRatio = Math.max(1 - Math.abs(p - i), 0.0f);
rTotal += (int) (Color.red(colors[i]) * iRatio);
gTotal += (int) (Color.green(colors[i]) * iRatio);
bTotal += (int) (Color.blue(colors[i]) * iRatio);
}
return Color.rgb(rTotal, gTotal, bTotal);
}
public class AirMapGradientPolylineProvider implements TileProvider {
public static final int BASE_TILE_SIZE = 256;
protected final List<LatLng> points;
protected final int[] colors;
protected final float width;
protected final float density;
protected final int tileDimension;
protected final SphericalMercatorProjection projection;
// Caching calculation-related stuff
protected LatLng[] trailLatLngs;
protected Point[] projectedPts;
protected Point[] projectedPtMids;
public AirMapGradientPolylineProvider(Context context, List<LatLng> points, int[] colors,
float width) {
super();
this.points = points;
this.colors = colors;
this.width = width;
density = context.getResources().getDisplayMetrics().density;
tileDimension = (int) (BASE_TILE_SIZE * density);
projection = new SphericalMercatorProjection(BASE_TILE_SIZE);
calculatePoints();
}
public void calculatePoints() {
trailLatLngs = new LatLng[points.size()];
projectedPts = new Point[points.size()];
projectedPtMids = new Point[Math.max(points.size() - 1, 0)];
for (int i = 0; i < points.size(); i++) {
LatLng latLng = points.get(i);
trailLatLngs[i] = latLng;
projectedPts[i] = projection.toPoint(latLng);
// Mids
if (i > 0) {
LatLng previousLatLng = points.get(i - 1);
LatLng latLngMid = SphericalUtil.interpolate(previousLatLng, latLng, 0.5);
projectedPtMids[i - 1] = projection.toPoint(latLngMid);
}
}
}
@Override
public Tile getTile(int x, int y, int zoom) {
// Because getTile can be called asynchronously by multiple threads, none of the info we keep in the class will be modified
// (getTile is essentially side-effect-less) :
// Instead, we create the bitmap, the canvas and the paints specifically for the call to getTile
Bitmap bitmap = Bitmap.createBitmap(tileDimension, tileDimension, Bitmap.Config.ARGB_8888);
// Normally, instead of the later calls for drawing being offset, we would offset them using scale() and translate() right here
// However, there seems to be funky issues related to float imprecisions that happen at large scales when using this method, so instead
// The points are offset properly when drawing
Canvas canvas = new Canvas(bitmap);
Matrix shaderMat = new Matrix();
Paint gradientPaint = new Paint();
gradientPaint.setStyle(Paint.Style.STROKE);
gradientPaint.setStrokeWidth(width);
gradientPaint.setStrokeCap(Paint.Cap.BUTT);
gradientPaint.setStrokeJoin(Paint.Join.ROUND);
gradientPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
gradientPaint.setShader(new LinearGradient(0, 0, 1, 0, colors, null,
Shader.TileMode.CLAMP));
gradientPaint.getShader().setLocalMatrix(shaderMat);
Paint colorPaint = new Paint();
colorPaint.setStyle(Paint.Style.STROKE);
colorPaint.setStrokeWidth(width);
colorPaint.setStrokeCap(Paint.Cap.BUTT);
colorPaint.setStrokeJoin(Paint.Join.ROUND);
colorPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
// See https://developers.google.com/maps/documentation/android/views#zoom for handy info regarding what zoom is
float scale = (float) (Math.pow(2, zoom) * density);
renderTrail(canvas, shaderMat, gradientPaint, colorPaint, scale, x, y);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
return new Tile(tileDimension, tileDimension, baos.toByteArray());
}
public void renderTrail(Canvas canvas, Matrix shaderMat, Paint gradientPaint, Paint colorPaint,
float scale, int x, int y) {
MutPoint pt1 = new MutPoint(), pt2 = new MutPoint(), pt3 = new MutPoint(), pt1mid2 =
new MutPoint(), pt2mid3 = new MutPoint();
if (points.size() == 1) {
pt1.set(projectedPts[0], scale, x, y, tileDimension);
colorPaint.setStyle(Paint.Style.FILL);
colorPaint.setColor(interpolateColor(colors, 1));
canvas
.drawCircle((float) pt1.x, (float) pt1.y, colorPaint.getStrokeWidth() / 2f, colorPaint);
colorPaint.setStyle(Paint.Style.STROKE);
return;
}
if (points.size() == 2) {
pt1.set(projectedPts[0], scale, x, y, tileDimension);
pt2.set(projectedPts[1], scale, x, y, tileDimension);
drawLine(canvas, colorPaint, pt1, pt2, 0);
return;
}
for (int i = 2; i < points.size(); i++) {
pt1.set(projectedPts[i - 2], scale, x, y, tileDimension);
pt2.set(projectedPts[i - 1], scale, x, y, tileDimension);
pt3.set(projectedPts[i], scale, x, y, tileDimension);
// Because we want to split the lines in two to ease over the corners, we need the middle points
pt1mid2.set(projectedPtMids[i - 2], scale, x, y, tileDimension);
pt2mid3.set(projectedPtMids[i - 1], scale, x, y, tileDimension);
float interp1 = ((float)i - 2) / points.size();
float interp2 = ((float)i - 1) / points.size();
float interp1to2 = (interp1 + interp2) / 2;
Log.d("AirMapGradientPolyline", String.valueOf(interp1to2));
// Circle for the corner (removes the weird empty corners that occur otherwise)
colorPaint.setStyle(Paint.Style.FILL);
colorPaint.setColor(interpolateColor(colors, interp1to2));
canvas
.drawCircle((float) pt2.x, (float) pt2.y, colorPaint.getStrokeWidth() / 2f, colorPaint);
colorPaint.setStyle(Paint.Style.STROKE);
// Corner
// Note that since for the very first point and the very last point we don't split it in two, we used them instead.
drawLine(canvas, shaderMat, gradientPaint, colorPaint, i - 2 == 0 ? pt1 : pt1mid2,
pt2, interp1, interp1to2);
drawLine(canvas, shaderMat, gradientPaint, colorPaint, pt2, i == points.size() - 1 ?
pt3 : pt2mid3, interp1to2, interp2);
}
}
/**
* Note: it is assumed the shader is 0, 0, 1, 0 (horizontal) so that it lines up with the rotation
* (rotations are usually setup so that the angle 0 points right)
*/
public void drawLine(Canvas canvas, Matrix shaderMat, Paint gradientPaint, Paint colorPaint,
MutPoint pt1, MutPoint pt2, float ratio1, float ratio2) {
// Degenerate case: both ratios are the same; we just handle it using the colorPaint (handling it using the shader is just messy and ineffective)
if (ratio1 == ratio2) {
drawLine(canvas, colorPaint, pt1, pt2, ratio1);
return;
}
shaderMat.reset();
// PS: don't ask me why this specfic orders for calls works but other orders will fuck up
// Since every call is pre, this is essentially ordered as (or my understanding is that it is):
// ratio translate -> ratio scale -> scale to pt length -> translate to pt start -> rotate
// (my initial intuition was to use only post calls and to order as above, but it resulted in odd corruptions)
// Setup based on points:
// We translate the shader so that it is based on the first point, rotated towards the second and since the length of the
// gradient is 1, then scaling to the length of the distance between the points makes it exactly as long as needed
shaderMat.preRotate((float) Math.toDegrees(Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x)),
(float) pt1.x, (float) pt1.y);
shaderMat.preTranslate((float) pt1.x, (float) pt1.y);
float scale = (float) Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
shaderMat.preScale(scale, scale);
// Setup based on ratio
// By basing the shader to the first ratio, we ensure that the start of the gradient corresponds to it
// The inverse scaling of the shader means that it takes the full length of the call to go to the second ratio
// For instance; if d(ratio1, ratio2) is 0.5, then the shader needs to be twice as long so that an entire call (1)
// Results in only half of the gradient being used
shaderMat.preScale(1f / (ratio2 - ratio1), 1f / (ratio2 - ratio1));
shaderMat.preTranslate(-ratio1, 0);
gradientPaint.getShader().setLocalMatrix(shaderMat);
canvas.drawLine(
(float) pt1.x,
(float) pt1.y,
(float) pt2.x,
(float) pt2.y,
gradientPaint
);
}
public void drawLine(Canvas canvas, Paint colorPaint, MutPoint pt1, MutPoint pt2, float ratio) {
colorPaint.setColor(interpolateColor(colors, ratio));
canvas.drawLine(
(float) pt1.x,
(float) pt1.y,
(float) pt2.x,
(float) pt2.y,
colorPaint
);
}
}
@Override
public Object getFeature() {
return tileOverlay;
}
@Override
public void addToMap(Object map) {
this.map = (GoogleMap) map;
this.tileOverlay = this.map.addTileOverlay(createTileOverlayOptions());
}
@Override
public void removeFromMap(Object map) {
tileOverlay.remove();
}
public static class MutPoint {
public double x, y;
public MutPoint set(Point point, float scale, int x, int y, int tileDimension) {
this.x = point.x * scale - x * tileDimension;
this.y = point.y * scale - y * tileDimension;
return this;
}
}
}

View File

@@ -0,0 +1,83 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.LatLng;
import java.util.List;
import java.util.ArrayList;
public class MapGradientPolylineManager extends ViewGroupManager<MapGradientPolyline> {
private final DisplayMetrics metrics;
public MapGradientPolylineManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapGradientPolyline";
}
@Override
public MapGradientPolyline createViewInstance(ThemedReactContext context) {
return new MapGradientPolyline(context);
}
@ReactProp(name = "coordinates")
public void setCoordinates(MapGradientPolyline view, ReadableArray coordinates) {
List<LatLng> p = new ArrayList<LatLng>();
for (int i = 0; i < coordinates.size(); i++) {
ReadableMap point = coordinates.getMap(i);
LatLng latLng = new LatLng(point.getDouble("latitude"), point.getDouble("longitude"));
p.add(latLng);
}
view.setCoordinates(p);
}
@ReactProp(name = "strokeColors", customType = "ColorArray")
public void setStrokeColors(MapGradientPolyline view, ReadableArray colors) {
if (colors != null) {
if (colors.size() == 0) {
int[] colorValues = {0,0};
view.setStrokeColors(colorValues);
} else if (colors.size() == 1) {
int[] colorValues = { colors.getInt(0), colors.getInt(0) };
view.setStrokeColors(colorValues);
} else {
int[] colorValues = new int[colors.size()];
for (int i = 0; i < colors.size(); i++) {
colorValues[i] = colors.getInt(i);
}
view.setStrokeColors(colorValues);
}
} else {
int[] colorValues = {0,0};
view.setStrokeColors(colorValues);
}
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapGradientPolyline view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapGradientPolyline view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setWidth(widthInScreenPx);
}
}

View File

@@ -0,0 +1,113 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.maps.android.heatmaps.HeatmapTileProvider;
import com.google.maps.android.heatmaps.WeightedLatLng;
import com.google.maps.android.heatmaps.Gradient;
import java.util.Arrays;
import java.util.List;
public class MapHeatmap extends MapFeature {
private TileOverlayOptions heatmapOptions;
private TileOverlay heatmap;
private HeatmapTileProvider heatmapTileProvider;
private List<WeightedLatLng> points;
private Gradient gradient;
private Double opacity;
private Integer radius;
public MapHeatmap(Context context) {
super(context);
}
public void setPoints(WeightedLatLng[] points) {
this.points = Arrays.asList(points);
if (heatmapTileProvider != null) {
heatmapTileProvider.setWeightedData(this.points);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public void setGradient(Gradient gradient) {
this.gradient = gradient;
if (heatmapTileProvider != null) {
heatmapTileProvider.setGradient(gradient);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public void setOpacity(double opacity) {
this.opacity = opacity;
if (heatmapTileProvider != null) {
heatmapTileProvider.setOpacity(opacity);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public void setRadius(int radius) {
this.radius = radius;
if (heatmapTileProvider != null) {
heatmapTileProvider.setRadius(radius);
}
if (heatmap != null) {
heatmap.clearTileCache();
}
}
public TileOverlayOptions getHeatmapOptions() {
if (heatmapOptions == null) {
heatmapOptions = createHeatmapOptions();
}
return heatmapOptions;
}
private TileOverlayOptions createHeatmapOptions() {
TileOverlayOptions options = new TileOverlayOptions();
if (heatmapTileProvider == null) {
HeatmapTileProvider.Builder builder =
new HeatmapTileProvider.Builder().weightedData(this.points);
if (radius != null) {
builder.radius(radius);
}
if (opacity != null) {
builder.opacity(opacity);
}
if (gradient != null) {
builder.gradient(gradient);
}
heatmapTileProvider = builder.build();
}
options.tileProvider(heatmapTileProvider);
return options;
}
@Override
public Object getFeature() {
return heatmap;
}
@Override
public void addToMap(Object map) {
heatmap = ((GoogleMap) map).addTileOverlay(getHeatmapOptions());
}
@Override
public void removeFromMap(Object map) {
heatmap.remove();
}
}

View File

@@ -0,0 +1,74 @@
package com.rnmaps.maps;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.heatmaps.WeightedLatLng;
import com.google.maps.android.heatmaps.Gradient;
public class MapHeatmapManager extends ViewGroupManager<MapHeatmap> {
@Override
public String getName() {
return "AIRMapHeatmap";
}
@Override
public MapHeatmap createViewInstance(ThemedReactContext context) {
return new MapHeatmap(context);
}
@ReactProp(name = "points")
public void setPoints(MapHeatmap view, ReadableArray points) {
WeightedLatLng[] p = new WeightedLatLng[points.size()];
for (int i = 0; i < points.size(); i++) {
ReadableMap point = points.getMap(i);
WeightedLatLng weightedLatLng;
LatLng latLng = new LatLng(point.getDouble("latitude"), point.getDouble("longitude"));
if (point.hasKey("weight")) {
weightedLatLng = new WeightedLatLng(latLng, point.getDouble("weight"));
} else {
weightedLatLng = new WeightedLatLng(latLng);
}
p[i] = weightedLatLng;
}
view.setPoints(p);
}
@ReactProp(name = "gradient")
public void setGradient(MapHeatmap view, ReadableMap gradient) {
ReadableArray srcColors = gradient.getArray("colors");
int[] colors = new int[srcColors.size()];
for (int i = 0; i < srcColors.size(); i++) {
colors[i] = srcColors.getInt(i);
}
ReadableArray srcStartPoints = gradient.getArray("startPoints");
float[] startPoints = new float[srcStartPoints.size()];
for (int i = 0; i < srcStartPoints.size(); i++) {
startPoints[i] = (float)srcStartPoints.getDouble(i);
}
if (gradient.hasKey("colorMapSize")) {
int colorMapSize = gradient.getInt("colorMapSize");
view.setGradient(new Gradient(colors, startPoints, colorMapSize));
} else {
view.setGradient(new Gradient(colors, startPoints));
}
}
@ReactProp(name = "opacity")
public void setOpacity(MapHeatmap view, double opacity) {
view.setOpacity(opacity);
}
@ReactProp(name = "radius")
public void setRadius(MapHeatmap view, int radius) {
view.setRadius(radius);
}
}

View File

@@ -0,0 +1,151 @@
package com.rnmaps.maps;
import android.content.Context;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.TileProvider;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MapLocalTile extends MapFeature {
class AIRMapLocalTileProvider implements TileProvider {
private static final int BUFFER_SIZE = 16 * 1024;
private int tileSize;
private String pathTemplate;
private final boolean useAssets;
public AIRMapLocalTileProvider(int tileSizet, String pathTemplate, boolean useAssets) {
this.tileSize = tileSizet;
this.pathTemplate = pathTemplate;
this.useAssets = useAssets;
}
@Override
public Tile getTile(int x, int y, int zoom) {
byte[] image = readTileImage(x, y, zoom);
return image == null ? TileProvider.NO_TILE : new Tile(this.tileSize, this.tileSize, image);
}
public void setPathTemplate(String pathTemplate) {
this.pathTemplate = pathTemplate;
}
public void setTileSize(int tileSize) {
this.tileSize = tileSize;
}
private byte[] readTileImage(int x, int y, int zoom) {
InputStream in = null;
ByteArrayOutputStream buffer = null;
String tileFilename = getTileFilename(x, y, zoom);
try {
in = useAssets ? getContext().getAssets().open(tileFilename) : new FileInputStream(tileFilename);
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
private String getTileFilename(int x, int y, int zoom) {
String s = this.pathTemplate
.replace("{x}", Integer.toString(x))
.replace("{y}", Integer.toString(y))
.replace("{z}", Integer.toString(zoom));
return s;
}
}
private TileOverlayOptions tileOverlayOptions;
private TileOverlay tileOverlay;
private MapLocalTile.AIRMapLocalTileProvider tileProvider;
private String pathTemplate;
private float tileSize;
private float zIndex;
private boolean useAssets;
public MapLocalTile(Context context) {
super(context);
}
public void setPathTemplate(String pathTemplate) {
this.pathTemplate = pathTemplate;
if (tileProvider != null) {
tileProvider.setPathTemplate(pathTemplate);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (tileOverlay != null) {
tileOverlay.setZIndex(zIndex);
}
}
public void setTileSize(float tileSize) {
this.tileSize = tileSize;
if (tileProvider != null) {
tileProvider.setTileSize((int)tileSize);
}
}
public void setUseAssets(boolean useAssets) {
this.useAssets = useAssets;
}
public TileOverlayOptions getTileOverlayOptions() {
if (tileOverlayOptions == null) {
tileOverlayOptions = createTileOverlayOptions();
}
return tileOverlayOptions;
}
private TileOverlayOptions createTileOverlayOptions() {
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
this.tileProvider = new MapLocalTile.AIRMapLocalTileProvider((int)this.tileSize, this.pathTemplate, this.useAssets);
options.tileProvider(this.tileProvider);
return options;
}
@Override
public Object getFeature() {
return tileOverlay;
}
@Override
public void addToMap(Object map) {
this.tileOverlay = ((GoogleMap) map).addTileOverlay(getTileOverlayOptions());
}
@Override
public void removeFromMap(Object map) {
tileOverlay.remove();
}
}

View File

@@ -0,0 +1,54 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* Created by zavadpe on 30/11/2017.
*/
public class MapLocalTileManager extends ViewGroupManager<MapLocalTile> {
public MapLocalTileManager(ReactApplicationContext reactContext) {
super();
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapLocalTile";
}
@Override
public MapLocalTile createViewInstance(ThemedReactContext context) {
return new MapLocalTile(context);
}
@ReactProp(name = "pathTemplate")
public void setPathTemplate(MapLocalTile view, String pathTemplate) {
view.setPathTemplate(pathTemplate);
}
@ReactProp(name = "tileSize", defaultFloat = 256f)
public void setTileSize(MapLocalTile view, float tileSize) {
view.setTileSize(tileSize);
}
@ReactProp(name = "zIndex", defaultFloat = -1.0f)
public void setZIndex(MapLocalTile view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "useAssets", defaultBoolean = false)
public void setUseAssets(MapLocalTile view, boolean useAssets) {
view.setUseAssets(useAssets);
}
}

View File

@@ -0,0 +1,504 @@
package com.rnmaps.maps;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.R;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.google.android.gms.location.Priority;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.MapsInitializer;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import java.util.Map;
public class MapManager extends ViewGroupManager<MapView> {
private static final String REACT_CLASS = "AIRMap";
private final Map<String, Integer> MAP_TYPES = MapBuilder.of(
"standard", GoogleMap.MAP_TYPE_NORMAL,
"satellite", GoogleMap.MAP_TYPE_SATELLITE,
"hybrid", GoogleMap.MAP_TYPE_HYBRID,
"terrain", GoogleMap.MAP_TYPE_TERRAIN,
"none", GoogleMap.MAP_TYPE_NONE
);
public static final Map<String, Integer> MY_LOCATION_PRIORITY = MapBuilder.of(
"balanced", Priority.PRIORITY_BALANCED_POWER_ACCURACY,
"high", Priority.PRIORITY_HIGH_ACCURACY,
"low", Priority.PRIORITY_LOW_POWER,
"passive", Priority.PRIORITY_PASSIVE
);
private final ReactApplicationContext appContext;
private MapMarkerManager markerManager;
protected GoogleMapOptions googleMapOptions;
protected MapsInitializer.Renderer renderer;
public MapManager(ReactApplicationContext context) {
this.appContext = context;
}
public MapMarkerManager getMarkerManager() {
return this.markerManager;
}
public void setMarkerManager(MapMarkerManager markerManager) {
this.markerManager = markerManager;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected MapView createViewInstance(@NonNull ThemedReactContext context) {
return new MapView(context, this.appContext, this, googleMapOptions);
}
@Override
protected MapView createViewInstance(int reactTag, @NonNull ThemedReactContext reactContext, @Nullable ReactStylesDiffMap initialProps, @Nullable StateWrapper stateWrapper) {
this.googleMapOptions = new GoogleMapOptions();
if (initialProps != null) {
if (initialProps.getString("googleMapId") != null) {
googleMapOptions.mapId(initialProps.getString("googleMapId"));
}
if (initialProps.hasKey("liteMode")) {
googleMapOptions.liteMode(initialProps.getBoolean("liteMode", false));
}
if (initialProps.hasKey("initialCamera")) {
CameraPosition position = MapView.cameraPositionFromMap(initialProps.getMap("initialCamera"));
if (position != null) {
googleMapOptions.camera(position);
}
} else if (initialProps.hasKey("camera")) {
CameraPosition position = MapView.cameraPositionFromMap(initialProps.getMap("camera"));
if (position != null) {
googleMapOptions.camera(position);
}
}
if (initialProps.hasKey("googleRenderer") && "LEGACY".equals(initialProps.getString("googleRenderer"))) {
renderer = MapsInitializer.Renderer.LEGACY;
} else {
renderer = MapsInitializer.Renderer.LATEST;
}
}
return super.createViewInstance(reactTag, reactContext, initialProps, stateWrapper);
}
private void emitMapError(ThemedReactContext context, String message, String type) {
WritableMap error = Arguments.createMap();
error.putString("message", message);
error.putString("type", type);
context
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onError", error);
}
@ReactProp(name = "region")
public void setRegion(MapView view, ReadableMap region) {
view.setRegion(region);
}
@ReactProp(name = "googleRenderer")
public void setGoogleRenderer(MapView view, @Nullable String googleRenderer) {
// do nothing, passed as part of the InitialProps
}
@ReactProp(name = "liteMode", defaultBoolean = false)
public void setLiteMode(MapView view, boolean liteMode) {
googleMapOptions.liteMode(liteMode);
}
@ReactProp(name = "googleMapId")
public void setGoogleMapId(MapView view, @Nullable String googleMapId) {
if (googleMapId != null) {
googleMapOptions.mapId(googleMapId);
}
}
@ReactProp(name = "initialRegion")
public void setInitialRegion(MapView view, ReadableMap initialRegion) {
view.setInitialRegion(initialRegion);
}
@ReactProp(name = "camera")
public void setCamera(MapView view, ReadableMap camera) {
view.setCamera(camera);
}
@ReactProp(name = "initialCamera")
public void setInitialCamera(MapView view, ReadableMap initialCamera) {
view.setInitialCamera(initialCamera);
}
@ReactProp(name = "mapType")
public void setMapType(MapView view, @Nullable String mapType) {
int typeId = MAP_TYPES.get(mapType);
view.map.setMapType(typeId);
}
@ReactProp(name = "customMapStyleString")
public void setMapStyle(MapView view, @Nullable String customMapStyleString) {
view.setMapStyle(customMapStyleString);
}
@ReactProp(name = "mapPadding")
public void setMapPadding(MapView view, @Nullable ReadableMap padding) {
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
double density = (double) view.getResources().getDisplayMetrics().density;
if (padding != null) {
if (padding.hasKey("left")) {
left = (int) (padding.getDouble("left") * density);
}
if (padding.hasKey("top")) {
top = (int) (padding.getDouble("top") * density);
}
if (padding.hasKey("right")) {
right = (int) (padding.getDouble("right") * density);
}
if (padding.hasKey("bottom")) {
bottom = (int) (padding.getDouble("bottom") * density);
}
}
view.applyBaseMapPadding(left, top, right, bottom);
view.map.setPadding(left, top, right, bottom);
}
@ReactProp(name = "showsUserLocation", defaultBoolean = false)
public void setShowsUserLocation(MapView view, boolean showUserLocation) {
view.setShowsUserLocation(showUserLocation);
}
@ReactProp(name = "userLocationPriority")
public void setUserLocationPriority(MapView view, @Nullable String accuracy) {
view.setUserLocationPriority(MY_LOCATION_PRIORITY.get(accuracy));
}
@ReactProp(name = "userLocationUpdateInterval", defaultInt = 5000)
public void setUserLocationUpdateInterval(MapView view, int updateInterval) {
view.setUserLocationUpdateInterval(updateInterval);
}
@ReactProp(name = "userLocationFastestInterval", defaultInt = 5000)
public void setUserLocationFastestInterval(MapView view, int fastestInterval) {
view.setUserLocationFastestInterval(fastestInterval);
}
@ReactProp(name = "showsMyLocationButton", defaultBoolean = true)
public void setShowsMyLocationButton(MapView view, boolean showMyLocationButton) {
view.setShowsMyLocationButton(showMyLocationButton);
}
@ReactProp(name = "toolbarEnabled", defaultBoolean = true)
public void setToolbarEnabled(MapView view, boolean toolbarEnabled) {
view.setToolbarEnabled(toolbarEnabled);
}
// This is a private prop to improve performance of panDrag by disabling it when the callback
// is not set
@ReactProp(name = "handlePanDrag", defaultBoolean = false)
public void setHandlePanDrag(MapView view, boolean handlePanDrag) {
view.setHandlePanDrag(handlePanDrag);
}
@ReactProp(name = "showsTraffic", defaultBoolean = false)
public void setShowTraffic(MapView view, boolean showTraffic) {
view.map.setTrafficEnabled(showTraffic);
}
@ReactProp(name = "showsBuildings", defaultBoolean = false)
public void setShowBuildings(MapView view, boolean showBuildings) {
view.setShowBuildings(showBuildings);
}
@ReactProp(name = "showsIndoors", defaultBoolean = false)
public void setShowIndoors(MapView view, boolean showIndoors) {
view.setShowIndoors(showIndoors);
}
@ReactProp(name = "showsIndoorLevelPicker", defaultBoolean = false)
public void setShowsIndoorLevelPicker(MapView view, boolean showsIndoorLevelPicker) {
view.setShowsIndoorLevelPicker(showsIndoorLevelPicker);
}
@ReactProp(name = "showsCompass", defaultBoolean = false)
public void setShowsCompass(MapView view, boolean showsCompass) {
view.setShowsCompass(showsCompass);
}
@ReactProp(name = "scrollEnabled", defaultBoolean = false)
public void setScrollEnabled(MapView view, boolean scrollEnabled) {
view.setScrollEnabled(scrollEnabled);
}
@ReactProp(name = "zoomEnabled", defaultBoolean = false)
public void setZoomEnabled(MapView view, boolean zoomEnabled) {
view.setZoomEnabled(zoomEnabled);
}
@ReactProp(name = "zoomControlEnabled", defaultBoolean = true)
public void setZoomControlEnabled(MapView view, boolean zoomControlEnabled) {
view.setZoomControlEnabled(zoomControlEnabled);
}
@ReactProp(name = "rotateEnabled", defaultBoolean = false)
public void setRotateEnabled(MapView view, boolean rotateEnabled) {
view.setRotateEnabled(rotateEnabled);
}
@ReactProp(name = "scrollDuringRotateOrZoomEnabled", defaultBoolean = true)
public void setScrollDuringRotateOrZoomEnabled(MapView view, boolean scrollDuringRotateOrZoomEnabled) {
view.setScrollDuringRotateOrZoomEnabled(scrollDuringRotateOrZoomEnabled);
}
@ReactProp(name = "cacheEnabled", defaultBoolean = false)
public void setCacheEnabled(MapView view, boolean cacheEnabled) {
view.setCacheEnabled(cacheEnabled);
}
@ReactProp(name = "poiClickEnabled", defaultBoolean = true)
public void setPoiClickEnabled(MapView view, boolean poiClickEnabled) {
view.setPoiClickEnabled(poiClickEnabled);
}
@ReactProp(name = "loadingEnabled", defaultBoolean = false)
public void setLoadingEnabled(MapView view, boolean loadingEnabled) {
view.setLoadingEnabled(loadingEnabled);
}
@ReactProp(name = "moveOnMarkerPress", defaultBoolean = true)
public void setMoveOnMarkerPress(MapView view, boolean moveOnPress) {
view.setMoveOnMarkerPress(moveOnPress);
}
@ReactProp(name = "loadingBackgroundColor", customType = "Color")
public void setLoadingBackgroundColor(MapView view, @Nullable Integer loadingBackgroundColor) {
view.setLoadingBackgroundColor(loadingBackgroundColor);
}
@ReactProp(name = "loadingIndicatorColor", customType = "Color")
public void setLoadingIndicatorColor(MapView view, @Nullable Integer loadingIndicatorColor) {
view.setLoadingIndicatorColor(loadingIndicatorColor);
}
@ReactProp(name = "pitchEnabled", defaultBoolean = false)
public void setPitchEnabled(MapView view, boolean pitchEnabled) {
view.setPitchEnabled(pitchEnabled);
}
@ReactProp(name = "minZoomLevel")
public void setMinZoomLevel(MapView view, float minZoomLevel) {
view.setMinZoomLevel(minZoomLevel);
}
@ReactProp(name = "maxZoomLevel")
public void setMaxZoomLevel(MapView view, float maxZoomLevel) {
view.setMaxZoomLevel(maxZoomLevel);
}
@ReactProp(name = "kmlSrc")
public void setKmlSrc(MapView view, String kmlUrl) {
if (kmlUrl != null) {
view.setKmlSrc(kmlUrl);
}
}
@ReactProp(name = "accessibilityLabel")
public void setAccessibilityLabel(MapView view, @Nullable String accessibilityLabel) {
view.setTag(R.id.accessibility_label, accessibilityLabel);
}
@Override
public void receiveCommand(@NonNull MapView view, String commandId, @Nullable ReadableArray args) {
int duration;
double lat;
double lng;
double lngDelta;
double latDelta;
ReadableMap region;
ReadableMap camera;
switch (commandId) {
case "setCamera":
if (args == null) {
break;
}
camera = args.getMap(0);
view.animateToCamera(camera, 0);
break;
case "animateCamera":
if (args == null) {
break;
}
camera = args.getMap(0);
duration = args.getInt(1);
view.animateToCamera(camera, duration);
break;
case "animateToRegion":
if (args == null) {
break;
}
region = args.getMap(0);
duration = args.getInt(1);
lng = region.getDouble("longitude");
lat = region.getDouble("latitude");
lngDelta = region.getDouble("longitudeDelta");
latDelta = region.getDouble("latitudeDelta");
LatLngBounds bounds = new LatLngBounds(
new LatLng(lat - latDelta / 2, lng - lngDelta / 2), // southwest
new LatLng(lat + latDelta / 2, lng + lngDelta / 2) // northeast
);
view.animateToRegion(bounds, duration);
break;
case "fitToElements":
if (args == null) {
break;
}
view.fitToElements(args.getMap(0), args.getBoolean(1));
break;
case "fitToSuppliedMarkers":
if (args == null) {
break;
}
view.fitToSuppliedMarkers(args.getArray(0), args.getMap(1), args.getBoolean(2));
break;
case "fitToCoordinates":
if (args == null) {
break;
}
view.fitToCoordinates(args.getArray(0), args.getMap(1), args.getBoolean(2));
break;
case "setMapBoundaries":
if (args == null) {
break;
}
view.setMapBoundaries(args.getMap(0), args.getMap(1));
break;
case "setIndoorActiveLevelIndex":
if (args == null) {
break;
}
view.setIndoorActiveLevelIndex(args.getInt(0));
break;
}
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
Map<String, Map<String, String>> map = MapBuilder.of(
"onMapReady", MapBuilder.of("registrationName", "onMapReady"),
"onPress", MapBuilder.of("registrationName", "onPress"),
"onLongPress", MapBuilder.of("registrationName", "onLongPress"),
"onMarkerPress", MapBuilder.of("registrationName", "onMarkerPress"),
"onCalloutPress", MapBuilder.of("registrationName", "onCalloutPress")
);
map.putAll(MapBuilder.of(
"onUserLocationChange", MapBuilder.of("registrationName", "onUserLocationChange"),
"onMarkerDragStart", MapBuilder.of("registrationName", "onMarkerDragStart"),
"onMarkerDrag", MapBuilder.of("registrationName", "onMarkerDrag"),
"onMarkerDragEnd", MapBuilder.of("registrationName", "onMarkerDragEnd"),
"onPanDrag", MapBuilder.of("registrationName", "onPanDrag"),
"onKmlReady", MapBuilder.of("registrationName", "onKmlReady"),
"onPoiClick", MapBuilder.of("registrationName", "onPoiClick")
));
map.putAll(MapBuilder.of(
"onIndoorLevelActivated", MapBuilder.of("registrationName", "onIndoorLevelActivated"),
"onIndoorBuildingFocused", MapBuilder.of("registrationName", "onIndoorBuildingFocused"),
"onDoublePress", MapBuilder.of("registrationName", "onDoublePress"),
"onMapLoaded", MapBuilder.of("registrationName", "onMapLoaded"),
"onMarkerSelect", MapBuilder.of("registrationName", "onMarkerSelect"),
"onMarkerDeselect", MapBuilder.of("registrationName", "onMarkerDeselect"),
"onRegionChangeStart", MapBuilder.of("registrationName", "onRegionChangeStart")
));
return map;
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// A custom shadow node is needed in order to pass back the width/height of the map to the
// view manager so that it can start applying camera moves with bounds.
return new SizeReportingShadowNode();
}
@Override
public void addView(MapView parent, View child, int index) {
parent.addFeature(child, index);
}
@Override
public int getChildCount(MapView view) {
return view.getFeatureCount();
}
@Override
public View getChildAt(MapView view, int index) {
return view.getFeatureAt(index);
}
@Override
public void removeViewAt(MapView parent, int index) {
parent.removeFeatureAt(index);
}
@Override
public void updateExtraData(MapView view, Object extraData) {
view.updateExtraData(extraData);
}
void pushEvent(ThemedReactContext context, View view, String name, WritableMap data) {
context
.getReactApplicationContext()
.getJSModule(RCTEventEmitter.class)
.receiveEvent(view.getId(), name, data);
}
@Override
public void onDropViewInstance(MapView view) {
view.doDestroy();
super.onDropViewInstance(view);
}
}

View File

@@ -0,0 +1,827 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.view.View;
import android.widget.LinearLayout;
import android.animation.ObjectAnimator;
import android.util.Property;
import android.animation.TypeEvaluator;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.AbstractDraweeController;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeHolder;
import com.facebook.drawee.view.DraweeView;
import com.facebook.fresco.ui.common.ControllerListener2;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.google.android.gms.common.images.ImageManager;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.maps.android.collections.MarkerManager;
import com.rnmaps.fabric.event.OnDeselectEvent;
import com.rnmaps.fabric.event.OnDragEndEvent;
import com.rnmaps.fabric.event.OnDragEvent;
import com.rnmaps.fabric.event.OnDragStartEvent;
import com.rnmaps.fabric.event.OnPressEvent;
import com.rnmaps.fabric.event.OnSelectEvent;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.Map;
public class MapMarker extends MapFeature {
private MarkerOptions markerOptions;
private Marker marker;
private int width;
private int height;
private String identifier;
private LatLng position;
private String title;
private String snippet;
private boolean anchorIsSet;
private float anchorX;
private float anchorY;
private MapCallout calloutView;
private View wrappedCalloutView;
private final Context context;
private float markerHue = 0.0f; // should be between 0 and 360
private BitmapDescriptor iconBitmapDescriptor;
private Bitmap iconBitmap;
private float rotation = 0.0f;
private boolean flat = false;
private boolean draggable = false;
private int zIndex = 0;
private float opacity = 1.0f;
private float calloutAnchorX;
private float calloutAnchorY;
private boolean calloutAnchorIsSet;
private int updated = 0;
private boolean tracksViewChanges = true;
private boolean tracksViewChangesActive = false;
private boolean hasCustomMarkerView = false;
private final MapMarkerManager markerManager;
private String imageUri;
private boolean loadingImage;
private SoftReference<MarkerManager.Collection> markerCollectionRef;
private final DraweeHolder<?> logoHolder;
private ImageManager.OnImageLoadedListener imageLoadedListener;
private DataSource<CloseableReference<CloseableImage>> dataSource;
private final ControllerListener<ImageInfo> mLogoControllerListener =
new BaseControllerListener<ImageInfo>() {
@Override
public void onSubmit(String id, Object callerContext) {
loadingImage = true;
}
@Override
public void onFinalImageSet(
String id,
@Nullable final ImageInfo imageInfo,
@Nullable Animatable animatable) {
CloseableReference<CloseableImage> imageReference = null;
try {
imageReference = dataSource.getResult();
if (imageReference != null) {
CloseableImage image = imageReference.get();
if (image instanceof CloseableStaticBitmap) {
CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) image;
Bitmap bitmap = closeableStaticBitmap.getUnderlyingBitmap();
if (bitmap != null) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
iconBitmap = bitmap;
iconBitmapDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap);
}
}
}
} finally {
dataSource.close();
if (imageReference != null) {
CloseableReference.closeSafely(imageReference);
}
}
if (MapMarker.this.markerManager != null && MapMarker.this.imageUri != null) {
MapMarker.this.markerManager.getSharedIcon(MapMarker.this.imageUri)
.updateIcon(iconBitmapDescriptor, iconBitmap);
}
update(true);
loadingImage = false;
if (imageLoadedListener != null) {
imageLoadedListener.onImageLoaded(null, null, false);
// fire and forget
imageLoadedListener = null;
}
}
};
public MapMarker(Context context, MapMarkerManager markerManager) {
super(context);
this.context = context;
this.markerManager = markerManager;
logoHolder = DraweeHolder.create(createDraweeHierarchy(), context);
logoHolder.onAttach();
}
public MapMarker(Context context, MarkerOptions options, MapMarkerManager markerManager) {
super(context);
this.context = context;
this.markerManager = markerManager;
logoHolder = DraweeHolder.create(createDraweeHierarchy(), context);
logoHolder.onAttach();
position = options.getPosition();
setAnchor(options.getAnchorU(), options.getAnchorV());
setCalloutAnchor(options.getInfoWindowAnchorU(), options.getInfoWindowAnchorV());
setTitle(options.getTitle());
setSnippet(options.getSnippet());
setRotation(options.getRotation());
setFlat(options.isFlat());
setDraggable(options.isDraggable());
setZIndex(Math.round(options.getZIndex()));
setOpacity(options.getAlpha());
iconBitmapDescriptor = options.getIcon();
}
private GenericDraweeHierarchy createDraweeHierarchy() {
return new GenericDraweeHierarchyBuilder(getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build();
}
public void setCoordinate(ReadableMap coordinate) {
setCoordinate(new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude")));
}
public void setCoordinate(LatLng position) {
this.position = position;
if (marker != null) {
marker.setPosition(position);
}
update(false);
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
update(false);
}
public void doDestroy() {
MarkerManager.Collection collection = markerCollectionRef != null
? markerCollectionRef.get()
: null;
if (collection != null) {
this.removeFromMap(collection);
}
markerCollectionRef = null;
}
public String getIdentifier() {
return this.identifier;
}
public void setTitle(String title) {
this.title = title;
if (marker != null) {
marker.setTitle(title);
}
update(false);
}
public void setSnippet(String snippet) {
this.snippet = snippet;
if (marker != null) {
marker.setSnippet(snippet);
}
update(false);
}
public void setRotation(float rotation) {
this.rotation = rotation;
if (marker != null) {
marker.setRotation(rotation);
}
update(false);
}
public void setFlat(boolean flat) {
this.flat = flat;
if (marker != null) {
marker.setFlat(flat);
}
update(false);
}
public void setDraggable(boolean draggable) {
this.draggable = draggable;
if (marker != null) {
marker.setDraggable(draggable);
}
update(false);
}
public void setZIndex(int zIndex) {
this.zIndex = zIndex;
if (marker != null) {
marker.setZIndex(zIndex);
}
update(false);
}
public void setOpacity(float opacity) {
this.opacity = opacity;
if (marker != null) {
marker.setAlpha(opacity);
}
update(false);
}
public void setMarkerHue(float markerHue) {
this.markerHue = markerHue;
update(true);
}
public void setAnchor(double x, double y) {
anchorIsSet = true;
anchorX = (float) x;
anchorY = (float) y;
if (marker != null) {
marker.setAnchor(anchorX, anchorY);
}
update(false);
}
public void setCalloutAnchor(double x, double y) {
calloutAnchorIsSet = true;
calloutAnchorX = (float) x;
calloutAnchorY = (float) y;
if (marker != null) {
marker.setInfoWindowAnchor(calloutAnchorX, calloutAnchorY);
}
update(false);
}
public void setTracksViewChanges(boolean tracksViewChanges) {
this.tracksViewChanges = tracksViewChanges;
updateTracksViewChanges();
}
private void updateTracksViewChanges() {
boolean shouldTrack = tracksViewChanges && hasCustomMarkerView && marker != null;
if (shouldTrack == tracksViewChangesActive) return;
tracksViewChangesActive = shouldTrack;
if (shouldTrack) {
ViewChangesTracker.getInstance().addMarker(this);
} else {
ViewChangesTracker.getInstance().removeMarker(this);
// Let it render one more time to avoid race conditions.
// i.e. Image onLoad ->
// ViewChangesTracker may not get a chance to render ->
// setState({ tracksViewChanges: false }) ->
// image loaded but not rendered.
updateMarkerIcon();
}
}
public LatLng getPosition() {
return position;
}
public boolean updateCustomForTracking() {
if (!tracksViewChangesActive || updated == 0) {
tracksViewChangesActive = false;
return false;
}
updateMarkerIcon();
if (updated > 0) {
updated--;
}
return true;
}
public void updateMarkerIcon() {
if (marker == null) return;
marker.setIcon(getIcon());
}
public LatLng interpolate(float fraction, LatLng a, LatLng b) {
double lat = (b.latitude - a.latitude) * fraction + a.latitude;
double lng = (b.longitude - a.longitude) * fraction + a.longitude;
return new LatLng(lat, lng);
}
public void animateToCoodinate(LatLng finalPosition, Integer duration) {
TypeEvaluator<LatLng> typeEvaluator = new TypeEvaluator<LatLng>() {
@Override
public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {
return interpolate(fraction, startValue, endValue);
}
};
Property<Marker, LatLng> property = Property.of(Marker.class, LatLng.class, "position");
ObjectAnimator animator = ObjectAnimator.ofObject(
marker,
property,
typeEvaluator,
finalPosition);
animator.setDuration(duration);
animator.start();
}
public void setImage(String uri) {
boolean shouldLoadImage = true;
if (this.markerManager != null) {
// remove marker from previous shared icon if needed, to avoid future updates from it.
// remove the shared icon completely if no markers on it as well.
// this is to avoid memory leak due to orphan bitmaps.
//
// However in case where client want to update all markers from icon A to icon B
// and after some time to update back from icon B to icon A
// it may be better to keep it though. We assume that is rare.
if (this.imageUri != null) {
this.markerManager.getSharedIcon(this.imageUri).removeMarker(this);
this.markerManager.removeSharedIconIfEmpty(this.imageUri);
}
if (uri != null) {
// listening for marker bitmap descriptor update, as well as check whether to load the image.
MapMarkerManager.AirMapMarkerSharedIcon sharedIcon = this.markerManager.getSharedIcon(uri);
sharedIcon.addMarker(this);
shouldLoadImage = sharedIcon.shouldLoadImage();
}
}
this.imageUri = uri;
if (!shouldLoadImage) {
return;
}
if (uri == null) {
iconBitmapDescriptor = null;
update(true);
} else if (uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(uri))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(imageRequest)
.setControllerListener(mLogoControllerListener)
.setOldController(logoHolder.getController())
.build();
logoHolder.setController(controller);
} else {
iconBitmapDescriptor = getBitmapDescriptorByName(uri);
int drawableId = getDrawableResourceByName(uri);
iconBitmap = BitmapFactory.decodeResource(getResources(), drawableId);
if (iconBitmap == null) { // VectorDrawable or similar
Drawable drawable = getResources().getDrawable(drawableId);
iconBitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
Canvas canvas = new Canvas(iconBitmap);
drawable.draw(canvas);
}
if (this.markerManager != null) {
this.markerManager.getSharedIcon(uri).updateIcon(iconBitmapDescriptor, iconBitmap);
}
update(true);
}
}
public void setIconBitmapDescriptor(BitmapDescriptor bitmapDescriptor, Bitmap bitmap) {
this.iconBitmapDescriptor = bitmapDescriptor;
this.iconBitmap = bitmap;
this.update(true);
}
public void setIconBitmap(Bitmap bitmap) {
this.iconBitmap = bitmap;
}
public MarkerOptions getMarkerOptions() {
if (markerOptions == null) {
markerOptions = new MarkerOptions();
}
fillMarkerOptions(markerOptions);
return markerOptions;
}
@Override
public void addView(View child, int index) {
super.addView(child, index);
// if children are added, it means we are rendering a custom marker
if (!(child instanceof MapCallout)) {
hasCustomMarkerView = true;
updateTracksViewChanges();
hackToHandleDraweeLifecycle(child);
}
update(true);
}
private void hackToHandleDraweeLifecycle(View child){
if (child instanceof DraweeView<?>) {
try {
DraweeView draweeView = (DraweeView) child;
Method onAttachMethod = DraweeView.class.getDeclaredMethod("onAttachedToWindow");
onAttachMethod.setAccessible(true);
onAttachMethod.invoke(child);
Handler mainHandler = new Handler(Looper.getMainLooper());
if (draweeView.getController() instanceof AbstractDraweeController<?,?>){
AbstractDraweeController abstractController = (AbstractDraweeController) draweeView.getController();
abstractController.addControllerListener2(new ControllerListener2() {
@Override
public void onSubmit(@NonNull String s, @Nullable Object o, @Nullable Extras extras) {
}
@Override
public void onFinalImageSet(@NonNull String s, @Nullable Object o, @Nullable Extras extras) {
mainHandler.postDelayed(() -> update(true), ((GenericDraweeHierarchy) draweeView.getHierarchy()).getFadeDuration());
}
@Override
public void onIntermediateImageSet(@NonNull String s, @Nullable Object o) {
mainHandler.post(() -> update(true));
}
@Override
public void onIntermediateImageFailed(@NonNull String s) {
}
@Override
public void onFailure(@NonNull String s, @Nullable Throwable throwable, @Nullable Extras extras) {
}
@Override
public void onRelease(@NonNull String s, @Nullable Extras extras) {
}
@Override
public void onEmptyEvent(@Nullable Object o) {
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void requestLayout() {
super.requestLayout();
if (getChildCount() == 0) {
if (hasCustomMarkerView) {
hasCustomMarkerView = false;
clearDrawableCache();
updateTracksViewChanges();
update(true);
}
} else {
// custom subview
if (!(getChildAt(0) instanceof MapCallout)) {
if (updated == 0) {
updated = 1;
updateTracksViewChanges();
}
}
}
}
@Override
public Object getFeature() {
return marker;
}
@Override
public void addToMap(Object collection) {
MarkerManager.Collection markerCollection = (MarkerManager.Collection) collection;
marker = markerCollection.addMarker(getMarkerOptions());
this.markerCollectionRef = new SoftReference<>(markerCollection);
updateTracksViewChanges();
}
@Override
public void removeFromMap(Object collection) {
if (marker == null) {
return;
}
MarkerManager.Collection markerCollection = (MarkerManager.Collection) collection;
markerCollection.remove(marker);
marker = null;
updateTracksViewChanges();
}
private BitmapDescriptor getIcon() {
if (hasCustomMarkerView) {
// creating a bitmap from an arbitrary view
if (iconBitmapDescriptor != null) {
Bitmap viewBitmap = createDrawable();
int width = Math.max(iconBitmap.getWidth(), viewBitmap.getWidth());
int height = Math.max(iconBitmap.getHeight(), viewBitmap.getHeight());
Bitmap combinedBitmap = Bitmap.createBitmap(width, height, iconBitmap.getConfig());
Canvas canvas = new Canvas(combinedBitmap);
canvas.drawBitmap(iconBitmap, 0, 0, null);
canvas.drawBitmap(viewBitmap, 0, 0, null);
return BitmapDescriptorFactory.fromBitmap(combinedBitmap);
} else {
return BitmapDescriptorFactory.fromBitmap(createDrawable());
}
} else if (iconBitmapDescriptor != null) {
// use local image as a marker
return iconBitmapDescriptor;
} else {
// render the default marker pin
return BitmapDescriptorFactory.defaultMarker(this.markerHue);
}
}
private MarkerOptions fillMarkerOptions(MarkerOptions options) {
options.position(position);
if (anchorIsSet) options.anchor(anchorX, anchorY);
if (calloutAnchorIsSet) options.infoWindowAnchor(calloutAnchorX, calloutAnchorY);
options.title(title);
options.snippet(snippet);
options.rotation(rotation);
options.flat(flat);
options.draggable(draggable);
options.zIndex(zIndex);
options.alpha(opacity);
options.icon(getIcon());
return options;
}
public void update(boolean updateIcon) {
if (marker == null) {
return;
}
if (updateIcon)
updateMarkerIcon();
if (anchorIsSet) {
marker.setAnchor(anchorX, anchorY);
} else {
marker.setAnchor(0.5f, 1.0f);
}
if (calloutAnchorIsSet) {
marker.setInfoWindowAnchor(calloutAnchorX, calloutAnchorY);
} else {
marker.setInfoWindowAnchor(0.5f, 0);
}
updated += 1;
}
public void update(int width, int height) {
this.width = width;
this.height = height;
updated += 1;
updateTracksViewChanges();
clearDrawableCache();
update(true);
}
public void redraw() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
updateMarkerIcon();
}
});
}
private Bitmap mLastBitmapCreated = null;
private void clearDrawableCache() {
mLastBitmapCreated = null;
}
private Bitmap createDrawable() {
int width = this.width <= 0 ? 100 : this.width;
int height = this.height <= 0 ? 100 : this.height;
// Do not create the doublebuffer-bitmap each time. reuse it to save memory.
Bitmap bitmap = mLastBitmapCreated;
if (bitmap == null ||
bitmap.isRecycled() ||
bitmap.getWidth() != width ||
bitmap.getHeight() != height) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mLastBitmapCreated = bitmap;
} else {
bitmap.eraseColor(Color.TRANSPARENT);
}
Canvas canvas = new Canvas(bitmap);
this.draw(canvas);
return bitmap;
}
public void setCalloutView(MapCallout view) {
this.calloutView = view;
}
public MapCallout getCalloutView() {
return this.calloutView;
}
public View getCallout() {
if (this.calloutView == null) return null;
if (this.wrappedCalloutView == null) {
this.wrapCalloutView();
}
if (this.calloutView.getTooltip()) {
return this.wrappedCalloutView;
} else {
return null;
}
}
public View getInfoContents() {
if (this.calloutView == null) return null;
if (this.wrappedCalloutView == null) {
this.wrapCalloutView();
}
if (this.calloutView.getTooltip()) {
return null;
} else {
return this.wrappedCalloutView;
}
}
private void wrapCalloutView() {
// some hackery is needed to get the arbitrary infowindow view to render centered, and
// with only the width/height that it needs.
if (this.calloutView == null || this.calloutView.getChildCount() == 0) {
return;
}
LinearLayout LL = new LinearLayout(context);
LL.setOrientation(LinearLayout.VERTICAL);
LL.setLayoutParams(new LinearLayout.LayoutParams(
this.calloutView.width,
this.calloutView.height,
0f
));
LinearLayout LL2 = new LinearLayout(context);
LL2.setOrientation(LinearLayout.HORIZONTAL);
LL2.setLayoutParams(new LinearLayout.LayoutParams(
this.calloutView.width,
this.calloutView.height,
0f
));
LL.addView(LL2);
LL2.addView(this.calloutView);
this.wrappedCalloutView = LL;
}
private int getDrawableResourceByName(String name) {
return getResources().getIdentifier(
name,
"drawable",
getContext().getPackageName());
}
public boolean isLoadingImage() {
return loadingImage;
}
public ImageManager.OnImageLoadedListener getImageLoadedListener() {
return imageLoadedListener;
}
public void setImageLoadedListener(ImageManager.OnImageLoadedListener imageLoadedListener) {
this.imageLoadedListener = imageLoadedListener;
}
public void setUpdated(boolean updated) {
if (updated) {
this.updated += 1;
} else {
this.updated = 0;
}
}
@FunctionalInterface
public interface EventCreator<T extends Event> {
T create(int surfaceId, int viewId, WritableMap payload);
}
public <T extends Event> void dispatchEvent(WritableMap payload, MapView.EventCreator<T> creator) {
// Cast context to ReactContext
ReactContext reactContext = (ReactContext) context;
// Get the event dispatcher
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
// If there is a dispatcher, create and dispatch the event
if (eventDispatcher != null) {
int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
T event = creator.create(surfaceId, getId(), payload);
eventDispatcher.dispatchEvent(event);
}
}
private BitmapDescriptor getBitmapDescriptorByName(String name) {
return BitmapDescriptorFactory.fromResource(getDrawableResourceByName(name));
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public static Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
OnSelectEvent.EVENT_NAME, MapBuilder.of("registrationName", OnSelectEvent.EVENT_NAME),
OnDeselectEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDeselectEvent.EVENT_NAME),
OnDragEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDragEvent.EVENT_NAME),
OnDragStartEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDragStartEvent.EVENT_NAME),
OnDragEndEvent.EVENT_NAME, MapBuilder.of("registrationName", OnDragEndEvent.EVENT_NAME)
);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.height = b - t;
this.width = r - l;
}
}

View File

@@ -0,0 +1,368 @@
package com.rnmaps.maps;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.R;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.LatLng;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class MapMarkerManager extends ViewGroupManager<MapMarker> {
public static class AirMapMarkerSharedIcon {
private BitmapDescriptor iconBitmapDescriptor;
private Bitmap bitmap;
private final Map<MapMarker, Boolean> markers;
private boolean loadImageStarted;
public AirMapMarkerSharedIcon() {
this.markers = new WeakHashMap<>();
this.loadImageStarted = false;
}
/**
* check whether the load image process started.
* caller AirMapMarker will only need to load it when this returns true.
*
* @return true if it is not started, false otherwise.
*/
public synchronized boolean shouldLoadImage() {
if (!this.loadImageStarted) {
this.loadImageStarted = true;
return true;
}
return false;
}
/**
* subscribe icon update for given marker.
* <p>
* The marker is wrapped in weakReference, so no need to remove it explicitly.
*
* @param marker
*/
public synchronized void addMarker(MapMarker marker) {
this.markers.put(marker, true);
if (this.iconBitmapDescriptor != null) {
marker.setIconBitmapDescriptor(this.iconBitmapDescriptor, this.bitmap);
}
}
/**
* Remove marker from this shared icon.
* <p>
* Marker will only need to call it when the marker receives a different marker image uri.
*
* @param marker
*/
public synchronized void removeMarker(MapMarker marker) {
this.markers.remove(marker);
}
/**
* check if there is markers still listening on this icon.
* when there are not markers listen on it, we can remove it.
*
* @return true if there is, false otherwise
*/
public synchronized boolean hasMarker() {
return this.markers.isEmpty();
}
/**
* Update the bitmap descriptor and bitmap for the image uri.
* And notify all subscribers about the update.
*
* @param bitmapDescriptor
* @param bitmap
*/
public synchronized void updateIcon(BitmapDescriptor bitmapDescriptor, Bitmap bitmap) {
this.iconBitmapDescriptor = bitmapDescriptor;
this.bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
if (this.markers.isEmpty()) {
return;
}
for (Map.Entry<MapMarker, Boolean> markerEntry : markers.entrySet()) {
if (markerEntry.getKey() != null) {
markerEntry.getKey().setIconBitmapDescriptor(bitmapDescriptor, bitmap);
}
}
}
}
private final Map<String, AirMapMarkerSharedIcon> sharedIcons = new ConcurrentHashMap<>();
/**
* get the shared icon object, if not existed, create a new one and store it.
*
* @param uri
* @return the icon object for the given uri.
*/
public AirMapMarkerSharedIcon getSharedIcon(String uri) {
AirMapMarkerSharedIcon icon = this.sharedIcons.get(uri);
if (icon == null) {
synchronized (this) {
if ((icon = this.sharedIcons.get(uri)) == null) {
icon = new AirMapMarkerSharedIcon();
this.sharedIcons.put(uri, icon);
}
}
}
return icon;
}
/**
* Remove the share icon object from our sharedIcons map when no markers are listening for it.
*
* @param uri
*/
public void removeSharedIconIfEmpty(String uri) {
AirMapMarkerSharedIcon icon = this.sharedIcons.get(uri);
if (icon == null) {
return;
}
if (!icon.hasMarker()) {
synchronized (this) {
if ((icon = this.sharedIcons.get(uri)) != null && !icon.hasMarker()) {
this.sharedIcons.remove(uri);
}
}
}
}
public MapMarkerManager() {
}
@Override
public String getName() {
return "AIRMapMarker";
}
@Override
public MapMarker createViewInstance(ThemedReactContext context) {
return new MapMarker(context, this);
}
@ReactProp(name = "coordinate")
public void setCoordinate(MapMarker view, ReadableMap map) {
view.setCoordinate(map);
}
@ReactProp(name = "title")
public void setTitle(MapMarker view, String title) {
view.setTitle(title);
}
@ReactProp(name = "identifier")
public void setIdentifier(MapMarker view, String identifier) {
view.setIdentifier(identifier);
}
@ReactProp(name = "description")
public void setDescription(MapMarker view, String description) {
view.setSnippet(description);
}
// NOTE(lmr):
// android uses normalized coordinate systems for this, and is provided through the
// `anchor` property and `calloutAnchor` instead. Perhaps some work could be done
// to normalize iOS and android to use just one of the systems.
// @ReactProp(name = "centerOffset")
// public void setCenterOffset(AirMapMarker view, ReadableMap map) {
//
// }
//
// @ReactProp(name = "calloutOffset")
// public void setCalloutOffset(AirMapMarker view, ReadableMap map) {
//
// }
@ReactProp(name = "anchor")
public void setAnchor(MapMarker view, ReadableMap map) {
// should default to (0.5, 1) (bottom middle)
double x = map != null && map.hasKey("x") ? map.getDouble("x") : 0.5;
double y = map != null && map.hasKey("y") ? map.getDouble("y") : 1.0;
view.setAnchor(x, y);
}
@ReactProp(name = "calloutAnchor")
public void setCalloutAnchor(MapMarker view, ReadableMap map) {
// should default to (0.5, 0) (top middle)
double x = map != null && map.hasKey("x") ? map.getDouble("x") : 0.5;
double y = map != null && map.hasKey("y") ? map.getDouble("y") : 0.0;
view.setCalloutAnchor(x, y);
}
@ReactProp(name = "image")
public void setImage(MapMarker view, @Nullable String source) {
view.setImage(source);
}
// public void setImage(AirMapMarker view, ReadableMap image) {
// view.setImage(image);
// }
@ReactProp(name = "icon")
public void setIcon(MapMarker view, @Nullable String source) {
view.setImage(source);
}
@ReactProp(name = "pinColor", defaultInt = Color.RED, customType = "Color")
public void setPinColor(MapMarker view, int pinColor) {
float[] hsv = new float[3];
Color.colorToHSV(pinColor, hsv);
// NOTE: android only supports a hue
view.setMarkerHue(hsv[0]);
}
@ReactProp(name = "rotation", defaultFloat = 0.0f)
public void setMarkerRotation(MapMarker view, float rotation) {
view.setRotation(rotation);
}
@ReactProp(name = "flat", defaultBoolean = false)
public void setFlat(MapMarker view, boolean flat) {
view.setFlat(flat);
}
@ReactProp(name = "draggable", defaultBoolean = false)
public void setDraggable(MapMarker view, boolean draggable) {
view.setDraggable(draggable);
}
@Override
@ReactProp(name = "zIndex", defaultFloat = 0.0f)
public void setZIndex(MapMarker view, float zIndex) {
super.setZIndex(view, zIndex);
int integerZIndex = Math.round(zIndex);
view.setZIndex(integerZIndex);
}
@Override
@ReactProp(name = "opacity", defaultFloat = 1.0f)
public void setOpacity(MapMarker view, float opacity) {
super.setOpacity(view, opacity);
view.setOpacity(opacity);
}
@ReactProp(name = "tracksViewChanges", defaultBoolean = true)
public void setTracksViewChanges(MapMarker view, boolean tracksViewChanges) {
view.setTracksViewChanges(tracksViewChanges);
}
@ReactProp(name = "accessibilityLabel")
public void setAccessibilityLabel(MapMarker view, @Nullable String accessibilityLabel) {
view.setTag(R.id.accessibility_label, accessibilityLabel);
}
@Override
public void addView(MapMarker parent, View child, int index) {
// if an <Callout /> component is a child, then it is a callout view, NOT part of the
// marker.
if (child instanceof MapCallout) {
parent.setCalloutView((MapCallout) child);
} else {
super.addView(parent, child, index);
parent.update(true);
}
}
@Override
public void removeViewAt(MapMarker parent, int index) {
super.removeViewAt(parent, index);
parent.update(true);
}
@Override
public void receiveCommand(@NonNull MapMarker view, String commandId, @Nullable ReadableArray args) {
int duration;
double lat;
double lng;
ReadableMap region;
switch (commandId) {
case "showCallout":
((Marker) view.getFeature()).showInfoWindow();
break;
case "hideCallout":
((Marker) view.getFeature()).hideInfoWindow();
break;
case "animateMarkerToCoordinate":
if (args == null) {
break;
}
region = args.getMap(0);
duration = args.getInt(1);
lng = region.getDouble("longitude");
lat = region.getDouble("latitude");
view.animateToCoodinate(new LatLng(lat, lng), duration);
break;
case "redraw":
view.updateMarkerIcon();
break;
}
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Map<String, String>>builder()
.put("onCalloutPress", MapBuilder.of("registrationName", "onCalloutPress"))
.put("onDragStart", MapBuilder.of("registrationName", "onDragStart"))
.put("onDrag", MapBuilder.of("registrationName", "onDrag"))
.put("onDragEnd", MapBuilder.of("registrationName", "onDragEnd"))
.put("onSelect", MapBuilder.of("registrationName", "onSelect"))
.put("onDeselect", MapBuilder.of("registrationName", "onDeselect"))
.build();
}
@Override
@Nullable
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.<String, Map<String, Object>>builder()
.put("onPress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onPress")))
.build();
}
@Override
public LayoutShadowNode createShadowNodeInstance() {
// we use a custom shadow node that emits the width/height of the view
// after layout with the updateExtraData method. Without this, we can't generate
// a bitmap of the appropriate width/height of the rendered view.
return new SizeReportingShadowNode();
}
@Override
public void updateExtraData(MapMarker view, Object extraData) {
// This method is called from the shadow node with the width/height of the rendered
// marker view.
HashMap<String, Float> data = (HashMap<String, Float>) extraData;
float width = data.get("width");
float height = data.get("height");
view.update((int) width, (int) height);
}
}

View File

@@ -0,0 +1,283 @@
package com.rnmaps.maps;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.location.Address;
import android.location.Geocoder;
import android.net.Uri;
import android.util.Base64;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.module.annotations.ReactModule;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ReactModule(name = MapModule.NAME)
public class MapModule extends ReactContextBaseJavaModule {
public static final String NAME = "AirMapModule";
public static final String SNAPSHOT_RESULT_FILE = "file";
public static final String SNAPSHOT_RESULT_BASE64 = "base64";
public static final String SNAPSHOT_FORMAT_PNG = "png";
public static final String SNAPSHOT_FORMAT_JPG = "jpg";
public MapModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("legalNotice", "This license information is displayed in Settings > Google > Open Source on any device running Google Play services.");
return constants;
}
public Activity getActivity() {
return getCurrentActivity();
}
public static void closeQuietly(Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (IOException ignored) {
}
}
@ReactMethod
public void takeSnapshot(final int tag, final ReadableMap options, final Promise promise) {
// Parse and verity options
final ReactApplicationContext context = getReactApplicationContext();
final String format = options.hasKey("format") ? options.getString("format") : "png";
final Bitmap.CompressFormat compressFormat =
format.equals(SNAPSHOT_FORMAT_PNG) ? Bitmap.CompressFormat.PNG :
format.equals(SNAPSHOT_FORMAT_JPG) ? Bitmap.CompressFormat.JPEG : null;
final double quality = options.hasKey("quality") ? options.getDouble("quality") : 1.0;
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
final Integer width =
options.hasKey("width") ? (int) (displayMetrics.density * options.getDouble("width")) : 0;
final Integer height =
options.hasKey("height") ? (int) (displayMetrics.density * options.getDouble("height")) : 0;
final String result = options.hasKey("result") ? options.getString("result") : "file";
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
view.map.snapshot(new GoogleMap.SnapshotReadyCallback() {
public void onSnapshotReady(@Nullable Bitmap snapshot) {
// Convert image to requested width/height if necessary
if (snapshot == null) {
promise.reject("Failed to generate bitmap, snapshot = null");
return;
}
if ((width != 0) && (height != 0) &&
(width != snapshot.getWidth() || height != snapshot.getHeight())) {
snapshot = Bitmap.createScaledBitmap(snapshot, width, height, true);
}
// Save the snapshot to disk
if (result.equals(SNAPSHOT_RESULT_FILE)) {
File tempFile;
FileOutputStream outputStream;
try {
tempFile =
File.createTempFile("AirMapSnapshot", "." + format, context.getCacheDir());
outputStream = new FileOutputStream(tempFile);
} catch (Exception e) {
promise.reject(e);
return;
}
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
String uri = Uri.fromFile(tempFile).toString();
promise.resolve(uri);
} else if (result.equals(SNAPSHOT_RESULT_BASE64)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
snapshot.compress(compressFormat, (int) (100.0 * quality), outputStream);
closeQuietly(outputStream);
byte[] bytes = outputStream.toByteArray();
String data = Base64.encodeToString(bytes, Base64.NO_WRAP);
promise.resolve(data);
}
}
});
return null;
});
// Add UI-block so we can get a valid reference to the map-view
uiBlock.addToUIManager();
}
@ReactMethod
public void getCamera(final int tag, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
CameraPosition position = view.map.getCameraPosition();
WritableMap centerJson = new WritableNativeMap();
centerJson.putDouble("latitude", position.target.latitude);
centerJson.putDouble("longitude", position.target.longitude);
WritableMap cameraJson = new WritableNativeMap();
cameraJson.putMap("center", centerJson);
cameraJson.putDouble("heading", (double) position.bearing);
cameraJson.putDouble("zoom", (double) position.zoom);
cameraJson.putDouble("pitch", (double) position.tilt);
promise.resolve(cameraJson);
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void getAddressFromCoordinates(final int tag, final ReadableMap coordinate, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, mapView -> {
if (coordinate == null ||
!coordinate.hasKey("latitude") ||
!coordinate.hasKey("longitude")) {
promise.reject("Invalid coordinate format");
return null;
}
Geocoder geocoder = new Geocoder(context);
try {
List<Address> list =
geocoder.getFromLocation(coordinate.getDouble("latitude"), coordinate.getDouble("longitude"), 1);
if (list.isEmpty()) {
promise.reject("Can not get address location");
return null;
}
Address address = list.get(0);
WritableMap addressJson = new WritableNativeMap();
addressJson.putString("name", address.getFeatureName());
addressJson.putString("locality", address.getLocality());
addressJson.putString("thoroughfare", address.getThoroughfare());
addressJson.putString("subThoroughfare", address.getSubThoroughfare());
addressJson.putString("subLocality", address.getSubLocality());
addressJson.putString("administrativeArea", address.getAdminArea());
addressJson.putString("subAdministrativeArea", address.getSubAdminArea());
addressJson.putString("postalCode", address.getPostalCode());
addressJson.putString("countryCode", address.getCountryCode());
addressJson.putString("country", address.getCountryName());
promise.resolve(addressJson);
} catch (IOException e) {
promise.reject("Can not get address location");
}
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void pointForCoordinate(final int tag, ReadableMap coordinate, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final LatLng coord = new LatLng(
coordinate.hasKey("latitude") ? coordinate.getDouble("latitude") : 0.0,
coordinate.hasKey("longitude") ? coordinate.getDouble("longitude") : 0.0
);
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
Point pt = view.map.getProjection().toScreenLocation(coord);
WritableMap ptJson = new WritableNativeMap();
ptJson.putDouble("x", (double) pt.x / density);
ptJson.putDouble("y", (double) pt.y / density);
promise.resolve(ptJson);
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void coordinateForPoint(final int tag, ReadableMap point, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final double density = (double) context.getResources().getDisplayMetrics().density;
final Point pt = new Point(
point.hasKey("x") ? (int) (point.getDouble("x") * density) : 0,
point.hasKey("y") ? (int) (point.getDouble("y") * density) : 0
);
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
LatLng coord = view.map.getProjection().fromScreenLocation(pt);
WritableMap coordJson = new WritableNativeMap();
coordJson.putDouble("latitude", coord.latitude);
coordJson.putDouble("longitude", coord.longitude);
promise.resolve(coordJson);
return null;
});
uiBlock.addToUIManager();
}
@ReactMethod
public void getMapBoundaries(final int tag, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
MapUIBlock uiBlock = new MapUIBlock(tag, promise, context, view -> {
double[][] boundaries = view.getMapBoundaries();
WritableMap coordinates = new WritableNativeMap();
WritableMap northEastHash = new WritableNativeMap();
WritableMap southWestHash = new WritableNativeMap();
northEastHash.putDouble("longitude", boundaries[0][0]);
northEastHash.putDouble("latitude", boundaries[0][1]);
southWestHash.putDouble("longitude", boundaries[1][0]);
southWestHash.putDouble("latitude", boundaries[1][1]);
coordinates.putMap("northEast", northEastHash);
coordinates.putMap("southWest", southWestHash);
promise.resolve(coordinates);
return null;
});
uiBlock.addToUIManager();
}
}

View File

@@ -0,0 +1,262 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.google.android.gms.common.images.ImageManager;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.maps.android.collections.GroundOverlayManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.Map;
import java.util.concurrent.Executors;
public class MapOverlay extends MapFeature {
private String imageUri;
private GroundOverlayOptions groundOverlayOptions;
private GroundOverlay groundOverlay;
private LatLngBounds bounds;
private float bearing;
private BitmapDescriptor bitmapDescriptor;
private boolean tappable;
private float zIndex;
private float transparency;
private DataSource<CloseableReference<CloseableImage>> dataSource;
private GroundOverlayManager.Collection groundOverlayCollection;
private ImageManager.OnImageLoadedListener imageLoadedListener;
public MapOverlay(Context context) {
super(context);
transparency = 1;
}
private GenericDraweeHierarchy createDraweeHierarchy() {
return new GenericDraweeHierarchyBuilder(getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setFadeDuration(0)
.build();
}
public void setBounds(LatLngBounds bounds) {
this.bounds = bounds;
if (this.groundOverlay != null) {
this.groundOverlay.setPositionFromBounds(this.bounds);
}
}
public void setBearing(float bearing) {
this.bearing = bearing;
if (this.groundOverlay != null) {
this.groundOverlay.setBearing(bearing);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (this.groundOverlay != null) {
this.groundOverlay.setZIndex(zIndex);
}
}
public void setTransparency(float transparency) {
this.transparency = transparency;
if (groundOverlay != null) {
groundOverlay.setTransparency(transparency);
}
}
public void setImage(String uri) {
boolean shouldLoadImage = true;
this.imageUri = uri;
if (!shouldLoadImage) {
return;
}
if (uri == null) {
bitmapDescriptor = null;
} else if (uri.startsWith("http://") || uri.startsWith("https://") ||
uri.startsWith("file://") || uri.startsWith("asset://") || uri.startsWith("data:")) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(uri))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
DataSubscriber<CloseableReference<CloseableImage>> subscriber = new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
@Override
protected void onNewResultImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (!dataSource.isFinished()) {
return;
}
CloseableReference<CloseableImage> imageReference = dataSource.getResult();
if (imageReference != null) {
try {
CloseableImage closeableImage = imageReference.get();
if (closeableImage instanceof CloseableBitmap) {
Bitmap bitmap = ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
if (bitmap != null) {
bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap);
}
}
} finally {
CloseableReference.closeSafely(imageReference);
}
new Handler(Looper.getMainLooper()).post(() -> update());
}
}
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
// Handle failure
}
};
dataSource.subscribe(subscriber, Executors.newSingleThreadExecutor());
} else {
bitmapDescriptor = getBitmapDescriptorByName(uri);
}
}
private BitmapDescriptor getBitmapDescriptorByName(String name) {
return BitmapDescriptorFactory.fromResource(getDrawableResourceByName(name));
}
private int getDrawableResourceByName(String name) {
return getResources().getIdentifier(
name,
"drawable",
getContext().getPackageName());
}
public void setTappable(boolean tapabble) {
this.tappable = tapabble;
if (groundOverlay != null) {
groundOverlay.setClickable(tappable);
}
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public GroundOverlayOptions getGroundOverlayOptions() {
if (this.groundOverlayOptions == null) {
this.groundOverlayOptions = createGroundOverlayOptions();
}
return this.groundOverlayOptions;
}
private GroundOverlayOptions createGroundOverlayOptions() {
if (this.groundOverlayOptions != null) {
return this.groundOverlayOptions;
}
GroundOverlayOptions options = new GroundOverlayOptions();
if (this.bitmapDescriptor != null) {
options.image(bitmapDescriptor);
} else {
// add stub image to be able to instantiate the overlay
// and store a reference to it in MapView
options.image(BitmapDescriptorFactory.defaultMarker());
// hide overlay until real image gets added
options.visible(false);
}
options.positionFromBounds(bounds);
options.zIndex(zIndex);
options.bearing(bearing);
// options.transparency(transparency);
return options;
}
@Override
public Object getFeature() {
return groundOverlay;
}
@Override
public void addToMap(Object collection) {
GroundOverlayManager.Collection groundOverlayCollection = (GroundOverlayManager.Collection) collection;
GroundOverlayOptions groundOverlayOptions = getGroundOverlayOptions();
if (groundOverlayOptions != null) {
groundOverlay = groundOverlayCollection.addGroundOverlay(groundOverlayOptions);
groundOverlay.setClickable(this.tappable);
} else {
this.groundOverlayCollection = groundOverlayCollection;
}
}
@Override
public void removeFromMap(Object collection) {
if (groundOverlay != null) {
GroundOverlayManager.Collection groundOverlayCollection = (GroundOverlayManager.Collection) collection;
groundOverlayCollection.remove(groundOverlay);
groundOverlay = null;
groundOverlayOptions = null;
}
groundOverlayCollection = null;
}
public void update() {
this.groundOverlay = getGroundOverlay();
if (this.groundOverlay != null) {
this.groundOverlay.setVisible(true);
this.groundOverlay.setImage(this.bitmapDescriptor);
// this.groundOverlay.setTransparency(this.transparency);
this.groundOverlay.setClickable(this.tappable);
}
}
private GroundOverlay getGroundOverlay() {
if (this.groundOverlay != null) {
return this.groundOverlay;
}
if (this.groundOverlayCollection == null) {
return null;
}
GroundOverlayOptions groundOverlayOptions = getGroundOverlayOptions();
if (groundOverlayOptions != null) {
return this.groundOverlayCollection.addGroundOverlay(groundOverlayOptions);
}
return null;
}
}

View File

@@ -0,0 +1,97 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import java.util.Map;
public class MapOverlayManager extends ViewGroupManager<MapOverlay> {
public MapOverlayManager(ReactApplicationContext reactContext) {
super();
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapOverlay";
}
@Override
public MapOverlay createViewInstance(ThemedReactContext context) {
return new MapOverlay(context);
}
@ReactProp(name = "bounds")
public void setBounds(MapOverlay view, ReadableArray bounds) {
view.setBounds(fixBoundsIfNecessary(bounds));
}
// seems like apple users north west, south east
// google uses north east, south west
private static LatLngBounds fixBoundsIfNecessary(ReadableArray bounds) {
double lat1 = bounds.getArray(0).getDouble(0);
double lon1 = bounds.getArray(0).getDouble(1);
double lat2 = bounds.getArray(1).getDouble(0);
double lon2 = bounds.getArray(1).getDouble(1);
// Ensure lat1/lon1 is the SW corner and lat2/lon2 is the NE corner
double southLat = Math.min(lat1, lat2);
double northLat = Math.max(lat1, lat2);
double westLon = Math.min(lon1, lon2);
double eastLon = Math.max(lon1, lon2);
// Create corrected LatLngs
LatLng sw = new LatLng(southLat, westLon);
LatLng ne = new LatLng(northLat, eastLon);
return new LatLngBounds(sw, ne);
}
@ReactProp(name = "bearing")
public void setBearing(MapOverlay view, float bearing){
view.setBearing(bearing);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapOverlay view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "opacity", defaultFloat = 1.0f)
public void setOpacity(MapOverlay view, float opacity) {
view.setTransparency(1 - opacity);
}
@ReactProp(name = "image")
public void setImage(MapOverlay view, @Nullable String source) {
view.setImage(source);
}
@ReactProp(name = "tappable", defaultBoolean = false)
public void setTappable(MapOverlay view, boolean tapabble) {
view.setTappable(tapabble);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}

View File

@@ -0,0 +1,224 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.google.android.gms.maps.model.Dash;
import com.google.android.gms.maps.model.Gap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.PatternItem;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.maps.android.collections.PolygonManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MapPolygon extends MapFeature {
private PolygonOptions polygonOptions;
private Polygon polygon;
private List<LatLng> coordinates;
private List<List<LatLng>> holes;
private int strokeColor;
private int fillColor;
private float strokeWidth;
private boolean geodesic;
private boolean tappable;
private float zIndex;
private ReadableArray patternValues;
private List<PatternItem> pattern;
public MapPolygon(Context context) {
super(context);
strokeWidth = 10;
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public <T extends Event> void dispatchEvent(WritableMap payload, MapView.EventCreator<T> creator, ReactContext reactContext) {
// Get the event dispatcher
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, getId());
// If there is a dispatcher, create and dispatch the event
if (eventDispatcher != null) {
int surfaceId = UIManagerHelper.getSurfaceId(reactContext);
T event = creator.create(surfaceId, getId(), payload);
eventDispatcher.dispatchEvent(event);
}
}
public void setCoordinates(ReadableArray coordinates) {
// it's kind of a bummer that we can't run map() or anything on the ReadableArray
this.coordinates = new ArrayList<>(coordinates.size());
for (int i = 0; i < coordinates.size(); i++) {
ReadableMap coordinate = coordinates.getMap(i);
this.coordinates.add(i,
new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude")));
}
if (polygon != null) {
polygon.setPoints(this.coordinates);
}
}
public void setHoles(ReadableArray holes) {
if (holes == null) { return; }
this.holes = new ArrayList<>(holes.size());
for (int i = 0; i < holes.size(); i++) {
ReadableArray hole = holes.getArray(i);
if (hole.size() < 3) { continue; }
List<LatLng> coordinates = new ArrayList<>();
for (int j = 0; j < hole.size(); j++) {
ReadableMap coordinate = hole.getMap(j);
coordinates.add(new LatLng(
coordinate.getDouble("latitude"),
coordinate.getDouble("longitude")));
}
// If hole is triangle
if (coordinates.size() == 3) {
coordinates.add(coordinates.get(0));
}
this.holes.add(coordinates);
}
if (polygon != null) {
polygon.setHoles(this.holes);
}
}
public void setFillColor(int color) {
this.fillColor = color;
if (polygon != null) {
polygon.setFillColor(color);
}
}
public void setStrokeColor(int color) {
this.strokeColor = color;
if (polygon != null) {
polygon.setStrokeColor(color);
}
}
public void setStrokeWidth(float width) {
this.strokeWidth = width;
if (polygon != null) {
polygon.setStrokeWidth(width);
}
}
public void setTappable(boolean tapabble) {
this.tappable = tapabble;
if (polygon != null) {
polygon.setClickable(tappable);
}
}
public void setGeodesic(boolean geodesic) {
this.geodesic = geodesic;
if (polygon != null) {
polygon.setGeodesic(geodesic);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (polygon != null) {
polygon.setZIndex(zIndex);
}
}
public void setLineDashPattern(ReadableArray patternValues) {
this.patternValues = patternValues;
this.applyPattern();
}
private void applyPattern() {
if(patternValues == null) {
return;
}
this.pattern = new ArrayList<>(patternValues.size());
for (int i = 0; i < patternValues.size(); i++) {
float patternValue = (float) patternValues.getDouble(i);
boolean isGap = i % 2 != 0;
if(isGap) {
this.pattern.add(new Gap(patternValue));
}else {
PatternItem patternItem;
patternItem = new Dash(patternValue);
this.pattern.add(patternItem);
}
}
if(polygon != null) {
polygon.setStrokePattern(this.pattern);
}
}
public PolygonOptions getPolygonOptions() {
if (polygonOptions == null) {
polygonOptions = createPolygonOptions();
}
return polygonOptions;
}
private PolygonOptions createPolygonOptions() {
PolygonOptions options = new PolygonOptions();
options.addAll(coordinates);
options.fillColor(fillColor);
options.strokeColor(strokeColor);
options.strokeWidth(strokeWidth);
options.geodesic(geodesic);
options.zIndex(zIndex);
options.strokePattern(this.pattern);
options.clickable(this.tappable);
if (this.holes != null) {
for (int i = 0; i < holes.size(); i++) {
options.addHole(holes.get(i));
}
}
return options;
}
@Override
public Object getFeature() {
return polygon;
}
@Override
public void addToMap(Object collection) {
PolygonManager.Collection polygonCollection = (PolygonManager.Collection) collection;
polygon = polygonCollection.addPolygon(getPolygonOptions());
}
@Override
public void removeFromMap(Object collection) {
PolygonManager.Collection polygonCollection = (PolygonManager.Collection) collection;
polygonCollection.remove(polygon);
}
}

View File

@@ -0,0 +1,93 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.Map;
public class MapPolygonManager extends ViewGroupManager<MapPolygon> {
private final DisplayMetrics metrics;
public MapPolygonManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapPolygon";
}
@Override
public MapPolygon createViewInstance(ThemedReactContext context) {
return new MapPolygon(context);
}
@ReactProp(name = "coordinates")
public void setCoordinate(MapPolygon view, ReadableArray coordinates) {
view.setCoordinates(coordinates);
}
@ReactProp(name = "holes")
public void setHoles(MapPolygon view, ReadableArray holes) {
view.setHoles(holes);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapPolygon view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setStrokeWidth(widthInScreenPx);
}
@ReactProp(name = "fillColor", defaultInt = Color.RED, customType = "Color")
public void setFillColor(MapPolygon view, int color) {
view.setFillColor(color);
}
@ReactProp(name = "strokeColor", defaultInt = Color.RED, customType = "Color")
public void setStrokeColor(MapPolygon view, int color) {
view.setStrokeColor(color);
}
@ReactProp(name = "tappable", defaultBoolean = false)
public void setTappable(MapPolygon view, boolean tapabble) {
view.setTappable(tapabble);
}
@ReactProp(name = "geodesic", defaultBoolean = false)
public void setGeodesic(MapPolygon view, boolean geodesic) {
view.setGeodesic(geodesic);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapPolygon view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "lineDashPattern")
public void setLineDashPattern(MapPolygon view, ReadableArray patternValues) {
view.setLineDashPattern(patternValues);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}

View File

@@ -0,0 +1,237 @@
package com.rnmaps.maps;
import android.content.Context;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.google.android.gms.maps.model.ButtCap;
import com.google.android.gms.maps.model.Cap;
import com.google.android.gms.maps.model.Dash;
import com.google.android.gms.maps.model.Dot;
import com.google.android.gms.maps.model.Gap;
import com.google.android.gms.maps.model.JointType;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.PatternItem;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.gms.maps.model.RoundCap;
import com.google.android.gms.maps.model.SquareCap;
import com.google.android.gms.maps.model.StrokeStyle;
import com.google.android.gms.maps.model.StyleSpan;
import com.google.maps.android.collections.PolylineManager;
import com.rnmaps.fabric.event.OnPressEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MapPolyline extends MapFeature {
private PolylineOptions polylineOptions;
private Polyline polyline;
private List<LatLng> coordinates;
private int color;
private float width;
private boolean tappable;
private boolean geodesic;
private float zIndex;
private Cap lineCap = new RoundCap();
private ReadableArray patternValues;
private List<PatternItem> pattern;
private List<StyleSpan> spans;
public MapPolyline(Context context) {
super(context);
}
public void setCoordinates(ReadableArray coordinates) {
this.coordinates = new ArrayList<>(coordinates.size());
for (int i = 0; i < coordinates.size(); i++) {
ReadableMap coordinate = coordinates.getMap(i);
this.coordinates.add(i,
new LatLng(coordinate.getDouble("latitude"), coordinate.getDouble("longitude")));
}
if (polyline != null) {
polyline.setPoints(this.coordinates);
}
}
public void setColor(int color) {
this.color = color;
if (polyline != null) {
polyline.setColor(color);
}
}
public void setStrokeColors(ReadableArray strokeColors) {
List<StyleSpan> spans = new ArrayList<>();
for (int i = 0; i < strokeColors.size(); i++) {
StrokeStyle stroke;
if (i == 0) {
stroke = StrokeStyle.colorBuilder(strokeColors.getInt(i)).build();
} else {
stroke = StrokeStyle.gradientBuilder(strokeColors.getInt(i - 1), strokeColors.getInt(i)).build();
}
spans.add(new StyleSpan(stroke));
}
this.spans = spans;
if (polyline != null){
polyline.setSpans(spans);
}
}
public void setWidth(float width) {
this.width = width;
if (polyline != null) {
polyline.setWidth(width);
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (polyline != null) {
polyline.setZIndex(zIndex);
}
}
public void setTappable(boolean tapabble) {
this.tappable = tapabble;
if (polyline != null) {
polyline.setClickable(tappable);
}
}
public void setGeodesic(boolean geodesic) {
this.geodesic = geodesic;
if (polyline != null) {
polyline.setGeodesic(geodesic);
}
}
public void setLineCap(Cap cap) {
this.lineCap = cap;
if (polyline != null) {
polyline.setStartCap(cap);
polyline.setEndCap(cap);
}
this.applyPattern();
}
public void setLineDashPattern(ReadableArray patternValues) {
this.patternValues = patternValues;
this.applyPattern();
}
private void applyPattern() {
if (patternValues == null) {
return;
}
this.pattern = new ArrayList<>(patternValues.size());
for (int i = 0; i < patternValues.size(); i++) {
float patternValue = (float) patternValues.getDouble(i);
boolean isGap = i % 2 != 0;
if (isGap) {
this.pattern.add(new Gap(patternValue));
} else {
PatternItem patternItem;
boolean isLineCapRound = this.lineCap instanceof RoundCap;
if (isLineCapRound) {
patternItem = new Dot();
} else {
patternItem = new Dash(patternValue);
}
this.pattern.add(patternItem);
}
}
if (polyline != null) {
polyline.setPattern(this.pattern);
}
}
public PolylineOptions getPolylineOptions() {
if (polylineOptions == null) {
polylineOptions = createPolylineOptions();
}
return polylineOptions;
}
private PolylineOptions createPolylineOptions() {
PolylineOptions options = new PolylineOptions();
options.addAll(coordinates);
options.color(color);
options.width(width);
options.geodesic(geodesic);
options.zIndex(zIndex);
options.startCap(lineCap);
options.endCap(lineCap);
options.pattern(this.pattern);
return options;
}
@Override
public Object getFeature() {
return polyline;
}
@Override
public void addToMap(Object collection) {
PolylineManager.Collection polylineCollection = (PolylineManager.Collection) collection;
polyline = polylineCollection.addPolyline(getPolylineOptions());
polyline.setClickable(this.tappable);
if (spans != null){
polyline.setSpans(spans);
}
}
@Override
public void removeFromMap(Object collection) {
PolylineManager.Collection polylineCollection = (PolylineManager.Collection) collection;
polylineCollection.remove(polyline);
}
public void setLineCap(String lineCap) {
Cap cap;
switch (lineCap) {
case "round":
cap = new RoundCap();
break;
case "square":
cap = new SquareCap();
break;
case "butt":
default:
cap = new ButtCap();
break;
}
setLineCap(cap);
}
public static Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
builder.put(OnPressEvent.EVENT_NAME, MapBuilder.of("registrationName", OnPressEvent.EVENT_NAME));
return builder.build();
}
public void setLineJoin(String lineJoin) {
int type;
switch (lineJoin) {
case "round":
type = JointType.ROUND;
break;
case "bevel":
type = JointType.BEVEL;
break;
case "miter":
default:
type = JointType.DEFAULT;
break;
}
if (polyline != null) {
polyline.setJointType(type);
}
}
}

View File

@@ -0,0 +1,92 @@
package com.rnmaps.maps;
import android.content.Context;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.google.android.gms.maps.model.ButtCap;
import com.google.android.gms.maps.model.Cap;
import com.google.android.gms.maps.model.RoundCap;
import com.google.android.gms.maps.model.SquareCap;
import java.util.Map;
public class MapPolylineManager extends ViewGroupManager<MapPolyline> {
private final DisplayMetrics metrics;
public MapPolylineManager(ReactApplicationContext reactContext) {
super();
metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapPolyline";
}
@Override
public MapPolyline createViewInstance(ThemedReactContext context) {
return new MapPolyline(context);
}
@ReactProp(name = "coordinates")
public void setCoordinate(MapPolyline view, ReadableArray coordinates) {
view.setCoordinates(coordinates);
}
@ReactProp(name = "strokeWidth", defaultFloat = 1f)
public void setStrokeWidth(MapPolyline view, float widthInPoints) {
float widthInScreenPx = metrics.density * widthInPoints; // done for parity with iOS
view.setWidth(widthInScreenPx);
}
@ReactProp(name = "strokeColor", defaultInt = Color.RED, customType = "Color")
public void setStrokeColor(MapPolyline view, int color) {
view.setColor(color);
}
@ReactProp(name = "tappable", defaultBoolean = false)
public void setTappable(MapPolyline view, boolean tapabble) {
view.setTappable(tapabble);
}
@ReactProp(name = "geodesic", defaultBoolean = false)
public void setGeodesic(MapPolyline view, boolean geodesic) {
view.setGeodesic(geodesic);
}
@ReactProp(name = "zIndex", defaultFloat = 1.0f)
public void setZIndex(MapPolyline view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "lineCap")
public void setlineCap(MapPolyline view, String lineCap) {
view.setLineCap(lineCap);
}
@ReactProp(name = "lineDashPattern")
public void setLineDashPattern(MapPolyline view, ReadableArray patternValues) {
view.setLineDashPattern(patternValues);
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onPress", MapBuilder.of("registrationName", "onPress")
);
}
}

View File

@@ -0,0 +1,494 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.Log;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Future;
import java.util.List;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Data;
import androidx.work.Constraints;
import androidx.work.NetworkType;
import androidx.work.ExistingWorkPolicy;
import androidx.work.Operation;
import androidx.work.WorkInfo;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileProvider;
import com.google.android.gms.maps.model.UrlTileProvider;
import java.lang.System;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class MapTileProvider implements TileProvider {
class AIRMapUrlTileProvider extends UrlTileProvider {
private String urlTemplate;
public AIRMapUrlTileProvider(int width, int height, String urlTemplate) {
super(width, height);
this.urlTemplate = urlTemplate;
}
@Override
public URL getTileUrl(int x, int y, int zoom) {
if (MapTileProvider.this.flipY) {
y = (1 << zoom) - y - 1;
}
String s = this.urlTemplate
.replace("{x}", Integer.toString(x))
.replace("{y}", Integer.toString(y))
.replace("{z}", Integer.toString(zoom));
URL url;
if(MapTileProvider.this.maximumZ > 0 && zoom > MapTileProvider.this.maximumZ) {
return null;
}
if(MapTileProvider.this.minimumZ > 0 && zoom < MapTileProvider.this.minimumZ) {
return null;
}
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
public void setUrlTemplate(String urlTemplate) {
this.urlTemplate = urlTemplate;
}
}
protected static final int BUFFER_SIZE = 16 * 1024;
protected static final int TARGET_TILE_SIZE = 512;
protected UrlTileProvider tileProvider;
protected String urlTemplate;
protected int tileSize;
protected boolean doubleTileSize;
protected int maximumZ;
protected int maximumNativeZ;
protected int minimumZ;
protected boolean flipY;
protected String tileCachePath;
protected int tileCacheMaxAge;
protected boolean offlineMode;
protected Context context;
protected boolean customMode;
public MapTileProvider(int tileSizet, boolean doubleTileSize, String urlTemplate,
int maximumZ, int maximumNativeZ, int minimumZ, boolean flipY, String tileCachePath,
int tileCacheMaxAge, boolean offlineMode, Context context, boolean customMode) {
this.tileProvider = new AIRMapUrlTileProvider(tileSizet, tileSizet, urlTemplate);
this.tileSize = tileSizet;
this.doubleTileSize = doubleTileSize;
this.urlTemplate = urlTemplate;
this.maximumZ = maximumZ;
this.maximumNativeZ = maximumNativeZ;
this.minimumZ = minimumZ;
this.flipY = flipY;
this.tileCachePath = tileCachePath;
this.tileCacheMaxAge = tileCacheMaxAge;
this.offlineMode = offlineMode;
this.context = context;
this.customMode = customMode;
}
@Override
public Tile getTile(int x, int y, int zoom) {
if (!this.customMode) return this.tileProvider.getTile(x, y, zoom);
byte[] image = null;
int maximumZ = this.maximumZ > 0 ? this.maximumZ : Integer.MAX_VALUE;
if (this.tileSize == 256 && this.doubleTileSize && zoom + 1 <= this.maximumNativeZ && zoom + 1 <= maximumZ) {
Log.d("urlTile", "pullTilesFromHigherZoom");
image = pullTilesFromHigherZoom(x, y, zoom);
}
if (zoom > this.maximumNativeZ) {
Log.d("urlTile", "scaleLowerZoomTile");
image = scaleLowerZoomTile(x, y, zoom, this.maximumNativeZ);
}
if (image == null && zoom <= maximumZ) {
Log.d("urlTile", "getTileImage");
image = getTileImage(x, y, zoom);
}
if (image == null && this.tileCachePath != null && this.offlineMode) {
Log.d("urlTile", "findLowerZoomTileForScaling");
int zoomLevelToStart = (zoom > this.maximumNativeZ) ? this.maximumNativeZ - 1 : zoom - 1;
int minimumZoomToSearch = Math.max(this.minimumZ, zoom - 3);
for (int tryZoom = zoomLevelToStart; tryZoom >= minimumZoomToSearch; tryZoom--) {
image = scaleLowerZoomTile(x, y, zoom, tryZoom);
if (image != null) {
break;
}
}
}
return image == null ? null : new Tile(this.tileSize, this.tileSize, image);
}
byte[] getTileImage(int x, int y, int zoom) {
byte[] image = null;
if (this.tileCachePath != null) {
image = readTileImage(x, y, zoom);
if (image != null) {
Log.d("urlTile", "tile cache HIT for " + zoom +
"/" + x + "/" + y);
} else {
Log.d("urlTile", "tile cache MISS for " + zoom +
"/" + x + "/" + y);
}
if (image != null && !this.offlineMode) {
checkForRefresh(x, y, zoom);
}
}
if (image == null && !this.offlineMode && this.tileCachePath != null) {
String fileName = getTileFilename(x, y, zoom);
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
OneTimeWorkRequest tileRefreshWorkRequest = new OneTimeWorkRequest.Builder(MapTileWorker.class)
.setConstraints(constraints)
.addTag(fileName)
.setInputData(
new Data.Builder()
.putString("url", getTileUrl(x, y, zoom).toString())
.putString("filename", fileName)
.putInt("maxAge", -1)
.build()
)
.build();
WorkManager workManager = WorkManager.getInstance(this.context.getApplicationContext());
Operation fetchOperation = workManager
.enqueueUniqueWork(fileName, ExistingWorkPolicy.KEEP, tileRefreshWorkRequest);
Future<Operation.State.SUCCESS> operationFuture = fetchOperation.getResult();
try {
operationFuture.get(1L, TimeUnit.SECONDS);
Thread.sleep(500);
Future<List<WorkInfo>> fetchFuture = workManager.getWorkInfosByTag(fileName);
List<WorkInfo> workInfo = fetchFuture.get(1L, TimeUnit.SECONDS);
Log.d("urlTile: ", workInfo.get(0).toString());
if (this.tileCachePath != null) {
image = readTileImage(x, y, zoom);
if (image != null) {
Log.d("urlTile","tile cache fetch HIT for " + zoom +
"/" + x + "/" + y);
} else {
Log.d("urlTile","tile cache fetch MISS for " + zoom +
"/" + x + "/" + y);
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (image == null && !this.offlineMode) {
Log.d("urlTile", "Normal fetch");
image = fetchTile(x, y, zoom);
if (image == null) {
Log.d("urlTile", "tile fetch TIMEOUT / FAIL for " + zoom +
"/" + x + "/" + y);
}
}
return image;
}
byte[] pullTilesFromHigherZoom(int x, int y, int zoom) {
byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
Paint paint = new Paint();
x = x * 2;
y = y * 2;
byte[] leftTop = getTileImage(x, y, zoom + 1);
byte[] leftBottom = getTileImage(x, y + 1, zoom + 1);
byte[] rightTop = getTileImage(x + 1, y, zoom + 1);
byte[] rightBottom = getTileImage(x + 1, y + 1, zoom + 1);
if (leftTop == null || leftBottom == null || rightTop == null || rightBottom == null) {
return null;
}
Bitmap bitmap;
bitmap = BitmapFactory.decodeByteArray(leftTop, 0, leftTop.length);
canvas.drawBitmap(bitmap, 0, 0, paint);
bitmap.recycle();
bitmap = BitmapFactory.decodeByteArray(leftBottom, 0, leftBottom.length);
canvas.drawBitmap(bitmap, 0, 256, paint);
bitmap.recycle();
bitmap = BitmapFactory.decodeByteArray(rightTop, 0, rightTop.length);
canvas.drawBitmap(bitmap, 256, 0, paint);
bitmap.recycle();
bitmap = BitmapFactory.decodeByteArray(rightBottom, 0, rightBottom.length);
canvas.drawBitmap(bitmap, 256, 256, paint);
bitmap.recycle();
data = bitmapToByteArray(image);
image.recycle();
return data;
}
Bitmap getNewBitmap() {
Bitmap image = Bitmap.createBitmap(TARGET_TILE_SIZE, TARGET_TILE_SIZE, Bitmap.Config.ARGB_8888);
image.eraseColor(Color.TRANSPARENT);
return image;
}
byte[] bitmapToByteArray(Bitmap bm) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] data = bos.toByteArray();
try {
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
byte[] scaleLowerZoomTile(int x, int y, int zoom, int maximumZoom) {
int overZoomLevel = zoom - maximumZoom;
int zoomFactor = 1 << overZoomLevel;
int xParent = x >> overZoomLevel;
int yParent = y >> overZoomLevel;
int zoomParent = zoom - overZoomLevel;
int xOffset = x % zoomFactor;
int yOffset = y % zoomFactor;
byte[] data;
Bitmap image = getNewBitmap();
Canvas canvas = new Canvas(image);
Paint paint = new Paint();
data = getTileImage(xParent, yParent, zoomParent);
if (data == null) return null;
Bitmap sourceImage;
sourceImage = BitmapFactory.decodeByteArray(data, 0, data.length);
int subTileSize = this.tileSize / zoomFactor;
Rect sourceRect = new Rect(xOffset * subTileSize, yOffset * subTileSize, xOffset * subTileSize + subTileSize , yOffset * subTileSize + subTileSize);
Rect targetRect = new Rect(0,0,TARGET_TILE_SIZE, TARGET_TILE_SIZE);
canvas.drawBitmap(sourceImage, sourceRect, targetRect, paint);
sourceImage.recycle();
data = bitmapToByteArray(image);
image.recycle();
return data;
}
void checkForRefresh(int x, int y, int zoom) {
String fileName = getTileFilename(x, y, zoom);
File file = new File(fileName);
long lastModified = file.lastModified();
long now = System.currentTimeMillis();
if ((now - lastModified) / 1000 > this.tileCacheMaxAge) {
Log.d("urlTile", "Refreshing");
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
OneTimeWorkRequest tileRefreshWorkRequest = new OneTimeWorkRequest.Builder(MapTileWorker.class)
.setConstraints(constraints)
.addTag(fileName)
.setInputData(
new Data.Builder()
.putString("url", getTileUrl(x, y, zoom).toString())
.putString("filename", fileName)
.putInt("maxAge", this.tileCacheMaxAge)
.build()
)
.build();
WorkManager.getInstance(this.context.getApplicationContext())
.enqueueUniqueWork(fileName, ExistingWorkPolicy.KEEP, tileRefreshWorkRequest);
}
}
byte[] fetchTile(int x, int y, int zoom) {
URL url = getTileUrl(x, y, zoom);
ByteArrayOutputStream buffer = null;
InputStream in = null;
try {
URLConnection conn = url.openConnection();
in = conn.getInputStream();
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
byte[] readTileImage(int x, int y, int zoom) {
InputStream in = null;
ByteArrayOutputStream buffer = null;
String fileName = getTileFilename(x, y, zoom);
if (fileName == null) {
return null;
}
File file = new File(fileName);
try {
in = new FileInputStream(file);
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
if (this.tileCacheMaxAge == 0) {
file.setLastModified(System.currentTimeMillis());
}
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
boolean writeTileImage(byte[] image, int x, int y, int zoom) {
OutputStream out = null;
String fileName = getTileFilename(x, y, zoom);
if (fileName == null) {
return false;
}
try {
File file = new File(fileName);
file.getParentFile().mkdirs();
out = new FileOutputStream(file);
out.write(image);
return true;
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return false;
} finally {
if (out != null) try { out.close(); } catch (Exception ignored) {}
}
}
String getTileFilename(int x, int y, int zoom) {
if (this.tileCachePath == null) {
return null;
}
return this.tileCachePath + '/' + zoom +
"/" + x + "/" + y;
}
protected URL getTileUrl(int x, int y, int zoom) {
return this.tileProvider.getTileUrl(x, y, zoom);
}
public void setUrlTemplate(String urlTemplate) {
if (this.urlTemplate != urlTemplate) {
this.tileProvider = new AIRMapUrlTileProvider(tileSize, tileSize, urlTemplate);
}
this.urlTemplate = urlTemplate;
}
public void setTileSize(int tileSize) {
if (this.tileSize != tileSize) {
this.tileProvider = new AIRMapUrlTileProvider(tileSize, tileSize, urlTemplate);
}
this.tileSize = tileSize;
}
public void setDoubleTileSize(boolean doubleTileSize) {
this.doubleTileSize = doubleTileSize;
}
public void setMaximumZ(int maximumZ) {
this.maximumZ = maximumZ;
}
public void setMaximumNativeZ(int maximumNativeZ) {
this.maximumNativeZ = maximumNativeZ;
}
public void setMinimumZ(int minimumZ) {
this.minimumZ = minimumZ;
}
public void setFlipY(boolean flipY) {
this.flipY = flipY;
}
public void setTileCachePath(String tileCachePath) {
this.tileCachePath = tileCachePath;
}
public void setTileCacheMaxAge(int tileCacheMaxAge) {
this.tileCacheMaxAge = tileCacheMaxAge;
}
public void setOfflineMode(boolean offlineMode) {
this.offlineMode = offlineMode;
}
public void setCustomMode() {
}
}

View File

@@ -0,0 +1,115 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class MapTileWorker extends Worker {
private static final int BUFFER_SIZE = 16 * 1024;
public MapTileWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
byte[] image;
URL url;
String fileName = getInputData().getString("filename");
try {
int tileCacheMaxAge = getInputData().getInt("maxAge", 0);
if (tileCacheMaxAge >= 0) {
File file = new File(fileName);
long lastModified = file.lastModified();
long now = System.currentTimeMillis();
if ((now - lastModified) / 1000 < tileCacheMaxAge) return Result.failure();
}
} catch (Error e) {
return Result.failure();
}
try {
url = new URL(getInputData().getString("url"));
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
image = fetchTile(url);
if (image != null) {
boolean success = writeTileImage(image, fileName);
if (!success) {
return Result.failure();
}
} else {
return Result.retry();
}
// Indicate whether the work finished successfully with the Result
Log.d("urlTile", "Worker fetched " + fileName);
return Result.success();
}
private byte[] fetchTile(URL url) {
ByteArrayOutputStream buffer = null;
InputStream in = null;
try {
in = url.openStream();
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[BUFFER_SIZE];
while ((nRead = in.read(data, 0, BUFFER_SIZE)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return null;
} finally {
if (in != null) try { in.close(); } catch (Exception ignored) {}
if (buffer != null) try { buffer.close(); } catch (Exception ignored) {}
}
}
private boolean writeTileImage(byte[] image, String fileName) {
OutputStream out = null;
if (fileName == null) {
return false;
}
try {
File file = new File(fileName);
file.getParentFile().mkdirs();
out = new FileOutputStream(file);
out.write(image);
return true;
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
return false;
} finally {
if (out != null) try { out.close(); } catch (Exception ignored) {}
}
}
}

View File

@@ -0,0 +1,67 @@
package com.rnmaps.maps;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
import com.facebook.react.fabric.FabricUIManager;
import com.facebook.react.fabric.interop.UIBlockViewResolver;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.UIManagerModule;
import java.util.function.Function;
@UnstableReactNativeAPI
public class MapUIBlock implements UIBlockInterface {
private int tag;
private Promise promise;
private ReactApplicationContext context;
private Function<MapView, Void> mapOperation;
public MapUIBlock(int tag, Promise promise, ReactApplicationContext context, Function<MapView, Void> mapOperation) {
this.tag = tag;
this.promise = promise;
this.context = context;
this.mapOperation = mapOperation;
}
@Override
public void execute(NativeViewHierarchyManager nvhm) {
executeImpl(nvhm, null);
}
@Override
public void execute(UIBlockViewResolver uiBlockViewResolver) {
executeImpl(null, uiBlockViewResolver);
}
private void executeImpl(NativeViewHierarchyManager nvhm, UIBlockViewResolver uiBlockViewResolver) {
MapView view = uiBlockViewResolver != null ? (MapView) uiBlockViewResolver.resolveView(tag) : (MapView) nvhm.resolveView(tag);
if (view == null) {
promise.reject("AirMapView not found");
return;
}
if (view.map == null) {
promise.reject("AirMapView.map is not valid");
return;
}
mapOperation.apply(view);
}
public void addToUIManager() {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
UIManager uiManager = UIManagerHelper.getUIManager(context, UIManagerType.FABRIC);
((FabricUIManager) uiManager).addUIBlock(this);
} else {
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(this);
}
}
}
@UnstableReactNativeAPI
interface UIBlockInterface extends UIBlock, com.facebook.react.fabric.interop.UIBlock {}

View File

@@ -0,0 +1,207 @@
package com.rnmaps.maps;
import android.util.Log;
import android.content.Context;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import java.net.MalformedURLException;
import java.net.URL;
public class MapUrlTile extends MapFeature {
protected TileOverlayOptions tileOverlayOptions;
protected TileOverlay tileOverlay;
protected MapTileProvider tileProvider;
protected String urlTemplate;
protected float zIndex;
protected int maximumZ;
protected int maximumNativeZ = 100;
protected int minimumZ;
protected boolean flipY = false;
protected int tileSize = 256;
protected boolean doubleTileSize = false;
protected String tileCachePath;
protected int tileCacheMaxAge;
protected boolean offlineMode = false;
protected float opacity = 1;
protected Context context;
protected boolean customTileProviderNeeded = false;
public MapUrlTile(Context context) {
super(context);
this.context = context;
}
public void setUrlTemplate(String urlTemplate) {
this.urlTemplate = urlTemplate;
if (tileProvider != null) {
tileProvider.setUrlTemplate(urlTemplate);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setZIndex(float zIndex) {
this.zIndex = zIndex;
if (tileOverlay != null) {
tileOverlay.setZIndex(zIndex);
}
}
public void setMaximumZ(int maximumZ) {
this.maximumZ = maximumZ;
if (tileProvider != null) {
tileProvider.setMaximumZ(maximumZ);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setMaximumNativeZ(int maximumNativeZ) {
this.maximumNativeZ = maximumNativeZ;
if (tileProvider != null) {
tileProvider.setMaximumNativeZ(maximumNativeZ);
}
setCustomTileProviderMode();
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setMinimumZ(int minimumZ) {
this.minimumZ = minimumZ;
if (tileProvider != null) {
tileProvider.setMinimumZ(minimumZ);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setFlipY(boolean flipY) {
this.flipY = flipY;
if (tileProvider != null) {
tileProvider.setFlipY(flipY);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setDoubleTileSize(boolean doubleTileSize) {
this.doubleTileSize = doubleTileSize;
if (tileProvider != null) {
tileProvider.setDoubleTileSize(doubleTileSize);
}
setCustomTileProviderMode();
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setTileSize(int tileSize) {
this.tileSize = tileSize;
if (tileProvider != null) {
tileProvider.setTileSize(tileSize);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setTileCachePath(String tileCachePath) {
if (tileCachePath == null || tileCachePath.isEmpty()) return;
try {
URL url = new URL(tileCachePath);
this.tileCachePath = url.getPath();
} catch (MalformedURLException e) {
this.tileCachePath = tileCachePath;
} catch (Exception e) {
return;
}
if (tileProvider != null) {
tileProvider.setTileCachePath(tileCachePath);
}
setCustomTileProviderMode();
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setTileCacheMaxAge(int tileCacheMaxAge) {
this.tileCacheMaxAge = tileCacheMaxAge;
if (tileProvider != null) {
tileProvider.setTileCacheMaxAge(tileCacheMaxAge);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setOfflineMode(boolean offlineMode) {
this.offlineMode = offlineMode;
if (tileProvider != null) {
tileProvider.setOfflineMode(offlineMode);
}
if (tileOverlay != null) {
tileOverlay.clearTileCache();
}
}
public void setOpacity(float opacity) {
this.opacity = opacity;
if (tileOverlay != null) {
tileOverlay.setTransparency(1 - opacity);
}
}
public TileOverlayOptions getTileOverlayOptions() {
if (tileOverlayOptions == null) {
tileOverlayOptions = createTileOverlayOptions();
}
return tileOverlayOptions;
}
protected void setCustomTileProviderMode() {
Log.d("urlTile ", "creating new mode TileProvider");
this.customTileProviderNeeded = true;
if (tileProvider != null) {
tileProvider.setCustomMode();
}
}
protected TileOverlayOptions createTileOverlayOptions() {
Log.d("urlTile ", "creating TileProvider");
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
options.transparency(1 - this.opacity);
this.tileProvider = new MapTileProvider((int)this.tileSize, this.doubleTileSize, this.urlTemplate,
this.maximumZ, this.maximumNativeZ, this.minimumZ, this.flipY, this.tileCachePath,
this.tileCacheMaxAge, this.offlineMode, this.context, this.customTileProviderNeeded);
options.tileProvider(this.tileProvider);
return options;
}
@Override
public Object getFeature() {
return tileOverlay;
}
@Override
public void addToMap(Object map) {
this.tileOverlay = ((GoogleMap) map).addTileOverlay(getTileOverlayOptions());
}
@Override
public void removeFromMap(Object map) {
tileOverlay.remove();
}
}

View File

@@ -0,0 +1,87 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
public class MapUrlTileManager extends ViewGroupManager<MapUrlTile> {
public MapUrlTileManager(ReactApplicationContext reactContext) {
super();
}
@Override
public String getName() {
return "AIRMapUrlTile";
}
@Override
public MapUrlTile createViewInstance(ThemedReactContext context) {
return new MapUrlTile(context);
}
@ReactProp(name = "urlTemplate")
public void setUrlTemplate(MapUrlTile view, String urlTemplate) {
view.setUrlTemplate(urlTemplate);
}
@ReactProp(name = "zIndex", defaultFloat = -1.0f)
public void setZIndex(MapUrlTile view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "minimumZ", defaultInt = 0)
public void setMinimumZ(MapUrlTile view, int minimumZ) {
view.setMinimumZ(minimumZ);
}
@ReactProp(name = "maximumZ", defaultInt = 100)
public void setMaximumZ(MapUrlTile view, int maximumZ) {
view.setMaximumZ(maximumZ);
}
@ReactProp(name = "maximumNativeZ", defaultInt = 100)
public void setMaximumNativeZ(MapUrlTile view, int maximumNativeZ) {
view.setMaximumNativeZ(maximumNativeZ);
}
@ReactProp(name = "flipY", defaultBoolean = false)
public void setFlipY(MapUrlTile view, boolean flipY) {
view.setFlipY(flipY);
}
@ReactProp(name = "tileSize", defaultInt = 256)
public void setTileSize(MapUrlTile view, int tileSize) {
view.setTileSize(tileSize);
}
@ReactProp(name = "doubleTileSize", defaultBoolean = false)
public void setDoubleTileSize(MapUrlTile view, boolean doubleTileSize) {
view.setDoubleTileSize(doubleTileSize);
}
@ReactProp(name = "tileCachePath")
public void setTileCachePath(MapUrlTile view, String tileCachePath) {
view.setTileCachePath(tileCachePath);
}
@ReactProp(name = "tileCacheMaxAge", defaultInt = 0)
public void setTileCacheMaxAge(MapUrlTile view, int tileCacheMaxAge) {
view.setTileCacheMaxAge(tileCacheMaxAge);
}
@ReactProp(name = "offlineMode", defaultBoolean = false)
public void setOfflineMode(MapUrlTile view, boolean offlineMode) {
view.setOfflineMode(offlineMode);
}
@ReactProp(name = "opacity", defaultFloat = 1.0f)
public void setOpacity(MapUrlTile view, float opacity) {
view.setOpacity(opacity);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
package com.rnmaps.maps;
import android.content.Context;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.google.android.gms.maps.model.UrlTileProvider;
import java.net.MalformedURLException;
import java.net.URL;
public class MapWMSTile extends MapUrlTile {
private static final double[] mapBound = {-20037508.34789244, 20037508.34789244};
private static final double FULL = 20037508.34789244 * 2;
class AIRMapGSUrlTileProvider extends MapTileProvider {
class AIRMapWMSTileProvider extends UrlTileProvider {
private String urlTemplate;
private final int tileSize;
public AIRMapWMSTileProvider(int width, int height, String urlTemplate) {
super(width, height);
this.urlTemplate = urlTemplate;
this.tileSize = width;
}
@Override
public URL getTileUrl(int x, int y, int zoom) {
if(MapWMSTile.this.maximumZ > 0 && zoom > maximumZ) {
return null;
}
if(MapWMSTile.this.minimumZ > 0 && zoom < minimumZ) {
return null;
}
double[] bb = getBoundingBox(x, y, zoom);
String s = this.urlTemplate
.replace("{minX}", Double.toString(bb[0]))
.replace("{minY}", Double.toString(bb[1]))
.replace("{maxX}", Double.toString(bb[2]))
.replace("{maxY}", Double.toString(bb[3]))
.replace("{width}", Integer.toString(this.tileSize))
.replace("{height}", Integer.toString(this.tileSize));
URL url = null;
try {
url = new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
return url;
}
private double[] getBoundingBox(int x, int y, int zoom) {
double tile = FULL / Math.pow(2, zoom);
return new double[]{
mapBound[0] + x * tile,
mapBound[1] - (y + 1) * tile,
mapBound[0] + (x + 1) * tile,
mapBound[1] - y * tile
};
}
public void setUrlTemplate(String urlTemplate) {
this.urlTemplate = urlTemplate;
}
}
public AIRMapGSUrlTileProvider(int tileSizet, String urlTemplate,
int maximumZ, int maximumNativeZ, int minimumZ, String tileCachePath,
int tileCacheMaxAge, boolean offlineMode, Context context, boolean customMode) {
super(tileSizet, false, urlTemplate, maximumZ, maximumNativeZ, minimumZ, false,
tileCachePath, tileCacheMaxAge, offlineMode, context, customMode);
this.tileProvider = new AIRMapWMSTileProvider(tileSizet, tileSizet, urlTemplate);
}
}
public MapWMSTile(Context context) {
super(context);
}
@Override
protected TileOverlayOptions createTileOverlayOptions() {
TileOverlayOptions options = new TileOverlayOptions();
options.zIndex(zIndex);
options.transparency(1 - this.opacity);
AIRMapGSUrlTileProvider tileProvider = new AIRMapGSUrlTileProvider(this.tileSize, this.urlTemplate,
this.maximumZ, this.maximumNativeZ, this.minimumZ, this.tileCachePath,
this.tileCacheMaxAge, this.offlineMode, this.context, this.customTileProviderNeeded);
options.tileProvider(tileProvider);
return options;
}
}

View File

@@ -0,0 +1,81 @@
package com.rnmaps.maps;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
public class MapWMSTileManager extends ViewGroupManager<MapWMSTile> {
public MapWMSTileManager(ReactApplicationContext reactContext) {
super();
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay()
.getRealMetrics(metrics);
}
@Override
public String getName() {
return "AIRMapWMSTile";
}
@Override
public MapWMSTile createViewInstance(ThemedReactContext context) {
return new MapWMSTile(context);
}
@ReactProp(name = "urlTemplate")
public void setUrlTemplate(MapWMSTile view, String urlTemplate) {
view.setUrlTemplate(urlTemplate);
}
@ReactProp(name = "zIndex", defaultFloat = -1.0f)
public void setZIndex(MapWMSTile view, float zIndex) {
view.setZIndex(zIndex);
}
@ReactProp(name = "minimumZ", defaultInt = 0)
public void setMinimumZ(MapWMSTile view, int minimumZ) {
view.setMinimumZ(minimumZ);
}
@ReactProp(name = "maximumZ", defaultInt = 100)
public void setMaximumZ(MapWMSTile view, int maximumZ) {
view.setMaximumZ(maximumZ);
}
@ReactProp(name = "maximumNativeZ", defaultInt= 100)
public void setMaximumNativeZ(MapWMSTile view, int maximumNativeZ) {
view.setMaximumNativeZ(maximumNativeZ);
}
@ReactProp(name = "tileSize", defaultInt = 256)
public void setTileSize(MapWMSTile view, int tileSize) {
view.setTileSize(tileSize);
}
@ReactProp(name = "tileCachePath")
public void setTileCachePath(MapWMSTile view, String tileCachePath) {
view.setTileCachePath(tileCachePath);
}
@ReactProp(name = "tileCacheMaxAge", defaultInt = 0)
public void setTileCacheMaxAge(MapWMSTile view, int tileCacheMaxAge) {
view.setTileCacheMaxAge(tileCacheMaxAge);
}
@ReactProp(name = "offlineMode", defaultBoolean = false)
public void setOfflineMode(MapWMSTile view, boolean offlineMode) {
view.setOfflineMode(offlineMode);
}
@ReactProp(name = "opacity", defaultFloat = 1.0f)
public void setOpacity(MapWMSTile view, float opacity) {
view.setOpacity(opacity);
}
}

View File

@@ -0,0 +1,102 @@
package com.rnmaps.maps;
import androidx.annotation.NonNull;
import com.facebook.fbreact.specs.NativeAirMapsModuleSpec;
import com.facebook.react.BaseReactPackage;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import com.rnmaps.fabric.CalloutManager;
import com.rnmaps.fabric.CircleManager;
import com.rnmaps.fabric.MapViewManager;
import com.rnmaps.fabric.MarkerManager;
import com.rnmaps.fabric.NativeAirMapsModule;
import com.rnmaps.fabric.OverlayManager;
import com.rnmaps.fabric.PolygonManager;
import com.rnmaps.fabric.PolylineManager;
import com.rnmaps.fabric.UrlTileManager;
import com.rnmaps.fabric.WMSTileManager;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MapsPackage extends BaseReactPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return List.of(new MapViewManager(reactContext),
new MarkerManager(reactContext),
new CalloutManager(reactContext),
new PolygonManager(reactContext),
new PolylineManager(reactContext),
new CircleManager(reactContext),
new OverlayManager(reactContext),
new UrlTileManager(reactContext),
new WMSTileManager(reactContext),
new MapGradientPolylineManager(reactContext),
new MapLocalTileManager(reactContext),
new MapHeatmapManager());
}
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (OverlayManager.REACT_CLASS.equals(name)) {
return new OverlayManager(reactContext);
}
if (CircleManager.REACT_CLASS.equals(name)) {
return new CircleManager(reactContext);
}
if (PolylineManager.REACT_CLASS.equals(name)) {
return new PolylineManager(reactContext);
}
if (PolygonManager.REACT_CLASS.equals(name)) {
return new PolygonManager(reactContext);
}
if (CalloutManager.REACT_CLASS.equals(name)) {
return new CalloutManager(reactContext);
}
if (MarkerManager.REACT_CLASS.equals(name)) {
return new MarkerManager(reactContext);
}
if (MapViewManager.REACT_CLASS.equals(name)) {
return new MapViewManager(reactContext);
}
if (UrlTileManager.REACT_CLASS.equals(name)) {
return new UrlTileManager(reactContext);
}
if (WMSTileManager.REACT_CLASS.equals(name)) {
return new WMSTileManager(reactContext);
}
if (NativeAirMapsModuleSpec.NAME.equals(name)) {
return new NativeAirMapsModule(reactContext);
} else {
return null;
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@NonNull
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
Map<String, ReactModuleInfo> map = new HashMap<>();
map.put(NativeAirMapsModuleSpec.NAME, new ReactModuleInfo(
NativeAirMapsModuleSpec.NAME, // name
NativeAirMapsModuleSpec.NAME, // className
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCXXModule
true // isTurboModule
));
return map;
}
};
}
}

Some files were not shown because too many files have changed in this diff Show More