fix(map): location perms and button visibility, breadcrumb taps (#4651)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-26 09:39:15 -06:00 committed by GitHub
parent 27e7669366
commit b60d67297d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 160 additions and 85 deletions

View file

@ -18,6 +18,7 @@
package org.meshtastic.feature.map
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.graphics.Canvas
@ -62,6 +63,8 @@ import androidx.core.graphics.createBitmap
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
@ -133,7 +136,12 @@ private const val TRACEROUTE_OFFSET_METERS = 100.0
private const val TRACEROUTE_BOUNDS_PADDING_PX = 120
@Suppress("CyclomaticComplexMethod", "LongMethod")
@OptIn(MapsComposeExperimentalApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@OptIn(
MapsComposeExperimentalApi::class,
ExperimentalMaterial3Api::class,
ExperimentalMaterial3ExpressiveApi::class,
ExperimentalPermissionsApi::class,
)
@Composable
fun MapView(
mapViewModel: MapViewModel = hiltViewModel(),
@ -147,14 +155,24 @@ fun MapView(
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val mapLayers by mapViewModel.mapLayers.collectAsStateWithLifecycle()
var hasLocationPermission by remember { mutableStateOf(false) }
val displayUnits by mapViewModel.displayUnits.collectAsStateWithLifecycle()
// Location permissions state
val locationPermissionsState =
rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION))
var triggerLocationToggleAfterPermission by remember { mutableStateOf(false) }
// Location tracking state
var isLocationTrackingEnabled by remember { mutableStateOf(false) }
var followPhoneBearing by remember { mutableStateOf(false) }
LocationPermissionsHandler { isGranted -> hasLocationPermission = isGranted }
// Effect to toggle location tracking after permission is granted
LaunchedEffect(locationPermissionsState.allPermissionsGranted) {
if (locationPermissionsState.allPermissionsGranted && triggerLocationToggleAfterPermission) {
isLocationTrackingEnabled = true
triggerLocationToggleAfterPermission = false
}
}
val filePickerLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { result ->
@ -226,8 +244,8 @@ fun MapView(
}
// Start/stop location tracking based on state
LaunchedEffect(isLocationTrackingEnabled, hasLocationPermission) {
if (isLocationTrackingEnabled && hasLocationPermission) {
LaunchedEffect(isLocationTrackingEnabled, locationPermissionsState.allPermissionsGranted) {
if (isLocationTrackingEnabled && locationPermissionsState.allPermissionsGranted) {
val locationRequest =
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000L)
.setMinUpdateIntervalMillis(2000L)
@ -431,7 +449,11 @@ fun MapView(
zoomGesturesEnabled = true,
),
properties =
MapProperties(mapType = effectiveGoogleMapType, isMyLocationEnabled = hasLocationPermission),
MapProperties(
mapType = effectiveGoogleMapType,
isMyLocationEnabled =
isLocationTrackingEnabled && locationPermissionsState.allPermissionsGranted,
),
onMapLongClick = { latLng ->
if (isConnected) {
val newWaypoint =
@ -457,7 +479,7 @@ fun MapView(
jointType = JointType.ROUND,
color = TracerouteColors.OutgoingRoute,
width = 9f,
zIndex = 1.5f,
zIndex = 3.0f,
)
}
if (tracerouteReturnPoints.size >= 2) {
@ -466,7 +488,7 @@ fun MapView(
jointType = JointType.ROUND,
color = TracerouteColors.ReturnRoute,
width = 7f,
zIndex = 1.4f,
zIndex = 2.5f,
)
}
@ -482,26 +504,33 @@ fun MapView(
.find { it.num == focusedNodeNum }
?.let { focusedNode ->
sortedPositions.forEachIndexed { index, position ->
val markerState = rememberUpdatedMarkerState(position = position.toLatLng())
val alpha = (index.toFloat() / (sortedPositions.size.toFloat() - 1))
val color = Color(focusedNode.colors.second).copy(alpha = alpha)
if (index == sortedPositions.lastIndex) {
MarkerComposable(state = markerState, zIndex = 1f) { NodeChip(node = focusedNode) }
} else {
MarkerInfoWindowComposable(
state = markerState,
title = stringResource(Res.string.position),
snippet = formatAgo(position.time),
zIndex = alpha,
infoContent = {
PositionInfoWindowContent(position = position, displayUnits = displayUnits)
},
) {
Icon(
imageVector = androidx.compose.material.icons.Icons.Rounded.TripOrigin,
contentDescription = stringResource(Res.string.track_point),
tint = color,
)
key(position.time) {
val markerState = rememberUpdatedMarkerState(position = position.toLatLng())
val alpha = (index.toFloat() / (sortedPositions.size.toFloat() - 1))
val color = Color(focusedNode.colors.second).copy(alpha = alpha)
if (index == sortedPositions.lastIndex) {
MarkerComposable(state = markerState, zIndex = 4f) {
NodeChip(node = focusedNode)
}
} else {
MarkerInfoWindowComposable(
state = markerState,
title = stringResource(Res.string.position),
snippet = formatAgo(position.time),
zIndex = 1f + alpha,
infoContent = {
PositionInfoWindowContent(
position = position,
displayUnits = displayUnits,
)
},
) {
Icon(
imageVector = androidx.compose.material.icons.Icons.Rounded.TripOrigin,
contentDescription = stringResource(Res.string.track_point),
tint = color,
)
}
}
}
}
@ -515,6 +544,7 @@ fun MapView(
jointType = JointType.ROUND,
color = Color(focusedNode.colors.second).copy(alpha = alpha),
width = 8f,
zIndex = 0.6f,
)
}
}
@ -550,25 +580,6 @@ fun MapView(
)
}
if (tracerouteForwardPoints.size >= 2) {
Polyline(
points = tracerouteForwardOffsetPoints,
jointType = JointType.ROUND,
color = TracerouteColors.OutgoingRoute,
width = 9f,
zIndex = 2f,
)
}
if (tracerouteReturnPoints.size >= 2) {
Polyline(
points = tracerouteReturnOffsetPoints,
jointType = JointType.ROUND,
color = TracerouteColors.ReturnRoute,
width = 7f,
zIndex = 1.5f,
)
}
WaypointMarkers(
displayableWaypoints = displayableWaypoints,
mapFilterState = mapFilterState,
@ -655,14 +666,16 @@ fun MapView(
showCustomTileManagerSheet = true
},
isNodeMap = focusedNodeNum != null,
hasLocationPermission = hasLocationPermission,
isLocationTrackingEnabled = isLocationTrackingEnabled,
onToggleLocationTracking = {
if (hasLocationPermission) {
if (locationPermissionsState.allPermissionsGranted) {
isLocationTrackingEnabled = !isLocationTrackingEnabled
if (!isLocationTrackingEnabled) {
followPhoneBearing = false
}
} else {
triggerLocationToggleAfterPermission = true
locationPermissionsState.launchMultiplePermissionRequest()
}
},
bearing = cameraPositionState.position.bearing,

View file

@ -56,7 +56,6 @@ fun MapControlsOverlay(
onManageCustomTileProvidersClicked: () -> Unit, // New parameter
isNodeMap: Boolean,
// Location tracking parameters
hasLocationPermission: Boolean = false,
isLocationTrackingEnabled: Boolean = false,
onToggleLocationTracking: () -> Unit = {},
bearing: Float = 0f,
@ -117,18 +116,16 @@ fun MapControlsOverlay(
)
// Location tracking button
if (hasLocationPermission) {
MapButton(
icon =
if (isLocationTrackingEnabled) {
Icons.Rounded.LocationDisabled
} else {
Icons.Outlined.MyLocation
},
contentDescription = stringResource(Res.string.toggle_my_position),
onClick = onToggleLocationTracking,
)
}
MapButton(
icon =
if (isLocationTrackingEnabled) {
Icons.Rounded.LocationDisabled
} else {
Icons.Outlined.MyLocation
},
contentDescription = stringResource(Res.string.toggle_my_position),
onClick = onToggleLocationTracking,
)
},
)
}