Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ fun GoogleMapView(
var shouldAnimateZoom by remember { mutableStateOf(true) }
var ticker by remember { mutableIntStateOf(0) }
var mapProperties by remember {
mutableStateOf(MapProperties(mapType = MapType.NORMAL))
mutableStateOf(MapProperties(isTrafficEnabled = true, mapType = MapType.NORMAL))
}
var mapVisible by remember { mutableStateOf(true) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
Expand Down Expand Up @@ -242,8 +241,6 @@ private fun CoroutineScope.launchSubcomposition(
composition.setContent {
MapUpdater(mapUpdaterState)

MapClickListenerUpdater()

CompositionLocalProvider(
LocalCameraPositionState provides mapUpdaterState.cameraPositionState,
content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ internal class MapApplier(
}

internal fun attachClickListeners() {
with(mapClickListeners) {
map.setOnIndoorStateChangeListener(object : GoogleMap.OnIndoorStateChangeListener {
override fun onIndoorBuildingFocused() =
indoorStateChangeListener.onIndoorBuildingFocused()

override fun onIndoorLevelActivated(building: com.google.android.gms.maps.model.IndoorBuilding) =
indoorStateChangeListener.onIndoorLevelActivated(building)
})
map.setOnMapClickListener { onMapClick?.invoke(it) }
map.setOnMapLongClickListener { onMapLongClick?.invoke(it) }
map.setOnMapLoadedCallback { onMapLoaded?.invoke() }
map.setOnMyLocationButtonClickListener { onMyLocationButtonClick?.invoke() ?: false }
map.setOnMyLocationClickListener { onMyLocationClick?.invoke(it) }
map.setOnPoiClickListener { onPOIClick?.invoke(it) }
}

map.setOnCircleClickListener { circle ->
decorations.findInputCallback<CircleNode, Circle, Unit>(
nodeMatchPredicate = { it.circle == circle },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,129 +68,3 @@ internal class MapClickListeners {
var onMyLocationClick: ((Location) -> Unit)? by mutableStateOf(null)
var onPOIClick: ((PointOfInterest) -> Unit)? by mutableStateOf(null)
}

/**
* @param L GoogleMap click listener type, e.g. [OnMapClickListener]
*/
internal class MapClickListenerNode<L : Any>(
private val map: GoogleMap,
private val setter: GoogleMap.(L?) -> Unit,
private val listener: L
) : MapNode {
override fun onAttached() = setListener(listener)
override fun onRemoved() = setListener(null)
override fun onCleared() = setListener(null)

private fun setListener(listenerOrNull: L?) = map.setter(listenerOrNull)
}

@Suppress("ComplexRedundantLet")
@Composable
internal fun MapClickListenerUpdater() {
// The mapClickListeners container object is not allowed to ever change
val mapClickListeners = (currentComposer.applier as MapApplier).mapClickListeners

with(mapClickListeners) {
::indoorStateChangeListener.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnIndoorStateChangeListener,
object : OnIndoorStateChangeListener {
override fun onIndoorBuildingFocused() =
callback().onIndoorBuildingFocused()

override fun onIndoorLevelActivated(building: IndoorBuilding) =
callback().onIndoorLevelActivated(building)
}
)
}

::onMapClick.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnMapClickListener,
OnMapClickListener { callback()?.invoke(it) }
)
}

::onMapLongClick.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnMapLongClickListener,
OnMapLongClickListener { callback()?.invoke(it) }
)
}

::onMapLoaded.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnMapLoadedCallback,
OnMapLoadedCallback { callback()?.invoke() }
)
}

::onMyLocationButtonClick.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnMyLocationButtonClickListener,
OnMyLocationButtonClickListener { callback()?.invoke() ?: false }
)
}

::onMyLocationClick.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnMyLocationClickListener,
OnMyLocationClickListener { callback()?.invoke(it) }
)
}

::onPOIClick.let { callback ->
MapClickListenerComposeNode(
callback,
GoogleMap::setOnPoiClickListener,
OnPoiClickListener { callback()?.invoke(it) }
)
}
}
}

/**
* Encapsulates the ComposeNode factory lambda as a recomposition optimization.
*
* @param L GoogleMap click listener type, e.g. [OnMapClickListener]
* @param callback a property reference to the callback lambda, i.e.
* invoking it returns the callback lambda
* @param setter a reference to a GoogleMap setter method, e.g. `setOnMapClickListener()`
* @param listener must include a call to `callback()` inside the listener
* to use the most up-to-date recomposed version of the callback lambda;
* However, the resulting callback reference might actually be null due to races;
* the caller must guard against this case.
*
*/
@Composable
@NonRestartableComposable
private fun <L : Any> MapClickListenerComposeNode(
callback: () -> Any?,
setter: GoogleMap.(L?) -> Unit,
listener: L
) {
val mapApplier = currentComposer.applier as MapApplier

MapClickListenerComposeNode(callback) { MapClickListenerNode(mapApplier.map, setter, listener) }
}

@Composable
@GoogleMapComposable
private fun MapClickListenerComposeNode(
callback: () -> Any?,
factory: () -> MapClickListenerNode<*>
) {
// Setting a GoogleMap listener may have side effects, so we unset it as needed.
// However, the listener is reset only when the corresponding callback lambda
// toggles between null and non-null. This is to avoid potential performance problems
// when callbacks recompose rapidly; setting GoogleMap listeners could potentially be
// expensive due to synchronization, etc. GoogleMap listeners are not designed with a
// use case of rapid recomposition in mind.
if (callback() != null) ComposeNode<MapClickListenerNode<*>, MapApplier>(factory) {}
}