mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(map): material3 controls + latitude-aware precision circles
- Add DisappearingScaleBar overlay (bottom-start) that auto-shows on zoom change and hides after 3 seconds, using CameraState.metersPerDpAtTarget - Add ExpandingAttributionButton overlay (bottom-end) for tile provider attribution display (legal compliance), auto-dismisses on map gesture - Thread StyleState from MapScreen → MaplibreMapContent → MaplibreMap to provide source attribution data for the attribution button - Use LocationPuckDefaults.colors() for Material 3 themed location puck (derives colors from MaterialTheme.colorScheme instead of hardcoded blue) - Replace hardcoded METERS_PER_PIXEL_ZOOM15 equatorial constant in InlineMap with CameraState.metersPerDpAtTarget for latitude-aware precision circles
This commit is contained in:
parent
2fd93bc67f
commit
22d46a50ef
3 changed files with 36 additions and 7 deletions
|
|
@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -40,6 +41,9 @@ import org.maplibre.compose.location.LocationTrackingEffect
|
|||
import org.maplibre.compose.location.rememberNullLocationProvider
|
||||
import org.maplibre.compose.location.rememberUserLocationState
|
||||
import org.maplibre.compose.map.GestureOptions
|
||||
import org.maplibre.compose.material3.DisappearingScaleBar
|
||||
import org.maplibre.compose.material3.ExpandingAttributionButton
|
||||
import org.maplibre.compose.style.rememberStyleState
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.map
|
||||
import org.meshtastic.core.ui.component.MainAppBar
|
||||
|
|
@ -53,6 +57,7 @@ import org.meshtastic.feature.map.util.toGeoPositionOrNull
|
|||
import org.maplibre.spatialk.geojson.Position as GeoPosition
|
||||
|
||||
private const val WAYPOINT_ZOOM = 15.0
|
||||
private val MAP_OVERLAY_PADDING = 16.dp
|
||||
|
||||
/**
|
||||
* Main map screen composable. Uses MapLibre Compose Multiplatform to render an interactive map with mesh node markers,
|
||||
|
|
@ -81,6 +86,7 @@ fun MapScreen(
|
|||
LaunchedEffect(waypointId) { viewModel.setWaypointId(waypointId) }
|
||||
|
||||
val cameraState = rememberCameraState(firstPosition = viewModel.initialCameraPosition)
|
||||
val styleState = rememberStyleState()
|
||||
|
||||
var filterMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -155,6 +161,7 @@ fun MapScreen(
|
|||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
gestureOptions = gestureOptions,
|
||||
styleState = styleState,
|
||||
onCameraMoved = { position -> viewModel.saveCameraPosition(position) },
|
||||
onWaypointClick = { wpId ->
|
||||
editingWaypointId = wpId
|
||||
|
|
@ -228,6 +235,20 @@ fun MapScreen(
|
|||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Scale bar — auto-shows on zoom change, hides after 3 seconds
|
||||
DisappearingScaleBar(
|
||||
metersPerDp = cameraState.metersPerDpAtTarget,
|
||||
zoom = cameraState.position.zoom,
|
||||
modifier = Modifier.align(Alignment.BottomStart).padding(MAP_OVERLAY_PADDING),
|
||||
)
|
||||
|
||||
// Attribution button — shows tile provider attributions (legal compliance)
|
||||
ExpandingAttributionButton(
|
||||
cameraState = cameraState,
|
||||
styleState = styleState,
|
||||
modifier = Modifier.align(Alignment.BottomEnd).padding(MAP_OVERLAY_PADDING),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,6 @@ private const val DEFAULT_ZOOM = 15.0
|
|||
private const val PRECISION_CIRCLE_FILL_ALPHA = 0.15f
|
||||
private const val PRECISION_CIRCLE_STROKE_ALPHA = 0.3f
|
||||
|
||||
/** Ground resolution at zoom 15 (equatorial): ~4.773 meters per pixel. */
|
||||
private const val METERS_PER_PIXEL_ZOOM15 = 4.773
|
||||
|
||||
/**
|
||||
* A compact, non-interactive map showing a single node's position. Used in node detail screens. Replaces both the
|
||||
* Google Maps and OSMDroid inline map implementations.
|
||||
|
|
@ -83,10 +80,11 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
|||
strokeColor = const(Color.White),
|
||||
)
|
||||
|
||||
// Precision circle — radius computed from precision_meters at zoom 15
|
||||
// Precision circle — radius computed from precision_meters using latitude-aware metersPerDp
|
||||
val precisionMeters = precisionBitsToMeters(position.precision_bits ?: 0)
|
||||
if (precisionMeters > 0) {
|
||||
val radiusDp = (precisionMeters / METERS_PER_PIXEL_ZOOM15).dp
|
||||
val metersPerDp = cameraState.metersPerDpAtTarget
|
||||
if (precisionMeters > 0 && metersPerDp > 0) {
|
||||
val radiusDp = (precisionMeters / metersPerDp).dp
|
||||
CircleLayer(
|
||||
id = "inline-node-precision",
|
||||
source = source,
|
||||
|
|
|
|||
|
|
@ -48,12 +48,15 @@ import org.maplibre.compose.map.GestureOptions
|
|||
import org.maplibre.compose.map.MapOptions
|
||||
import org.maplibre.compose.map.MaplibreMap
|
||||
import org.maplibre.compose.map.OrnamentOptions
|
||||
import org.maplibre.compose.material3.LocationPuckDefaults
|
||||
import org.maplibre.compose.sources.GeoJsonData
|
||||
import org.maplibre.compose.sources.GeoJsonOptions
|
||||
import org.maplibre.compose.sources.RasterDemEncoding
|
||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||
import org.maplibre.compose.sources.rememberRasterDemSource
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
import org.maplibre.compose.style.StyleState
|
||||
import org.maplibre.compose.style.rememberStyleState
|
||||
import org.maplibre.compose.util.ClickResult
|
||||
import org.maplibre.spatialk.geojson.Point
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
|
@ -108,6 +111,7 @@ fun MaplibreMapContent(
|
|||
onMapLongClick: (GeoPosition) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
gestureOptions: GestureOptions = GestureOptions.Standard,
|
||||
styleState: StyleState = rememberStyleState(),
|
||||
onCameraMoved: (CameraPosition) -> Unit = {},
|
||||
onWaypointClick: (Int) -> Unit = {},
|
||||
onMapLoadFinished: () -> Unit = {},
|
||||
|
|
@ -118,6 +122,7 @@ fun MaplibreMapContent(
|
|||
modifier = modifier,
|
||||
baseStyle = baseStyle,
|
||||
cameraState = cameraState,
|
||||
styleState = styleState,
|
||||
options = MapOptions(gestureOptions = gestureOptions, ornamentOptions = OrnamentOptions.OnlyLogo),
|
||||
onMapLongClick = { position, _ ->
|
||||
onMapLongClick(position)
|
||||
|
|
@ -148,7 +153,12 @@ fun MaplibreMapContent(
|
|||
|
||||
// --- User location puck ---
|
||||
if (locationState != null) {
|
||||
LocationPuck(idPrefix = "user-location", locationState = locationState, cameraState = cameraState)
|
||||
LocationPuck(
|
||||
idPrefix = "user-location",
|
||||
locationState = locationState,
|
||||
cameraState = cameraState,
|
||||
colors = LocationPuckDefaults.colors(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue