diff --git a/android/build.gradle b/android/build.gradle
index ad78c0e..d48138b 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -28,7 +28,7 @@ apply from: "./fix-prefab.gradle"
if (rootProject.name != "rngooglemapsplus.example") {
apply plugin: "com.facebook.react"
} else {
- println("\u001B[33m⚠️ Skipping React Native Gradle plugin in library (example build detected)\u001B[0m")
+ println("\u001B[33mSkipping React Native Gradle plugin in library (example build detected)\u001B[0m")
}
def getExtOrIntegerDefault(name) {
diff --git a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt
index ad55a07..1e859b6 100644
--- a/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt
+++ b/android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt
@@ -35,9 +35,11 @@ import com.google.android.gms.maps.model.TileOverlayOptions
import com.google.maps.android.data.kml.KmlLayer
import com.margelo.nitro.core.Promise
import com.rngooglemapsplus.extensions.toGooglePriority
+import com.rngooglemapsplus.extensions.toLatLng
import com.rngooglemapsplus.extensions.toLocationErrorCode
import com.rngooglemapsplus.extensions.toRNIndoorBuilding
import com.rngooglemapsplus.extensions.toRNIndoorLevel
+import com.rngooglemapsplus.extensions.toRnLatLng
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
@@ -88,11 +90,7 @@ class GoogleMapsViewImpl(
reactContext.addLifecycleEventListener(this)
}
- fun initMapView(
- mapId: String?,
- liteMode: Boolean?,
- cameraPosition: CameraPosition?,
- ) {
+ fun initMapView(googleMapsOptions: GoogleMapOptions) {
if (initialized) return
initialized = true
val result = playServiceHandler.playServicesAvailability()
@@ -126,13 +124,7 @@ class GoogleMapsViewImpl(
mapView =
MapView(
reactContext,
- GoogleMapOptions().apply {
- mapId?.let { mapId(it) }
- liteMode?.let { liteMode(it) }
- cameraPosition?.let {
- camera(it)
- }
- },
+ googleMapsOptions,
)
super.addView(mapView)
@@ -173,12 +165,12 @@ class GoogleMapsViewImpl(
onCameraChangeStart?.invoke(
RNRegion(
- center = RNLatLng(bounds.center.latitude, bounds.center.longitude),
+ center = bounds.center.toRnLatLng(),
latitudeDelta = latDelta,
longitudeDelta = lngDelta,
),
RNCamera(
- center = RNLatLng(cameraPosition.target.latitude, cameraPosition.target.longitude),
+ center = cameraPosition.target.toRnLatLng(),
zoom = cameraPosition.zoom.toDouble(),
bearing = cameraPosition.bearing.toDouble(),
tilt = cameraPosition.tilt.toDouble(),
@@ -205,12 +197,12 @@ class GoogleMapsViewImpl(
onCameraChange?.invoke(
RNRegion(
- center = RNLatLng(bounds.center.latitude, bounds.center.longitude),
+ center = bounds.center.toRnLatLng(),
latitudeDelta = latDelta,
longitudeDelta = lngDelta,
),
RNCamera(
- center = RNLatLng(cameraPosition.target.latitude, cameraPosition.target.longitude),
+ center = cameraPosition.target.toRnLatLng(),
zoom = cameraPosition.zoom.toDouble(),
bearing = cameraPosition.bearing.toDouble(),
tilt = cameraPosition.tilt.toDouble(),
@@ -233,12 +225,12 @@ class GoogleMapsViewImpl(
onCameraChangeComplete?.invoke(
RNRegion(
- center = RNLatLng(bounds.center.latitude, bounds.center.longitude),
+ center = bounds.center.toRnLatLng(),
latitudeDelta = latDelta,
longitudeDelta = lngDelta,
),
RNCamera(
- center = RNLatLng(cameraPosition.target.latitude, cameraPosition.target.longitude),
+ center = cameraPosition.target.toRnLatLng(),
zoom = cameraPosition.zoom.toDouble(),
bearing = cameraPosition.bearing.toDouble(),
tilt = cameraPosition.tilt.toDouble(),
@@ -544,7 +536,7 @@ class GoogleMapsViewImpl(
onUi {
val builder = LatLngBounds.Builder()
coordinates.forEach { coord ->
- builder.include(LatLng(coord.latitude, coord.longitude))
+ builder.include(coord.toLatLng())
}
val bounds = builder.build()
@@ -1069,28 +1061,28 @@ class GoogleMapsViewImpl(
override fun onMapClick(coordinates: LatLng) {
onMapPress?.invoke(
- RNLatLng(coordinates.latitude, coordinates.longitude),
+ coordinates.toRnLatLng(),
)
}
override fun onMarkerDragStart(marker: Marker) {
onMarkerDragStart?.invoke(
marker.tag?.toString(),
- RNLatLng(marker.position.latitude, marker.position.longitude),
+ marker.position.toRnLatLng(),
)
}
override fun onMarkerDrag(marker: Marker) {
onMarkerDrag?.invoke(
marker.tag?.toString(),
- RNLatLng(marker.position.latitude, marker.position.longitude),
+ marker.position.toRnLatLng(),
)
}
override fun onMarkerDragEnd(marker: Marker) {
onMarkerDragEnd?.invoke(
marker.tag?.toString(),
- RNLatLng(marker.position.latitude, marker.position.longitude),
+ marker.position.toRnLatLng(),
)
}
diff --git a/android/src/main/java/com/rngooglemapsplus/MapCircleBuilder.kt b/android/src/main/java/com/rngooglemapsplus/MapCircleBuilder.kt
index 07a98f5..45e2104 100644
--- a/android/src/main/java/com/rngooglemapsplus/MapCircleBuilder.kt
+++ b/android/src/main/java/com/rngooglemapsplus/MapCircleBuilder.kt
@@ -4,13 +4,13 @@ import android.graphics.Color
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.google.android.gms.maps.model.Circle
import com.google.android.gms.maps.model.CircleOptions
-import com.google.android.gms.maps.model.LatLng
import com.rngooglemapsplus.extensions.toColor
+import com.rngooglemapsplus.extensions.toLatLng
class MapCircleBuilder {
fun build(circle: RNCircle): CircleOptions =
CircleOptions().apply {
- center(LatLng(circle.center.latitude, circle.center.longitude))
+ center(circle.center.toLatLng())
radius(circle.radius)
circle.strokeWidth?.let { strokeWidth(it.dpToPx()) }
circle.strokeColor?.let { strokeColor(it.toColor()) }
@@ -23,11 +23,12 @@ class MapCircleBuilder {
circle: Circle,
next: RNCircle,
) {
- circle.center = LatLng(next.center.latitude, next.center.longitude)
+ circle.center = next.center.toLatLng()
circle.radius = next.radius
circle.strokeWidth = next.strokeWidth?.dpToPx() ?: 1f
circle.strokeColor = next.strokeColor?.toColor() ?: Color.BLACK
circle.fillColor = next.fillColor?.toColor() ?: Color.TRANSPARENT
+ circle.isClickable = next.pressable ?: false
circle.zIndex = next.zIndex?.toFloat() ?: 0f
}
}
diff --git a/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt b/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt
index 1e2fe56..ddb48dd 100644
--- a/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt
+++ b/android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt
@@ -8,11 +8,11 @@ import com.caverock.androidsvg.SVG
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
-import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.rngooglemapsplus.extensions.markerStyleEquals
import com.rngooglemapsplus.extensions.styleHash
+import com.rngooglemapsplus.extensions.toLatLng
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -40,13 +40,15 @@ class MapMarkerBuilder(
icon: BitmapDescriptor?,
): MarkerOptions =
MarkerOptions().apply {
- position(LatLng(m.coordinate.latitude, m.coordinate.longitude))
+ position(m.coordinate.toLatLng())
icon(icon)
m.title?.let { title(it) }
m.snippet?.let { snippet(it) }
m.opacity?.let { alpha(it.toFloat()) }
m.flat?.let { flat(it) }
m.draggable?.let { draggable(it) }
+ m.rotation?.let { rotation(it.toFloat()) }
+ m.infoWindowAnchor?.let { infoWindowAnchor(it.x.toFloat(), it.y.toFloat()) }
m.anchor?.let { anchor((m.anchor.x).toFloat(), (m.anchor.y).toFloat()) }
m.zIndex?.let { zIndex(it.toFloat()) }
}
@@ -57,10 +59,7 @@ class MapMarkerBuilder(
next: RNMarker,
) {
marker.position =
- LatLng(
- next.coordinate.latitude,
- next.coordinate.longitude,
- )
+ next.coordinate.toLatLng()
if (!prev.markerStyleEquals(next)) {
buildIconAsync(marker.id, next) { icon ->
@@ -69,9 +68,14 @@ class MapMarkerBuilder(
}
marker.title = next.title
marker.snippet = next.snippet
- marker.alpha = next.opacity?.toFloat() ?: 0f
+ marker.alpha = next.opacity?.toFloat() ?: 1f
marker.isFlat = next.flat ?: false
marker.isDraggable = next.draggable ?: false
+ marker.rotation = next.rotation?.toFloat() ?: 0f
+ marker.setInfoWindowAnchor(
+ (next.infoWindowAnchor?.x ?: 0.5).toFloat(),
+ (next.infoWindowAnchor?.y ?: 0).toFloat(),
+ )
marker.setAnchor(
(next.anchor?.x ?: 0.5).toFloat(),
(next.anchor?.y ?: 1.0).toFloat(),
diff --git a/android/src/main/java/com/rngooglemapsplus/MapPolygonBuilder.kt b/android/src/main/java/com/rngooglemapsplus/MapPolygonBuilder.kt
index f3173ae..a69e88e 100644
--- a/android/src/main/java/com/rngooglemapsplus/MapPolygonBuilder.kt
+++ b/android/src/main/java/com/rngooglemapsplus/MapPolygonBuilder.kt
@@ -2,38 +2,46 @@ package com.rngooglemapsplus
import android.graphics.Color
import com.facebook.react.uimanager.PixelUtil.dpToPx
-import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Polygon
import com.google.android.gms.maps.model.PolygonOptions
import com.rngooglemapsplus.extensions.toColor
+import com.rngooglemapsplus.extensions.toLatLng
class MapPolygonBuilder {
fun build(poly: RNPolygon): PolygonOptions =
PolygonOptions().apply {
poly.coordinates.forEach { pt ->
add(
- com.google.android.gms.maps.model
- .LatLng(pt.latitude, pt.longitude),
+ pt.toLatLng(),
)
}
poly.fillColor?.let { fillColor(it.toColor()) }
poly.strokeColor?.let { strokeColor(it.toColor()) }
poly.strokeWidth?.let { strokeWidth(it.dpToPx()) }
poly.pressable?.let { clickable(it) }
+ poly.geodesic?.let { geodesic(it) }
+ poly.holes?.forEach { hole ->
+ addHole(hole.coordinates.map { it.toLatLng() })
+ }
poly.zIndex?.let { zIndex(it.toFloat()) }
}
fun update(
- gmsPoly: Polygon,
+ poly: Polygon,
next: RNPolygon,
) {
- gmsPoly.points =
+ poly.points =
next.coordinates.map {
- LatLng(it.latitude, it.longitude)
+ it.toLatLng()
}
- gmsPoly.fillColor = next.fillColor?.toColor() ?: Color.TRANSPARENT
- gmsPoly.strokeColor = next.strokeColor?.toColor() ?: Color.BLACK
- gmsPoly.strokeWidth = next.strokeWidth?.dpToPx() ?: 1f
- gmsPoly.zIndex = next.zIndex?.toFloat() ?: 0f
+ poly.fillColor = next.fillColor?.toColor() ?: Color.TRANSPARENT
+ poly.strokeColor = next.strokeColor?.toColor() ?: Color.BLACK
+ poly.strokeWidth = next.strokeWidth?.dpToPx() ?: 1f
+ poly.isClickable = next.pressable ?: false
+ poly.isGeodesic = next.geodesic ?: false
+ poly.holes = next.holes?.map { hole ->
+ hole.coordinates.map { it.toLatLng() }
+ } ?: emptyList()
+ poly.zIndex = next.zIndex?.toFloat() ?: 0f
}
}
diff --git a/android/src/main/java/com/rngooglemapsplus/MapPolylineBuilder.kt.kt b/android/src/main/java/com/rngooglemapsplus/MapPolylineBuilder.kt.kt
index f5571a9..d4bb76f 100644
--- a/android/src/main/java/com/rngooglemapsplus/MapPolylineBuilder.kt.kt
+++ b/android/src/main/java/com/rngooglemapsplus/MapPolylineBuilder.kt.kt
@@ -5,18 +5,18 @@ import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.google.android.gms.maps.model.ButtCap
import com.google.android.gms.maps.model.Cap
import com.google.android.gms.maps.model.JointType
-import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Polyline
import com.google.android.gms.maps.model.PolylineOptions
import com.google.android.gms.maps.model.RoundCap
import com.google.android.gms.maps.model.SquareCap
import com.rngooglemapsplus.extensions.toColor
+import com.rngooglemapsplus.extensions.toLatLng
class MapPolylineBuilder {
fun build(pl: RNPolyline): PolylineOptions =
PolylineOptions().apply {
pl.coordinates.forEach { pt ->
- add(LatLng(pt.latitude, pt.longitude))
+ add(pt.toLatLng())
}
pl.width?.let { width(it.dpToPx()) }
pl.lineCap?.let {
@@ -25,6 +25,7 @@ class MapPolylineBuilder {
}
pl.lineJoin?.let { jointType(mapLineJoin(it)) }
pl.color?.let { color(it.toColor()) }
+ pl.geodesic?.let { geodesic(it) }
pl.pressable?.let { clickable(it) }
pl.zIndex?.let { zIndex(it.toFloat()) }
}
@@ -33,14 +34,15 @@ class MapPolylineBuilder {
polyline: Polyline,
next: RNPolyline,
) {
- polyline.points = next.coordinates.map { LatLng(it.latitude, it.longitude) }
-
+ polyline.points = next.coordinates.map { it.toLatLng() }
polyline.width = next.width?.dpToPx() ?: 1f
val cap = mapLineCap(next.lineCap ?: RNLineCapType.BUTT)
polyline.startCap = cap
polyline.endCap = cap
polyline.jointType = mapLineJoin(next.lineJoin ?: RNLineJoinType.MITER)
polyline.color = next.color?.toColor() ?: Color.BLACK
+ polyline.isClickable = next.pressable ?: false
+ polyline.isGeodesic = next.geodesic ?: false
polyline.zIndex = next.zIndex?.toFloat() ?: 0f
}
diff --git a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt
index d31d24e..899dbf4 100644
--- a/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt
+++ b/android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt
@@ -3,6 +3,7 @@ package com.rngooglemapsplus
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.uimanager.ThemedReactContext
+import com.google.android.gms.maps.GoogleMapOptions
import com.google.android.gms.maps.model.MapStyleOptions
import com.margelo.nitro.core.Promise
import com.rngooglemapsplus.extensions.circleEquals
@@ -40,11 +41,13 @@ class RNGoogleMapsPlusView(
super.afterUpdate()
if (!propsInitialized) {
propsInitialized = true
- view.initMapView(
- initialProps?.mapId,
- initialProps?.liteMode,
- initialProps?.camera?.toCameraPosition(),
- )
+ val options =
+ GoogleMapOptions().apply {
+ initialProps?.mapId?.let { mapId(it) }
+ initialProps?.liteMode?.let { liteMode(it) }
+ initialProps?.camera?.let { camera(it.toCameraPosition()) }
+ }
+ view.initMapView(options)
}
}
diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/LatLngExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/LatLngExtension.kt
new file mode 100644
index 0000000..7042edb
--- /dev/null
+++ b/android/src/main/java/com/rngooglemapsplus/extensions/LatLngExtension.kt
@@ -0,0 +1,6 @@
+package com.rngooglemapsplus.extensions
+
+import com.google.android.gms.maps.model.LatLng
+import com.rngooglemapsplus.RNLatLng
+
+fun LatLng.toRnLatLng(): RNLatLng = RNLatLng(latitude, longitude)
diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNCameraExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNCameraExtension.kt
index ed29245..65c87e4 100644
--- a/android/src/main/java/com/rngooglemapsplus/extensions/RNCameraExtension.kt
+++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNCameraExtension.kt
@@ -1,14 +1,13 @@
package com.rngooglemapsplus.extensions
import com.google.android.gms.maps.model.CameraPosition
-import com.google.android.gms.maps.model.LatLng
import com.rngooglemapsplus.RNCamera
fun RNCamera.toCameraPosition(): CameraPosition {
val builder = CameraPosition.builder()
center?.let {
- builder.target(LatLng(it.latitude, it.longitude))
+ builder.target(it.toLatLng())
}
zoom?.let { builder.zoom(it.toFloat()) }
diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNLatLngExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNLatLngExtension.kt
new file mode 100644
index 0000000..0925f6a
--- /dev/null
+++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNLatLngExtension.kt
@@ -0,0 +1,6 @@
+package com.rngooglemapsplus.extensions
+
+import com.google.android.gms.maps.model.LatLng
+import com.rngooglemapsplus.RNLatLng
+
+fun RNLatLng.toLatLng(): LatLng = LatLng(latitude, longitude)
diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt
index 01fde5c..6da266d 100644
--- a/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt
+++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNMarkerExtension.kt
@@ -7,6 +7,14 @@ fun RNMarker.markerEquals(b: RNMarker): Boolean =
zIndex == b.zIndex &&
coordinate == b.coordinate &&
anchor == b.anchor &&
+ showInfoWindow == b.showInfoWindow &&
+ title == b.title &&
+ snippet == b.snippet &&
+ opacity == b.opacity &&
+ flat == b.flat &&
+ draggable == b.draggable &&
+ rotation == b.rotation &&
+ infoWindowAnchor == b.infoWindowAnchor &&
markerStyleEquals(b)
fun RNMarker.markerStyleEquals(b: RNMarker): Boolean = iconSvg == b.iconSvg
diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNPolygonExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNPolygonExtension.kt
index cc037e8..4814d4b 100644
--- a/android/src/main/java/com/rngooglemapsplus/extensions/RNPolygonExtension.kt
+++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNPolygonExtension.kt
@@ -8,6 +8,8 @@ fun RNPolygon.polygonEquals(b: RNPolygon): Boolean {
if (strokeWidth != b.strokeWidth) return false
if (fillColor != b.fillColor) return false
if (strokeColor != b.strokeColor) return false
+ if (geodesic != b.geodesic) return false
+ if (!holes.contentEquals(b.holes)) return false
val ac = coordinates
val bc = b.coordinates
if (ac.size != bc.size) return false
diff --git a/android/src/main/java/com/rngooglemapsplus/extensions/RNPolylineExtension.kt b/android/src/main/java/com/rngooglemapsplus/extensions/RNPolylineExtension.kt
index 61b4ae7..0b7cdc6 100644
--- a/android/src/main/java/com/rngooglemapsplus/extensions/RNPolylineExtension.kt
+++ b/android/src/main/java/com/rngooglemapsplus/extensions/RNPolylineExtension.kt
@@ -8,6 +8,7 @@ fun RNPolyline.polylineEquals(b: RNPolyline): Boolean {
if ((width ?: 0.0) != (b.width ?: 0.0)) return false
if (lineCap != b.lineCap) return false
if (lineJoin != b.lineJoin) return false
+ if (geodesic != b.geodesic) return false
if (color != b.color) return false
val ac = coordinates
val bc = b.coordinates
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 5935e1a..ff231e0 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -16,7 +16,7 @@ PODS:
- hermes-engine (0.82.0):
- hermes-engine/Pre-built (= 0.82.0)
- hermes-engine/Pre-built (0.82.0)
- - NitroModules (0.29.8):
+ - NitroModules (0.30.0):
- boost
- DoubleConversion
- fast_float
@@ -3018,7 +3018,7 @@ SPEC CHECKSUMS:
Google-Maps-iOS-Utils: bed22fa703c919259b3901449434d60d994fae20
GoogleMaps: a40d3b1f511f0fa2036e7b08c920c33279b1d5fd
hermes-engine: 8642d8f14a548ab718ec112e9bebdfdd154138b5
- NitroModules: 73c42f3089bd74f411172ea8e69024aac4a2ac9f
+ NitroModules: 9d7097ba832aa88b678bf65b8a04e5ea565334d8
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: 22bf66112da540a7d40e536366ddd8557934fca1
RCTRequired: a0ed4dc41b35f79fbb6d8ba320e06882a8c792cf
diff --git a/example/package.json b/example/package.json
index ccbb59e..4c81cd0 100644
--- a/example/package.json
+++ b/example/package.json
@@ -18,11 +18,12 @@
"react-native": "0.82.0",
"react-native-gesture-handler": "2.28.0",
"react-native-google-maps-plus": "workspace:*",
- "react-native-nitro-modules": "0.29.8",
+ "react-native-nitro-modules": "0.30.0",
"react-native-reanimated": "4.1.3",
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "4.16.0",
- "react-native-worklets": "0.6.1"
+ "react-native-worklets": "0.6.1",
+ "superstruct": "^2.0.2"
},
"devDependencies": {
"@babel/core": "7.28.4",
@@ -36,7 +37,7 @@
"@react-native/typescript-config": "0.82.0",
"@types/react": "19.1.1",
"react-native-builder-bob": "0.40.13",
- "react-native-monorepo-config": "0.2.1"
+ "react-native-monorepo-config": "0.2.2"
},
"engines": {
"node": ">=20"
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 05f1756..30c2af2 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import {
- NavigationContainer,
- DefaultTheme,
DarkTheme,
+ DefaultTheme,
+ NavigationContainer,
} from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
@@ -113,7 +113,7 @@ export default function App() {
options={{ title: 'Snapshot test' }}
/>
diff --git a/example/src/components/ControlPanel.tsx b/example/src/components/ControlPanel.tsx
index 1df87fa..2d6d58b 100644
--- a/example/src/components/ControlPanel.tsx
+++ b/example/src/components/ControlPanel.tsx
@@ -1,17 +1,17 @@
import React, { useMemo } from 'react';
import {
- View,
- Text,
- TouchableOpacity,
ScrollView,
StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
} from 'react-native';
import Animated, {
- useSharedValue,
+ Extrapolation,
+ interpolate,
useAnimatedStyle,
+ useSharedValue,
withTiming,
- interpolate,
- Extrapolation,
} from 'react-native-reanimated';
import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
import { useAppTheme } from '../theme';
@@ -29,7 +29,7 @@ export default function ControlPanel({ mapRef, buttons }: Props) {
const theme = useAppTheme();
const navigation = useNavigation();
const progress = useSharedValue(0);
- const styles = getThemedStyles(theme);
+ const styles = useMemo(() => getThemedStyles(theme), [theme]);
const toggle = () => {
progress.value = withTiming(progress.value === 1 ? 0 : 1, {
@@ -62,7 +62,10 @@ export default function ControlPanel({ mapRef, buttons }: Props) {
{
title: 'Check Google Play Services',
onPress: () =>
- console.log(mapRef.current?.isGooglePlayServicesAvailable()),
+ console.log(
+ 'Google Play Services result',
+ mapRef.current?.isGooglePlayServicesAvailable()
+ ),
},
],
[buttons, mapRef, navigation]
diff --git a/example/src/components/HeaderButton.tsx b/example/src/components/HeaderButton.tsx
new file mode 100644
index 0000000..68e9880
--- /dev/null
+++ b/example/src/components/HeaderButton.tsx
@@ -0,0 +1,34 @@
+import React, { useMemo } from 'react';
+import { Pressable, StyleSheet, Text } from 'react-native';
+import { useAppTheme } from '../theme';
+
+type Props = {
+ title: string;
+ onPress: () => void;
+};
+
+export default function HeaderButton({ title, onPress }: Props) {
+ const theme = useAppTheme();
+ const styles = useMemo(() => getThemedStyles(theme), [theme]);
+
+ return (
+ [styles.headerButton]}>
+ {title}
+
+ );
+}
+
+const getThemedStyles = (theme: any) =>
+ StyleSheet.create({
+ headerButton: {
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 6,
+ marginRight: 12,
+ },
+ headerButtonText: {
+ color: theme.textPrimary,
+ fontSize: 15,
+ fontWeight: '600',
+ },
+ });
diff --git a/example/src/components/MapWrapper.tsx b/example/src/components/MapWrapper.tsx
index 5ccb035..14943ac 100644
--- a/example/src/components/MapWrapper.tsx
+++ b/example/src/components/MapWrapper.tsx
@@ -1,25 +1,23 @@
import React, { useMemo } from 'react';
+import type { ViewProps } from 'react-native';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
-import {
- GoogleMapsView,
- type RNIndoorBuilding,
- type RNIndoorLevel,
- RNLocationErrorCode,
- RNMapErrorCode,
-} from 'react-native-google-maps-plus';
import type {
GoogleMapsViewRef,
- RNGoogleMapsPlusViewProps,
RNCamera,
+ RNGoogleMapsPlusViewProps,
+ RNLatLng,
RNLocation,
RNRegion,
- RNLatLng,
} from 'react-native-google-maps-plus';
import {
+ GoogleMapsView,
RNAndroidLocationPriority,
+ type RNIndoorBuilding,
+ type RNIndoorLevel,
RNIOSLocationAccuracy,
+ RNLocationErrorCode,
+ RNMapErrorCode,
} from 'react-native-google-maps-plus';
-import type { ViewProps } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { callback } from 'react-native-nitro-modules';
import { useTheme } from '@react-navigation/native';
@@ -33,7 +31,7 @@ type Props = ViewProps &
export default function MapWrapper(props: Props) {
const { children, ...rest } = props;
const theme = useTheme();
- const styles = getThemedStyles(theme);
+ const styles = useMemo(() => getThemedStyles(theme), [theme]);
const layout = useSafeAreaInsets();
const [mapReady, setMapReady] = React.useState(false);
@@ -67,7 +65,7 @@ export default function MapWrapper(props: Props) {
const mapPadding = useMemo(() => {
return props.children
? { top: 20, left: 20, bottom: layout.bottom + 80, right: 20 }
- : undefined;
+ : { top: 20, left: 20, bottom: layout.bottom, right: 20 };
}, [layout.bottom, props.children]);
const mapZoomConfig = useMemo(() => ({ min: 0, max: 20 }), []);
@@ -217,7 +215,7 @@ const getThemedStyles = (theme: any) =>
StyleSheet.create({
container: {
flex: 1,
- backgroundColor: theme.colors.background,
+ backgroundColor: theme.background,
},
map: {
position: 'absolute',
@@ -227,10 +225,10 @@ const getThemedStyles = (theme: any) =>
bottom: 0,
},
loadingOverlay: {
- ...StyleSheet.absoluteFillObject,
+ ...StyleSheet.absoluteFill,
justifyContent: 'center',
alignItems: 'center',
zIndex: 10,
- backgroundColor: theme.dark ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.7)',
+ backgroundColor: theme.overlay,
},
});
diff --git a/example/src/components/maptConfigDialog/MapConfigDialog.tsx b/example/src/components/maptConfigDialog/MapConfigDialog.tsx
new file mode 100644
index 0000000..d981748
--- /dev/null
+++ b/example/src/components/maptConfigDialog/MapConfigDialog.tsx
@@ -0,0 +1,498 @@
+import React, { useMemo, useState } from 'react';
+import {
+ Alert,
+ FlatList,
+ Modal,
+ Pressable,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TextInput,
+ View,
+} from 'react-native';
+import type { Struct } from 'superstruct';
+import { validate } from 'superstruct';
+import { useAppTheme } from '../../theme';
+
+type FieldType = 'text' | 'number' | 'boolean' | 'json';
+
+export type FieldSchema = {
+ key: keyof T;
+ label?: string;
+ type: FieldType;
+ multiline?: boolean;
+ placeholder?: string;
+ options?: any[];
+};
+
+type Props = {
+ visible: boolean;
+ title: string;
+ initialData: T;
+ onClose: () => void;
+ onSave: (updated: T) => void;
+ validator?: Struct;
+};
+
+const boolOptions = [true, false] as const;
+
+export default function MapConfigDialog({
+ visible,
+ title,
+ initialData,
+ onClose,
+ onSave,
+ validator,
+}: Props) {
+ const theme = useAppTheme();
+ const styles = useMemo(() => getThemedStyles(theme), [theme]);
+ const [draft, setDraft] = useState(initialData);
+ const [activeDropdown, setActiveDropdown] = useState(null);
+
+ const autoSchema: FieldSchema[] = useMemo(() => {
+ if (!validator || !('schema' in validator)) return [];
+ const unwrap = (s: any): any => {
+ let base = s;
+ while (
+ base &&
+ ['optional', 'nullable', 'defaulted'].includes(base.type)
+ ) {
+ if (base._values && base.schema && !base.schema._values)
+ base.schema._values = base._values;
+ if (base._schema && base.schema && !base.schema._schema)
+ base.schema._schema = base._schema;
+ base = base.schema;
+ }
+ return base;
+ };
+
+ const extractOptions = (schema: any): string[] | undefined => {
+ let v = schema;
+ while (v && ['optional', 'nullable', 'defaulted'].includes(v.type)) {
+ v = v.schema;
+ }
+
+ if (!v) return;
+ if (
+ Array.isArray(v._values) &&
+ v._values.every((x: any) => typeof x === 'string')
+ ) {
+ return v._values;
+ }
+
+ if (v.type === 'enums' && v.schema && typeof v.schema === 'object') {
+ const vals = Object.values(v.schema).filter(
+ (x): x is string => typeof x === 'string'
+ );
+ if (vals.length) {
+ return vals;
+ }
+ }
+
+ if (v.type === 'union' && Array.isArray(v._schema)) {
+ const literals = v._schema
+ .map((x: any) => {
+ return typeof x === 'object' &&
+ x.schema &&
+ typeof x.schema === 'string'
+ ? x.schema
+ : (x.value ?? x._value);
+ })
+ .filter((x: any): x is string => typeof x === 'string');
+ if (literals.length) {
+ return literals;
+ }
+ }
+
+ if (v.type === 'union' && Array.isArray(v.schema)) {
+ const literals = v.schema
+ .map((x: any) => {
+ return typeof x === 'object' &&
+ x.schema &&
+ typeof x.schema === 'string'
+ ? x.schema
+ : (x.value ?? x._value);
+ })
+ .filter((x: any): x is string => typeof x === 'string');
+ if (literals.length) {
+ return literals;
+ }
+ }
+
+ return undefined;
+ };
+
+ const result: FieldSchema[] = [];
+
+ for (const [key, raw] of Object.entries((validator as any).schema)) {
+ const unwrapped = unwrap(raw);
+
+ const options = extractOptions(raw);
+
+ let type: FieldType = 'text';
+ if (!options) {
+ if (unwrapped?.type === 'boolean') type = 'boolean';
+ else if (unwrapped?.type === 'number') type = 'number';
+ else if (unwrapped?.type === 'object' || unwrapped?.type === 'array')
+ type = 'json';
+ }
+
+ result.push({
+ key: key as keyof T,
+ label: key,
+ type,
+ options,
+ multiline: type === 'json',
+ });
+ }
+
+ return result;
+ }, [validator]);
+
+ const [jsonInputs, setJsonInputs] = useState>(() => {
+ const initial: Record = {};
+ autoSchema.forEach((f) => {
+ if (f.type !== 'json') return;
+ const key = String(f.key);
+ const v = (initialData as any)[key];
+ initial[key] =
+ v && typeof v === 'object'
+ ? JSON.stringify(v, null, 2)
+ : String(v ?? '');
+ });
+ return initial;
+ });
+
+ const updateField = (key: keyof T, value: any, type: FieldType) => {
+ setDraft((prev) => ({
+ ...(prev as any),
+ [key]:
+ type === 'number'
+ ? value === '' || isNaN(Number(value))
+ ? undefined
+ : Number(value)
+ : value,
+ }));
+ };
+
+ const handleSave = () => {
+ const updated: any = { ...(draft as any) };
+ for (const f of autoSchema) {
+ const k = String(f.key);
+ if (f.type === 'json') {
+ const jsonStr = jsonInputs[k] ?? '';
+ if (jsonStr.trim().length === 0) {
+ updated[k] = undefined;
+ } else {
+ try {
+ updated[k] = JSON.parse(jsonStr);
+ } catch (e: any) {
+ Alert.alert('Invalid JSON', `${f.label ?? k}: ${e.message}`);
+ return;
+ }
+ }
+ }
+ }
+ if (validator) {
+ const [err, value] = validate(updated, validator);
+ if (err) {
+ Alert.alert(
+ 'Validation Error',
+ `${err.path?.join('.') || '(root)'}: ${err.message}`
+ );
+ return;
+ }
+ onSave(value as T);
+ } else {
+ onSave(updated as T);
+ }
+ onClose();
+ };
+
+ const renderDropdown = (
+ key: keyof T,
+ label: string,
+ value: any,
+ options: any[]
+ ) => (
+
+ {label}
+ setActiveDropdown(String(key))}
+ style={[
+ styles.dropdownTrigger,
+ activeDropdown === String(key) && { borderColor: theme.bgAccent },
+ ]}
+ >
+
+ {value?.toString() ?? 'Select...'}
+
+
+
+ {activeDropdown === String(key) && (
+
+
+ setActiveDropdown(null)}
+ />
+
+ {label}
+ item.toString()}
+ showsVerticalScrollIndicator={false}
+ renderItem={({ item }) => (
+ [styles.dropdownItem]}
+ onPress={() => {
+ updateField(key, item, 'text');
+ setActiveDropdown(null);
+ }}
+ >
+
+ {item.toString()}
+
+
+ )}
+ />
+ setActiveDropdown(null)}
+ style={styles.dropdownCancel}
+ >
+ Cancel
+
+
+
+
+ )}
+
+ );
+
+ const renderMultilineInput = (
+ k: string,
+ value: string,
+ onChangeText: (v: string) => void,
+ placeholder?: string
+ ) => (
+
+ {k}
+
+
+
+
+ );
+
+ return (
+
+
+
+ {title}
+
+ {autoSchema.map((f) => {
+ const key = f.key;
+ const k = String(key);
+ const label = f.label ?? k;
+ const value = (draft as any)[k];
+
+ if (f.options?.length) {
+ return renderDropdown(key, label, value, f.options);
+ }
+
+ if (f.type === 'boolean') {
+ return renderDropdown(key, label, value, boolOptions as any);
+ }
+
+ if (f.type === 'json') {
+ const jsonValue = jsonInputs[k] ?? '';
+ return renderMultilineInput(
+ k,
+ jsonValue,
+ (v) => setJsonInputs((prev) => ({ ...prev, [k]: v })),
+ f.placeholder ?? '{}'
+ );
+ }
+
+ if (f.multiline) {
+ const str = value?.toString() ?? '';
+ return renderMultilineInput(
+ k,
+ str,
+ (v) => updateField(key, v, f.type),
+ f.placeholder ?? label
+ );
+ }
+
+ return (
+
+ {label}
+ updateField(key, v, f.type)}
+ placeholder={f.placeholder ?? label}
+ placeholderTextColor={styles.placeholder.color}
+ autoCorrect={false}
+ autoComplete="off"
+ spellCheck={false}
+ autoCapitalize="none"
+ style={styles.input}
+ />
+
+ );
+ })}
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+
+
+ );
+}
+
+const getThemedStyles = (theme: any) =>
+ StyleSheet.create({
+ overlay: {
+ flex: 1,
+ backgroundColor: theme.overlay,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ dialog: {
+ width: '85%',
+ maxHeight: '85%',
+ backgroundColor: theme.bgPrimary,
+ borderRadius: 12,
+ flexShrink: 1,
+ },
+ scroll: { paddingBottom: 12, margin: 12 },
+ title: {
+ padding: 12,
+ fontSize: 18,
+ fontWeight: '600',
+ color: theme.textPrimary,
+ marginBottom: 12,
+ },
+ field: { marginBottom: 12 },
+ label: { fontSize: 14, marginBottom: 4, color: theme.label },
+ placeholder: { color: theme.placeholder },
+ input: {
+ borderWidth: 1,
+ borderRadius: 8,
+ paddingHorizontal: 10,
+ paddingVertical: 6,
+ fontSize: 13,
+ borderColor: theme.border,
+ color: theme.textPrimary,
+ backgroundColor: theme.inputBg,
+ fontFamily: 'monospace',
+ },
+ multilineInput: { minHeight: 90, textAlignVertical: 'top' },
+ innerScroll: { borderRadius: 8 },
+ dropdownOverlay: {
+ flex: 1,
+ backgroundColor: theme.overlay,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ dropdownBackdrop: {
+ ...StyleSheet.absoluteFillObject,
+ },
+ dropdownContainer: {
+ width: '80%',
+ maxHeight: '70%',
+ borderRadius: 12,
+ backgroundColor: theme.bgPrimary,
+ paddingVertical: 12,
+ paddingHorizontal: 14,
+ shadowColor: '#000',
+ shadowOpacity: 0.25,
+ shadowRadius: 8,
+ elevation: 6,
+ },
+ dropdownHeader: {
+ fontSize: 16,
+ fontWeight: '600',
+ color: theme.textPrimary,
+ marginBottom: 10,
+ textAlign: 'center',
+ },
+ dropdownItem: {
+ paddingVertical: 10,
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderBottomColor: theme.border,
+ },
+ dropdownItemText: {
+ fontSize: 14,
+ color: theme.textPrimary,
+ textAlign: 'center',
+ },
+ dropdownCancel: {
+ marginTop: 10,
+ backgroundColor: theme.cancelBg,
+ alignSelf: 'center',
+ borderRadius: 8,
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ },
+ dropdownCancelText: {
+ color: theme.textOnAccent,
+ fontWeight: '500',
+ fontSize: 14,
+ },
+ dropdownTrigger: {
+ borderWidth: 1,
+ borderRadius: 8,
+ paddingHorizontal: 10,
+ paddingVertical: 10,
+ borderColor: theme.border,
+ backgroundColor: theme.inputBg,
+ },
+ dropdownText: { fontSize: 14, color: theme.textPrimary },
+ actions: {
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ padding: 12,
+ },
+ cancelButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 14,
+ borderRadius: 8,
+ backgroundColor: theme.cancelBg,
+ marginRight: 8,
+ },
+ saveButton: {
+ paddingVertical: 8,
+ paddingHorizontal: 14,
+ borderRadius: 8,
+ backgroundColor: theme.bgAccent,
+ },
+ buttonText: { color: theme.textOnAccent, fontWeight: '500' },
+ });
diff --git a/example/src/components/maptConfigDialog/validator.ts b/example/src/components/maptConfigDialog/validator.ts
new file mode 100644
index 0000000..e017bc6
--- /dev/null
+++ b/example/src/components/maptConfigDialog/validator.ts
@@ -0,0 +1,347 @@
+import {
+ array,
+ boolean,
+ enums,
+ literal,
+ number,
+ object,
+ optional,
+ string,
+ type Struct,
+ union,
+} from 'superstruct';
+
+import {
+ RNAndroidLocationPermissionResult,
+ RNAndroidLocationPriority,
+ RNIOSLocationAccuracy,
+ RNIOSPermissionResult,
+ RNLocationErrorCode,
+ RNMapErrorCode,
+} from 'react-native-google-maps-plus';
+
+const enumValues = (e: T): T[keyof T][] =>
+ Object.values(e).filter(
+ (v): v is T[keyof T] => typeof v === 'number' || typeof v === 'string'
+ );
+
+export function unionWithValues(
+ ...values: T[]
+): Struct {
+ if (values.length === 0) throw new Error('unionWithValues: no values given');
+ const literals = values.map((v) => literal(v)) as [Struct, ...Struct[]];
+ const innerUnion = union(literals);
+ (innerUnion as any)._values = values;
+ (innerUnion as any)._schema = literals;
+ const wrapped = optional(innerUnion);
+ (wrapped as any)._values = values;
+ (wrapped as any)._schema = literals;
+ if ((wrapped as any).schema) {
+ (wrapped as any).schema._values = values;
+ (wrapped as any).schema._schema = literals;
+ }
+
+ return wrapped as any;
+}
+
+export const RNLatLngValidator = object({
+ latitude: number(),
+ longitude: number(),
+});
+
+export const RNLatLngBoundsValidator = object({
+ northEast: RNLatLngValidator,
+ southWest: RNLatLngValidator,
+});
+
+export const RNSizeValidator = object({
+ width: number(),
+ height: number(),
+});
+
+export const RNSnapshotFormatValidator = union([
+ literal('png'),
+ literal('jpg'),
+ literal('jpeg'),
+]);
+
+export const RNSnapshotResultTypeValidator = union([
+ literal('base64'),
+ literal('file'),
+]);
+
+export const RNSnapshotOptionsValidator = object({
+ size: optional(RNSizeValidator),
+ format: RNSnapshotFormatValidator,
+ quality: number(),
+ resultType: RNSnapshotResultTypeValidator,
+});
+
+export const RNMapPaddingValidator = object({
+ top: number(),
+ left: number(),
+ bottom: number(),
+ right: number(),
+});
+
+export const RNMapTypeValidator = unionWithValues(
+ 'none',
+ 'normal',
+ 'hybrid',
+ 'satellite',
+ 'terrain'
+);
+
+export const RNUserInterfaceStyleValidator = unionWithValues(
+ 'light',
+ 'dark',
+ 'default'
+);
+
+export const RNPositionValidator = object({
+ x: number(),
+ y: number(),
+});
+
+export const RNCameraValidator = object({
+ center: optional(RNLatLngValidator),
+ zoom: optional(number()),
+ bearing: optional(number()),
+ tilt: optional(number()),
+});
+
+export const RNRegionValidator = object({
+ center: RNLatLngValidator,
+ latitudeDelta: number(),
+ longitudeDelta: number(),
+});
+
+export const RNMapUiSettingsValidator = object({
+ allGesturesEnabled: optional(boolean()),
+ compassEnabled: optional(boolean()),
+ indoorLevelPickerEnabled: optional(boolean()),
+ mapToolbarEnabled: optional(boolean()),
+ myLocationButtonEnabled: optional(boolean()),
+ rotateEnabled: optional(boolean()),
+ scrollEnabled: optional(boolean()),
+ scrollDuringRotateOrZoomEnabled: optional(boolean()),
+ tiltEnabled: optional(boolean()),
+ zoomControlsEnabled: optional(boolean()),
+ zoomGesturesEnabled: optional(boolean()),
+});
+
+export const RNMapZoomConfigValidator = object({
+ min: optional(number()),
+ max: optional(number()),
+});
+
+export const RNLineCapTypeValidator = union([
+ literal('butt'),
+ literal('round'),
+ literal('square'),
+]);
+
+export const RNLineJoinTypeValidator = union([
+ literal('miter'),
+ literal('round'),
+ literal('bevel'),
+]);
+
+export const RNMarkerSvgValidator = object({
+ width: number(),
+ height: number(),
+ svgString: string(),
+});
+
+export const RNMarkerValidator = object({
+ id: string(),
+ zIndex: optional(number()),
+ coordinate: RNLatLngValidator,
+ anchor: optional(RNPositionValidator),
+ showInfoWindow: optional(boolean()),
+ title: optional(string()),
+ snippet: optional(string()),
+ opacity: optional(number()),
+ flat: optional(boolean()),
+ draggable: optional(boolean()),
+ rotation: optional(number()),
+ infoWindowAnchor: optional(RNPositionValidator),
+ iconSvg: optional(RNMarkerSvgValidator),
+});
+
+export const RNPolygonHoleValidator = object({
+ coordinates: array(RNLatLngValidator),
+});
+
+export const RNPolygonValidator = object({
+ id: string(),
+ zIndex: optional(number()),
+ pressable: optional(boolean()),
+ coordinates: array(RNLatLngValidator),
+ fillColor: optional(string()),
+ strokeColor: optional(string()),
+ strokeWidth: optional(number()),
+ holes: optional(array(RNPolygonHoleValidator)),
+ geodesic: optional(boolean()),
+});
+
+export const RNPolylineValidator = object({
+ id: string(),
+ zIndex: optional(number()),
+ pressable: optional(boolean()),
+ coordinates: array(RNLatLngValidator),
+ lineCap: optional(RNLineCapTypeValidator),
+ lineJoin: optional(RNLineJoinTypeValidator),
+ geodesic: optional(boolean()),
+ color: optional(string()),
+ width: optional(number()),
+});
+
+export const RNCircleValidator = object({
+ id: string(),
+ pressable: optional(boolean()),
+ zIndex: optional(number()),
+ center: RNLatLngValidator,
+ radius: number(),
+ strokeWidth: optional(number()),
+ strokeColor: optional(string()),
+ fillColor: optional(string()),
+});
+
+export const RNHeatmapPointValidator = object({
+ latitude: number(),
+ longitude: number(),
+ weight: number(),
+});
+
+export const RNHeatmapGradientValidator = object({
+ colors: array(string()),
+ startPoints: array(number()),
+ colorMapSize: number(),
+});
+
+export const RNHeatmapValidator = object({
+ id: string(),
+ pressable: optional(boolean()),
+ zIndex: optional(number()),
+ weightedData: array(RNHeatmapPointValidator),
+ radius: optional(number()),
+ opacity: optional(number()),
+ gradient: optional(RNHeatmapGradientValidator),
+});
+
+export const RNKMLayerValidator = object({
+ id: string(),
+ kmlString: string(),
+});
+
+export const RNIndoorLevelValidator = object({
+ index: number(),
+ name: optional(string()),
+ shortName: optional(string()),
+ active: optional(boolean()),
+});
+
+export const RNIndoorBuildingValidator = object({
+ activeLevelIndex: optional(number()),
+ defaultLevelIndex: optional(number()),
+ levels: array(RNIndoorLevelValidator),
+ underground: optional(boolean()),
+});
+
+const RNAndroidLocationConfigValidator = object({
+ priority: optional(enums(enumValues(RNAndroidLocationPriority))),
+ interval: optional(number()),
+ minUpdateInterval: optional(number()),
+});
+
+export const RNIOSLocationConfigValidator = object({
+ desiredAccuracy: optional(enums(enumValues(RNIOSLocationAccuracy))),
+ distanceFilterMeters: optional(number()),
+});
+
+export const RNLocationConfigValidator = object({
+ android: optional(RNAndroidLocationConfigValidator),
+ ios: optional(RNIOSLocationConfigValidator),
+});
+
+export const RNLocationPermissionResultValidator = object({
+ android: optional(enums(enumValues(RNAndroidLocationPermissionResult))),
+ ios: optional(enums(enumValues(RNIOSPermissionResult))),
+});
+
+export const RNLocationValidator = object({
+ center: RNLatLngValidator,
+ bearing: number(),
+});
+
+export const RNLocationErrorCodeStructValidator = optional(
+ enums(enumValues(RNLocationErrorCode))
+);
+
+export const RNMapErrorCodeStructValidator = optional(
+ enums(enumValues(RNMapErrorCode))
+);
+
+export const RNMapStylerValidator = object({
+ color: optional(string()),
+ visibility: optional(string()),
+ weight: optional(number()),
+ gamma: optional(number()),
+ lightness: optional(number()),
+ saturation: optional(number()),
+ invert_lightness: optional(boolean()),
+});
+
+export const RNMapStyleElementValidator = object({
+ featureType: optional(string()),
+ elementType: optional(string()),
+ stylers: array(RNMapStylerValidator),
+});
+
+export const RNBasicMapConfigValidator = object({
+ initialProps: optional(
+ object({
+ mapId: optional(string()),
+ liteMode: optional(boolean()),
+ camera: optional(RNCameraValidator),
+ })
+ ),
+ uiSettings: optional(RNMapUiSettingsValidator),
+ myLocationEnabled: optional(boolean()),
+ buildingEnabled: optional(boolean()),
+ trafficEnabled: optional(boolean()),
+ indoorEnabled: optional(boolean()),
+ customMapStyle: optional(string()),
+ userInterfaceStyle: optional(RNUserInterfaceStyleValidator),
+ mapZoomConfig: optional(RNMapZoomConfigValidator),
+ mapPadding: optional(RNMapPaddingValidator),
+ mapType: optional(RNMapTypeValidator),
+ locationConfig: optional(RNLocationConfigValidator),
+});
+
+const schema: any = (RNBasicMapConfigValidator as any).schema;
+
+if (schema.mapType?.type === 'union' && !schema.mapType._schema) {
+ schema.mapType._schema = [
+ { type: 'literal', schema: 'none' },
+ { type: 'literal', schema: 'normal' },
+ { type: 'literal', schema: 'hybrid' },
+ { type: 'literal', schema: 'satellite' },
+ { type: 'literal', schema: 'terrain' },
+ ];
+}
+
+if (
+ schema.userInterfaceStyle?.type === 'union' &&
+ !schema.userInterfaceStyle._schema
+) {
+ schema.userInterfaceStyle._schema = [
+ { type: 'literal', schema: 'light' },
+ { type: 'literal', schema: 'dark' },
+ { type: 'literal', schema: 'default' },
+ ];
+}
+
+export type RNBasicMapConfigType =
+ typeof RNBasicMapConfigValidator extends Struct ? O : never;
diff --git a/example/src/hooks/useHeaderButton.tsx b/example/src/hooks/useHeaderButton.tsx
new file mode 100644
index 0000000..cd39644
--- /dev/null
+++ b/example/src/hooks/useHeaderButton.tsx
@@ -0,0 +1,18 @@
+import { useCallback, useLayoutEffect } from 'react';
+import HeaderButton from '../components/HeaderButton';
+import React from 'react';
+
+export function useHeaderButton(
+ navigation: any,
+ title: string,
+ onPress: () => void
+) {
+ const renderHeaderButton = useCallback(
+ () => ,
+ [title, onPress]
+ );
+
+ useLayoutEffect(() => {
+ navigation.setOptions({ headerRight: renderHeaderButton });
+ }, [navigation, renderHeaderButton]);
+}
diff --git a/example/src/screens/BasicMapScreen.tsx b/example/src/screens/BasicMapScreen.tsx
index 7a8048b..d6cf5d8 100644
--- a/example/src/screens/BasicMapScreen.tsx
+++ b/example/src/screens/BasicMapScreen.tsx
@@ -1,14 +1,91 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
-import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
+import {
+ type GoogleMapsViewRef,
+ RNAndroidLocationPriority,
+ RNIOSLocationAccuracy,
+} from 'react-native-google-maps-plus';
import ControlPanel from '../components/ControlPanel';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import type { RNBasicMapConfig } from '../types/basicMapConfig';
+import { useNavigation } from '@react-navigation/native';
+import { RNBasicMapConfigValidator } from '../components/maptConfigDialog/validator';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function BasicMapScreen() {
const mapRef = useRef(null);
+ const layout = useSafeAreaInsets();
+ const navigation = useNavigation();
+ const [init, setInit] = useState(false);
+ const [mapConfig, setMapConfig] = useState({
+ initialProps: {
+ mapId: undefined,
+ liteMode: false,
+ camera: {
+ center: { latitude: 37.7749, longitude: -122.4194 },
+ zoom: 12,
+ },
+ },
+ uiSettings: {
+ allGesturesEnabled: true,
+ compassEnabled: true,
+ indoorLevelPickerEnabled: true,
+ mapToolbarEnabled: true,
+ myLocationButtonEnabled: true,
+ rotateEnabled: true,
+ scrollEnabled: true,
+ scrollDuringRotateOrZoomEnabled: true,
+ tiltEnabled: true,
+ zoomControlsEnabled: true,
+ zoomGesturesEnabled: true,
+ },
+ myLocationEnabled: false,
+ buildingEnabled: undefined,
+ trafficEnabled: undefined,
+ indoorEnabled: undefined,
+ customMapStyle: '',
+ userInterfaceStyle: 'default',
+ mapZoomConfig: { min: 0, max: 20 },
+ mapPadding: { top: 20, left: 20, bottom: layout.bottom, right: 20 },
+ mapType: 'normal',
+ locationConfig: {
+ android: {
+ priority: RNAndroidLocationPriority.PRIORITY_BALANCED_POWER_ACCURACY,
+ interval: 5000,
+ minUpdateInterval: 5000,
+ },
+ ios: {
+ desiredAccuracy: RNIOSLocationAccuracy.ACCURACY_BEST,
+ distanceFilterMeters: 10,
+ },
+ },
+ });
+
+ const [dialogVisible, setDialogVisible] = useState(true);
+
+ useHeaderButton(navigation, mapConfig ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
+ );
return (
-
-
-
+ <>
+ {init && (
+
+
+
+ )}
+
+ visible={dialogVisible}
+ title="Edit Map Settings"
+ initialData={mapConfig}
+ validator={RNBasicMapConfigValidator}
+ onClose={() => {
+ setInit(true);
+ setDialogVisible(false);
+ }}
+ onSave={(c) => setMapConfig(c)}
+ />
+ >
);
}
diff --git a/example/src/screens/BlankScreen.tsx b/example/src/screens/BlankScreen.tsx
index 5eeff57..8bf6cd2 100644
--- a/example/src/screens/BlankScreen.tsx
+++ b/example/src/screens/BlankScreen.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useNavigation, useTheme } from '@react-navigation/native';
import type { RootNavigationProp } from '../types/navigation';
diff --git a/example/src/screens/CameraTestScreen.tsx b/example/src/screens/CameraTestScreen.tsx
index 3610685..91e2a22 100644
--- a/example/src/screens/CameraTestScreen.tsx
+++ b/example/src/screens/CameraTestScreen.tsx
@@ -3,9 +3,9 @@ import MapWrapper from '../components/MapWrapper';
import ControlPanel from '../components/ControlPanel';
import type {
GoogleMapsViewRef,
- RNLatLngBounds,
RNCamera,
RNLatLng,
+ RNLatLngBounds,
} from 'react-native-google-maps-plus';
export default function CameraTestScreen() {
diff --git a/example/src/screens/CirclesScreen.tsx b/example/src/screens/CirclesScreen.tsx
index a215fa6..3379c0a 100644
--- a/example/src/screens/CirclesScreen.tsx
+++ b/example/src/screens/CirclesScreen.tsx
@@ -1,10 +1,36 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import { makeCircle } from '../utils/mapGenerators';
-import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
+import type {
+ GoogleMapsViewRef,
+ RNCircle,
+} from 'react-native-google-maps-plus';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import { useNavigation } from '@react-navigation/native';
+import { RNCircleValidator } from '../components/maptConfigDialog/validator';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function CirclesScreen() {
const mapRef = useRef(null);
- const circles = [makeCircle(1)];
- return ;
+ const navigation = useNavigation();
+ const [circle, setCircle] = useState(undefined);
+ const [dialogVisible, setDialogVisible] = useState(true);
+
+ useHeaderButton(navigation, circle ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
+ );
+
+ return (
+ <>
+
+
+ visible={dialogVisible}
+ title="Edit circle"
+ initialData={makeCircle(1)}
+ validator={RNCircleValidator}
+ onClose={() => setDialogVisible(false)}
+ onSave={(c) => setCircle(c)}
+ />
+ >
+ );
}
diff --git a/example/src/screens/CustomStyleScreen.tsx b/example/src/screens/CustomStyleScreen.tsx
index 0141db6..f61a45d 100644
--- a/example/src/screens/CustomStyleScreen.tsx
+++ b/example/src/screens/CustomStyleScreen.tsx
@@ -1,7 +1,7 @@
import React, { useMemo, useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import ControlPanel from '../components/ControlPanel';
-import { standardMapStyle, silverMapStyle } from '../utils/mapStyles';
+import { silverMapStyle, standardMapStyle } from '../utils/mapStyles';
import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
export default function CustomStyleScreen() {
diff --git a/example/src/screens/HeatmapScreen.tsx b/example/src/screens/HeatmapScreen.tsx
index 7f9fa7e..ba6b35a 100644
--- a/example/src/screens/HeatmapScreen.tsx
+++ b/example/src/screens/HeatmapScreen.tsx
@@ -1,10 +1,36 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import { makeHeatmap } from '../utils/mapGenerators';
-import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
+import type {
+ GoogleMapsViewRef,
+ RNHeatmap,
+} from 'react-native-google-maps-plus';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import { useNavigation } from '@react-navigation/native';
+import { RNHeatmapValidator } from '../components/maptConfigDialog/validator';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function HeatmapScreen() {
const mapRef = useRef(null);
- const heatmaps = [makeHeatmap(1)];
- return ;
+ const navigation = useNavigation();
+ const [heatmap, setHeatmap] = useState(undefined);
+ const [dialogVisible, setDialogVisible] = useState(true);
+
+ useHeaderButton(navigation, heatmap ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
+ );
+
+ return (
+ <>
+
+
+ visible={dialogVisible}
+ title="Edit heatmap"
+ initialData={makeHeatmap(1)}
+ validator={RNHeatmapValidator}
+ onClose={() => setDialogVisible(false)}
+ onSave={(c) => setHeatmap(c)}
+ />
+ >
+ );
}
diff --git a/example/src/screens/HomeScreen.tsx b/example/src/screens/HomeScreen.tsx
index 5c4455a..08aa896 100644
--- a/example/src/screens/HomeScreen.tsx
+++ b/example/src/screens/HomeScreen.tsx
@@ -1,5 +1,5 @@
-import React from 'react';
-import { Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
+import React, { useMemo } from 'react';
+import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native';
import type { StackNavigationProp } from '@react-navigation/stack';
import { useNavigation } from '@react-navigation/native';
import { useAppTheme } from '../theme';
@@ -23,7 +23,7 @@ const screens = [
export default function HomeScreen() {
const navigation = useNavigation>();
const theme = useAppTheme();
- const styles = getThemedStyles(theme);
+ const styles = useMemo(() => getThemedStyles(theme), [theme]);
return (
diff --git a/example/src/screens/KmlLayerScreen.tsx b/example/src/screens/KmlLayerScreen.tsx
index 66602ed..f3cf25a 100644
--- a/example/src/screens/KmlLayerScreen.tsx
+++ b/example/src/screens/KmlLayerScreen.tsx
@@ -1,11 +1,36 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import { kmlString } from '../utils/kmlData';
-import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
+import type {
+ GoogleMapsViewRef,
+ RNKMLayer,
+} from 'react-native-google-maps-plus';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import { useNavigation } from '@react-navigation/native';
+import { RNKMLayerValidator } from '../components/maptConfigDialog/validator';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function KmlLayerScreen() {
const mapRef = useRef(null);
+ const navigation = useNavigation();
+ const [kmlLayer, setKmlLayer] = useState(undefined);
+ const [dialogVisible, setDialogVisible] = useState(true);
- const kmlLayers = [{ id: '21', zIndex: 1, kmlString }];
- return ;
+ useHeaderButton(navigation, kmlLayer ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
+ );
+
+ return (
+ <>
+
+
+ visible={dialogVisible}
+ title="Edit KML layer"
+ initialData={{ id: '1', kmlString: kmlString }}
+ validator={RNKMLayerValidator}
+ onClose={() => setDialogVisible(false)}
+ onSave={(c) => setKmlLayer(c)}
+ />
+ >
+ );
}
diff --git a/example/src/screens/MarkersScreen.tsx b/example/src/screens/MarkersScreen.tsx
index 6e5c303..880c7df 100644
--- a/example/src/screens/MarkersScreen.tsx
+++ b/example/src/screens/MarkersScreen.tsx
@@ -1,47 +1,36 @@
-import React, { useMemo, useRef, useState } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
-import ControlPanel from '../components/ControlPanel';
import { makeMarker } from '../utils/mapGenerators';
import type {
GoogleMapsViewRef,
RNMarker,
} from 'react-native-google-maps-plus';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import { useNavigation } from '@react-navigation/native';
+import { RNMarkerValidator } from '../components/maptConfigDialog/validator';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function MarkersScreen() {
const mapRef = useRef(null);
- const [markers, setMarkers] = useState(
- Array.from({ length: 2 }, (_, i) => makeMarker(i + 1))
- );
+ const navigation = useNavigation();
+ const [marker, setMarker] = useState(undefined);
+ const [dialogVisible, setDialogVisible] = useState(true);
- const buttons = useMemo(
- () => [
- {
- title: 'Marker +1',
- onPress: () => setMarkers((m) => [...m, makeMarker(m.length + 1)]),
- },
- {
- title: 'Marker -1',
- onPress: () => setMarkers((m) => m.slice(0, Math.max(0, m.length - 1))),
- },
- {
- title: 'Fit to markers',
- onPress: () => {
- const coords = markers.map((m) => m.coordinate);
- mapRef.current?.setCameraToCoordinates(
- coords,
- { top: 50, left: 50, bottom: 50, right: 50 },
- true,
- 300
- );
- },
- },
- ],
- [markers]
+ useHeaderButton(navigation, marker ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
);
return (
-
-
-
+ <>
+
+
+ visible={dialogVisible}
+ title="Edit marker"
+ initialData={makeMarker(1)}
+ validator={RNMarkerValidator}
+ onClose={() => setDialogVisible(false)}
+ onSave={(c) => setMarker(c)}
+ />
+ >
);
}
diff --git a/example/src/screens/PolygonsScreen.tsx b/example/src/screens/PolygonsScreen.tsx
index 8f2df34..dfd2ec9 100644
--- a/example/src/screens/PolygonsScreen.tsx
+++ b/example/src/screens/PolygonsScreen.tsx
@@ -1,11 +1,36 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import { makePolygon } from '../utils/mapGenerators';
-import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
+import type {
+ GoogleMapsViewRef,
+ RNPolygon,
+} from 'react-native-google-maps-plus';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import { useNavigation } from '@react-navigation/native';
+import { RNPolygonValidator } from '../components/maptConfigDialog/validator';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function PolygonsScreen() {
const mapRef = useRef(null);
+ const navigation = useNavigation();
+ const [polygon, setPolygon] = useState(undefined);
+ const [dialogVisible, setDialogVisible] = useState(true);
- const polygons = [makePolygon(1)];
- return ;
+ useHeaderButton(navigation, polygon ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
+ );
+
+ return (
+ <>
+
+
+ visible={dialogVisible}
+ title="Edit polygon"
+ initialData={makePolygon(1)}
+ validator={RNPolygonValidator}
+ onClose={() => setDialogVisible(false)}
+ onSave={(c) => setPolygon(c)}
+ />
+ >
+ );
}
diff --git a/example/src/screens/PolylinesScreen.tsx b/example/src/screens/PolylinesScreen.tsx
index fa3aaff..e697fe5 100644
--- a/example/src/screens/PolylinesScreen.tsx
+++ b/example/src/screens/PolylinesScreen.tsx
@@ -1,10 +1,36 @@
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import { makePolyline } from '../utils/mapGenerators';
-import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
+import type {
+ GoogleMapsViewRef,
+ RNPolyline,
+} from 'react-native-google-maps-plus';
+import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
+import { useNavigation } from '@react-navigation/native';
+import { RNPolylineValidator } from '../components/maptConfigDialog/validator';
+import { useHeaderButton } from '../hooks/useHeaderButton';
export default function PolylinesScreen() {
const mapRef = useRef(null);
- const polylines = [makePolyline(1)];
- return ;
+ const navigation = useNavigation();
+ const [polyline, setPolyline] = useState(undefined);
+ const [dialogVisible, setDialogVisible] = useState(true);
+
+ useHeaderButton(navigation, polyline ? 'Edit' : 'Add', () =>
+ setDialogVisible(true)
+ );
+
+ return (
+ <>
+
+
+ visible={dialogVisible}
+ title="Edit polyline"
+ initialData={makePolyline(1)}
+ validator={RNPolylineValidator}
+ onClose={() => setDialogVisible(false)}
+ onSave={(c) => setPolyline(c)}
+ />
+ >
+ );
}
diff --git a/example/src/screens/SnaptshotTestScreen.tsx b/example/src/screens/SnaptshotTestScreen.tsx
index 142baf6..73c88ae 100644
--- a/example/src/screens/SnaptshotTestScreen.tsx
+++ b/example/src/screens/SnaptshotTestScreen.tsx
@@ -7,11 +7,10 @@ import type { GoogleMapsViewRef } from 'react-native-google-maps-plus';
export default function SnapshotTestScreen() {
const mapRef = useRef(null);
+ const theme = useAppTheme();
const [snapshotUri, setSnapshotUri] = useState(null);
const [visible, setVisible] = useState(false);
- const theme = useAppTheme();
-
const buttons = useMemo(
() => [
{
@@ -57,7 +56,7 @@ export default function SnapshotTestScreen() {
[]
);
- const styles = getThemedStyles(theme);
+ const styles = useMemo(() => getThemedStyles(theme), [theme]);
return (
diff --git a/example/src/screens/StressTestScreen.tsx b/example/src/screens/StressTestScreen.tsx
index 27ea360..12a1fc2 100644
--- a/example/src/screens/StressTestScreen.tsx
+++ b/example/src/screens/StressTestScreen.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import MapWrapper from '../components/MapWrapper';
import ControlPanel from '../components/ControlPanel';
-import { makeMarker } from '../utils/mapGenerators';
+import { makeRandomMarkerForStressTest } from '../utils/mapGenerators';
import type {
GoogleMapsViewRef,
RNMarker,
@@ -18,7 +18,8 @@ export default function StressTestScreen() {
setMarkers((m) => {
const next = [...m];
while (next.length > 100) next.shift();
- for (let i = 0; i < 500; i++) next.push(makeMarker(next.length + 1));
+ for (let i = 0; i < 500; i++)
+ next.push(makeRandomMarkerForStressTest(next.length + 1));
return next;
});
}, 100);
@@ -46,7 +47,11 @@ export default function StressTestScreen() {
},
{
title: 'Marker +1',
- onPress: () => setMarkers((m) => [...m, makeMarker(m.length + 1)]),
+ onPress: () =>
+ setMarkers((m) => [
+ ...m,
+ makeRandomMarkerForStressTest(m.length + 1),
+ ]),
},
{
title: 'Marker -1',
diff --git a/example/src/theme.ts b/example/src/theme.ts
index c881ee3..7b2cf42 100644
--- a/example/src/theme.ts
+++ b/example/src/theme.ts
@@ -7,6 +7,13 @@ export const lightTheme = {
textPrimary: '#111827',
textOnAccent: '#FFFFFF',
shadow: '#000000',
+ overlay: 'rgba(0,0,0,0.5)',
+ label: '#222',
+ placeholder: '#9CA3AF',
+ border: '#D1D5DB',
+ inputBg: '#FFFFFF',
+ buttonBg: '#3B82F6',
+ cancelBg: '#9CA3AF',
};
export const darkTheme = {
@@ -16,6 +23,13 @@ export const darkTheme = {
textPrimary: '#FFFFFF',
textOnAccent: '#FFFFFF',
shadow: '#000000',
+ overlay: 'rgba(0,0,0,0.6)',
+ label: '#D1D5DB',
+ placeholder: '#6B7280',
+ border: '#3F3F46',
+ inputBg: '#2C2C2E',
+ buttonBg: '#2D6BE9',
+ cancelBg: '#4B5563',
};
export type AppTheme = typeof lightTheme;
diff --git a/example/src/types/basicMapConfig.ts b/example/src/types/basicMapConfig.ts
new file mode 100644
index 0000000..065f5b2
--- /dev/null
+++ b/example/src/types/basicMapConfig.ts
@@ -0,0 +1,28 @@
+import type {
+ RNCamera,
+ RNLocationConfig,
+ RNMapPadding,
+ RNMapType,
+ RNMapUiSettings,
+ RNMapZoomConfig,
+ RNUserInterfaceStyle,
+} from 'react-native-google-maps-plus';
+
+export type RNBasicMapConfig = {
+ initialProps?: {
+ mapId?: string;
+ liteMode?: boolean;
+ camera?: RNCamera;
+ };
+ uiSettings?: RNMapUiSettings;
+ myLocationEnabled?: boolean;
+ buildingEnabled?: boolean;
+ trafficEnabled?: boolean;
+ indoorEnabled?: boolean;
+ customMapStyle?: string;
+ userInterfaceStyle?: RNUserInterfaceStyle;
+ mapZoomConfig?: RNMapZoomConfig;
+ mapPadding?: RNMapPadding;
+ mapType?: RNMapType;
+ locationConfig?: RNLocationConfig;
+};
diff --git a/example/src/types/navigation.ts b/example/src/types/navigation.ts
index 0cdaa46..bf29d2f 100644
--- a/example/src/types/navigation.ts
+++ b/example/src/types/navigation.ts
@@ -13,7 +13,7 @@ export type RootStackParamList = {
IndoorLevelMap: undefined;
Camera: undefined;
Snapshot: undefined;
- StressTest: undefined;
+ Stress: undefined;
};
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
diff --git a/example/src/utils/kmlData.ts b/example/src/utils/kmlData.ts
index b9ea623..b8ce087 100644
--- a/example/src/utils/kmlData.ts
+++ b/example/src/utils/kmlData.ts
@@ -1,53 +1 @@
-export const kmlString = `
-
-
- Example KML Data
- Example with marker, polygon and circle near San Francisco center
-
- Center Point
- -122.4194,37.7749,0
-
-
- Example Polygon
-
-
-
-
-
- -122.4244,37.7784,0
- -122.4144,37.7784,0
- -122.4144,37.7714,0
- -122.4244,37.7714,0
- -122.4244,37.7784,0
-
-
-
-
-
-
- Approximate Circle
-
-
-
-
-
- -122.4194,37.7770,0
- -122.4174,37.7770,0
- -122.4174,37.7730,0
- -122.4194,37.7730,0
- -122.4214,37.7730,0
- -122.4214,37.7770,0
- -122.4194,37.7770,0
-
-
-
-
-
-
-;`;
+export const kmlString = `Example KML DataExample with marker, polygon and circle near San Francisco centerCenter Point-122.4194,37.7749,0Example Polygon-122.4244,37.7784,0 -122.4144,37.7784,0 -122.4144,37.7714,0 -122.4244,37.7714,0 -122.4244,37.7784,0Approximate Circle-122.4194,37.7770,0 -122.4174,37.7770,0 -122.4174,37.7730,0 -122.4194,37.7730,0 -122.4214,37.7730,0 -122.4214,37.7770,0 -122.4194,37.7770,0`;
diff --git a/example/src/utils/mapGenerators.ts b/example/src/utils/mapGenerators.ts
index af0720d..060d17f 100644
--- a/example/src/utils/mapGenerators.ts
+++ b/example/src/utils/mapGenerators.ts
@@ -1,9 +1,9 @@
import type {
+ RNCircle,
+ RNHeatmap,
RNMarker,
RNPolygon,
RNPolyline,
- RNCircle,
- RNHeatmap,
} from 'react-native-google-maps-plus';
export function randomColor() {
@@ -15,8 +15,12 @@ export function randomColor() {
);
}
-export function makeSvgIcon(width: number, height: number): string {
- const color = randomColor();
+export function makeSvgIcon(
+ width: number,
+ height: number,
+ color?: string
+): string {
+ color = color ?? randomColor();
return `