feat: node map position history log (#1384)

pull/1388/head
Andre K 2024-11-06 11:00:38 -03:00 zatwierdzone przez GitHub
rodzic a8c810bae2
commit 227c65f191
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
6 zmienionych plików z 228 dodań i 44 usunięć

Wyświetl plik

@ -61,6 +61,7 @@ import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.model.RadioConfigViewModel
import com.geeksville.mesh.ui.components.DeviceMetricsScreen
import com.geeksville.mesh.ui.components.EnvironmentMetricsScreen
import com.geeksville.mesh.ui.components.NodeMapScreen
import com.geeksville.mesh.ui.components.PositionLogScreen
import com.geeksville.mesh.ui.components.SignalMetricsScreen
import com.geeksville.mesh.ui.components.TracerouteLogScreen
@ -251,6 +252,10 @@ fun NavGraph(
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") }
DeviceMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry))
}
composable("NodeMap") {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") }
NodeMapScreen(hiltViewModel<MetricsViewModel>(parentEntry))
}
composable("PositionLog") {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") }
PositionLogScreen(hiltViewModel<MetricsViewModel>(parentEntry))

Wyświetl plik

@ -34,6 +34,7 @@ import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.KeyOff
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Map
import androidx.compose.material.icons.filled.Numbers
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Power
@ -100,7 +101,6 @@ fun NodeDetailScreen(
}
}
@Suppress("LongMethod")
@Composable
private fun NodeDetailList(
node: NodeEntity,
@ -135,50 +135,20 @@ private fun NodeDetailList(
}
item {
NavCard(
title = stringResource(R.string.device_metrics_log),
icon = Icons.Default.ChargingStation,
enabled = metricsState.hasDeviceMetrics()
) {
onNavigate("DeviceMetrics")
}
PreferenceCategory(stringResource(id = R.string.logs))
LogNavigationList(metricsState, onNavigate)
}
NavCard(
title = stringResource(R.string.position_log),
icon = Icons.Default.LocationOn,
enabled = metricsState.hasPositionLogs()
) { onNavigate("PositionLog") }
NavCard(
title = stringResource(R.string.env_metrics_log),
icon = Icons.Default.Thermostat,
enabled = metricsState.hasEnvironmentMetrics()
) {
onNavigate("EnvironmentMetrics")
}
NavCard(
title = stringResource(R.string.sig_metrics_log),
icon = Icons.Default.SignalCellularAlt,
enabled = metricsState.hasSignalMetrics()
) {
onNavigate("SignalMetrics")
}
NavCard(
title = stringResource(R.string.traceroute_log),
icon = Icons.Default.Route,
enabled = metricsState.hasTracerouteLogs()
) {
onNavigate("TracerouteList")
}
NavCard(
title = "Remote Administration",
icon = Icons.Default.Settings,
enabled = !metricsState.isManaged
) {
onNavigate("RadioConfig")
if (!metricsState.isManaged) {
item {
PreferenceCategory(stringResource(id = R.string.administration))
NavCard(
title = stringResource(id = R.string.remote_admin),
icon = Icons.Default.Settings,
enabled = true
) {
onNavigate("RadioConfig")
}
}
}
}
@ -260,6 +230,57 @@ private fun NodeDetailsContent(node: NodeEntity) {
)
}
@Composable
fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) {
NavCard(
title = stringResource(R.string.device_metrics_log),
icon = Icons.Default.ChargingStation,
enabled = state.hasDeviceMetrics()
) {
onNavigate("DeviceMetrics")
}
NavCard(
title = stringResource(R.string.node_map),
icon = Icons.Default.Map,
enabled = state.hasPositionLogs()
) {
onNavigate("NodeMap")
}
NavCard(
title = stringResource(R.string.position_log),
icon = Icons.Default.LocationOn,
enabled = state.hasPositionLogs()
) {
onNavigate("PositionLog")
}
NavCard(
title = stringResource(R.string.env_metrics_log),
icon = Icons.Default.Thermostat,
enabled = state.hasEnvironmentMetrics()
) {
onNavigate("EnvironmentMetrics")
}
NavCard(
title = stringResource(R.string.sig_metrics_log),
icon = Icons.Default.SignalCellularAlt,
enabled = state.hasSignalMetrics()
) {
onNavigate("SignalMetrics")
}
NavCard(
title = stringResource(R.string.traceroute_log),
icon = Icons.Default.Route,
enabled = state.hasTracerouteLogs()
) {
onNavigate("TracerouteList")
}
}
@Composable
private fun InfoCard(
icon: ImageVector,

Wyświetl plik

@ -0,0 +1,84 @@
package com.geeksville.mesh.ui.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.ui.map.rememberMapViewWithLifecycle
import com.geeksville.mesh.util.addCopyright
import com.geeksville.mesh.util.addPositionMarkers
import com.geeksville.mesh.util.addPolyline
import com.geeksville.mesh.util.addScaleBarOverlay
import com.geeksville.mesh.util.requiredZoomLevel
import org.osmdroid.config.Configuration
import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
private const val DegD = 1e-7
@Composable
fun NodeMapScreen(
viewModel: MetricsViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val density = LocalDensity.current
val mapView = rememberMapViewWithLifecycle(context)
val state by viewModel.state.collectAsStateWithLifecycle()
val geoPoints = state.positionLogs.map { GeoPoint(it.latitudeI * DegD, it.longitudeI * DegD) }
var savedCenter by rememberSaveable(stateSaver = Saver(
save = { mapOf("latitude" to it.latitude, "longitude" to it.longitude) },
restore = { GeoPoint(it["latitude"] ?: 0.0, it["longitude"] ?: .0) }
)) {
val box = BoundingBox.fromGeoPoints(geoPoints)
mutableStateOf(GeoPoint(box.centerLatitude, box.centerLongitude))
}
var savedZoom by rememberSaveable {
val box = BoundingBox.fromGeoPoints(geoPoints)
mutableDoubleStateOf(box.requiredZoomLevel())
}
LifecycleStartEffect(true) {
onStopOrDispose {
savedCenter = mapView.projection.currentCenter
savedZoom = mapView.zoomLevelDouble
}
}
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = {
mapView.apply {
Configuration.getInstance().userAgentValue = BuildConfig.APPLICATION_ID
setMultiTouchControls(true)
isTilesScaledToDpi = true
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
controller.setCenter(savedCenter)
controller.setZoom(savedZoom)
}
},
update = { map ->
map.overlays.clear()
map.addCopyright()
map.addScaleBarOverlay(density)
map.addPolyline(density, geoPoints) {}
map.addPositionMarkers(state.positionLogs) {}
}
)
}

Wyświetl plik

@ -1,18 +1,26 @@
package com.geeksville.mesh.util
import android.graphics.Color
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.graphics.Typeface
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R
import org.osmdroid.events.DelayedMapListener
import org.osmdroid.events.MapListener
import org.osmdroid.events.ScrollEvent
import org.osmdroid.events.ZoomEvent
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.CopyrightOverlay
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.Polyline
import org.osmdroid.views.overlay.ScaleBarOverlay
import org.osmdroid.views.overlay.advancedpolyline.MonochromaticPaintList
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
/**
@ -81,3 +89,62 @@ fun MapView.addMapEventListener(onEvent: () -> Unit) {
}
}, INACTIVITY_DELAY_MILLIS))
}
fun MapView.addPolyline(
density: Density,
geoPoints: List<GeoPoint>,
onClick: () -> Unit
): Polyline {
val polyline = Polyline(this).apply {
val borderPaint = Paint().apply {
color = Color.BLACK
isAntiAlias = true
strokeWidth = with(density) { 10.dp.toPx() }
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
pathEffect = DashPathEffect(floatArrayOf(80f, 60f), 0f)
}
outlinePaintLists.add(MonochromaticPaintList(borderPaint))
val fillPaint = Paint().apply {
color = Color.WHITE
isAntiAlias = true
strokeWidth = with(density) { 6.dp.toPx() }
style = Paint.Style.FILL_AND_STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
pathEffect = DashPathEffect(floatArrayOf(80f, 60f), 0f)
}
outlinePaintLists.add(MonochromaticPaintList(fillPaint))
setPoints(geoPoints)
setOnClickListener { _, _, _ ->
onClick()
true
}
}
overlays.add(polyline)
return polyline
}
fun MapView.addPositionMarkers(
positions: List<MeshProtos.Position>,
onClick: () -> Unit
): List<Marker> {
val navIcon = ContextCompat.getDrawable(context, R.drawable.ic_map_navigation_24)
val markers = positions.map {
Marker(this).apply {
icon = navIcon
rotation = (it.groundTrack * 1e-5).toFloat()
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
position = GeoPoint(it.latitudeI * 1e-7, it.longitudeI * 1e-7)
setOnMarkerClickListener { _, _ ->
onClick()
true
}
}
}
overlays.addAll(markers)
return markers
}

Wyświetl plik

@ -280,9 +280,12 @@
<string name="rssi_definition">Received Signal Strength Indicator, a measurement used to determine the power level being received by the antenna. A higher RSSI value generally indicates a stronger and more stable connection.</string>
<string name="iaq_definition">(Indoor Air Quality) relative scale IAQ value as measured by Bosch BME680. Value Range 0–500.</string>
<string name="device_metrics_log">Device Metrics Log</string>
<string name="node_map">Node Map</string>
<string name="position_log">Position Log</string>
<string name="env_metrics_log">Environment Metrics Log</string>
<string name="sig_metrics_log">Signal Metrics Log</string>
<string name="administration">Administration</string>
<string name="remote_admin">Remote Administration</string>
<string name="bad">Bad</string>
<string name="fair">Fair</string>
<string name="good">Good</string>

Wyświetl plik

@ -269,8 +269,12 @@
<ID>MagicNumber:MapFragment.kt$12F</ID>
<ID>MagicNumber:MapFragment.kt$1e-7</ID>
<ID>MagicNumber:MapFragment.kt$&lt;no name provided&gt;$1e7</ID>
<ID>MagicNumber:MapViewExtensions.kt$1e-5</ID>
<ID>MagicNumber:MapViewExtensions.kt$1e-7</ID>
<ID>MagicNumber:MapViewExtensions.kt$3.0f</ID>
<ID>MagicNumber:MapViewExtensions.kt$40f</ID>
<ID>MagicNumber:MapViewExtensions.kt$60f</ID>
<ID>MagicNumber:MapViewExtensions.kt$80f</ID>
<ID>MagicNumber:MarkerWithLabel.kt$MarkerWithLabel$3</ID>
<ID>MagicNumber:MeshService.kt$MeshService$0xffffffff</ID>
<ID>MagicNumber:MeshService.kt$MeshService$100</ID>