fix(node): navigate up after confirming node removal

After the user taps Confirm on the remove-node dialog, the screen was
left in a dead end showing ghost data. CommonGetNodeDetailsUseCase
substitutes a fallback node rather than emitting null, so the UI had
no signal to navigate away.

Fix: thread an onAfterRemove callback through requestRemoveNode →
handleNodeMenuAction → handleNodeAction, so onNavigateUp is invoked
inside the onConfirm lambda immediately after removeNode() is launched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich 2026-04-20 07:26:15 -05:00
parent 58f09d7083
commit f2e664614f
5 changed files with 31 additions and 7 deletions

View file

@ -43,7 +43,7 @@ internal fun handleNodeAction(
val route = viewModel.getDirectMessageRoute(menuAction.node, uiState.ourNode)
navigateToMessages(route)
}
is NodeMenuAction.Remove -> viewModel.handleNodeMenuAction(menuAction)
is NodeMenuAction.Remove -> viewModel.handleNodeMenuAction(menuAction, onNavigateUp)
else -> viewModel.handleNodeMenuAction(menuAction)
}
}

View file

@ -89,9 +89,10 @@ class NodeDetailViewModel(
}
/** Dispatches high-level node management actions like removal, muting, or favoriting. */
fun handleNodeMenuAction(action: NodeMenuAction) {
fun handleNodeMenuAction(action: NodeMenuAction, onAfterRemove: () -> Unit = {}) {
when (action) {
is NodeMenuAction.Remove -> nodeManagementActions.requestRemoveNode(viewModelScope, action.node)
is NodeMenuAction.Remove ->
nodeManagementActions.requestRemoveNode(viewModelScope, action.node, onAfterRemove)
is NodeMenuAction.Ignore -> nodeManagementActions.requestIgnoreNode(viewModelScope, action.node)
is NodeMenuAction.Mute -> nodeManagementActions.requestMuteNode(viewModelScope, action.node)
is NodeMenuAction.Favorite -> nodeManagementActions.requestFavoriteNode(viewModelScope, action.node)

View file

@ -50,11 +50,14 @@ constructor(
private val radioController: RadioController,
private val alertManager: AlertManager,
) {
open fun requestRemoveNode(scope: CoroutineScope, node: Node) {
open fun requestRemoveNode(scope: CoroutineScope, node: Node, onAfterRemove: () -> Unit = {}) {
alertManager.showAlert(
titleRes = Res.string.remove,
messageRes = Res.string.remove_node_text,
onConfirm = { removeNode(scope, node.num) },
onConfirm = {
removeNode(scope, node.num)
onAfterRemove()
},
)
}

View file

@ -63,7 +63,7 @@ class HandleNodeActionTest {
@Test
fun `remove action delegates to viewModel and does not navigate up immediately`() = runTest(testDispatcher) {
val node = Node(num = 1234, user = User(id = "!1234"))
every { nodeManagementActions.requestRemoveNode(any(), any()) } returns Unit
every { nodeManagementActions.requestRemoveNode(any(), any(), any()) } returns Unit
val viewModel = createViewModel()
var navigateUpCalled = false
@ -76,7 +76,7 @@ class HandleNodeActionTest {
viewModel = viewModel,
)
verify { nodeManagementActions.requestRemoveNode(any(), node) }
verify { nodeManagementActions.requestRemoveNode(any(), node, any()) }
assertFalse(navigateUpCalled)
}

View file

@ -30,6 +30,7 @@ import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.core.ui.util.AlertManager
import org.meshtastic.proto.User
import kotlin.test.Test
import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class)
class NodeManagementActionsTest {
@ -69,4 +70,23 @@ class NodeManagementActionsTest {
)
}
}
@Test
fun requestRemoveNode_invokes_onAfterRemove_when_user_confirms() {
val realAlertManager = AlertManager()
val actionsWithRealAlert =
NodeManagementActions(
nodeRepository = nodeRepository,
serviceRepository = serviceRepository,
radioController = radioController,
alertManager = realAlertManager,
)
val node = Node(num = 123, user = User(long_name = "Test Node"))
var afterRemoveCalled = false
actionsWithRealAlert.requestRemoveNode(testScope, node) { afterRemoveCalled = true }
realAlertManager.currentAlert.value?.onConfirm?.invoke()
assertTrue(afterRemoveCalled)
}
}