diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt index 04a581467..4e8066e6f 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapView.kt @@ -116,7 +116,7 @@ import com.google.maps.android.compose.Polyline import com.google.maps.android.compose.TileOverlay import com.google.maps.android.compose.rememberCameraPositionState import com.google.maps.android.compose.rememberUpdatedMarkerState -import com.google.maps.android.compose.widgets.DisappearingScaleBar +import com.google.maps.android.compose.widgets.ScaleBar import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import timber.log.Timber @@ -207,7 +207,6 @@ fun MapView( val mapFilterState by mapViewModel.mapFilterStateFlow.collectAsStateWithLifecycle() val ourNodeInfo by uiViewModel.ourNodeInfo.collectAsStateWithLifecycle() var editingWaypoint by remember { mutableStateOf(null) } - val savedCameraPosition by mapViewModel.cameraPosition.collectAsStateWithLifecycle() val selectedGoogleMapType by mapViewModel.selectedGoogleMapType.collectAsStateWithLifecycle() val currentCustomTileProviderUrl by mapViewModel.selectedCustomTileProviderUrl.collectAsStateWithLifecycle() @@ -215,13 +214,7 @@ fun MapView( var mapTypeMenuExpanded by remember { mutableStateOf(false) } var showCustomTileManagerSheet by remember { mutableStateOf(false) } - val defaultLatLng = LatLng(0.0, 0.0) - val cameraPositionState = rememberCameraPositionState { - position = - savedCameraPosition?.let { - CameraPosition(LatLng(it.targetLat, it.targetLng), it.zoom, it.tilt, it.bearing) - } ?: CameraPosition.fromLatLngZoom(defaultLatLng, 7f) - } + val cameraPositionState = rememberCameraPositionState {} // Location tracking functionality val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) } @@ -339,7 +332,6 @@ fun MapView( } var showClusterItemsDialog by remember { mutableStateOf?>(null) } - LaunchedEffect(cameraPositionState.position) { mapViewModel.onCameraPositionChanged(cameraPositionState.position) } Scaffold { paddingValues -> Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) { @@ -351,7 +343,7 @@ fun MapView( MapUiSettings( zoomControlsEnabled = true, mapToolbarEnabled = true, - compassEnabled = true, + compassEnabled = false, myLocationButtonEnabled = false, // Disabled - we use custom location button rotationGesturesEnabled = true, scrollGesturesEnabled = true, @@ -370,32 +362,27 @@ fun MapView( } }, onMapLoaded = { - if ( - savedCameraPosition?.targetLat == defaultLatLng.latitude && - savedCameraPosition?.targetLng == defaultLatLng.longitude - ) { - val pointsToBound: List = - when { - !nodeTrack.isNullOrEmpty() -> nodeTrack.map { it.toLatLng() } + val pointsToBound: List = + when { + !nodeTrack.isNullOrEmpty() -> nodeTrack.map { it.toLatLng() } - allNodes.isNotEmpty() || displayableWaypoints.isNotEmpty() -> - allNodes.mapNotNull { it.toLatLng() } + displayableWaypoints.map { it.toLatLng() } + allNodes.isNotEmpty() || displayableWaypoints.isNotEmpty() -> + allNodes.mapNotNull { it.toLatLng() } + displayableWaypoints.map { it.toLatLng() } - else -> emptyList() - } - - if (pointsToBound.isNotEmpty()) { - val bounds = LatLngBounds.builder().apply { pointsToBound.forEach(::include) }.build() - - val padding = if (!pointsToBound.isEmpty()) 100 else 48 - - try { - coroutineScope.launch { - cameraPositionState.animate(CameraUpdateFactory.newLatLngBounds(bounds, padding)) - } - } catch (e: IllegalStateException) { - warn("MapView Could not animate to bounds: ${e.message}") + else -> emptyList() + } + + if (pointsToBound.isNotEmpty()) { + val bounds = LatLngBounds.builder().apply { pointsToBound.forEach(::include) }.build() + + val padding = if (!pointsToBound.isEmpty()) 100 else 48 + + try { + coroutineScope.launch { + cameraPositionState.animate(CameraUpdateFactory.newLatLngBounds(bounds, padding)) } + } catch (e: IllegalStateException) { + warn("MapView Could not animate to bounds: ${e.message}") } } }, @@ -537,12 +524,10 @@ fun MapView( } } - val currentCameraPosition = cameraPositionState.position - var displayedZoom by remember { mutableStateOf(currentCameraPosition.zoom) } - - if (displayedZoom != 0f) { - DisappearingScaleBar(cameraPositionState = cameraPositionState) - } + ScaleBar( + cameraPositionState = cameraPositionState, + modifier = Modifier.align(Alignment.BottomStart).padding(bottom = 48.dp), + ) editingWaypoint?.let { waypointToEdit -> EditWaypointDialog( waypoint = waypointToEdit, @@ -613,6 +598,7 @@ fun MapView( isLocationTrackingEnabled = !isLocationTrackingEnabled } }, + bearing = cameraPositionState.position.bearing, onOrientNorth = { coroutineScope.launch { try { diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt index 2c9b10358..a0beb072c 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt @@ -30,7 +30,6 @@ import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.repository.datastore.RadioConfigRepository import com.geeksville.mesh.repository.map.CustomTileProviderRepository import com.google.android.gms.maps.GoogleMap -import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.TileProvider import com.google.android.gms.maps.model.UrlTileProvider import com.google.maps.android.compose.MapType @@ -101,10 +100,6 @@ constructor( private val _selectedGoogleMapType = MutableStateFlow(MapType.NORMAL) val selectedGoogleMapType: StateFlow = _selectedGoogleMapType.asStateFlow() - private val _cameraPosition = MutableStateFlow(null) - - val cameraPosition: StateFlow = _cameraPosition.asStateFlow() - val displayUnits = radioConfigRepository.deviceProfileFlow .mapNotNull { it.config.display.units } @@ -114,17 +109,6 @@ constructor( initialValue = ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC, ) - fun onCameraPositionChanged(cameraPosition: CameraPosition) { - _cameraPosition.value = - MapCameraPosition( - targetLat = cameraPosition.target.latitude, - targetLng = cameraPosition.target.longitude, - zoom = cameraPosition.zoom, - tilt = cameraPosition.tilt, - bearing = cameraPosition.bearing, - ) - } - fun addCustomTileProvider(name: String, urlTemplate: String) { viewModelScope.launch { if (name.isBlank() || urlTemplate.isBlank() || !isValidTileUrlTemplate(urlTemplate)) { diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt index a35ff9b7d..6e9af80dc 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapButton.kt @@ -19,13 +19,25 @@ package com.geeksville.mesh.ui.map.components import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon +import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector @Composable -fun MapButton(icon: ImageVector, contentDescription: String, modifier: Modifier = Modifier, onClick: () -> Unit) { +fun MapButton( + modifier: Modifier = Modifier, + icon: ImageVector, + iconTint: Color? = null, + contentDescription: String, + onClick: () -> Unit, +) { FilledIconButton(onClick = onClick, modifier = modifier) { - Icon(imageVector = icon, contentDescription = contentDescription) + Icon( + imageVector = icon, + contentDescription = contentDescription, + tint = iconTint ?: IconButtonDefaults.filledIconButtonColors().contentColor, + ) } } diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt index 65b49b5d4..34ca2bf70 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/components/MapControlsOverlay.kt @@ -20,17 +20,20 @@ package com.geeksville.mesh.ui.map.components import androidx.compose.foundation.layout.Box import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.LocationDisabled -import androidx.compose.material.icons.outlined.Explore import androidx.compose.material.icons.outlined.Layers import androidx.compose.material.icons.outlined.Map import androidx.compose.material.icons.outlined.MyLocation +import androidx.compose.material.icons.outlined.Navigation import androidx.compose.material.icons.outlined.Tune import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.HorizontalFloatingToolbar +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.stringResource import com.geeksville.mesh.R +import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed import com.geeksville.mesh.ui.map.MapViewModel @OptIn(ExperimentalMaterial3ExpressiveApi::class) @@ -51,6 +54,7 @@ fun MapControlsOverlay( hasLocationPermission: Boolean = false, isLocationTrackingEnabled: Boolean = false, onToggleLocationTracking: () -> Unit = {}, + bearing: Float = 0f, onOrientNorth: () -> Unit = {}, ) { HorizontalFloatingToolbar( @@ -59,6 +63,7 @@ fun MapControlsOverlay( leadingContent = {}, trailingContent = {}, content = { + CompassButton(onOrientNorth = onOrientNorth, bearing = bearing) if (showFilterButton) { Box { MapButton( @@ -94,8 +99,6 @@ fun MapControlsOverlay( onClick = onManageLayersClicked, ) - CompassButton(onOrientNorth = onOrientNorth) - // Location tracking button if (hasLocationPermission) { MapButton( @@ -114,9 +117,13 @@ fun MapControlsOverlay( } @Composable -private fun CompassButton(onOrientNorth: () -> Unit) { +private fun CompassButton(onOrientNorth: () -> Unit, bearing: Float) { + val icon = Icons.Outlined.Navigation + MapButton( - icon = Icons.Outlined.Explore, + modifier = Modifier.rotate(-bearing), + icon = icon, + iconTint = MaterialTheme.colorScheme.StatusRed.takeIf { bearing == 0f }, contentDescription = stringResource(id = R.string.orient_north), onClick = onOrientNorth, )