mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Traceroute map position snapshots (#4035)
Signed-off-by: Jord <650645+DivineOmega@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
03fd2bf9ba
commit
9833795864
17 changed files with 1195 additions and 44 deletions
|
|
@ -43,6 +43,7 @@ import org.meshtastic.core.data.repository.FirmwareReleaseRepository
|
|||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.data.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
|
|
@ -87,6 +88,7 @@ constructor(
|
|||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val tracerouteSnapshotRepository: TracerouteSnapshotRepository,
|
||||
private val deviceHardwareRepository: DeviceHardwareRepository,
|
||||
private val firmwareReleaseRepository: FirmwareReleaseRepository,
|
||||
) : ViewModel() {
|
||||
|
|
@ -146,6 +148,8 @@ constructor(
|
|||
return overlay
|
||||
}
|
||||
|
||||
fun tracerouteSnapshotPositions(logUuid: String) = tracerouteSnapshotRepository.getSnapshotPositions(logUuid)
|
||||
|
||||
fun clearTracerouteResponse() = serviceRepository.clearTracerouteResponse()
|
||||
|
||||
fun tracerouteMapAvailability(forwardRoute: List<Int>, returnRoute: List<Int>): TracerouteMapAvailability =
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.pluralStringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.evaluateTracerouteMapAvailability
|
||||
import org.meshtastic.core.model.fullRouteDiscovery
|
||||
import org.meshtastic.core.model.getTracerouteResponse
|
||||
import org.meshtastic.core.model.toMessageRes
|
||||
|
|
@ -89,7 +90,12 @@ import org.meshtastic.feature.node.metrics.CommonCharts.MS_PER_SEC
|
|||
import org.meshtastic.proto.MeshProtos
|
||||
import java.text.DateFormat
|
||||
|
||||
private data class TracerouteDialog(val message: AnnotatedString, val requestId: Int, val overlay: TracerouteOverlay?)
|
||||
private data class TracerouteDialog(
|
||||
val message: AnnotatedString,
|
||||
val requestId: Int,
|
||||
val responseLogUuid: String,
|
||||
val overlay: TracerouteOverlay?,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -98,7 +104,7 @@ fun TracerouteLogScreen(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: MetricsViewModel = hiltViewModel(),
|
||||
onNavigateUp: () -> Unit,
|
||||
onViewOnMap: (requestId: Int) -> Unit = {},
|
||||
onViewOnMap: (requestId: Int, responseLogUuid: String) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val dateFormat = remember { DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM) }
|
||||
|
|
@ -185,10 +191,12 @@ fun TracerouteLogScreen(
|
|||
AnnotatedString(it)
|
||||
}
|
||||
dialogMessage?.let {
|
||||
val responseLogUuid = result?.uuid ?: return@combinedClickable
|
||||
showDialog =
|
||||
TracerouteDialog(
|
||||
message = it,
|
||||
requestId = log.fromRadio.packet.id,
|
||||
responseLogUuid = responseLogUuid,
|
||||
overlay = overlay,
|
||||
)
|
||||
}
|
||||
|
|
@ -211,23 +219,34 @@ private fun TracerouteLogDialogs(
|
|||
dialog: TracerouteDialog?,
|
||||
errorMessageRes: StringResource?,
|
||||
viewModel: MetricsViewModel,
|
||||
onViewOnMap: (requestId: Int) -> Unit,
|
||||
onViewOnMap: (requestId: Int, responseLogUuid: String) -> Unit,
|
||||
onShowErrorMessageRes: (StringResource) -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
onDismissError: () -> Unit,
|
||||
) {
|
||||
dialog?.let { dialogState ->
|
||||
val snapshotPositionsFlow =
|
||||
remember(dialogState.responseLogUuid) { viewModel.tracerouteSnapshotPositions(dialogState.responseLogUuid) }
|
||||
val snapshotPositions by snapshotPositionsFlow.collectAsStateWithLifecycle(emptyMap<Int, MeshProtos.Position>())
|
||||
SimpleAlertDialog(
|
||||
title = Res.string.traceroute,
|
||||
text = { SelectionContainer { Text(text = dialogState.message) } },
|
||||
confirmText = stringResource(Res.string.view_on_map),
|
||||
onConfirm = {
|
||||
val positionedNodeNums =
|
||||
if (snapshotPositions.isNotEmpty()) {
|
||||
snapshotPositions.keys
|
||||
} else {
|
||||
viewModel.positionedNodeNums()
|
||||
}
|
||||
val availability =
|
||||
viewModel.tracerouteMapAvailability(
|
||||
evaluateTracerouteMapAvailability(
|
||||
forwardRoute = dialogState.overlay?.forwardRoute.orEmpty(),
|
||||
returnRoute = dialogState.overlay?.returnRoute.orEmpty(),
|
||||
positionedNodeNums = positionedNodeNums,
|
||||
)
|
||||
availability.toMessageRes()?.let(onShowErrorMessageRes) ?: onViewOnMap(dialogState.requestId)
|
||||
availability.toMessageRes()?.let(onShowErrorMessageRes)
|
||||
?: onViewOnMap(dialogState.requestId, dialogState.responseLogUuid)
|
||||
onDismissDialog()
|
||||
},
|
||||
onDismiss = onDismissDialog,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.fullRouteDiscovery
|
||||
import org.meshtastic.core.strings.Res
|
||||
|
|
@ -54,37 +55,59 @@ import org.meshtastic.core.ui.component.MainAppBar
|
|||
import org.meshtastic.core.ui.theme.TracerouteColors
|
||||
import org.meshtastic.feature.map.MapView
|
||||
import org.meshtastic.feature.map.model.TracerouteOverlay
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
|
||||
@Composable
|
||||
fun TracerouteMapScreen(
|
||||
metricsViewModel: MetricsViewModel = hiltViewModel(),
|
||||
requestId: Int,
|
||||
logUuid: String? = null,
|
||||
onNavigateUp: () -> Unit,
|
||||
) {
|
||||
val state by metricsViewModel.state.collectAsStateWithLifecycle()
|
||||
val nodeTitle = state.node?.user?.longName ?: stringResource(Res.string.traceroute)
|
||||
val routeDiscovery =
|
||||
state.tracerouteResults
|
||||
.find { it.fromRadio.packet.decoded.requestId == requestId }
|
||||
?.fromRadio
|
||||
?.packet
|
||||
?.fullRouteDiscovery
|
||||
val snapshotPositions by
|
||||
remember(logUuid) {
|
||||
logUuid?.let(metricsViewModel::tracerouteSnapshotPositions)
|
||||
?: flowOf(emptyMap<Int, MeshProtos.Position>())
|
||||
}
|
||||
.collectAsStateWithLifecycle(emptyMap<Int, MeshProtos.Position>())
|
||||
val tracerouteResult =
|
||||
if (logUuid != null) {
|
||||
state.tracerouteResults.find { it.uuid == logUuid }
|
||||
} else {
|
||||
state.tracerouteResults.find { it.fromRadio.packet.decoded.requestId == requestId }
|
||||
}
|
||||
val routeDiscovery = tracerouteResult?.fromRadio?.packet?.fullRouteDiscovery
|
||||
val overlayFromLogs =
|
||||
remember(routeDiscovery) {
|
||||
routeDiscovery?.let {
|
||||
TracerouteOverlay(requestId = requestId, forwardRoute = it.routeList, returnRoute = it.routeBackList)
|
||||
}
|
||||
remember(routeDiscovery, requestId) {
|
||||
routeDiscovery?.let { TracerouteOverlay(requestId, it.routeList, it.routeBackList) }
|
||||
}
|
||||
val overlayFromService = remember(requestId) { metricsViewModel.getTracerouteOverlay(requestId) }
|
||||
val overlay = overlayFromLogs ?: overlayFromService
|
||||
var tracerouteNodesShown by remember { mutableStateOf(0) }
|
||||
var tracerouteNodesTotal by remember { mutableStateOf(0) }
|
||||
LaunchedEffect(Unit) { metricsViewModel.clearTracerouteResponse() }
|
||||
|
||||
TracerouteMapScaffold(
|
||||
title = state.node?.user?.longName ?: stringResource(Res.string.traceroute),
|
||||
overlay = overlay,
|
||||
snapshotPositions = snapshotPositions,
|
||||
onNavigateUp = onNavigateUp,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TracerouteMapScaffold(
|
||||
title: String,
|
||||
overlay: TracerouteOverlay?,
|
||||
snapshotPositions: Map<Int, MeshProtos.Position>,
|
||||
onNavigateUp: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var tracerouteNodesShown by remember { mutableStateOf(0) }
|
||||
var tracerouteNodesTotal by remember { mutableStateOf(0) }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = nodeTitle,
|
||||
title = title,
|
||||
ourNode = null,
|
||||
showNodeChip = false,
|
||||
canNavigateUp = true,
|
||||
|
|
@ -94,10 +117,11 @@ fun TracerouteMapScreen(
|
|||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
|
||||
Box(modifier = modifier.fillMaxSize().padding(paddingValues)) {
|
||||
MapView(
|
||||
navigateToNodeDetails = {},
|
||||
tracerouteOverlay = overlay,
|
||||
tracerouteNodePositions = snapshotPositions,
|
||||
onTracerouteMappableCountChanged = { shown, total ->
|
||||
tracerouteNodesShown = shown
|
||||
tracerouteNodesTotal = total
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue