mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Traceroute map visualisation (#4002)
This commit is contained in:
parent
24f40b2005
commit
3dbc5108c2
18 changed files with 917 additions and 60 deletions
|
|
@ -52,10 +52,13 @@ import org.meshtastic.core.data.repository.PacketRepository
|
|||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.TracerouteMapAvailability
|
||||
import org.meshtastic.core.model.evaluateTracerouteMapAvailability
|
||||
import org.meshtastic.core.model.util.toChannelSet
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.service.TracerouteResponse
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.client_notification
|
||||
import org.meshtastic.core.ui.component.ScrollToTopEvent
|
||||
|
|
@ -152,6 +155,14 @@ constructor(
|
|||
private val _currentAlert: MutableStateFlow<AlertData?> = MutableStateFlow(null)
|
||||
val currentAlert = _currentAlert.asStateFlow()
|
||||
|
||||
fun tracerouteMapAvailability(forwardRoute: List<Int>, returnRoute: List<Int>): TracerouteMapAvailability =
|
||||
evaluateTracerouteMapAvailability(
|
||||
forwardRoute = forwardRoute,
|
||||
returnRoute = returnRoute,
|
||||
positionedNodeNums =
|
||||
nodeDB.nodeDBbyNum.value.values.filter { it.validPosition != null }.map { it.num }.toSet(),
|
||||
)
|
||||
|
||||
fun showAlert(
|
||||
title: String,
|
||||
message: String? = null,
|
||||
|
|
@ -248,7 +259,7 @@ constructor(
|
|||
Timber.d("ViewModel cleared")
|
||||
}
|
||||
|
||||
val tracerouteResponse: LiveData<String?>
|
||||
val tracerouteResponse: LiveData<TracerouteResponse?>
|
||||
get() = serviceRepository.tracerouteResponse.asLiveData()
|
||||
|
||||
fun clearTracerouteResponse() {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import org.meshtastic.feature.node.metrics.PositionLogScreen
|
|||
import org.meshtastic.feature.node.metrics.PowerMetricsScreen
|
||||
import org.meshtastic.feature.node.metrics.SignalMetricsScreen
|
||||
import org.meshtastic.feature.node.metrics.TracerouteLogScreen
|
||||
import org.meshtastic.feature.node.metrics.TracerouteMapScreen
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
fun NavGraphBuilder.nodesGraph(navController: NavHostController, scrollToTopEvents: Flow<ScrollToTopEvent>) {
|
||||
|
|
@ -121,6 +122,54 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, scrollToTo
|
|||
NodeMapScreen(vm, onNavigateUp = navController::navigateUp)
|
||||
}
|
||||
|
||||
composable<NodeDetailRoutes.TracerouteLog>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<NodeDetailRoutes.TracerouteLog>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/node/{destNum}/traceroute",
|
||||
),
|
||||
navDeepLink<NodeDetailRoutes.TracerouteLog>(basePath = "$DEEP_LINK_BASE_URI/node/traceroute"),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val metricsViewModel = hiltViewModel<MetricsViewModel>(parentGraphBackStackEntry)
|
||||
|
||||
val args = backStackEntry.toRoute<NodeDetailRoutes.TracerouteLog>()
|
||||
metricsViewModel.setNodeId(args.destNum)
|
||||
|
||||
TracerouteLogScreen(
|
||||
viewModel = metricsViewModel,
|
||||
onNavigateUp = navController::navigateUp,
|
||||
onViewOnMap = { requestId ->
|
||||
navController.navigate(NodeDetailRoutes.TracerouteMap(args.destNum, requestId))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
composable<NodeDetailRoutes.TracerouteMap>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<NodeDetailRoutes.TracerouteMap>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/node/{destNum}/traceroute_map",
|
||||
),
|
||||
navDeepLink<NodeDetailRoutes.TracerouteMap>(basePath = "$DEEP_LINK_BASE_URI/node/traceroute_map"),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val metricsViewModel = hiltViewModel<MetricsViewModel>(parentGraphBackStackEntry)
|
||||
|
||||
val args = backStackEntry.toRoute<NodeDetailRoutes.TracerouteMap>()
|
||||
metricsViewModel.setNodeId(args.destNum)
|
||||
|
||||
TracerouteMapScreen(
|
||||
metricsViewModel = metricsViewModel,
|
||||
requestId = args.requestId,
|
||||
onNavigateUp = navController::navigateUp,
|
||||
)
|
||||
}
|
||||
|
||||
NodeDetailRoute.entries.forEach { entry ->
|
||||
when (entry.routeClass) {
|
||||
NodeDetailRoutes.DeviceMetrics::class ->
|
||||
|
|
@ -163,14 +212,6 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, scrollToTo
|
|||
) {
|
||||
it.destNum
|
||||
}
|
||||
NodeDetailRoutes.TracerouteLog::class ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.TracerouteLog>(
|
||||
navController,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
) {
|
||||
it.destNum
|
||||
}
|
||||
NodeDetailRoutes.HostMetricsLog::class ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.HostMetricsLog>(
|
||||
navController,
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ import org.meshtastic.core.model.MessageStatus
|
|||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.NodeInfo
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.fullRouteDiscovery
|
||||
import org.meshtastic.core.model.getFullTracerouteResponse
|
||||
import org.meshtastic.core.model.util.anonymize
|
||||
import org.meshtastic.core.model.util.toOneLineString
|
||||
|
|
@ -92,6 +93,7 @@ import org.meshtastic.core.service.MeshServiceNotifications
|
|||
import org.meshtastic.core.service.SERVICE_NOTIFY_ID
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.service.TracerouteResponse
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.connected_count
|
||||
import org.meshtastic.core.strings.connecting
|
||||
|
|
@ -927,11 +929,12 @@ class MeshService : Service() {
|
|||
|
||||
Portnums.PortNum.TRACEROUTE_APP_VALUE -> {
|
||||
Timber.d("Received TRACEROUTE_APP from $fromId")
|
||||
val routeDiscovery = packet.fullRouteDiscovery
|
||||
val full = packet.getFullTracerouteResponse(::getUserName)
|
||||
if (full != null) {
|
||||
val requestId = packet.decoded.requestId
|
||||
val start = tracerouteStartTimes.remove(requestId)
|
||||
val response =
|
||||
val responseText =
|
||||
if (start != null) {
|
||||
val elapsedMs = System.currentTimeMillis() - start
|
||||
val seconds = elapsedMs / 1000.0
|
||||
|
|
@ -940,7 +943,19 @@ class MeshService : Service() {
|
|||
} else {
|
||||
full
|
||||
}
|
||||
serviceRepository.setTracerouteResponse(response)
|
||||
val destination =
|
||||
routeDiscovery?.routeList?.firstOrNull()
|
||||
?: routeDiscovery?.routeBackList?.lastOrNull()
|
||||
?: 0
|
||||
serviceRepository.setTracerouteResponse(
|
||||
TracerouteResponse(
|
||||
message = responseText,
|
||||
destinationNodeNum = destination,
|
||||
requestId = requestId,
|
||||
forwardRoute = routeDiscovery?.routeList.orEmpty(),
|
||||
returnRoute = routeDiscovery?.routeBackList.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,9 +106,11 @@ import org.jetbrains.compose.resources.StringResource
|
|||
import org.jetbrains.compose.resources.getString
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.model.toMessageRes
|
||||
import org.meshtastic.core.navigation.ConnectionsRoutes
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.MapRoutes
|
||||
import org.meshtastic.core.navigation.NodeDetailRoutes
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
|
|
@ -117,6 +119,7 @@ import org.meshtastic.core.strings.Res
|
|||
import org.meshtastic.core.strings.app_too_old
|
||||
import org.meshtastic.core.strings.bottom_nav_settings
|
||||
import org.meshtastic.core.strings.client_notification
|
||||
import org.meshtastic.core.strings.close
|
||||
import org.meshtastic.core.strings.compromised_keys
|
||||
import org.meshtastic.core.strings.connected
|
||||
import org.meshtastic.core.strings.connecting
|
||||
|
|
@ -133,6 +136,7 @@ import org.meshtastic.core.strings.okay
|
|||
import org.meshtastic.core.strings.should_update
|
||||
import org.meshtastic.core.strings.should_update_firmware
|
||||
import org.meshtastic.core.strings.traceroute
|
||||
import org.meshtastic.core.strings.view_on_map
|
||||
import org.meshtastic.core.ui.component.MultipleChoiceAlertDialog
|
||||
import org.meshtastic.core.ui.component.ScrollToTopEvent
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
|
|
@ -239,16 +243,49 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode
|
|||
}
|
||||
|
||||
val traceRouteResponse by uIViewModel.tracerouteResponse.observeAsState()
|
||||
traceRouteResponse?.let { response ->
|
||||
var tracerouteMapError by remember { mutableStateOf<StringResource?>(null) }
|
||||
var dismissedTracerouteRequestId by remember { mutableStateOf<Int?>(null) }
|
||||
traceRouteResponse
|
||||
?.takeIf { it.requestId != dismissedTracerouteRequestId }
|
||||
?.let { response ->
|
||||
SimpleAlertDialog(
|
||||
title = Res.string.traceroute,
|
||||
text = {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
Text(text = annotateTraceroute(response.message))
|
||||
}
|
||||
},
|
||||
confirmText = stringResource(Res.string.view_on_map),
|
||||
onConfirm = {
|
||||
val availability =
|
||||
uIViewModel.tracerouteMapAvailability(
|
||||
forwardRoute = response.forwardRoute,
|
||||
returnRoute = response.returnRoute,
|
||||
)
|
||||
val errorRes = availability.toMessageRes()
|
||||
if (errorRes == null) {
|
||||
dismissedTracerouteRequestId = response.requestId
|
||||
navController.navigate(
|
||||
NodeDetailRoutes.TracerouteMap(response.destinationNodeNum, response.requestId),
|
||||
)
|
||||
} else {
|
||||
tracerouteMapError = errorRes
|
||||
uIViewModel.clearTracerouteResponse()
|
||||
}
|
||||
},
|
||||
dismissText = stringResource(Res.string.okay),
|
||||
onDismiss = {
|
||||
uIViewModel.clearTracerouteResponse()
|
||||
dismissedTracerouteRequestId = null
|
||||
},
|
||||
)
|
||||
}
|
||||
tracerouteMapError?.let { res ->
|
||||
SimpleAlertDialog(
|
||||
title = Res.string.traceroute,
|
||||
text = {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
Text(text = annotateTraceroute(response))
|
||||
}
|
||||
},
|
||||
dismissText = stringResource(Res.string.okay),
|
||||
onDismiss = { uIViewModel.clearTracerouteResponse() },
|
||||
text = { Text(text = stringResource(res)) },
|
||||
dismissText = stringResource(Res.string.close),
|
||||
onDismiss = { tracerouteMapError = null },
|
||||
)
|
||||
}
|
||||
val navSuiteType = NavigationSuiteScaffoldDefaults.navigationSuiteType(currentWindowAdaptiveInfo())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue