fix(nav): restore broken traceroute map navigation (#5104)

This commit is contained in:
James Rich 2026-04-13 07:25:21 -05:00 committed by GitHub
parent 35bf1fded5
commit 39620d063b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 71 additions and 6 deletions

View file

@ -21,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import org.meshtastic.core.navigation.MultiBackstack
import org.meshtastic.core.navigation.NodeDetailRoute
import org.meshtastic.core.navigation.NodesRoute
import org.meshtastic.core.ui.viewmodel.UIViewModel
/**
@ -43,8 +44,11 @@ fun MeshtasticAppShell(
MeshtasticCommonAppSetup(
uiViewModel = uiViewModel,
onNavigateToTracerouteMap = { destNum, requestId, logUuid ->
multiBackstack.activeBackStack.add(
NodeDetailRoute.TracerouteMap(destNum = destNum, requestId = requestId, logUuid = logUuid),
multiBackstack.handleDeepLink(
listOf(
NodesRoute.NodesGraph,
NodeDetailRoute.TracerouteMap(destNum = destNum, requestId = requestId, logUuid = logUuid),
),
)
},
)

View file

@ -26,9 +26,11 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.okay
import org.meshtastic.core.resources.traceroute
@ -52,6 +54,7 @@ fun TracerouteAlertHandler(
val traceRouteResponse by uiViewModel.tracerouteResponse.collectAsStateWithLifecycle(null)
var dismissedTracerouteRequestId by remember { mutableStateOf<Int?>(null) }
val colorScheme = MaterialTheme.colorScheme
val scope = rememberCoroutineScope()
LaunchedEffect(traceRouteResponse, dismissedTracerouteRequestId) {
val response = traceRouteResponse
@ -83,8 +86,10 @@ fun TracerouteAlertHandler(
dismissedTracerouteRequestId = response.requestId
onNavigateToMap(response.destinationNodeNum, response.requestId, response.logUuid)
} else {
uiViewModel.showAlert(titleRes = Res.string.traceroute, messageRes = errorRes)
uiViewModel.clearTracerouteResponse()
// Post the error alert after the current alert is dismissed to avoid
// the wrapping dismissAlert() in AlertManager immediately clearing it.
scope.launch { uiViewModel.showAlert(titleRes = Res.string.traceroute, messageRes = errorRes) }
}
},
dismissTextRes = Res.string.okay,

View file

@ -68,4 +68,27 @@ class AlertManagerTest {
assertEquals(true, dismissClicked)
assertNull(alertManager.currentAlert.value)
}
@Test
fun showAlert_inside_onConfirm_is_dismissed_by_wrapping_dismissAlert() {
// Documents the known race condition: AlertManager wraps onConfirm to call
// dismissAlert() AFTER the user callback, so a showAlert() inside onConfirm
// gets immediately cleared. Callers must defer via launch {} to work around this.
alertManager.showAlert(
title = "First",
onConfirm = {
// This simulates an error path where onConfirm shows a follow-up alert
alertManager.showAlert(title = "Second", message = "Error details")
},
)
// Trigger the wrapped onConfirm (user callback + dismissAlert)
alertManager.currentAlert.value?.onConfirm?.invoke()
// The second alert is wiped by dismissAlert() — currentAlert is null
assertNull(
alertManager.currentAlert.value,
"showAlert inside onConfirm is cleared by the wrapping dismissAlert; callers must defer via launch {}",
)
}
}