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,18 @@
//
// AIRDummyView.h
// AirMapsExplorer
//
// Created by Gil Birman on 10/4/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
@interface AIRDummyView : UIView
@property (nonatomic, weak) UIView *view;
- (instancetype)initWithView:(UIView*)view;
@end
#endif

View File

@@ -0,0 +1,23 @@
//
// AIRDummyView.m
// AirMapsExplorer
//
// Created by Gil Birman on 10/4/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import "AIRDummyView.h"
@implementation AIRDummyView
- (instancetype)initWithView:(UIView*)view
{
if ((self = [super init])) {
self.view = view;
}
return self;
}
@end
#endif

View File

@@ -0,0 +1,29 @@
//
// AIRGMSMarker.h
// AirMaps
//
// Created by Gil Birman on 9/5/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <GoogleMaps/GoogleMaps.h>
#import <React/UIView+React.h>
@class AIRGoogleMapMarker;
@interface AIRGMSMarker : GMSMarker
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, weak) AIRGoogleMapMarker *fakeMarker;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, copy) RCTDirectEventBlock onSelect;
@property (nonatomic, copy) RCTDirectEventBlock onDeselect;
@end
@protocol AIRGMSMarkerDelegate <NSObject>
@required
-(void)didTapMarker;
@end
#endif

View File

@@ -0,0 +1,16 @@
//
// AIRGMSMarker.m
// AirMaps
//
// Created by Gil Birman on 9/5/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGMSMarker.h"
@implementation AIRGMSMarker
@end
#endif

View File

@@ -0,0 +1,20 @@
//
// AIRGMSPolygon.h
// AirMaps
//
// Created by Gerardo Pacheco 02/05/2017.
//
#ifdef HAVE_GOOGLE_MAPS
#import <GoogleMaps/GoogleMaps.h>
#import <React/UIView+React.h>
@class AIRGoogleMapPolygon;
@interface AIRGMSPolygon : GMSPolygon
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@end
#endif

View File

@@ -0,0 +1,16 @@
//
// AIRGMSPolygon.m
// AirMaps
//
// Created by Gerardo Pacheco 02/05/2017.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGMSPolygon.h"
@implementation AIRGMSPolygon
@end
#endif

View File

@@ -0,0 +1,20 @@
//
// AIRGMSPolyline.h
// AirMaps
//
// Created by Guilherme Pontes 04/05/2017.
//
#ifdef HAVE_GOOGLE_MAPS
#import <GoogleMaps/GoogleMaps.h>
#import <React/UIView+React.h>
@class AIRGoogleMapPolyline;
@interface AIRGMSPolyline : GMSPolyline
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@end
#endif

View File

@@ -0,0 +1,15 @@
//
// AIRGMSPolyline.m
// AirMaps
//
// Created by Guilherme Pontes 04/05/2017.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGMSPolyline.h"
@implementation AIRGMSPolyline
@end
#endif

View File

@@ -0,0 +1,95 @@
//
// AIRGoogleMap.h
// AirMaps
//
// Created by Gil Birman on 9/1/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
#import <React/RCTComponent.h>
#import <React/RCTBridge.h>
#import <GoogleMaps/GoogleMaps.h>
#import <MapKit/MapKit.h>
#import "AIRGMSMarker.h"
#import "AIRGoogleMapCoordinate.h"
@interface AIRGoogleMap : GMSMapView
// TODO: don't use MK region?
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, assign) MKCoordinateRegion initialRegion;
@property (nonatomic, assign) MKCoordinateRegion region;
@property (nonatomic, assign) GMSCameraPosition *cameraProp; // Because the base class already has a "camera" prop.
@property (nonatomic, strong) GMSCameraPosition *initialCamera;
@property (nonatomic, assign) NSString *customMapStyleString;
@property (nonatomic, assign) UIEdgeInsets mapPadding;
@property (nonatomic, assign) NSString *paddingAdjustmentBehaviorString;
@property (nonatomic, copy) RCTDirectEventBlock onMapReady;
@property (nonatomic, copy) RCTDirectEventBlock onMapLoaded;
@property (nonatomic, copy) RCTDirectEventBlock onKmlReady;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, copy) RCTBubblingEventBlock onLongPress;
@property (nonatomic, copy) RCTDirectEventBlock onPanDrag;
@property (nonatomic, copy) RCTDirectEventBlock onUserLocationChange;
@property (nonatomic, copy) RCTDirectEventBlock onMarkerPress;
@property (nonatomic, copy) RCTDirectEventBlock onMarkerSelect;
@property (nonatomic, copy) RCTDirectEventBlock onMarkerDeselect;
@property (nonatomic, copy) RCTDirectEventBlock onPoiClick;
@property (nonatomic, copy) RCTDirectEventBlock onRegionChangeStart;
@property (nonatomic, copy) RCTDirectEventBlock onRegionChange;
@property (nonatomic, copy) RCTDirectEventBlock onRegionChangeComplete;
@property (nonatomic, copy) RCTDirectEventBlock onIndoorLevelActivated;
@property (nonatomic, copy) RCTDirectEventBlock onIndoorBuildingFocused;
@property (nonatomic, strong) NSMutableArray *markers;
@property (nonatomic, strong) NSMutableArray *polygons;
@property (nonatomic, strong) NSMutableArray *polylines;
@property (nonatomic, strong) NSMutableArray *circles;
@property (nonatomic, strong) NSMutableArray *heatmaps;
@property (nonatomic, strong) NSMutableArray *tiles;
@property (nonatomic, strong) NSMutableArray *overlays;
@property (nonatomic, assign) BOOL showsBuildings;
@property (nonatomic, assign) BOOL showsTraffic;
@property (nonatomic, assign) BOOL showsCompass;
@property (nonatomic, assign) BOOL scrollEnabled;
@property (nonatomic, assign) BOOL zoomEnabled;
@property (nonatomic, assign) BOOL rotateEnabled;
@property (nonatomic, assign) BOOL scrollDuringRotateOrZoomEnabled;
@property (nonatomic, assign) BOOL pitchEnabled;
@property (nonatomic, assign) BOOL zoomTapEnabled;
@property (nonatomic, assign) BOOL showsUserLocation;
@property (nonatomic, assign) BOOL showsMyLocationButton;
@property (nonatomic, assign) BOOL showsIndoors;
@property (nonatomic, assign) BOOL showsIndoorLevelPicker;
@property (nonatomic, assign) NSString *kmlSrc;
- (BOOL) isReady;
- (void)didPrepareMap;
- (void)mapViewDidFinishTileRendering;
- (BOOL)didTapMarker:(GMSMarker *)marker;
- (void)didTapPolyline:(GMSPolyline *)polyline;
- (void)didTapPolygon:(GMSPolygon *)polygon;
- (void)didTapAtCoordinate:(CLLocationCoordinate2D)coordinate;
- (void)didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate;
- (void)willMove:(BOOL)gesture;
- (void)didChangeCameraPosition:(GMSCameraPosition *)position isGesture:(BOOL)isGesture;
- (void)idleAtCameraPosition:(GMSCameraPosition *)position isGesture:(BOOL)isGesture;
- (void)didTapPOIWithPlaceID:(NSString *)placeID name:(NSString *) name location:(CLLocationCoordinate2D) location;
- (NSDictionary *)getMapBoundaries;
+ (MKCoordinateRegion)makeGMSCameraPositionFromMap:(GMSMapView *)map andGMSCameraPosition:(GMSCameraPosition *)position;
+ (GMSCameraPosition*)makeGMSCameraPositionFromMap:(GMSMapView *)map andMKCoordinateRegion:(MKCoordinateRegion)region;
- (NSDictionary*) getMarkersFramesWithOnlyVisible:(BOOL)onlyVisible;
- (instancetype)initWithMapId:(NSString *)mapId initialCamera:(GMSCameraPosition*) camera backgroundColor:(UIColor *) backgroundColor andZoomTapEnabled:(BOOL)zoomTapEnabled;
-(void) fitToSuppliedMarkers:(NSArray*) markers withEdgePadding:(NSDictionary*) edgePadding animated:(BOOL)animated;
-(void) fitToCoordinates:(NSArray<AIRGoogleMapCoordinate *> *) coordinates withEdgePadding:(NSDictionary*) edgePadding animated:(BOOL)animated;
-(void) fitToElementsWithEdgePadding:(nonnull NSDictionary *)edgePadding
animated:(BOOL)animated;
@end
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
//
// AIRGoogleMapCallout.h
// AirMaps
//
// Created by Gil Birman on 9/6/16.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
#import <React/RCTView.h>
@interface AIRGoogleMapCallout : UIView
@property (nonatomic, assign) BOOL tooltip;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, assign) BOOL alphaHitTest;
- (BOOL) isPointInside:(CGPoint)pointInCallout;
@end
#endif

View File

@@ -0,0 +1,39 @@
//
// AIRGoogleMapCallout.m
// AirMaps
//
// Created by Gil Birman on 9/6/16.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapCallout.h"
#import <React/RCTUtils.h>
#import <React/RCTView.h>
#import <React/RCTBridge.h>
@implementation AIRGoogleMapCallout
- (BOOL) isPointInside:(CGPoint)pointInCallout {
if (!self.alphaHitTest)
return TRUE;
CGFloat alpha = [self alphaOfPoint:pointInCallout];
return alpha >= 0.01;
}
- (CGFloat) alphaOfPoint:(CGPoint)point {
unsigned char pixel[4] = {0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
CGContextTranslateCTM(context, -point.x, -point.y);
[self.layer renderInContext:context];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return pixel[3]/255.0;
}
@end
#endif

View File

@@ -0,0 +1,17 @@
//
// AIRGoogleMapCalloutManager.h
// AirMaps
//
// Created by Gil Birman on 9/6/16.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@interface AIRGoogleMapCalloutManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,30 @@
//
// AIRGoogleMapCalloutManager.m
// AirMaps
//
// Created by Gil Birman on 9/6/16.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapCalloutManager.h"
#import "AIRGoogleMapCallout.h"
#import <React/RCTView.h>
@implementation AIRGoogleMapCalloutManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapCallout *callout = [AIRGoogleMapCallout new];
return callout;
}
RCT_EXPORT_VIEW_PROPERTY(tooltip, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(alphaHitTest, BOOL)
@end
#endif

View File

@@ -0,0 +1,18 @@
//
// AIRGoogleMapCalloutSubview.h
// AirMaps
//
// Created by Denis Oblogin on 10/8/18.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
#import <React/RCTView.h>
@interface AIRGoogleMapCalloutSubview : UIView
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@end
#endif

View File

@@ -0,0 +1,19 @@
//
// AIRGoogleMapCalloutSubview.m
// AirMaps
//
// Created by Denis Oblogin on 10/8/18.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapCalloutSubview.h"
#import <React/RCTUtils.h>
#import <React/RCTView.h>
#import <React/RCTBridge.h>
@implementation AIRGoogleMapCalloutSubview
@end
#endif

View File

@@ -0,0 +1,17 @@
//
// AIRGoogleMapCalloutSubviewManager.h
// AirMaps
//
// Created by Denis Oblogin on 10/8/18.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@interface AIRGoogleMapCalloutSubviewManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,28 @@
//
// AIRGoogleMapCalloutSubviewManager.m
// AirMaps
//
// Created by Denis Oblogin on 10/8/18.
//
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapCalloutSubviewManager.h"
#import "AIRGoogleMapCalloutSubview.h"
#import <React/RCTView.h>
@implementation AIRGoogleMapCalloutSubviewManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapCalloutSubview *calloutSubview = [AIRGoogleMapCalloutSubview new];
return calloutSubview;
}
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
@end
#endif

View File

@@ -0,0 +1,23 @@
//
// AIRGoogleMapsCircle.h
//
// Created by Nick Italiano on 10/24/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <GoogleMaps/GoogleMaps.h>
@interface AIRGoogleMapCircle : UIView
@property (nonatomic, strong) GMSCircle *circle;
@property (nonatomic, assign) double radius;
@property (nonatomic, assign) CLLocationCoordinate2D centerCoordinate;
@property (nonatomic, strong) UIColor *strokeColor;
@property (nonatomic, assign) double strokeWidth;
@property (nonatomic, strong) UIColor *fillColor;
@property (nonatomic, assign) int zIndex;
@end
#endif

View File

@@ -0,0 +1,100 @@
//
// AIRGoogleMapsCircle.m
//
// Created by Nick Italiano on 10/24/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
#import "AIRGoogleMapCircle.h"
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTUtils.h>
@implementation AIRGoogleMapCircle
{
BOOL _didMoveToWindow;
}
- (instancetype)init
{
if (self = [super init]) {
_didMoveToWindow = false;
_circle = [[GMSCircle alloc] init];
_circle.fillColor = _fillColor;
_circle.strokeColor = _strokeColor;
}
return self;
}
- (void) prepare
{
if(_didMoveToWindow) return;
_didMoveToWindow = true;
if(_fillColor) {
_circle.fillColor = _fillColor;
}
if(_strokeColor) {
_circle.strokeColor = _strokeColor;
}
if(_strokeWidth) {
_circle.strokeWidth = _strokeWidth;
}
if (_zIndex) {
_circle.zIndex = _zIndex;
}
}
- (void) didMoveToSuperview
{
[super didMoveToSuperview];
[self prepare];
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self prepare];
}
- (void)setRadius:(double)radius
{
_radius = radius;
_circle.radius = radius;
}
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
{
_centerCoordinate = centerCoordinate;
_circle.position = centerCoordinate;
}
-(void)setStrokeColor:(UIColor *)strokeColor
{
_strokeColor = strokeColor;
if(_didMoveToWindow) {
_circle.strokeColor = strokeColor;
}
}
-(void)setStrokeWidth:(double)strokeWidth
{
_strokeWidth = strokeWidth;
if(_didMoveToWindow) {
_circle.strokeWidth = strokeWidth;
}
}
-(void)setFillColor:(UIColor *)fillColor
{
_fillColor = fillColor;
if(_didMoveToWindow) {
_circle.fillColor = fillColor;
}
}
-(void)setZIndex:(int)zIndex
{
_zIndex = zIndex;
_circle.zIndex = zIndex;
}
@end
#endif

View File

@@ -0,0 +1,15 @@
//
// AIRGoogleMapCircleManager.h
//
// Created by Nick Italiano on 10/24/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@interface AIRGoogleMapCircleManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,37 @@
//
// AIRGoogleMapCircleManager.m
//
// Created by Nick Italiano on 10/24/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapCircleManager.h"
#import "AIRGoogleMapCircle.h"
#import <React/RCTBridge.h>
#import <React/UIView+React.h>
@interface AIRGoogleMapCircleManager()
@end
@implementation AIRGoogleMapCircleManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapCircle *circle = [AIRGoogleMapCircle new];
return circle;
}
RCT_EXPORT_VIEW_PROPERTY(radius, double)
RCT_REMAP_VIEW_PROPERTY(center, centerCoordinate, CLLocationCoordinate2D)
RCT_EXPORT_VIEW_PROPERTY(strokeColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, double)
RCT_EXPORT_VIEW_PROPERTY(fillColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(zIndex, int)
@end
#endif

View File

@@ -0,0 +1,13 @@
//
// Created by Leland Richardson on 12/27/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface AIRGoogleMapCoordinate : NSObject
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@end

View File

@@ -0,0 +1,10 @@
//
// Created by Leland Richardson on 12/27/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "AIRGoogleMapCoordinate.h"
@implementation AIRGoogleMapCoordinate
@end

View File

@@ -0,0 +1,17 @@
//
// AIRGoogleMapHeatmap.h
//
// Created by David Cako on 29 April 2018.
//
#import "GMUHeatmapTileLayer.h"
@interface AIRGoogleMapHeatmap : UIView
@property (nonatomic, strong) GMUHeatmapTileLayer *heatmap;
@property (nonatomic, strong) NSMutableArray<GMUWeightedLatLng *> *points;
@property (nonatomic, assign) NSUInteger radius;
@property (nonatomic, assign) float opacity;
@property (nonatomic, assign) GMUGradient *gradient;
@end

View File

@@ -0,0 +1,64 @@
//
// AIRGoogleMapHeatmap.m
//
// Created by David Cako on 29 April 2018.
//
#import <UIKit/UIKit.h>
#import "AIRGoogleMapHeatmap.h"
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTConvert.h>
#import <React/RCTConvert+CoreLocation.h>
@implementation AIRGoogleMapHeatmap
- (instancetype)init
{
if (self = [super init]) {
_heatmap = [[GMUHeatmapTileLayer alloc] init];
}
return self;
}
- (void)setPoints:(NSArray<NSDictionary *> *)points
{
NSMutableArray<GMUWeightedLatLng *> *w = [NSMutableArray arrayWithCapacity:points.count];
for (int i = 0; i < points.count; i++) {
CLLocationCoordinate2D coord = [RCTConvert CLLocationCoordinate2D:points[i]];
float intensity = 1.0;
if (points[i][@"weight"] != nil) {
intensity = [RCTConvert float:points[i][@"weight"]];
}
[w addObject:[[GMUWeightedLatLng alloc] initWithCoordinate:coord intensity:intensity]];
}
_points = w;
[self.heatmap setWeightedData:w];
[self.heatmap clearTileCache];
[self.heatmap setMap:self.heatmap.map];
}
- (void)setRadius:(NSUInteger)radius
{
_radius = radius;
[self.heatmap setRadius:radius];
}
- (void)setOpacity:(float)opacity
{
_opacity = opacity;
[self.heatmap setOpacity:opacity];
}
- (void)setGradient:(NSDictionary *)gradient
{
NSArray<UIColor *> *colors = [RCTConvert UIColorArray:gradient[@"colors"]];
NSArray<NSNumber *> *colorStartPoints = [RCTConvert NSNumberArray:gradient[@"startPoints"]];
NSUInteger colorMapSize = [RCTConvert NSUInteger:gradient[@"colorMapSize"]];
GMUGradient *gmuGradient = [[GMUGradient alloc] initWithColors:colors
startPoints:colorStartPoints
colorMapSize:colorMapSize];
_gradient = gmuGradient;
[self.heatmap setGradient:gmuGradient];
}
@end

View File

@@ -0,0 +1,11 @@
//
// AIRGoogleMapHeatmapManager.h
//
// Created by David Cako on 29 April 2018.
//
#import <React/RCTViewManager.h>
@interface AIRGoogleMapHeatmapManager : RCTViewManager
@end

View File

@@ -0,0 +1,32 @@
//
// AIRGoogleMapHeatmapManager.m
//
// Created by David Cako on 29 April 2018.
//
#import "AIRGoogleMapHeatmapManager.h"
#import "AIRGoogleMapHeatmap.h"
#import "AIRGoogleMap.h"
#import <React/RCTBridge.h>
#import <React/UIView+React.h>
@interface AIRGoogleMapHeatmapManager()
@end
@implementation AIRGoogleMapHeatmapManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapHeatmap *heatmap = [AIRGoogleMapHeatmap new];
return heatmap;
}
RCT_EXPORT_VIEW_PROPERTY(points, NSArray<NSDictionary *>)
RCT_EXPORT_VIEW_PROPERTY(radius, NSUInteger)
RCT_EXPORT_VIEW_PROPERTY(opacity, float)
RCT_EXPORT_VIEW_PROPERTY(gradient, NSDictionary *)
@end

View File

@@ -0,0 +1,28 @@
//
// AIRGoogleMapManager.h
// AirMaps
//
// Created by Gil Birman on 9/1/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@class GMSCameraPosition;
@interface AIRGoogleMapManager : RCTViewManager
@property (nonatomic, strong) NSString* googleMapId;
@property (nonatomic, strong) NSString* customMapStyle;
@property (nonatomic) BOOL zoomTapEnabled;
@property (nonatomic, strong) UIColor* backgroundColor;
@property (nonatomic, strong) GMSCameraPosition* camera;
@property (nonatomic, strong) NSDictionary *initialProps;
@property (nonatomic) BOOL isGesture;
@end
#endif

View File

@@ -0,0 +1,543 @@
//
// AIRGoogleMapManager.m
// AirMaps
//
// Created by Gil Birman on 9/1/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapManager.h"
#import <React/RCTViewManager.h>
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/RCTConvert+CoreLocation.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTViewManager.h>
#import <React/RCTConvert.h>
#import <React/UIView+React.h>
#import "RCTConvert+GMSMapViewType.h"
#import "AIRGoogleMap.h"
#import "AIRGoogleMapMarker.h"
#import "AIRGoogleMapCoordinate.h"
#import "RCTConvert+GMSMapViewType.h"
#import <MapKit/MapKit.h>
#import <QuartzCore/QuartzCore.h>
#if __has_include(<ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>)
#import <ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>
#else
#import "RNMapsAirModuleDelegate.h"
#endif
static NSString *const RCTMapViewKey = @"MapView";
@interface AIRGoogleMapManager() <GMSMapViewDelegate>
{
BOOL didCallOnMapReady;
}
@end
@implementation AIRGoogleMapManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
if (self.initialProps){
if (self.initialProps[@"googleMapId"]){
_googleMapId = self.initialProps[@"googleMapId"];
}
if (self.initialProps[@"zoomTapEnabled"]){
_zoomTapEnabled = self.initialProps[@"zoomTapEnabled"];
}
if (self.initialProps[@"loadingBackgroundColor"]){
_backgroundColor = [RCTConvert UIColor:self.initialProps[@"loadingBackgroundColor"]];
}
if (self.initialProps[@"initialCamera"]){
_camera = [RCTConvert GMSCameraPositionWithDefaults:self.initialProps[@"initialCamera"] existingCamera:nil];
}
}
AIRGoogleMap *map = [[AIRGoogleMap alloc] initWithMapId:self.googleMapId initialCamera:self.camera backgroundColor:self.backgroundColor andZoomTapEnabled:self.zoomTapEnabled];
map.bridge = self.bridge;
map.delegate = self;
map.isAccessibilityElement = NO;
map.accessibilityElementsHidden = NO;
map.settings.consumesGesturesInView = NO;
if (self.customMapStyle != nil){
map.customMapStyleString = self.customMapStyle;
}
UIPanGestureRecognizer *drag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapDrag:)];
[drag setMinimumNumberOfTouches:1];
[drag setMaximumNumberOfTouches:1];
[map addGestureRecognizer:drag];
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapDrag:)];
[map addGestureRecognizer:pinch];
return map;
}
RCT_EXPORT_VIEW_PROPERTY(isAccessibilityElement, BOOL)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString)
RCT_EXPORT_VIEW_PROPERTY(googleMapId, NSString)
RCT_EXPORT_VIEW_PROPERTY(initialCamera, GMSCameraPosition)
RCT_REMAP_VIEW_PROPERTY(camera, cameraProp, GMSCameraPosition)
RCT_EXPORT_VIEW_PROPERTY(initialRegion, MKCoordinateRegion)
RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
RCT_EXPORT_VIEW_PROPERTY(showsBuildings, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsCompass, BOOL)
//RCT_EXPORT_VIEW_PROPERTY(showsScale, BOOL) // Not supported by GoogleMaps
RCT_EXPORT_VIEW_PROPERTY(showsTraffic, BOOL)
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollDuringRotateOrZoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(zoomTapEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsMyLocationButton, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsIndoors, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsIndoorLevelPicker, BOOL)
RCT_EXPORT_VIEW_PROPERTY(customMapStyleString, NSString)
RCT_EXPORT_VIEW_PROPERTY(mapPadding, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(paddingAdjustmentBehavior, paddingAdjustmentBehaviorString, NSString)
RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMapLoaded, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onKmlReady, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPanDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onUserLocationChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMarkerPress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMarkerSelect, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMarkerDeselect, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChangeStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChangeComplete, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPoiClick, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onIndoorLevelActivated, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onIndoorBuildingFocused, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(mapType, GMSMapViewType)
RCT_EXPORT_VIEW_PROPERTY(minZoomLevel, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(kmlSrc, NSString)
RCT_EXPORT_VIEW_PROPERTY(loadingBackgroundColor, UIColor)
RCT_EXPORT_METHOD(getCamera:(nonnull NSNumber *)reactTag
resolver: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
reject(@"Invalid argument", [NSString stringWithFormat:@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view], NULL);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
resolve(@{
@"center": @{
@"latitude": @(mapView.camera.target.latitude),
@"longitude": @(mapView.camera.target.longitude),
},
@"pitch": @(mapView.camera.viewingAngle),
@"heading": @(mapView.camera.bearing),
@"zoom": @(mapView.camera.zoom),
});
}
}];
}
RCT_EXPORT_METHOD(setCamera:(nonnull NSNumber *)reactTag
camera:(id)json)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
GMSCameraPosition *camera = [RCTConvert GMSCameraPositionWithDefaults:json existingCamera:[mapView cameraProp]];
[mapView setCameraProp:camera];
}
}];
}
RCT_EXPORT_METHOD(animateCamera:(nonnull NSNumber *)reactTag
withCamera:(id)json
withDuration:(CGFloat)duration)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
[CATransaction begin];
[CATransaction setAnimationDuration:duration/1000];
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
GMSCameraPosition *camera = [RCTConvert GMSCameraPositionWithDefaults:json existingCamera:[mapView cameraProp]];
[mapView animateToCameraPosition:camera];
[CATransaction commit];
}
}];
}
RCT_EXPORT_METHOD(animateToRegion:(nonnull NSNumber *)reactTag
withRegion:(MKCoordinateRegion)region
withDuration:(CGFloat)duration)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
// Core Animation must be used to control the animation's duration
// See http://stackoverflow.com/a/15663039/171744
[CATransaction begin];
[CATransaction setAnimationDuration:duration/1000];
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
GMSCameraPosition *camera = [AIRGoogleMap makeGMSCameraPositionFromMap:mapView andMKCoordinateRegion:region];
[mapView animateToCameraPosition:camera];
[CATransaction commit];
}
}];
}
RCT_EXPORT_METHOD(fitToElements:(nonnull NSNumber *)reactTag
edgePadding:(nonnull NSDictionary *)edgePadding
animated:(BOOL)animated)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
[mapView fitToElementsWithEdgePadding:edgePadding animated:animated];
}
}];
}
RCT_EXPORT_METHOD(fitToSuppliedMarkers:(nonnull NSNumber *)reactTag
markers:(nonnull NSArray *)markers
edgePadding:(nonnull NSDictionary *)edgePadding
animated:(BOOL)animated)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
[mapView fitToSuppliedMarkers:markers withEdgePadding:edgePadding animated:animated];
}
}];
}
RCT_EXPORT_METHOD(fitToCoordinates:(nonnull NSNumber *)reactTag
coordinates:(nonnull NSArray<AIRGoogleMapCoordinate *> *)coordinates
edgePadding:(nonnull NSDictionary *)edgePadding
animated:(BOOL)animated)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
[mapView fitToCoordinates:coordinates withEdgePadding:edgePadding animated:animated];
}
}];
}
RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)reactTag
withWidth:(nonnull NSNumber *)width
withHeight:(nonnull NSNumber *)height
withRegion:(MKCoordinateRegion)region
format:(nonnull NSString *)format
quality:(nonnull NSNumber *)quality
result:(nonnull NSString *)result
withCallback:(RCTPromiseResolveBlock)callback)
{
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
NSString *pathComponent = [NSString stringWithFormat:@"Documents/snapshot-%.20lf.%@", timeStamp, format];
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent: pathComponent];
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
NSMutableDictionary* config = [NSMutableDictionary new];
[view takeSnapshotWithConfig:config success:callback error:^(NSString *code, NSString *message, NSError *error) {
callback(@[error]);
}];
[config setObject:width forKey:@"width"];
[config setObject:height forKey:@"height"];
[config setObject:format forKey:@"format"];
[config setObject:quality forKey:@"quality"];
[config setObject:result forKey:@"result"];
[config setObject:filePath forKey:@"filePath"];
// TODO: currently we are ignoring width, height, region
AIRGoogleMap* mapView = (AIRGoogleMap *) view;
UIGraphicsBeginImageContextWithOptions(mapView.frame.size, YES, 0.0f);
[mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
NSData *data;
if ([format isEqualToString:@"png"]) {
data = UIImagePNGRepresentation(image);
}
else if([format isEqualToString:@"jpg"]) {
data = UIImageJPEGRepresentation(image, quality.floatValue);
}
if ([result isEqualToString:@"file"]) {
[data writeToFile:filePath atomically:YES];
callback(@[[NSNull null], filePath]);
}
else if ([result isEqualToString:@"base64"]) {
callback(@[[NSNull null], [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]]);
}
}
UIGraphicsEndImageContext();
}];
}
RCT_EXPORT_METHOD(pointForCoordinate:(nonnull NSNumber *)reactTag
coordinate:(NSDictionary *)coordinate
resolver: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
CLLocationCoordinate2D coord =
CLLocationCoordinate2DMake(
[coordinate[@"latitude"] doubleValue],
[coordinate[@"longitude"] doubleValue]
);
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
id<RNMapsAirModuleDelegate> mapView = (id<RNMapsAirModuleDelegate>) view;
resolve([mapView getPointForCoordinates:coord]);
}
}];
}
RCT_EXPORT_METHOD(coordinateForPoint:(nonnull NSNumber *)reactTag
point:(NSDictionary *)point
resolver: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
CGPoint pt = CGPointMake(
[point[@"x"] doubleValue],
[point[@"y"] doubleValue]
);
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
id<RNMapsAirModuleDelegate> mapView = (id<RNMapsAirModuleDelegate>) view;
resolve([view getCoordinatesForPoint:pt]);
}
}];
}
RCT_EXPORT_METHOD(getMarkersFrames:(nonnull NSNumber *)reactTag
onlyVisible:(BOOL)onlyVisible
resolver: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
resolve([mapView getMarkersFramesWithOnlyVisible:onlyVisible]);
}
}];
}
RCT_EXPORT_METHOD(getMapBoundaries:(nonnull NSNumber *)reactTag
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
resolve([view getMapBoundaries]);
}
}];
}
RCT_EXPORT_METHOD(setMapBoundaries:(nonnull NSNumber *)reactTag
northEast:(CLLocationCoordinate2D)northEast
southWest:(CLLocationCoordinate2D)southWest)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:northEast coordinate:southWest];
mapView.cameraTargetBounds = bounds;
}
}];
}
RCT_EXPORT_METHOD(setIndoorActiveLevelIndex:(nonnull NSNumber *)reactTag
levelIndex:(NSInteger) levelIndex)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMap class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view);
} else {
AIRGoogleMap *mapView = (AIRGoogleMap *)view;
if (!mapView.indoorDisplay) {
return;
}
if ( levelIndex < [mapView.indoorDisplay.activeBuilding.levels count]) {
mapView.indoorDisplay.activeLevel = mapView.indoorDisplay.activeBuilding.levels[levelIndex];
}
}
}];
}
+ (BOOL)requiresMainQueueSetup {
return YES;
}
- (NSDictionary *)constantsToExport {
return @{ @"legalNotice": [GMSServices openSourceLicenseInfo] };
}
- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture {
self.isGesture = gesture;
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView willMove:gesture];
}
- (void)mapViewDidStartTileRendering:(GMSMapView *)mapView {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView didPrepareMap];
}
- (void)mapViewDidFinishTileRendering:(GMSMapView *)mapView {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView mapViewDidFinishTileRendering];
}
- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
return [googleMapView didTapMarker:marker];
}
- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSPolygon *)polygon {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView didTapPolygon:polygon];
}
- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView didTapAtCoordinate:coordinate];
}
- (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView didLongPressAtCoordinate:coordinate];
}
- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView didChangeCameraPosition:position isGesture:self.isGesture];
}
- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView idleAtCameraPosition:position isGesture:self.isGesture];
}
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker {
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
return [aMarker.fakeMarker markerInfoWindow];}
- (UIView *)mapView:(GMSMapView *)mapView markerInfoContents:(GMSMarker *)marker {
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
return [aMarker.fakeMarker markerInfoContents];
}
- (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker {
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
[aMarker.fakeMarker didTapInfoWindowOfMarker:aMarker];
}
- (void)mapView:(GMSMapView *)mapView didBeginDraggingMarker:(GMSMarker *)marker {
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
[aMarker.fakeMarker didBeginDraggingMarker:aMarker];
}
- (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
[aMarker.fakeMarker didEndDraggingMarker:aMarker];
}
- (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker {
AIRGMSMarker *aMarker = (AIRGMSMarker *)marker;
[aMarker.fakeMarker didDragMarker:aMarker];
}
- (void)mapView:(GMSMapView *)mapView
didTapPOIWithPlaceID:(NSString *)placeID
name:(NSString *)name
location:(CLLocationCoordinate2D)location {
AIRGoogleMap *googleMapView = (AIRGoogleMap *)mapView;
[googleMapView didTapPOIWithPlaceID:placeID name:name location:location];
}
#pragma mark Gesture Recognizer Handlers
- (void)handleMapDrag:(UIPanGestureRecognizer*)recognizer {
AIRGoogleMap *map = (AIRGoogleMap *)recognizer.view;
if (!map.onPanDrag) return;
CGPoint touchPoint = [recognizer locationInView:map];
CLLocationCoordinate2D coord = [map.projection coordinateForPoint:touchPoint];
map.onPanDrag(@{
@"coordinate": @{
@"latitude": @(coord.latitude),
@"longitude": @(coord.longitude),
},
@"position": @{
@"x": @(touchPoint.x),
@"y": @(touchPoint.y),
},
@"numberOfTouches": @(recognizer.numberOfTouches),
});
}
@end
#endif

View File

@@ -0,0 +1,60 @@
//
// AIRGoogleMapMarker.h
// AirMaps
//
// Created by Gil Birman on 9/2/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTBridge.h>
#import "AIRGMSMarker.h"
#import "AIRGoogleMap.h"
#import "AIRGoogleMapCallout.h"
#import "AIRGoogleMapCalloutSubview.h"
@interface AIRGoogleMapMarker : UIView
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, strong) AIRGoogleMapCallout *calloutView;
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, assign) CLLocationDegrees rotation;
@property (nonatomic, strong) AIRGMSMarker* realMarker;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, copy) RCTDirectEventBlock onDragStart;
@property (nonatomic, copy) RCTDirectEventBlock onDrag;
@property (nonatomic, copy) RCTDirectEventBlock onDragEnd;
@property (nonatomic, copy) NSString *imageSrc;
@property (nonatomic, copy) NSString *iconSrc;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic, strong) UIColor *pinColor;
@property (nonatomic, assign) CGPoint anchor;
@property (nonatomic, assign) CGPoint calloutAnchor;
@property (nonatomic, assign) NSInteger zIndex;
@property (nonatomic, assign) double opacity;
@property (nonatomic, assign) BOOL draggable;
@property (nonatomic, assign) BOOL tappable;
@property (nonatomic, assign) BOOL tracksViewChanges;
@property (nonatomic, assign) BOOL tracksInfoWindowChanges;
- (void)showCalloutView;
- (void)hideCalloutView;
- (void)redraw;
- (UIView *)markerInfoContents;
- (UIView *)markerInfoWindow;
- (void)didTapInfoWindowOfMarker:(AIRGMSMarker *)marker;
- (void)didTapInfoWindowOfMarker:(AIRGMSMarker *)marker point:(CGPoint)point frame:(CGRect)frame;
- (void)didTapInfoWindowOfMarker:(AIRGMSMarker *)marker subview:(AIRGoogleMapCalloutSubview*)subview point:(CGPoint)point frame:(CGRect)frame;
- (void)didBeginDraggingMarker:(AIRGMSMarker *)marker;
- (void)didEndDraggingMarker:(AIRGMSMarker *)marker;
- (void)didDragMarker:(AIRGMSMarker *)marker;
- (id)makeEventData;
- (id)makeEventData:(NSString *)action;
- (UIView *) iconView;
- (void) didInsertInMap:(AIRGoogleMap *) map;
@end
#endif

View File

@@ -0,0 +1,560 @@
//
// AIRGoogleMapMarker.m
// AirMaps
//
// Created by Gil Birman on 9/2/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapMarker.h"
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTImageLoaderProtocol.h>
#import <React/RCTUtils.h>
#import "AIRGMSMarker.h"
#import "AIRGoogleMapCallout.h"
#import "AIRDummyView.h"
CGRect unionRect(CGRect a, CGRect b) {
return CGRectMake(
MIN(a.origin.x, b.origin.x),
MIN(a.origin.y, b.origin.y),
MAX(a.size.width, b.size.width),
MAX(a.size.height, b.size.height));
}
@interface AIRGoogleMapMarker ()
@end
@implementation AIRGoogleMapMarker {
RCTImageLoaderCancellationBlock _reloadImageCancellationBlock;
RCTBubblingEventBlock _onPress;
RCTDirectEventBlock _onSelect;
RCTDirectEventBlock _onDeselect;
__weak UIImageView *_iconImageView;
UIView *_iconView;
UIColor *_pinColor;
CLLocationCoordinate2D _coordinates;
CLLocationDegrees _rotation;
BOOL _tracksInfoWindowChanges;
BOOL _tracksViewChanges;
BOOL _draggable;
BOOL _tappable;
BOOL _flat;
double _opacity;
NSString* _identifier;
NSString* _title;
NSString* _subtitle;
}
- (instancetype)init
{
if ((self = [super init])) {
_tracksViewChanges = true;
_tracksInfoWindowChanges = false;
_tappable = true;
_opacity = 1.0;
}
return self;
}
- (void)layoutSubviews {
float width = 0;
float height = 0;
for (UIView *v in [_iconView subviews]) {
float fw = v.frame.origin.x + v.frame.size.width;
float fh = v.frame.origin.y + v.frame.size.height;
width = MAX(fw, width);
height = MAX(fh, height);
}
[_iconView setFrame:CGRectMake(0, 0, width, height)];
}
- (UIView *) iconView
{
return _iconView;
}
- (void) didUpdateReactSubviews
{
[super didUpdateReactSubviews];
if (_iconView){
[_iconView setFrame:self.frame];
}
}
- (void) didInsertInMap:(AIRGoogleMap *) map
{
_realMarker = [AIRGMSMarker new];
_realMarker.fakeMarker = self;
_realMarker.tracksViewChanges = _tracksViewChanges;
_realMarker.tracksInfoWindowChanges = _tracksInfoWindowChanges;
[_realMarker setPosition:_coordinates];
if (_iconView){
[_realMarker setIconView:_iconView];
}
if (_rotation != 0){
[_realMarker setRotation:_rotation];
}
if (_identifier){
[_realMarker setIdentifier:_identifier];
}
if (_title){
[_realMarker setTitle:_title];
}
if (_subtitle){
[_realMarker setSnippet:_subtitle];
}
if (!CGPointEqualToPoint(_anchor, CGPointZero)){
[_realMarker setGroundAnchor:_anchor];
}
if (!CGPointEqualToPoint(_calloutAnchor, CGPointZero)){
[_realMarker setInfoWindowAnchor:_calloutAnchor];
}
if (_flat){
[_realMarker setFlat:_flat];
}
if (_draggable){
[_realMarker setDraggable:_draggable];
}
[_realMarker setTappable:_tappable];
if (_pinColor){
_realMarker.icon = [GMSMarker markerImageWithColor:_pinColor];
}
if (_opacity != 1.0){
[_realMarker setOpacity:_opacity];
}
if (_onSelect){
[_realMarker setOnSelect:_onSelect];
}
if (_onDeselect){
[_realMarker setOnDeselect:_onDeselect];
}
if (_onPress){
[_realMarker setOnPress:_onPress];
}
if (_zIndex){
[_realMarker setZIndex:_zIndex];
}
[_realMarker setMap:map];
}
- (void)iconViewInsertSubview:(UIView*)subview atIndex:(NSInteger)atIndex {
if (!_iconView){
_iconView = [[UIView alloc] init];
}
if (!_realMarker.iconView) {
_realMarker.iconView = _iconView;
}
[_iconView insertSubview:subview atIndex:atIndex];
}
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex {
if ([subview isKindOfClass:[AIRGoogleMapCallout class]]) {
self.calloutView = (AIRGoogleMapCallout *)subview;
} else { // a child view of the marker
[self iconViewInsertSubview:(UIView*)subview atIndex:atIndex+1];
}
AIRDummyView *dummySubview = [[AIRDummyView alloc] initWithView:(UIView *)subview];
[super insertReactSubview:(UIView*)dummySubview atIndex:atIndex];
}
- (void)removeReactSubview:(id<RCTComponent>)dummySubview {
UIView *subview = [dummySubview isKindOfClass:[AIRDummyView class]] ? ((AIRDummyView *)dummySubview).view : (UIView *)dummySubview;
if ([subview isKindOfClass:[AIRGoogleMapCallout class]]) {
self.calloutView = nil;
} else {
[subview removeFromSuperview];
}
[super removeReactSubview:(UIView*)dummySubview];
}
- (void)showCalloutView {
[_realMarker.map setSelectedMarker:_realMarker];
}
- (void)hideCalloutView {
[_realMarker.map setSelectedMarker:Nil];
}
- (void)redraw {
if (!_realMarker.iconView) return;
BOOL oldValue = _realMarker.tracksViewChanges;
if (oldValue == YES)
{
// Immediate refresh, like right now. Not waiting for next frame.
UIView *view = _realMarker.iconView;
_realMarker.iconView = nil;
_realMarker.iconView = view;
}
else
{
// Refresh according to docs
_realMarker.tracksViewChanges = YES;
_realMarker.tracksViewChanges = NO;
}
}
- (UIView *)markerInfoContents {
if (self.calloutView && !self.calloutView.tooltip) {
return self.calloutView;
}
return nil;
}
- (UIView *)markerInfoWindow {
if (self.calloutView && self.calloutView.tooltip) {
return self.calloutView;
}
return nil;
}
- (void)didTapInfoWindowOfMarker:(AIRGMSMarker *)marker point:(CGPoint)point frame:(CGRect)frame {
if (self.calloutView && self.calloutView.onPress) {
//todo: why not 'callout-press' ?
id event = @{
@"action": @"marker-overlay-press",
@"id": self.identifier ?: @"unknown",
@"point": @{
@"x": @(point.x),
@"y": @(point.y),
},
@"frame": @{
@"x": @(frame.origin.x),
@"y": @(frame.origin.y),
@"width": @(frame.size.width),
@"height": @(frame.size.height),
}
};
self.calloutView.onPress(event);
}
}
- (void)didTapInfoWindowOfMarker:(AIRGMSMarker *)marker {
[self didTapInfoWindowOfMarker:marker point:CGPointMake(-1, -1) frame:CGRectZero];
}
- (void)didTapInfoWindowOfMarker:(AIRGMSMarker *)marker subview:(AIRGoogleMapCalloutSubview*)subview point:(CGPoint)point frame:(CGRect)frame {
if (subview && subview.onPress) {
//todo: why not 'callout-inside-press' ?
id event = @{
@"action": @"marker-inside-overlay-press",
@"id": self.identifier ?: @"unknown",
@"point": @{
@"x": @(point.x),
@"y": @(point.y),
},
@"frame": @{
@"x": @(frame.origin.x),
@"y": @(frame.origin.y),
@"width": @(frame.size.width),
@"height": @(frame.size.height),
}
};
subview.onPress(event);
} else {
[self didTapInfoWindowOfMarker:marker point:point frame:frame];
}
}
- (void)didBeginDraggingMarker:(AIRGMSMarker *)marker {
if (!self.onDragStart) return;
self.onDragStart([self makeEventData]);
}
- (void)didEndDraggingMarker:(AIRGMSMarker *)marker {
if (!self.onDragEnd) return;
self.onDragEnd([self makeEventData]);
}
- (void)didDragMarker:(AIRGMSMarker *)marker {
if (!self.onDrag) return;
self.onDrag([self makeEventData]);
}
- (void)setCoordinate:(CLLocationCoordinate2D)coordinate {
_realMarker.position = coordinate;
_coordinates = coordinate;
}
- (CLLocationCoordinate2D)coordinate {
return _realMarker.position;
}
- (void)setRotation:(CLLocationDegrees)rotation {
_realMarker.rotation = rotation;
_rotation = rotation;
}
- (CLLocationDegrees)rotation {
return _realMarker.rotation;
}
- (void)setIdentifier:(NSString *)identifier {
_realMarker.identifier = identifier;
_identifier = identifier;
}
- (NSString *)identifier {
return _realMarker.identifier;
}
- (void)setOnPress:(RCTBubblingEventBlock)onPress {
_realMarker.onPress = onPress;
_onPress = onPress;
}
- (RCTBubblingEventBlock)onPress {
return _realMarker.onPress;
}
- (void)setOnSelect:(RCTDirectEventBlock)onSelect {
_realMarker.onSelect = onSelect;
_onSelect = onSelect;
}
- (RCTDirectEventBlock)onSelect {
return _realMarker.onSelect;
}
- (void)setOnDeselect:(RCTDirectEventBlock)onDeselect {
_realMarker.onDeselect = onDeselect;
_onDeselect = onDeselect;
}
- (RCTDirectEventBlock)onDeselect {
return _realMarker.onDeselect;
}
- (void)setOpacity:(double)opacity
{
_realMarker.opacity = opacity;
_opacity = opacity;
}
- (void)setImageSrc:(NSString *)imageSrc
{
_imageSrc = imageSrc;
if (_reloadImageCancellationBlock) {
_reloadImageCancellationBlock();
_reloadImageCancellationBlock = nil;
}
if (!_imageSrc) {
if (_iconImageView) [_iconImageView removeFromSuperview];
return;
}
if (!_iconImageView) {
// prevent glitch with marker (cf. https://github.com/react-native-maps/react-native-maps/issues/738)
UIImageView *empyImageView = [[UIImageView alloc] init];
_iconImageView = empyImageView;
[self iconViewInsertSubview:_iconImageView atIndex:0];
}
__weak AIRGoogleMapMarker* weakSelf = self;
_reloadImageCancellationBlock = [[_bridge moduleForName:@"ImageLoader"] loadImageWithURLRequest:[RCTConvert NSURLRequest:_imageSrc]
size:self.bounds.size
scale:RCTScreenScale()
clipped:YES
resizeMode:RCTResizeModeCenter
progressBlock:nil
partialLoadBlock:nil
completionBlock:^(NSError *error, UIImage *image) {
if (error) {
// TODO(lmr): do something with the error?
NSLog(@"%@", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
__strong AIRGoogleMapMarker* strongSelf = weakSelf;
// TODO(gil): This way allows different image sizes
if (strongSelf->_iconImageView) [strongSelf->_iconImageView removeFromSuperview];
// ... but this way is more efficient?
// if (_iconImageView) {
// [_iconImageView setImage:image];
// return;
// }
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// TODO: w,h or pixel density could be a prop.
float density = 1;
float w = image.size.width/density;
float h = image.size.height/density;
CGRect bounds = CGRectMake(0, 0, w, h);
imageView.contentMode = UIViewContentModeScaleAspectFit;
[imageView setFrame:bounds];
// NOTE: sizeToFit doesn't work instead. Not sure why.
// TODO: Doing it this way is not ideal because it causes things to reshuffle
// when the image loads IF the image is larger than the UIView.
// Shouldn't required images have size info automatically via RN?
CGRect selfBounds = unionRect(bounds, self.bounds);
[strongSelf setFrame:selfBounds];
strongSelf->_iconImageView = imageView;
[strongSelf iconViewInsertSubview:imageView atIndex:0];
[strongSelf layoutSubviews];
[strongSelf.realMarker setIconView:strongSelf.iconView];
});
}];
}
- (void)setIconSrc:(NSString *)iconSrc
{
_iconSrc = iconSrc;
if (_reloadImageCancellationBlock) {
_reloadImageCancellationBlock();
_reloadImageCancellationBlock = nil;
}
if (!_realMarker.icon) {
// prevent glitch with marker (cf. https://github.com/react-native-maps/react-native-maps/issues/3657)
UIImage *emptyImage = [[UIImage alloc] init];
_realMarker.icon = emptyImage;
}
_reloadImageCancellationBlock =
[[_bridge moduleForName:@"ImageLoader"] loadImageWithURLRequest:[RCTConvert NSURLRequest:_iconSrc]
size:self.bounds.size
scale:RCTScreenScale()
clipped:YES
resizeMode:RCTResizeModeCenter
progressBlock:nil
partialLoadBlock:nil
completionBlock:^(NSError *error, UIImage *image) {
if (error) {
// TODO(lmr): do something with the error?
NSLog(@"%@", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
self->_realMarker.icon = image;
});
}];
}
- (void)setTitle:(NSString *)title {
_realMarker.title = [title copy];
_title = title;
}
- (NSString *)title {
return _realMarker.title;
}
- (void)setSubtitle:(NSString *)subtitle {
_realMarker.snippet = subtitle;
_subtitle = subtitle;
}
- (NSString *)subtitle {
return _realMarker.snippet;
}
- (void)setPinColor:(UIColor *)pinColor {
_pinColor = pinColor;
_realMarker.icon = [GMSMarker markerImageWithColor:pinColor];
}
- (void)setAnchor:(CGPoint)anchor {
_anchor = anchor;
_realMarker.groundAnchor = anchor;
}
- (void)setCalloutAnchor:(CGPoint)calloutAnchor {
_calloutAnchor = calloutAnchor;
_realMarker.infoWindowAnchor = calloutAnchor;
}
- (void)setZIndex:(NSInteger)zIndex
{
_zIndex = zIndex;
_realMarker.zIndex = (int)zIndex;
}
- (void)setDraggable:(BOOL)draggable {
_realMarker.draggable = draggable;
_draggable = draggable;
}
- (BOOL)draggable {
return _realMarker.draggable;
}
- (void)setTappable:(BOOL)tappable {
_realMarker.tappable = tappable;
_tappable = tappable;
}
- (BOOL)tappable {
return _realMarker.tappable;
}
- (void)setFlat:(BOOL)flat {
_realMarker.flat = flat;
_flat = flat;
}
- (BOOL)flat {
return _realMarker.flat;
}
- (void)setTracksViewChanges:(BOOL)tracksViewChanges {
_realMarker.tracksViewChanges = tracksViewChanges;
}
- (BOOL)tracksViewChanges {
return _realMarker.tracksViewChanges;
}
- (void)setTracksInfoWindowChanges:(BOOL)tracksInfoWindowChanges {
_realMarker.tracksInfoWindowChanges = tracksInfoWindowChanges;
}
- (BOOL)tracksInfoWindowChanges {
return _realMarker.tracksInfoWindowChanges;
}
- (id)makeEventData:(NSString *)action {
CLLocationCoordinate2D coordinate = self.realMarker.position;
CGPoint position = [self.realMarker.map.projection pointForCoordinate:coordinate];
return @{
@"id": self.identifier ?: @"unknown",
@"position": @{
@"x": @(position.x),
@"y": @(position.y),
},
@"coordinate": @{
@"latitude": @(coordinate.latitude),
@"longitude": @(coordinate.longitude),
},
@"action": action,
};
}
- (id)makeEventData {
return [self makeEventData:@"unknown"];
}
@end
#endif

View File

@@ -0,0 +1,16 @@
//
// AIRGoogleMapMarkerManager.h
// AirMaps
//
// Created by Gil Birman on 9/2/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@interface AIRGoogleMapMarkerManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,116 @@
//
// AIRGoogleMapMarkerManager.m
// AirMaps
//
// Created by Gil Birman on 9/2/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapMarkerManager.h"
#import "AIRGoogleMapMarker.h"
#import <MapKit/MapKit.h>
#import <React/RCTUIManager.h>
@implementation AIRGoogleMapMarkerManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapMarker *marker = [AIRGoogleMapMarker new];
// UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_handleTap:)];
// // setting this to NO allows the parent MapView to continue receiving marker selection events
// tapGestureRecognizer.cancelsTouchesInView = NO;
// [marker addGestureRecognizer:tapGestureRecognizer];
marker.bridge = self.bridge;
marker.isAccessibilityElement = YES;
marker.accessibilityElementsHidden = NO;
return marker;
}
RCT_EXPORT_VIEW_PROPERTY(identifier, NSString)
RCT_EXPORT_VIEW_PROPERTY(coordinate, CLLocationCoordinate2D)
RCT_EXPORT_VIEW_PROPERTY(rotation, CLLocationDegrees)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_REMAP_VIEW_PROPERTY(image, imageSrc, NSString)
RCT_REMAP_VIEW_PROPERTY(icon, iconSrc, NSString)
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier, NSString)
RCT_REMAP_VIEW_PROPERTY(description, subtitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(pinColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(anchor, CGPoint)
RCT_EXPORT_VIEW_PROPERTY(calloutAnchor, CGPoint)
RCT_EXPORT_VIEW_PROPERTY(zIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(draggable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(tappable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(flat, BOOL)
RCT_EXPORT_VIEW_PROPERTY(tracksViewChanges, BOOL)
RCT_EXPORT_VIEW_PROPERTY(tracksInfoWindowChanges, BOOL)
RCT_EXPORT_VIEW_PROPERTY(opacity, double)
RCT_EXPORT_VIEW_PROPERTY(onDragStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDragEnd, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelect, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDeselect, RCTDirectEventBlock)
RCT_EXPORT_METHOD(showCallout:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMapMarker class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
[(AIRGoogleMapMarker *) view showCalloutView];
}
}];
}
RCT_EXPORT_METHOD(hideCallout:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMapMarker class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
[(AIRGoogleMapMarker *) view hideCalloutView];
}
}];
}
RCT_EXPORT_METHOD(redrawCallout:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMapMarker class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
AIRGoogleMapMarker* marker = (AIRGoogleMapMarker *) view;
[NSTimer scheduledTimerWithTimeInterval:0.0
target:[NSBlockOperation blockOperationWithBlock:^{
[marker hideCalloutView];
[marker showCalloutView];
}]
selector:@selector(main)
userInfo:nil
repeats:NO
];
}
}];
}
RCT_EXPORT_METHOD(redraw:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[AIRGoogleMapMarker class]]) {
RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view);
} else {
[(AIRGoogleMapMarker *) view redraw];
}
}];
}
@end
#endif

View File

@@ -0,0 +1,28 @@
//
// AIRGoogleMapOverlay.h
//
// Created by Taro Matsuzawa on 5/3/17.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTBridge.h>
#import "AIRGoogleMap.h"
@interface AIRGoogleMapOverlay : UIView
@property (nonatomic, strong) GMSGroundOverlay *overlay;
@property (nonatomic, copy) NSString *imageSrc;
@property (nonatomic, strong, readonly) UIImage *overlayImage;
@property (nonatomic, copy) NSArray *boundsRect;
@property (nonatomic, assign) CGFloat opacity;
@property (nonatomic, readonly) GMSCoordinateBounds *overlayBounds;
@property (nonatomic, readonly) double bearing;
@property (nonatomic, weak) RCTBridge *bridge;
@end
#endif

View File

@@ -0,0 +1,92 @@
//
// AIRGoogleMapOverlay.m
// Created by Nick Italiano on 3/5/17.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapOverlay.h"
#import <React/RCTEventDispatcher.h>
#import <React/RCTImageLoaderProtocol.h>
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
@interface AIRGoogleMapOverlay()
@property (nonatomic, strong, readwrite) UIImage *overlayImage;
@property (nonatomic, readwrite) GMSCoordinateBounds *overlayBounds;
@property (nonatomic) CLLocationDirection bearing;
@end
@implementation AIRGoogleMapOverlay {
RCTImageLoaderCancellationBlock _reloadImageCancellationBlock;
CLLocationCoordinate2D _southWest;
CLLocationCoordinate2D _northEast;
}
- (instancetype)init
{
if ((self = [super init])) {
_overlay = [[GMSGroundOverlay alloc] init];
}
return self;
}
- (void)setImageSrc:(NSString *)imageSrc
{
NSLog(@">>> SET IMAGESRC: %@", imageSrc);
_imageSrc = imageSrc;
if (_reloadImageCancellationBlock) {
_reloadImageCancellationBlock();
_reloadImageCancellationBlock = nil;
}
__weak typeof(self) weakSelf = self;
_reloadImageCancellationBlock = [[_bridge moduleForName:@"ImageLoader"] loadImageWithURLRequest:[RCTConvert NSURLRequest:_imageSrc]
size:weakSelf.bounds.size
scale:RCTScreenScale()
clipped:YES
resizeMode:RCTResizeModeCenter
progressBlock:nil
partialLoadBlock:nil
completionBlock:^(NSError *error, UIImage *image) {
if (error) {
NSLog(@"%@", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@">>> IMAGE: %@", image);
weakSelf.overlayImage = image;
weakSelf.overlay.icon = image;
});
}];
}
- (void)setBoundsRect:(NSArray *)boundsRect
{
_boundsRect = boundsRect;
_southWest = CLLocationCoordinate2DMake([boundsRect[1][0] doubleValue], [boundsRect[0][1] doubleValue]);
_northEast = CLLocationCoordinate2DMake([boundsRect[0][0] doubleValue], [boundsRect[1][1] doubleValue]);
_overlayBounds = [[GMSCoordinateBounds alloc] initWithCoordinate:_southWest
coordinate:_northEast];
_overlay.bounds = _overlayBounds;
}
- (void)setBearing:(double)bearing
{
_bearing = (double)bearing;
_overlay.bearing = _bearing;
}
- (void)setOpacity:(CGFloat)opacity
{
_overlay.opacity = opacity;
}
@end
#endif

View File

@@ -0,0 +1,10 @@
//
// AIRGoogleMapOverlayManager.h
// Created by Taro Matsuzawa on 3/5/17.
//
#import <Foundation/Foundation.h>
#import <React/RCTViewManager.h>
@interface AIRGoogleMapOverlayManager : RCTViewManager
@end

View File

@@ -0,0 +1,24 @@
#import "AIRGoogleMapOverlayManager.h"
#import "AIRGoogleMapOverlay.h"
@interface AIRGoogleMapOverlayManager()
@end
@implementation AIRGoogleMapOverlayManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapOverlay *overlay = [AIRGoogleMapOverlay new];
overlay.bridge = self.bridge;
return overlay;
}
RCT_REMAP_VIEW_PROPERTY(bounds, boundsRect, NSArray)
RCT_REMAP_VIEW_PROPERTY(bearing, bearing, double)
RCT_REMAP_VIEW_PROPERTY(image, imageSrc, NSString)
RCT_REMAP_VIEW_PROPERTY(opacity, opacity, CGFloat)
@end

View File

@@ -0,0 +1,33 @@
//
// AIRGoogleMapPolygon.h
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTBridge.h>
#import "AIRGMSPolygon.h"
#import "AIRGoogleMapCoordinate.h"
#import "AIRGoogleMap.h"
@interface AIRGoogleMapPolygon : UIView
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, strong) AIRGMSPolygon *polygon;
@property (nonatomic, strong) NSArray<AIRGoogleMapCoordinate *> *coordinates;
@property (nonatomic, strong) NSArray<NSArray<AIRGoogleMapCoordinate *> *> *holes;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, strong) UIColor *fillColor;
@property (nonatomic, assign) double strokeWidth;
@property (nonatomic, strong) UIColor *strokeColor;
@property (nonatomic, assign) BOOL geodesic;
@property (nonatomic, assign) int zIndex;
@property (nonatomic, assign) BOOL tappable;
- (void) didInsertInMap:(AIRGoogleMap *) map;
@end
#endif

View File

@@ -0,0 +1,157 @@
//
// AIRGoogleMapPolygon.m
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapPolygon.h"
#import "AIRGMSPolygon.h"
#import <GoogleMaps/GoogleMaps.h>
#import "AIRGoogleMap.h"
@implementation AIRGoogleMapPolygon
{
BOOL _didMoveToWindow;
}
- (instancetype)init
{
if (self = [super init]) {
_didMoveToWindow = false;
_polygon = [[AIRGMSPolygon alloc] init];
_polygon.fillColor = _fillColor;
_polygon.strokeColor = _strokeColor;
}
return self;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
if(_didMoveToWindow) return;
_didMoveToWindow = true;
if(_fillColor) {
_polygon.fillColor = _fillColor;
}
if(_strokeColor) {
_polygon.strokeColor = _strokeColor;
}
if(_strokeWidth) {
_polygon.strokeWidth = _strokeWidth;
}
}
- (void) didInsertInMap:(AIRGoogleMap *) map
{
_polygon = [AIRGMSPolygon new];
if (_identifier){
[_polygon setIdentifier:_identifier];
}
[_polygon setTappable:_tappable];
if (_strokeColor){
_polygon.strokeColor = _strokeColor;
}
if (_fillColor){
_polygon.fillColor = _fillColor;
}
if (_strokeWidth){
_polygon.strokeWidth = _strokeWidth;
}
if (_coordinates){
[self setCoordinates:_coordinates];
}
if (_holes){
[self setHoles:_holes];
}
if (_zIndex){
_polygon.zIndex = _zIndex;
}
[_polygon setMap:map];
}
- (void)setCoordinates:(NSArray<AIRGoogleMapCoordinate *> *)coordinates
{
_coordinates = coordinates;
GMSMutablePath *path = [GMSMutablePath path];
for(int i = 0; i < coordinates.count; i++)
{
[path addCoordinate:coordinates[i].coordinate];
}
_polygon.path = path;
}
- (void)setHoles:(NSArray<NSArray<AIRGoogleMapCoordinate *> *> *)holes
{
_holes = holes;
if (holes.count)
{
NSMutableArray<GMSMutablePath *> *interiorPolygons = [NSMutableArray array];
for(int h = 0; h < holes.count; h++)
{
GMSMutablePath *path = [GMSMutablePath path];
for(int i = 0; i < holes[h].count; i++)
{
[path addCoordinate:holes[h][i].coordinate];
}
[interiorPolygons addObject:path];
}
_polygon.holes = interiorPolygons;
}
}
-(void)setFillColor:(UIColor *)fillColor
{
_fillColor = fillColor;
if(_didMoveToWindow) {
_polygon.fillColor = fillColor;
}
}
-(void)setStrokeWidth:(double)strokeWidth
{
_strokeWidth = strokeWidth;
if(_didMoveToWindow) {
_polygon.strokeWidth = strokeWidth;
}
}
-(void)setStrokeColor:(UIColor *) strokeColor
{
_strokeColor = strokeColor;
if(_didMoveToWindow) {
_polygon.strokeColor = strokeColor;
}
}
-(void)setGeodesic:(BOOL)geodesic
{
_geodesic = geodesic;
_polygon.geodesic = geodesic;
}
-(void)setZIndex:(int)zIndex
{
_zIndex = zIndex;
_polygon.zIndex = zIndex;
}
-(void)setTappable:(BOOL)tappable
{
_tappable = tappable;
_polygon.tappable = tappable;
}
- (void)setOnPress:(RCTBubblingEventBlock)onPress {
_polygon.onPress = onPress;
}
@end
#endif

View File

@@ -0,0 +1,15 @@
//
// AIRGoogleMapPolylgoneManager.h
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@interface AIRGoogleMapPolygonManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,46 @@
//
// AIRGoogleMapPolylgoneManager.m
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapPolygonManager.h"
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTConvert+CoreLocation.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTViewManager.h>
#import <React/UIView+React.h>
#import "AIRGoogleMapPolygon.h"
#import "RCTConvert+GMSMapViewType.h"
@interface AIRGoogleMapPolygonManager()
@end
@implementation AIRGoogleMapPolygonManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapPolygon *polygon = [AIRGoogleMapPolygon new];
polygon.bridge = self.bridge;
return polygon;
}
RCT_EXPORT_VIEW_PROPERTY(coordinates, AIRGoogleMapCoordinateArray)
RCT_EXPORT_VIEW_PROPERTY(holes, AIRGoogleMapCoordinateArrayArray)
RCT_EXPORT_VIEW_PROPERTY(fillColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, double)
RCT_EXPORT_VIEW_PROPERTY(strokeColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(geodesic, BOOL)
RCT_EXPORT_VIEW_PROPERTY(zIndex, int)
RCT_EXPORT_VIEW_PROPERTY(tappable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
@end
#endif

View File

@@ -0,0 +1,36 @@
//
// AIRGoogleMapPolyline.h
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTBridge.h>
#import "AIRGMSPolyline.h"
#import "AIRGoogleMapMarker.h"
#import "AIRGoogleMapCoordinate.h"
@interface AIRGoogleMapPolyline : UIView
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, strong) AIRGMSPolyline *polyline;
@property (nonatomic, strong) NSArray<AIRGoogleMapCoordinate *> *coordinates;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@property (nonatomic, strong) GMSMapView *originalMap;
@property (nonatomic, strong) UIColor *strokeColor;
@property (nonatomic, strong) NSArray<UIColor *> *strokeColors;
@property (nonatomic, assign) double strokeWidth;
@property (nonatomic, strong) NSArray<NSNumber *> *lineDashPattern;
@property (nonatomic, assign) BOOL geodesic;
@property (nonatomic, assign) NSString *title;
@property (nonatomic, assign) int zIndex;
@property (nonatomic, assign) BOOL tappable;
@end
#endif

View File

@@ -0,0 +1,139 @@
//
// AIRGoogleMapPolyline.m
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <UIKit/UIKit.h>
#import "AIRGoogleMapPolyline.h"
#import "AIRGMSPolyline.h"
#import "AIRGoogleMapMarker.h"
#import "AIRGoogleMapMarkerManager.h"
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTUtils.h>
@implementation AIRGoogleMapPolyline
- (instancetype)init
{
if (self = [super init]) {
_polyline = [[AIRGMSPolyline alloc] init];
_polyline.strokeColor = _strokeColor;
}
return self;
}
-(void)setCoordinates:(NSArray<AIRGoogleMapCoordinate *> *)coordinates
{
_coordinates = coordinates;
GMSMutablePath *path = [GMSMutablePath path];
if (!coordinates || coordinates.count == 0)
{
[path removeAllCoordinates];
return;
}
for (int i = 0; i < coordinates.count; i++) {
[path addCoordinate:coordinates[i].coordinate];
}
_polyline.path = path;
[self configureStyleSpansIfNeeded];
}
-(void)setStrokeColor:(UIColor *)strokeColor
{
_strokeColor = strokeColor;
_polyline.strokeColor = strokeColor;
[self configureStyleSpansIfNeeded];
}
-(void)setStrokeColors:(NSArray<UIColor *> *)strokeColors
{
NSMutableArray *spans = [NSMutableArray arrayWithCapacity:[strokeColors count]];
for (int i = 0; i < [strokeColors count]; i++)
{
GMSStrokeStyle *stroke;
if (i == 0) {
stroke = [GMSStrokeStyle solidColor:strokeColors[i]];
} else {
stroke = [GMSStrokeStyle gradientFromColor:strokeColors[i-1] toColor:strokeColors[i]];
}
[spans addObject:[GMSStyleSpan spanWithStyle:stroke]];
}
_strokeColors = strokeColors;
_polyline.spans = spans;
}
-(void)setStrokeWidth:(double)strokeWidth
{
_strokeWidth = strokeWidth;
_polyline.strokeWidth = strokeWidth;
}
- (void)setFillColor:(UIColor *)fillColor {
// fillColor not support
}
- (void)setLineDashPattern:(NSArray<NSNumber *> *)lineDashPattern {
_lineDashPattern = lineDashPattern;
[self configureStyleSpansIfNeeded];
}
-(void)setGeodesic:(BOOL)geodesic
{
_geodesic = geodesic;
_polyline.geodesic = geodesic;
}
-(void)setTitle:(NSString *)title
{
_title = title;
_polyline.title = _title;
}
-(void) setZIndex:(int)zIndex
{
_zIndex = zIndex;
_polyline.zIndex = zIndex;
}
-(void)setTappable:(BOOL)tappable
{
_tappable = tappable;
_polyline.tappable = tappable;
}
- (void)setOnPress:(RCTBubblingEventBlock)onPress {
_polyline.onPress = onPress;
}
- (void)configureStyleSpansIfNeeded {
if (!_strokeColor || !_lineDashPattern || !_polyline.path) {
return;
}
BOOL isLine = YES;
NSMutableArray *styles = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < _lineDashPattern.count; i++) {
if (isLine) {
[styles addObject:[GMSStrokeStyle solidColor:_strokeColor]];
} else {
[styles addObject:[GMSStrokeStyle solidColor:[UIColor clearColor]]];
}
isLine = !isLine;
}
_polyline.spans = GMSStyleSpans(_polyline.path, styles, _lineDashPattern, kGMSLengthRhumb);
}
@end
#endif

View File

@@ -0,0 +1,15 @@
//
// AIRGoogleMapPolylineManager.h
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewManager.h>
@interface AIRGoogleMapPolylineManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,48 @@
//
// AIRGoogleMapPolylineManager.m
//
// Created by Nick Italiano on 10/22/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapPolylineManager.h"
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTConvert+CoreLocation.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTViewManager.h>
#import <React/UIView+React.h>
#import "AIRGoogleMapPolyline.h"
#import "RCTConvert+GMSMapViewType.h"
@interface AIRGoogleMapPolylineManager()
@end
@implementation AIRGoogleMapPolylineManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapPolyline *polyline = [AIRGoogleMapPolyline new];
polyline.bridge = self.bridge;
return polyline;
}
RCT_EXPORT_VIEW_PROPERTY(coordinates, AIRGoogleMapCoordinateArray)
RCT_EXPORT_VIEW_PROPERTY(fillColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(strokeColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(strokeColors, UIColorArray)
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, double)
RCT_EXPORT_VIEW_PROPERTY(lineDashPattern, NSArray)
RCT_EXPORT_VIEW_PROPERTY(geodesic, BOOL)
RCT_EXPORT_VIEW_PROPERTY(zIndex, int)
RCT_EXPORT_VIEW_PROPERTY(tappable, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
@end
#endif

View File

@@ -0,0 +1,33 @@
//
// AIRGoogleMapURLTileManager.m
// Created by Nick Italiano on 11/5/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapUrlTileManager.h"
#import "AIRGoogleMapUrlTile.h"
@interface AIRGoogleMapUrlTileManager()
@end
@implementation AIRGoogleMapUrlTileManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapUrlTile *tileLayer = [AIRGoogleMapUrlTile new];
return tileLayer;
}
RCT_EXPORT_VIEW_PROPERTY(urlTemplate, NSString)
RCT_EXPORT_VIEW_PROPERTY(zIndex, int)
RCT_EXPORT_VIEW_PROPERTY(maximumZ, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(minimumZ, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(flipY, BOOL)
@end
#endif

View File

@@ -0,0 +1,22 @@
//
// AIRGoogleMapURLTile.h
// Created by Nick Italiano on 11/5/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
@interface AIRGoogleMapUrlTile : UIView
@property (nonatomic, strong) GMSURLTileLayer *tileLayer;
@property (nonatomic, assign) NSString *urlTemplate;
@property (nonatomic, assign) int zIndex;
@property NSInteger *maximumZ;
@property NSInteger *minimumZ;
@property BOOL flipY;
@end
#endif

View File

@@ -0,0 +1,56 @@
//
// AIRGoogleMapURLTile.m
// Created by Nick Italiano on 11/5/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapUrlTile.h"
@implementation AIRGoogleMapUrlTile
- (void)setZIndex:(int)zIndex
{
_zIndex = zIndex;
_tileLayer.zIndex = zIndex;
}
- (void)setUrlTemplate:(NSString *)urlTemplate
{
_urlTemplate = urlTemplate;
_tileLayer = [GMSURLTileLayer tileLayerWithURLConstructor:[self _getTileURLConstructor]];
_tileLayer.tileSize = [[UIScreen mainScreen] scale] * 256;
}
- (GMSTileURLConstructor)_getTileURLConstructor
{
NSString *urlTemplate = self.urlTemplate;
NSInteger *maximumZ = self.maximumZ;
NSInteger *minimumZ = self.minimumZ;
GMSTileURLConstructor urls = ^NSURL* _Nullable (NSUInteger x, NSUInteger y, NSUInteger zoom) {
if (self.flipY == YES) {
y = (1 << zoom) - y - 1;
}
NSString *url = urlTemplate;
url = [url stringByReplacingOccurrencesOfString:@"{x}" withString:[NSString stringWithFormat: @"%ld", (long)x]];
url = [url stringByReplacingOccurrencesOfString:@"{y}" withString:[NSString stringWithFormat: @"%ld", (long)y]];
url = [url stringByReplacingOccurrencesOfString:@"{z}" withString:[NSString stringWithFormat: @"%ld", (long)zoom]];
if(maximumZ && (long)zoom > (long)maximumZ) {
return nil;
}
if(minimumZ && (long)zoom < (long)minimumZ) {
return nil;
}
return [NSURL URLWithString:url];
};
return urls;
}
@end
#endif

View File

@@ -0,0 +1,14 @@
//
// AIRGoogleMapURLTileManager.h
// Created by Nick Italiano on 11/5/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import <React/RCTViewManager.h>
@interface AIRGoogleMapUrlTileManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,33 @@
//
// AIRGoogleMapWMSTile.h
// AirMaps
//
// Created by nizam on 10/28/18.
// Copyright © 2018. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
@interface WMSTileOverlay : GMSSyncTileLayer
@property (nonatomic) double MapX,MapY,FULL;
@property (nonatomic, assign) NSString *urlTemplate;
@property (nonatomic, assign) NSInteger maximumZ;
@property (nonatomic, assign) NSInteger minimumZ;
@end
@interface AIRGoogleMapWMSTile : UIView
@property (nonatomic, strong) WMSTileOverlay *tileLayer;
@property (nonatomic, assign) NSString *urlTemplate;
@property (nonatomic, assign) int zIndex;
@property (nonatomic, assign) NSInteger maximumZ;
@property (nonatomic, assign) NSInteger minimumZ;
@property (nonatomic, assign) NSInteger tileSize;
@property (nonatomic, assign) float opacity;
@end
#endif

View File

@@ -0,0 +1,125 @@
//
// AIRGoogleMapWMSTile.m
// AirMaps
//
// Created by nizam on 10/28/18.
// Copyright © 2018. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapWMSTile.h"
@implementation AIRGoogleMapWMSTile
-(id) init
{
self = [super init];
_opacity = 1;
return self ;
}
- (void)setZIndex:(int)zIndex
{
_zIndex = zIndex;
_tileLayer.zIndex = zIndex;
}
- (void)setTileSize:(NSInteger)tileSize
{
_tileSize = tileSize;
if(self.tileLayer) {
self.tileLayer.tileSize = tileSize;
[self.tileLayer clearTileCache];
}
}
- (void)setMinimumZ:(NSInteger)minimumZ
{
_minimumZ = minimumZ;
if(self.tileLayer && _minimumZ) {
[self.tileLayer setMinimumZ: _minimumZ ];
[self.tileLayer clearTileCache];
}
}
- (void)setMaximumZ:(NSInteger)maximumZ
{
_maximumZ = maximumZ;
if(self.tileLayer && maximumZ) {
[self.tileLayer setMaximumZ: _maximumZ ];
[self.tileLayer clearTileCache];
}
}
- (void)setOpacity:(float)opacity
{
_opacity = opacity;
if(self.tileLayer ) {
[self.tileLayer setOpacity:opacity];
[self.tileLayer clearTileCache];
}
}
- (void)setUrlTemplate:(NSString *)urlTemplate
{
_urlTemplate = urlTemplate;
WMSTileOverlay *tile = [[WMSTileOverlay alloc] init];
[tile setUrlTemplate:urlTemplate];
[tile setMaximumZ: _maximumZ];
[tile setMinimumZ: _minimumZ];
[tile setOpacity: _opacity];
[tile setTileSize: _tileSize];
[tile setZIndex: _zIndex];
_tileLayer = tile;
}
@end
@implementation WMSTileOverlay
-(id) init
{
self = [super init];
_MapX = -20037508.34789244;
_MapY = 20037508.34789244;
_FULL = 20037508.34789244 * 2;
return self ;
}
-(NSArray *)getBoundBox:(NSInteger)x yAxis:(NSInteger)y zoom:(NSInteger)zoom
{
double tile = _FULL / pow(2.0, (double)zoom);
NSArray *result =[[NSArray alloc] initWithObjects:
[NSNumber numberWithDouble:_MapX + (double)x * tile ],
[NSNumber numberWithDouble:_MapY - (double)(y+1) * tile ],
[NSNumber numberWithDouble:_MapX + (double)(x+1) * tile ],
[NSNumber numberWithDouble:_MapY - (double)y * tile ],
nil];
return result;
}
- (UIImage *)tileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom
{
NSInteger maximumZ = self.maximumZ;
NSInteger minimumZ = self.minimumZ;
if(maximumZ && (long)zoom > (long)maximumZ) {
return nil;
}
if(minimumZ && (long)zoom < (long)minimumZ) {
return nil;
}
NSArray *bb = [self getBoundBox:x yAxis:y zoom:zoom];
NSMutableString *url = [self.urlTemplate mutableCopy];
[url replaceOccurrencesOfString: @"{minX}" withString:[NSString stringWithFormat:@"%@", bb[0]] options:0 range:NSMakeRange(0, url.length)];
[url replaceOccurrencesOfString: @"{minY}" withString:[NSString stringWithFormat:@"%@", bb[1]] options:0 range:NSMakeRange(0, url.length)];
[url replaceOccurrencesOfString: @"{maxX}" withString:[NSString stringWithFormat:@"%@", bb[2]] options:0 range:NSMakeRange(0, url.length)];
[url replaceOccurrencesOfString: @"{maxY}" withString:[NSString stringWithFormat:@"%@", bb[3]] options:0 range:NSMakeRange(0, url.length)];
[url replaceOccurrencesOfString: @"{width}" withString:[NSString stringWithFormat:@"%d", (int)self.tileSize] options:0 range:NSMakeRange(0, url.length)];
[url replaceOccurrencesOfString: @"{height}" withString:[NSString stringWithFormat:@"%d", (int)self.tileSize] options:0 range:NSMakeRange(0, url.length)];
NSURL *uri = [NSURL URLWithString:url];
NSData *data = [NSData dataWithContentsOfURL:uri];
UIImage *img = [[UIImage alloc] initWithData:data];
return img;
}
@end
#endif

View File

@@ -0,0 +1,17 @@
//
// AIRGoogleMapWMSTileManager.h
// AirMaps
//
// Created by nizam on 10/28/18.
// Copyright © 2018. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import <React/RCTViewManager.h>
@interface AIRGoogleMapWMSTileManager : RCTViewManager
@end
#endif

View File

@@ -0,0 +1,37 @@
//
// AIRGoogleMapWMSTileManager.m
// AirMaps
//
// Created by nizam on 10/28/18.
// Copyright © 2018. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import "AIRGoogleMapWMSTileManager.h"
#import "AIRGoogleMapWMSTile.h"
@interface AIRGoogleMapWMSTileManager()
@end
@implementation AIRGoogleMapWMSTileManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
AIRGoogleMapWMSTile *tileLayer = [AIRGoogleMapWMSTile new];
return tileLayer;
}
RCT_EXPORT_VIEW_PROPERTY(urlTemplate, NSString)
RCT_EXPORT_VIEW_PROPERTY(zIndex, int)
RCT_EXPORT_VIEW_PROPERTY(maximumZ, int)
RCT_EXPORT_VIEW_PROPERTY(minimumZ, int)
RCT_EXPORT_VIEW_PROPERTY(tileSize, int)
RCT_EXPORT_VIEW_PROPERTY(opacity, float)
@end
#endif

View File

@@ -0,0 +1,20 @@
//
// RCTConvert+GMSMapViewType.h
//
// Created by Nick Italiano on 10/23/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import <Foundation/Foundation.h>
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTConvert.h>
#import "AIRGoogleMapCoordinate.h"
@interface RCTConvert (GMSMapViewType)
+ (GMSCameraPosition*)GMSCameraPositionWithDefaults:(id)json existingCamera:(GMSCameraPosition*)existingCamera;
+ (NSArray<NSArray<AIRGoogleMapCoordinate *> *> *)AIRGoogleMapCoordinateArrayArray:(id)json;
+ (AIRGoogleMapCoordinate *)AIRGoogleMapCoordinate:(id)json;
@end
#endif

View File

@@ -0,0 +1,97 @@
//
// RCTConvert+GMSMapViewType.m
//
// Created by Nick Italiano on 10/23/16.
//
#ifdef HAVE_GOOGLE_MAPS
#import "RCTConvert+GMSMapViewType.h"
#import <GoogleMaps/GoogleMaps.h>
#import <React/RCTConvert.h>
#import <React/RCTConvert+CoreLocation.h>
#import "AIRGoogleMapCoordinate.h"
@implementation RCTConvert (GMSMapViewType)
RCT_ENUM_CONVERTER(GMSMapViewType,
(
@{
@"standard": @(kGMSTypeNormal),
@"satellite": @(kGMSTypeSatellite),
@"hybrid": @(kGMSTypeHybrid),
@"terrain": @(kGMSTypeTerrain),
@"none": @(kGMSTypeNone)
}
), kGMSTypeTerrain, intValue)
+ (GMSCameraPosition*)GMSCameraPosition:(id)json
{
json = [self NSDictionary:json];
return [RCTConvert GMSCameraPositionWithDefaults:json existingCamera:nil];
}
+ (AIRGoogleMapCoordinate *)AIRGoogleMapCoordinate:(id)json
{
// empty coordinates
if ([json isKindOfClass:[NSArray class]] && [json count] == 0) return nil;
AIRGoogleMapCoordinate *coord = [AIRGoogleMapCoordinate new];
coord.coordinate = [self CLLocationCoordinate2D:json];
return coord;
}
RCT_ARRAY_CONVERTER(AIRGoogleMapCoordinate)
+ (NSArray<NSArray<AIRGoogleMapCoordinate *> *> *)AIRGoogleMapCoordinateArrayArray:(id)json
{
return RCTConvertArrayValue(@selector(AIRGoogleMapCoordinate:), json);
}
+ (GMSCameraPosition*)GMSCameraPositionWithDefaults:(id)json existingCamera:(GMSCameraPosition*)existingCamera
{
CLLocationDegrees latitude = 0;
CLLocationDegrees longitude = 0;
double viewingAngle = 0;
double zoom = 0;
double bearing = 0;
if (existingCamera != nil) {
viewingAngle = existingCamera.viewingAngle;
latitude = existingCamera.target.latitude;
longitude = existingCamera.target.longitude;
zoom = existingCamera.zoom;
bearing = existingCamera.bearing;
}
if (json[@"center"]) {
CLLocationCoordinate2D target = [self CLLocationCoordinate2D:json[@"center"]];
latitude = target.latitude;
longitude = target.longitude;
}
if (json[@"pitch"]) {
viewingAngle = [self double:json[@"pitch"]];
}
// zoomAtCoordinate:forMeters:perPoints is offered by the SDK, which would allow
// us to support the "altitude" property of the camera as an alternative to "zoom".
// However, I am not clear on what the "perPoints" argument does...
if (json[@"zoom"]) {
zoom = [self double:json[@"zoom"]];
}
if (json[@"heading"]) {
bearing = [self double:json[@"heading"]];
}
return [GMSCameraPosition cameraWithLatitude:latitude
longitude:longitude
zoom:zoom
bearing:bearing
viewingAngle:viewingAngle];
}
@end
#endif

View File

@@ -0,0 +1,29 @@
//
// RNMapsGoogleMapView.h
//
//
// Created by Salah Ghanim on 23.11.24.
// Copyright © 2024 react-native-maps. All rights reserved.
//
#ifdef RCT_NEW_ARCH_ENABLED
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
#if __has_include(<ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>)
#import <ReactNativeMaps/generated/RNMapsHostViewDelegate.h>
#else
#import "RNMapsHostViewDelegate.h"
#endif
@class AIRGoogleMap;
NS_ASSUME_NONNULL_BEGIN
@interface RNMapsGoogleMapView : RCTViewComponentView<RNMapsHostViewDelegate>
@end
NS_ASSUME_NONNULL_END
#endif
#endif

View File

@@ -0,0 +1,692 @@
//
// RNMapsGoogleMapView.mm
//
//
// Created by Salah Ghanim on 23.11.24.
// Copyright © 2024 react-native-maps. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import "RNMapsGoogleMapView.h"
#import "RNMapsGooglePolygonView.h"
#import "AIRGoogleMap.h"
#import "AIRGoogleMapManager.h"
#if __has_include(<ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>)
#import <ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>
#import <ReactNativeMaps/generated/RNMapsHostViewDelegate.h>
#import <ReactNativeMaps/generated/ComponentDescriptors.h>
#import <ReactNativeMaps/generated/EventEmitters.h>
#import <ReactNativeMaps/generated/Props.h>
#import <ReactNativeMaps/generated/RCTComponentViewHelpers.h>
#else
#import "../generated/RNMapsAirModuleDelegate.h"
#import "../generated/RNMapsSpecs/ComponentDescriptors.h"
#import "../generated/RNMapsSpecs/EventEmitters.h"
#import "../generated/RNMapsSpecs/Props.h"
#import "../generated/RNMapsSpecs/RCTComponentViewHelpers.h"
#endif
#import "RCTFabricComponentsPlugins.h"
#import <React/RCTConversions.h>
#import <React/RCTUtils.h>
#import "RCTConvert+GMSMapViewType.h"
#if __has_include(<ReactNativeMaps/RCTConvert+AirMap.h>)
#import <ReactNativeMaps/RCTConvert+AirMap.h>
#import <ReactNativeMaps/UIView+AirMap.h>
#else
#import "RCTConvert+AirMap.h"
#import "UIView+AirMap.h"
#endif
using namespace facebook::react;
@interface RNMapsGoogleMapView () <RCTRNMapsGoogleMapViewViewProtocol>
@end
@implementation RNMapsGoogleMapView {
AIRGoogleMap *_view;
AIRGoogleMapManager* _legacyMapManager;
NSMutableDictionary<NSNumber*, UIView*>* _pendingInsertsSubviews;
NSMutableArray *_polygons;
}
- (id<RNMapsAirModuleDelegate>) mapView {
return (id<RNMapsAirModuleDelegate>)_view;
}
#pragma mark - JS Commands
- (void)animateToRegion:(NSString *)regionJSON duration:(NSInteger)duration{
NSDictionary* regionDic = [RCTConvert dictonaryFromString:regionJSON];
MKCoordinateRegion region = [RCTConvert MKCoordinateRegion:regionDic];
GMSCameraPosition *camera = [AIRGoogleMap makeGMSCameraPositionFromMap:_view andMKCoordinateRegion:region];
if (duration == 0){
[_view setRegion:region];
} else {
[_view animateToCameraPosition:camera];
}
}
- (void)setCamera:(NSString *)cameraJSON{
NSDictionary* cameraDic = [RCTConvert dictonaryFromString:cameraJSON];
GMSCameraPosition* camera = [RCTConvert GMSCameraPositionWithDefaults:cameraDic existingCamera:[_view camera]];
[_view setCamera:camera];
}
- (void)animateCamera:(NSString *)cameraJSON duration:(NSInteger)duration{
NSDictionary* cameraDic = [RCTConvert dictonaryFromString:cameraJSON];
GMSCameraPosition* camera = [RCTConvert GMSCameraPositionWithDefaults:cameraDic existingCamera:[_view camera]];
[_view animateToCameraPosition:camera];
}
- (void)fitToElements:(NSString *)edgePaddingJSON animated:(BOOL)animated {
NSDictionary* edgePadding = [RCTConvert dictonaryFromString:edgePaddingJSON];
[_view fitToElementsWithEdgePadding:edgePadding animated:animated];
}
- (void)fitToSuppliedMarkers:(NSString *)markersJSON edgePaddingJSON:(NSString *)edgePaddingJSON animated:(BOOL)animated {
NSArray* markers = [RCTConvert arrayFromString:markersJSON];
NSDictionary* edgePadding = [RCTConvert dictonaryFromString:edgePaddingJSON];
[_view fitToSuppliedMarkers:markers withEdgePadding:edgePadding animated:animated];
}
- (void)fitToCoordinates:(NSString *)coordinatesJSON edgePaddingJSON:(NSString *)edgePaddingJSON animated:(BOOL)animated {
NSArray* coordinatesArr = [RCTConvert arrayFromString:coordinatesJSON];
NSMutableArray<AIRGoogleMapCoordinate*>* coordinatesArray = [NSMutableArray new];
for (id json : coordinatesArr){
[coordinatesArray addObject:[RCTConvert AIRGoogleMapCoordinate:json]];
}
NSDictionary* edgePadding = [RCTConvert dictonaryFromString:edgePaddingJSON];
[_view fitToCoordinates:coordinatesArray withEdgePadding:edgePadding animated:animated];
}
- (void) setIndoorActiveLevelIndex:(NSInteger)activeLevelIndex
{
if (!_view.indoorDisplay) {
return;
}
if ( activeLevelIndex < [_view.indoorDisplay.activeBuilding.levels count]) {
_view.indoorDisplay.activeLevel = _view.indoorDisplay.activeBuilding.levels[activeLevelIndex];
}
}
#pragma mark - Native commands
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTRNMapsGoogleMapViewHandleCommand(self, commandName, args);
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNMapsGoogleMapViewComponentDescriptor>();
}
- (void) loadPendingInsertSubviews
{
if ([_pendingInsertsSubviews count] > 0){
NSArray<NSNumber *> *sortedKeys = [[_pendingInsertsSubviews allKeys] sortedArrayUsingSelector:@selector(compare:)];
for (NSNumber *key in sortedKeys) {
// Get the corresponding view
UIView *view = [_pendingInsertsSubviews objectForKey:key];
[_view insertReactSubview:view atIndex:[key integerValue]];
// Remove the entry from the dictionary
[_pendingInsertsSubviews removeObjectForKey:key];
}
}
}
- (void) prepareContentView {
_view = (AIRGoogleMap *)[_legacyMapManager view];
_polygons = [NSMutableArray new];
self.contentView = _view;
_view.onPress = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
// Extract values from the NSDictionary
NSDictionary* coordinateDict = dictionary[@"coordinate"];
NSDictionary* positionDict = dictionary[@"position"];
// Populate the OnMapPressCoordinate struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPressCoordinate coordinate = {
.latitude = [coordinateDict[@"latitude"] doubleValue],
.longitude = [coordinateDict[@"longitude"] doubleValue],
};
// Populate the OnMapPressPosition struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPressPosition position = {
.x = [positionDict[@"x"] doubleValue],
.y = [positionDict[@"y"] doubleValue],
};
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPress data = {
.action = std::string([@"press" UTF8String]),
.position = position,
.coordinate = coordinate
};
mapViewEventEmitter->onPress(data);
}
};
_view.onLongPress = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
// Extract values from the NSDictionary
NSDictionary* coordinateDict = dictionary[@"coordinate"];
NSDictionary* positionDict = dictionary[@"position"];
// Populate the OnMapPressCoordinate struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnLongPressCoordinate coordinate = {
.latitude = [coordinateDict[@"latitude"] doubleValue],
.longitude = [coordinateDict[@"longitude"] doubleValue],
};
// Populate the OnMapPressPosition struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnLongPressPosition position = {
.x = [positionDict[@"x"] doubleValue],
.y = [positionDict[@"y"] doubleValue],
};
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnLongPress data = {
.action = std::string([@"press" UTF8String]),
.position = position,
.coordinate = coordinate
};
mapViewEventEmitter->onLongPress(data);
}
};
_view.onMapReady = [self](NSDictionary* dictionary) {
[self loadPendingInsertSubviews];
if (_eventEmitter) {
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnMapReady data = {};
mapViewEventEmitter->onMapReady(data);
}
};
_view.onIndoorLevelActivated = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnIndoorLevelActivated data = {
.activeLevelIndex= (int) [dictionary[@"activeLevelIndex"] integerValue],
.name = [dictionary[@"name"] UTF8String],
.shortName = [dictionary[@"shortName"] UTF8String],
};
mapViewEventEmitter->onIndoorLevelActivated(data);
}
};
_view.onIndoorBuildingFocused = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
NSError *jsError = nil;
NSString *levelsStr = RCTJSONStringify(dictionary[@"levels"], &jsError);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnIndoorBuildingFocused data = {
.underground = [dictionary[@"underground"] boolValue],
.activeLevelIndex = (int) [dictionary[@"activeLevelIndex"] integerValue],
.levels = [levelsStr UTF8String]
};
mapViewEventEmitter->onIndoorBuildingFocused(data);
}
};
_view.onMapLoaded = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnMapLoaded data = {};
mapViewEventEmitter->onMapLoaded(data);
}
};
_view.onKmlReady = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnKmlReady data = {};
mapViewEventEmitter->onKmlReady(data);
}
};
_view.onRegionChange = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* regionDict = dictionary[@"region"];
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnRegionChange data = {
.region.latitude = [regionDict[@"latitude"] doubleValue],
.region.longitude = [regionDict[@"longitude"] doubleValue],
.region.latitudeDelta = [regionDict[@"latitudeDelta"] doubleValue],
.region.longitudeDelta = [regionDict[@"longitudeDelta"] doubleValue],
.isGesture = [dictionary[@"isGesture"] boolValue],
};
mapViewEventEmitter->onRegionChange(data);
}
};
_view.onPanDrag = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* coordinateDict = dictionary[@"coordinate"];
NSDictionary* positionDict = dictionary[@"position"];
// Populate the OnMapPressCoordinate struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPanDragCoordinate coordinate = {
.latitude = [coordinateDict[@"latitude"] doubleValue],
.longitude = [coordinateDict[@"longitude"] doubleValue],
};
// Populate the OnMapDouplePressPosition struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPanDragPosition position = {
.x = [positionDict[@"x"] doubleValue],
.y = [positionDict[@"y"] doubleValue],
};
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPanDrag data = {
.position = position,
.coordinate = coordinate,
};
mapViewEventEmitter->onPanDrag(data);
}
};
_view.onUserLocationChange = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* coordinateDict = dictionary[@"coordinate"];
NSDictionary* errorDict = dictionary[@"error"];
// Populate the OnMapPressCoordinate struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnUserLocationChangeCoordinate coordinate = {
.latitude = [coordinateDict[@"latitude"] doubleValue],
.longitude = [coordinateDict[@"longitude"] doubleValue],
};
NSString* str = @"";
if (errorDict){
str = errorDict[@"message"];
}
facebook::react::RNMapsGoogleMapViewEventEmitter::OnUserLocationChangeError error = {
.message = std::string([str UTF8String]),
};
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnUserLocationChange data = {
.coordinate = coordinate,
.error = error,
};
mapViewEventEmitter->onUserLocationChange(data);
}
};
_view.onPoiClick = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* coordinateDict = dictionary[@"coordinate"];
NSDictionary* positionDict = dictionary[@"position"];
// Populate the OnMapPressCoordinate struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPoiClickCoordinate coordinate = {
.latitude = [coordinateDict[@"latitude"] doubleValue],
.longitude = [coordinateDict[@"longitude"] doubleValue],
};
// Populate the OnMapDouplePressPosition struct
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPoiClickPosition position = {
.x = [positionDict[@"x"] doubleValue],
.y = [positionDict[@"y"] doubleValue],
};
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnPoiClick data = {
.coordinate = coordinate,
.position = position,
.placeId = [dictionary[@"placeId"] UTF8String],
.name = [dictionary[@"name"] UTF8String],
};
mapViewEventEmitter->onPoiClick(data);
}
};
_view.onRegionChangeStart = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* regionDict = dictionary[@"region"];
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnRegionChangeStart data = {
.region.latitude = [regionDict[@"latitude"] doubleValue],
.region.longitude = [regionDict[@"longitude"] doubleValue],
.region.latitudeDelta = [regionDict[@"latitudeDelta"] doubleValue],
.region.longitudeDelta = [regionDict[@"longitudeDelta"] doubleValue],
.isGesture = [dictionary[@"isGesture"] boolValue],
};
mapViewEventEmitter->onRegionChangeStart(data);
}
};
_view.onRegionChangeComplete = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
NSDictionary* regionDict = dictionary[@"region"];
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGoogleMapViewEventEmitter::OnRegionChangeComplete data = {
.region.latitude = [regionDict[@"latitude"] doubleValue],
.region.longitude = [regionDict[@"longitude"] doubleValue],
.region.latitudeDelta = [regionDict[@"latitudeDelta"] doubleValue],
.region.longitudeDelta = [regionDict[@"longitudeDelta"] doubleValue],
.isGesture = [dictionary[@"isGesture"] boolValue],
};
mapViewEventEmitter->onRegionChangeComplete(data);
}
};
#define HANDLE_MARKER_DRAG_EVENT(eventName, emitterFunction) \
if (_eventEmitter) { \
NSDictionary* coordinateDict = dictionary[@"coordinate"]; \
auto mapViewEventEmitter = \
std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter); \
facebook::react::RNMapsGoogleMapViewEventEmitter::eventName data = { \
.coordinate.latitude = [coordinateDict[@"latitude"] doubleValue], \
.coordinate.longitude = [coordinateDict[@"longitude"] doubleValue], \
.id = std::string([[dictionary valueForKey:@"id"] UTF8String]), \
}; \
mapViewEventEmitter->emitterFunction(data); \
}
#define HANDLE_MARKER_EVENT(eventName, emitterFunction, actionName) \
if (_eventEmitter) { \
NSDictionary* coordinateDict = dictionary[@"coordinate"]; \
facebook::react::RNMapsGoogleMapViewEventEmitter::eventName##Coordinate coordinate = { \
.latitude = [coordinateDict[@"latitude"] doubleValue], \
.longitude = [coordinateDict[@"longitude"] doubleValue], \
}; \
\
auto mapViewEventEmitter = \
std::static_pointer_cast<RNMapsGoogleMapViewEventEmitter const>(_eventEmitter); \
facebook::react::RNMapsGoogleMapViewEventEmitter::eventName data = { \
.action = std::string([@actionName UTF8String]), \
.id = std::string([[dictionary valueForKey:@"id"] UTF8String]), \
.coordinate = coordinate \
}; \
mapViewEventEmitter->emitterFunction(data); \
}
_view.onMarkerSelect = [self](NSDictionary* dictionary) {
HANDLE_MARKER_EVENT(OnMarkerSelect, onMarkerSelect, "marker-select");
};
_view.onMarkerDeselect = [self](NSDictionary* dictionary) {
HANDLE_MARKER_EVENT(OnMarkerDeselect, onMarkerDeselect, "marker-deselect");
};
_view.onMarkerPress = [self](NSDictionary* dictionary) {
HANDLE_MARKER_EVENT(OnMarkerPress, onMarkerPress, "marker-press");
};
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RNMapsGoogleMapViewProps>();
_props = defaultProps;
_legacyMapManager = [[AIRGoogleMapManager alloc] init];
_pendingInsertsSubviews = [NSMutableDictionary new];
}
return self;
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index{
id<RCTComponent> paperView = [childComponentView getPaperViewFromChildComponentView];
if (paperView){
if(_view && [_view isReady]){
[_view insertReactSubview:paperView atIndex:index];
} else {
[_pendingInsertsSubviews setObject:paperView forKey:[NSNumber numberWithInteger:index]];
}
} else {
[_view insertReactSubview:childComponentView atIndex:index];
if ([childComponentView isKindOfClass:[RNMapsGooglePolygonView class]]){
RNMapsGooglePolygonView* polygon = (RNMapsGooglePolygonView *) childComponentView;
[_polygons addObject:childComponentView];
[polygon didInsertInMap:_view];
}
}
}
- (void) unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
id<RCTComponent> paperView = [childComponentView getPaperViewFromChildComponentView];
if (paperView){
if(_view && [_view isReady]){
[_view removeReactSubview:paperView];
} else {
[_pendingInsertsSubviews removeObjectForKey:[NSNumber numberWithInteger:index]];
}
} else {
[_view removeReactSubview:childComponentView];
if ([childComponentView isKindOfClass:[RNMapsGooglePolygonView class]]){
RNMapsGooglePolygonView* polygon = (RNMapsGooglePolygonView *) childComponentView;
[_polygons removeObject:childComponentView];
[polygon didRemoveFromMap];
}
}
}
- (void) prepareForRecycle
{
[super prepareForRecycle];
static const auto defaultProps = std::make_shared<const RNMapsGoogleMapViewProps>();
_props = defaultProps;
_legacyMapManager = [[AIRGoogleMapManager alloc] init];
_pendingInsertsSubviews = [NSMutableDictionary new];
[_view removeFromSuperview];
_view = nil;
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<RNMapsGoogleMapViewProps const>(_props);
const auto &newViewProps = *std::static_pointer_cast<RNMapsGoogleMapViewProps const>(props);
if (oldViewProps.googleMapId != newViewProps.googleMapId) {
NSString* mapID = RCTNSStringFromString(newViewProps.googleMapId);
if (mapID && [mapID length]){
_legacyMapManager.googleMapId = mapID;
}
}
if (oldViewProps.customMapStyleString != newViewProps.customMapStyleString) {
NSString* customStyle = RCTNSStringFromString(newViewProps.customMapStyleString);
if (customStyle && [customStyle length]){
_legacyMapManager.customMapStyle = customStyle;
}
}
if (!newViewProps.zoomTapEnabled) {
_legacyMapManager.zoomTapEnabled = newViewProps.zoomTapEnabled;
} else {
_legacyMapManager.zoomTapEnabled = true;
}
if (oldViewProps.loadingBackgroundColor != newViewProps.loadingBackgroundColor){
_legacyMapManager.backgroundColor = RCTUIColorFromSharedColor(newViewProps.backgroundColor);
}
// bug with zoom / pitch / heading where value is not 0 even though
// nothing is passed from JS so we depend on lat / lng comparison for now
if (newViewProps.initialCamera.center.latitude != oldViewProps.initialCamera.center.latitude ||
newViewProps.initialCamera.center.longitude != oldViewProps.initialCamera.center.longitude ||
newViewProps.initialCamera.pitch != oldViewProps.initialCamera.pitch ||
newViewProps.initialCamera.zoom != oldViewProps.initialCamera.zoom
|| newViewProps.initialCamera.heading != oldViewProps.initialCamera.heading) {
GMSCameraPosition* camera = [GMSCameraPosition cameraWithLatitude:newViewProps.initialCamera.center.latitude
longitude:newViewProps.initialCamera.center.longitude
zoom:newViewProps.initialCamera.zoom
bearing:newViewProps.initialCamera.heading
viewingAngle:newViewProps.initialCamera.pitch];
_legacyMapManager.camera = camera;
}
if (!_view){
[self prepareContentView];
}
#define REMAP_MAPVIEW_PROP(name) \
if (oldViewProps.name != newViewProps.name) { \
_view.name = newViewProps.name; \
}
#define REMAP_MAPVIEW_STRING_PROP(name) \
if (oldViewProps.name != newViewProps.name) { \
_view.name = RCTNSStringFromString(newViewProps.name); \
}
#define REMAP_MAPVIEW_POINT_PROP(name) \
if (newViewProps.name.x != oldViewProps.name.x || \
newViewProps.name.y != oldViewProps.name.y) { \
_view.name = CGPointMake(newViewProps.name.x, newViewProps.name.y); \
}
#define REMAP_MAPVIEW_REGION_PROP(name) \
if (newViewProps.name.latitude != oldViewProps.name.latitude || \
newViewProps.name.longitude != oldViewProps.name.longitude || \
newViewProps.name.latitudeDelta != oldViewProps.name.latitudeDelta ||\
newViewProps.name.longitudeDelta != oldViewProps.name.longitudeDelta) { \
MKCoordinateSpan span = MKCoordinateSpanMake(newViewProps.name.latitudeDelta, \
newViewProps.name.longitudeDelta); \
MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake( \
newViewProps.name.latitude, newViewProps.name.longitude), span); \
_view.name = region; \
}
#define REMAP_MAPVIEW_EDGEINSETS_PROP(name) \
if (newViewProps.name.top != oldViewProps.name.top || \
newViewProps.name.right != oldViewProps.name.right || \
newViewProps.name.bottom != oldViewProps.name.bottom || \
newViewProps.name.left != oldViewProps.name.left) { \
_view.name = UIEdgeInsetsMake(newViewProps.name.top, \
newViewProps.name.left, \
newViewProps.name.bottom, \
newViewProps.name.right); \
}
REMAP_MAPVIEW_PROP(pitchEnabled)
REMAP_MAPVIEW_PROP(zoomTapEnabled)
REMAP_MAPVIEW_PROP(scrollEnabled)
REMAP_MAPVIEW_STRING_PROP(kmlSrc)
REMAP_MAPVIEW_STRING_PROP(customMapStyleString)
REMAP_MAPVIEW_PROP(showsBuildings)
REMAP_MAPVIEW_PROP(rotateEnabled)
if (newViewProps.minZoom != oldViewProps.minZoom || newViewProps.maxZoom != oldViewProps.maxZoom){
[_view setMinZoom:newViewProps.minZoom maxZoom:newViewProps.maxZoom];
}
REMAP_MAPVIEW_PROP(showsCompass)
REMAP_MAPVIEW_PROP(showsMyLocationButton)
REMAP_MAPVIEW_PROP(showsTraffic)
REMAP_MAPVIEW_PROP(showsUserLocation)
REMAP_MAPVIEW_PROP(zoomEnabled)
REMAP_MAPVIEW_REGION_PROP(region)
REMAP_MAPVIEW_REGION_PROP(initialRegion)
if (newViewProps.camera.center.latitude != oldViewProps.camera.center.latitude ||
newViewProps.camera.center.longitude != oldViewProps.camera.center.longitude ||
newViewProps.camera.heading != oldViewProps.camera.heading ||
newViewProps.camera.pitch != oldViewProps.camera.pitch ||
newViewProps.camera.zoom != oldViewProps.camera.zoom) {
GMSCameraPosition* camera = [GMSCameraPosition cameraWithLatitude:newViewProps.camera.center.latitude
longitude:newViewProps.camera.center.longitude
zoom:newViewProps.camera.zoom
bearing:newViewProps.camera.heading
viewingAngle:newViewProps.camera.pitch];
_view.cameraProp = camera;
}
REMAP_MAPVIEW_EDGEINSETS_PROP(mapPadding)
if (oldViewProps.mapType != newViewProps.mapType){
switch (newViewProps.mapType) {
case RNMapsGoogleMapViewMapType::Standard:
_view.mapType = kGMSTypeNormal;
break;
case RNMapsGoogleMapViewMapType::Satellite:
_view.mapType = kGMSTypeSatellite;
break;
case RNMapsGoogleMapViewMapType::Terrain:
_view.mapType = kGMSTypeTerrain;
break;
case RNMapsGoogleMapViewMapType::Hybrid:
_view.mapType = kGMSTypeHybrid;
break;
case RNMapsGoogleMapViewMapType::None:
_view.mapType = kGMSTypeNone;
break;
default:
_view.mapType = kGMSTypeNormal;
break;
}
}
if (oldViewProps.userInterfaceStyle != newViewProps.userInterfaceStyle){
switch (newViewProps.userInterfaceStyle) {
case RNMapsGoogleMapViewUserInterfaceStyle::Light:
_view.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
break;
case RNMapsGoogleMapViewUserInterfaceStyle::Dark:
_view.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
break;
case RNMapsGoogleMapViewUserInterfaceStyle::System:
_view.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified;
break;
}
}
if (oldViewProps.paddingAdjustmentBehavior != newViewProps.paddingAdjustmentBehavior){
switch (newViewProps.paddingAdjustmentBehavior) {
case RNMapsGoogleMapViewPaddingAdjustmentBehavior::Never:
_view.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever;
break;
case RNMapsGoogleMapViewPaddingAdjustmentBehavior::Automatic:
_view.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorAutomatic;
break;
case RNMapsGoogleMapViewPaddingAdjustmentBehavior::Always:
_view.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorAlways;
break;
}
}
[super updateProps:props oldProps:oldProps];
}
@end
Class<RCTComponentViewProtocol> RNMapsGoogleMapViewCls(void)
{
return RNMapsGoogleMapView.class;
}
#endif

View File

@@ -0,0 +1,40 @@
//
// RNMapsMapViewManager.mm
// AirMaps
//
// Created by Salah Ghanim on 23.11.24.
// Copyright © 2024 react-native-maps. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTLog.h>
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>
@interface RNMapsGoogleMapViewManager : RCTViewManager
@end
@implementation RNMapsGoogleMapViewManager
RCT_EXPORT_MODULE(RNMapsGoogleMapViewManager)
RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMapLoaded, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onKmlReady, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPanDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onUserLocationChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMarkerPress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMarkerSelect, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMarkerDeselect, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChangeStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRegionChangeComplete, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPoiClick, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onIndoorLevelActivated, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onIndoorBuildingFocused, RCTDirectEventBlock)
@end
#endif

View File

@@ -0,0 +1,26 @@
//
// RNMapsGoogleMapView.h
//
//
// Created by Salah Ghanim on 23.11.24.
// Copyright © 2024 react-native-maps. All rights reserved.
//
#ifdef RCT_NEW_ARCH_ENABLED
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTViewComponentView.h>
#import <UIKit/UIKit.h>
@class AIRGoogleMap;
@class AIRGMSPolygon;
NS_ASSUME_NONNULL_BEGIN
@interface RNMapsGooglePolygonView : RCTViewComponentView
- (void) didInsertInMap:(AIRGoogleMap*) map;
- (void) didRemoveFromMap;
@end
NS_ASSUME_NONNULL_END
#endif
#endif

View File

@@ -0,0 +1,226 @@
//
// RNMapsGoogleMapView.mm
//
//
// Created by Salah Ghanim on 23.11.24.
// Copyright © 2024 react-native-maps. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import "RNMapsGooglePolygonView.h"
#import "AIRGMSPolygon.h"
#import "AIRGoogleMap.h"
#if __has_include(<ReactNativeMaps/generated/RNMapsAirModuleDelegate.h>)
#import <ReactNativeMaps/generated/RNMapsSpecs.h>
#import <ReactNativeMaps/generated/RNMapsHostViewDelegate.h>
#import <ReactNativeMaps/generated/ComponentDescriptors.h>
#import <ReactNativeMaps/generated/EventEmitters.h>
#import <ReactNativeMaps/generated/Props.h>
#import <ReactNativeMaps/generated/RCTComponentViewHelpers.h>
#else
#import "../generated/RNMapsSpecs/RNMapsSpecs.h"
#import "../generated/RNMapsSpecs/ComponentDescriptors.h"
#import "../generated/RNMapsSpecs/EventEmitters.h"
#import "../generated/RNMapsSpecs/Props.h"
#import "../generated/RNMapsSpecs/RCTComponentViewHelpers.h"
#endif
#import "RCTFabricComponentsPlugins.h"
#import <React/RCTConversions.h>
#import "RCTConvert+GMSMapViewType.h"
#import "RCTConvert+AirMap.h"
#import "UIView+AirMap.h"
using namespace facebook::react;
bool areHolesEqual(const std::vector<std::vector<RNMapsGooglePolygonHolesStruct>>& newHoles, const std::vector<std::vector<RNMapsGooglePolygonHolesStruct>>& oldHoles) {
// First-level check: compare the sizes of the top-level vectors
if (newHoles.size() != oldHoles.size()) {
return false;
}
// Second-level check: compare the contents of each child vector
for (size_t i = 0; i < newHoles.size(); ++i) {
const auto& newHole = newHoles.at(i);
const auto& oldHole = oldHoles.at(i);
// Compare sizes of the child vectors
if (newHole.size() != oldHole.size()) {
return false;
}
}
return true;
}
@interface RNMapsGooglePolygonView () <RCTRNMapsGooglePolygonViewProtocol>
@end
@implementation RNMapsGooglePolygonView {
AIRGMSPolygon *_view;
}
- (AIRGMSPolygon*) polygon {
return _view;
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNMapsGooglePolygonComponentDescriptor>();
}
- (void) didInsertInMap:(AIRGoogleMap*) map
{
_view.map = map;
}
-(void) didRemoveFromMap
{
_view.map = nil;
_view = nil;
}
- (void) prepareContentView {
_view = [AIRGMSPolygon new];
_view.onPress = [self](NSDictionary* dictionary) {
if (_eventEmitter) {
// Extract values from the NSDictionary
NSDictionary* coordinateDict = dictionary[@"coordinate"];
NSDictionary* positionDict = dictionary[@"position"];
// Populate the OnMapPressCoordinate struct
facebook::react::RNMapsGooglePolygonEventEmitter::OnPressCoordinate coordinate = {
.latitude = [coordinateDict[@"latitude"] doubleValue],
.longitude = [coordinateDict[@"longitude"] doubleValue],
};
// Populate the OnMapPressPosition struct
facebook::react::RNMapsGooglePolygonEventEmitter::OnPressPosition position = {
.x = [positionDict[@"x"] doubleValue],
.y = [positionDict[@"y"] doubleValue],
};
auto mapViewEventEmitter = std::static_pointer_cast<RNMapsGooglePolygonEventEmitter const>(_eventEmitter);
facebook::react::RNMapsGooglePolygonEventEmitter::OnPress data = {
.action = std::string([@"press" UTF8String]),
.position = position,
.coordinate = coordinate
};
mapViewEventEmitter->onPress(data);
}
};
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RNMapsGooglePolygonProps>();
_props = defaultProps;
[self prepareContentView];
}
return self;
}
- (void) prepareForRecycle
{
[super prepareForRecycle];
static const auto defaultProps = std::make_shared<const RNMapsGooglePolygonProps>();
_props = defaultProps;
_view = nil;
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<RNMapsGooglePolygonProps const>(_props);
const auto &newViewProps = *std::static_pointer_cast<RNMapsGooglePolygonProps const>(props);
if (!_view){
[self prepareContentView];
}
#define REMAP_MAPVIEW_PROP(name) \
if (oldViewProps.name != newViewProps.name) { \
_view.name = newViewProps.name; \
}
#define REMAP_MAPVIEW_STRING_PROP(name) \
if (oldViewProps.name != newViewProps.name) { \
_view.name = RCTNSStringFromString(newViewProps.name); \
}
#define REMAP_MAPVIEW_POINT_PROP(name) \
if (newViewProps.name.x != oldViewProps.name.x || \
newViewProps.name.y != oldViewProps.name.y) { \
_view.name = CGPointMake(newViewProps.name.x, newViewProps.name.y); \
}
if(newViewProps.zIndex.has_value()){
_view.zIndex = newViewProps.zIndex.value();
}
if (newViewProps.coordinates.size() != oldViewProps.coordinates.size()){
GMSMutablePath *path = [GMSMutablePath path];
for(int i = 0; i < newViewProps.coordinates.size(); i++)
{
CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(
newViewProps.coordinates.at(i).latitude,newViewProps.coordinates.at(i).longitude);
[path addCoordinate:coordinates];
}
_view.path = path;
}
if (!areHolesEqual(newViewProps.holes, oldViewProps.holes)){
NSMutableArray<GMSMutablePath *> *interiorPolygons = [NSMutableArray array];
for(int h = 0; h < newViewProps.holes.size(); h++)
{
GMSMutablePath *path = [GMSMutablePath path];
for(int i = 0; i < newViewProps.holes[h].size(); i++)
{
CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(
newViewProps.holes.at(h).at(i).latitude,
newViewProps.holes.at(h).at(i).longitude);
[path addCoordinate:coordinates];
}
[interiorPolygons addObject:path];
}
_view.holes = interiorPolygons;
}
REMAP_MAPVIEW_PROP(tappable)
if (newViewProps.fillColor){
_view.fillColor = RCTUIColorFromSharedColor(newViewProps.fillColor);
}
if (newViewProps.strokeColor){
_view.strokeColor = RCTUIColorFromSharedColor(newViewProps.strokeColor);
}
if (newViewProps.strokeWidth != oldViewProps.strokeWidth){
_view.strokeWidth = newViewProps.strokeWidth;
}
[super updateProps:props oldProps:oldProps];
}
- (void) didMoveToSuperview {
[super didMoveToSuperview];
NSLog(@"SuperView ? %@", self.superview);
}
@end
Class<RCTComponentViewProtocol> RNMapsGooglePolygonCls(void)
{
return RNMapsGooglePolygonView.class;
}
#endif

View File

@@ -0,0 +1,26 @@
//
// RNMapsMapViewManager.mm
// AirMaps
//
// Created by Salah Ghanim on 23.11.24.
// Copyright © 2024 react-native-maps. All rights reserved.
//
#ifdef HAVE_GOOGLE_MAPS
#import <React/RCTLog.h>
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>
@interface RNMapsGooglePolygonViewManager : RCTViewManager
@end
@implementation RNMapsGooglePolygonViewManager
RCT_EXPORT_MODULE(RNMapsGooglePolygonViewManager)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
@end
#endif

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeCrashData</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeDeviceID</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypePerformanceData</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeProductInteraction</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeUserID</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>85F4.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
</array>
</dict>
</plist>