feat(map): allow map to follow phone bearing (#3002)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
pull/3005/head
James Rich 2025-09-06 13:24:06 -05:00 zatwierdzone przez GitHub
rodzic 93e7eb3aa0
commit 91ce6c5b93
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
2 zmienionych plików z 45 dodań i 43 usunięć

Wyświetl plik

@ -190,6 +190,7 @@ fun MapView(
// Location tracking state
var isLocationTrackingEnabled by remember { mutableStateOf(false) }
var followPhoneBearing by remember { mutableStateOf(false) }
LocationPermissionsHandler { isGranted -> hasLocationPermission = isGranted }
@ -224,11 +225,27 @@ fun MapView(
if (isLocationTrackingEnabled) {
locationResult.lastLocation?.let { location ->
val latLng = LatLng(location.latitude, location.longitude)
val cameraUpdate =
if (followPhoneBearing) {
val bearing =
if (location.hasBearing()) {
location.bearing
} else {
cameraPositionState.position.bearing
}
CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder()
.target(latLng)
.zoom(cameraPositionState.position.zoom)
.bearing(bearing)
.build(),
)
} else {
CameraUpdateFactory.newLatLngZoom(latLng, cameraPositionState.position.zoom)
}
coroutineScope.launch {
try {
cameraPositionState.animate(
CameraUpdateFactory.newLatLngZoom(latLng, cameraPositionState.position.zoom),
)
cameraPositionState.animate(cameraUpdate)
} catch (e: IllegalStateException) {
debug("Error animating camera to location: ${e.message}")
}
@ -260,7 +277,6 @@ fun MapView(
}
}
// Clean up location tracking on disposal
DisposableEffect(Unit) { onDispose { fusedLocationClient.removeLocationUpdates(locationCallback) } }
val allNodes by
@ -344,7 +360,7 @@ fun MapView(
zoomControlsEnabled = true,
mapToolbarEnabled = true,
compassEnabled = false,
myLocationButtonEnabled = false, // Disabled - we use custom location button
myLocationButtonEnabled = false,
rotationGesturesEnabled = true,
scrollGesturesEnabled = true,
tiltGesturesEnabled = true,
@ -574,46 +590,30 @@ fun MapView(
isLocationTrackingEnabled = isLocationTrackingEnabled,
onToggleLocationTracking = {
if (hasLocationPermission) {
if (!isLocationTrackingEnabled) {
// When enabling tracking, get current location and center on it
try {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
location?.let {
val latLng = LatLng(it.latitude, it.longitude)
coroutineScope.launch {
try {
cameraPositionState.animate(
CameraUpdateFactory.newLatLngZoom(latLng, 16f),
)
} catch (e: IllegalStateException) {
debug("Error centering camera on location: ${e.message}")
}
}
}
}
} catch (e: SecurityException) {
debug("Location permission not available: ${e.message}")
}
}
isLocationTrackingEnabled = !isLocationTrackingEnabled
if (!isLocationTrackingEnabled) {
followPhoneBearing = false
}
}
},
bearing = cameraPositionState.position.bearing,
onOrientNorth = {
coroutineScope.launch {
try {
val currentPosition = cameraPositionState.position
val newCameraPosition =
CameraPosition.Builder(currentPosition)
.bearing(0f) // Orient to north
.build()
cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(newCameraPosition))
debug("Oriented map to north")
} catch (e: IllegalStateException) {
debug("Error orienting map to north: ${e.message}")
onCompassClick = {
if (isLocationTrackingEnabled) {
followPhoneBearing = !followPhoneBearing
} else {
coroutineScope.launch {
try {
val currentPosition = cameraPositionState.position
val newCameraPosition = CameraPosition.Builder(currentPosition).bearing(0f).build()
cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(newCameraPosition))
debug("Oriented map to north")
} catch (e: IllegalStateException) {
debug("Error orienting map to north: ${e.message}")
}
}
}
},
followPhoneBearing = followPhoneBearing,
)
}
if (showLayersBottomSheet) {

Wyświetl plik

@ -20,6 +20,7 @@ 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.filled.Navigation
import androidx.compose.material.icons.outlined.Layers
import androidx.compose.material.icons.outlined.Map
import androidx.compose.material.icons.outlined.MyLocation
@ -55,7 +56,8 @@ fun MapControlsOverlay(
isLocationTrackingEnabled: Boolean = false,
onToggleLocationTracking: () -> Unit = {},
bearing: Float = 0f,
onOrientNorth: () -> Unit = {},
onCompassClick: () -> Unit = {},
followPhoneBearing: Boolean,
) {
HorizontalFloatingToolbar(
modifier = modifier,
@ -63,7 +65,7 @@ fun MapControlsOverlay(
leadingContent = {},
trailingContent = {},
content = {
CompassButton(onOrientNorth = onOrientNorth, bearing = bearing)
CompassButton(onClick = onCompassClick, bearing = bearing, isFollowing = followPhoneBearing)
if (showFilterButton) {
Box {
MapButton(
@ -117,14 +119,14 @@ fun MapControlsOverlay(
}
@Composable
private fun CompassButton(onOrientNorth: () -> Unit, bearing: Float) {
val icon = Icons.Outlined.Navigation
private fun CompassButton(onClick: () -> Unit, bearing: Float, isFollowing: Boolean) {
val icon = if (isFollowing) Icons.Filled.Navigation else Icons.Outlined.Navigation
MapButton(
modifier = Modifier.rotate(-bearing),
icon = icon,
iconTint = MaterialTheme.colorScheme.StatusRed.takeIf { bearing == 0f },
contentDescription = stringResource(id = R.string.orient_north),
onClick = onOrientNorth,
onClick = onClick,
)
}