diff --git a/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Extensions.kt b/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Extensions.kt index 45961d3..fe69667 100644 --- a/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Extensions.kt +++ b/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Extensions.kt @@ -22,7 +22,6 @@ import com.google.maps.android.compose.ComposeMapColorScheme import com.google.maps.android.compose.MapProperties as GoogleMapProperties import com.google.maps.android.compose.MapType import com.google.maps.android.compose.MapUiSettings as GoogleMapUiSettings -import com.google.maps.android.compose.MarkerState import com.google.maps.android.data.geojson.GeoJsonFeature import com.google.maps.android.data.geojson.GeoJsonLayer as GoogleGeoJsonLayer import com.google.maps.android.data.geojson.GeoJsonLineString @@ -61,19 +60,18 @@ internal fun GoogleCameraPosition.toCameraPosition() = ) /** - * Converts MapMarker to Google Maps MarkerState. + * Converts Coordinates to Google Maps LatLng. * - * @return MarkerState with position coordinates + * @return LatLng with latitude and longitude */ -internal fun Marker.toGoogleMapsMarkerState() = - MarkerState(position = LatLng(coordinates.latitude, coordinates.longitude)) +internal fun Coordinates.toGoogleMapsLatLng() = LatLng(latitude, longitude) /** - * Converts Coordinates to Google Maps LatLng. + * Converts Google Maps LatLng back to [Coordinates]. * - * @return LatLng with latitude and longitude + * @return Coordinates with latitude and longitude */ -internal fun Coordinates.toGoogleMapsLatLng(): LatLng = LatLng(latitude, longitude) +internal fun LatLng.toCoordinates() = Coordinates(latitude, longitude) /** * Converts MapTheme to native ComposeMapColorScheme. diff --git a/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Map.kt b/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Map.kt index 2a7a6d1..a536b54 100644 --- a/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Map.kt +++ b/kmp-maps/core/src/androidMain/kotlin/com/swmansion/kmpmaps/core/Map.kt @@ -24,6 +24,7 @@ import com.google.maps.android.compose.MapEffect import com.google.maps.android.compose.MapsComposeExperimentalApi import com.google.maps.android.compose.Marker import com.google.maps.android.compose.MarkerComposable +import com.google.maps.android.compose.MarkerState import com.google.maps.android.compose.Polygon import com.google.maps.android.compose.Polyline import com.google.maps.android.compose.clustering.Clustering @@ -46,6 +47,7 @@ public actual fun Map( polylines: List, onCameraMove: ((CameraPosition) -> Unit)?, onMarkerClick: ((Marker) -> Unit)?, + onMarkerDragEnd: ((Marker) -> Unit)?, onCircleClick: ((Circle) -> Unit)?, onPolygonClick: ((Polygon) -> Unit)?, onPolylineClick: ((Polyline) -> Unit)?, @@ -173,11 +175,32 @@ public actual fun Map( } else { markers.forEach { marker -> key(marker.getId(), marker.contentId) { + val markerState = + remember(marker.getId()) { + MarkerState(marker.coordinates.toGoogleMapsLatLng()) + } + + LaunchedEffect(marker.coordinates) { + val newLatLng = marker.coordinates.toGoogleMapsLatLng() + if (markerState.position != newLatLng) { + markerState.position = newLatLng + } + } + val content = customMarkerContent[marker.contentId] + if (marker.androidMarkerOptions.draggable) { + LaunchedEffect(markerState.isDragging) { + if (!markerState.isDragging) { + marker.coordinates = markerState.position.toCoordinates() + onMarkerDragEnd?.invoke(marker) + } + } + } + if (content != null) { MarkerComposable( - state = marker.toGoogleMapsMarkerState(), + state = markerState, title = marker.title, anchor = marker.androidMarkerOptions.anchor.toOffset(), draggable = marker.androidMarkerOptions.draggable, @@ -191,7 +214,7 @@ public actual fun Map( ) } else { Marker( - state = marker.toGoogleMapsMarkerState(), + state = markerState, title = marker.title, anchor = marker.androidMarkerOptions.anchor.toOffset(), draggable = marker.androidMarkerOptions.draggable, diff --git a/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/AndroidMapTypes.kt b/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/AndroidMapTypes.kt index 79d0455..d931a94 100644 --- a/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/AndroidMapTypes.kt +++ b/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/AndroidMapTypes.kt @@ -41,6 +41,9 @@ public data class AndroidUISettings( * @property draggable Whether the marker can be dragged by the user * @property snippet Additional text displayed below the title * @property zIndex The z-index for layering markers + * + * Note: In the current Android implementation, draggability is only supported when + * [ClusterSettings.enabled] is set to `false`. */ @Serializable public data class AndroidMarkerOptions( diff --git a/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/Map.kt b/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/Map.kt index c9b3307..4b9ca43 100644 --- a/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/Map.kt +++ b/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/Map.kt @@ -95,6 +95,7 @@ import androidx.compose.ui.Modifier * @param polylines List of polylines to display on the map * @param onCameraMove Callback invoked when the map camera position changes due to user interaction * @param onMarkerClick Callback invoked when a marker is clicked + * @param onMarkerDragEnd Callback invoked when dragging a marker completes (drag operation ends) * @param onCircleClick Callback invoked when a circle is clicked * @param onPolygonClick Callback invoked when a polygon is clicked * @param onPolylineClick Callback invoked when a polyline is clicked @@ -103,8 +104,8 @@ import androidx.compose.ui.Modifier * @param onPOIClick Callback invoked when the user clicks on a Point of Interest * @param onMapLoaded Callback invoked when the map has finished loading * @param geoJsonLayers List of GeoJSON layers to display on the map - * @param customMarkerContent Map of content IDs to Composable functions for custom marker content. - * @param webCustomMarkerContent Map of content IDs to a function returning an HTML string. + * @param customMarkerContent Map of content IDs to Composable functions for custom marker content + * @param webCustomMarkerContent Map of content IDs to a function returning an HTML string */ @Composable public expect fun Map( @@ -119,6 +120,7 @@ public expect fun Map( polylines: List = emptyList(), onCameraMove: ((CameraPosition) -> Unit)? = null, onMarkerClick: ((Marker) -> Unit)? = null, + onMarkerDragEnd: ((Marker) -> Unit)? = null, onCircleClick: ((Circle) -> Unit)? = null, onPolygonClick: ((Polygon) -> Unit)? = null, onPolylineClick: ((Polyline) -> Unit)? = null, diff --git a/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/MapTypes.kt b/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/MapTypes.kt index c377213..333d22a 100644 --- a/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/MapTypes.kt +++ b/kmp-maps/core/src/commonMain/kotlin/com/swmansion/kmpmaps/core/MapTypes.kt @@ -79,7 +79,14 @@ public data class MapUISettings( /** * Represents a marker on the map. * - * @property coordinates The geographical coordinates where the marker should be placed + * @property coordinates The geographical coordinates where the marker should be placed. + * + * Note on Mutability: Although this is a `var` to allow the library to update positions during + * dragging, you should treat it as immutable. To move the marker programmatically, pass a new + * [Marker] instance to the map. + * + * Directly mutating this field may not trigger UI updates, especially on iOS. + * * @property title The title text displayed when the marker is tapped * @property androidMarkerOptions Android-specific options for customizing a marker * @property iosMarkerOptions iOS-specific options for customizing a marker @@ -92,7 +99,7 @@ public data class MapUISettings( @JsonIgnoreUnknownKeys @OptIn(ExperimentalSerializationApi::class) public data class Marker( - val coordinates: Coordinates, + var coordinates: Coordinates, val title: String? = "No title was provided", val androidMarkerOptions: AndroidMarkerOptions = AndroidMarkerOptions(), val iosMarkerOptions: IosMarkerOptions? = null, diff --git a/kmp-maps/core/src/iosMain/kotlin/com/swmansion/kmpmaps/core/Map.kt b/kmp-maps/core/src/iosMain/kotlin/com/swmansion/kmpmaps/core/Map.kt index e94a493..25766f2 100644 --- a/kmp-maps/core/src/iosMain/kotlin/com/swmansion/kmpmaps/core/Map.kt +++ b/kmp-maps/core/src/iosMain/kotlin/com/swmansion/kmpmaps/core/Map.kt @@ -41,6 +41,7 @@ public actual fun Map( polylines: List, onCameraMove: ((CameraPosition) -> Unit)?, onMarkerClick: ((Marker) -> Unit)?, + onMarkerDragEnd: ((Marker) -> Unit)?, onCircleClick: ((Circle) -> Unit)?, onPolygonClick: ((Polygon) -> Unit)?, onPolylineClick: ((Polyline) -> Unit)?, diff --git a/kmp-maps/core/src/jvmMain/kotlin/com/swmansion/kmpmaps/core/Map.kt b/kmp-maps/core/src/jvmMain/kotlin/com/swmansion/kmpmaps/core/Map.kt index 0907769..10c74a4 100644 --- a/kmp-maps/core/src/jvmMain/kotlin/com/swmansion/kmpmaps/core/Map.kt +++ b/kmp-maps/core/src/jvmMain/kotlin/com/swmansion/kmpmaps/core/Map.kt @@ -30,6 +30,7 @@ public actual fun Map( polylines: List, onCameraMove: ((CameraPosition) -> Unit)?, onMarkerClick: ((Marker) -> Unit)?, + onMarkerDragEnd: ((Marker) -> Unit)?, onCircleClick: ((Circle) -> Unit)?, onPolygonClick: ((Polygon) -> Unit)?, onPolylineClick: ((Polyline) -> Unit)?, diff --git a/kmp-maps/google-maps/src/androidMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt b/kmp-maps/google-maps/src/androidMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt index 1482efb..a8ca89d 100644 --- a/kmp-maps/google-maps/src/androidMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt +++ b/kmp-maps/google-maps/src/androidMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt @@ -28,6 +28,7 @@ public actual fun Map( polylines: List, onCameraMove: ((CameraPosition) -> Unit)?, onMarkerClick: ((Marker) -> Unit)?, + onMarkerDragEnd: ((Marker) -> Unit)?, onCircleClick: ((Circle) -> Unit)?, onPolygonClick: ((Polygon) -> Unit)?, onPolylineClick: ((Polyline) -> Unit)?, @@ -51,6 +52,7 @@ public actual fun Map( polylines, onCameraMove, onMarkerClick, + onMarkerDragEnd, onCircleClick, onPolygonClick, onPolylineClick, diff --git a/kmp-maps/google-maps/src/commonMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt b/kmp-maps/google-maps/src/commonMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt index 3f041c5..6559493 100644 --- a/kmp-maps/google-maps/src/commonMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt +++ b/kmp-maps/google-maps/src/commonMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt @@ -34,6 +34,7 @@ import com.swmansion.kmpmaps.core.Polyline * @param polylines List of polylines to display on the map * @param onCameraMove Callback invoked when the map camera position changes due to user interaction * @param onMarkerClick Callback invoked when a marker is clicked + * @param onMarkerDragEnd Callback invoked when a marker drag operation ends * @param onCircleClick Callback invoked when a circle is clicked * @param onPolygonClick Callback invoked when a polygon is clicked * @param onPolylineClick Callback invoked when a polyline is clicked @@ -58,6 +59,7 @@ public expect fun Map( polylines: List = emptyList(), onCameraMove: ((CameraPosition) -> Unit)? = null, onMarkerClick: ((Marker) -> Unit)? = null, + onMarkerDragEnd: ((Marker) -> Unit)? = null, onCircleClick: ((Circle) -> Unit)? = null, onPolygonClick: ((Polygon) -> Unit)? = null, onPolylineClick: ((Polyline) -> Unit)? = null, diff --git a/kmp-maps/google-maps/src/iosMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt b/kmp-maps/google-maps/src/iosMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt index d5d30fa..56eb555 100644 --- a/kmp-maps/google-maps/src/iosMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt +++ b/kmp-maps/google-maps/src/iosMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt @@ -56,6 +56,7 @@ public actual fun Map( polylines: List, onCameraMove: ((CameraPosition) -> Unit)?, onMarkerClick: ((Marker) -> Unit)?, + onMarkerDragEnd: ((Marker) -> Unit)?, onCircleClick: ((Circle) -> Unit)?, onPolygonClick: ((Polygon) -> Unit)?, onPolylineClick: ((Polyline) -> Unit)?, diff --git a/kmp-maps/google-maps/src/jvmMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt b/kmp-maps/google-maps/src/jvmMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt index 49022ea..4138a41 100644 --- a/kmp-maps/google-maps/src/jvmMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt +++ b/kmp-maps/google-maps/src/jvmMain/kotlin/com/swmansion/kmpmaps/googlemaps/Map.kt @@ -28,6 +28,7 @@ public actual fun Map( polylines: List, onCameraMove: ((CameraPosition) -> Unit)?, onMarkerClick: ((Marker) -> Unit)?, + onMarkerDragEnd: ((Marker) -> Unit)?, onCircleClick: ((Circle) -> Unit)?, onPolygonClick: ((Polygon) -> Unit)?, onPolylineClick: ((Polyline) -> Unit)?, @@ -51,6 +52,7 @@ public actual fun Map( polylines, onCameraMove, onMarkerClick, + onMarkerDragEnd, onCircleClick, onPolygonClick, onPolylineClick, diff --git a/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/Examples.kt b/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/Examples.kt index b7337b2..5c3dad7 100644 --- a/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/Examples.kt +++ b/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/Examples.kt @@ -49,7 +49,8 @@ val exampleMarkers = Marker( coordinates = softwareMansionPin, title = "Software Mansion", - androidMarkerOptions = AndroidMarkerOptions(snippet = "Software house"), + androidMarkerOptions = + AndroidMarkerOptions(snippet = "Software house", draggable = true), contentId = "swmansion_marker", ), Marker( diff --git a/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/MapWrapper.kt b/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/MapWrapper.kt index 761c741..bec8c14 100644 --- a/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/MapWrapper.kt +++ b/sample/src/commonMain/kotlin/com/swmansion/kmpmaps/sample/MapWrapper.kt @@ -27,14 +27,14 @@ internal data class MapOptions( val cameraPosition: CameraPosition = CameraPosition( coordinates = Coordinates(latitude = 50.0619, longitude = 19.9373), - zoom = 10f, + zoom = 14f, ), val showAllComponents: Boolean = true, val useGoogleMapsMapView: Boolean = true, val showPointGeoJson: Boolean = false, val showPolygonGeoJson: Boolean = false, val showLineGeoJson: Boolean = false, - val clusteringEnabled: Boolean = true, + val clusteringEnabled: Boolean = false, ) @Composable @@ -91,7 +91,9 @@ internal fun MapWrapper( onPOIClick = { println("POI clicked: $it") }, onMapLoaded = { println("Map loaded") }, onMapLongClick = { println("Map long clicked: $it") }, - onMarkerClick = { marker -> println("Marker clicked: ${marker.title}") }, + onMarkerClick = { marker -> + println("Marker clicked: ${marker.title} ${marker.coordinates}") + }, onMapClick = { coordinates -> println("Map clicked at: $coordinates") }, geoJsonLayers = geoJsonLayers, customMarkerContent = customMarkerContent, @@ -118,6 +120,7 @@ private fun Map( polylines: List = emptyList(), onCameraMove: ((CameraPosition) -> Unit)? = null, onMarkerClick: ((Marker) -> Unit)? = null, + onMarkerDragEnd: ((Marker) -> Unit)? = null, onCircleClick: ((Circle) -> Unit)? = null, onPolygonClick: ((Polygon) -> Unit)? = null, onPolylineClick: ((Polyline) -> Unit)? = null, @@ -143,6 +146,7 @@ private fun Map( polylines = polylines, onCameraMove = onCameraMove, onMarkerClick = onMarkerClick, + onMarkerDragEnd = onMarkerDragEnd, onCircleClick = onCircleClick, onPolygonClick = onPolygonClick, onPolylineClick = onPolylineClick, @@ -167,6 +171,7 @@ private fun Map( polylines = polylines, onCameraMove = onCameraMove, onMarkerClick = onMarkerClick, + onMarkerDragEnd = onMarkerDragEnd, onCircleClick = onCircleClick, onPolygonClick = onPolygonClick, onPolylineClick = onPolylineClick,