From 68350b1e985c2ea182bd5caeabcb3743d856386e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Duy=20Ph=E1=BA=A1m?= Date: Mon, 18 Nov 2024 12:40:06 +0700 Subject: [PATCH 1/3] Refactor camera state --- .../compose/maps/W3WMapComponent.kt | 40 +++-- .../components/compose/maps/W3WMapDefaults.kt | 2 +- .../components/compose/maps/W3WMapManager.kt | 133 ++++++++------ .../components/compose/maps/W3WMapState.kt | 124 ------------- .../compose/maps/mapper/GoogleMapConverter.kt | 33 +--- .../compose/maps/mapper/MapBoxConverter.kt | 32 +--- .../components/compose/maps/models/MapType.kt | 8 + .../components/compose/maps/models/Marker.kt | 11 ++ .../compose/maps/models/ZoomOption.kt | 7 + .../maps/providers/googlemap/W3WGoogleMap.kt | 168 ++++++++++++++++++ .../maps/providers/mapbox/W3WMapBoxDrawers.kt | 39 +--- .../compose/maps/state/W3WCameraPosition.kt | 38 ++++ .../compose/maps/state/W3WCameraState.kt | 17 ++ .../maps/state/W3WGoogleCameraState.kt | 46 +++++ .../compose/maps/state/W3WMapState.kt | 78 ++++++++ .../maps/state/W3WMapboxCameraState.kt | 25 +++ .../src/main/res/drawable/ic_marker.xml | 14 +- 17 files changed, 518 insertions(+), 297 deletions(-) delete mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapState.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMap.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraState.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WGoogleCameraState.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt index 8371a6fb..12232837 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt @@ -14,6 +14,8 @@ import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.what3words.components.compose.maps.buttons.W3WMapButtons import com.what3words.components.compose.maps.providers.googlemap.W3WGoogleMap import com.what3words.components.compose.maps.providers.mapbox.W3WMapBox +import com.what3words.components.compose.maps.state.W3WCameraState +import com.what3words.components.compose.maps.state.W3WMapState import com.what3words.core.types.common.W3WError import com.what3words.core.types.geometry.W3WCoordinates @@ -24,7 +26,6 @@ fun W3WMapComponent( modifier: Modifier = Modifier, layoutConfig: W3WMapDefaults.LayoutConfig = W3WMapDefaults.defaultLayoutConfig(), mapConfig: W3WMapDefaults.MapConfig = W3WMapDefaults.defaultMapConfig(), - mapProvider: MapProvider, mapManager: W3WMapManager, content: (@Composable () -> Unit)? = null, onError: ((W3WError) -> Unit)? = null, @@ -49,7 +50,7 @@ fun W3WMapComponent( modifier = modifier, layoutConfig = layoutConfig, mapConfig = mapConfig, - mapProvider = mapProvider, + mapProvider = mapManager.mapProvider, content = content, state = state, onMapTypeClicked = { @@ -59,8 +60,8 @@ fun W3WMapComponent( }, onCameraUpdated = { - mapManager.onCameraUpdated(it) - }, + mapManager.updateCameraState(it) + } ) } @@ -81,7 +82,7 @@ fun W3WMapComponent( content: (@Composable () -> Unit)? = null, onMapTypeClicked: ((W3WMapState.MapType) -> Unit)? = null, onMapClicked: ((W3WCoordinates) -> Unit)? = null, - onCameraUpdated: ((W3WMapState.CameraPosition) -> Unit)? = null + onCameraUpdated: (W3WCameraState<*>) -> Unit ) { W3WMapContent( modifier = modifier, @@ -90,20 +91,20 @@ fun W3WMapComponent( mapProvider = mapProvider, content = content, state = state, - onCameraUpdated = { - onCameraUpdated?.invoke(it) - }, onMapClicked = { onMapClicked?.invoke(it) }, onMapTypeClicked = { onMapTypeClicked?.invoke(it) }, + onCameraUpdated = { + onCameraUpdated.invoke(it) + } ) } @Composable -fun W3WMapContent( +internal fun W3WMapContent( modifier: Modifier = Modifier, layoutConfig: W3WMapDefaults.LayoutConfig = W3WMapDefaults.defaultLayoutConfig(), mapConfig: W3WMapDefaults.MapConfig = W3WMapDefaults.defaultMapConfig(), @@ -111,8 +112,8 @@ fun W3WMapContent( mapProvider: MapProvider, content: (@Composable () -> Unit)? = null, onMapTypeClicked: ((W3WMapState.MapType) -> Unit), - onMapClicked: ((W3WCoordinates) -> Unit), - onCameraUpdated: ((W3WMapState.CameraPosition) -> Unit) + onMapClicked: (W3WCoordinates) -> Unit, + onCameraUpdated: (W3WCameraState<*>) -> Unit ) { Box(modifier = modifier) { W3WMapView( @@ -122,8 +123,10 @@ fun W3WMapContent( mapProvider = mapProvider, state = state, onMapClicked = onMapClicked, - onCameraUpdated = onCameraUpdated, - content = content + content = content, + onCameraUpdated = { + onCameraUpdated.invoke(it) + } ) W3WMapButtons( @@ -139,7 +142,7 @@ fun W3WMapContent( } @Composable -fun W3WMapView( +internal fun W3WMapView( modifier: Modifier, layoutConfig: W3WMapDefaults.LayoutConfig, mapConfig: W3WMapDefaults.MapConfig, @@ -147,7 +150,7 @@ fun W3WMapView( state: W3WMapState, content: (@Composable () -> Unit)? = null, onMapClicked: ((W3WCoordinates) -> Unit), - onCameraUpdated: ((W3WMapState.CameraPosition) -> Unit) + onCameraUpdated: (W3WCameraState<*>) -> Unit ) { when (mapProvider) { MapProvider.GOOGLE_MAP -> { @@ -157,8 +160,10 @@ fun W3WMapView( mapConfig = mapConfig, state = state, onMapClicked = onMapClicked, - onCameraUpdated = onCameraUpdated, - content = content + content = content, + onCameraUpdated = { + onCameraUpdated.invoke(it) + } ) } @@ -169,7 +174,6 @@ fun W3WMapView( mapConfig = mapConfig, state = state, onMapClicked = onMapClicked, - onCameraUpdated = onCameraUpdated, content = content ) } diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapDefaults.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapDefaults.kt index 456ffa52..d1f3b6b1 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapDefaults.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapDefaults.kt @@ -55,7 +55,7 @@ object W3WMapDefaults { fun defaultGridLineConfig( isGridEnabled: Boolean = true, gridColor: Color? = null, - zoomSwitchLevel: Float = 0f + zoomSwitchLevel: Float = 19f ): GridLineConfig { return GridLineConfig( isGridEnabled = isGridEnabled, 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 b5e2d1d5..6e84ceac 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 @@ -4,6 +4,21 @@ import android.annotation.SuppressLint import androidx.annotation.RequiresPermission import androidx.compose.ui.graphics.Color import androidx.core.util.Consumer +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.CameraPositionState +import com.mapbox.maps.extension.compose.animation.viewport.MapViewportState +import com.what3words.androidwrapper.datasource.text.api.error.BadBoundingBoxTooBigError +import com.what3words.components.compose.maps.extensions.computeHorizontalLines +import com.what3words.components.compose.maps.extensions.computeVerticalLines +import com.what3words.components.compose.maps.models.MapType +import com.what3words.components.compose.maps.models.Marker +import com.what3words.components.compose.maps.models.ZoomOption +import com.what3words.components.compose.maps.state.W3WCameraState +import com.what3words.components.compose.maps.state.W3WGoogleCameraState +import com.what3words.components.compose.maps.state.W3WMapState +import com.what3words.components.compose.maps.state.W3WMapboxCameraState +import com.what3words.components.compose.maps.state.addOrUpdateMarker import com.what3words.core.datasource.text.W3WTextDataSource import com.what3words.core.types.common.W3WError import com.what3words.core.types.common.W3WResult @@ -18,6 +33,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * A class that manages the state and interactions of a what3words map. @@ -34,26 +50,39 @@ import kotlinx.coroutines.launch class W3WMapManager( private val textDataSource: W3WTextDataSource, private val dispatcher: CoroutineDispatcher = Dispatchers.IO, + val mapProvider: MapProvider ) { private var language: W3WRFC5646Language = W3WRFC5646Language.EN_GB - private val _state = MutableStateFlow(W3WMapState()) + private val _state: MutableStateFlow = MutableStateFlow(W3WMapState()) val state: StateFlow = _state.asStateFlow() - // Should be combine functions of W3WMap, W3WMapManager - init { - _state.update { - it.copy( - language = language, - cameraPosition = W3WMapState.CameraPosition( - zoom = 19f, - W3WCoordinates(10.782147, 106.671892), - 50f, - 0f, - false + if (mapProvider == MapProvider.MAPBOX) { + _state.update { + it.copy(cameraState = W3WMapboxCameraState(MapViewportState())) + } + } else if (mapProvider == MapProvider.GOOGLE_MAP) { + _state.update { + it.copy( + cameraState = W3WGoogleCameraState( + CameraPositionState( + position = CAMERA_POSITION_DEFAULT + ) + ) ) - ) + } + } + } + + fun updateCameraState(newCameraState: W3WCameraState<*>) { + CoroutineScope(Dispatchers.IO).launch { + _state.update { + it.copy( + cameraState = newCameraState, + gridPolyline = calculateGridPolylines(newCameraState) + ) + } } } @@ -81,11 +110,11 @@ class W3WMapManager( //endregion //region Map Ui Settings - fun getMapType(): W3WMapState.MapType { + fun getMapType(): MapType { return state.value.mapType } - fun setMapType(mapType: W3WMapState.MapType) { + fun setMapType(mapType: MapType) { _state.update { it.copy( mapType = mapType @@ -93,14 +122,6 @@ class W3WMapManager( } } - fun setCameraPosition(cameraPosition: W3WMapState.CameraPosition) { - _state.update { - it.copy( - cameraPosition = cameraPosition - ) - } - } - fun setMapGesturesEnabled(enabled: Boolean) { _state.update { it.copy( @@ -131,23 +152,11 @@ class W3WMapManager( // region Camera control fun orientCamera() { - _state.update { - it.copy( - cameraPosition = it.cameraPosition?.copy( - bearing = 0f, - isAnimated = true, - ) - ) - } - + // Not implemented } - fun moveToPosition(cameraPosition: W3WMapState.CameraPosition) { - _state.update { - it.copy( - cameraPosition = cameraPosition - ) - } + fun moveToPosition(coordinates: W3WCoordinates) { + // Not implemented } //endregion @@ -156,7 +165,6 @@ class W3WMapManager( return state.value.selectedAddress } - fun unselect() { _state.update { it.copy( @@ -171,7 +179,7 @@ class W3WMapManager( fun addMarkerAtWords( words: String, markerColor: Color = Color.Red, - zoomOption: W3WMapState.ZoomOption = W3WMapState.ZoomOption.CENTER_AND_ZOOM, + zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer? = null, onError: Consumer? = null, zoomLevel: Float? = null @@ -180,7 +188,7 @@ class W3WMapManager( when (val c23wa = textDataSource.convertToCoordinates(words)) { is W3WResult.Success -> { _state.value = state.value.addOrUpdateMarker( - marker = W3WMapState.Marker(address = c23wa.value, color = markerColor) + marker = Marker(address = c23wa.value, color = markerColor) ) onSuccess?.accept(c23wa.value) } @@ -195,7 +203,7 @@ class W3WMapManager( fun addMarkerAtListWords( listWords: List, markerColor: Color = Color.Red, - zoomOption: W3WMapState.ZoomOption = W3WMapState.ZoomOption.CENTER_AND_ZOOM, + zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer>? = null, onError: Consumer? = null ) { @@ -205,7 +213,7 @@ class W3WMapManager( fun addMarkerAtCoordinates( coordinates: W3WCoordinates, markerColor: Color = Color.Red, - zoomOption: W3WMapState.ZoomOption = W3WMapState.ZoomOption.CENTER_AND_ZOOM, + zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer? = null, onError: Consumer? = null, zoomLevel: Float? = null @@ -214,7 +222,7 @@ class W3WMapManager( when (val c23wa = textDataSource.convertTo3wa(coordinates, language)) { is W3WResult.Success -> { _state.value = state.value.addOrUpdateMarker( - marker = W3WMapState.Marker(address = c23wa.value, color = markerColor) + marker = Marker(address = c23wa.value, color = markerColor) ) onSuccess?.accept(c23wa.value) } @@ -228,7 +236,7 @@ class W3WMapManager( fun selectAtCoordinates( coordinates: W3WCoordinates, - zoomOption: W3WMapState.ZoomOption = W3WMapState.ZoomOption.CENTER_AND_ZOOM, + zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer? = null, onError: Consumer? = null, zoomLevel: Float? = null @@ -249,19 +257,30 @@ class W3WMapManager( } } - fun onCameraUpdated(cameraPosition: W3WMapState.CameraPosition) { - if (cameraPosition != state.value.cameraPosition) { - if (cameraPosition.isMoving) { - //TODO implement same as updateMove in Wrapper - - } else { - //TODO implement same as updateMap in Wrapper - _state.update { - it.copy( - cameraPosition = cameraPosition - ) + private suspend fun calculateGridPolylines(cameraState: W3WCameraState<*>): Pair, List> { + return withContext(Dispatchers.IO) { + cameraState.gridBound?.let { safeBox -> + when (val grid = textDataSource.gridSection(safeBox)) { + is W3WResult.Failure -> { + if (grid.error is BadBoundingBoxTooBigError) { + return@withContext Pair(emptyList(), emptyList()) + } else { + throw grid.error + } + } + + is W3WResult.Success -> { + val verticalLines = grid.value.lines.computeVerticalLines() + val horizontalLines = grid.value.lines.computeHorizontalLines() + Pair(verticalLines, horizontalLines) + } } - } + } ?: Pair(emptyList(), emptyList()) } } + + companion object { + @JvmField + val CAMERA_POSITION_DEFAULT = CameraPosition(LatLng(51.521251, -0.203586), 19f, 0f, 0f) + } } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapState.kt deleted file mode 100644 index ddf7cd9d..00000000 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapState.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.what3words.components.compose.maps - -import androidx.compose.ui.graphics.Color -import com.what3words.core.types.domain.W3WAddress -import com.what3words.core.types.geometry.W3WCoordinates -import com.what3words.core.types.language.W3WRFC5646Language - -const val LIST_DEFAULT_ID = "LIST_DEFAULT_ID" - -data class W3WMapState( - // Map Config - val language: W3WRFC5646Language = W3WRFC5646Language.EN_GB, - val mapType: MapType = MapType.NORMAL, - val isDarkMode: Boolean = false, - val isMapGestureEnable: Boolean = true, - val isMyLocationEnabled: Boolean = true, - - // Button control - val isMyLocationButtonEnabled: Boolean = true, - - // Camera control - val cameraPosition: CameraPosition? = null, - - // Square selected - val selectedAddress: W3WAddress? = null, - - internal val grid: List = emptyList(), - - // List marker - val listMakers: Map> = emptyMap(), -) { - data class CameraPosition( - val zoom: Float, - val coordinates: W3WCoordinates, - val bearing: Float, - val tilt: Float, - val isAnimated: Boolean = false, - val isMoving: Boolean = false, - ) { - - override fun hashCode(): Int { - var result = zoom.hashCode() - result = 31 * result + coordinates.hashCode() - result = 31 * result + bearing.hashCode() - result = 31 * result + tilt.hashCode() - result = 31 * result + isAnimated.hashCode() - result = 31 * result + isMoving.hashCode() - return result - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as CameraPosition - - if (zoom != other.zoom) return false - if (coordinates != other.coordinates) return false - if (bearing != other.bearing) return false - if (tilt != other.tilt) return false - - return true - } - } - - data class Marker( - val address: W3WAddress, - val color: Color, - val title: String? = null, - val snippet: String? = null - ) - - enum class ZoomOption { - NONE, - CENTER, - CENTER_AND_ZOOM - } - - enum class MapType { - NORMAL, - HYBRID, - TERRAIN, - SATELLITE - } -} - -fun W3WMapState.addOrUpdateMarker( - listId: String? = null, - marker: W3WMapState.Marker // Assuming W3WMapMarker holds marker data -): W3WMapState { - val key = listId ?: LIST_DEFAULT_ID - val currentList = listMakers[key] ?: emptyList() - val existingMarkerIndex = currentList.indexOfFirst { it.address == marker.address } - - val updatedList = if (existingMarkerIndex != -1) { - // Update existing marker - currentList.toMutableList().also { it[existingMarkerIndex] = marker } - } else { - // Add new marker - currentList + marker - } - - return copy(listMakers = listMakers + (key to updatedList)) -} - -fun W3WMapState.getListIdsByMarker(marker: W3WMapState.Marker): List { - return listMakers.entries.filter { (_, markers) -> marker in markers }.map { it.key } -} - -fun W3WMapState.getMarkersByListId(listId: String): List { - return listMakers[listId] ?: emptyList() -} - -fun W3WMapState.removeMarkersByList(listId: String): W3WMapState { - return copy(listMakers = listMakers - listId) -} - -fun W3WMapState.getAllMarkers(): List { - return listMakers.values.flatten() -} - -fun W3WMapState.removeAllMarkers(): W3WMapState { - return copy(listMakers = emptyMap()) -} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt index fb8fc76a..c1921337 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt @@ -1,10 +1,7 @@ package com.what3words.components.compose.maps.mapper -import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng -import com.google.maps.android.compose.CameraPositionState import com.google.maps.android.compose.MapType -import com.what3words.components.compose.maps.W3WMapState import com.what3words.core.types.geometry.W3WCoordinates fun W3WCoordinates.toGoogleLatLng(): LatLng { @@ -15,31 +12,11 @@ fun LatLng.toW3WCoordinates(): W3WCoordinates { return W3WCoordinates(this.latitude, this.longitude) } -fun W3WMapState.CameraPosition.toGoogleCameraPosition(): CameraPosition { - return CameraPosition( - this.coordinates.toGoogleLatLng(), - this.zoom, - this.tilt, - this.bearing - ) -} - -fun CameraPositionState.toW3WMapStateCameraPosition(): W3WMapState.CameraPosition { - return W3WMapState.CameraPosition( - coordinates = this.position.target.toW3WCoordinates(), - zoom = this.position.zoom, - bearing = this.position.bearing, - isMoving = this.isMoving, - tilt = this.position.tilt, - isAnimated = false, - ) -} - -fun W3WMapState.MapType.toGoogleMapType(): MapType { +fun com.what3words.components.compose.maps.models.MapType.toGoogleMapType(): MapType { return when (this) { - W3WMapState.MapType.NORMAL -> MapType.NORMAL - W3WMapState.MapType.SATELLITE -> MapType.SATELLITE - W3WMapState.MapType.HYBRID -> MapType.HYBRID - W3WMapState.MapType.TERRAIN -> MapType.TERRAIN + com.what3words.components.compose.maps.models.MapType.NORMAL -> MapType.NORMAL + com.what3words.components.compose.maps.models.MapType.SATELLITE -> MapType.SATELLITE + com.what3words.components.compose.maps.models.MapType.HYBRID -> MapType.HYBRID + com.what3words.components.compose.maps.models.MapType.TERRAIN -> MapType.TERRAIN } } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt index 6b091be0..8cbd5d89 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt @@ -1,33 +1,13 @@ package com.what3words.components.compose.maps.mapper -import com.mapbox.geojson.Point -import com.mapbox.maps.CameraOptions -import com.mapbox.maps.CameraState import com.mapbox.maps.Style -import com.what3words.components.compose.maps.W3WMapState -import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.components.compose.maps.models.MapType -fun W3WMapState.MapType.toMapBoxMapType(isDarkMode: Boolean): String { +fun MapType.toMapBoxMapType(isDarkMode: Boolean): String { return when (this) { - W3WMapState.MapType.NORMAL -> if (isDarkMode) Style.DARK else Style.MAPBOX_STREETS - W3WMapState.MapType.SATELLITE -> Style.SATELLITE_STREETS - W3WMapState.MapType.HYBRID -> Style.SATELLITE_STREETS - W3WMapState.MapType.TERRAIN -> Style.OUTDOORS + MapType.NORMAL -> if (isDarkMode) Style.DARK else Style.MAPBOX_STREETS + MapType.SATELLITE -> Style.SATELLITE_STREETS + MapType.HYBRID -> Style.SATELLITE_STREETS + MapType.TERRAIN -> Style.OUTDOORS } -} - -fun W3WMapState.CameraPosition.toMapBoxCameraOptions(): CameraOptions { - return CameraOptions.Builder().pitch(this.tilt.toDouble()).bearing(this.bearing.toDouble()) - .center(Point.fromLngLat(this.coordinates.lng, this.coordinates.lat)) - .zoom(this.zoom.toDouble()).build() -} - -fun CameraState.toW3WCameraPosition(isMoving: Boolean): W3WMapState.CameraPosition { - return W3WMapState.CameraPosition( - zoom = this.zoom.toFloat(), - bearing = this.bearing.toFloat(), - coordinates = this.center.let { W3WCoordinates(it.latitude(), it.longitude()) }, - tilt = this.pitch.toFloat(), - isMoving = isMoving, - ) } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt new file mode 100644 index 00000000..d6f27fc1 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt @@ -0,0 +1,8 @@ +package com.what3words.components.compose.maps.models + +enum class MapType { + NORMAL, + HYBRID, + TERRAIN, + SATELLITE +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt new file mode 100644 index 00000000..7786c2b0 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt @@ -0,0 +1,11 @@ +package com.what3words.components.compose.maps.models + +import androidx.compose.ui.graphics.Color +import com.what3words.core.types.domain.W3WAddress + +data class Marker( + val address: W3WAddress, + val color: Color, + val title: String? = null, + val snippet: String? = null +) \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt new file mode 100644 index 00000000..e7aee038 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt @@ -0,0 +1,7 @@ +package com.what3words.components.compose.maps.models + +enum class ZoomOption { + NONE, + CENTER, + CENTER_AND_ZOOM +} 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 new file mode 100644 index 00000000..c0dd5b81 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMap.kt @@ -0,0 +1,168 @@ +package com.what3words.components.compose.maps.providers.googlemap + +import android.graphics.Point +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Modifier +import com.google.android.gms.maps.Projection +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.MapStyleOptions +import com.google.maps.android.compose.GoogleMap +import com.google.maps.android.compose.MapProperties +import com.google.maps.android.compose.MapUiSettings +import com.what3words.components.compose.maps.W3WMapDefaults +import com.what3words.components.compose.maps.mapper.toGoogleMapType +import com.what3words.components.compose.maps.state.W3WCameraState +import com.what3words.components.compose.maps.state.W3WGoogleCameraState +import com.what3words.components.compose.maps.state.W3WMapState +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 + +@Composable +fun W3WGoogleMap( + modifier: Modifier, + layoutConfig: W3WMapDefaults.LayoutConfig, + mapConfig: W3WMapDefaults.MapConfig, + state: W3WMapState, + content: (@Composable () -> Unit)? = null, + onMapClicked: (W3WCoordinates) -> Unit, + onCameraUpdated: (W3WCameraState<*>) -> Unit +) { + val mapProperties = remember(state.mapType, state.isMyLocationEnabled, state.isDarkMode) { + MapProperties( + mapType = state.mapType.toGoogleMapType(), + isMyLocationEnabled = state.isMyLocationEnabled, + mapStyleOptions = if (state.isDarkMode) MapStyleOptions(mapConfig.darkModeCustomJsonStyle) else null + ) + } + + val uiSettings = remember(state.isMyLocationButtonEnabled, state.isMapGestureEnable) { + MapUiSettings( + zoomControlsEnabled = false, + myLocationButtonEnabled = false, + scrollGesturesEnabled = state.isMapGestureEnable, + tiltGesturesEnabled = state.isMapGestureEnable, + zoomGesturesEnabled = state.isMapGestureEnable, + rotationGesturesEnabled = state.isMapGestureEnable, + scrollGesturesEnabledDuringRotateOrZoom = state.isMapGestureEnable + ) + } + + val cameraPositionState = (state.cameraState as W3WGoogleCameraState).cameraState + + var lastProcessedPosition by remember { mutableStateOf(cameraPositionState.position) } + + LaunchedEffect(cameraPositionState) { + /// A hybrid approach that combines immediate updates for significant changes with debounced updates for fine-tuning + 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) { newBound -> + lastProcessedPosition = position + val newCameraState = W3WGoogleCameraState(cameraPositionState) + newCameraState.gridBound = newBound + onCameraUpdated(newCameraState) + } + } + }.launchIn(this) + } + + GoogleMap( + modifier = modifier, + cameraPositionState = cameraPositionState, + contentPadding = layoutConfig.contentPadding, + uiSettings = uiSettings, + properties = mapProperties, + onMapClick = { + onMapClicked.invoke(W3WCoordinates(it.latitude, it.longitude)) + }, + ) { + W3WGoogleMapDrawer(state = state, mapConfig) + content?.invoke() + } +} + +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, + onGridBoundUpdate: (W3WRectangle) -> Unit +) { + withContext(Dispatchers.IO) { + val lastScaledBounds = scaleBounds(projection.visibleRegion.latLngBounds, projection) + val box = W3WRectangle( + W3WCoordinates( + lastScaledBounds.southwest.latitude, + lastScaledBounds.southwest.longitude + ), + W3WCoordinates( + lastScaledBounds.northeast.latitude, + lastScaledBounds.northeast.longitude + ) + ) + + withContext(Dispatchers.Main) { + onGridBoundUpdate.invoke(box) + } + } +} + +private fun scaleBounds( + bounds: LatLngBounds, + projection: Projection, + scale: Float = 6f +): LatLngBounds { + try { + val center = bounds.center + val centerPoint: Point = projection.toScreenLocation(center) + val screenPositionNortheast: Point = + projection.toScreenLocation(bounds.northeast) + screenPositionNortheast.x = + ((scale * (screenPositionNortheast.x - centerPoint.x) + centerPoint.x).roundToInt()) + screenPositionNortheast.y = + ((scale * (screenPositionNortheast.y - centerPoint.y) + centerPoint.y).roundToInt()) + val scaledNortheast = projection.fromScreenLocation(screenPositionNortheast) + val screenPositionSouthwest: Point = + projection.toScreenLocation(bounds.southwest) + screenPositionSouthwest.x = + ((scale * (screenPositionSouthwest.x - centerPoint.x) + centerPoint.x).roundToInt()) + screenPositionSouthwest.y = + ((scale * (screenPositionSouthwest.y - centerPoint.y) + centerPoint.y).roundToInt()) + val scaledSouthwest = projection.fromScreenLocation(screenPositionSouthwest) + return LatLngBounds(scaledSouthwest, scaledNortheast) + } catch (e: Exception) { + //fallback to original bounds if something goes wrong with scaling + e.printStackTrace() + return bounds + } +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt index aef8bd50..5730ab44 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt @@ -22,6 +22,8 @@ import com.what3words.components.compose.maps.W3WMapState import com.what3words.components.compose.maps.mapper.toMapBoxCameraOptions import com.what3words.components.compose.maps.mapper.toMapBoxMapType import com.what3words.components.compose.maps.mapper.toW3WCameraPosition +import com.what3words.components.compose.maps.models.Marker +import com.what3words.components.compose.maps.state.W3WMapState import com.what3words.core.types.domain.W3WAddress import com.what3words.core.types.geometry.W3WCoordinates import kotlinx.coroutines.launch @@ -34,7 +36,6 @@ fun W3WMapBox( state: W3WMapState, content: (@Composable () -> Unit)? = null, onMapClicked: ((W3WCoordinates) -> Unit), - onCameraUpdated: ((W3WMapState.CameraPosition) -> Unit) ) { var mapView: MapView? by remember { mutableStateOf(null) @@ -56,28 +57,6 @@ fun W3WMapBox( state.mapType.toMapBoxMapType(state.isDarkMode) } - LaunchedEffect(key1 = Unit) { - mapState.apply { - launch { - cameraChangedEvents.collect { - isCameraMoving = true - onCameraUpdated.invoke(it.cameraState.toW3WCameraPosition(isCameraMoving)) - } - } - - launch { - mapIdleEvents.collect { - if(isCameraMoving) { - isCameraMoving = false - mapViewportState.cameraState?.let { - onCameraUpdated.invoke(it.toW3WCameraPosition(isCameraMoving)) - } - } - } - } - } - } - LaunchedEffect(state.isMyLocationEnabled) { mapView?.let { it.location.updateSettings { @@ -100,18 +79,6 @@ fun W3WMapBox( } } - LaunchedEffect(state.cameraPosition) { - state.cameraPosition?.let { - if (it != mapViewportState.cameraState?.toW3WCameraPosition(false)) { - if (it.isAnimated) { - mapViewportState.flyTo(it.toMapBoxCameraOptions()) - } else { - mapViewportState.setCameraOptions(it.toMapBoxCameraOptions()) - } - } - } - } - MapboxMap( modifier = modifier, mapState = mapState, @@ -151,7 +118,7 @@ fun W3WMapBoxDrawSelectedAddress(zoomLevel: Float, address: W3WAddress) { @Composable @MapboxMapComposable -fun W3WMapBoxDrawMarkers(zoomLevel: Float, listMakers: Map>) { +fun W3WMapBoxDrawMarkers(zoomLevel: Float, listMakers: Map>) { //TODO: Draw select for zoom in: filled square //TODO: Draw select for zoom out: circle diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt new file mode 100644 index 00000000..90446eaa --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt @@ -0,0 +1,38 @@ +//package com.what3words.components.compose.maps.state +// +//import com.what3words.core.types.geometry.W3WCoordinates +//import com.what3words.core.types.geometry.W3WRectangle +// +//data class W3WCameraPosition( +// val zoomLevel: Float, +// val coordinates: W3WCoordinates, +// val bearing: Float, +// val isAnimated: Boolean = false, +// val isMoving: Boolean = false, +// val tilt: Float, +// val gridBound: W3WRectangle? = null +//) { +// override fun hashCode(): Int { +// var result = zoomLevel.hashCode() +// result = 31 * result + coordinates.hashCode() +// result = 31 * result + bearing.hashCode() +// result = 31 * result + tilt.hashCode() +// result = 31 * result + isAnimated.hashCode() +// result = 31 * result + isMoving.hashCode() +// return result +// } +// +// override fun equals(other: Any?): Boolean { +// if (this === other) return true +// if (javaClass != other?.javaClass) return false +// +// other as W3WCameraPosition +// +// if (zoomLevel != other.zoomLevel) return false +// if (coordinates != other.coordinates) return false +// if (bearing != other.bearing) return false +// if (tilt != other.tilt) return false +// +// return true +// } +//} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraState.kt new file mode 100644 index 00000000..7fd06f1c --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraState.kt @@ -0,0 +1,17 @@ +package com.what3words.components.compose.maps.state + +import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.core.types.geometry.W3WRectangle + +interface W3WCameraState { + + val cameraState: T + + var gridBound: W3WRectangle? + + fun orientCamera() + + fun moveToPosition(coordinates: W3WCoordinates, animated: Boolean) + + fun getZoomLevel(): Float +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WGoogleCameraState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WGoogleCameraState.kt new file mode 100644 index 00000000..a1382753 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WGoogleCameraState.kt @@ -0,0 +1,46 @@ +package com.what3words.components.compose.maps.state + +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.CameraPositionState +import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.core.types.geometry.W3WRectangle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class W3WGoogleCameraState(override val cameraState: CameraPositionState) : + W3WCameraState { + + override var gridBound: W3WRectangle? = null + + override fun orientCamera() { + TODO("Not yet implemented") + } + + override fun moveToPosition(coordinates: W3WCoordinates, animated: Boolean) { + CoroutineScope(Dispatchers.IO).launch { + if (animated) { + cameraState.animate( + update = CameraUpdateFactory.newLatLngZoom( + LatLng(coordinates.lat, coordinates.lng), + cameraState.position.zoom + ) + ) + } else { + cameraState.position = CameraPosition( + LatLng(coordinates.lat, coordinates.lng), + cameraState.position.zoom, + cameraState.position.tilt, + cameraState.position.bearing + ) + } + } + + } + + override fun getZoomLevel(): Float { + return cameraState.position.zoom + } +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt new file mode 100644 index 00000000..93932533 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt @@ -0,0 +1,78 @@ +package com.what3words.components.compose.maps.state + +import com.what3words.components.compose.maps.models.MapType +import com.what3words.components.compose.maps.models.Marker +import com.what3words.core.types.domain.W3WAddress +import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.core.types.language.W3WRFC5646Language + +const val LIST_DEFAULT_ID = "LIST_DEFAULT_ID" + +data class W3WMapState( + // Map Config + val language: W3WRFC5646Language = W3WRFC5646Language.EN_GB, + + val mapType: MapType = MapType.NORMAL, + + val isDarkMode: Boolean = false, + + val isMapGestureEnable: Boolean = true, + + val isMyLocationEnabled: Boolean = true, + + // Button control + val isMyLocationButtonEnabled: Boolean = true, + + // Square selected + val selectedAddress: W3WAddress? = null, + + // List marker + val listMakers: Map> = emptyMap(), + + val cameraState: W3WCameraState<*>? = null, + + // The pair of vertical and horizontal grid polylines + internal val gridPolyline: Pair, List> = Pair( + emptyList(), + emptyList() + ), +) + +fun W3WMapState.addOrUpdateMarker( + listId: String? = null, + marker: Marker +): W3WMapState { + val key = listId ?: LIST_DEFAULT_ID + val currentList = listMakers[key] ?: emptyList() + val existingMarkerIndex = currentList.indexOfFirst { it.address == marker.address } + + val updatedList = if (existingMarkerIndex != -1) { + // Update existing marker + currentList.toMutableList().also { it[existingMarkerIndex] = marker } + } else { + // Add new marker + currentList + marker + } + + return copy(listMakers = listMakers + (key to updatedList)) +} + +fun W3WMapState.getListIdsByMarker(marker: Marker): List { + return listMakers.entries.filter { (_, markers) -> marker in markers }.map { it.key } +} + +fun W3WMapState.getMarkersByListId(listId: String): List { + return listMakers[listId] ?: emptyList() +} + +fun W3WMapState.removeMarkersByList(listId: String): W3WMapState { + return copy(listMakers = listMakers - listId) +} + +fun W3WMapState.getAllMarkers(): List { + return listMakers.values.flatten() +} + +fun W3WMapState.removeAllMarkers(): W3WMapState { + return copy(listMakers = emptyMap()) +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt new file mode 100644 index 00000000..7906fa27 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt @@ -0,0 +1,25 @@ +package com.what3words.components.compose.maps.state + +import com.mapbox.maps.extension.compose.animation.viewport.MapViewportState +import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.core.types.geometry.W3WRectangle + +class W3WMapboxCameraState(override val cameraState: MapViewportState) : + W3WCameraState { + + override var gridBound: W3WRectangle? + get() = TODO("Not yet implemented") + set(value) {} + + override fun orientCamera() { + TODO("Not yet implemented") + } + + override fun moveToPosition(coordinates: W3WCoordinates, animated: Boolean) { + TODO("Not yet implemented") + } + + override fun getZoomLevel(): Float { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/lib-compose/src/main/res/drawable/ic_marker.xml b/lib-compose/src/main/res/drawable/ic_marker.xml index 4df399c8..b6f77d97 100644 --- a/lib-compose/src/main/res/drawable/ic_marker.xml +++ b/lib-compose/src/main/res/drawable/ic_marker.xml @@ -3,11 +3,11 @@ android:height="56dp" android:viewportWidth="48" android:viewportHeight="56"> - - + + From 040180427fd16f7f8f6d0be64caf80dca11ed081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Duy=20Ph=E1=BA=A1m?= Date: Mon, 18 Nov 2024 12:40:26 +0700 Subject: [PATCH 2/3] Draw grid on GoogleMap --- .../maps/providers/googlemap/W3WGoogleMap.kt | 2 +- .../providers/googlemap/W3WGoogleMapDrawer.kt | 161 ++++++++++++++ .../googlemap/W3WGoogleMapDrawers.kt | 202 ------------------ .../maps/providers/mapbox/W3WMapBoxDrawers.kt | 2 - .../compose/maps/state/W3WCameraPosition.kt | 38 ---- 5 files changed, 162 insertions(+), 243 deletions(-) create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt delete mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawers.kt delete mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt 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 c0dd5b81..998bb3f3 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 @@ -50,7 +50,7 @@ fun W3WGoogleMap( ) } - val uiSettings = remember(state.isMyLocationButtonEnabled, state.isMapGestureEnable) { + val uiSettings = remember(state.isMapGestureEnable) { MapUiSettings( zoomControlsEnabled = false, myLocationButtonEnabled = false, diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt new file mode 100644 index 00000000..39e022d1 --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt @@ -0,0 +1,161 @@ +package com.what3words.components.compose.maps.providers.googlemap + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.GoogleMapComposable +import com.google.maps.android.compose.Marker +import com.google.maps.android.compose.Polyline +import com.google.maps.android.compose.rememberMarkerState +import com.what3words.components.compose.maps.W3WMapDefaults +import com.what3words.components.compose.maps.models.Marker +import com.what3words.components.compose.maps.state.W3WMapState +import com.what3words.core.types.domain.W3WAddress +import com.what3words.core.types.geometry.W3WCoordinates +import com.what3words.map.components.compose.R + +@Composable +@GoogleMapComposable +fun W3WGoogleMapDrawer( + state: W3WMapState, + mapConfig: W3WMapDefaults.MapConfig +) { + + if (mapConfig.gridLineConfig.isGridEnabled) { + state.cameraState?.let { + W3WGoogleMapDrawGridLines( + verticalLines = state.gridPolyline.first, + horizontalLines = state.gridPolyline.second, + zoomLevel = it.getZoomLevel(), + zoomSwitchLevel = mapConfig.gridLineConfig.zoomSwitchLevel + ) + } + } + + //Draw the markers + W3WGoogleMapDrawMarkers(mapConfig.gridLineConfig.zoomSwitchLevel, state.listMakers) + + //Draw the selected address + state.selectedAddress?.let { W3WGoogleMapDrawSelectedAddress(mapConfig.gridLineConfig.zoomSwitchLevel, it) } +} + +@Composable +@GoogleMapComposable +fun W3WGoogleMapDrawGridLines( + verticalLines: List, + horizontalLines: List, + zoomSwitchLevel: Float, + zoomLevel: Float +) { + W3WGoogleMapGrid( + verticalLines = verticalLines, + horizontalLines = horizontalLines, + zoomLevel = zoomLevel, + zoomSwitchLevel = zoomSwitchLevel + ) +} + +@Composable +@GoogleMapComposable +fun W3WGoogleMapDrawSelectedAddress(zoomLevel: Float, address: W3WAddress) { + //TODO: Draw select for zoom in: grid, square + + //TODO: Draw select for zoom out: pin (maker) + + + // The code below is an example of how to draw a marker on the map. + val context = LocalContext.current + + val markerState = + rememberMarkerState(position = LatLng(address.center!!.lat, address.center!!.lng)) + LaunchedEffect(address) { + markerState.position = LatLng(address.center!!.lat, address.center!!.lng) + } + + val icon = remember(context) { + ContextCompat.getDrawable(context, R.drawable.ic_marker)?.let { drawable -> + BitmapDescriptorFactory.fromBitmap( + drawable.toBitmap( + width = drawable.intrinsicWidth, + height = drawable.intrinsicHeight + ) + ) + } + } + + Marker( + state = markerState, + icon = icon + ) +} + +@Composable +@GoogleMapComposable +fun W3WGoogleMapDrawMarkers(zoomLevel: Float, listMakers: Map>) { + //TODO: Draw select for zoom in: filled square + + //TODO: Draw select for zoom out: circle + + // This code below is an example of how to draw a marker on the map. + listMakers.forEach { (key, markers) -> + markers.forEach { marker -> + Marker( + state = rememberMarkerState( + position = LatLng( + marker.address.center!!.lat, + marker.address.center!!.lng + ) + ), + title = marker.title, + snippet = marker.snippet, + ) + } + } +} + +@Composable +@GoogleMapComposable +private fun W3WGoogleMapGrid( + verticalLines: List, + horizontalLines: List, + zoomSwitchLevel: Float, + zoomLevel: Float +) { + + val horizontalPolylines = horizontalLines.map { coordinate -> + LatLng(coordinate.lat, coordinate.lng) + } + + val verticalPolylines = verticalLines.map { coordinate -> + LatLng(coordinate.lat, coordinate.lng) + } + + Polyline( + points = horizontalPolylines, + color = Color.LightGray, + width = getGridBorderSizeBasedOnZoomLevel(zoomLevel, zoomSwitchLevel).value, + clickable = false + ) + + Polyline( + points = verticalPolylines, + color = Color.LightGray, + width = getGridBorderSizeBasedOnZoomLevel(zoomLevel, zoomSwitchLevel).value, + clickable = false + ) +} + +private fun getGridBorderSizeBasedOnZoomLevel(zoomLevel: Float, zoomSwitchLevel: Float): Dp { + return when { + zoomLevel < zoomSwitchLevel -> 0.dp + else -> 1.dp + } +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawers.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawers.kt deleted file mode 100644 index 9f482acb..00000000 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawers.kt +++ /dev/null @@ -1,202 +0,0 @@ -package com.what3words.components.compose.maps.providers.googlemap - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.toBitmap -import com.google.android.gms.maps.CameraUpdateFactory -import com.google.android.gms.maps.model.BitmapDescriptorFactory -import com.google.android.gms.maps.model.LatLng -import com.google.android.gms.maps.model.MapStyleOptions -import com.google.maps.android.compose.GoogleMap -import com.google.maps.android.compose.GoogleMapComposable -import com.google.maps.android.compose.MapProperties -import com.google.maps.android.compose.MapUiSettings -import com.google.maps.android.compose.Marker -import com.google.maps.android.compose.Polyline -import com.google.maps.android.compose.rememberCameraPositionState -import com.google.maps.android.compose.rememberMarkerState -import com.what3words.components.compose.maps.W3WMapDefaults -import com.what3words.components.compose.maps.W3WMapState -import com.what3words.components.compose.maps.mapper.toGoogleCameraPosition -import com.what3words.components.compose.maps.mapper.toGoogleMapType -import com.what3words.components.compose.maps.mapper.toW3WMapStateCameraPosition -import com.what3words.core.types.domain.W3WAddress -import com.what3words.core.types.geometry.W3WCoordinates -import com.what3words.map.components.compose.R - -@Composable -fun W3WGoogleMap( - modifier: Modifier, - layoutConfig: W3WMapDefaults.LayoutConfig, - mapConfig: W3WMapDefaults.MapConfig, - state: W3WMapState, - content: (@Composable () -> Unit)? = null, - onMapClicked: ((W3WCoordinates) -> Unit), - onCameraUpdated: ((W3WMapState.CameraPosition) -> Unit) -) { - val mapProperties = remember(state.mapType, state.isMyLocationEnabled, state.isDarkMode) { - MapProperties( - mapType = state.mapType.toGoogleMapType(), - isMyLocationEnabled = state.isMyLocationEnabled, - mapStyleOptions = if (state.isDarkMode) MapStyleOptions(mapConfig.darkModeCustomJsonStyle) else null - ) - } - - val uiSettings = remember(state.isMapGestureEnable) { - MapUiSettings( - zoomControlsEnabled = false, - myLocationButtonEnabled = false, - scrollGesturesEnabled = state.isMapGestureEnable, - tiltGesturesEnabled = state.isMapGestureEnable, - zoomGesturesEnabled = state.isMapGestureEnable, - rotationGesturesEnabled = state.isMapGestureEnable, - scrollGesturesEnabledDuringRotateOrZoom = state.isMapGestureEnable - ) - } - - val cameraPositionState = rememberCameraPositionState { - state.cameraPosition?.let { - position = it.toGoogleCameraPosition() - } - } - - LaunchedEffect(state.cameraPosition) { - state.cameraPosition?.let { - if (it != cameraPositionState.toW3WMapStateCameraPosition()) { - if (it.isAnimated) { - cameraPositionState.animate( - update = CameraUpdateFactory.newCameraPosition(it.toGoogleCameraPosition()) - ) - } else { - cameraPositionState.position = it.toGoogleCameraPosition() - } - } - } - } - - LaunchedEffect(cameraPositionState.position, cameraPositionState.isMoving) { -// cameraPositionState.projection.visibleRegion.latLngBounds to Box - onCameraUpdated.invoke(cameraPositionState.toW3WMapStateCameraPosition()) - } - - GoogleMap( - modifier = modifier, - cameraPositionState = cameraPositionState, - contentPadding = layoutConfig.contentPadding, - uiSettings = uiSettings, - properties = mapProperties, - onMapClick = { - onMapClicked.invoke(W3WCoordinates(it.latitude, it.longitude)) - }, - ) { - W3WGoogleMapDrawer(state = state, mapConfig) - content?.invoke() - } -} - -@Composable -@GoogleMapComposable -fun W3WGoogleMapDrawer( - state: W3WMapState, - mapConfig: W3WMapDefaults.MapConfig -) { - - //Draw the grid lines by zoom in state - W3WGoogleMapDrawGridLines(mapConfig.gridLineConfig) - - //Draw the markers - W3WGoogleMapDrawMarkers(mapConfig.gridLineConfig.zoomSwitchLevel, state.listMakers) - - //Draw the selected address - state.selectedAddress?.let { - W3WGoogleMapDrawSelectedAddress( - mapConfig.gridLineConfig.zoomSwitchLevel, - it - ) - } -} - -@Composable -@GoogleMapComposable -fun W3WGoogleMapDrawGridLines(gridLineConfig: W3WMapDefaults.GridLineConfig) { - //TODO: Draw visible grid lines based on zoomLevel - - - //The code below is an example of how to draw a polyline on the map. - val polylineCoordinates = remember { - listOf( - LatLng(37.7749, -122.4194), // San Francisco - LatLng(34.0522, -118.2437), // Los Angeles - LatLng(40.7128, -74.0060) // New York City - ) - } - - Polyline( - points = polylineCoordinates, - color = Color.Blue, - width = 10f - ) -} - -@Composable -@GoogleMapComposable -fun W3WGoogleMapDrawSelectedAddress(zoomLevel: Float, address: W3WAddress) { - //TODO: Draw select for zoom in: grid, square - - //TODO: Draw select for zoom out: pin (maker) - - - // The code below is an example of how to draw a marker on the map. - val context = LocalContext.current - - val markerState = - rememberMarkerState(position = LatLng(address.center!!.lat, address.center!!.lng)) - LaunchedEffect(address) { - markerState.position = LatLng(address.center!!.lat, address.center!!.lng) - } - - val icon = remember(context) { - ContextCompat.getDrawable(context, R.drawable.ic_marker)?.let { drawable -> - BitmapDescriptorFactory.fromBitmap( - drawable.toBitmap( - width = drawable.intrinsicWidth, - height = drawable.intrinsicHeight - ) - ) - } - } - - Marker( - state = markerState, - icon = icon - ) -} - -@Composable -@GoogleMapComposable -fun W3WGoogleMapDrawMarkers(zoomLevel: Float, listMakers: Map>) { - //TODO: Draw select for zoom in: filled square - - //TODO: Draw select for zoom out: circle - - // This code below is an example of how to draw a marker on the map. - listMakers.forEach { (key, markers) -> - markers.forEach { marker -> - Marker( - state = rememberMarkerState( - position = LatLng( - marker.address.center!!.lat, - marker.address.center!!.lng - ) - ), - title = marker.title, - snippet = marker.snippet, - ) - } - } -} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt index 5730ab44..5f65b165 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt @@ -19,9 +19,7 @@ import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck import com.mapbox.maps.plugin.locationcomponent.location import com.what3words.components.compose.maps.W3WMapDefaults import com.what3words.components.compose.maps.W3WMapState -import com.what3words.components.compose.maps.mapper.toMapBoxCameraOptions import com.what3words.components.compose.maps.mapper.toMapBoxMapType -import com.what3words.components.compose.maps.mapper.toW3WCameraPosition import com.what3words.components.compose.maps.models.Marker import com.what3words.components.compose.maps.state.W3WMapState import com.what3words.core.types.domain.W3WAddress diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt deleted file mode 100644 index 90446eaa..00000000 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WCameraPosition.kt +++ /dev/null @@ -1,38 +0,0 @@ -//package com.what3words.components.compose.maps.state -// -//import com.what3words.core.types.geometry.W3WCoordinates -//import com.what3words.core.types.geometry.W3WRectangle -// -//data class W3WCameraPosition( -// val zoomLevel: Float, -// val coordinates: W3WCoordinates, -// val bearing: Float, -// val isAnimated: Boolean = false, -// val isMoving: Boolean = false, -// val tilt: Float, -// val gridBound: W3WRectangle? = null -//) { -// override fun hashCode(): Int { -// var result = zoomLevel.hashCode() -// result = 31 * result + coordinates.hashCode() -// result = 31 * result + bearing.hashCode() -// result = 31 * result + tilt.hashCode() -// result = 31 * result + isAnimated.hashCode() -// result = 31 * result + isMoving.hashCode() -// return result -// } -// -// override fun equals(other: Any?): Boolean { -// if (this === other) return true -// if (javaClass != other?.javaClass) return false -// -// other as W3WCameraPosition -// -// if (zoomLevel != other.zoomLevel) return false -// if (coordinates != other.coordinates) return false -// if (bearing != other.bearing) return false -// if (tilt != other.tilt) return false -// -// return true -// } -//} \ No newline at end of file From f643c78c8a3e02e7462087b326cebb7aef69c139 Mon Sep 17 00:00:00 2001 From: kaytran2992 Date: Mon, 18 Nov 2024 14:47:19 +0700 Subject: [PATCH 3/3] Update model name marker, maptype, zoom with prefix w3w --- .../compose/maps/W3WMapComponent.kt | 5 +- .../components/compose/maps/W3WMapManager.kt | 22 ++-- .../compose/maps/buttons/W3WMapButtons.kt | 4 +- .../buttons/mapswitch/W3WMapSwitchButton.kt | 21 ++- .../compose/maps/mapper/GoogleMapConverter.kt | 10 +- .../compose/maps/mapper/MapBoxConverter.kt | 12 +- .../maps/models/{MapType.kt => W3WMapType.kt} | 2 +- .../maps/models/{Marker.kt => W3WMarker.kt} | 2 +- .../{ZoomOption.kt => W3WZoomOption.kt} | 2 +- .../maps/providers/googlemap/W3WGoogleMap.kt | 1 - .../providers/googlemap/W3WGoogleMapDrawer.kt | 4 +- .../maps/providers/mapbox/W3WMapBox.kt | 88 +++++++++++++ .../maps/providers/mapbox/W3WMapBoxDrawers.kt | 122 +++--------------- .../compose/maps/state/W3WMapState.kt | 16 +-- .../maps/state/W3WMapboxCameraState.kt | 27 +++- 15 files changed, 179 insertions(+), 159 deletions(-) rename lib-compose/src/main/java/com/what3words/components/compose/maps/models/{MapType.kt => W3WMapType.kt} (81%) rename lib-compose/src/main/java/com/what3words/components/compose/maps/models/{Marker.kt => W3WMarker.kt} (92%) rename lib-compose/src/main/java/com/what3words/components/compose/maps/models/{ZoomOption.kt => W3WZoomOption.kt} (78%) create mode 100644 lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBox.kt diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt index 12232837..dd861b35 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/W3WMapComponent.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.what3words.components.compose.maps.buttons.W3WMapButtons +import com.what3words.components.compose.maps.models.W3WMapType import com.what3words.components.compose.maps.providers.googlemap.W3WGoogleMap import com.what3words.components.compose.maps.providers.mapbox.W3WMapBox import com.what3words.components.compose.maps.state.W3WCameraState @@ -80,7 +81,7 @@ fun W3WMapComponent( state: W3WMapState, mapProvider: MapProvider, content: (@Composable () -> Unit)? = null, - onMapTypeClicked: ((W3WMapState.MapType) -> Unit)? = null, + onMapTypeClicked: ((W3WMapType) -> Unit)? = null, onMapClicked: ((W3WCoordinates) -> Unit)? = null, onCameraUpdated: (W3WCameraState<*>) -> Unit ) { @@ -111,7 +112,7 @@ internal fun W3WMapContent( state: W3WMapState, mapProvider: MapProvider, content: (@Composable () -> Unit)? = null, - onMapTypeClicked: ((W3WMapState.MapType) -> Unit), + onMapTypeClicked: ((W3WMapType) -> Unit), onMapClicked: (W3WCoordinates) -> Unit, onCameraUpdated: (W3WCameraState<*>) -> Unit ) { 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 6e84ceac..74e958b1 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 @@ -11,9 +11,9 @@ import com.mapbox.maps.extension.compose.animation.viewport.MapViewportState import com.what3words.androidwrapper.datasource.text.api.error.BadBoundingBoxTooBigError import com.what3words.components.compose.maps.extensions.computeHorizontalLines import com.what3words.components.compose.maps.extensions.computeVerticalLines -import com.what3words.components.compose.maps.models.MapType -import com.what3words.components.compose.maps.models.Marker -import com.what3words.components.compose.maps.models.ZoomOption +import com.what3words.components.compose.maps.models.W3WMapType +import com.what3words.components.compose.maps.models.W3WMarker +import com.what3words.components.compose.maps.models.W3WZoomOption import com.what3words.components.compose.maps.state.W3WCameraState import com.what3words.components.compose.maps.state.W3WGoogleCameraState import com.what3words.components.compose.maps.state.W3WMapState @@ -110,11 +110,11 @@ class W3WMapManager( //endregion //region Map Ui Settings - fun getMapType(): MapType { + fun getMapType(): W3WMapType { return state.value.mapType } - fun setMapType(mapType: MapType) { + fun setMapType(mapType: W3WMapType) { _state.update { it.copy( mapType = mapType @@ -179,7 +179,7 @@ class W3WMapManager( fun addMarkerAtWords( words: String, markerColor: Color = Color.Red, - zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, + zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer? = null, onError: Consumer? = null, zoomLevel: Float? = null @@ -188,7 +188,7 @@ class W3WMapManager( when (val c23wa = textDataSource.convertToCoordinates(words)) { is W3WResult.Success -> { _state.value = state.value.addOrUpdateMarker( - marker = Marker(address = c23wa.value, color = markerColor) + marker = W3WMarker(address = c23wa.value, color = markerColor) ) onSuccess?.accept(c23wa.value) } @@ -203,7 +203,7 @@ class W3WMapManager( fun addMarkerAtListWords( listWords: List, markerColor: Color = Color.Red, - zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, + zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer>? = null, onError: Consumer? = null ) { @@ -213,7 +213,7 @@ class W3WMapManager( fun addMarkerAtCoordinates( coordinates: W3WCoordinates, markerColor: Color = Color.Red, - zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, + zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer? = null, onError: Consumer? = null, zoomLevel: Float? = null @@ -222,7 +222,7 @@ class W3WMapManager( when (val c23wa = textDataSource.convertTo3wa(coordinates, language)) { is W3WResult.Success -> { _state.value = state.value.addOrUpdateMarker( - marker = Marker(address = c23wa.value, color = markerColor) + marker = W3WMarker(address = c23wa.value, color = markerColor) ) onSuccess?.accept(c23wa.value) } @@ -236,7 +236,7 @@ class W3WMapManager( fun selectAtCoordinates( coordinates: W3WCoordinates, - zoomOption: ZoomOption = ZoomOption.CENTER_AND_ZOOM, + zoomOption: W3WZoomOption = W3WZoomOption.CENTER_AND_ZOOM, onSuccess: Consumer? = null, onError: Consumer? = null, zoomLevel: Float? = null diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/W3WMapButtons.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/W3WMapButtons.kt index c6c8bea9..8cadd8e9 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/W3WMapButtons.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/W3WMapButtons.kt @@ -16,14 +16,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import com.what3words.components.compose.maps.W3WMapState import com.what3words.components.compose.maps.buttons.mapswitch.W3WMapSwitchButton +import com.what3words.components.compose.maps.models.W3WMapType @Composable fun W3WMapButtons( modifier: Modifier = Modifier, onMyLocationClicked: (() -> Unit), - onMapTypeClicked: ((W3WMapState.MapType) -> Unit), + onMapTypeClicked: ((W3WMapType) -> Unit), ) { Column( modifier = modifier, diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/mapswitch/W3WMapSwitchButton.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/mapswitch/W3WMapSwitchButton.kt index 243d849d..79dad5f4 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/mapswitch/W3WMapSwitchButton.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/buttons/mapswitch/W3WMapSwitchButton.kt @@ -3,7 +3,6 @@ package com.what3words.components.compose.maps.buttons.mapswitch import androidx.compose.foundation.Image import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -15,7 +14,7 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.what3words.components.compose.maps.W3WMapState +import com.what3words.components.compose.maps.models.W3WMapType import com.what3words.map.components.compose.R /** @@ -29,8 +28,8 @@ import com.what3words.map.components.compose.R @Composable fun W3WMapSwitchButton( modifier: Modifier = Modifier, - w3wMapType: W3WMapState.MapType = W3WMapState.MapType.NORMAL, - onMapTypeChange: (W3WMapState.MapType) -> Unit + w3wMapType: W3WMapType = W3WMapType.NORMAL, + onMapTypeChange: (W3WMapType) -> Unit ) { var mapType by remember { mutableStateOf(w3wMapType) } IconButton( @@ -39,9 +38,9 @@ fun W3WMapSwitchButton( .size(50.dp), onClick = { mapType = when (mapType) { - W3WMapState.MapType.NORMAL -> W3WMapState.MapType.SATELLITE - W3WMapState.MapType.SATELLITE -> W3WMapState.MapType.NORMAL - else -> W3WMapState.MapType.NORMAL // Default to NORMAL + W3WMapType.NORMAL -> W3WMapType.SATELLITE + W3WMapType.SATELLITE -> W3WMapType.NORMAL + else -> W3WMapType.NORMAL // Default to NORMAL } onMapTypeChange(mapType) } @@ -49,8 +48,8 @@ fun W3WMapSwitchButton( Image( painter = painterResource( id = when (mapType) { - W3WMapState.MapType.NORMAL -> R.drawable.ic_map_satellite - W3WMapState.MapType.SATELLITE -> R.drawable.ic_map_normal + W3WMapType.NORMAL -> R.drawable.ic_map_satellite + W3WMapType.SATELLITE -> R.drawable.ic_map_normal else -> R.drawable.ic_map_satellite } ), @@ -62,11 +61,11 @@ fun W3WMapSwitchButton( @Preview @Composable fun MapNormalPreview() { - W3WMapSwitchButton(w3wMapType = W3WMapState.MapType.NORMAL) {} + W3WMapSwitchButton(w3wMapType = W3WMapType.NORMAL) {} } @Preview @Composable fun MapTypeSwitcherPreview() { - W3WMapSwitchButton(w3wMapType = W3WMapState.MapType.SATELLITE) {} + W3WMapSwitchButton(w3wMapType = W3WMapType.SATELLITE) {} } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt index c1921337..6974a037 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/GoogleMapConverter.kt @@ -12,11 +12,11 @@ fun LatLng.toW3WCoordinates(): W3WCoordinates { return W3WCoordinates(this.latitude, this.longitude) } -fun com.what3words.components.compose.maps.models.MapType.toGoogleMapType(): MapType { +fun com.what3words.components.compose.maps.models.W3WMapType.toGoogleMapType(): MapType { return when (this) { - com.what3words.components.compose.maps.models.MapType.NORMAL -> MapType.NORMAL - com.what3words.components.compose.maps.models.MapType.SATELLITE -> MapType.SATELLITE - com.what3words.components.compose.maps.models.MapType.HYBRID -> MapType.HYBRID - com.what3words.components.compose.maps.models.MapType.TERRAIN -> MapType.TERRAIN + com.what3words.components.compose.maps.models.W3WMapType.NORMAL -> MapType.NORMAL + com.what3words.components.compose.maps.models.W3WMapType.SATELLITE -> MapType.SATELLITE + com.what3words.components.compose.maps.models.W3WMapType.HYBRID -> MapType.HYBRID + com.what3words.components.compose.maps.models.W3WMapType.TERRAIN -> MapType.TERRAIN } } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt index 8cbd5d89..3068f881 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/mapper/MapBoxConverter.kt @@ -1,13 +1,13 @@ package com.what3words.components.compose.maps.mapper import com.mapbox.maps.Style -import com.what3words.components.compose.maps.models.MapType +import com.what3words.components.compose.maps.models.W3WMapType -fun MapType.toMapBoxMapType(isDarkMode: Boolean): String { +fun W3WMapType.toMapBoxMapType(isDarkMode: Boolean): String { return when (this) { - MapType.NORMAL -> if (isDarkMode) Style.DARK else Style.MAPBOX_STREETS - MapType.SATELLITE -> Style.SATELLITE_STREETS - MapType.HYBRID -> Style.SATELLITE_STREETS - MapType.TERRAIN -> Style.OUTDOORS + W3WMapType.NORMAL -> if (isDarkMode) Style.DARK else Style.MAPBOX_STREETS + W3WMapType.SATELLITE -> Style.SATELLITE_STREETS + W3WMapType.HYBRID -> Style.SATELLITE_STREETS + W3WMapType.TERRAIN -> Style.OUTDOORS } } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WMapType.kt similarity index 81% rename from lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt rename to lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WMapType.kt index d6f27fc1..c1d2db75 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/MapType.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WMapType.kt @@ -1,6 +1,6 @@ package com.what3words.components.compose.maps.models -enum class MapType { +enum class W3WMapType { NORMAL, HYBRID, TERRAIN, diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WMarker.kt similarity index 92% rename from lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt rename to lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WMarker.kt index 7786c2b0..928a2a47 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/Marker.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WMarker.kt @@ -3,7 +3,7 @@ package com.what3words.components.compose.maps.models import androidx.compose.ui.graphics.Color import com.what3words.core.types.domain.W3WAddress -data class Marker( +data class W3WMarker( val address: W3WAddress, val color: Color, val title: String? = null, diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WZoomOption.kt similarity index 78% rename from lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt rename to lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WZoomOption.kt index e7aee038..b127298d 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/models/ZoomOption.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/models/W3WZoomOption.kt @@ -1,6 +1,6 @@ package com.what3words.components.compose.maps.models -enum class ZoomOption { +enum class W3WZoomOption { NONE, CENTER, CENTER_AND_ZOOM 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 998bb3f3..51819f21 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 @@ -1,7 +1,6 @@ package com.what3words.components.compose.maps.providers.googlemap import android.graphics.Point -import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt index 39e022d1..22a81112 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/googlemap/W3WGoogleMapDrawer.kt @@ -16,7 +16,7 @@ import com.google.maps.android.compose.Marker import com.google.maps.android.compose.Polyline import com.google.maps.android.compose.rememberMarkerState import com.what3words.components.compose.maps.W3WMapDefaults -import com.what3words.components.compose.maps.models.Marker +import com.what3words.components.compose.maps.models.W3WMarker import com.what3words.components.compose.maps.state.W3WMapState import com.what3words.core.types.domain.W3WAddress import com.what3words.core.types.geometry.W3WCoordinates @@ -99,7 +99,7 @@ fun W3WGoogleMapDrawSelectedAddress(zoomLevel: Float, address: W3WAddress) { @Composable @GoogleMapComposable -fun W3WGoogleMapDrawMarkers(zoomLevel: Float, listMakers: Map>) { +fun W3WGoogleMapDrawMarkers(zoomLevel: Float, listMakers: Map>) { //TODO: Draw select for zoom in: filled square //TODO: Draw select for zoom out: circle 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 new file mode 100644 index 00000000..386f48ab --- /dev/null +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBox.kt @@ -0,0 +1,88 @@ +package com.what3words.components.compose.maps.providers.mapbox + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import com.mapbox.maps.MapView +import com.mapbox.maps.extension.compose.MapEffect +import com.mapbox.maps.extension.compose.MapboxMap +import com.mapbox.maps.extension.compose.rememberMapState +import com.mapbox.maps.extension.compose.style.GenericStyle +import com.mapbox.maps.plugin.gestures.generated.GesturesSettings +import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck +import com.mapbox.maps.plugin.locationcomponent.location +import com.what3words.components.compose.maps.W3WMapDefaults +import com.what3words.components.compose.maps.mapper.toMapBoxMapType +import com.what3words.components.compose.maps.state.W3WMapState +import com.what3words.components.compose.maps.state.W3WMapboxCameraState +import com.what3words.core.types.geometry.W3WCoordinates + +@Composable +fun W3WMapBox( + modifier: Modifier, + layoutConfig: W3WMapDefaults.LayoutConfig, + mapConfig: W3WMapDefaults.MapConfig, + state: W3WMapState, + content: (@Composable () -> Unit)? = null, + onMapClicked: ((W3WCoordinates) -> Unit), +) { + var mapView: MapView? by remember { + mutableStateOf(null) + } + + val mapViewportState = (state.cameraState as W3WMapboxCameraState).cameraState + + val mapState = rememberMapState() + + val mapStyle = remember(state.mapType, state.isDarkMode) { + state.mapType.toMapBoxMapType(state.isDarkMode) + } + + LaunchedEffect(state.isMyLocationEnabled) { + mapView?.let { + it.location.updateSettings { + enabled = state.isMyLocationEnabled + } + } + } + + LaunchedEffect(state.isMapGestureEnable) { + state.isMapGestureEnable.let { + mapState.gesturesSettings = GesturesSettings { + pitchEnabled = it + rotateEnabled = it + scrollEnabled = it + quickZoomEnabled = it + pinchScrollEnabled = it + doubleTapToZoomInEnabled = it + doubleTouchToZoomOutEnabled = it + } + } + } + + MapboxMap( + modifier = modifier, + mapState = mapState, + mapViewportState = mapViewportState, + style = { + GenericStyle( + style = mapStyle, + ) + } + ) { + MapEffect(Unit) { + mapView = it + it.location.updateSettings { + enabled = state.isMyLocationEnabled + locationPuck = createDefault2DPuck(withBearing = false) + } + } + + W3WMapBoxDrawer(state, mapConfig) + content?.invoke() + } +} \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt index 5f65b165..6e62fcb0 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/providers/mapbox/W3WMapBoxDrawers.kt @@ -1,102 +1,28 @@ package com.what3words.components.compose.maps.providers.mapbox import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import com.mapbox.maps.MapView -import com.mapbox.maps.extension.compose.MapEffect -import com.mapbox.maps.extension.compose.MapboxMap import com.mapbox.maps.extension.compose.MapboxMapComposable -import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState -import com.mapbox.maps.extension.compose.rememberMapState -import com.mapbox.maps.extension.compose.style.GenericStyle -import com.mapbox.maps.plugin.gestures.generated.GesturesSettings -import com.mapbox.maps.plugin.locationcomponent.createDefault2DPuck -import com.mapbox.maps.plugin.locationcomponent.location import com.what3words.components.compose.maps.W3WMapDefaults -import com.what3words.components.compose.maps.W3WMapState -import com.what3words.components.compose.maps.mapper.toMapBoxMapType -import com.what3words.components.compose.maps.models.Marker +import com.what3words.components.compose.maps.models.W3WMarker import com.what3words.components.compose.maps.state.W3WMapState import com.what3words.core.types.domain.W3WAddress -import com.what3words.core.types.geometry.W3WCoordinates -import kotlinx.coroutines.launch -@Composable -fun W3WMapBox( - modifier: Modifier, - layoutConfig: W3WMapDefaults.LayoutConfig, - mapConfig: W3WMapDefaults.MapConfig, - state: W3WMapState, - content: (@Composable () -> Unit)? = null, - onMapClicked: ((W3WCoordinates) -> Unit), -) { - var mapView: MapView? by remember { - mutableStateOf(null) - } - - var isCameraMoving: Boolean by remember { - mutableStateOf(false) - } - - val mapViewportState = rememberMapViewportState { - setCameraOptions { - state.cameraPosition?.toMapBoxCameraOptions() - } - } - - val mapState = rememberMapState() - - val mapStyle = remember(state.mapType, state.isDarkMode) { - state.mapType.toMapBoxMapType(state.isDarkMode) - } - - LaunchedEffect(state.isMyLocationEnabled) { - mapView?.let { - it.location.updateSettings { - enabled = state.isMyLocationEnabled - } - } - } - LaunchedEffect(state.isMapGestureEnable) { - state.isMapGestureEnable.let { - mapState.gesturesSettings = GesturesSettings { - pitchEnabled = it - rotateEnabled = it - scrollEnabled = it - quickZoomEnabled = it - pinchScrollEnabled = it - doubleTapToZoomInEnabled = it - doubleTouchToZoomOutEnabled = it - } - } - } +@Composable +@MapboxMapComposable +fun W3WMapBoxDrawer(state: W3WMapState, mapConfig: W3WMapDefaults.MapConfig) { + //Draw the grid lines by zoom in state + W3WMapBoxDrawGridLines(mapConfig.gridLineConfig) - MapboxMap( - modifier = modifier, - mapState = mapState, - mapViewportState = mapViewportState, - style = { - GenericStyle( - style = mapStyle, - ) - } - ) { - MapEffect(Unit) { - mapView = it - it.location.updateSettings { - enabled = state.isMyLocationEnabled - locationPuck = createDefault2DPuck(withBearing = false) - } - } + //Draw the markers + W3WMapBoxDrawMarkers(mapConfig.gridLineConfig.zoomSwitchLevel, state.listMakers) - W3WMapBoxDrawer(state, mapConfig) - content?.invoke() + //Draw the selected address + state.selectedAddress?.let { + W3WMapBoxDrawSelectedAddress( + mapConfig.gridLineConfig.zoomSwitchLevel, + it + ) } } @@ -116,26 +42,8 @@ fun W3WMapBoxDrawSelectedAddress(zoomLevel: Float, address: W3WAddress) { @Composable @MapboxMapComposable -fun W3WMapBoxDrawMarkers(zoomLevel: Float, listMakers: Map>) { +fun W3WMapBoxDrawMarkers(zoomLevel: Float, listMakers: Map>) { //TODO: Draw select for zoom in: filled square //TODO: Draw select for zoom out: circle -} - -@Composable -@MapboxMapComposable -fun W3WMapBoxDrawer(state: W3WMapState, mapConfig: W3WMapDefaults.MapConfig) { - //Draw the grid lines by zoom in state - W3WMapBoxDrawGridLines(mapConfig.gridLineConfig) - - //Draw the markers - W3WMapBoxDrawMarkers(mapConfig.gridLineConfig.zoomSwitchLevel, state.listMakers) - - //Draw the selected address - state.selectedAddress?.let { - W3WMapBoxDrawSelectedAddress( - mapConfig.gridLineConfig.zoomSwitchLevel, - it - ) - } } \ No newline at end of file diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt index 93932533..f3497405 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapState.kt @@ -1,7 +1,7 @@ package com.what3words.components.compose.maps.state -import com.what3words.components.compose.maps.models.MapType -import com.what3words.components.compose.maps.models.Marker +import com.what3words.components.compose.maps.models.W3WMapType +import com.what3words.components.compose.maps.models.W3WMarker import com.what3words.core.types.domain.W3WAddress import com.what3words.core.types.geometry.W3WCoordinates import com.what3words.core.types.language.W3WRFC5646Language @@ -12,7 +12,7 @@ data class W3WMapState( // Map Config val language: W3WRFC5646Language = W3WRFC5646Language.EN_GB, - val mapType: MapType = MapType.NORMAL, + val mapType: W3WMapType = W3WMapType.NORMAL, val isDarkMode: Boolean = false, @@ -27,7 +27,7 @@ data class W3WMapState( val selectedAddress: W3WAddress? = null, // List marker - val listMakers: Map> = emptyMap(), + val listMakers: Map> = emptyMap(), val cameraState: W3WCameraState<*>? = null, @@ -40,7 +40,7 @@ data class W3WMapState( fun W3WMapState.addOrUpdateMarker( listId: String? = null, - marker: Marker + marker: W3WMarker ): W3WMapState { val key = listId ?: LIST_DEFAULT_ID val currentList = listMakers[key] ?: emptyList() @@ -57,11 +57,11 @@ fun W3WMapState.addOrUpdateMarker( return copy(listMakers = listMakers + (key to updatedList)) } -fun W3WMapState.getListIdsByMarker(marker: Marker): List { +fun W3WMapState.getListIdsByMarker(marker: W3WMarker): List { return listMakers.entries.filter { (_, markers) -> marker in markers }.map { it.key } } -fun W3WMapState.getMarkersByListId(listId: String): List { +fun W3WMapState.getMarkersByListId(listId: String): List { return listMakers[listId] ?: emptyList() } @@ -69,7 +69,7 @@ fun W3WMapState.removeMarkersByList(listId: String): W3WMapState { return copy(listMakers = listMakers - listId) } -fun W3WMapState.getAllMarkers(): List { +fun W3WMapState.getAllMarkers(): List { return listMakers.values.flatten() } diff --git a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt index 7906fa27..ded968fa 100644 --- a/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt +++ b/lib-compose/src/main/java/com/what3words/components/compose/maps/state/W3WMapboxCameraState.kt @@ -1,8 +1,16 @@ package com.what3words.components.compose.maps.state +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.mapbox.geojson.Point +import com.mapbox.maps.CameraOptions import com.mapbox.maps.extension.compose.animation.viewport.MapViewportState import com.what3words.core.types.geometry.W3WCoordinates import com.what3words.core.types.geometry.W3WRectangle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class W3WMapboxCameraState(override val cameraState: MapViewportState) : W3WCameraState { @@ -16,7 +24,24 @@ class W3WMapboxCameraState(override val cameraState: MapViewportState) : } override fun moveToPosition(coordinates: W3WCoordinates, animated: Boolean) { - TODO("Not yet implemented") + CoroutineScope(Dispatchers.IO).launch { + val cameraOptions = CameraOptions.Builder() + .pitch(cameraState.cameraState?.pitch) + .bearing(cameraState.cameraState?.bearing) + .center(Point.fromLngLat(coordinates.lng, coordinates.lat)) + .zoom(cameraState.cameraState?.zoom) + .build() + + if (animated) { + cameraState.flyTo( + cameraOptions + ) + } else { + cameraState.setCameraOptions( + cameraOptions + ) + } + } } override fun getZoomLevel(): Float {