From 9cbd84364efb1e535f8703b993c8f040bde50790 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Sat, 21 Feb 2026 04:54:46 +0800 Subject: [PATCH 1/6] feat: add Polygon component for iOS, Android, and Web --- .../src/main/java/com/luggmaps/LuggMapView.kt | 3 + .../src/main/java/com/luggmaps/LuggPackage.kt | 2 +- .../main/java/com/luggmaps/LuggPolygonView.kt | 65 +++++++++ .../com/luggmaps/LuggPolygonViewManager.kt | 71 ++++++++++ .../com/luggmaps/core/GoogleMapProvider.kt | 69 ++++++++++ .../com/luggmaps/core/MapProviderDelegate.kt | 3 + ios/LuggMapView.mm | 13 ++ ios/LuggPolygonView.h | 27 ++++ ios/LuggPolygonView.mm | 121 +++++++++++++++++ ios/core/AppleMapProvider.mm | 126 +++++++++++++++++- ios/core/GoogleMapProvider.mm | 83 +++++++++++- ios/core/MapProviderDelegate.h | 4 + src/components/Polygon.tsx | 64 +++++++++ src/components/Polygon.web.tsx | 58 ++++++++ src/components/index.ts | 2 + src/components/index.web.ts | 2 + src/fabric/LuggPolygonViewNativeComponent.ts | 19 +++ src/index.ts | 2 + src/index.web.ts | 2 + 19 files changed, 732 insertions(+), 4 deletions(-) create mode 100644 android/src/main/java/com/luggmaps/LuggPolygonView.kt create mode 100644 android/src/main/java/com/luggmaps/LuggPolygonViewManager.kt create mode 100644 ios/LuggPolygonView.h create mode 100644 ios/LuggPolygonView.mm create mode 100644 src/components/Polygon.tsx create mode 100644 src/components/Polygon.web.tsx create mode 100644 src/fabric/LuggPolygonViewNativeComponent.ts diff --git a/android/src/main/java/com/luggmaps/LuggMapView.kt b/android/src/main/java/com/luggmaps/LuggMapView.kt index 6c00049..777c98e 100644 --- a/android/src/main/java/com/luggmaps/LuggMapView.kt +++ b/android/src/main/java/com/luggmaps/LuggMapView.kt @@ -63,6 +63,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) : is LuggMapWrapperView -> mapWrapperView = child is LuggMarkerView -> provider?.addMarkerView(child) is LuggPolylineView -> provider?.addPolylineView(child) + is LuggPolygonView -> provider?.addPolygonView(child) } } @@ -71,6 +72,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) : when (view) { is LuggMarkerView -> provider?.removeMarkerView(view) is LuggPolylineView -> provider?.removePolylineView(view) + is LuggPolygonView -> provider?.removePolygonView(view) } super.removeViewAt(index) } @@ -118,6 +120,7 @@ class LuggMapView(private val reactContext: ThemedReactContext) : when (val child = getChildAt(i)) { is LuggMarkerView -> google.addMarkerView(child) is LuggPolylineView -> google.addPolylineView(child) + is LuggPolygonView -> google.addPolygonView(child) } } } diff --git a/android/src/main/java/com/luggmaps/LuggPackage.kt b/android/src/main/java/com/luggmaps/LuggPackage.kt index 4ecfa3d..4598a42 100644 --- a/android/src/main/java/com/luggmaps/LuggPackage.kt +++ b/android/src/main/java/com/luggmaps/LuggPackage.kt @@ -6,5 +6,5 @@ import com.facebook.react.uimanager.ViewManager class LuggPackage : ReactPackage { override fun createViewManagers(reactContext: ReactApplicationContext): List> = - listOf(LuggMapViewManager(), LuggMarkerViewManager(), LuggMapWrapperViewManager(), LuggPolylineViewManager()) + listOf(LuggMapViewManager(), LuggMarkerViewManager(), LuggMapWrapperViewManager(), LuggPolylineViewManager(), LuggPolygonViewManager()) } diff --git a/android/src/main/java/com/luggmaps/LuggPolygonView.kt b/android/src/main/java/com/luggmaps/LuggPolygonView.kt new file mode 100644 index 0000000..ac12e87 --- /dev/null +++ b/android/src/main/java/com/luggmaps/LuggPolygonView.kt @@ -0,0 +1,65 @@ +package com.luggmaps + +import android.content.Context +import android.graphics.Color +import com.facebook.react.views.view.ReactViewGroup +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polygon + +interface LuggPolygonViewDelegate { + fun polygonViewDidUpdate(polygonView: LuggPolygonView) +} + +class LuggPolygonView(context: Context) : ReactViewGroup(context) { + var coordinates: List = emptyList() + private set + + var strokeColor: Int = Color.BLACK + private set + + var fillColor: Int = Color.argb(77, 0, 0, 0) + private set + + var strokeWidth: Float = 1f + private set + + var zIndex: Float = 0f + private set + + var delegate: LuggPolygonViewDelegate? = null + var polygon: Polygon? = null + + init { + visibility = GONE + } + + fun setCoordinates(coords: List) { + coordinates = coords + } + + fun setStrokeColor(color: Int) { + strokeColor = color + } + + fun setFillColor(color: Int) { + fillColor = color + } + + fun setStrokeWidth(width: Float) { + strokeWidth = width + } + + fun setZIndex(value: Float) { + zIndex = value + } + + fun onAfterUpdateTransaction() { + delegate?.polygonViewDidUpdate(this) + } + + fun onDropViewInstance() { + delegate = null + polygon?.remove() + polygon = null + } +} diff --git a/android/src/main/java/com/luggmaps/LuggPolygonViewManager.kt b/android/src/main/java/com/luggmaps/LuggPolygonViewManager.kt new file mode 100644 index 0000000..22553f3 --- /dev/null +++ b/android/src/main/java/com/luggmaps/LuggPolygonViewManager.kt @@ -0,0 +1,71 @@ +package com.luggmaps + +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.LuggPolygonViewManagerDelegate +import com.facebook.react.viewmanagers.LuggPolygonViewManagerInterface +import com.google.android.gms.maps.model.LatLng + +@ReactModule(name = LuggPolygonViewManager.NAME) +class LuggPolygonViewManager : + ViewGroupManager(), + LuggPolygonViewManagerInterface { + private val delegate: ViewManagerDelegate = LuggPolygonViewManagerDelegate(this) + + override fun getDelegate(): ViewManagerDelegate = delegate + override fun getName(): String = NAME + override fun createViewInstance(context: ThemedReactContext): LuggPolygonView = LuggPolygonView(context) + + override fun onDropViewInstance(view: LuggPolygonView) { + super.onDropViewInstance(view) + view.onDropViewInstance() + } + + override fun onAfterUpdateTransaction(view: LuggPolygonView) { + super.onAfterUpdateTransaction(view) + view.onAfterUpdateTransaction() + } + + @ReactProp(name = "coordinates") + override fun setCoordinates(view: LuggPolygonView, value: ReadableArray?) { + value?.let { array -> + val coords = mutableListOf() + for (i in 0 until array.size()) { + val coord = array.getMap(i) + val lat = coord?.getDouble("latitude") ?: 0.0 + val lng = coord?.getDouble("longitude") ?: 0.0 + coords.add(LatLng(lat, lng)) + } + view.setCoordinates(coords) + } + } + + @ReactProp(name = "strokeColor", customType = "Color") + override fun setStrokeColor(view: LuggPolygonView, value: Int?) { + view.setStrokeColor(value ?: android.graphics.Color.BLACK) + } + + @ReactProp(name = "fillColor", customType = "Color") + override fun setFillColor(view: LuggPolygonView, value: Int?) { + view.setFillColor(value ?: android.graphics.Color.argb(77, 0, 0, 0)) + } + + @ReactProp(name = "strokeWidth", defaultDouble = 1.0) + override fun setStrokeWidth(view: LuggPolygonView, value: Double) { + view.setStrokeWidth(value.toFloat()) + } + + @ReactProp(name = "zIndex", defaultFloat = 0f) + override fun setZIndex(view: LuggPolygonView, value: Float) { + super.setZIndex(view, value) + view.setZIndex(value) + } + + companion object { + const val NAME = "LuggPolygonView" + } +} diff --git a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt index 14c21e4..e7c465c 100644 --- a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +++ b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt @@ -14,9 +14,12 @@ import com.google.android.gms.maps.model.AdvancedMarker import com.google.android.gms.maps.model.AdvancedMarkerOptions import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MapColorScheme +import com.google.android.gms.maps.model.PolygonOptions import com.google.android.gms.maps.model.PolylineOptions import com.luggmaps.LuggMarkerView import com.luggmaps.LuggMarkerViewDelegate +import com.luggmaps.LuggPolygonView +import com.luggmaps.LuggPolygonViewDelegate import com.luggmaps.LuggPolylineView import com.luggmaps.LuggPolylineViewDelegate @@ -25,6 +28,7 @@ class GoogleMapProvider(private val context: Context) : OnMapReadyCallback, LuggMarkerViewDelegate, LuggPolylineViewDelegate, + LuggPolygonViewDelegate, GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraIdleListener { @@ -40,6 +44,7 @@ class GoogleMapProvider(private val context: Context) : private var isDragging = false private val pendingMarkerViews = mutableSetOf() private val pendingPolylineViews = mutableSetOf() + private val pendingPolygonViews = mutableSetOf() private val polylineAnimators = mutableMapOf() // Initial camera settings @@ -86,6 +91,7 @@ class GoogleMapProvider(private val context: Context) : override fun destroy() { pendingMarkerViews.clear() pendingPolylineViews.clear() + pendingPolygonViews.clear() polylineAnimators.values.forEach { it.destroy() } polylineAnimators.clear() googleMap?.setOnCameraMoveStartedListener(null) @@ -117,6 +123,7 @@ class GoogleMapProvider(private val context: Context) : applyUserLocation() processPendingMarkers() processPendingPolylines() + processPendingPolygons() delegate?.mapProviderDidReady() } @@ -270,6 +277,14 @@ class GoogleMapProvider(private val context: Context) : // endregion + // region PolygonViewDelegate + + override fun polygonViewDidUpdate(polygonView: LuggPolygonView) { + syncPolygonView(polygonView) + } + + // endregion + // region Marker Management override fun addMarkerView(markerView: LuggMarkerView) { @@ -404,6 +419,60 @@ class GoogleMapProvider(private val context: Context) : // endregion + // region Polygon Management + + override fun addPolygonView(polygonView: LuggPolygonView) { + polygonView.delegate = this + syncPolygonView(polygonView) + } + + override fun removePolygonView(polygonView: LuggPolygonView) { + polygonView.polygon?.remove() + polygonView.polygon = null + } + + private fun syncPolygonView(polygonView: LuggPolygonView) { + if (googleMap == null) { + pendingPolygonViews.add(polygonView) + return + } + + if (polygonView.polygon == null) { + addPolygonViewToMap(polygonView) + return + } + + polygonView.polygon?.apply { + points = polygonView.coordinates + fillColor = polygonView.fillColor + strokeColor = polygonView.strokeColor + strokeWidth = polygonView.strokeWidth.dpToPx() + zIndex = polygonView.zIndex + } + } + + private fun processPendingPolygons() { + if (googleMap == null) return + pendingPolygonViews.forEach { addPolygonViewToMap(it) } + pendingPolygonViews.clear() + } + + private fun addPolygonViewToMap(polygonView: LuggPolygonView) { + val map = googleMap ?: return + + val options = PolygonOptions() + .addAll(polygonView.coordinates) + .fillColor(polygonView.fillColor) + .strokeColor(polygonView.strokeColor) + .strokeWidth(polygonView.strokeWidth.dpToPx()) + .zIndex(polygonView.zIndex) + + val polygon = map.addPolygon(options) + polygonView.polygon = polygon + } + + // endregion + // region Lifecycle override fun pauseAnimations() { diff --git a/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt b/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt index 210a45e..17af711 100644 --- a/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +++ b/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt @@ -2,6 +2,7 @@ package com.luggmaps.core import android.view.View import com.luggmaps.LuggMarkerView +import com.luggmaps.LuggPolygonView import com.luggmaps.LuggPolylineView data class EdgeInsets(val top: Int = 0, val left: Int = 0, val bottom: Int = 0, val right: Int = 0) @@ -37,6 +38,8 @@ interface MapProvider { fun removeMarkerView(markerView: LuggMarkerView) fun addPolylineView(polylineView: LuggPolylineView) fun removePolylineView(polylineView: LuggPolylineView) + fun addPolygonView(polygonView: LuggPolygonView) + fun removePolygonView(polygonView: LuggPolygonView) // Lifecycle fun pauseAnimations() diff --git a/ios/LuggMapView.mm b/ios/LuggMapView.mm index aa4b035..108315b 100644 --- a/ios/LuggMapView.mm +++ b/ios/LuggMapView.mm @@ -1,6 +1,7 @@ #import "LuggMapView.h" #import "LuggMapWrapperView.h" #import "LuggMarkerView.h" +#import "LuggPolygonView.h" #import "LuggPolylineView.h" #import "core/AppleMapProvider.h" #import "core/GoogleMapProvider.h" @@ -83,6 +84,11 @@ - (void)mountChildComponentView: if (_provider) { [_provider addPolylineView:polylineView]; } + } else if ([childComponentView isKindOfClass:[LuggPolygonView class]]) { + LuggPolygonView *polygonView = (LuggPolygonView *)childComponentView; + if (_provider) { + [_provider addPolygonView:polygonView]; + } } } @@ -99,6 +105,11 @@ - (void)unmountChildComponentView: if (_provider) { [_provider removePolylineView:polylineView]; } + } else if ([childComponentView isKindOfClass:[LuggPolygonView class]]) { + LuggPolygonView *polygonView = (LuggPolygonView *)childComponentView; + if (_provider) { + [_provider removePolygonView:polygonView]; + } } [super unmountChildComponentView:childComponentView index:index]; @@ -164,6 +175,8 @@ - (void)initializeProvider { [_provider addMarkerView:(LuggMarkerView *)subview]; } else if ([subview isKindOfClass:[LuggPolylineView class]]) { [_provider addPolylineView:(LuggPolylineView *)subview]; + } else if ([subview isKindOfClass:[LuggPolygonView class]]) { + [_provider addPolygonView:(LuggPolygonView *)subview]; } } } diff --git a/ios/LuggPolygonView.h b/ios/LuggPolygonView.h new file mode 100644 index 0000000..f5d2748 --- /dev/null +++ b/ios/LuggPolygonView.h @@ -0,0 +1,27 @@ +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@class LuggPolygonView; + +@protocol LuggPolygonViewDelegate +@optional +- (void)polygonViewDidUpdate:(LuggPolygonView *)polygonView; +@end + +@interface LuggPolygonView : RCTViewComponentView + +@property(nonatomic, readonly) NSArray *coordinates; +@property(nonatomic, readonly) UIColor *strokeColor; +@property(nonatomic, readonly) UIColor *fillColor; +@property(nonatomic, readonly) CGFloat strokeWidth; +@property(nonatomic, readonly) NSInteger zIndex; +@property(nonatomic, weak, nullable) id delegate; +@property(nonatomic, strong, nullable) NSObject *polygon; +@property(nonatomic, weak, nullable) NSObject *renderer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/LuggPolygonView.mm b/ios/LuggPolygonView.mm new file mode 100644 index 0000000..cda012b --- /dev/null +++ b/ios/LuggPolygonView.mm @@ -0,0 +1,121 @@ +#import "LuggPolygonView.h" + +#import +#import +#import +#import + +#import "RCTFabricComponentsPlugins.h" +#import + +using namespace facebook::react; + +@interface LuggPolygonView () +@end + +@implementation LuggPolygonView { + NSArray *_coordinates; + UIColor *_strokeColor; + UIColor *_fillColor; + CGFloat _strokeWidth; + NSInteger _zIndex; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider { + return concreteComponentDescriptorProvider< + LuggPolygonViewComponentDescriptor>(); +} + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = + std::make_shared(); + _props = defaultProps; + + _coordinates = @[]; + _strokeColor = [UIColor blackColor]; + _fillColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.3]; + _strokeWidth = 1.0; + + self.hidden = YES; + } + + return self; +} + +- (void)updateProps:(Props::Shared const &)props + oldProps:(Props::Shared const &)oldProps { + [super updateProps:props oldProps:oldProps]; + const auto &newViewProps = + *std::static_pointer_cast(props); + + NSMutableArray *coords = [NSMutableArray array]; + for (const auto &coord : newViewProps.coordinates) { + CLLocation *location = + [[CLLocation alloc] initWithLatitude:coord.latitude + longitude:coord.longitude]; + [coords addObject:location]; + } + _coordinates = [coords copy]; + + if (newViewProps.strokeColor) { + UIColor *color = RCTUIColorFromSharedColor(newViewProps.strokeColor); + if (color) { + _strokeColor = color; + } + } + + if (newViewProps.fillColor) { + UIColor *color = RCTUIColorFromSharedColor(newViewProps.fillColor); + if (color) { + _fillColor = color; + } + } + + _strokeWidth = newViewProps.strokeWidth > 0 ? newViewProps.strokeWidth : 1.0; + _zIndex = newViewProps.zIndex.value_or(0); +} + +- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask { + [super finalizeUpdates:updateMask]; + + if (updateMask & RNComponentViewUpdateMaskProps) { + if ([self.delegate + respondsToSelector:@selector(polygonViewDidUpdate:)]) { + [self.delegate polygonViewDidUpdate:self]; + } + } +} + +- (NSArray *)coordinates { + return _coordinates; +} + +- (UIColor *)strokeColor { + return _strokeColor; +} + +- (UIColor *)fillColor { + return _fillColor; +} + +- (CGFloat)strokeWidth { + return _strokeWidth; +} + +- (NSInteger)zIndex { + return _zIndex; +} + +- (void)prepareForRecycle { + [super prepareForRecycle]; + self.polygon = nil; + self.renderer = nil; + self.delegate = nil; +} + +Class LuggPolygonViewCls(void) { + return LuggPolygonView.class; +} + +@end diff --git a/ios/core/AppleMapProvider.mm b/ios/core/AppleMapProvider.mm index e01d711..1f787ee 100644 --- a/ios/core/AppleMapProvider.mm +++ b/ios/core/AppleMapProvider.mm @@ -1,5 +1,6 @@ #import "AppleMapProvider.h" #import "../LuggMarkerView.h" +#import "../LuggPolygonView.h" #import "../LuggPolylineView.h" #import "../extensions/MKMapView+Zoom.h" #import "MKPolylineAnimator.h" @@ -19,7 +20,8 @@ @implementation LuggAppleMapViewContent @end @interface AppleMapProvider () + LuggPolylineViewDelegate, + LuggPolygonViewDelegate> @end @implementation AppleMapProvider { @@ -29,6 +31,7 @@ @implementation AppleMapProvider { double _minZoom; double _maxZoom; NSMapTable, LuggPolylineView *> *_overlayToPolylineMap; + NSMapTable, LuggPolygonView *> *_overlayToPolygonMap; // Edge insets animation CADisplayLink *_edgeInsetsDisplayLink; @@ -43,6 +46,7 @@ @implementation AppleMapProvider { - (instancetype)init { if (self = [super init]) { _overlayToPolylineMap = [NSMapTable strongToWeakObjectsMapTable]; + _overlayToPolygonMap = [NSMapTable strongToWeakObjectsMapTable]; } return self; } @@ -336,6 +340,23 @@ - (MKOverlayRenderer *)mapView:(MKMapView *)mapView [[MKPolylineRenderer alloc] initWithPolyline:polyline]; return renderer; } + + if ([overlay isKindOfClass:[MKPolygon class]]) { + LuggPolygonView *polygonView = + [_overlayToPolygonMap objectForKey:overlay]; + MKPolygon *polygon = (MKPolygon *)overlay; + + MKPolygonRenderer *renderer = + [[MKPolygonRenderer alloc] initWithPolygon:polygon]; + if (polygonView) { + renderer.fillColor = polygonView.fillColor; + renderer.strokeColor = polygonView.strokeColor; + renderer.lineWidth = polygonView.strokeWidth; + polygonView.renderer = renderer; + } + return renderer; + } + return nil; } @@ -375,6 +396,12 @@ - (void)polylineViewDidUpdate:(LuggPolylineView *)polylineView { [self syncPolylineView:polylineView]; } +#pragma mark - PolygonViewDelegate + +- (void)polygonViewDidUpdate:(LuggPolygonView *)polygonView { + [self syncPolygonView:polygonView]; +} + #pragma mark - Marker Management - (void)addMarkerView:(LuggMarkerView *)markerView { @@ -547,6 +574,91 @@ - (void)addPolylineOverlayToMap:(LuggPolylineView *)polylineView { [self insertOverlay:polyline withZIndex:polylineView.zIndex]; } +#pragma mark - Polygon Management + +- (void)addPolygonView:(LuggPolygonView *)polygonView { + polygonView.delegate = self; + [self addPolygonOverlayToMap:polygonView]; +} + +- (void)removePolygonView:(LuggPolygonView *)polygonView { + polygonView.delegate = nil; + MKPolygon *polygon = (MKPolygon *)polygonView.polygon; + if (polygon) { + [_overlayToPolygonMap removeObjectForKey:polygon]; + [_mapView removeOverlay:polygon]; + polygonView.polygon = nil; + } +} + +- (void)syncPolygonView:(LuggPolygonView *)polygonView { + if (!_mapView) + return; + + MKPolygon *oldPolygon = (MKPolygon *)polygonView.polygon; + + NSArray *coordinates = polygonView.coordinates; + if (coordinates.count == 0) { + if (oldPolygon) { + [_overlayToPolygonMap removeObjectForKey:oldPolygon]; + [_mapView removeOverlay:oldPolygon]; + polygonView.polygon = nil; + polygonView.renderer = nil; + } + return; + } + + CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc( + sizeof(CLLocationCoordinate2D) * coordinates.count); + for (NSUInteger i = 0; i < coordinates.count; i++) { + coords[i] = coordinates[i].coordinate; + } + MKPolygon *newPolygon = + [MKPolygon polygonWithCoordinates:coords count:coordinates.count]; + free(coords); + + polygonView.polygon = newPolygon; + [_overlayToPolygonMap setObject:polygonView forKey:newPolygon]; + + MKPolygonRenderer *renderer = (MKPolygonRenderer *)polygonView.renderer; + if (renderer && oldPolygon) { + [_overlayToPolygonMap removeObjectForKey:oldPolygon]; + [_mapView removeOverlay:oldPolygon]; + [self insertOverlay:newPolygon withZIndex:polygonView.zIndex]; + polygonView.renderer = nil; + return; + } + + if (oldPolygon) { + [_overlayToPolygonMap removeObjectForKey:oldPolygon]; + [_mapView removeOverlay:oldPolygon]; + } + [self insertOverlay:newPolygon withZIndex:polygonView.zIndex]; +} + +- (void)addPolygonOverlayToMap:(LuggPolygonView *)polygonView { + if (!_mapView) + return; + + NSArray *coordinates = polygonView.coordinates; + if (coordinates.count == 0) + return; + + CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc( + sizeof(CLLocationCoordinate2D) * coordinates.count); + for (NSUInteger i = 0; i < coordinates.count; i++) { + coords[i] = coordinates[i].coordinate; + } + + MKPolygon *polygon = [MKPolygon polygonWithCoordinates:coords + count:coordinates.count]; + free(coords); + + polygonView.polygon = polygon; + [_overlayToPolygonMap setObject:polygonView forKey:polygon]; + [self insertOverlay:polygon withZIndex:polygonView.zIndex]; +} + - (void)insertOverlay:(id)overlay withZIndex:(NSInteger)zIndex { if (zIndex == 0) { [_mapView addOverlay:overlay]; @@ -557,9 +669,19 @@ - (void)insertOverlay:(id)overlay withZIndex:(NSInteger)zIndex { NSInteger insertIndex = overlays.count; for (NSInteger i = 0; i < overlays.count; i++) { + NSInteger existingZIndex = 0; LuggPolylineView *existingPolylineView = [_overlayToPolylineMap objectForKey:overlays[i]]; - if (existingPolylineView && existingPolylineView.zIndex > zIndex) { + LuggPolygonView *existingPolygonView = + [_overlayToPolygonMap objectForKey:overlays[i]]; + if (existingPolylineView) { + existingZIndex = existingPolylineView.zIndex; + } else if (existingPolygonView) { + existingZIndex = existingPolygonView.zIndex; + } else { + continue; + } + if (existingZIndex > zIndex) { insertIndex = i; break; } diff --git a/ios/core/GoogleMapProvider.mm b/ios/core/GoogleMapProvider.mm index a0e19fe..8d8c9e8 100644 --- a/ios/core/GoogleMapProvider.mm +++ b/ios/core/GoogleMapProvider.mm @@ -1,5 +1,6 @@ #import "GoogleMapProvider.h" #import "../LuggMarkerView.h" +#import "../LuggPolygonView.h" #import "../LuggPolylineView.h" #import "GMSPolylineAnimator.h" #import "PolylineAnimatorBase.h" @@ -7,7 +8,8 @@ static NSString *const kDemoMapId = @"DEMO_MAP_ID"; @interface GoogleMapProvider () + LuggPolylineViewDelegate, + LuggPolygonViewDelegate> @end @implementation GoogleMapProvider { @@ -18,6 +20,7 @@ @implementation GoogleMapProvider { UIEdgeInsets _edgeInsets; NSMutableArray *_pendingMarkerViews; NSMutableArray *_pendingPolylineViews; + NSMutableArray *_pendingPolygonViews; NSMapTable *_polylineAnimators; // Edge insets animation @@ -36,6 +39,7 @@ - (instancetype)init { _mapId = kDemoMapId; _pendingMarkerViews = [NSMutableArray array]; _pendingPolylineViews = [NSMutableArray array]; + _pendingPolygonViews = [NSMutableArray array]; _polylineAnimators = [NSMapTable weakToStrongObjectsMapTable]; } return self; @@ -88,6 +92,7 @@ - (void)initializeMapInView:(UIView *)wrapperView _isMapReady = YES; [self processPendingMarkers]; [self processPendingPolylines]; + [self processPendingPolygons]; [_delegate mapProviderDidReady]; } @@ -96,6 +101,7 @@ - (void)destroy { [self stopEdgeInsetsAnimation]; [_pendingMarkerViews removeAllObjects]; [_pendingPolylineViews removeAllObjects]; + [_pendingPolygonViews removeAllObjects]; [_polylineAnimators removeAllObjects]; [_mapView clear]; [_mapView removeFromSuperview]; @@ -270,6 +276,12 @@ - (void)polylineViewDidUpdate:(LuggPolylineView *)polylineView { [self syncPolylineView:polylineView]; } +#pragma mark - PolygonViewDelegate + +- (void)polygonViewDidUpdate:(LuggPolygonView *)polygonView { + [self syncPolygonView:polygonView]; +} + #pragma mark - Marker Management - (void)addMarkerView:(LuggMarkerView *)markerView { @@ -438,6 +450,75 @@ - (void)addPolylineViewToMap:(LuggPolylineView *)polylineView { [_polylineAnimators setObject:animator forKey:polylineView]; } +#pragma mark - Polygon Management + +- (void)addPolygonView:(LuggPolygonView *)polygonView { + polygonView.delegate = self; + [self syncPolygonView:polygonView]; +} + +- (void)removePolygonView:(LuggPolygonView *)polygonView { + GMSPolygon *polygon = (GMSPolygon *)polygonView.polygon; + if (polygon) { + polygon.map = nil; + polygonView.polygon = nil; + } +} + +- (void)syncPolygonView:(LuggPolygonView *)polygonView { + if (!_mapView) { + if (![_pendingPolygonViews containsObject:polygonView]) { + [_pendingPolygonViews addObject:polygonView]; + } + return; + } + + if (!polygonView.polygon) { + [self addPolygonViewToMap:polygonView]; + return; + } + + GMSPolygon *polygon = (GMSPolygon *)polygonView.polygon; + + GMSMutablePath *path = [GMSMutablePath path]; + for (CLLocation *location in polygonView.coordinates) { + [path addCoordinate:location.coordinate]; + } + polygon.path = path; + polygon.fillColor = polygonView.fillColor; + polygon.strokeColor = polygonView.strokeColor; + polygon.strokeWidth = polygonView.strokeWidth; + polygon.zIndex = (int)polygonView.zIndex; +} + +- (void)processPendingPolygons { + if (!_mapView) + return; + + for (LuggPolygonView *polygonView in _pendingPolygonViews) { + [self addPolygonViewToMap:polygonView]; + } + [_pendingPolygonViews removeAllObjects]; +} + +- (void)addPolygonViewToMap:(LuggPolygonView *)polygonView { + if (!_mapView) + return; + + GMSMutablePath *path = [GMSMutablePath path]; + for (CLLocation *location in polygonView.coordinates) { + [path addCoordinate:location.coordinate]; + } + + GMSPolygon *polygon = [GMSPolygon polygonWithPath:path]; + polygon.fillColor = polygonView.fillColor; + polygon.strokeColor = polygonView.strokeColor; + polygon.strokeWidth = polygonView.strokeWidth; + polygon.zIndex = (int)polygonView.zIndex; + polygon.map = _mapView; + polygonView.polygon = polygon; +} + #pragma mark - Lifecycle - (void)pauseAnimations { diff --git a/ios/core/MapProviderDelegate.h b/ios/core/MapProviderDelegate.h index 9e571e0..3897f90 100644 --- a/ios/core/MapProviderDelegate.h +++ b/ios/core/MapProviderDelegate.h @@ -5,6 +5,7 @@ NS_ASSUME_NONNULL_BEGIN @class LuggMarkerView; @class LuggPolylineView; +@class LuggPolygonView; @protocol MapProviderDelegate - (void)mapProviderDidReady; @@ -50,6 +51,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)addPolylineView:(LuggPolylineView *)polylineView; - (void)removePolylineView:(LuggPolylineView *)polylineView; - (void)syncPolylineView:(LuggPolylineView *)polylineView; +- (void)addPolygonView:(LuggPolygonView *)polygonView; +- (void)removePolygonView:(LuggPolygonView *)polygonView; +- (void)syncPolygonView:(LuggPolygonView *)polygonView; // Lifecycle - (void)pauseAnimations; diff --git a/src/components/Polygon.tsx b/src/components/Polygon.tsx new file mode 100644 index 0000000..df286a1 --- /dev/null +++ b/src/components/Polygon.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import type { ColorValue } from 'react-native'; +import { StyleSheet } from 'react-native'; +import LuggPolygonViewNativeComponent from '../fabric/LuggPolygonViewNativeComponent'; +import type { Coordinate } from '../types'; + +export interface PolygonProps { + /** + * Array of coordinates forming the polygon boundary + */ + coordinates: Coordinate[]; + /** + * Stroke (outline) color + */ + strokeColor?: ColorValue; + /** + * Stroke width in points + */ + strokeWidth?: number; + /** + * Fill color of the polygon + */ + fillColor?: ColorValue; + /** + * Z-index for layering + */ + zIndex?: number; +} + +export class Polygon extends React.PureComponent { + private _cachedZIndex: number | undefined; + private _cachedStyle: any; + + private getStyle(zIndex: number | undefined) { + if (zIndex == null) return styles.polygon; + if (zIndex !== this._cachedZIndex) { + this._cachedZIndex = zIndex; + this._cachedStyle = [{ zIndex }, styles.polygon]; + } + return this._cachedStyle!; + } + + render() { + const { coordinates, strokeColor, strokeWidth, fillColor, zIndex } = + this.props; + + return ( + + ); + } +} + +const styles = StyleSheet.create({ + polygon: { + position: 'absolute', + pointerEvents: 'none', + }, +}); diff --git a/src/components/Polygon.web.tsx b/src/components/Polygon.web.tsx new file mode 100644 index 0000000..38c309b --- /dev/null +++ b/src/components/Polygon.web.tsx @@ -0,0 +1,58 @@ +import { useEffect, useRef } from 'react'; +import { useMapContext } from '../MapProvider.web'; +import type { PolygonProps } from './Polygon'; + +export function Polygon({ + coordinates, + strokeColor = '#000000', + strokeWidth = 1, + fillColor = 'rgba(0, 0, 0, 0.3)', + zIndex = 0, +}: PolygonProps) { + const { map } = useMapContext(); + const polygonRef = useRef(null); + + // Cleanup on unmount + useEffect(() => { + return () => { + polygonRef.current?.setMap(null); + polygonRef.current = null; + }; + }, []); + + // Sync polygon with props + useEffect(() => { + if (!map || coordinates.length === 0) { + polygonRef.current?.setMap(null); + return; + } + + const path = coordinates.map((c) => ({ + lat: c.latitude, + lng: c.longitude, + })); + + if (polygonRef.current) { + polygonRef.current.setPath(path); + polygonRef.current.setOptions({ + strokeColor: strokeColor as string, + strokeWeight: strokeWidth, + fillColor: fillColor as string, + zIndex, + }); + } else { + polygonRef.current = new google.maps.Polygon({ + paths: path, + strokeColor: strokeColor as string, + strokeWeight: strokeWidth, + strokeOpacity: 1, + fillColor: fillColor as string, + fillOpacity: 1, + zIndex, + map, + }); + } + }, [map, coordinates, strokeColor, strokeWidth, fillColor, zIndex]); + + return null; +} diff --git a/src/components/index.ts b/src/components/index.ts index 9b8eae3..1ab2158 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,5 +1,7 @@ export { Marker } from './Marker'; export type { MarkerProps } from './Marker'; +export { Polygon } from './Polygon'; +export type { PolygonProps } from './Polygon'; export { Polyline } from './Polyline'; export type { PolylineProps, diff --git a/src/components/index.web.ts b/src/components/index.web.ts index f3f7b0e..8583c34 100644 --- a/src/components/index.web.ts +++ b/src/components/index.web.ts @@ -1,4 +1,6 @@ export { Marker } from './Marker.web'; +export { Polygon } from './Polygon.web'; export { Polyline } from './Polyline.web'; export type { MarkerProps } from './Marker'; +export type { PolygonProps } from './Polygon'; export type { PolylineProps } from './Polyline'; diff --git a/src/fabric/LuggPolygonViewNativeComponent.ts b/src/fabric/LuggPolygonViewNativeComponent.ts new file mode 100644 index 0000000..6b9433d --- /dev/null +++ b/src/fabric/LuggPolygonViewNativeComponent.ts @@ -0,0 +1,19 @@ +import { codegenNativeComponent } from 'react-native'; +import type { ViewProps, HostComponent, ColorValue } from 'react-native'; +import type { Double } from 'react-native/Libraries/Types/CodegenTypes'; + +export interface Coordinate { + latitude: Double; + longitude: Double; +} + +export interface NativeProps extends ViewProps { + coordinates: ReadonlyArray; + strokeColor?: ColorValue; + strokeWidth?: Double; + fillColor?: ColorValue; +} + +export default codegenNativeComponent( + 'LuggPolygonView' +) as HostComponent; diff --git a/src/index.ts b/src/index.ts index d2ea408..f05b7d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ export { MapProvider } from './MapProvider'; export type { MapProviderProps } from './MapProvider.types'; export { Marker } from './components'; export type { MarkerProps } from './components'; +export { Polygon } from './components'; +export type { PolygonProps } from './components'; export { Polyline } from './components'; export type { PolylineProps, diff --git a/src/index.web.ts b/src/index.web.ts index cbbba6c..d6e87bb 100644 --- a/src/index.web.ts +++ b/src/index.web.ts @@ -3,6 +3,8 @@ export { MapProvider } from './MapProvider.web'; export type { MapProviderProps } from './MapProvider.types'; export { Marker } from './components/index.web'; export type { MarkerProps } from './components/index.web'; +export { Polygon } from './components/index.web'; +export type { PolygonProps } from './components/index.web'; export { Polyline } from './components/index.web'; export type { PolylineProps } from './components/index.web'; export type { From 6ae9a9681e0e5da04c2a0a45edb3d4809d69347b Mon Sep 17 00:00:00 2001 From: lodev09 Date: Sat, 21 Feb 2026 04:57:00 +0800 Subject: [PATCH 2/6] feat: add Polygon example to Map component --- example/shared/src/components/Map.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/example/shared/src/components/Map.tsx b/example/shared/src/components/Map.tsx index 1e55229..73cb7f3 100644 --- a/example/shared/src/components/Map.tsx +++ b/example/shared/src/components/Map.tsx @@ -3,6 +3,7 @@ import { StyleSheet, View, useWindowDimensions } from 'react-native'; import { MapView, Marker, + Polygon, type MapViewProps, type CameraEventPayload, } from '@lugg/maps'; @@ -140,6 +141,17 @@ export const Map = forwardRef( {markers.map(renderMarker)} + Date: Sat, 21 Feb 2026 17:14:08 +0800 Subject: [PATCH 3/6] fix(ios): register LuggPolygonView in codegen componentProvider Missing entry in codegenConfig.ios.componentProvider prevented Fabric from instantiating the native polygon view on iOS. Also update polygon example to render a circle. --- example/bare/ios/Podfile.lock | 4 ++-- example/shared/src/components/Map.tsx | 22 +++++++++++++++------- package.json | 3 ++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/example/bare/ios/Podfile.lock b/example/bare/ios/Podfile.lock index 0c75b36..9315d4a 100644 --- a/example/bare/ios/Podfile.lock +++ b/example/bare/ios/Podfile.lock @@ -11,7 +11,7 @@ PODS: - hermes-engine (0.14.0): - hermes-engine/Pre-built (= 0.14.0) - hermes-engine/Pre-built (0.14.0) - - LuggMaps (0.2.0-alpha.27): + - LuggMaps (0.2.0-alpha.30): - boost - DoubleConversion - fast_float @@ -3050,7 +3050,7 @@ SPEC CHECKSUMS: glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 GoogleMaps: 0608099d4870cac8754bdba9b6953db543432438 hermes-engine: 3515eff1a2de44b79dfa94a03d1adeed40f0dafe - LuggMaps: 52b43077d9f8ca87c7c9c22771aa61fd93df5ae3 + LuggMaps: 9fadfbfebeead30c7c578722bbb306e433fce8ed RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: 2b70c6e3abe00396cefd8913efbf6a2db01a2b36 RCTRequired: f3540eee8094231581d40c5c6d41b0f170237a81 diff --git a/example/shared/src/components/Map.tsx b/example/shared/src/components/Map.tsx index 73cb7f3..b612fa6 100644 --- a/example/shared/src/components/Map.tsx +++ b/example/shared/src/components/Map.tsx @@ -27,6 +27,19 @@ interface MapProps extends MapViewProps { const INITIAL_ZOOM = 14; +const CIRCLE_CENTER = { latitude: 37.78, longitude: -122.43 }; +const CIRCLE_RADIUS = 0.003; +const CIRCLE_COORDS = Array.from({ length: 36 }, (_, i) => { + const angle = (i * 10 * Math.PI) / 180; + return { + latitude: CIRCLE_CENTER.latitude + CIRCLE_RADIUS * Math.cos(angle), + longitude: + CIRCLE_CENTER.longitude + + (CIRCLE_RADIUS * Math.sin(angle)) / + Math.cos((CIRCLE_CENTER.latitude * Math.PI) / 180), + }; +}); + const renderMarker = (marker: MarkerData) => { const { id, @@ -142,13 +155,8 @@ export const Map = forwardRef( diff --git a/package.json b/package.json index 361fc13..105887d 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,8 @@ "LuggMapView": "LuggMapView", "LuggMarkerView": "LuggMarkerView", "LuggMapWrapperView": "LuggMapWrapperView", - "LuggPolylineView": "LuggPolylineView" + "LuggPolylineView": "LuggPolylineView", + "LuggPolygonView": "LuggPolygonView" } } }, From ac06d7aed67e054852e73e9f2bd28c6610e7c82c Mon Sep 17 00:00:00 2001 From: lodev09 Date: Sat, 21 Feb 2026 17:15:57 +0800 Subject: [PATCH 4/6] docs: add Polygon documentation --- README.md | 14 +++++++++++++- docs/POLYGON.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 docs/POLYGON.md diff --git a/README.md b/README.md index 1c18d24..4d3fd3c 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ function App() { ## Usage ```tsx -import { MapView, Marker, Polyline } from '@lugg/maps'; +import { MapView, Marker, Polyline, Polygon } from '@lugg/maps'; + ``` @@ -104,6 +115,7 @@ import { MapView, Marker, Polyline } from '@lugg/maps'; - [MapView](docs/MAPVIEW.md) - Main map component - [Marker](docs/MARKER.md) - Map markers - [Polyline](docs/POLYLINE.md) - Draw lines on the map +- [Polygon](docs/POLYGON.md) - Draw filled shapes on the map ## Types diff --git a/docs/POLYGON.md b/docs/POLYGON.md new file mode 100644 index 0000000..54fad23 --- /dev/null +++ b/docs/POLYGON.md @@ -0,0 +1,34 @@ +# Polygon + +Polygon component for drawing filled shapes on the map. + +## Usage + +```tsx +import { MapView, Polygon } from '@lugg/maps'; + + + {/* Simple polygon */} + + +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `coordinates` | `Coordinate[]` | **required** | Array of coordinates forming the polygon boundary | +| `fillColor` | `ColorValue` | - | Fill color of the polygon | +| `strokeColor` | `ColorValue` | - | Stroke (outline) color | +| `strokeWidth` | `number` | - | Stroke width in points | +| `zIndex` | `number` | - | Z-index for layering | From ee90164eb5f4201f81c8def8af182702c81e7b00 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Sat, 21 Feb 2026 17:29:34 +0800 Subject: [PATCH 5/6] feat: add onPress event to Polygon component --- .../main/java/com/luggmaps/LuggPolygonView.kt | 6 ++ .../com/luggmaps/core/GoogleMapProvider.kt | 15 ++++- .../com/luggmaps/events/PolygonPressEvent.kt | 12 ++++ docs/POLYGON.md | 2 + example/shared/src/Home.tsx | 45 ++++++--------- example/shared/src/components/Map.tsx | 3 + ios/LuggPolygonView.h | 2 + ios/LuggPolygonView.mm | 6 ++ ios/core/AppleMapProvider.mm | 57 +++++++++++++++++++ ios/core/GoogleMapProvider.mm | 14 +++++ ios/events/PolygonPressEvent.h | 20 +++++++ src/components/Polygon.tsx | 7 ++- src/components/Polygon.web.tsx | 41 ++++++++++++- src/fabric/LuggPolygonViewNativeComponent.ts | 6 +- 14 files changed, 203 insertions(+), 33 deletions(-) create mode 100644 android/src/main/java/com/luggmaps/events/PolygonPressEvent.kt create mode 100644 ios/events/PolygonPressEvent.h diff --git a/android/src/main/java/com/luggmaps/LuggPolygonView.kt b/android/src/main/java/com/luggmaps/LuggPolygonView.kt index ac12e87..631f452 100644 --- a/android/src/main/java/com/luggmaps/LuggPolygonView.kt +++ b/android/src/main/java/com/luggmaps/LuggPolygonView.kt @@ -5,6 +5,8 @@ import android.graphics.Color import com.facebook.react.views.view.ReactViewGroup import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Polygon +import com.luggmaps.events.PolygonPressEvent +import com.luggmaps.extensions.dispatchEvent interface LuggPolygonViewDelegate { fun polygonViewDidUpdate(polygonView: LuggPolygonView) @@ -53,6 +55,10 @@ class LuggPolygonView(context: Context) : ReactViewGroup(context) { zIndex = value } + fun emitPressEvent() { + dispatchEvent(PolygonPressEvent(this)) + } + fun onAfterUpdateTransaction() { delegate?.polygonViewDidUpdate(this) } diff --git a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt index e7c465c..bd64ebb 100644 --- a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +++ b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt @@ -14,6 +14,7 @@ import com.google.android.gms.maps.model.AdvancedMarker import com.google.android.gms.maps.model.AdvancedMarkerOptions import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MapColorScheme +import com.google.android.gms.maps.model.Polygon import com.google.android.gms.maps.model.PolygonOptions import com.google.android.gms.maps.model.PolylineOptions import com.luggmaps.LuggMarkerView @@ -31,7 +32,8 @@ class GoogleMapProvider(private val context: Context) : LuggPolygonViewDelegate, GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnCameraMoveListener, - GoogleMap.OnCameraIdleListener { + GoogleMap.OnCameraIdleListener, + GoogleMap.OnPolygonClickListener { override var delegate: MapProviderDelegate? = null override val isMapReady: Boolean get() = _isMapReady @@ -46,6 +48,7 @@ class GoogleMapProvider(private val context: Context) : private val pendingPolylineViews = mutableSetOf() private val pendingPolygonViews = mutableSetOf() private val polylineAnimators = mutableMapOf() + private val polygonToViewMap = mutableMapOf() // Initial camera settings private var initialLatitude: Double = 0.0 @@ -94,9 +97,11 @@ class GoogleMapProvider(private val context: Context) : pendingPolygonViews.clear() polylineAnimators.values.forEach { it.destroy() } polylineAnimators.clear() + polygonToViewMap.clear() googleMap?.setOnCameraMoveStartedListener(null) googleMap?.setOnCameraMoveListener(null) googleMap?.setOnCameraIdleListener(null) + googleMap?.setOnPolygonClickListener(null) googleMap?.clear() googleMap = null _isMapReady = false @@ -115,6 +120,7 @@ class GoogleMapProvider(private val context: Context) : map.setOnCameraMoveStartedListener(this) map.setOnCameraMoveListener(this) map.setOnCameraIdleListener(this) + map.setOnPolygonClickListener(this) applyUiSettings() applyZoomLimits() @@ -155,6 +161,10 @@ class GoogleMapProvider(private val context: Context) : isDragging = false } + override fun onPolygonClick(polygon: Polygon) { + polygonToViewMap[polygon]?.emitPressEvent() + } + // endregion // region Props @@ -427,6 +437,7 @@ class GoogleMapProvider(private val context: Context) : } override fun removePolygonView(polygonView: LuggPolygonView) { + polygonView.polygon?.let { polygonToViewMap.remove(it) } polygonView.polygon?.remove() polygonView.polygon = null } @@ -468,7 +479,9 @@ class GoogleMapProvider(private val context: Context) : .zIndex(polygonView.zIndex) val polygon = map.addPolygon(options) + polygon.isClickable = true polygonView.polygon = polygon + polygonToViewMap[polygon] = polygonView } // endregion diff --git a/android/src/main/java/com/luggmaps/events/PolygonPressEvent.kt b/android/src/main/java/com/luggmaps/events/PolygonPressEvent.kt new file mode 100644 index 0000000..f68882d --- /dev/null +++ b/android/src/main/java/com/luggmaps/events/PolygonPressEvent.kt @@ -0,0 +1,12 @@ +package com.luggmaps.events + +import android.view.View +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.events.Event + +class PolygonPressEvent(view: View) : Event(UIManagerHelper.getSurfaceId(view), view.id) { + override fun getEventName() = "topPress" + + override fun getEventData() = Arguments.createMap() +} diff --git a/docs/POLYGON.md b/docs/POLYGON.md index 54fad23..bd7d633 100644 --- a/docs/POLYGON.md +++ b/docs/POLYGON.md @@ -19,6 +19,7 @@ import { MapView, Polygon } from '@lugg/maps'; fillColor="rgba(66, 133, 244, 0.3)" strokeColor="#4285F4" strokeWidth={2} + onPress={() => console.log('Polygon pressed')} /> ``` @@ -32,3 +33,4 @@ import { MapView, Polygon } from '@lugg/maps'; | `strokeColor` | `ColorValue` | - | Stroke (outline) color | | `strokeWidth` | `number` | - | Stroke width in points | | `zIndex` | `number` | - | Z-index for layering | +| `onPress` | `() => void` | - | Called when the polygon is tapped | diff --git a/example/shared/src/Home.tsx b/example/shared/src/Home.tsx index 66c1e74..7c43b98 100644 --- a/example/shared/src/Home.tsx +++ b/example/shared/src/Home.tsx @@ -63,8 +63,8 @@ function HomeContent() { const [provider, setProvider] = useState('apple'); const [showMap, setShowMap] = useState(true); const [markers, setMarkers] = useState(INITIAL_MARKERS); - const [cameraPosition, setCameraPosition] = useState(); - const [isIdle, setIsIdle] = useState(true); + const [statusText, setStatusText] = useState('Loading...'); + const lastCoordinate = useRef({ latitude: 37.78, longitude: -122.43 }); const getSheetBottom = useCallback( (event: DetentChangeEvent) => screenHeight - event.nativeEvent.position, @@ -94,17 +94,22 @@ function HomeContent() { [getSheetBottom] ); - const handleCameraEvent = useCallback( + const formatCameraEvent = useCallback( (event: { nativeEvent: CameraEventPayload }, idle: boolean) => { - setCameraPosition(event.nativeEvent); - setIsIdle(idle); + const { coordinate, zoom, gesture } = event.nativeEvent; + lastCoordinate.current = coordinate; + const pos = `${coordinate.latitude.toFixed(5)}, ${coordinate.longitude.toFixed(5)} (z${zoom.toFixed(1)})`; + const suffix = idle + ? ` (idle${gesture ? ', gesture' : ''})` + : gesture + ? ' (gesture)' + : ''; + setStatusText(pos + suffix); }, [] ); const addMarker = () => { - if (!cameraPosition) return; - const type = randomFrom(MARKER_TYPES); const id = Date.now().toString(); @@ -113,7 +118,7 @@ function HomeContent() { { id, name: `marker-${id}`, - coordinate: cameraPosition.coordinate, + coordinate: lastCoordinate.current, type, anchor: { x: 0.5, y: type === 'icon' ? 1 : 0.5 }, text: randomLetter(), @@ -159,8 +164,9 @@ function HomeContent() { animatedPosition={animatedPosition} userLocationEnabled={locationPermission} onReady={handleMapReady} - onCameraMove={(e) => handleCameraEvent(e, false)} - onCameraIdle={(e) => handleCameraEvent(e, true)} + onCameraMove={(e) => formatCameraEvent(e, false)} + onCameraIdle={(e) => formatCameraEvent(e, true)} + onPolygonPress={() => setStatusText('Polygon pressed!')} /> )} @@ -176,22 +182,7 @@ function HomeContent() { onDidPresent={handleSheetPresent} onDetentChange={handleDetentChange} > - - {cameraPosition ? ( - <> - {cameraPosition.coordinate.latitude.toFixed(5)},{' '} - {cameraPosition.coordinate.longitude.toFixed(5)} (z - {cameraPosition.zoom.toFixed(1)}) - {isIdle - ? ` (idle${cameraPosition.gesture ? ', gesture' : ''})` - : cameraPosition.gesture - ? ' (gesture)' - : ''} - - ) : ( - 'Loading...' - )} - + {statusText}