mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-28 18:52:42 -04:00
refactor(map): fix scalebar and compass (#2973)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
@@ -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<Waypoint?>(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<List<NodeClusterItem>?>(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<LatLng> =
|
||||
when {
|
||||
!nodeTrack.isNullOrEmpty() -> nodeTrack.map { it.toLatLng() }
|
||||
val pointsToBound: List<LatLng> =
|
||||
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 {
|
||||
|
||||
@@ -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<MapType> = _selectedGoogleMapType.asStateFlow()
|
||||
|
||||
private val _cameraPosition = MutableStateFlow<MapCameraPosition?>(null)
|
||||
|
||||
val cameraPosition: StateFlow<MapCameraPosition?> = _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)) {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user