diff --git a/lib-compose/build.gradle.kts b/lib-compose/build.gradle.kts index f6f15214..50149a16 100644 --- a/lib-compose/build.gradle.kts +++ b/lib-compose/build.gradle.kts @@ -80,7 +80,7 @@ dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.android) testImplementation(libs.kotlinx.coroutines.test) - implementation(libs.kotlinx.collections.immutable) + api(libs.kotlinx.collections.immutable) // Compose implementation(platform(libs.androidx.compose.bom)) diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapManager.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapManager.kt index 55ed8710..78ad27ad 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapManager.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapManager.kt @@ -34,18 +34,24 @@ import com.what3words.core.types.common.W3WError import com.what3words.core.types.common.W3WResult import com.what3words.core.types.domain.W3WAddress import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.core.types.geometry.W3WGridSection import com.what3words.core.types.language.W3WRFC5646Language import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** @@ -73,47 +79,60 @@ class W3WMapManager( ) { private var language: W3WRFC5646Language = W3WRFC5646Language.EN_GB + private val scope = CoroutineScope(dispatcher + SupervisorJob()) + private val _mapState: MutableStateFlow = MutableStateFlow(mapState) val mapState: StateFlow = _mapState.asStateFlow() private val _buttonState: MutableStateFlow = MutableStateFlow(buttonState) val buttonState: StateFlow = _buttonState.asStateFlow() + private val gridCalculationFlow = MutableStateFlow?>(null) + init { - if (mapProvider == MapProvider.MAPBOX) { - _mapState.update { - it.copy( - cameraState = W3WMapboxCameraState( - MapViewportState( - initialCameraState = CameraState( - Point.fromLngLat( - LOCATION_DEFAULT.lng, - LOCATION_DEFAULT.lat - ), - EdgeInsets(0.0, 0.0, 0.0, 0.0), - W3WMapboxCameraState.MY_LOCATION_ZOOM, - 0.0, - 0.0 - ) - ) - ) + _mapState.update { + it.copy( + cameraState = when (mapProvider) { + MapProvider.MAPBOX -> createMapboxCameraState() + MapProvider.GOOGLE_MAP -> createGoogleCameraState() + } + ) + } + observeGridCalculation() + } + + private fun createMapboxCameraState(): W3WMapboxCameraState { + return W3WMapboxCameraState( + MapViewportState( + initialCameraState = CameraState( + Point.fromLngLat(LOCATION_DEFAULT.lng, LOCATION_DEFAULT.lat), + EdgeInsets(0.0, 0.0, 0.0, 0.0), + W3WMapboxCameraState.MY_LOCATION_ZOOM, + 0.0, + 0.0 ) - } - } else if (mapProvider == MapProvider.GOOGLE_MAP) { - _mapState.update { - it.copy( - cameraState = W3WGoogleCameraState( - CameraPositionState( - position = CameraPosition( - LOCATION_DEFAULT.toGoogleLatLng(), - W3WGoogleCameraState.MY_LOCATION_ZOOM, - 0f, - 0f - ) - ) - ) + ) + ) + } + + private fun createGoogleCameraState(): W3WGoogleCameraState { + return W3WGoogleCameraState( + CameraPositionState( + position = CameraPosition( + LOCATION_DEFAULT.toGoogleLatLng(), + W3WGoogleCameraState.MY_LOCATION_ZOOM, + 0f, + 0f ) - } + ) + ) + } + + private fun observeGridCalculation() { + scope.launch { + gridCalculationFlow + .filterNotNull() + .collectLatest { calculateAndUpdateGrid(it) } } } @@ -199,14 +218,14 @@ class W3WMapManager( } suspend fun updateCameraState(newCameraState: W3WCameraState<*>) = withContext(IO) { - val newGridLine = calculateGridPolylines(newCameraState) _mapState.update { it.copy( cameraState = newCameraState, - gridLines = newGridLine ) } + gridCalculationFlow.value = newCameraState } + //endregion //region Selected Address @@ -235,7 +254,7 @@ class W3WMapManager( listName: String, listWords: List, listColor: W3WMarkerColor, - ): List> = withContext(IO) { + ): List> = withContext(dispatcher) { // Create a list of deferred results to process the words concurrently val deferredResults = listWords.map { word -> async { @@ -273,7 +292,7 @@ class W3WMapManager( markerColor: W3WMarkerColor = MAKER_COLOR_DEFAULT, zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM, zoomLevel: Float? = null - ): W3WResult = withContext(IO) { + ): W3WResult = withContext(dispatcher) { val result = textDataSource.convertToCoordinates(words) if (result is W3WResult.Success) { @@ -297,7 +316,7 @@ class W3WMapManager( markerColor: W3WMarkerColor = MAKER_COLOR_DEFAULT, zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM, zoomLevel: Float? = null - ): W3WResult = withContext(IO) { + ): W3WResult = withContext(dispatcher) { val c23wa = textDataSource.convertTo3wa(coordinates, language) if (c23wa is W3WResult.Success) { @@ -316,7 +335,7 @@ class W3WMapManager( suspend fun selectAtCoordinates( coordinates: W3WCoordinates, - ): W3WResult = withContext(IO) { + ): W3WResult = withContext(dispatcher) { val c23wa = textDataSource.convertTo3wa(coordinates, language) if (c23wa is W3WResult.Success) { @@ -348,33 +367,39 @@ class W3WMapManager( } } - private suspend fun calculateGridPolylines(cameraState: W3WCameraState<*>): W3WGridLines { - return withContext(IO) { + private suspend fun calculateAndUpdateGrid(cameraState: W3WCameraState<*>) { + val newGridLine = calculateGridPolylines(cameraState) + _mapState.update { + it.copy(gridLines = newGridLine) + } + } + + private suspend fun calculateGridPolylines(cameraState: W3WCameraState<*>): W3WGridLines = + withContext(dispatcher) { cameraState.gridBound?.let { safeBox -> when (val grid = textDataSource.gridSection(safeBox)) { - is W3WResult.Failure -> { - if (grid.error is BadBoundingBoxTooBigError || grid.error is BadBoundingBoxError) { - return@withContext W3WGridLines() - } else { - throw grid.error - } - } - - is W3WResult.Success -> { - W3WGridLines( - verticalLines = grid.value.lines.computeVerticalLines() - .toImmutableList(), - horizontalLines = grid.value.lines.computeHorizontalLines() - .toImmutableList() - ) - } + is W3WResult.Failure -> handleGridError(grid.error) + is W3WResult.Success -> grid.toW3WGridLines() } } ?: W3WGridLines() } + + private fun handleGridError(error: W3WError): W3WGridLines { + return if (error is BadBoundingBoxTooBigError || error is BadBoundingBoxError) { + W3WGridLines() + } else { + throw error + } } - // Button state region + private fun W3WResult.Success.toW3WGridLines(): W3WGridLines { + return W3WGridLines( + verticalLines = value.lines.computeVerticalLines().toImmutableList(), + horizontalLines = value.lines.computeHorizontalLines().toImmutableList() + ) + } + // Button state region fun updateAccuracyDistance(distance: Float) { _buttonState.update { it.copy(accuracyDistance = distance) diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMap.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMap.kt index 0dc3db04..3c63f1d0 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMap.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMap.kt @@ -24,12 +24,10 @@ import com.what3words.components.compose.maps.state.camera.W3WGoogleCameraState import com.what3words.core.types.geometry.W3WCoordinates import com.what3words.core.types.geometry.W3WRectangle import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext -import kotlin.math.abs import kotlin.math.roundToInt /** @@ -86,12 +84,6 @@ fun W3WGoogleMap( snapshotFlow { cameraPositionState.position to cameraPositionState.projection } .conflate() .onEach { (position, projection) -> - val significantChange = isSignificantChange(position, lastProcessedPosition) - if (significantChange) { - delay(300) - } else { - delay(500) - } projection?.let { updateGridBound(projection, mapConfig.gridLineConfig) { newBound -> lastProcessedPosition = position @@ -118,17 +110,6 @@ fun W3WGoogleMap( } } -private fun isSignificantChange( - newPosition: com.google.android.gms.maps.model.CameraPosition, - lastPosition: com.google.android.gms.maps.model.CameraPosition -): Boolean { - val latDiff = abs(newPosition.target.latitude - lastPosition.target.latitude) - val lngDiff = abs(newPosition.target.longitude - lastPosition.target.longitude) - val zoomDiff = abs(newPosition.zoom - lastPosition.zoom) - - return latDiff > 0.01 || lngDiff > 0.01 || zoomDiff > 0.5 -} - private suspend fun updateGridBound( projection: Projection, gridLinesConfig: W3WMapDefaults.GridLinesConfig, diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBox.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBox.kt index 3fa185d7..0a54bd34 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBox.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBox.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier -import com.mapbox.maps.CameraState import com.mapbox.maps.MapView import com.mapbox.maps.MapboxMap import com.mapbox.maps.extension.compose.MapEffect @@ -27,12 +26,9 @@ import com.what3words.components.compose.maps.state.camera.W3WCameraState import com.what3words.components.compose.maps.state.camera.W3WMapboxCameraState import com.what3words.core.types.geometry.W3WCoordinates import com.what3words.core.types.geometry.W3WRectangle -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlin.math.abs /** * A composable function that displays a What3Words (W3W) map using the Mapbox Maps SDK for Android. @@ -67,16 +63,8 @@ fun W3WMapBox( LaunchedEffect(mapViewportState) { snapshotFlow { mapViewportState.cameraState } - .conflate() .filterNotNull() .onEach { currentCameraState -> - val significantChange = - isSignificantChange(currentCameraState, lastProcessedCameraState) - if (significantChange) { - delay(300) - } else { - delay(500) - } mapView?.mapboxMap?.let { mapboxMap -> updateGridBound( mapboxMap, @@ -148,21 +136,6 @@ fun W3WMapBox( } } -private fun isSignificantChange( - newCameraState: CameraState, - lastCameraState: CameraState? -): Boolean { - if (lastCameraState == null) { - return true - } - - val latDiff = abs(newCameraState.center.latitude() - lastCameraState.center.latitude()) - val lngDiff = abs(newCameraState.center.longitude() - lastCameraState.center.longitude()) - val zoomDiff = abs(newCameraState.zoom - lastCameraState.zoom) - - return latDiff > 0.01 || lngDiff > 0.01 || zoomDiff > 0.5 -} - private fun updateGridBound( mapboxMap: MapboxMap, gridLinesConfig: W3WMapDefaults.GridLinesConfig,