mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-14 17:05:44 -04:00
refactor: Remove AIDL API and modernize service architecture (#5586)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -51,9 +51,9 @@ import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.Base64Factory
|
||||
import org.meshtastic.core.common.util.MetricFormatter
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeAddress
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.a11y_label_value
|
||||
@@ -214,7 +214,7 @@ private fun NodeIdentificationRow(node: Node) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
InfoItem(
|
||||
label = stringResource(Res.string.node_id),
|
||||
value = DataPacket.nodeNumToDefaultId(node.num),
|
||||
value = NodeAddress.numToDefaultId(node.num),
|
||||
icon = MeshtasticIcons.DeviceNumbers,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
@@ -17,18 +17,15 @@
|
||||
package org.meshtastic.feature.node.detail
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.ioDispatcher
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.core.repository.RadioController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.neighbor_info
|
||||
@@ -62,60 +59,50 @@ constructor(
|
||||
snackbarManager.showSnackbar(message = text.resolve())
|
||||
}
|
||||
|
||||
override fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting UserInfo for '$destNum'" }
|
||||
radioController.requestUserInfo(destNum)
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName))
|
||||
}
|
||||
override suspend fun requestUserInfo(destNum: Int, longName: String) {
|
||||
Logger.i { "Requesting UserInfo for '$destNum'" }
|
||||
radioController.requestUserInfo(destNum)
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName))
|
||||
}
|
||||
|
||||
override fun requestNeighborInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting NeighborInfo for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestNeighborInfo(packetId, destNum)
|
||||
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName))
|
||||
}
|
||||
override suspend fun requestNeighborInfo(destNum: Int, longName: String) {
|
||||
Logger.i { "Requesting NeighborInfo for '$destNum'" }
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.requestNeighborInfo(packetId, destNum)
|
||||
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName))
|
||||
}
|
||||
|
||||
override fun requestPosition(scope: CoroutineScope, destNum: Int, longName: String, position: Position) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting position for '$destNum'" }
|
||||
radioController.requestPosition(destNum, position)
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.position, longName))
|
||||
}
|
||||
override suspend fun requestPosition(destNum: Int, longName: String, position: Position) {
|
||||
Logger.i { "Requesting position for '$destNum'" }
|
||||
radioController.requestPosition(destNum, position)
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.position, longName))
|
||||
}
|
||||
|
||||
override fun requestTelemetry(scope: CoroutineScope, destNum: Int, longName: String, type: TelemetryType) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting telemetry for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestTelemetry(packetId, destNum, type.ordinal)
|
||||
override suspend fun requestTelemetry(destNum: Int, longName: String, type: TelemetryType) {
|
||||
Logger.i { "Requesting telemetry for '$destNum'" }
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.requestTelemetry(packetId, destNum, type.ordinal)
|
||||
|
||||
val typeRes =
|
||||
when (type) {
|
||||
TelemetryType.DEVICE -> Res.string.request_device_metrics
|
||||
TelemetryType.ENVIRONMENT -> Res.string.request_environment_metrics
|
||||
TelemetryType.AIR_QUALITY -> Res.string.request_air_quality_metrics
|
||||
TelemetryType.POWER -> Res.string.request_power_metrics
|
||||
TelemetryType.LOCAL_STATS -> Res.string.signal_quality
|
||||
TelemetryType.HOST -> Res.string.request_host_metrics
|
||||
TelemetryType.PAX -> Res.string.request_pax_metrics
|
||||
}
|
||||
val typeRes =
|
||||
when (type) {
|
||||
TelemetryType.DEVICE -> Res.string.request_device_metrics
|
||||
TelemetryType.ENVIRONMENT -> Res.string.request_environment_metrics
|
||||
TelemetryType.AIR_QUALITY -> Res.string.request_air_quality_metrics
|
||||
TelemetryType.POWER -> Res.string.request_power_metrics
|
||||
TelemetryType.LOCAL_STATS -> Res.string.signal_quality
|
||||
TelemetryType.HOST -> Res.string.request_host_metrics
|
||||
TelemetryType.PAX -> Res.string.request_pax_metrics
|
||||
}
|
||||
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, typeRes, longName))
|
||||
}
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, typeRes, longName))
|
||||
}
|
||||
|
||||
override fun requestTraceroute(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Requesting traceroute for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTime.value = nowMillis
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName))
|
||||
}
|
||||
override suspend fun requestTraceroute(destNum: Int, longName: String) {
|
||||
Logger.i { "Requesting traceroute for '$destNum'" }
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTime.value = nowMillis
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@ internal fun handleNodeAction(
|
||||
when (action) {
|
||||
is NodeDetailAction.Navigate -> onNavigate(action.route)
|
||||
|
||||
is NodeDetailAction.TriggerServiceAction -> viewModel.onServiceAction(action.action)
|
||||
|
||||
is NodeDetailAction.OpenRemoteAdmin -> viewModel.openRemoteAdmin(action.nodeNum)
|
||||
|
||||
is NodeDetailAction.RefreshMetadata -> viewModel.refreshMetadata(action.nodeNum)
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.feature.node.detail
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
|
||||
@Single
|
||||
class NodeDetailActions
|
||||
constructor(
|
||||
private val nodeManagementActions: NodeManagementActions,
|
||||
private val nodeRequestActions: NodeRequestActions,
|
||||
) {
|
||||
fun handleNodeMenuAction(scope: CoroutineScope, action: NodeMenuAction) {
|
||||
when (action) {
|
||||
is NodeMenuAction.Remove -> nodeManagementActions.removeNode(scope, action.node.num)
|
||||
|
||||
is NodeMenuAction.Ignore -> nodeManagementActions.ignoreNode(scope, action.node)
|
||||
|
||||
is NodeMenuAction.Mute -> nodeManagementActions.muteNode(scope, action.node)
|
||||
|
||||
is NodeMenuAction.Favorite -> nodeManagementActions.favoriteNode(scope, action.node)
|
||||
|
||||
is NodeMenuAction.RequestUserInfo ->
|
||||
nodeRequestActions.requestUserInfo(scope, action.node.num, action.node.user.long_name)
|
||||
|
||||
is NodeMenuAction.RequestNeighborInfo ->
|
||||
nodeRequestActions.requestNeighborInfo(scope, action.node.num, action.node.user.long_name)
|
||||
|
||||
is NodeMenuAction.RequestPosition ->
|
||||
nodeRequestActions.requestPosition(scope, action.node.num, action.node.user.long_name)
|
||||
|
||||
is NodeMenuAction.RequestTelemetry ->
|
||||
nodeRequestActions.requestTelemetry(scope, action.node.num, action.node.user.long_name, action.type)
|
||||
|
||||
is NodeMenuAction.TraceRoute ->
|
||||
nodeRequestActions.requestTraceroute(scope, action.node.num, action.node.user.long_name)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
fun setNodeNotes(scope: CoroutineScope, nodeNum: Int, notes: String) {
|
||||
nodeManagementActions.setNodeNotes(scope, nodeNum, notes)
|
||||
}
|
||||
|
||||
fun requestPosition(scope: CoroutineScope, destNum: Int, longName: String, position: Position) {
|
||||
nodeRequestActions.requestPosition(scope, destNum, longName, position)
|
||||
}
|
||||
|
||||
fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
nodeRequestActions.requestUserInfo(scope, destNum, longName)
|
||||
}
|
||||
|
||||
fun requestNeighborInfo(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
nodeRequestActions.requestNeighborInfo(scope, destNum, longName)
|
||||
}
|
||||
|
||||
fun requestTelemetry(scope: CoroutineScope, destNum: Int, longName: String, type: TelemetryType) {
|
||||
nodeRequestActions.requestTelemetry(scope, destNum, longName, type)
|
||||
}
|
||||
|
||||
fun requestTraceroute(scope: CoroutineScope, destNum: Int, longName: String) {
|
||||
nodeRequestActions.requestTraceroute(scope, destNum, longName)
|
||||
}
|
||||
}
|
||||
@@ -34,13 +34,12 @@ import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.domain.usecase.session.EnsureRemoteAdminSessionUseCase
|
||||
import org.meshtastic.core.domain.usecase.session.EnsureSessionResult
|
||||
import org.meshtastic.core.domain.usecase.session.ObserveRemoteAdminSessionStatusUseCase
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeAddress
|
||||
import org.meshtastic.core.model.SessionStatus
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoute
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.connect_radio_for_remote_admin
|
||||
@@ -82,7 +81,7 @@ class NodeDetailViewModel(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val nodeManagementActions: NodeManagementActions,
|
||||
private val nodeRequestActions: NodeRequestActions,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val queryController: QueryController,
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase,
|
||||
private val ensureRemoteAdminSession: EnsureRemoteAdminSessionUseCase,
|
||||
private val observeRemoteAdminSessionStatus: ObserveRemoteAdminSessionStatusUseCase,
|
||||
@@ -144,39 +143,47 @@ class NodeDetailViewModel(
|
||||
is NodeMenuAction.Favorite -> nodeManagementActions.requestFavoriteNode(viewModelScope, action.node)
|
||||
|
||||
is NodeMenuAction.RequestUserInfo ->
|
||||
nodeRequestActions.requestUserInfo(viewModelScope, action.node.num, action.node.user.long_name)
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestUserInfo(action.node.num, action.node.user.long_name)
|
||||
}
|
||||
|
||||
is NodeMenuAction.RequestNeighborInfo ->
|
||||
nodeRequestActions.requestNeighborInfo(viewModelScope, action.node.num, action.node.user.long_name)
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestNeighborInfo(action.node.num, action.node.user.long_name)
|
||||
}
|
||||
|
||||
is NodeMenuAction.RequestPosition ->
|
||||
nodeRequestActions.requestPosition(viewModelScope, action.node.num, action.node.user.long_name)
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestPosition(action.node.num, action.node.user.long_name)
|
||||
}
|
||||
|
||||
is NodeMenuAction.RequestTelemetry ->
|
||||
nodeRequestActions.requestTelemetry(
|
||||
viewModelScope,
|
||||
action.node.num,
|
||||
action.node.user.long_name,
|
||||
action.type,
|
||||
)
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestTelemetry(action.node.num, action.node.user.long_name, action.type)
|
||||
}
|
||||
|
||||
is NodeMenuAction.TraceRoute ->
|
||||
nodeRequestActions.requestTraceroute(viewModelScope, action.node.num, action.node.user.long_name)
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestTraceroute(action.node.num, action.node.user.long_name)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
fun onServiceAction(action: ServiceAction) = viewModelScope.launch { serviceRepository.onServiceAction(action) }
|
||||
/**
|
||||
* Re-fetch device metadata (firmware/edition/role) for [destNum]. Refreshes the session passkey as a side effect.
|
||||
*/
|
||||
fun refreshMetadata(destNum: Int) = viewModelScope.launch { queryController.refreshMetadata(destNum) }
|
||||
|
||||
/**
|
||||
* Ensure a remote-admin session passkey is fresh, then request navigation to the remote-admin screen. Surfaces a
|
||||
* snackbar with the appropriate guidance on [EnsureSessionResult.Disconnected] or [EnsureSessionResult.Timeout].
|
||||
*/
|
||||
fun openRemoteAdmin(destNum: Int) {
|
||||
if (isEnsuringSession.value) return
|
||||
// Atomic check-and-flip prevents a double-tap from queuing two passkey exchanges + two navigation events.
|
||||
if (!isEnsuringSession.compareAndSet(expect = false, update = true)) return
|
||||
viewModelScope.launch {
|
||||
isEnsuringSession.value = true
|
||||
try {
|
||||
when (ensureRemoteAdminSession(destNum)) {
|
||||
EnsureSessionResult.AlreadyActive,
|
||||
@@ -199,19 +206,14 @@ class NodeDetailViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-fetch device metadata (firmware/edition/role) for [destNum]. Refreshes the session passkey as a side effect.
|
||||
*/
|
||||
fun refreshMetadata(destNum: Int) = onServiceAction(ServiceAction.GetDeviceMetadata(destNum))
|
||||
|
||||
fun setNodeNotes(nodeNum: Int, notes: String) {
|
||||
nodeManagementActions.setNodeNotes(viewModelScope, nodeNum, notes)
|
||||
viewModelScope.launch { nodeManagementActions.setNodeNotes(nodeNum, notes) }
|
||||
}
|
||||
|
||||
/** Returns the type-safe navigation route for a direct message to this node. */
|
||||
fun getDirectMessageRoute(node: Node, ourNode: Node?): String {
|
||||
val hasPKC = ourNode?.hasPKC == true && node.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
val channel = if (hasPKC) NodeAddress.PKC_CHANNEL_INDEX else node.channel
|
||||
return "${channel}${node.user.id}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,9 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.ioDispatcher
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.RadioController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.favorite
|
||||
import org.meshtastic.core.resources.favorite_add
|
||||
@@ -41,12 +38,12 @@ import org.meshtastic.core.resources.remove
|
||||
import org.meshtastic.core.resources.remove_node_text
|
||||
import org.meshtastic.core.resources.unmute
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
@Single
|
||||
open class NodeManagementActions
|
||||
constructor(
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val alertManager: AlertManager,
|
||||
) {
|
||||
@@ -55,19 +52,17 @@ constructor(
|
||||
titleRes = Res.string.remove,
|
||||
messageRes = Res.string.remove_node_text,
|
||||
onConfirm = {
|
||||
removeNode(scope, node.num)
|
||||
scope.launch { removeNode(node.num) }
|
||||
onAfterRemove()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
open fun removeNode(scope: CoroutineScope, nodeNum: Int) {
|
||||
scope.launch(ioDispatcher) {
|
||||
Logger.i { "Removing node '$nodeNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
radioController.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
}
|
||||
open suspend fun removeNode(nodeNum: Int) {
|
||||
Logger.i { "Removing node '$nodeNum'" }
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
}
|
||||
|
||||
open fun requestIgnoreNode(scope: CoroutineScope, node: Node) {
|
||||
@@ -77,13 +72,13 @@ constructor(
|
||||
alertManager.showAlert(
|
||||
titleRes = Res.string.ignore,
|
||||
message = message,
|
||||
onConfirm = { ignoreNode(scope, node) },
|
||||
onConfirm = { scope.launch { setIgnored(node.num, !node.isIgnored) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun ignoreNode(scope: CoroutineScope, node: Node) {
|
||||
scope.launch(ioDispatcher) { serviceRepository.onServiceAction(ServiceAction.Ignore(node)) }
|
||||
open suspend fun setIgnored(nodeNum: Int, ignored: Boolean) {
|
||||
radioController.setIgnored(nodeNum, ignored)
|
||||
}
|
||||
|
||||
open fun requestMuteNode(scope: CoroutineScope, node: Node) {
|
||||
@@ -93,13 +88,13 @@ constructor(
|
||||
alertManager.showAlert(
|
||||
titleRes = if (node.isMuted) Res.string.unmute else Res.string.mute_notifications,
|
||||
message = message,
|
||||
onConfirm = { muteNode(scope, node) },
|
||||
onConfirm = { scope.launch { toggleMuted(node.num) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun muteNode(scope: CoroutineScope, node: Node) {
|
||||
scope.launch(ioDispatcher) { serviceRepository.onServiceAction(ServiceAction.Mute(node)) }
|
||||
open suspend fun toggleMuted(nodeNum: Int) {
|
||||
radioController.toggleMuted(nodeNum)
|
||||
}
|
||||
|
||||
open fun requestFavoriteNode(scope: CoroutineScope, node: Node) {
|
||||
@@ -112,22 +107,22 @@ constructor(
|
||||
alertManager.showAlert(
|
||||
titleRes = Res.string.favorite,
|
||||
message = message,
|
||||
onConfirm = { favoriteNode(scope, node) },
|
||||
onConfirm = { scope.launch { setFavorite(node.num, !node.isFavorite) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun favoriteNode(scope: CoroutineScope, node: Node) {
|
||||
scope.launch(ioDispatcher) { serviceRepository.onServiceAction(ServiceAction.Favorite(node)) }
|
||||
open suspend fun setFavorite(nodeNum: Int, favorite: Boolean) {
|
||||
radioController.setFavorite(nodeNum, favorite)
|
||||
}
|
||||
|
||||
open fun setNodeNotes(scope: CoroutineScope, nodeNum: Int, notes: String) {
|
||||
scope.launch(ioDispatcher) {
|
||||
try {
|
||||
nodeRepository.setNodeNotes(nodeNum, notes)
|
||||
} catch (ex: Exception) {
|
||||
Logger.e(ex) { "Set node notes error" }
|
||||
}
|
||||
open suspend fun setNodeNotes(nodeNum: Int, notes: String) {
|
||||
try {
|
||||
nodeRepository.setNodeNotes(nodeNum, notes)
|
||||
} catch (ex: CancellationException) {
|
||||
throw ex
|
||||
} catch (ex: Exception) {
|
||||
Logger.e(ex) { "Set node notes error" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.meshtastic.feature.node.detail
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.TelemetryType
|
||||
@@ -26,18 +25,13 @@ interface NodeRequestActions {
|
||||
val lastTracerouteTime: StateFlow<Long?>
|
||||
val lastRequestNeighborTimes: StateFlow<Map<Int, Long>>
|
||||
|
||||
fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String)
|
||||
suspend fun requestUserInfo(destNum: Int, longName: String)
|
||||
|
||||
fun requestNeighborInfo(scope: CoroutineScope, destNum: Int, longName: String)
|
||||
suspend fun requestNeighborInfo(destNum: Int, longName: String)
|
||||
|
||||
fun requestPosition(
|
||||
scope: CoroutineScope,
|
||||
destNum: Int,
|
||||
longName: String,
|
||||
position: Position = Position(0.0, 0.0, 0),
|
||||
)
|
||||
suspend fun requestPosition(destNum: Int, longName: String, position: Position = Position(0.0, 0.0, 0))
|
||||
|
||||
fun requestTelemetry(scope: CoroutineScope, destNum: Int, longName: String, type: TelemetryType)
|
||||
suspend fun requestTelemetry(destNum: Int, longName: String, type: TelemetryType)
|
||||
|
||||
fun requestTraceroute(scope: CoroutineScope, destNum: Int, longName: String)
|
||||
suspend fun requestTraceroute(destNum: Int, longName: String)
|
||||
}
|
||||
|
||||
@@ -28,17 +28,17 @@ import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceType
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeAddress
|
||||
import org.meshtastic.core.model.NodeListDensity
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.model.RadioController
|
||||
import org.meshtastic.core.repository.AdminController
|
||||
import org.meshtastic.core.repository.ConnectionStateProvider
|
||||
import org.meshtastic.core.repository.DeviceHardwareRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
|
||||
import org.meshtastic.feature.node.detail.NodeManagementActions
|
||||
import org.meshtastic.feature.node.detail.NodeRequestActions
|
||||
@@ -52,8 +52,8 @@ class NodeListViewModel(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val radioController: RadioController,
|
||||
private val connectionStateProvider: ConnectionStateProvider,
|
||||
private val adminController: AdminController,
|
||||
private val radioInterfaceService: RadioInterfaceService,
|
||||
private val deviceHardwareRepository: DeviceHardwareRepository,
|
||||
val nodeManagementActions: NodeManagementActions,
|
||||
@@ -68,7 +68,7 @@ class NodeListViewModel(
|
||||
|
||||
val totalNodeCount = nodeRepository.totalNodeCount.stateInWhileSubscribed(initialValue = 0)
|
||||
|
||||
val connectionState = serviceRepository.connectionState
|
||||
val connectionState = connectionStateProvider.connectionState
|
||||
|
||||
val deviceType: StateFlow<DeviceType?> =
|
||||
radioInterfaceService.currentDeviceAddressFlow
|
||||
@@ -184,7 +184,7 @@ class NodeListViewModel(
|
||||
radioConfigRepository.replaceAllSettings(channelSet.settings)
|
||||
val newLoraConfig = channelSet.lora_config
|
||||
if (newLoraConfig != null) {
|
||||
radioController.setLocalConfig(Config(lora = newLoraConfig))
|
||||
adminController.setLocalConfig(Config(lora = newLoraConfig))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,13 +200,13 @@ class NodeListViewModel(
|
||||
fun getDirectMessageRoute(node: Node): String {
|
||||
val ourNode = ourNodeInfo.value
|
||||
val hasPKC = ourNode?.hasPKC == true && node.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
val channel = if (hasPKC) NodeAddress.PKC_CHANNEL_INDEX else node.channel
|
||||
return "${channel}${node.user.id}"
|
||||
}
|
||||
|
||||
/** Initiates a trace route request to the specified node. */
|
||||
fun traceRoute(node: Node) {
|
||||
nodeRequestActions.requestTraceroute(viewModelScope, node.num, node.user.long_name)
|
||||
viewModelScope.launch { nodeRequestActions.requestTraceroute(node.num, node.user.long_name) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
@@ -51,7 +52,7 @@ import org.meshtastic.core.model.util.UnitConversions
|
||||
import org.meshtastic.core.repository.FileService
|
||||
import org.meshtastic.core.repository.MeshLogRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.TracerouteResponseProvider
|
||||
import org.meshtastic.core.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.okay
|
||||
@@ -80,7 +81,7 @@ open class MetricsViewModel(
|
||||
@InjectedParam val destNum: Int,
|
||||
protected val dispatchers: CoroutineDispatchers,
|
||||
private val meshLogRepository: MeshLogRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val tracerouteResponseProvider: TracerouteResponseProvider,
|
||||
private val nodeRepository: NodeRepository,
|
||||
private val tracerouteSnapshotRepository: TracerouteSnapshotRepository,
|
||||
private val nodeRequestActions: NodeRequestActions,
|
||||
@@ -191,7 +192,7 @@ open class MetricsViewModel(
|
||||
if (cached != null) return cached
|
||||
|
||||
val overlay =
|
||||
serviceRepository.tracerouteResponse.value
|
||||
tracerouteResponseProvider.tracerouteResponse.value
|
||||
?.takeIf { it.requestId == requestId }
|
||||
?.let { response ->
|
||||
TracerouteOverlay(
|
||||
@@ -211,7 +212,7 @@ open class MetricsViewModel(
|
||||
|
||||
fun tracerouteSnapshotPositions(logUuid: String) = tracerouteSnapshotRepository.getSnapshotPositions(logUuid)
|
||||
|
||||
fun clearTracerouteResponse() = serviceRepository.clearTracerouteResponse()
|
||||
fun clearTracerouteResponse() = tracerouteResponseProvider.clearTracerouteResponse()
|
||||
|
||||
fun positionedNodeNums(): Set<Int> =
|
||||
nodeRepository.nodeDBbyNum.value.values.filter { it.validPosition != null }.numSet()
|
||||
@@ -220,7 +221,7 @@ open class MetricsViewModel(
|
||||
|
||||
init {
|
||||
safeLaunch(tag = "tracerouteCollector") {
|
||||
serviceRepository.tracerouteResponse.filterNotNull().collect { response ->
|
||||
tracerouteResponseProvider.tracerouteResponse.filterNotNull().collect { response ->
|
||||
val overlay =
|
||||
TracerouteOverlay(
|
||||
requestId = response.requestId,
|
||||
@@ -243,25 +244,29 @@ open class MetricsViewModel(
|
||||
|
||||
fun requestPosition() {
|
||||
(manualNodeId.value ?: nodeIdFromRoute)?.let {
|
||||
nodeRequestActions.requestPosition(viewModelScope, it, state.value.node?.user?.long_name ?: "")
|
||||
viewModelScope.launch { nodeRequestActions.requestPosition(it, state.value.node?.user?.long_name ?: "") }
|
||||
}
|
||||
}
|
||||
|
||||
fun requestTelemetry(type: TelemetryType) {
|
||||
(manualNodeId.value ?: nodeIdFromRoute)?.let {
|
||||
nodeRequestActions.requestTelemetry(viewModelScope, it, state.value.node?.user?.long_name ?: "", type)
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestTelemetry(it, state.value.node?.user?.long_name ?: "", type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun requestTraceroute() {
|
||||
(manualNodeId.value ?: nodeIdFromRoute)?.let {
|
||||
nodeRequestActions.requestTraceroute(viewModelScope, it, state.value.node?.user?.long_name ?: "")
|
||||
viewModelScope.launch { nodeRequestActions.requestTraceroute(it, state.value.node?.user?.long_name ?: "") }
|
||||
}
|
||||
}
|
||||
|
||||
fun requestNeighborInfo() {
|
||||
(manualNodeId.value ?: nodeIdFromRoute)?.let {
|
||||
nodeRequestActions.requestNeighborInfo(viewModelScope, it, state.value.node?.user?.long_name ?: "")
|
||||
viewModelScope.launch {
|
||||
nodeRequestActions.requestNeighborInfo(it, state.value.node?.user?.long_name ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.meshtastic.feature.node.model
|
||||
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.proto.Config
|
||||
@@ -25,8 +24,6 @@ import org.meshtastic.proto.Config
|
||||
sealed interface NodeDetailAction {
|
||||
data class Navigate(val route: Route) : NodeDetailAction
|
||||
|
||||
data class TriggerServiceAction(val action: ServiceAction) : NodeDetailAction
|
||||
|
||||
data class HandleNodeMenuAction(val action: NodeMenuAction) : NodeDetailAction
|
||||
|
||||
/** Open the remote-administration screen, ensuring a fresh session passkey first. */
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.meshtastic.core.domain.usecase.session.EnsureRemoteAdminSessionUseCas
|
||||
import org.meshtastic.core.domain.usecase.session.ObserveRemoteAdminSessionStatusUseCase
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.SessionStatus
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.ui.util.SnackbarManager
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
|
||||
@@ -51,7 +51,7 @@ class HandleNodeActionTest {
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val nodeManagementActions: NodeManagementActions = mock()
|
||||
private val nodeRequestActions: NodeRequestActions = mock()
|
||||
private val serviceRepository: ServiceRepository = mock()
|
||||
private val queryController: QueryController = mock()
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase = mock()
|
||||
private val ensureRemoteAdminSession: EnsureRemoteAdminSessionUseCase = mock()
|
||||
private val observeRemoteAdminSessionStatus: ObserveRemoteAdminSessionStatusUseCase = mock()
|
||||
@@ -93,7 +93,7 @@ class HandleNodeActionTest {
|
||||
savedStateHandle = SavedStateHandle(mapOf("destNum" to 1234)),
|
||||
nodeManagementActions = nodeManagementActions,
|
||||
nodeRequestActions = nodeRequestActions,
|
||||
serviceRepository = serviceRepository,
|
||||
queryController = queryController,
|
||||
getNodeDetailsUseCase = getNodeDetailsUseCase,
|
||||
ensureRemoteAdminSession = ensureRemoteAdminSession,
|
||||
observeRemoteAdminSessionStatus = observeRemoteAdminSessionStatus,
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.meshtastic.core.domain.usecase.session.ObserveRemoteAdminSessionStatu
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.SessionStatus
|
||||
import org.meshtastic.core.navigation.SettingsRoute
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.connect_radio_for_remote_admin
|
||||
@@ -64,7 +64,7 @@ class NodeDetailViewModelTest {
|
||||
private lateinit var viewModel: NodeDetailViewModel
|
||||
private val nodeManagementActions: NodeManagementActions = mock()
|
||||
private val nodeRequestActions: NodeRequestActions = mock()
|
||||
private val serviceRepository: ServiceRepository = mock()
|
||||
private val queryController: QueryController = mock()
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase = mock()
|
||||
private val ensureRemoteAdminSession: EnsureRemoteAdminSessionUseCase = mock()
|
||||
private val observeRemoteAdminSessionStatus: ObserveRemoteAdminSessionStatusUseCase = mock()
|
||||
@@ -97,7 +97,7 @@ class NodeDetailViewModelTest {
|
||||
savedStateHandle = SavedStateHandle(if (nodeId != null) mapOf("destNum" to nodeId) else emptyMap()),
|
||||
nodeManagementActions = nodeManagementActions,
|
||||
nodeRequestActions = nodeRequestActions,
|
||||
serviceRepository = serviceRepository,
|
||||
queryController = queryController,
|
||||
getNodeDetailsUseCase = getNodeDetailsUseCase,
|
||||
ensureRemoteAdminSession = ensureRemoteAdminSession,
|
||||
observeRemoteAdminSessionStatus = observeRemoteAdminSessionStatus,
|
||||
@@ -158,11 +158,11 @@ class NodeDetailViewModelTest {
|
||||
@Test
|
||||
fun `handleNodeMenuAction delegates to nodeRequestActions for Traceroute`() = runTest(testDispatcher) {
|
||||
val node = Node(num = 1234, user = User(id = "!1234", long_name = "Test Node"))
|
||||
every { nodeRequestActions.requestTraceroute(any(), any(), any()) } returns Unit
|
||||
everySuspend { nodeRequestActions.requestTraceroute(any(), any()) } returns Unit
|
||||
|
||||
viewModel.handleNodeMenuAction(NodeMenuAction.TraceRoute(node))
|
||||
|
||||
verify { nodeRequestActions.requestTraceroute(any(), 1234, "Test Node") }
|
||||
verifySuspend { nodeRequestActions.requestTraceroute(1234, "Test Node") }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,7 +24,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.testing.FakeNodeRepository
|
||||
import org.meshtastic.core.testing.FakeRadioController
|
||||
import org.meshtastic.core.ui.util.AlertManager
|
||||
@@ -36,7 +35,6 @@ import kotlin.test.assertTrue
|
||||
class NodeManagementActionsTest {
|
||||
|
||||
private val nodeRepository = FakeNodeRepository()
|
||||
private val serviceRepository = mock<ServiceRepository>(MockMode.autofill)
|
||||
private val radioController = FakeRadioController()
|
||||
private val alertManager = mock<AlertManager>(MockMode.autofill)
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
@@ -45,7 +43,6 @@ class NodeManagementActionsTest {
|
||||
private val actions =
|
||||
NodeManagementActions(
|
||||
nodeRepository = nodeRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
alertManager = alertManager,
|
||||
)
|
||||
@@ -77,7 +74,6 @@ class NodeManagementActionsTest {
|
||||
val actionsWithRealAlert =
|
||||
NodeManagementActions(
|
||||
nodeRepository = nodeRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
alertManager = realAlertManager,
|
||||
)
|
||||
|
||||
@@ -28,8 +28,8 @@ import kotlinx.coroutines.test.runTest
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.NodeSortOption
|
||||
import org.meshtastic.core.repository.ConnectionStateProvider
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.testing.FakeDeviceHardwareRepository
|
||||
import org.meshtastic.core.testing.FakeNodeRepository
|
||||
import org.meshtastic.core.testing.FakeRadioController
|
||||
@@ -50,7 +50,7 @@ class NodeListViewModelTest {
|
||||
private lateinit var radioController: FakeRadioController
|
||||
private lateinit var radioInterfaceService: FakeRadioInterfaceService
|
||||
private val radioConfigRepository: RadioConfigRepository = mock(MockMode.autofill)
|
||||
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
|
||||
private val connectionStateProvider: ConnectionStateProvider = mock(MockMode.autofill)
|
||||
private val nodeFilterPreferences: NodeFilterPreferences = mock(MockMode.autofill)
|
||||
private val nodeManagementActions: NodeManagementActions = mock(MockMode.autofill)
|
||||
private val nodeRequestActions: NodeRequestActions = mock(MockMode.autofill)
|
||||
@@ -64,7 +64,7 @@ class NodeListViewModelTest {
|
||||
|
||||
every { radioConfigRepository.localConfigFlow } returns MutableStateFlow(org.meshtastic.proto.LocalConfig())
|
||||
every { radioConfigRepository.deviceProfileFlow } returns MutableStateFlow(org.meshtastic.proto.DeviceProfile())
|
||||
every { serviceRepository.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
every { connectionStateProvider.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
|
||||
|
||||
every { nodeFilterPreferences.nodeSortOption } returns MutableStateFlow(NodeSortOption.LAST_HEARD)
|
||||
every { nodeFilterPreferences.includeUnknown } returns MutableStateFlow(true)
|
||||
@@ -83,8 +83,8 @@ class NodeListViewModelTest {
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
nodeRepository = nodeRepository,
|
||||
radioConfigRepository = radioConfigRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
radioController = radioController,
|
||||
connectionStateProvider = connectionStateProvider,
|
||||
adminController = radioController,
|
||||
radioInterfaceService = radioInterfaceService,
|
||||
deviceHardwareRepository = FakeDeviceHardwareRepository(),
|
||||
nodeManagementActions = nodeManagementActions,
|
||||
@@ -120,7 +120,7 @@ class NodeListViewModelTest {
|
||||
@Test
|
||||
fun `connectionState reflects serviceRepository state`() = runTest {
|
||||
val stateFlow = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
|
||||
every { serviceRepository.connectionState } returns stateFlow
|
||||
every { connectionStateProvider.connectionState } returns stateFlow
|
||||
|
||||
val vm = createViewModel()
|
||||
vm.connectionState.test {
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.FileService
|
||||
import org.meshtastic.core.repository.MeshLogRepository
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.TracerouteResponseProvider
|
||||
import org.meshtastic.core.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.feature.node.detail.NodeDetailUiState
|
||||
import org.meshtastic.feature.node.detail.NodeRequestActions
|
||||
@@ -66,7 +66,7 @@ class MetricsViewModelTest {
|
||||
private val dispatchers = CoroutineDispatchers(main = testDispatcher, io = testDispatcher, default = testDispatcher)
|
||||
|
||||
private val meshLogRepository: MeshLogRepository = mock()
|
||||
private val serviceRepository: ServiceRepository = mock()
|
||||
private val tracerouteResponseProvider: TracerouteResponseProvider = mock()
|
||||
private val nodeRepository: NodeRepository = mock()
|
||||
private val tracerouteSnapshotRepository: TracerouteSnapshotRepository = mock()
|
||||
private val nodeRequestActions: NodeRequestActions = mock()
|
||||
@@ -81,7 +81,7 @@ class MetricsViewModelTest {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
|
||||
// Default setup for flows
|
||||
every { serviceRepository.tracerouteResponse } returns MutableStateFlow(null)
|
||||
every { tracerouteResponseProvider.tracerouteResponse } returns MutableStateFlow(null)
|
||||
every { nodeRequestActions.lastTracerouteTime } returns MutableStateFlow(null)
|
||||
every { nodeRequestActions.lastRequestNeighborTimes } returns MutableStateFlow(emptyMap())
|
||||
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(emptyMap())
|
||||
@@ -96,7 +96,7 @@ class MetricsViewModelTest {
|
||||
destNum = destNum,
|
||||
dispatchers = dispatchers,
|
||||
meshLogRepository = meshLogRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
tracerouteResponseProvider = tracerouteResponseProvider,
|
||||
nodeRepository = nodeRepository,
|
||||
tracerouteSnapshotRepository = tracerouteSnapshotRepository,
|
||||
nodeRequestActions = nodeRequestActions,
|
||||
|
||||
Reference in New Issue
Block a user