Skip to content
Merged
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
2 changes: 1 addition & 1 deletion lib-compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -73,47 +79,60 @@ class W3WMapManager(
) {
private var language: W3WRFC5646Language = W3WRFC5646Language.EN_GB

private val scope = CoroutineScope(dispatcher + SupervisorJob())

private val _mapState: MutableStateFlow<W3WMapState> = MutableStateFlow(mapState)
val mapState: StateFlow<W3WMapState> = _mapState.asStateFlow()

private val _buttonState: MutableStateFlow<W3WButtonsState> = MutableStateFlow(buttonState)
val buttonState: StateFlow<W3WButtonsState> = _buttonState.asStateFlow()

private val gridCalculationFlow = MutableStateFlow<W3WCameraState<*>?>(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) }
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -235,7 +254,7 @@ class W3WMapManager(
listName: String,
listWords: List<String>,
listColor: W3WMarkerColor,
): List<W3WResult<W3WAddress>> = withContext(IO) {
): List<W3WResult<W3WAddress>> = withContext(dispatcher) {
// Create a list of deferred results to process the words concurrently
val deferredResults = listWords.map { word ->
async {
Expand Down Expand Up @@ -273,7 +292,7 @@ class W3WMapManager(
markerColor: W3WMarkerColor = MAKER_COLOR_DEFAULT,
zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM,
zoomLevel: Float? = null
): W3WResult<W3WAddress> = withContext(IO) {
): W3WResult<W3WAddress> = withContext(dispatcher) {
val result = textDataSource.convertToCoordinates(words)

if (result is W3WResult.Success) {
Expand All @@ -297,7 +316,7 @@ class W3WMapManager(
markerColor: W3WMarkerColor = MAKER_COLOR_DEFAULT,
zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM,
zoomLevel: Float? = null
): W3WResult<W3WAddress> = withContext(IO) {
): W3WResult<W3WAddress> = withContext(dispatcher) {
val c23wa = textDataSource.convertTo3wa(coordinates, language)

if (c23wa is W3WResult.Success) {
Expand All @@ -316,7 +335,7 @@ class W3WMapManager(

suspend fun selectAtCoordinates(
coordinates: W3WCoordinates,
): W3WResult<W3WAddress> = withContext(IO) {
): W3WResult<W3WAddress> = withContext(dispatcher) {
val c23wa = textDataSource.convertTo3wa(coordinates, language)

if (c23wa is W3WResult.Success) {
Expand Down Expand Up @@ -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<W3WGridSection>.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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down