diff --git a/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt b/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt index abdd2a46..e9689180 100644 --- a/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt +++ b/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt @@ -243,6 +243,49 @@ class GoogleMapViewTests { } } + @Test + fun testAdvancedMarkerInfoWindowContentDoesNotCrash() { + // Regression test for https://github.com/googlemaps/android-maps-compose/issues/822: + // AdvancedMarker didn't expose infoContent/infoWindow customization, even though the + // underlying implementation already supported it. Custom info window content is + // rendered to a bitmap by ComposeInfoWindowAdapter (not present in the Compose + // semantics tree), so this asserts the composable wires up and shows without crashing + // rather than asserting on-screen content. + lateinit var markerState: MarkerState + + initMap { + markerState = rememberUpdatedMarkerState(position = startingPosition) + AdvancedMarkerInfoWindowContent(state = markerState) { + Text(text = "custom advanced marker info window") + } + } + + composeTestRule.runOnUiThread { + markerState.showInfoWindow() + } + composeTestRule.waitForIdle() + } + + @Test + fun testAdvancedMarkerInfoWindowDoesNotCrash() { + // Regression test for https://github.com/googlemaps/android-maps-compose/issues/822: + // see testAdvancedMarkerInfoWindowContentDoesNotCrash for why this doesn't assert + // on-screen content. + lateinit var markerState: MarkerState + + initMap { + markerState = rememberUpdatedMarkerState(position = startingPosition) + AdvancedMarkerInfoWindow(state = markerState) { + Text(text = "custom advanced marker whole info window") + } + } + + composeTestRule.runOnUiThread { + markerState.showInfoWindow() + } + composeTestRule.waitForIdle() + } + @Test(expected = IllegalStateException::class) fun testMarkerStateInsideMarkerInfoWindowComposableCannotBeReused() { initMap { diff --git a/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/AdvancedMarkersActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/AdvancedMarkersActivity.kt index 54db3e22..9746e3b4 100644 --- a/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/AdvancedMarkersActivity.kt +++ b/maps-app/src/main/java/com/google/maps/android/compose/markerexamples/AdvancedMarkersActivity.kt @@ -24,13 +24,18 @@ import android.widget.TextView import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color as ComposeColor +import androidx.compose.ui.unit.dp import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.MapsInitializer import com.google.android.gms.maps.OnMapsSdkInitializedCallback @@ -40,6 +45,7 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.PinConfig import com.google.maps.android.compose.AdvancedMarker +import com.google.maps.android.compose.AdvancedMarkerInfoWindowContent import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.MapProperties import com.google.maps.android.compose.MapType @@ -138,13 +144,24 @@ class AdvancedMarkersActivity : ComponentActivity(), OnMapsSdkInitializedCallbac .setBorderColor(Color.WHITE) .build() - AdvancedMarker( + // AdvancedMarkerInfoWindowContent lets you customize the info window's + // content while keeping the default info window frame. Tap this marker + // to see it. + AdvancedMarkerInfoWindowContent( state = marker1State, onClick = markerClick, collisionBehavior = 1, pinConfig = pinConfig, - title="Marker 1" - ) + title = "Marker 1" + ) { + Box( + modifier = Modifier + .background(ComposeColor.Yellow) + .padding(8.dp) + ) { + Text("Custom advanced marker info window!") + } + } val glyphOne = PinConfig.Glyph("A", Color.BLACK) val pinConfig2 = PinConfig.builder() diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt b/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt index f2333f8c..40d329b8 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt @@ -778,6 +778,170 @@ public fun AdvancedMarker( ) } +/** + * A composable for an advanced marker on the map wherein its entire info window can be + * customized. If this customization is not required, use + * [com.google.maps.android.compose.AdvancedMarker]. + * + * @param state the [MarkerState] to be used to control or observe the marker + * state such as its position and info window + * @param contentDescription the content description for accessibility purposes + * @param alpha the alpha (opacity) of the marker + * @param anchor the anchor for the marker image + * @param draggable sets the draggability for the marker + * @param flat sets if the marker should be flat against the map + * @param infoWindowAnchor the anchor point of the info window on the marker image + * @param rotation the rotation of the marker in degrees clockwise about the marker's anchor point + * @param snippet the snippet for the marker + * @param tag optional tag to associate with the marker + * @param title the title for the marker + * @param visible the visibility of the marker + * @param zIndex the z-index of the marker + * @param onClick a lambda invoked when the marker is clicked + * @param onInfoWindowClick a lambda invoked when the marker's info window is clicked + * @param onInfoWindowClose a lambda invoked when the marker's info window is closed + * @param onInfoWindowLongClick a lambda invoked when the marker's info window is long clicked + * @param icon sets the icon for the marker + * @param pinConfig the PinConfig object that will be used for the advanced marker + * @param iconView the custom view to be used on the advanced marker + * @param collisionBehavior the expected collision behavior + * @param content optional composable lambda expression for customizing the + * info window's content + */ +@Composable +@GoogleMapComposable +public fun AdvancedMarkerInfoWindow( + state: MarkerState = rememberUpdatedMarkerState(), + contentDescription: String? = "", + alpha: Float = 1.0f, + anchor: Offset = Offset(0.5f, 1.0f), + draggable: Boolean = false, + flat: Boolean = false, + infoWindowAnchor: Offset = Offset(0.5f, 0.0f), + rotation: Float = 0.0f, + snippet: String? = null, + tag: Any? = null, + title: String? = null, + visible: Boolean = true, + zIndex: Float = 0.0f, + onClick: (Marker) -> Boolean = { false }, + onInfoWindowClick: (Marker) -> Unit = {}, + onInfoWindowClose: (Marker) -> Unit = {}, + onInfoWindowLongClick: (Marker) -> Unit = {}, + icon: BitmapDescriptor? = null, + pinConfig: PinConfig? = null, + iconView: View? = null, + collisionBehavior: Int = AdvancedMarkerOptions.CollisionBehavior.REQUIRED, + content: (@UiComposable @Composable (Marker) -> Unit)? = null, +) { + AdvancedMarkerImpl( + state = state, + contentDescription = contentDescription, + alpha = alpha, + anchor = anchor, + draggable = draggable, + flat = flat, + infoWindowAnchor = infoWindowAnchor, + rotation = rotation, + snippet = snippet, + tag = tag, + title = title, + visible = visible, + zIndex = zIndex, + onClick = onClick, + onInfoWindowClick = onInfoWindowClick, + onInfoWindowClose = onInfoWindowClose, + onInfoWindowLongClick = onInfoWindowLongClick, + icon = icon, + pinConfig = pinConfig, + iconView = iconView, + collisionBehavior = collisionBehavior, + infoWindow = content, + ) +} + +/** + * A composable for an advanced marker on the map wherein its info window contents can be + * customized. If this customization is not required, use + * [com.google.maps.android.compose.AdvancedMarker]. + * + * @param state the [MarkerState] to be used to control or observe the marker + * state such as its position and info window + * @param contentDescription the content description for accessibility purposes + * @param alpha the alpha (opacity) of the marker + * @param anchor the anchor for the marker image + * @param draggable sets the draggability for the marker + * @param flat sets if the marker should be flat against the map + * @param infoWindowAnchor the anchor point of the info window on the marker image + * @param rotation the rotation of the marker in degrees clockwise about the marker's anchor point + * @param snippet the snippet for the marker + * @param tag optional tag to associate with the marker + * @param title the title for the marker + * @param visible the visibility of the marker + * @param zIndex the z-index of the marker + * @param onClick a lambda invoked when the marker is clicked + * @param onInfoWindowClick a lambda invoked when the marker's info window is clicked + * @param onInfoWindowClose a lambda invoked when the marker's info window is closed + * @param onInfoWindowLongClick a lambda invoked when the marker's info window is long clicked + * @param icon sets the icon for the marker + * @param pinConfig the PinConfig object that will be used for the advanced marker + * @param iconView the custom view to be used on the advanced marker + * @param collisionBehavior the expected collision behavior + * @param content optional composable lambda expression for customizing the + * info window's content + */ +@Composable +@GoogleMapComposable +public fun AdvancedMarkerInfoWindowContent( + state: MarkerState = rememberUpdatedMarkerState(), + contentDescription: String? = "", + alpha: Float = 1.0f, + anchor: Offset = Offset(0.5f, 1.0f), + draggable: Boolean = false, + flat: Boolean = false, + infoWindowAnchor: Offset = Offset(0.5f, 0.0f), + rotation: Float = 0.0f, + snippet: String? = null, + tag: Any? = null, + title: String? = null, + visible: Boolean = true, + zIndex: Float = 0.0f, + onClick: (Marker) -> Boolean = { false }, + onInfoWindowClick: (Marker) -> Unit = {}, + onInfoWindowClose: (Marker) -> Unit = {}, + onInfoWindowLongClick: (Marker) -> Unit = {}, + icon: BitmapDescriptor? = null, + pinConfig: PinConfig? = null, + iconView: View? = null, + collisionBehavior: Int = AdvancedMarkerOptions.CollisionBehavior.REQUIRED, + content: (@UiComposable @Composable (Marker) -> Unit)? = null, +) { + AdvancedMarkerImpl( + state = state, + contentDescription = contentDescription, + alpha = alpha, + anchor = anchor, + draggable = draggable, + flat = flat, + infoWindowAnchor = infoWindowAnchor, + rotation = rotation, + snippet = snippet, + tag = tag, + title = title, + visible = visible, + zIndex = zIndex, + onClick = onClick, + onInfoWindowClick = onInfoWindowClick, + onInfoWindowClose = onInfoWindowClose, + onInfoWindowLongClick = onInfoWindowLongClick, + icon = icon, + pinConfig = pinConfig, + iconView = iconView, + collisionBehavior = collisionBehavior, + infoContent = content, + ) +} + /** * Internal implementation for an advanced marker on a Google map. *